Sticky index reference while inserting into 2D list in Python - python

While attempting to implement a function that produces all permutations given a list of integers, I'm seeing this behavior where the inserts are not occurring as expected.
My code:
def permute(nums):
perms = [[]]
for i in range(len(nums)):
new_perms = perms * (i + 1)
for j in range(len(new_perms)):
new_perms[j].insert(j % len(perms), nums[i])
perms = new_perms
return perms
When calling permute([1, 2, 3]) I'm expecting the perms to grow like:
[[]]
[[1]]
[[2, 1], [1, 2]
[[3, 2, 1], [1, 3, 2], [2, 1, 3], [3, 1, 2], [2, 3, 1], [1, 2, 3]
However, by the second iteration of the interior loop with new_perms: [[1], [1]] I'm expecting it to grow to [[2, 1], [1, 2]], instead I'm getting [[2,1],[2,1]] and then [[2,2,1],[2,2,1]]. On each iteration of the j loop, the number is getting inserted into the current j position of all values of the list simultaneously on each iteration. Not what I was trying to do or expecting.
Ultimately, the code outputs:
[[3,3,3,3,3,3,2,2,1],[3,3,3,3,3,3,2,2,1],[3,3,3,3,3,3,2,2,1],[3,3,3,3,3,3,2,2,1],[3,3,3,3,3,3,2,2,1],[3,3,3,3,3,3,2,2,1]]
Either this is some subtle reference behavior (yay, learn something new!) or I'm just having a really dumb day ;) Any help appreciated.
PLEASE NOTE: I'm NOT asking for help with an alternate or optimal permutations function! I'm trying to figure out why this particular code is behaving in an unexpected way. Thank you.

Ok, learned a good one here. DEEPCOPY!
import copy
def permute(nums):
perms = [[]]
for i in range(len(nums)):
len_perms = len(perms)
old_perms = perms
perms = []
for _ in range(i+1):
perms += copy.deepcopy(old_perms)
for j in range(len(perms)):
perms[j].insert(j // len_perms, nums[i])
return perms

Related

Check if two lists in list have common elements. Connect them if so, without duplicates

I am stuck on how to solve this problem.
Given a set of lists in a list, if any two sets of lists contain a common element, the two lists would be combined into one.
Suppose I have a set of lists in a list [[0, 1], [3, 6], [3, 9]]. Notice that [3, 6] and [3, 9] have a common element 3, so they are combined into [3, 6, 9], so how to convert this set of lists in a list into [[0,1], [3, 6, 9]]?
This is my current code but I am stuck.
for i in connected:
for j in connected:
a_set = set(i)
b_set = set(j)
if (a_set & b_set):
i.extend(j)
connected.remove(j)
Challenging! This is my approach:
def combine_commons(input: list) -> list:
combine_found = False
for ct_current, item_current in enumerate(input):
# try to find a element that shares item:
combine_into = None
for ct_search, item_search in enumerate(input):
if ct_current==ct_search: continue # can skip if i==j
if any(i in item_search for i in item_current):
# if any elements match, combine them.
combine_into = item_search
combine_found = True
break
if isinstance(combine_into, list):
input[ct_current] = list(set(item_current + combine_into)) # overwrite with new combined
del input[ct_search]
if combine_found: return combine_commons(input)
return input
print(combine_commons([[0, 1], [3, 6], [3, 9]]))
print(combine_commons([[1,2],[2,3],[2,5],[5,1]]))
# >>> [[0, 1], [9, 3, 6]]
# >>> [[1, 2, 3, 5]]
What it basically does is it loops twice through the list of lists. Then, for each i and j it checks if they have something in common. If they do, combine them into one (overwriting element i with the new long list and deleting element j). This then breaks the loop, so my solution was to check all the items again (looking for mergers) in a recursive fashion. Hope this helps!
Sometimes using the right data structures can make all the difference. Your problem seems to require something like an adjacency matrix to resolve. So, here is a quick way to do this using Graphs.
The intuition is as follows -
This is inspired by the approaches mentioned here. Here is the code which is using highly optimized inbuilt networkx functions.
import networkx as nx
def merge_lists(l):
islands = nx.connected_components(nx.from_edgelist(l))
return [list(i) for i in islands]
print(merge_lists([[0,1],[3,6],[3,9]]))
print(merge_lists([[1,2],[2,3],[2,5],[5,1]]))
print(merge_lists([[1,2],[2,3],[9,8],[5,6],[5,7]]))
[[0, 1], [9, 3, 6]] #case 1, [[0,1],[3,6],[3,9]]
[[1, 2, 3, 5]] #case 2, [[1,2],[2,3],[2,5],[5,1]]
[[1, 2, 3], [8, 9], [5, 6, 7]] #case 3, [[1,2],[2,3],[9,8],[5,6],[5,7]]
Any variations in the cases can be easily accommodated by modifying the graph created in the function.
Here is an idea for this particular case, whether it can handle other edge cases is another question but perhaps something you can build on
connected = [[0, 1], [3, 6], [3, 9]]
new_list = []
for i, v in enumerate(connected):
for j in v:
try:
if j in connected[i+1]:
new_list.append(sorted(list(set(connected[i] + connected[i+1]))))
connected.pop(i)
connected.pop(i)
break
except IndexError:
pass
connected += new_list
print(connected)

How to find subsets from a set that product equals the target?

Let us say we have a list and target of:
list: [1,2,3,4,5] and target: 20
and we want to find total combinations to reach this, with multiplication, which is:
[1,4,5], [4,5], [2,5,2], [1,2,2,5]
I did this code, but I can't seem to know how to remove the ones so I have them too, I mean that I receive:
[1,4,5], [1,2,2,5].
But without [1], I can't seem to get it, I tried to "cheat" somehow to get it, but I can't since my code doesn't fit for it...
def Coin(target, lst, temp=[], comb=[]):
if target == 1:
comb.append(temp)
return comb
if len(lst) == 0:
return []
if target >= lst[0]:
if lst[0] > 1:
take=Coin(target/lst[0],lst,temp+[lst[0]],comb)
dont_take=Coin(target,lst[1:],temp,comb)
else:
take_1 = Coin(target, lst[1:], temp + [1], comb)
return take_1
return take
return comb
print(Coin(20, [1,2,3,4,5], [], []))
[[1, 2, 2, 5], [1, 4, 5]]
How to add the parts without 1? I don't need a solution, since as said, not homework, but practice for exam. Just a clue will be enough, I want to find it myself, but I need a clue for it.
Maybe you should combine
if lst[0] > 1:
else:
together, that means for 1 we should also decide whether take it or not.
This is much easier to do with a generator using yield than a function using return.
The difference is that you can only return once, while you can yield any number of times (including 0 if there are no solutions).
If you wish to return a list, you still can, like this:
def coin (target, lst):
return list(_coin(target, lst, 0)
def _coin (target, lst, i):
...
If that doesn't matter, the generator saves memory by not having to generate the whole list at once. And you simply:
def coin (target, lst, i=0):
... # Use yield as often as you want, all will be returned.
Second, you are running into the most common gotcha in Python. You are using mutable objects as defaults. If you're going to continue with your current approach you need to:
def coin(target, lst, temp=None, comb=None):
if temp is None:
temp = []
if comb is None:
comb = []
...
And third, you should get in the habit of following standard style conventions. In many ways, what the convention is doesn't matter that much. But that everyone is on the same page, does. Therefore you should try to follow the most common Python convention. In which the function should be named coin instead of Coin.
Edit:
The rules for the question is:
Positive integers only ( 0 not allowed ).
Number can appear once only ( at input ).
can not repeat numbers on list.
EG: [1,2,3,4], n=12 >> [1,12], [12,1], [3,4], [2,6], [1,3,4], [1,2,6].
NOT: [1,2,2,3], [2,2,3]
That is all I guess.
def coin_change_MULTI(num, lst):
if num == 1 and 1 not in lst:
return []
return Coin_MULTI(num, sorted(lst), [], [])
def Coin_MULTI(target, lst, temp=[], comb=[]):
if target == 1:
if big_than_target(1, lst):
return [[1]]
comb.append(temp)
return comb
if len(lst) == 0: return []
if target >= lst[0]:
if lst[0] > 1:
take=Coin_MULTI(target/lst[0],lst[1:],temp+[lst[0]],comb)
dont_take=Coin_MULTI(target,lst[1:],temp,comb)
return comb
else:
take_1 = Coin_MULTI(target, lst[1:], temp + [1], comb)
dont_take_1 = Coin_MULTI(target, lst[1:], temp, comb)
return comb
return take + dont_take
return comb
print(coin_change_MULTI(12, [2,4,6,12,7,3, 1]))
print(coin_change_MULTI(1, [2,4,6,12,7,3,1]))
print(coin_change_MULTI(1, [2,4,6,12,7,3]))
print(coin_change_MULTI(100, [2,4,6,12,7,3,1]))
print(coin_change_MULTI(576, [2,4,6,12,7,3,1]))
print(coin_change_MULTI(12096, [2,4,6,12,7,3,1]))
print(coin_change_MULTI(0, [2,4,6,12,7,3,1]))
print((coin_change_MULTI(24, [2,4,6,12,7,3,1])))
[[1, 2, 6], [1, 3, 4], [1, 12], [2, 6], [3, 4], [12]]
[[1]]
[]
[]
[[1, 2, 4, 6, 12], [2, 4, 6, 12]]
[[1, 2, 3, 4, 6, 7, 12], [2, 3, 4, 6, 7, 12]]
[]
[[1, 2, 3, 4], [1, 2, 12], [1, 4, 6], [2, 3, 4], [2, 12], [4, 6]]
Process finished with exit code 0

A combination algorithm in python

import copy
def combine(l, n):
answers = []
one = [0] * n
def next_c(li = 0, ni = 0):
if ni == n:
answers.append(copy.copy(one))
return
for k in range(li, len(l)):
one[ni] = l[k]
next_c(k+1, ni+1)
next_c()
return answers
print(combine([1,2,3,4],2))
Recently I found this code on internet. It works very well. However, I do not know the details that how does it work? So can anybody tell me how does it work? And how to understand a recursive code quickly? Thanks very much
As #AChampion said, You could also get the combinations with the itertools library, which is much easier to understand:
import itertools
def combine2(lst, n):
return [list(x) for x in itertools.combinations(lst, n)]
Output:
>>> combine2([1,2,3,4],2)
[[1, 2], [1, 3], [1, 4], [2, 3], [2, 4], [3, 4]]

Mirroring rows in matrix with loops/recursion?

Given some matrix, I need to mirror all the rows in the matrix. For example
[[2, 1],
[4, 3]]
would become
[[1, 2],
[3, 4]]
I managed to do it for the (2 x 2)-case. But I'm having trouble mirroring something like this:
[[1, 2, 3, 4],
[1, 2, 3, 4]]
This has to become
[[4, 3, 2, 1],
[4, 3, 2, 1]]
I want to do this with loops/recursion. If I use recursion, I would probably have as basic step that the inner most elements get swapped first, and then from here on we would make the matrix bigger by also including the outer elements and swapping them too. However, I'm having trouble with the recursion step. After having swapped the inner most elements, I want to include the next to inner most elements in the matrix, and swap them too, and then continue like this until we reach the outer elements. How can this be implemented in code? This is what I did so far:
matrix = [[1, 2, 3, 4],
[1, 2, 3, 4]]
def mirror(matrix):
# This corresponds to the basic step. The two inner most elements get swapped.
if len(matrix) == 2:
for i in range(len(matrix)):
for j in range(len(matrix)):
# Store one element in a temporal variable
temp = matrix[i][j]
matrix[i][j] = matrix[i][len(matrix) - 1]
matrix[i][len(matrix)-1] = temp
return matrix
else:
# Recursion step
for i in range(len(matrix)):
for j in range(len(matrix)):
return (matrix + mirror(matrix[(len(matrix) // 2) - 1 : len(matrix)]))
The recursion step is wrong I think. I tried using the slice operator, but I'm not sure how this should be done correctly. Any help with this problem would be appreciated.
A recursive solution is pretty trivial, just recurse across the array reversing each subarray:
arr= [[2, 1],
[4, 3]]
def reve(l):
# if we have recursed across all sub arrays just return empty list
if not l:
return []
# else reverse the first current sublist l[0] and recurse on the remaining sublists
return [l[0][::-1]] + reve(l[1:])
print(reve(arr))
[[1, 2], [3, 4]]
Which can be written concisely as:
def reve(l):
return [l[0][::-1]] + reve(l[1:]) if l else []
If you wanted it inplace:
arr = [[1, 2, 3, 4],
[1, 2, 3, 4]]
def reve(l):
if not l:
return
# call inplace list.reverse on each sublist
l[0].reverse()
return reve(l[1:])
reve(arr)
Output:
[[4, 3, 2, 1], [4, 3, 2, 1]]
And lastly we can achieve what you want inplace with no slicing at all using iter with the special method __length__hint:
def reve(l):
if l.__length_hint__() == 0:
return
sub = next(l)
sub.reverse()
return reve(l)
reve(iter(arr))
print(arr)
Output:
[[4, 3, 2, 1], [4, 3, 2, 1]]
Both functions might use the map function, but you can use a imperative for also. About my recursive approach, the else statement refers to all cases between the ultimate and second elements of the list, they are being concatenated until the first element is reached.
My recursive approach:
a = [[1, 2, 3],
[5, 6, 7]]
def mirror(matrix):
def revert(row):
if len(row) == 1:
return [row[0]]
else:
return [row[-1]] + revert(row[:-1]) # concatenates two lists.
return [revert(row) for row in matrix]
mirror(a)
My declarative approach:
def mirror(matrix):
def revert(row):
return row[::-1] # copies the array in reverse order
return list(map(revert, matrix)) #<-for python3, and just map(...) for python2
mirror(a)
Both functions outputs
[[3, 2, 1], [7, 6, 5]]
Actually, a more Pythonic way of doing that would be using list comprehensions. You can do that simply by:
matrix = [[1, 2, 3, 4],
[1, 2, 3, 4]]
reversed_matrix = (i[::-1] for i in matrix)
reversed_matrix will be a generator expression. You may convert it into a list by replacing "()" with "[]" In the list comprehension.
i[::-1] reverses the array in-place using slice operator

Python: Recursively create a list of lists of ints with conditions

I want to create all possible distributions of n items. This refers to the commonly known pigeonhole principle.
The following values are the result of Microsoft Excel:
get_distributions(list, number_of_items_to_distribute)
get_distributions([], 1) = [[1]]
get_distributions([], 2) = [[1, 1], [2]]
get_distributions([], 3) = [[1, 1, 1], [1, 2], [2, 1], [3]]
get_distributions([], 4) = [[1, 1, 1, 1], [1, 1, 2], [1, 2, 1], [1, 3], [2, 1, 1], [2, 2], [3, 1], [4]]
I already have some code, but there are some issues with deleting the temporary lists.
all_distributions = []
def get_distributions(distribution, items):
print('call with distribution = ' + str(distribution) + ', items = ' + str(items))
print('---------------')
# base case
if items == 0:
all_distributions.append(distribution)
print('end: ' + str(distribution))
distribution.clear()
return []
# recursion
else:
for i in range(1, items + 1):
distribution.append(i)
get_distributions(distribution, items - i)
With this I get good results printed out after "end: ", but some values like [1, 2] (calling with n = 3) are missing. Additionally to this the values are not appended to my all_distributions.
I'm interested in the way I tried to solve this problem. Is this a good approach or am I absolutely wrong?
The main problem with your code is that the list all_distributions ends up containing many references to the same input list distribution. When you call all_distributions.append(distribution), the list distribution is not copied into the list all_distributions, but merely a reference to the list is appended. You can fix this by explicitly inserting a copy: all_distributions.append(list(distribution))
A minimal fix to your code is to insert copies, remove distribution.clear() in the base case, and adding distribution.pop() after the recursive call:
all_distributions = []
def get_distributions(distribution, items):
if items == 0:
all_distributions.append(list(distribution))
else:
for i in range(1, items + 1):
distribution.append(i)
get_distributions(distribution, items - i)
distribution.pop()
get_distributions([], 3)
print(all_distributions)
Outputs: [[1, 1, 1], [1, 2], [2, 1], [3]]
A better way is to avoid using distribution.append, and instead using the plus operator on lists, like so:
def get_distributions(distribution, items):
if items == 0:
all_distributions.append(distribution)
else:
for i in range(1, items + 1):
d = distribution + [i]
get_distributions(d, items - i)
The plus operator on lists creates a new list by concatenating the two given lists. In this case, we are concatenating a single element i on the right side of distribution to get a new copy containing the elements in distribution followed by i.
Another improvement is to avoid the global variable all_distributions, and instead return the list of distributions:
def get_distributions(distribution, items):
if items == 0:
return [distribution]
else:
all_distributions = []
for i in range(1, items + 1):
d = distribution + [i]
all_distributions += get_distributions(d, items - i)
return all_distributions
print(get_distributions([], 4))
Outputs: [[1, 1, 1, 1], [1, 1, 2], [1, 2, 1], [1, 3], [2, 1, 1], [2, 2], [3, 1], [4]]

Categories