Creating a list of mutable items repeated N times - python

I have a list containing a set of x mutable items. I would like to create another list where the set of x mutable items is repeated n times. However, the items must be references to unique objects rather than simply references to the original objects.
For example, let a = [[1],[2],[3]]. Suppose I would like the numbers inside to be repeated n=3 times, i.e. [[0],[2],[3],[1],[2],[3],[1],[2],[3]].
I can do this easily by using a * 3. The problem is that if I change a[0][0] = 0, then I will get a == [[0],[2],[3],[0],[2],[3],[0],[2],[3]], which is not desirable because I would like to change only the first element. Is there any other way to achieve this?

itertools has a method for this called repeat. Just pair it with chain.
from itertools import repeat, chain
a = [1,2,3]
b = list(chain(*repeat(a, 3)))
>>> b
[1,2,3,1,2,3,1,2,3]

a = [1,2,3]
a = a*3
a[0] =0
print(a)

Very simply, you need to make copies of a, rather than repeating references to the original, mutable object. If you need just one level of copy, you can do it like this, noting that it's a shallow copy (see second output).
a = [[1, 1], [2], [3, 3, 3]]
b = a[:] * 3
b[0] = "change"
print(b)
b[2][1] = "deep"
print(b)
Output:
['change', [2], [3, 3, 3], [1, 1], [2], [3, 3, 3], [1, 1], [2], [3, 3, 3]]
['change', [2], [3, 'deep', 3], [1, 1], [2], [3, 'deep', 3], [1, 1], [2], [3, 'deep', 3]]
See how only the first level is given a new reference. When you try to change a deeper element, you see that the nested lists are still the same object. To fix that problem, use a deep copy for each element of b:
import copy
a = [[1, 1], [2], [3, 3, 3]]
b = [copy.deepcopy(a) for _ in range(3)]
b[0] = "change"
print(b)
b[2][1] = "deep"
print(b)
Output:
['change', [[1, 1], [2], [3, 3, 3]], [[1, 1], [2], [3, 3, 3]]]
['change', [[1, 1], [2], [3, 3, 3]], [[1, 1], 'deep', [3, 3, 3]]]

Just do:
n = [sub_a.copy() for sub_a in a] * 3
You need copy() to avoid the problem.
a = [[1],[2],[3]]
n = [sub_a.copy() for sub_a in a] * 3
a[0] = [-1]
a[2][0] = -1
print (n)
Output:
[[1], [2], [3], [1], [2], [3], [1], [2], [3]]

To get new instances of mutable objects, you can map the object type's copy() method to the multiplied list:
a = [{1},{2},{3}]
b = [*map(set.copy,a*3)]
a[0].add(7)
b[0].add(8)
b[3].add(9)
print(a) # [{1, 7}, {2}, {3}]
print(b) # [{8, 1}, {2}, {3}, {1, 9}, {2}, {3}, {1}, {2}, {3}]
If you don't know the type of the items, or if they have different types, you can do it in a list comprehension:
b = [ i.copy() for i in a*3 ]
If not all of the items are mutable, you can do it like this:
b = [type(i)(i) for i in a*3]
If some can be None ...
b = [i if i is None else type(i)(i) for i in a*3]
You can also use deepcopy which covers all these cases including objects that contain nested objects ...
from copy import deepcopy
b = [*map(deepcopy,a*3)]

I just figured out I can do this through:
from copy import deepcopy
a = [[1],[2],[3]]
n = 3
b = [deepcopy(val) for _ in range(n) for val in a]
Now if I set b[0][0] = 0, I will get b == [[0],[2],[3],[1],[2],[3],[1],[2],[3]].

Related

How to copy list elements in python

Regarding the code below:
A = [[1, 2], [3, 4]]
A[0] = A[1]
B = A[:][0]
B[0] = 5
print(A)
print(B)
I'm wondering why printing B gives [5, 4].
I thought that B = A[:][0] is the same as A[0][0], A[1][0], which would then be [3, 3]. Then, B[0] = 5, so it would print [5, 3].
Could someone kindly clear up my confusion?
after this line:
A[0] = A[1]
A = [[3,4],[3,4]]
A[:]
will return A itself, so when you access 0, you will get [3,4]
B = A[:][0]
and after putting 5, B = [5,4]
Here is your confusion:
I thought that B = A[:][0] is the same as A[0][0], A[1][0]
It's not. The : symbol in Python isn't used for that sort of shorthand like in some other languages. A[:] simply returns a copy of A, so A[:][0] is equivalent to A[0], which in this context is [3, 4].
(The more general use of the : is for an operation called slicing, which is when you copy only part of a list, from a given starting index to a given end index. If we omit the indices, they default to the start and end of the entire list, so we copy the whole thing.)
Using the copy() method is relatively straightforward.
a = [[1, 2], [3, 4]]
b = a.copy()
print(a)
print(b)
result
[[1, 2], [3, 4]]
[[1, 2], [3, 4]]
If you want to make copies of the nested lists, then you would extend to this:
a = [[1, 2], [3, 4]]
b = [a[0].copy(), a[1].copy()]

How can I select outer item in a sublist on the basis of matching of items from my other list?

I have a list like the one below:
mylist = [[0, [1, 2]], [1, [1]], [2, [0, 2]], [3, [3]], [4, []], [5, []], [6, [0]]]
The inner list is [1, 2], [1], [0, 2] and so on.
The outer list is [0], [1], [2] and so on.
CASE 1: If the inner list has one item from myOtherlist and does not share this inner list with another number then get the outer item. Like the one below:
INPUT:
myOtherlist = [2, 3]
OUTPUT:
outer_item_list = [3]
CASE 2: If the inner list has all the items from myOtherlist then get the outer item and of course if one of the items from myOtherlist is on its own somewhere else in mylist. Like the one below:
INPUT:
myOtherlist = [0, 2]
OUTPUT:
outer_item_list = [2, 6]
CASE 3: If the inner list has one item from myOtherlist and it is on its own in the inner list then get the outer item. Like the one below:
INPUT:
myOtherlist = [0]
OUTPUT:
outer_item = [6]
MY CODE LOOKS LIKE THIS:
outer_item_list = []
for number in list:
for inner in mylist:
if number in inner[1] and len(inner[1]) == 1:
fetch_outer_number = inner[0]
outer_item_list.append(fetch_outer_number)
print(outer_item_list)
I think I have managed to do CASE 3, but I need some help with CASE 1 and CASE 2.
NOTE: mylist is a common input for all 3 cases.
You can iterate over mylist and check if all of the elements in the second sublist are available in myOtherlist:
def find_items(mylist, myOtherlist):
outer_item = []
for x, y in mylist:
if y and all(z in myOtherlist for z in y):
outer_item.append(x)
return outer_item
Usage:
>>> mylist = [[0, [1, 2]], [1, [1]], [2, [0, 2]], [3, [3]], [4, []], [5, []], [6, [0]]]
>>> find_items(mylist, [2, 3])
[3]
>>> find_items(mylist, [0, 2])
[2, 6]
>>> find_items(mylist, [0])
[6]

Sum of two nested lists

I have two nested lists:
a = [[1, 1, 1], [1, 1, 1]]
b = [[2, 2, 2], [2, 2, 2]]
I want to make:
c = [[3, 3, 3], [3, 3, 3]]
I have been referencing the zip documentation, and researching other posts, but don't really understand how they work. Any help would be greatly appreciated!
You may use list comprehension with zip() as:
>>> a = [[1, 1, 1], [1, 1, 1]]
>>> b = [[2, 2, 2], [2, 2, 2]]
>>> [[i1+j1 for i1, j1 in zip(i,j)] for i, j in zip(a, b)]
[[3, 3, 3], [3, 3, 3]]
More generic way is to create a function as:
def my_sum(*nested_lists):
return [[sum(items) for items in zip(*zipped_list)] for zipped_list in zip(*nested_lists)]
which can accept any number of list. Sample run:
>>> a = [[1, 1, 1], [1, 1, 1]]
>>> b = [[2, 2, 2], [2, 2, 2]]
>>> c = [[3, 3, 3], [3, 3, 3]]
>>> my_sum(a, b, c)
[[6, 6, 6], [6, 6, 6]]
If you're going to do this a whole bunch, you'll be better off using numpy:
import numpy as np
a = [[1, 1, 1], [1, 1, 1]]
b = [[2, 2, 2], [2, 2, 2]]
aa = np.array(a)
bb = np.array(b)
c = aa + bb
Working with numpy arrays will be much more efficient than repeated uses of zip on lists. On top of that, numpy allows you to work with arrays much more expressively so the resulting code us usually much easier to read.
If you don't want the third party dependency, you'll need to do something a little different:
c = []
for a_sublist, b_sublist in zip(a, b):
c.append([a_sublist_item + b_sublist_item for a_sublist_item, b_sublist_item in zip(a_sublist, b_sublist)])
Hopefully the variable names make it clear enough what it going on here, but basically, each zip takes the inputs and combines them (one element from each input). We need 2 zips here -- the outermost zip pairs lists from a with lists from b whereas the inner zip pairs up individual elements from the sublists that were already paired.
I use python build-in function map() to do this.
If I have simple list a and b, I sum them as this way:
>>> a = [1,1,1]
>>> b = [2,2,2]
>>> map(lambda x, y: x + y, a, b)
[3, 3, 3]
If I have nested list a and b, I sum them as a similar way:
>>> a = [[1, 1, 1], [1, 1, 1]]
>>> b = [[2, 2, 2], [2, 2, 2]]
>>> map(lambda x, y: map(lambda i, j: i + j, x, y), a, b)
[[3, 3, 3], [3, 3, 3]]

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