0

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?

Alvin
  • 59
  • 1
  • 6
  • 1
    Just some pedantics: `1` is not mutable, though `[1]` is. What changes when you do `lst[idx] = val` is that the *binding* of `lst[idx]` is changed, the original value that the binding referred to remains untouched. Once you grok the underlying binding/immutability concepts, your Python skills reach a new level. – paxdiablo Jan 10 '21 at 22:58
  • @paxdiablo. Ok I edited my question for future references. – Alvin Jan 10 '21 at 23:04

6 Answers6

2

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]
Jab
  • 26,853
  • 21
  • 75
  • 114
0

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]]
Synthase
  • 5,849
  • 2
  • 12
  • 34
  • 2
    This will just create three references to the same underlying copy, effectively changing nothing. – Paul M. Jan 10 '21 at 22:54
  • Thanks. I thought `copy()` will create a shallow copy according to the [docs](https://docs.python.org/3/library/copy.html). Did you mean `deepcopy()`? – Alvin Jan 10 '21 at 22:56
  • Well, I may be wrong on the exact terminology I edited. copy() is sufficient to do what you want tho, as deepcopy() should be too I think. – Synthase Jan 10 '21 at 22:57
  • @Synthase Well, of course your code "works", because `a` is a list of integers. Integers are immutable. The OP was specifically asking about a collection of mutable objects. Try this with a list of lists. – Paul M. Jan 10 '21 at 22:57
  • @Paul, as far as I see, the OP took integers in the example. Sorry to use the OP's example. – Synthase Jan 10 '21 at 22:59
  • @Synthase I agree, the OP didn't make it immediately obvious what they were after by using a list of integers as an example. – Paul M. Jan 10 '21 at 23:00
  • Changed my example into a list of lists. Sorry about the unclear example. – Alvin Jan 10 '21 at 23:06
0

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]].

Alvin
  • 59
  • 1
  • 6
0
a = [1,2,3]
a = a*3  
a[0] =0
print(a)
  • If you get the id of the first two `2`s (`id(a[1])` and `id(a[4])`), you'll see they're the *same* object. – paxdiablo Jan 10 '21 at 23:06
0

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)]
Alain T.
  • 40,517
  • 4
  • 31
  • 51
0

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]]]
Prune
  • 76,765
  • 14
  • 60
  • 81