Understanding recursion using power set example - python

I have written a simple piece of code to print all subsets of a set using recursion. I have a hard time understanding the output. For example, the first line in the output shows an empty set and a singleton of 3 whereas I was expecting an empty set and singleton of 1 to be printed followed by an empty set, singleton of 1, singleton of 2 etc. However, that is not what gets printed. I do not know how to visualize recursion tree. Are there any general techniques to accomplish the visualisation? I tried drawing a tree but it quickly gets confusing.
def subsets(self, nums):
inp = nums
out = []
result=[]
def helper(inp,out,index):
if index==len(inp):
result.append(out)
return
helper(inp,out,index+1)
helper(inp,out+[inp[index]],index+1)
print(result)
helper(inp,out,0)
return result
The output from the print statement for the input '[1,2,3]' is shown below
[[], [3]]
[[], [3], [2], [2, 3]]
[[], [3], [2], [2, 3]]
[[], [3], [2], [2, 3], [1], [1, 3]]
[[], [3], [2], [2, 3], [1], [1, 3], [1, 2], [1, 2, 3]]
[[], [3], [2], [2, 3], [1], [1, 3], [1, 2], [1, 2, 3]]
[[], [3], [2], [2, 3], [1], [1, 3], [1, 2], [1, 2, 3]]

If you add an "indentation" parameter to your function, while you explore it, you can immediately see which function calls which:
def subsets(nums):
inp = nums
out = []
result=[]
def helper(indent,inp,out,index):
print(f"{indent}->helper({inp},{out},{index})")
if index==len(inp):
result.append(out)
return
helper(indent+'--',inp,out,index+1)
helper(indent+'--',inp,out+[inp[index]],index+1)
helper('',inp,out,0)
return result
The result will look like:
->helper([1, 2, 3],[],0)
--->helper([1, 2, 3],[],1)
----->helper([1, 2, 3],[],2)
------->helper([1, 2, 3],[],3)
------->helper([1, 2, 3],[3],3)
----->helper([1, 2, 3],[2],2)
------->helper([1, 2, 3],[2],3)
------->helper([1, 2, 3],[2, 3],3)
--->helper([1, 2, 3],[1],1)
----->helper([1, 2, 3],[1],2)
------->helper([1, 2, 3],[1],3)
------->helper([1, 2, 3],[1, 3],3)
----->helper([1, 2, 3],[1, 2],2)
------->helper([1, 2, 3],[1, 2],3)
------->helper([1, 2, 3],[1, 2, 3],3)
So you can immidiately see why you get [] first--you get it when you go all the way through the list without including anything in the results. You get [3] next because you backtrack to the call where you add 3 and then go to the end. You get [2] by backtracking a bit further, to where you include 2 in the output, and then down the path that doesn't add 3. Then you get [2,3] because you backtrack one level up, to the call that has 2 included in the result, and this time go to the path that adds 3.
It probably isn't the easiest way to compute a power-set, though. There is a one-to-one correspondence between the powerset of size n and the binary numbers between 0 and 2**n-1. For each number, the 1-bits indicate which elements to include in the set. So you can also compute the powerset like this:
def subsets(nums):
return [
[nums[j] for j, b in enumerate(reversed(format(i, 'b'))) if b == '1']
for i in range(2**len(nums))
]
It runs in exponential size, but so does the recursive version, and that is unavoidable when the output is exponential in the size of the input.

Related

My recursive solution in Python is appending and replacing all current items in answer list with the current list

I am trying to implement a Python solution to the programming question
of finding all subsets in an integer array. I should return an array
that contains all subsets of the integer array with no duplicates and
sorted.
def subsetHelper(cur_set, index, A, ans):
if index >= len(A):
print "appending ", cur_set
ans.append(cur_set)
print "ans: ", ans
return
# don't include current number
subsetHelper(cur_set, index+1, A, ans)
# include current number
cur_set.append(A[index])
subsetHelper(cur_set, index+1, A, ans)
cur_set.pop()
def subsets(A):
A.sort()
ans = []
cur_set = []
# dont include current number
subsetHelper(cur_set, 0, A, ans)
return ans
Implementing this same logic in C++ results in the correct return value. However when I do this in Python all I get are a collection of empty lists at the very end, while during iteration it's copying the same current list to all items in the list, even though the print out shows its appending the correct subset each time. Why is this happening? Here is the output:
print subsets([1,2,3])
appending []
ans: [[]]
appending [3]
ans: [[3], [3]]
appending [2]
ans: [[2], [2], [2]]
appending [2, 3]
ans: [[2, 3], [2, 3], [2, 3], [2, 3]]
appending [1]
ans: [[1], [1], [1], [1], [1]]
appending [1, 3]
ans: [[1, 3], [1, 3], [1, 3], [1, 3], [1, 3], [1, 3]]
appending [1, 2]
ans: [[1, 2], [1, 2], [1, 2], [1, 2], [1, 2], [1, 2], [1, 2]]
appending [1, 2, 3]
ans: [[1, 2, 3], [1, 2, 3], [1, 2, 3], [1, 2, 3], [1, 2, 3], [1, 2, 3], [1, 2, 3], [1, 2, 3]]
[[], [], [], [], [], [], [], []]
there are 2 problem here: the problem first is that you ans.extend instead of ans.append, once change that we get the second problem which is empty list at the end and the reason is that append only add to the list ans a reference to cur_set which you end up removing all element in the course of the recursion so you end up with several references to the same list that is empty at the end, to fix this you only need to append a copy of the current content of the list with list(cur_set) for instance.
Also there is no reason to make this a class
def subsetHelper(cur_set, index, A, ans):
if index >= len(A):
print "appending ", cur_set
ans.append(list(cur_set)) # <--- here was the problem
print "ans: ", ans
return
# don't include current number
subsetHelper(cur_set, index+1, A, ans)
# include current number
cur_set.append(A[index])
self.subsetHelper(cur_set, index+1, A, ans)
cur_set.pop()
def subsets(A):
A.sort()
ans = []
cur_set = []
# dont include current number
subsetHelper(cur_set, 0, A, ans)
return ans
test
>>> subsets([1,2,3])
appending []
ans: [[]]
appending [3]
ans: [[], [3]]
appending [2]
ans: [[], [3], [2]]
appending [2, 3]
ans: [[], [3], [2], [2, 3]]
appending [1]
ans: [[], [3], [2], [2, 3], [1]]
appending [1, 3]
ans: [[], [3], [2], [2, 3], [1], [1, 3]]
appending [1, 2]
ans: [[], [3], [2], [2, 3], [1], [1, 3], [1, 2]]
appending [1, 2, 3]
ans: [[], [3], [2], [2, 3], [1], [1, 3], [1, 2], [1, 2, 3]]
[[], [3], [2], [2, 3], [1], [1, 3], [1, 2], [1, 2, 3]]
>>>
The problem is you're re-using the cur_set list object between calls so even after cur_set has been added to ans, futures calls to susetHelper still modify the list, specifically future calls to cur_set.pop() empty the list. You can solve this problem by storing a copy of cur_set instead of the exact object.
def subsetHelper(cur_set, index, A, ans):
if index >= len(A):
print "appending ", cur_set
ans.append(cur_set[:])
print "ans: ", ans
return
# don't include current number
subsetHelper(cur_set, index+1, A, ans)
# include current number
cur_set.append(A[index])
subsetHelper(cur_set, index+1, A, ans)
cur_set.pop()
def subsets(A):
A.sort()
ans = []
cur_set = []
# dont include current number
subsetHelper(cur_set, 0, A, ans)
return ans
print subsets([12, 13])
Not that you're mixing state based oop and functional programing styles here. There is nothing wrong with that in principle, but debugging state based recursion can be very frustrating. If this is a learning exercise, I'd suggest you try to solve this problem in a purely functional way first (recursive way) then do it again in a purely state based way. Doing it both ways will give you a better understanding of the problem.

python - Concatenate list elements in every possible way

My issue might be quite difficult to explain (maybe that's also the reason I did not find a solution or a similar problem).
What I have is a list with some elements (in my specific case also lists).
What I want is having every possible combinations of concatenations of this list in the same order.
For example:
[[1], [2], [3], [4]] # what I have
{ # what I want
[[1], [2], [3], [4]],
[[1, 2], [3], [4]],
[[1], [2, 3], [4]],
[[1], [2], [3, 4]],
[[1, 2], [3, 4]], # Update 1
[[1, 2, 3], [4]],
[[1], [2, 3, 4]],
[[1, 2, 3, 4]]
}
In general the length of the sublists is greater then 1; also the list itself may have more than 4 elements.
Any help is highly appreciated.
UPDATE 1:
added missing combination in code.
Try this:
def concats(l):
if len(l) < 2:
return [l]
return [[l[0]] + x for x in concats(l[1:])] + \
concats([l[0] + l[1]] + l[2:])
Here's a sample case:
l = [[1], [2], [3], [4]]
r = concats(l)
And the result:
[[[1], [2], [3], [4]],
[[1], [2], [3, 4]],
[[1], [2, 3], [4]],
[[1], [2, 3, 4]],
[[1, 2], [3], [4]],
[[1, 2], [3, 4]],
[[1, 2, 3], [4]],
[[1, 2, 3, 4]]]
Edit: It wasn't clear to me how the empty list should be handled, but in that case you may want to simply return an empty list without wrapping it in an outer list - I'll leave that case up to you. A simple check at the top of the function can handle it any way you choose (and it won't affect the larger cases).

Data structure to represent multiple equivalent keys in set in Python?

Currently, I want to find the correct data structure to meet the following requirement.
There are multiple arrays with disordered element, for example,
[1, 2], [2, 1], [3, 2, 2], [2], [2, 1, 3], [2, 2, 3]
After processing those data, the result is,
[1, 2], [2, 2, 3], [2], [1, 2, 3]
With sorted element in each array and filter the duplicate arrays.
Here are my thoughts:
Data structure Set(Arrays)? - Failed. It seems there is only one array in the build-in set
set([])
Data structure Array(Sets)? - Failed. However, there is no duplicate element in the build-in set. I want to know whether there is one data structure like multiset in C++ within Python?
Transform your list to tuple(thus can be a item of set), then back to list.
>>> [list(i) for i in set([tuple(sorted(i)) for i in a])]
[[1, 2], [2], [2, 2, 3], [1, 2, 3]]
lst = [[1, 2], [2, 1], [3, 2, 2], [2], [2, 1, 3], [2, 2, 3]]
map(list, set(map(tuple, map(sorted, lst)))
Output:
[[1, 2], [2], [2, 2, 3], [1, 2, 3]]
Try this:
[list(i) for i in set(map(tuple, a))]
EDIT:
Assuming that list is already sorted. Thanks to #PM2RING to remind me.
If not, then add this line above
a = [sorted(i) for i in a]
Thanks again to #PM2RING: one liner
[list(i) for i in set(map(tuple, (sorted(i) for i in a)))]
Demo
Some of the solutions currently here are destroying ordering. I'm not sure if that's important to you or not, but here is a version which preserves original ordering:
>>> from collections import OrderedDict
>>> A = [[1, 2], [2, 1], [3, 2, 2], [2], [2, 1, 3], [2, 2, 3]]
>>> [list(k) for k in OrderedDict.fromkeys(tuple(sorted(a)) for a in A)]
[[1, 2], [2, 2, 3], [2], [1, 2, 3]]
No Python, doesn't have a built-in multiset; the closest equivalent in the standard modules is collections.Counter, which is a type of dictionary. A Counter may be suitable for your needs, but it's hard to tell without more context.
Note that sets do not preserve order of addition. If you need to preserve the initial ordering of the lists, you can do what you want like this:
data = [[1, 2], [2, 1], [3, 2, 2], [2], [2, 1, 3], [2, 2, 3]]
a = set()
outlist = []
for s in data:
t = tuple(sorted(s))
if t not in a:
a.add(t)
outlist.append(list(t))
print(outlist)
output
[[1, 2], [2, 2, 3], [2], [1, 2, 3]]
If the number of input lists is fairly small you don't need the set (and the list<->tuple conversions), just test membership in outlist. However, that's not efficient for larger input lists since it performs a linear search on the list.

A strange behavior when I append to a list in Python

I am looking for the Josephus_problem ,but the result is not my Expected. Why?
def J(n,x):
li=range(1,n+1)
k=0
res=[]
while len(li)>1:
k= (x+k-1) % len(li)
li.pop(k)
res.append(li)
#print li
return res
print J(5,3)
Expected Output:
[1, 2, 4, 5]
[2, 4, 5]
[2, 4]
[4]
Actual Output:
[[4], [4], [4], [4]]
You need to append copy of list here:
res.append(li[:]) # <-- not res.append(li) !!!
The actual reason of what's going on it that list is mutable data structure in Python. Look at this snippet
>>> l = [1,2,3]
>>> p = [l,l,l]
>>> p
[[1, 2, 3], [1, 2, 3], [1, 2, 3]]
>>> l.pop()
3
>>> p
[[1, 2], [1, 2], [1, 2]]

Iterating over consecutive sublists in Python

Does Python offer a way to iterate over all "consecutive sublists" of a given list L - i.e. sublists of L where any two consecutive elements are also consecutive in L - or should I write my own?
(Example: if L = [1, 2, 3], then the set over which I want to iterate is {[1], [2], [3], [1, 2], [2,3], [1, 2, 3]}. [1, 3] is skipped since 1 and 3 are not consecutive in L.)
I don't think there's a built-in for exactly that; but it probably wouldn't be too difficult to code up by hand - you're basically just looping through all of the possible lengths from 1 to L.length, and then taking all substrings of each length.
You could probably use itertools.chain() to combine the sequences for each length of substring together into a generator for all of them.
Example:
>>> a = [1,2,3,4]
>>> list(
... itertools.chain(
... *[[a[i:i+q] for q in xrange(1,len(a)-i+1)] for i in xrange(len(a))]
... )
... )
[[1], [1, 2], [1, 2, 3], [1, 2, 3, 4], [2], [2, 3], [2, 3, 4], [3], [3, 4], [4]]
If you prefer them in the increasing-length-and-then-lexographical-order sequence that you described, you'd want this instead:
itertools.chain(*[[a[q:i+q] for q in xrange(len(a)-i+1)] for i in xrange(1,len(a)+1)])
Try something like this:
def iter_sublists(l):
n = len(l)+1
for i in xrange(n):
for j in xrange(i+1, n):
yield l[i:j]
>>> print list(iter_sublists([1,2,3]))
[[1], [1, 2], [1, 2, 3], [2], [2, 3], [3]]
This should work:
def sublists(lst):
for sublen in xrange(1,len(lst)+1):
for idx in xrange(0,len(lst)-sublen+1):
yield lst[idx:idx+sublen]

Categories