1

I have two lists a b with a random number of elements (let's say 8) which I want to split in the point cp. After that, I want to keep the part of the list before cp as it was and change the part after cp for its part in the other list.

I use the following code:

cp = 4
a = [1, 3, 2, 4, 5, 8, 7, 6]
b = [3, 1, 5, 6, 2, 8, 4, 7]
parents = np.array([a,b])
parents[0][cp:], parents[1][cp:] = parents[1][cp:], parents[0][cp:]
print parents

# Print result:
# [[1 3 2 4 5 8 7 6]
#  [3 1 5 6 5 8 7 6]]

As show in the code, I am getting an error apparently because it assigns the values of the sub-array in parents[0] before the assignation has ended.

If I use the traditional python lists this seems to work fine:

a = [1,3,2,4, 5,8,7,6]
b = [3,1,5,6, 2,8,4,7]
a[cp:] , b[cp:] = b[cp:], a[cp:]
print a,b

# Print result:
# [1, 3, 2, 4, 2, 8, 4, 7] [3, 1, 5, 6, 5, 8, 7, 6]

Is there a way to make this work using the previous notation and not adding a third var?

Manuel Lagunas
  • 2,611
  • 19
  • 31
  • 2
    It is probably because `parents[1][cp:]` is a *view* and not a real list. – Willem Van Onsem Feb 15 '17 at 00:15
  • 2
    NumPy slicing creates views, rather than copying data. (Also, don't do `parents[0][cp:]`; do `parents[0, cp:]`. Those syntaxes are only equivalent for a small subset of possible cases. While this case happens to be in that small subset, it's best not to rely on that, so you don't get screwed when you unthinkingly do something like `arr[:5][1]`.) – user2357112 Feb 15 '17 at 00:16

1 Answers1

9

The difference between numpy arrays and lists:

lst[cp:]  # makes a copy
array[cp:]  # no copy

Note that even using a temporary variable doesn't actually help you out here due to the lack of copying:

import numpy as np

cp = 4
a = [1, 3, 2, 4, 5, 8, 7, 6]
b = [3, 1, 5, 6, 2, 8, 4, 7]
parents = np.array([a,b])
tmp = parents[0][cp:]
parents[0][cp:] = parents[1][cp:]
parents[1][cp:] = parents[0][cp:]
print(parents)
# [[1 3 2 4 2 8 4 7]
#  [3 1 5 6 2 8 4 7]]

Solution

So we can re-write your line to force the copy on the right hand side -- then things should work out OK:

parents[0][cp:], parents[1][cp:] = parents[1][cp:].copy(), parents[0][cp:].copy()

Also note that if you really want to be clever and save a few operations in the process, you only really need one copy:

parents[0][cp:], parents[1][cp:] = parents[1][cp:], parents[0][cp:].copy()

Basically this has the following order of operations:

  • Create view from parents[1][cp:] (call it v1 for the sake of discussion)
  • Create view from parents[0][cp:] and then copy it (call it c2)
  • Assign v1 to parents[0][cp:] (note this doesn't change c2 due to the copy)
  • Assign c2 to parents[1][cp:]. This will influence v1, but v1 has already been copied into it's final destination so that's OK.
mgilson
  • 300,191
  • 65
  • 633
  • 696
  • Great! it worked. Thanks! – Manuel Lagunas Feb 15 '17 at 00:19
  • _"you only really need one copy"_ - does python make any guarantee about which order tuple unpacking assignments occur in? That sounds like it might be in the realm of "CPython implementation detail" – Eric Feb 15 '17 at 01:28
  • 1
    @Eric -- I actually think that this is well defined behavior. From [here](https://docs.python.org/3.6/reference/simple_stmts.html#assignment-statements): "the items are assigned, from left to right, to the corresponding targets.". Since the right hand side is always evaluated first (from left to right), it works out exactly as I spelled out above (that's the _only_ way it could work given those constraints as far as I can tell anyway...) – mgilson Feb 15 '17 at 03:56
  • @mgilson: Nice find :) – Eric Feb 15 '17 at 08:12