Find ways to get to a number of levels - python

I got this problem on CoderByte. The requirement was to find a number of ways. I found solutions for that in StackOverflow and other sites. But moving ahead, I need all possible ways as well to reach the Nth step.
Problem description: There is a staircase of N steps and you can climb either 1 or 2 steps at a time. You need to count and return the total number of unique ways to climb the staircase. The order of steps taken matters.
For Example,
Input: N = 3
Output: 3
Explanation: There are 3 unique ways of climbing a staircase of 3 steps :{1,1,1}, {2,1} and {1,2}
Note: There might be another case that a person can take 2 or 3 or 4 steps at a time (I know that's realistically not possible but trying to add scalability to the input steps in the code)
I'm unable to find the right logic to get all the ways possible. It's useful if I get the solution in Python, but it's not a strict requirement though.

Here's a minimal solution using itertools library:
from itertools import permutations, chain
solve = lambda n: [(1,)*n] + list(set(chain(*[permutations((2,)*i + (1,)*(n-2*i)) for i in range(1, n//2+1)])))
For your example input:
> solve(3)
[(1, 1, 1), (1, 2), (2, 1)]
How it works?
It's easier to see what's happening if we take a step backwards:
def solve(n):
combinations = [(1,)*n]
for i in range(1, n//2+1):
combinations.extend(permutations((2,)*i + (1,)*(n-2*i)))
return list(set(combinations))
The most trivial case is the one where you take one step at a time, so n steps: (1,)*n. Then we can look for how many double steps could we take at most, and that's the floor of n divided by 2: n//2. Then we iterate over the possible double steps: try to add a double step each iteration (2,)*i, filling the remaining space with single steps (1,)*(n-2*i).
The function permutations from itertools will generate all the possible permutations of single and double steps for that iteration. With an input of (1,1,2), it will generate (1,1,2), (1,2,1) and (2,1,1). At the end we use the trick of converting the result to a set in order to remove duplicates, then converting it back into a list.
Generalization for any amount and length of steps (not optimal!)
One liner:
from itertools import permutations, chain, combinations_with_replacement
solve = lambda n, steps: list(set(chain(*[permutations(sequence) for sequence in chain(*[combinations_with_replacement(steps, r) for r in range(n//min(steps)+1)]) if sum(sequence) == n])))
Example output:
> solve(8, [2,3])
[(3, 2, 3), (2, 3, 3), (2, 2, 2, 2), (3, 3, 2)]
Easier to read version:
def solve(n, steps):
result = []
for sequence_length in range(n//min(steps)+1):
sequences = combinations_with_replacement(steps, sequence_length)
for sequence in sequences:
if sum(sequence) == n:
result.extend(permutations(sequence))
return list(set(result))

def solve(n) :
if (n == 0):
return [[]]
else:
left_results = []
right_results = []
if (n > 0):
left_results = solve(n - 1)
for res in left_results: # Add the current step to every result
res.append(1)
if (n > 1):
right_results = solve(n - 2)
for res in right_results: # Same above
res.append(2)
return left_results + right_results
I think there is a better way to do this using dynamic programming but I don't know how to do that. Hope it helps anyway.

Related

Efficient enumeration of non-negative integer composition

I would like to write a function my_func(n,l) that, for some positive integer n, efficiently enumerates the ordered non-negative integer composition* of length l (where l is greater than n). For example, I want my_func(2,3) to return [[0,0,2],[0,2,0],[2,0,0],[1,1,0],[1,0,1],[0,1,1]].
My initial idea was to use existing code for positive integer partitions (e.g. accel_asc() from this post), extend the positive integer partitions by a couple zeros and return all permutations.
def my_func(n, l):
for ip in accel_asc(n):
nic = numpy.zeros(l, dtype=int)
nic[:len(ip)] = ip
for p in itertools.permutations(nic):
yield p
The output of this function is wrong, because every non-negative integer composition in which a number appears twice (or multiple times) appears several times in the output of my_func. For example, list(my_func(2,3)) returns [(1, 1, 0), (1, 0, 1), (1, 1, 0), (1, 0, 1), (0, 1, 1), (0, 1, 1), (2, 0, 0), (2, 0, 0), (0, 2, 0), (0, 0, 2), (0, 2, 0), (0, 0, 2)].
I could correct this by generating a list of all non-negative integer compositions, removing repeated entries, and then returning a remaining list (instead of a generator). But this seems incredibly inefficient and will likely run into memory issues. What is a better way to fix this?
EDIT
I did a quick comparison of the solutions offered in answers to this post and to another post that cglacet has pointed out in the comments.
On the left, we have the l=2*n and on the right we have l=n+1. In these two cases, user2357112's second solutions is faster than the others, when n<=5. For n>5, solutions proposed by user2357112, Nathan Verzemnieks, and AndyP are more or less tied. But the conclusions could be different when considering other relationships between l and n.
..........
*I originally asked for non-negative integer partitions. Joseph Wood correctly pointed out that I am in fact looking for integer compositions, because the order of numbers in a sequence matters to me.
Use the stars and bars concept: pick positions to place l-1 bars between n stars, and count how many stars end up in each section:
import itertools
def diff(seq):
return [seq[i+1] - seq[i] for i in range(len(seq)-1)]
def generator(n, l):
for combination in itertools.combinations_with_replacement(range(n+1), l-1):
yield [combination[0]] + diff(combination) + [n-combination[-1]]
I've used combinations_with_replacement instead of combinations here, so the index handling is a bit different from what you'd need with combinations. The code with combinations would more closely match a standard treatment of stars and bars.
Alternatively, a different way to use combinations_with_replacement: start with a list of l zeros, pick n positions with replacement from l possible positions, and add 1 to each of the chosen positions to produce an output:
def generator2(n, l):
for combination in itertools.combinations_with_replacement(range(l), n):
output = [0]*l
for i in combination:
output[i] += 1
yield output
Starting from a simple recursive solution, which has the same problem as yours:
def nn_partitions(n, l):
if n == 0:
yield [0] * l
else:
for part in nn_partitions(n - 1, l):
for i in range(l):
new = list(part)
new[i] += 1
yield new
That is, for each partition for the next lower number, for each place in that partition, add 1 to the element in that place. It yields the same duplicates yours does. I remembered a trick for a similar problem, though: when you alter a partition p for n into one for n+1, fix all the elements of p to the left of the element you increase. That is, keep track of where p was modified, and never modify any of p's "descendants" to the left of that. Here's the code for that:
def _nn_partitions(n, l):
if n == 0:
yield [0] * l, 0
else:
for part, start in _nn_partitions(n - 1, l):
for i in range(start, l):
new = list(part)
new[i] += 1
yield new, i
def nn_partitions(n, l):
for part, _ in _nn_partitions(n, l):
yield part
It's very similar - there's just the extra parameter passed along at each step, so I added wrapper to remove that for the caller.
I haven't tested it extensively, but this appears to be reasonably fast - about 35 microseconds for nn_partitions(3, 5) and about 18s for nn_partitions(10, 20) (which yields just over 20 million partitions). (The very elegant solution from user2357112 takes about twice as long for the smaller case and about four times as long for the larger one. Edit: this refers to the first solution from that answer; the second one is faster than mine under some circumstances and slower under others.)

Python Get Random Unique N Pairs

Say I have a range(1, n + 1). I want to get m unique pairs.
What I found is, if the number of pairs is close to n(n-1)/2 (maxiumum number of pairs), one can't simply generate random pairs everytime because they will start overriding eachother. I'm looking for a somewhat lazy solution, that will be very efficient (in Python's world).
My attempt so far:
def get_input(n, m):
res = str(n) + "\n" + str(m) + "\n"
buffet = range(1, n + 1)
points = set()
while len(points) < m:
x, y = random.sample(buffet, 2)
points.add((x, y)) if x > y else points.add((y, x)) # meeh
for (x, y) in points:
res += "%d %d\n" % (x, y);
return res
You can use combinations to generate all pairs and use sample to choose randomly. Admittedly only lazy in the "not much to type" sense, and not in the use a generator not a list sense :-)
from itertools import combinations
from random import sample
n = 100
sample(list(combinations(range(1,n),2)),5)
If you want to improve performance you can make it lazy by studying this
Python random sample with a generator / iterable / iterator
the generator you want to sample from is this: combinations(range(1,n)
Here is an approach which works by taking a number in the range 0 to n*(n-1)/2 - 1 and decodes it to a unique pair of items in the range 0 to n-1. I used 0-based math for convenience, but you could of course add 1 to all of the returned pairs if you want:
import math
import random
def decode(i):
k = math.floor((1+math.sqrt(1+8*i))/2)
return k,i-k*(k-1)//2
def rand_pair(n):
return decode(random.randrange(n*(n-1)//2))
def rand_pairs(n,m):
return [decode(i) for i in random.sample(range(n*(n-1)//2),m)]
For example:
>>> >>> rand_pairs(5,8)
[(2, 1), (3, 1), (4, 2), (2, 0), (3, 2), (4, 1), (1, 0), (4, 0)]
The math is hard to easily explain, but the k in the definition of decode is obtained by solving a quadratic equation which gives the number of triangular numbers which are <= i, and where i falls in the sequence of triangular numbers tells you how to decode a unique pair from it. The interesting thing about this decode is that it doesn't use n at all but implements a one-to-one correspondence from the set of natural numbers (starting at 0) to the set of all pairs of natural numbers.
I don't think any thing on your line can improve. After all, as your m get closer and closer to the limit n(n-1)/2, you have thinner and thinner chance to find the unseen pair.
I would suggest to split into two cases: if m is small, use your random approach. But if m is large enough, try
pairs = list(itertools.combination(buffet,2))
ponits = random.sample(pairs, m)
Now you have to determine the threshold of m that determines which code path it should go. You need some math here to find the right trade off.

Generate all combinations of success sets in Python

There are k treatments and N total tests to distribute among the treatments, which is called a plan. For a fixed plan, I want to output in Python all the possible success sets.
Question:
For example, if doctors are testing headache medicine, if k=2 types of treatments (i.e. Aspirin and Ibuprofen) and N=3 total tests, one plan could be (1 test for Aspirin, 2 tests for Ibuprofen). For that plan, how do I output all possible combinations of 0-1 successful tests of Aspirin and 0-2 successful tests for Ibuprofen? One successful test means that when a patient with a headache is given Aspirin, the Aspirin cures their headache.
Please post an answer with python code, NOT a math answer.
Desired output is a list w/n a list that has [# successes for treatment 1, # successes of treatment 2]:
[ [0,0], [0,1], [0,2], [1,0], [1,1], [1,2] ]
It would be great if yield could be used because the list above could be really long and I don't want to store the whole list in memory, which would increase computation time.
Below I have the code for enumerating all possible combinations of N balls in A boxes, which should be similar to creating all possible success sets I think, but I'm not sure how.
Code
#Return list of tuples of all possible plans (n1,..,nk), where N = total # of tests = balls, K = # of treatments = boxes
#Code: Glyph, http://stackoverflow.com/questions/996004/enumeration-of-combinations-of-n-balls-in-a-boxes
def ballsAndBoxes(balls, boxes, boxIndex=0, sumThusFar=0):
if boxIndex < (boxes - 1):
for counter in range(balls + 1 - sumThusFar):
for rest in ballsAndBoxes(balls, boxes,
boxIndex + 1,
sumThusFar + counter):
yield (counter,) + rest
else:
yield (balls - sumThusFar,)
Generating the plans is a partition problem, but generating the success sets for a given plan only requires generating the Cartesian product of a set of ranges.
from itertools import product
def success_sets(plan):
return product(*map(lambda n: range(n + 1), plan))
plan = [1, 2]
for s in success_sets(plan):
print(s)
# (0, 0), (0, 1), (0, 2), (1, 0), (1, 1), (1, 2)
Since itertools.product returns a generator, the entire list will not be stored in memory as requested.
I am not sure exactly what you're trying to achieve. But combinations can be generated using itertools.
from itertools import combinations
#You can add an extra loop for all treatments
for j in range(1, N): #N is number of tests
for i in combinations(tests, r = j):
indexes = set(i)
df_cur = tests[indexes] #for tests i am using a pandas df
if :# condition for success
#actions
else:
#other_actions

iterate through all L length sequences of N symbols, that include all N symbols

What is an efficient way to do the following in python?
Given N symbols, iterate through all L length sequences of N symbols, that include all N symbols.
The order does not matter, as long as all sequences are covered, and each only once.
Let's call this iterator seq(symbols,L). Then, for example,
list(seq([1,2,3],2))=[]
list(seq([1,2,3],3))=[(1, 2, 3), (1, 3, 2), (2, 1, 3), (2, 3, 1), (3, 1, 2), (3, 2, 1)]
list(seq([1,2,3],4))=[(1, 1, 2, 3), (1, 1, 3, 2), (1, 2, 1, 3), ...
Here's an intuitive, yet slow implementation:
import itertools
def seq(symbols,L):
for x in itertools.product(symbols,repeat=L):
if all(s in x for s in symbols):
yield x
When N is large and L is close to N, there is a lot of wasted effort. For example, when L==N, it would be much better to use itertools.permutations(). Since every sequence needs to have all N symbols, it seems like a better solution would somehow start with the permuted solution, then add in the extra repeated symbols somehow, but I can't figure out how to do this without double counting (and without resorting to saving all previous output to check for a repeat).
An idea:
import itertools
def solve(size, symbols, todo = None):
if todo is None: todo = frozenset(symbols)
if size < len(todo): return
if size == len(todo):
yield from itertools.permutations(todo) # use sorted(todo) here
# for lexicographical order
return
for s in symbols:
for xs in solve(size - 1, symbols, todo - frozenset((s,))):
yield (s,) + xs
for x in solve(5, (1,2,3)):
print(x)
Will print all sequences of size 5 that contain each of 1,2,3 and 2 more arbitrary elements. You can use bitmasks instead of a set if you aim for efficiency, but I guess you're not since you are using Python :) The complexity is optimal in the sense that it is linear in the output size.
Some "proof":
$ python3 test.py | wc -l # number of output lines
150
$ python3 test.py | sort | uniq | wc -l # unique output lines
150
$ python3 test.py | grep "1"|grep "2"|grep "3"| wc -l # lines with 1,2,3
150
You can do this by breaking the problem into two parts:
Find every possible multiset of size L of N symbols which includes every symbol at least once.
For each multiset, find all unique permutations.
For simplicity, let's suppose the N symbols are the integers in range(N). Then we can represent a multiset as a vector of length N whose values are non-negative integers summing to L. To restrict the multiset to include every symbol at least once, we require that the values in the vector all be strictly positive.
def msets(L, N):
if L == N:
yield (1,) * L
elif N == 1:
yield (L,)
elif N > 0:
for i in range(L - N + 1):
for m in msets(L - i - 1, N - 1):
yield (i + 1,) + m
Unfortunately, itertools.permutations does not produce unique iterations of lists with repeating elements. If we were writing this in C++, we could use std::next_permutation, which does produce unique iterations. There is a sample implementation (in C++, but it's straightforward to convert it to Python) on the linked page.

How to rewrite a recursive function to use a loop instead?

This stack overflow thread claims that every recursive function can be written as a loop.
Which recursive functions cannot be rewritten using loops?
It makes complete sense. But I'm not sure how to express the following recursive function as a loop because it has a pre recursive piece of logic and a post recursive piece of logic.
Obviously the solution cannot use the goto statement. The code is here:
def gen_perms(lst, k, m):
if k == m:
all_perms.append(list(lst))
else:
for i in xrange(k, m+1):
#swap char
tmp = lst[k]
lst[k] = lst[i]
lst[i] = tmp
gen_perms(lst, k+1, m)
#swap char
tmp = lst[k]
lst[k] = lst[i]
lst[i] = tmp
Invoking it would be like this:
all_perms = []
gen_perm([1, 2, 3], 0, 2)
and it generates every permutation of the list 1,2,3.
The most pythonic way of doing permutations is to use:
>>> from itertools import permutations
>>> permutations([1,2,3])
>>> list(permutations([1,2,3]))
[(1, 2, 3), (1, 3, 2), (2, 1, 3), (2, 3, 1), (3, 1, 2), (3, 2, 1)]
Let's say you want to find all permutations of [1, 2, 3, 4]. There are 24 (=4!) of these, so number them 0-23. What we want is a non-recursive way to find the Nth permutation.
Let's say we sort the permutations in increasing numerical order. Then:
Permutations 0-5 start with 1
Permutations 6-11 start with 2
Permutations 12-17 start with 3
Permutations 18-23 start with 4
So we can get the first number of permutation N by dividing N by 6 (=3!), and rounding up.
How do we get the next number? Look at the second numbers in permutations 0-5:
Permutations 0-1 have second number 2.
Permutations 2-3 have second number 3.
Permutations 4-5 have second number 4.
We see a similar thing with permutations 6-11:
Permutations 6-7 have second number 1.
Permutations 8-9 have second number 3.
Permutations 10-11 have second number 4.
In general, take the remainder after dividing by 6 earlier, divide that by 2 (=2!), and round up. That gives you 1, 2, or 3, and the second item is the 1st, 2nd or 3rd item left in the list (after you've taken out the first item).
You can keep going in this way. Here's some code that does this:
from math import factorial
def gen_perms(lst):
all_perms = []
# Find the number of permutations.
num_perms = factorial(len(lst))
for i in range(num_perms):
# Generate the ith permutation.
perm = []
remainder = i
# Clone the list so we can remove items from it as we
# add them to our permutation.
items = lst[:]
# Pick out each item in turn.
for j in range(len(lst) - 1):
# Divide the remainder at the previous step by the
# next factorial down, to get the item number.
divisor = factorial(len(lst) - j - 1)
item_num = remainder / divisor
# Add the item to the permutation, and remove it
# from the list of available items.
perm.append(items[item_num])
items.remove(items[item_num])
# Take the remainder for the next step.
remainder = remainder % divisor
# Only one item left - add it to the permutation.
perm.append(items[0])
# Add the permutation to the list.
all_perms.append(perm)
return all_perms
I am not too familiar with the python syntax, but the following code (in 'c') shouldn't be too hard to translate assuming python can do nested for statements.
int list[3]={1,2,3};
int i,j,k;
for(i=0;i < SIZE;i++)
for(j=0;j < SIZE;j++)
for(k=0;k < SIZE;k++)
if(i!=j && j!=k && i!=k)
printf("%d%d%d\n",list[i],list[j],list[k]);

Categories

Resources