I'm trying to solve the following exercise from "Data Structures and Algorithms in Python" by Goodrich et al.:
"Describe a nonrecursive algorithm for enumerating all permutations of the numbers {1, 2, ..., n} using an explicit stack."
The hint for this excercise is to use "[...] a stack to reduce the problem to that of enumerating all permutations of the numbers {1, 2, ..., n-1}."
My first idea was to push n onto the stack, then n - 1, ..., until I reach 1. Now, I'd save all permutations of 1 ("1") in a list. The next step would be to pop the next value from the stack ("2") and to insert it into every possible position for every permutation that is in my list so far. As a result, when the stack is empty again, there would be a list containing all permutations of the numbers 1 to n.
However, I doubt that this is what is meant by the hint (I'm using an extra list, which is not mentioned in the hint, for example). What other approaches are there that fit the hint better?
Edit: This is my implementation of the solution I've described
def permutations(n):
s = MyStack()
perms = [[]]
while n > 0:
s.push(n)
n -= 1
total = 1
factor = 1
while not s.is_empty():
val = s.pop()
total *= factor
factor += 1
prev = perms
perms = [None] * total
idx = 0
for perm in prev:
for pos in range(len(perm) + 1):
perms[idx] = [*perm[:pos], val, *perm[pos:]]
idx += 1
for perm in perms:
print(perm)
Edit 2: explicit stack implementation of #btilly‘s answer
def permutations_explicit_stack(items):
items = list(items)
n = len(items)
s = MyStack()
i = 0
perm = []
while i < n:
perm.append(items.pop(0))
s.push(i)
i = 0
if len(items) == 0:
print(perm)
while i == len(items) and s:
i = s.pop()
items.append(perm.pop())
i += 1
Better hint. Write a recursive function to calculate permutations. That uses an implicit stack. Then figure out how to use an explicit stack to do the same thing that the recursive function did, but without having to use recursion. The point of the exercise being to understand what your programming language did for you with recursion.
The reason why this matters is that, as you learn data structures, you'll encounter more and more situations where recursion really is easier. But you'll also encounter situations where rewriting it with an explicit stack allows you to understand and modify the algorithm in a way that straight recursion hid from you. A classic example being that depth first search is easiest to write recursively. But if you rewrite with a stack, then switch the stack for a queue, you get breadth first search. And switch out the queue for a priority queue, and you've got A* search.
Here is a recursive permutation function to get you started.
def permutations (items):
_permutations([], list(items))
def _permutations(perm, items):
if 0 == len(items):
print(perm)
else:
for i in range(len(items)):
perm.append(items.pop(0))
_permutations(perm, items)
items.append(perm.pop())
What information is being kept on the implicit function call stack? And how can you store the same information in an explicit stack? How can you rewrite this function without any recursion?
Related
When having a list of lists in which all the sublists are ordered e.g.:
[[1,3,7,20,31], [1,2,5,6,7], [2,4,25,26]]
what is the fastest way to get the union of these lists without having duplicates in it and also get an ordered result?
So the resulting list should be:
[1,2,3,4,5,6,7,20,25,26,31].
I know I could just union them all without duplicates and then sort them but are there faster ways (like: do the sorting while doing the union) built into python?
EDIT:
Is the proposed answer faster than executing the following algorithm pairwise with all the sublists?
You can use heapq.merge for this:
def mymerge(v):
from heapq import merge
last = None
for a in merge(*v):
if a != last: # remove duplicates
last = a
yield a
print(list(mymerge([[1,3,7,20,31], [1,2,5,6,7], [2,4,25,26]])))
# [1, 2, 3, 4, 5, 6, 7, 20, 25, 26, 31]
(EDITED)
The asymptotic theoretical best approach to the problem is to use the priority queue, like, for example, the one implemented in heapq.merge() (thanks to #kaya3 for pointing this out).
However, in practice, a number of things can go wrong. For example the constant factors in the complexity analysis are large enough that a theoretically-optimal approach is, in real-life scenarios, slower.
This is fundamentally dependent on the implementation.
For example, Python suffer some speed penalty for explicit looping.
So, let's consider a couple of approaches and how the do perform for some concrete inputs.
Approaches
Just to give you some idea of the numbers we are discussing, here are a few approaches:
merge_sorted() which uses the most naive approach of flatten the sequence, reduce it to a set() (removing duplicates) and sort it as required
import itertools
def merge_sorted(seqs):
return sorted(set(itertools.chain.from_iterable(seqs)))
merge_heapq() which essentially #arshajii's answer. Note that the itertools.groupby() variation is slightly (less than ~1%) faster.
import heapq
def i_merge_heapq(seqs):
last_item = None
for item in heapq.merge(*seqs):
if item != last_item:
yield item
last_item = item
def merge_heapq(seqs):
return list(i_merge_heapq(seqs))
merge_bisect_set() is substantially the same algorithm as merge_sorted() except that the result is now constructed explicitly using the efficient bisect module for sorted insertions. Since sorted() is doing fundamentally the same thing but looping in Python, this is not going to be faster.
import itertools
import bisect
def merge_bisect_set(seqs):
result = []
for item in set(itertools.chain.from_iterable(seqs)):
bisect.insort(result, item)
return result
merge_bisect_cond() is similar to merge_bisect_set() but now the non-repeating constraint is explicitly done using the final list. However, this is much more expensive than just using set() (in fact it is so slow that it was excluded from the plots).
def merge_bisect_cond(seqs):
result = []
for item in itertools.chain.from_iterable(seqs):
if item not in result:
bisect.insort(result, item)
return result
merge_pairwise() explicitly implements the the theoretically efficient algorithm, similar to what you outlined in your question.
def join_sorted(seq1, seq2):
result = []
i = j = 0
len1, len2 = len(seq1), len(seq2)
while i < len1 and j < len2:
if seq1[i] < seq2[j]:
result.append(seq1[i])
i += 1
elif seq1[i] > seq2[j]:
result.append(seq2[j])
j += 1
else: # seq1[i] == seq2[j]
result.append(seq1[i])
i += 1
j += 1
if i < len1:
result.extend(seq1[i:])
elif j < len2:
result.extend(seq2[j:])
return result
def merge_pairwise(seqs):
result = []
for seq in seqs:
result = join_sorted(result, seq)
return result
merge_loop() implements a generalization of the above, where now pass is done only once for all sequences, instead of doing this pairwise.
def merge_loop(seqs):
result = []
lengths = list(map(len, seqs))
idxs = [0] * len(seqs)
while any(idx < length for idx, length in zip(idxs, lengths)):
item = min(
seq[idx]
for idx, seq, length in zip(idxs, seqs, lengths) if idx < length)
result.append(item)
for i, (idx, seq, length) in enumerate(zip(idxs, seqs, lengths)):
if idx < length and seq[idx] == item:
idxs[i] += 1
return result
Benchmarks
By generating the input using:
def gen_input(n, m=100, a=None, b=None):
if a is None and b is None:
b = 2 * n * m
a = -b
return tuple(tuple(sorted(set(random.randint(int(a), int(b)) for _ in range(n)))) for __ in range(m))
one can plot the timings for varying n:
Note that, in general, the performances will vary for different values of n (the size of each sequence) and m (the number of sequences), but also of a and b (the minimum and the maximum number generated).
For brevity, this was not explored in this answer, but feel free to play around with it here, which also includes some other implementations, notably some tentative speed-ups with Cython that were only partially successful.
You can make use of sets in Python-3.
mylist = [[1,3,7,20,31], [1,2,5,6,7], [2,4,25,26]]
mynewlist = mylist[0] + mylist[1] + mylist[2]
print(list(set(mynewlist)))
Output:
[1, 2, 3, 4, 5, 6, 7, 20, 25, 26, 31]
First merge all the sub-lists using list addition.
Then convert it into a set object where it will delete all the duplicates which will also be sorted in ascending order.
Convert it back to list.It gives your desired output.
Hope it answers your question.
Given a set of integers 1,2, and 3, find the number of ways that these can add up to n. (The order matters, i.e. say n is 5. 1+2+1+1 and 2+1+1+1 are two distinct solutions)
My solution involves splitting n into a list of 1s so if n = 5, A = [1,1,1,1,1]. And I will generate more sublists recursively from each list by adding adjacent numbers. So A will generate 4 more lists: [2,1,1,1], [1,2,1,1], [1,1,2,1],[1,1,1,2], and each of these lists will generate further sublists until it reaches a terminating case like [3,2] or [2,3]
Here is my proposed solution (in Python)
ways = []
def check_terminating(A,n):
# check for terminating case
for i in range(len(A)-1):
if A[i] + A[i+1] <= 3:
return False # means still can compute
return True
def count_ways(n,A=[]):
if A in ways:
# check if alr computed if yes then don't compute
return True
if A not in ways: # check for duplicates
ways.append(A) # global ways
if check_terminating(A,n):
return True # end of the tree
for i in range(len(A)-1):
# for each index i,
# combine with the next element and form a new list
total = A[i] + A[i+1]
print(total)
if total <= 3:
# form new list and compute
newA = A[:i] + [total] + A[i+2:]
count_ways(A,newA)
# recursive call
# main
n = 5
A = [1 for _ in range(n)]
count_ways(5,A)
print("No. of ways for n = {} is {}".format(n,len(ways)))
May I know if I'm on the right track, and if so, is there any way to make this code more efficient?
Please note that this is not a coin change problem. In coin change, order of occurrence is not important. In my problem, 1+2+1+1 is different from 1+1+1+2 but in coin change, both are same. Please don't post coin change solutions for this answer.
Edit: My code is working but I would like to know if there are better solutions. Thank you for all your help :)
The recurrence relation is F(n+3)=F(n+2)+F(n+1)+F(n) with F(0)=1, F(-1)=F(-2)=0. These are the tribonacci numbers (a variant of the Fibonacci numbers):
It's possible to write an easy O(n) solution:
def count_ways(n):
a, b, c = 1, 0, 0
for _ in xrange(n):
a, b, c = a+b+c, a, b
return a
It's harder, but possible to compute the result in relatively few arithmetic operations:
def count_ways(n):
A = 3**(n+3)
P = A**3-A**2-A-1
return pow(A, n+3, P) % A
for i in xrange(20):
print i, count_ways(i)
The idea that you describe sounds right. It is easy to write a recursive function that produces the correct answer..slowly.
You can then make it faster by memoizing the answer. Just keep a dictionary of answers that you've already calculated. In your recursive function look at whether you have a precalculated answer. If so, return it. If not, calculate it, save that answer in the dictionary, then return the answer.
That version should run quickly.
An O(n) method is possible:
def countways(n):
A=[1,1,2]
while len(A)<=n:
A.append(A[-1]+A[-2]+A[-3])
return A[n]
The idea is that we can work out how many ways of making a sequence with n by considering each choice (1,2,3) for the last partition size.
e.g. to count choices for (1,1,1,1) consider:
choices for (1,1,1) followed by a 1
choices for (1,1) followed by a 2
choices for (1) followed by a 3
If you need the results (instead of just the count) you can adapt this approach as follows:
cache = {}
def countwaysb(n):
if n < 0:
return []
if n == 0:
return [[]]
if n in cache:
return cache[n]
A = []
for last in range(1,4):
for B in countwaysb(n-last):
A.append(B+[last])
cache[n] = A
return A
Hi i have a nested list which i should sum it from recursive function
how can i do that?
my solution didn't work
def n_l_sum(n):
s=0
for i in n:
if i==list:
s+=i
else:
s+=n_l_s(n)
return s
use the isinstanceof function instead of using the ==
def summer(lst):
if not isinstance(lst, list) : return lst
sum = 0
for x in lst:
sum += summer(x)
return sum
def main():
a= [1,2,3, [5,6]]
print(summer(a))
Recursion can be quite difficult to grasp at first; because it can be very intuitive to conceptualize but an pain to implement. I will try to explain my answer as thoroughly as possible so that you understand what exactly is happening:
def n_l_sum(n,i):
s=0 #somewhere to store our sum
lenLst= len(n)
cur = n[i] #sets current element to 'cur'
if lenLst-1-i < 1: #this is the base case
return cur
else:
s = n_l_sum(n,i+1)+cur #where the actual iteration happens
print(s) #give result
return s
n=[6,4]
n_l_sum(n,0)
The Base Case
The first important thing to understand is the base case this gives the recursion a way of stopping.
For example without it the function would return an IndexError because eventually n_l_sum(n,i+1) would eventually go over the maximum index. Therefore lenLst-1-i < 1 stops this; what it is saying: if there is only one element left do this.
Understanding Recursion
Next, I like to think of recursion as a mining drill that goes deep into the ground and collects its bounty back on the way up to the surface. This brings us to stack frames you can think of these as depths into the ground (0-100 m, 100-200 m). In terms of programming they are literal frames which store different instances of variables. In this example cur will be 4 in frame 1 then 6 in frame 2.
once we hit the base case we go back up through the frames this is where s = n_l_sum(n,i+1)+cur does the legwork and calculates the sum for us.
If you are still struggling to understand recursion try and run this code through http://www.pythontutor.com/visualize.html#mode=edit to see what is happening on a variable level.
def n_l_s(n):
s=0
for i in n:
if type(i) != type([]):
s+=i
else:
s+=n_l_s(i)
return s
n=[3, -1, [2, -2], [6, -3, [4, -4]]]
print(n_l_s(n))
output:
5
The functional way would be to to this:
def list_sum(l):
if not isinstance(l, list): # recursion stop, l is a number
return l
# do list_sum over each element of l, sum result
return sum(map(list_sum, l))
I am being tasked with designing a python function that returns the index of a given item inside a given list. It is called binary_sort(l, item) where l is a list(unsorted or sorted), and item is the item you're looking for the index of.
Here's what I have so far, but it can only handle sorted lists
def binary_search(l, item, issorted=False):
templist = list(l)
templist.sort()
if l == templist:
issorted = True
i = 0
j = len(l)-1
if item in l:
while i != j + 1:
m = (i + j)//2
if l[m] < item:
i = m + 1
else:
j = m - 1
if 0 <= i < len(l) and l[i] == item:
return(i)
else:
return(None)
How can I modify this so it will return the index of a value in an unsorted list if it is given an unsorted list and a value as parameters?
Binary Search (you probably misnamed it - the algorithm above is not called "Binary Sort") - requires ordered sequences to work.
It simply can't work on an unordered sequence, since is the ordering that allows it to throw away at least half of the items in each search step.
On the other hand, since you are allowed to use the list.sorted method, that seems to be the way to go: calling l.sort() will sort your target list before starting the search operations, and the algorithm will work.
In a side note, avoid in a program to call anything just l - it maybe a nice name for a list for someone with a background in Mathematics and used to do things on paper - but on the screen, l is hard to disinguish from 1 and makes for poor source code reading. Good names for this case could be sequence lst, or data. (list should be avoided as well, since it would override the Python built-in with the same name).
How could I implement this using recursion, and would it be more effcient?
My code:
def insertionSort(array):
'''(list) - > list
Returns a sorted list of integers by implementing
the insertion sort which returns numbers in array from
least to greatest
'''
for i in range(1, len(array)):
if array[i-1] > array[i]: #Finds a number out of place
temp = array[i]
for a in range(0,i):
if temp < array[a]:
array.insert(a,temp)
del array[i+1]
break
return array
A simple recursive version:
def insertionSort(array,i=1):
if i >= len(array):
return array
if array[i-1] > array[i]:
temp = array[i]
for a in range(0, i):
if temp < array[a]:
array.insert(a,temp)
del array[i+1]
break
return insertionSort(array, i+1)
Generally recursion is better for certain data structures such as trees and linked lists. Sometimes it is easier to think in terms of recursion to solve a problem. I don't remember a case where recursion actually used for efficiency.
Any for loop for x in range(a,b): Body can be reimplemented with tail recursion:
def rfor(b, x = a):
if a < b:
Body
rfor(b, x + 1)
This ought to give you enough to get the answer yourself. In standard Python this will definitely be slower than the loop and eat one stack frame per iteration. In other languages and implementations with good tail call optimization, they'll be equal in cost. Apparently Guido has said he's uninterested in tail call optimization.