How can I randomly shuffle a list so that none of the elements remains in its original position?
In other words, given a list A with distinct elements, I'd like to generate a permutation B of it so that
this permutation is random
and for each n, a[n] != b[n]
e.g.
a = [1,2,3,4]
b = [4,1,2,3] # good
b = [4,2,1,3] # good
a = [1,2,3,4]
x = [2,4,3,1] # bad
I don't know the proper term for such a permutation (is it "total"?) thus having a hard time googling. The correct term appears to be "derangement".
After some research I was able to implement the "early refusal" algorithm as described e.g. in this paper [1]. It goes like this:
import random
def random_derangement(n):
while True:
v = [i for i in range(n)]
for j in range(n - 1, -1, -1):
p = random.randint(0, j)
if v[p] == j:
break
else:
v[j], v[p] = v[p], v[j]
else:
if v[0] != 0:
return tuple(v)
The idea is: we keep shuffling the array, once we find that the permutation we're working on is not valid (v[i]==i), we break and start from scratch.
A quick test shows that this algorithm generates all derangements uniformly:
N = 4
# enumerate all derangements for testing
import itertools
counter = {}
for p in itertools.permutations(range(N)):
if all(p[i] != i for i in p):
counter[p] = 0
# make M probes for each derangement
M = 5000
for _ in range(M*len(counter)):
# generate a random derangement
p = random_derangement(N)
# is it really?
assert p in counter
# ok, record it
counter[p] += 1
# the distribution looks uniform
for p, c in sorted(counter.items()):
print p, c
Results:
(1, 0, 3, 2) 4934
(1, 2, 3, 0) 4952
(1, 3, 0, 2) 4980
(2, 0, 3, 1) 5054
(2, 3, 0, 1) 5032
(2, 3, 1, 0) 5053
(3, 0, 1, 2) 4951
(3, 2, 0, 1) 5048
(3, 2, 1, 0) 4996
I choose this algorithm for simplicity, this presentation [2] briefly outlines other ideas.
References:
[1] An analysis of a simple algorithm for random derangements. Merlini, Sprugnoli, Verri. WSPC Proceedings, 2007.
[2] Generating random derangements. Martínez, Panholzer, Prodinger.
Such permutations are called derangements. In practice you can just try random permutations until hitting a derangement, their ratio approaches the inverse of 'e' as 'n' grows.
As a possible starting point, the Fisher-Yates shuffle goes like this.
def swap(xs, a, b):
xs[a], xs[b] = xs[b], xs[a]
def permute(xs):
for a in xrange(len(xs)):
b = random.choice(xrange(a, len(xs)))
swap(xs, a, b)
Perhaps this will do the trick?
def derange(xs):
for a in xrange(len(xs) - 1):
b = random.choice(xrange(a + 1, len(xs) - 1))
swap(xs, a, b)
swap(len(xs) - 1, random.choice(xrange(n - 1))
Here's the version described by Vatine:
def derange(xs):
for a in xrange(1, len(xs)):
b = random.choice(xrange(0, a))
swap(xs, a, b)
return xs
A quick statistical test:
from collections import Counter
def test(n):
derangements = (tuple(derange(range(n))) for _ in xrange(10000))
for k,v in Counter(derangements).iteritems():
print('{} {}').format(k, v)
test(4):
(1, 3, 0, 2) 1665
(2, 0, 3, 1) 1702
(3, 2, 0, 1) 1636
(1, 2, 3, 0) 1632
(3, 0, 1, 2) 1694
(2, 3, 1, 0) 1671
This does appear uniform over its range, and it has the nice property that each element has an equal chance to appear in each allowed slot.
But unfortunately it doesn't include all of the derangements. There are 9 derangements of size 4. (The formula and an example for n=4 are given on the Wikipedia article).
This should work
import random
totalrandom = False
array = [1, 2, 3, 4]
it = 0
while totalrandom == False:
it += 1
shuffledArray = sorted(array, key=lambda k: random.random())
total = 0
for i in array:
if array[i-1] != shuffledArray[i-1]: total += 1
if total == 4:
totalrandom = True
if it > 10*len(array):
print("'Total random' shuffle impossible")
exit()
print(shuffledArray)
Note the variable it which exits the code if too many iterations are called. This accounts for arrays such as [1, 1, 1] or [3]
EDIT
Turns out that if you're using this with large arrays (bigger than 15 or so), it will be CPU intensive. Using a randomly generated 100 element array and upping it to len(array)**3, it takes my Samsung Galaxy S4 a long time to solve.
EDIT 2
After about 1200 seconds (20 minutes), the program ended saying 'Total Random shuffle impossible'. For large arrays, you need a very large number of permutations... Say len(array)**10 or something.
Code:
import random, time
totalrandom = False
array = []
it = 0
for i in range(1, 100):
array.append(random.randint(1, 6))
start = time.time()
while totalrandom == False:
it += 1
shuffledArray = sorted(array, key=lambda k: random.random())
total = 0
for i in array:
if array[i-1] != shuffledArray[i-1]: total += 1
if total == 4:
totalrandom = True
if it > len(array)**3:
end = time.time()
print(end-start)
print("'Total random' shuffle impossible")
exit()
end = time.time()
print(end-start)
print(shuffledArray)
Here is a smaller one, with pythonic syntax -
import random
def derange(s):
d=s[:]
while any([a==b for a,b in zip(d,s)]):random.shuffle(d)
return d
All it does is shuffles the list until there is no element-wise match. Also, be careful that it'll run forever if a list that cannot be deranged is passed.It happens when there are duplicates. To remove duplicates simply call the function like this derange(list(set(my_list_to_be_deranged))).
import random
a=[1,2,3,4]
c=[]
i=0
while i < len(a):
while 1:
k=random.choice(a)
#print k,a[i]
if k==a[i]:
pass
else:
if k not in c:
if i==len(a)-2:
if a[len(a)-1] not in c:
if k==a[len(a)-1]:
c.append(k)
break
else:
c.append(k)
break
else:
c.append(k)
break
i=i+1
print c
A quick way is to try to shuffle your list until you reach that state. You simply try to shuffle your list until you are left with a list that satisfies your condition.
import random
import copy
def is_derangement(l_original, l_proposal):
return all([l_original[i] != item for i, item in enumerate(l_proposal)])
l_original = [1, 2, 3, 4, 5]
l_proposal = copy.copy(l_original)
while not is_derangement(l_original, l_proposal):
random.shuffle(l_proposal)
print(l_proposal)
Related
I'm wanting to create a list of permutations or cartesian products (not sure which one applies here) where the sum of values in each permutation totals to a provided value.
There should be three parameters required for the function.
Sample Size: The number of items in each permutation
Desired Sum: The total that each permutation should add up to
Set of Numbers: The set of numbers that can be included with repetition in the permutations
I have an implementation working below but it seems quite slow I would prefer to use an iterator to stream the results but I would also need a function that would be able to calculate the total number of items that the iterator would produce.
def buildPerms(sample_size, desired_sum, set_of_number):
blank = [0] * sample_size
return recurseBuildPerms([], blank, set_of_number, desired_sum)
def recurseBuildPerms(perms, blank, values, desired_size, search_index = 0):
for i in range(0, len(values)):
for j in range(search_index, len(blank)):
if(blank[j] == 0):
new_blank = blank.copy()
new_blank[j] = values[i]
remainder = desired_size - sum(new_blank)
new_values = list(filter(lambda x: x <= remainder, values))
if(len(new_values) > 0):
recurseBuildPerms(perms, new_blank, new_values, desired_size, j)
elif(sum(new_blank) <= desired_size):
perms.append( new_blank)
return perms
perms = buildPerms(4, 10, [1,2,3])
print(perms)
## Output
[[1, 3, 3, 3], [2, 2, 3, 3], [2, 3, 2, 3],
[2, 3, 3, 2], [3, 1, 3, 3], [3, 2, 2, 3],
[3, 2, 3, 2], [3, 3, 1, 3], [3, 3, 2, 2],
[3, 3, 3, 1]]
https://www.online-python.com/9cmOev3zlg
Questions:
Can someone help me convert my solution into an iterator?
Is it possible to have a calculation to know the total number of items without seeing the full list?
Here is one way to break this down into two subproblems:
Find all restricted integer partitions of target_sum into sample_size summands s.t. all summands come from set_of_number.
Compute multiset permutations for each partition (takes up most of the time).
Problem 1 can be solved with dynamic programming. I used multiset_permutations from sympy for part 2, although you might be able to get better performance by writing your own numba code.
Here is the code:
from functools import lru_cache
from sympy.utilities.iterables import multiset_permutations
#lru_cache(None)
def restricted_partitions(n, k, *xs):
'partitions of n into k summands using only elements in xs (assumed positive integers)'
if n == k == 0:
# case of unique empty partition
return [[]]
elif n <= 0 or k <= 0 or not xs:
# case where no partition is possible
return []
# general case
result = list()
x = xs[0] # element x we consider including in a partition
i = 0 # number of times x should be included
while True:
i += 1
if i > k or x * i > n:
break
for rest in restricted_partitions(n - x * i, k - i, *xs[1:]):
result.append([x] * i + rest)
result.extend(restricted_partitions(n, k, *xs[1:]))
return result
def buildPerms2(sample_size, desired_sum, set_of_number):
for part in restricted_partitions(desired_sum, sample_size, *set_of_number):
yield from multiset_permutations(part)
# %timeit sum(1 for _ in buildPerms2(8, 16, [1, 2, 3, 4])) # 16 ms
# %timeit sum(1 for _ in buildPerms (8, 16, [1, 2, 3, 4])) # 604 ms
The current solution requires computing all restricted partitions before iteration can begin, but it may still be practical if restricted partitions can be computed quickly. It may be possible to compute partitions iteratively as well, although this may require more work.
On the second question, you can indeed count the number of such permutations without generating them all:
# present in the builtin math library for Python 3.8+
#lru_cache(None)
def binomial(n, k):
if k == 0:
return 1
if n == 0:
return 0
return binomial(n - 1, k) + binomial(n - 1, k - 1)
#lru_cache(None)
def perm_counts(n, k, *xs):
if n == k == 0:
# case of unique empty partition
return 1
elif n <= 0 or k <= 0 or not xs:
# case where no partition is possible
return 0
# general case
result = 0
x = xs[0] # element x we consider including in a partition
i = 0 # number of times x should be included
while True:
i += 1
if i > k or x * i > n:
break
result += binomial(k, i) * perm_counts(n - x * i, k - i, *xs[1:])
result += perm_counts(n, k, *xs[1:])
return result
# assert perm_counts(15, 6, *[1,2,3,4]) == sum(1 for _ in buildPerms2(6, 15, [1,2,3,4])) == 580
# perm_counts(1000, 100, *[1,2,4,8,16,32,64])
# 902366143258890463230784240045750280765827746908124462169947051257879292738672
The function used to count all restricted permutations looks very similar to the function that generates partitions above. The only significant change is in the following line:
result += binomial(k, i) * perm_counts(n - x * i, k - i, *xs[1:])
There are i copies of x to include and k possible positions where x's may end up. To account for this multiplicity, the number of ways to resolve the recursive sub-problem is multiplied by k choose i.
there is an issue that i am trying to solve which requires me to generate the indices for an n - dimensional list. Eg: [5, 4, 3] is a 3 dimensional list so valid indices are [0, 0, 0], [0, 0, 1], [0, 1, 0] ... [2, 2, 1] ..... [4, 3, 2]. The best I could come up with a recursive algorithm but this isn't constant space
def f1(dims):
def recur(res, lst, depth, dims):
if depth == len(dims):
res.append(lst[::])
return
curr = dims[depth]
for i in range(curr):
lst[depth] = i
recur(res, lst, depth + 1, dims)
res = []
lst = [0] * len(dims)
recur(res, lst, 0, dims)
return res
the dimensions can be any number , ie: 4D, 5D, 15D etc. Each time it would be given in the form of a list . Eg: 5D would be [3,2,1,5,2] and I would need to generate all the valid indices for these while using constant space ( just while loops and indices processing ) . How would I go about generating these efficiently without the help of any in built python functions ( just while, for loops etc )
This is a working solution in constant space (a single loop variable i, and a vector idx of which modified copies are being yielded, and a single temporary length variable n to avoid calling len() on every iteration):
def all_indices(dimensions):
n = len(dimensions)
idx = [0] * n
while True:
for i in range(n):
yield tuple(idx)
if idx[i] + 1 < dimensions[i]:
idx[i] += 1
break
else:
idx[i] = 0
if not any(idx):
break
print(list(all_indices([3, 2, 1])))
Result:
[(0, 0, 0), (1, 0, 0), (2, 0, 0), (0, 0, 0), (0, 1, 0), (1, 1, 0), (2, 1, 0), (0, 1, 0), (0, 0, 0)]
As pointed out in the comments, there's duplicates there, a bit sloppy, this is cleaner:
def all_indices(dimensions):
n = len(dimensions)
idx = [0] * n
yield tuple(idx) # yield the initial 'all zeroes' state
while True:
for i in range(n):
if idx[i] + 1 < dimensions[i]:
idx[i] += 1
yield tuple(idx) # yield new states
break
else:
idx[i] = 0 # no yield, repeated state
if not any(idx):
break
print(list(all_indices([3, 2, 1])))
Alternatively, you could yield before the break instead of at the start of the loop, but I feel having the 'all zeroes' at the start looks cleaner.
The break is there to force a depth first on running through the indices, which ensures the loop never reaches 'all zeroes' again before having passed all possibilities. Try removing the break and then passing in something like [2, 1, 2] and you'll find it is missing a result.
I think a break is actually the 'clean' way to do it, since it allows using a simple for instead of using a while with a more complicated condition and a separate increment statement. You could do that though:
def all_indices3(dimensions):
n = len(dimensions)
idx = [1] + [0] * (n - 1)
yield tuple([0] * n)
while any(idx):
yield tuple(idx)
i = 0
while i < n and idx[i] + 1 == dimensions[i]:
idx[i] = 0
i += 1 % n
if i < n:
idx[i] += 1
This has the same result, but only uses while, if and yields the results in the same order.
some balls of different colors in a line. When a continuous block of three or more balls of the same color is formed, it is removed from the line. In this case, all the balls are shifted to each other, and the situation may be repeated.
Write a function lines(a) that determines how many balls will be destroyed. There can be at most one continuous block of three or more same-colored balls at the initial moment.
Input data:
The function takes a list a with initial balls disposition. Balls number is less or equals 1000, balls colors can be from 0 to 9, each color has its own integer.
Output data:
The function has to return one number, the number of the balls that will be destroyed.
Input:[2,2,1,1,1,2,1] Output:6
input : [0, 0, 0, 0, 0], output: 5
input:[2, 3, 1, 4], output: 0
I try to use the two pointer approach, but not sure how to do it.
def lines(a):
left = 0
right = len(a)-1
s=[]
while left < right :
if a[left] == a[left:right+1]:
s.extend(a[left: right+1])
del a[left: right+1]
else:
left += 1
right -= 1
return len(s)
test_a = [2, 2, 1, 1, 1, 2, 1]
print(lines(test_a))
I think if a[left] == a[left:right+1]: did not work, i am try to compare elements from left to right is same as elements from right to left. Alsodel a[left: right+1] did not work, i try delete those elements which already extend to new list s.
Thanks for your advice.
This is an iterative solution using a lo and hi pointer traversing the input.
Note that by appending an end marker guaranteed not to be an input colour means that the logic checking for runs of colours does not need duplication outside the while loop.
The code
in_outs = [([2,2,1,1,1,2,1], 6),
([0, 0, 0, 0, 0], 5),
([2, 3, 1, 4], 0),
]
def test(f):
print(f"\nSTART Testing answer {f.__doc__}")
for arg, ans in in_outs:
try:
out = f(arg.copy())
except:
ans = '<Exception thrown!>'
if out != ans:
print(f" {f.__name__}({arg}) != {ans} # instead gives: {out}")
else:
print(f" {f.__name__}({arg}) == {out}")
print(f"STOP Testing answer {f.__doc__}\n")
#%% From me, Paddy3118
def lines(a):
"From Paddy3118"
a = a.copy() + [-1] # Add terminator
d = lo = hi = 0 # delete count, lo & hi pointers
lo_c = hi_c = a[0] # Colours at pointer positions
while hi +1 < len(a):
hi += 1; hi_c = a[hi]
if lo_c != hi_c:
if hi - lo > 2:
d += hi - lo
del a[lo: hi]
lo, hi, lo_c, hi_c = 0, 0, a[0], a[0]
else:
lo, lo_c = hi, hi_c
return d
test(lines)
Output
START Testing answer From Paddy3118
lines([2, 2, 1, 1, 1, 2, 1]) == 6
lines([0, 0, 0, 0, 0]) == 5
lines([2, 3, 1, 4]) == 0
STOP Testing answer From Paddy3118
Checking other examples
Extend the above with the following harnesses to aid testing
#%% IA from Ignacio Alorre
import itertools
def grouping_balls(balls):
return ["".join(g) for k, g in itertools.groupby(balls)]
def destroy_balls(list_balls, destroyed):
if len(list_balls) < 1:
return destroyed
balls_grp = grouping_balls(list_balls)
# No more balls to destroy
if max(map(len,balls_grp)) < 3:
return destroyed
# Destroying and merging balls
else:
non_dest_balls = ""
for l in balls_grp:
if len(l) < 3:
non_dest_balls += l
else:
destroyed += len(l)
return destroy_balls(non_dest_balls, destroyed)
def lines_IA(a):
"From Ignacio Alorre"
return destroy_balls(''.join(map(str, a)), 0)
test(lines_IA)
#%% AHX from Ahx
total_destroyed = 0
counter = 0
previous = -1
previous_index = -1
def _lines_ahx(a):
"""
Args:
a (list): input array
Returns:
(int): total number of destroyed balls.
"""
global total_destroyed
global counter
global previous
global previous_index
for i, element in enumerate(a):
if element == previous:
counter += 1
else:
counter = 1
previous = element
previous_index = i
if counter >= 3:
for _ in range(previous_index, i+1):
a.pop(previous_index)
total_destroyed += 1
_lines_ahx(a)
return total_destroyed
def lines_AHX(a):
"From Ahx"
global total_destroyed
global counter
global previous
global previous_index
total_destroyed = 0
counter = 0
previous = -1
previous_index = -1
return _lines_ahx(a)
test(lines_AHX)
Full Output
All three examples work on the given testds. No timings are given as the tests are very small.
START Testing answer From Paddy3118
lines([2, 2, 1, 1, 1, 2, 1]) == 6
lines([0, 0, 0, 0, 0]) == 5
lines([2, 3, 1, 4]) == 0
STOP Testing answer From Paddy3118
START Testing answer From Ignacio Alorre
lines_IA([2, 2, 1, 1, 1, 2, 1]) == 6
lines_IA([0, 0, 0, 0, 0]) == 5
lines_IA([2, 3, 1, 4]) == 0
STOP Testing answer From Ignacio Alorre
START Testing answer From Ahx
lines_AHX([2, 2, 1, 1, 1, 2, 1]) == 6
lines_AHX([0, 0, 0, 0, 0]) == 5
lines_AHX([2, 3, 1, 4]) == 0
STOP Testing answer From Ahx
You can use a stack for this (simple list implementation). Check if the last 3 balls are of the same kind. If they are, then keep popping all balls of the same kind. Else, add the ball to the stack and continue. Each ball can be added once and removed once to the list, so the complexity should be O(N) as well.
At the end, number of balls destroyed = count of original balls - length of stack.
We need to count the number of occurrences in the input list.
If the current ball color (element) is equals to the previous ball color (previous), then increase the counter by one.
for i, element in enumerate(a):
if element == previous:
counter += 1
If they are not equal, then there will be a possibility the next color may repeat more than three. Therefore store the current colors' index
for i, element in enumerate(a):
if element == previous:
counter += 1
else:
counter = 1
previous = element
previous_index = i
Now check if the color is repeated more than three times.
If it does we need to remove the balls from the list.
While removing we also need to count the number of destroyed balls.
if counter >= 3:
for _ in range(previous_index, i+1):
a.pop(previous_index)
total_destroyed += 1
There might be a confusion, why I did a.pop(previous_index)?
If you debug this part of code on an example. For instance: [2, 2, 1, 1, 1, 2, 1]
When i== 4, the current list is [2, 2, 1, 1, 1] which satisfies the count >= 3
Iteration = 1, the list will become
[2, 2, 1, 1]
If you say remove the next element, which will pop the final element
Iteration = 2, the list will become
[2, 2, 1]
Now, on Iteration 3, which element will be pop? index-out-of-bound.
Iteration = 3, the list won't change
[2, 2, 1]
Therefore, during the iteration always pop the current element. Since the next element will be the current one.
Now we need to call the method again, to see if there is any balls left
if counter >= 3:
for _ in range(previous_index, i+1):
a.pop(previous_index)
total_destroyed += 1
lines(a)
But, we have to be careful since we have declared previous, previous_index, counter and totally_destroyed as a local variable.
If we keep them as a local attributes, all variables will be re-iniitalized so the alogrithm result won't be true.
Therefore we have to initialize them as a global variable and return the total number of destroyed balls.
Code:
total_destroyed = 0
counter = 0
previous = -1
previous_index = -1
def lines(a):
"""
Args:
a (list): input array
Returns:
(int): total number of destroyed balls.
"""
global total_destroyed
global counter
global previous
global previous_index
for i, element in enumerate(a):
if element == previous:
counter += 1
else:
counter = 1
previous = element
previous_index = i
if counter >= 3:
for _ in range(previous_index, i+1):
a.pop(previous_index)
total_destroyed += 1
lines(a)
return total_destroyed
test_a = [2, 2, 1, 1, 1, 2, 1]
test_b = [0, 0, 0, 0, 0]
test_c = [2, 3, 1, 4]
print(lines(test_a))
total_destroyed = 0
counter = 0
previous = -1
previous_index = -1
print(lines(test_b))
total_destroyed = 0
counter = 0
previous = -1
previous_index = -1
print(lines(test_c))
Results:
6
5
0
My solution has two parts. One method to group by balls by their number grouping_balls. Then a recursive method first check there are still groups bigger than 3, if so destroy them and merge the rest for the next iteration destroy_balls.
import itertools
# Split balls by type
# For 2211121 you would get: ["22", "111", "2", "1"]
def grouping_balls(balls):
return ["".join(g) for k, g in itertools.groupby(balls)]
# Keeps destroying balls as long as there are groups of 3 or more
def destroy_balls(list_balls, destroyed):
if len(list_balls) < 1:
return destroyed
balls_grp = grouping_balls(list_balls)
# No more balls to destroy
if max(map(len,balls_grp)) < 3:
return destroyed
# Destroying and merging balls
else:
non_dest_balls = ""
for l in balls_grp:
if len(l) < 3:
non_dest_balls += l
else:
destroyed += len(l)
return destroy_balls(non_dest_balls, destroyed)
Input = [0,0,0,0,0]
destroy_balls(''.join(map(str,Input)), 0)
I thought of another algorithm that first does a run-length encoding of the whole input before using a single pointer traversing the list and deleting stuff. It works a lot better on larger input.
To test it I wrote a routine downto_zero that generates sequences of ones and zeroes which are all deleted, eventually.
I follow up by doing a timed run of all the examples.
Note: run in Ipython shell of Spyder IDE for timings.
Note: the example for Ahx makes great use of recursion and fails with an input of length 12, let alone 7500.
RLE code and Test Generator
#%% Test Generator for long tests
in_outs = [([2,2,1,1,1,2,1], 6),
([0, 0, 0, 0, 0], 5),
([2, 3, 1, 4], 0),
]
import sys
def test(f, in_outs=in_outs):
print(f"\nSTART Testing answer {f.__doc__}")
for arg, ans in in_outs:
arg_txt = arg if len(arg) <= 8 else f"[<{len(arg)} terms>]"
try:
out = f(arg.copy())
except:
out = f'<Exception thrown! {sys.exc_info()[0]}>'
if out != ans:
print(f" {f.__name__}({arg_txt}) != {ans} # instead gives: {out}")
else:
print(f" {f.__name__}({arg_txt}) == {out}")
print(f"STOP Testing answer {f.__doc__}\n")
def downto_zero(n=3):
"Test generator that reduces all input"
if n == 0:
return []
x = ([0, 1] * ((n+1)//2))[:n] # 0,1 ... of length n
e = x[-1] # end item
ne = 0 if e == 1 else 1 # not end item
r = ([e, e, ne, ne] * n)[:2*n]
return x + r
#%% RLE Runlengh encoded from me, Paddy
from itertools import groupby
def lines_RLE(a):
"From Paddy3118 using run-length encoding"
a = a.copy() + [-1] # Add terminator
a = [[key, len(list(group))] for key, group in groupby(a)] # RLE
d = pt = 0 # delete count, pointer
while pt +1 < len(a):
i0, n0 = a[pt] # item, count at pt
if n0 > 2:
d += n0
del a[pt]
if pt > 0:
if a[pt - 1][0] == a[pt][0]: # consolidate
a[pt - 1][1] += a[pt][1]
del a[pt]
pt -= 1
continue
else:
pt += 1
return d
test(lines_RLE, in_outs)
#%% Timed testing
print("TIMED TESTING\n=============")
n = 2_500
for f in (lines, lines_RLE, lines_IA, lines_AHX):
dn = downto_zero(n)
%time test(f, [(dn, len(dn))])
Output with timings
START Testing answer From Paddy3118 using run-length encoding
lines_RLE([2, 2, 1, 1, 1, 2, 1]) == 6
lines_RLE([0, 0, 0, 0, 0]) == 5
lines_RLE([2, 3, 1, 4]) == 0
STOP Testing answer From Paddy3118 using run-length encoding
TIMED TESTING
=============
START Testing answer From Paddy3118
lines([<7500 terms>]) == 7500
STOP Testing answer From Paddy3118
Wall time: 2.44 s
START Testing answer From Paddy3118 using run-length encoding
lines_RLE([<7500 terms>]) == 7500
STOP Testing answer From Paddy3118 using run-length encoding
Wall time: 19 ms
START Testing answer From Ignacio Alorre
lines_IA([<7500 terms>]) == 7500
STOP Testing answer From Ignacio Alorre
Wall time: 10.9 s
START Testing answer From Ahx
lines_AHX([<7500 terms>]) != 7500 # instead gives: <Exception thrown! <class 'RecursionError'>>
STOP Testing answer From Ahx
Wall time: 16 ms
My third, and last answer builds on my second RLE entry by swapping to use a doubly linked list data structure to remove what can be costly array delete operations. The new code looks like it has better big-O characteristics compared to my other two solutions so may proove even faster for larger inputs.
Doubly-Linked list code:
#%% LL Linked-list and Runlengh encoded from me, Paddy
from itertools import groupby
def lines_LL(a):
"From Paddy3118 using a linked-list and run-length encoding"
a = a.copy() + [-1] # Add terminator
# a is list of [item, reps, prev_pointer, next_pointer]
a = [[key, len(list(group)), i - 1, i + 1]
for i, (key, group) in enumerate(groupby(a))] # linke-list RLE
a[0][-2] = None # No previous item
a[-1][-1] = None # No next item
d = pt = 0 # delete count, pointer
while pt is not None:
i0, n0, pre_pt, nxt_pt = a[pt] # item, count, next at pt
if n0 > 2:
d += n0
deleted_pt = pt
if pre_pt is not None:
a[pre_pt][-1] = pt = nxt_pt # del a[pt] & pt to next
if a[pre_pt][0] == a[pt][0]: # consolidate same items in pre
a[pre_pt][1] += a[pt][1]
a[pre_pt][-1] = a[pt][-1] # del a[pt]
pt = pre_pt # ... & pt to previous
else:
pt = nxt_pt
else:
pt = nxt_pt
return d
print("SIMPLE FUNCTIONAL TESTING\n=============")
test(lines_LL, in_outs)
#%% Timed testing
if 1:
print("TIMED TESTING\n=============")
n = 20_000
for f in (lines_LL, lines_RLE, lines): #, lines_IA, lines_AHX):
dn = downto_zero(n)
%time test(f, [(dn, len(dn))])
Output and timings
SIMPLE FUNCTIONAL TESTING
=============
START Testing answer From Paddy3118 using a linked-list and run-length encoding
lines_LL([2, 2, 1, 1, 1, 2, 1]) == 6
lines_LL([0, 0, 0, 0, 0]) == 5
lines_LL([2, 3, 1, 4]) == 0
STOP Testing answer From Paddy3118 using a linked-list and run-length encoding
TIMED TESTING
=============
START Testing answer From Paddy3118 using a linked-list and run-length encoding
lines_LL([<60000 terms>]) == 60000
STOP Testing answer From Paddy3118 using a linked-list and run-length encoding
Wall time: 104 ms
START Testing answer From Paddy3118 using run-length encoding
lines_RLE([<60000 terms>]) == 60000
STOP Testing answer From Paddy3118 using run-length encoding
Wall time: 387 ms
START Testing answer From Paddy3118
lines([<60000 terms>]) == 60000
STOP Testing answer From Paddy3118
Wall time: 2min 31s
Instructions : rat can move just up or right
input:
The first line contains the number of table size n and the number of cheese m.
From the next line, the position x, y of the cheese is given
Output :
The maximum number of cheese to eat
Exemple1:
input : 1 1 1
output : 1 1
Example2:
input :
3 2
1 2
3 1
output : 1
Example 3:
input :
5 5
2 3
3 2
4 3
4 5
5 2
output: 3
how can I solve with python?
I tried
def maxAverageOfPath(table, N):
dp = [[0 for i in range(N)] for j in range(N)]
dp[0][0] = table[0][0]
# Initialize first column of total table(dp) array
for i in range(0, N):
dp[i][0] = 0
for j in range(0, N):
dp[0][j] = 0
for i in range(0, N):
for j in range(0, N):
print(i, j)
if i == N-1 and j == N-1:
dp[i][j] = max(dp[i - 1][j], dp[i][j - 1])
continue
if i == N-1 :
dp[i][j] = table[i][j + 1]
continue
if j == N-1 :
dp[i][j] = table[i + 1][j]
continue
dp[i][j] = max(table[i + 1][j], table[i][j + 1])
return dp
but failed...
For dynamic programming you want an edge condition(s) and a way to score where you are right now. After that it's more-or-less smart brute force. The smart part comes from memoizing so you don't repeat work.
Here's a basic recursive approach for python that does the following:
Organize table of cheese as a frozen set of tuples. This can be hashed for memoization and you can determine of a location is in the set in constant time.
Creates an edge condition for the end (when both coordinates are N) and an edge condition for when you walk off the map -- that just returns 0.
Uses lru_cache to memoize. You can implement this yourself easily.
from functools import lru_cache
def hasCheese(table, location):
''' Helper function just to improve readability '''
return 1 if location in table else 0
#lru_cache()
def maxC(table, N, location = (0, 0)):
# edge conditions final square and off the grid:
if location[0] == N and location[1] == N:
return hasCheese(table, location)
if any(l > N for l in location):
return 0
# recursion
score_here = hasCheese(table, location)
return max(score_here + maxC(table, N, (location[0] + 1, location[1])),
score_here + maxC(table, N, (location[0], location[1] + 1))
)
t = frozenset([(2, 3), (3, 2), (4, 3), (4, 5), (5, 2)])
N = 5
print(maxC(t, N))
# prints 3
If you want to do this in a top-down manner using a matrix, you need to be very careful that you always have the previous index set. It's easier to make mistakes doing it this way because you need to get the indexes and order just right. When you set it up as two nested increasing loops, that means the next value is always the current cell plus the max of the two cells one unit less — you should always be looking backward in the matrix. It's not clear what you are trying to do when you are looking forward with this:
dp[i][j] = table[i][j + 1]
because j+1 has not been determined yet.
Since the cheese coordinates are 1 indexed, an easy way forward is to make your matrix zero indexed and N+1 in size. Then when you start your for loops at 1 you can always look and at lower index without undershooting the matrix and avoid a lot of the if/else logic. For example:
def hasCheese(table, location):
''' Helper function just to improve readability '''
return 1 if location in table else 0
def maxAverageOfPath(table, N):
# matrix is sized one unit bigger
dp = [[0 for i in range(N+1)] for j in range(N+1)]
# iterate 1-5 inclusive
for i in range(1, N+1):
for j in range(1, N+1):
# because the zeroth row and column are already zero, this works without undershooting the table
dp[i][j] = hasCheese(table, (i, j)) + max(dp[i][j-1], dp[i-1][j])
# solution is in the corner
return dp[N][N]
t = {(2, 3), (3, 2), (4, 3), (4, 5), (5, 2)}
N = 5
print(maxAverageOfPath(t, N)) #3
When you'r done your matrix will look like:
[0, 0, 0, 0, 0, 0]
[0, 0, 0, 0, 0, 0]
[0, 0, 0, 1, 1, 1]
[0, 0, 1, 1, 1, 1]
[0, 0, 1, 2, 2, 3]
[0, 0, 2, 2, 2, 3]
Your starting point is at (1, 1) starting in the top-right and your answer is the bottom left corner.
At each point you have two options to move further :
array [row] [col+1]
array [row+1] [col]
As we have to find out a path which involves max cheese.
It can be achived by recurring through the array like below to solve the same:
Solution =>
array [i] [j] + Max(Recur(array [i] [j+1]), Recur(array [i+1] [j]));
I want to create an infinite loop that counts up and down from 0 to 100 to 0 (and so on) and only stops when some convergence criterion inside the loop is met, so basically something like this:
for i in range(0, infinity):
for j in range(0, 100, 1):
print(j) # (in my case 100 lines of code)
for j in range(100, 0, -1):
print(j) # (same 100 lines of code as above)
Is there any way to merge the two for loops over j into one so that I don't have write out the same code inside the loops twice?
Use the chain method of itertools
import itertools
for i in range(0, infinity):
for j in itertools.chain(range(0, 100, 1), range(100, 0, -1)):
print(j) # (in my case 100 lines of code)
As suggested by #Chepner, you can use itertools.cycle() for the infinite loop:
from itertools import cycle, chain
for i in cycle(chain(range(0, 100, 1), range(100, 0, -1))):
....
As well as the other answers you can use a bit of maths:
while(True):
for i in range(200):
if i > 100:
i = 200 - i
Here's yet another possibility:
while notConverged:
for i in xrange(-100, 101):
print 100 - abs(i)
If you've got a repeated set of code, use a function to save space and effort:
def function(x, y, x, num_from_for_loop):
# 100 lines of code
while not condition:
for i in range(1, 101):
if condition:
break
function(x, y, z, i)
for i in range(100, 0, -1):
if condition:
break
function(x, y, z, i)
You could even use a while True
If you're using Python 3.5+, you can using generic unpacking:
for j in (*range(0, 100, 1), *range(100, 0, -1)):
or prior to Python 3.5, you can use itertools.chain:
from itertools import chain
...
for j in chain(range(0, 100, 1), range(100, 0, -1)):
up = True # since we want to go from 0 to 100 first
while True: #for infinite loop
# For up == True we will print 0-->100 (0,100,1)
# For up == False we will print 100-->0 (100,0,-1)
start,stop,step = (0,100,1) if up else (100,0,-1)
for i in range(start,stop,step):
print(i)
up = not up # if we have just printed from 0-->100 (ie up==True), we want to print 100-->0 next so make up False ie up = not up( True)
# up will help toggle, between 0-->100 and 100-->0
def up_down(lowest_value, highest_value):
current = lowest_value
delta = 1
while True: # Begin infinite loop
yield current
current += delta
if current <= lowest_value or current >= highest_value:
delta *= -1 # Turn around when either limit is hit
This defines a generator, which will continue to yield values for as long as you need. For example:
>>> u = up_down(0, 10)
>>> count = 0
>>> for j in u:
print(j) # for demonstration purposes
count += 1 # your other 100 lines of code here
if count >= 25: # your ending condition here
break
0
1
2
3
4
5
6
7
8
9
10
9
8
7
6
5
4
3
2
1
0
1
2
3
4
I became curious if it's possible to implement such kind of triangle oscillator without conditions and enumerations. Well, one option is the following:
def oscillator(magnitude):
i = 0
x = y = -1
double_magnitude = magnitude + magnitude
while True:
yield i
x = (x + 1) * (1 - (x // (double_magnitude - 1))) # instead of (x + 1) % double_magnitude
y = (y + 1) * (1 - (y // (magnitude - 1))) # instead of (y + 1) % magnitude
difference = x - y # difference ∈ {0, magnitude}
derivative = (-1 * (difference > 0) + 1 * (difference == 0))
i += derivative
The idea behind this is to take 2 sawtooth waves with different periods and subtract one from another. The result will be a square wave with values in {0, magnitude}. Then we just substitute {0, magnitude} with {-1, +1} respectively to get derivative values for our target signal.
Let's look at example with magnitude = 5:
o = oscillator(5)
[next(o) for _ in range(21)]
This outputs [0, 1, 2, 3, 4, 5, 4, 3, 2, 1, 0, 1, 2, 3, 4, 5, 4, 3, 2, 1, 0].
If abs() is allowed, it can be used for simplicity. For example, the following code gives the same output as above:
[abs(5 - ((x + 5) % 10)) for x in range(21)]
This is more of a partial answer than a direct answer to your question, but you can also use the notion of trigonometric functions and their oscillation to imitate a 'back and forth' loop.
If we have a cos function with an amplitude of 100, shifted left and upwards so that f(x) = 0 and 0 <= f(x) <= 100, we then have the formula f(x) = 50(cos(x-pi)+1) (plot of graph may be found here. The range is what you require, and oscillation occurs so there's no need to negate any values.
>>> from math import cos, pi
>>> f = lambda x: 50*(cos(x-pi)+1)
>>> f(0)
0.0
>>> f(pi/2)
50.0
>>> f(pi)
100.0
>>> f(3*pi/2)
50.0
>>> f(2*pi)
0.0
The issue of course comes in that the function doesn't give integer values so easily, thus it's not that helpful - but this may be useful for future readers where trigonometric functions might be helpful for their case.
I had a similar problem a while ago where I also wanted to create values in the form of an infinite triangle wave, but wanted to step over some values. I ended up using a generator (and the range function as other also have been using):
def tri_wave(min, max, step=1):
while True:
yield from range(min, max, step)
yield from range(max, min, -1 * step)
With carefully selected values on min, max and step (i.e. evenly divisible),
for value in tri_wave(0, 8, 2):
print(value, end=", ")
I get the min and max value only once, which was my goal:
...0, 2, 4, 6, 8, 6, 4, 2, 0, 2, 4, 6, 8, 6, 4...
I was using Python 3.6 at the time.
Here is a simple and straightforward solution that does not require any imports:
index = 0
constant = 100
while True:
print(index)
if index == constant:
step = -1
elif index == 0:
step = 1
index += step