-1

I have a list of numbers and I want to extract N elements as lists, and store them in another list. Example:

list1 = [1,2,3,4,5,6,7,8,9]
resultList = [[1,2,3],[4,5,6],[7,8,9]]

I've done the following

def getLines(square, N):
    i = 0

    line  = [None]*N
    lines = list()

    for elt in square:
        line[i] = elt
        i += 1
        if i == N:
            lines.append(line)
            i = 0

return lines

Why do I always get the last list three times

[[7,8,9],[7,8,9],[7,8,9]]

when I call the function getLines(list1, 3).

I also tried to eliminate the temporary list and add the elements directly to resultList like this:

def getLines(square, N):
    i = 0
    j = 0
    lines = [[None]*N]*N # Need to be initialized to be able to index it.

    for elt in square:
        lines[i][j] = elt
        j += 1
        if j == N:
            i += 1
            j = 0

return lines

The last group is still appearing N times. Any hints on how to fix that?

cl3m
  • 2,791
  • 19
  • 21
Ouss4
  • 479
  • 4
  • 11

2 Answers2

1

You might consider solving this like:

def getLines(square, N):
    return [square[i:i + N] for i in range(0, len(square), N)]

For example: getLines([1, 2, 3, 4, 5, 6, 7, 8, 9], 3) will return [[1, 2, 3], [4, 5, 6], [7, 8, 9]], or getLines([1, 2, 3, 4, 5, 6, 7, 8, 9], 2) results in [[1, 2], [3, 4], [5, 6], [7, 8], [9]], and so on.

Zorgmorduk
  • 1,265
  • 2
  • 16
  • 32
  • That's really neat thank you. I was going to try to do it with list comprehension, but got caught trying to solve the last problem. – Ouss4 Apr 13 '16 at 20:07
1

This is because you are creating only one inner list object, and altering it.
In pseudocode, what you are doing is:

  • Create a list called line assigning [None, None, None] to it
  • Create an empty list called lines
  • For three times:
    -- Pick n items from the square list
    -- Assign these three items to line[0], line[1] and line[2]
    -- Append line to lines

So, what you are doing is assigning to individual items of line. This is important - you're not making a new object each time, you're changing individual items in the line list.
At the end of it all, line will point to the list [7, 8, 9]. And you can see lines as being substantially [line, line, line] (a list of three times the same object), so specifically now it will point to [[7,8,9], [7,8,9], [7,8,9]].

To solve this, possibly the solution that most keeps your original code is to re-define line after appending it. This way, the variable name line will refer to a different list each time, and you won't have this problem.

def getLines(square, N):
    i = 0

    line  = [None]*N
    lines = list()

    for elt in square:
        line[i] = elt
        i += 1
        if i == N:
            lines.append(line)
            line  = [None]*N # Now `line` points to a different object
            i = 0

    return lines

Of course, there is leaner, more Pythonic code that can do the same thing (I see that an answer has already been given).

EDIT - Ok, here goes a somehow more detailed explanation.
Perhaps one of the key concepts is that lists are not containers of other objects; they merely hold references to other objects.
Another key concept is that when you change an item in a list (item assignment), you're not making the whole list object become another object. You're merely changing a reference inside it. This is something we give for granted in a lot of situations, but somehow becomes counter-intuitive when we'd want things to go the other way and "recycle" a list.

As I was writing in the comments, if list was a cat named Fluffy, every time you're appending you're creating a mirror that points to Fluffy. So you can dress Fluffy with a party hat, put a mirror pointing to it, then give Fluffy a clown nose, put on another mirror, then dress Fluffy as a ballerina, add a third mirror, and when you look at the mirrors, all three of them will show the ballerina Fluffy. (Sorry Fluffy).

What I mean is that in practice in your first script, when you do the append:

    lines.append(line)  

by the first concept I mentioned, you are not making lines contain the current status of line as a separate object. You are appending a reference to the line list.

And when you do,

    line[i] = elt

by the second concept, of course line is always the same object; you're just changing what's referenced at the i-th position.

This is why, at the end of your script, lines will appear to "contain three identical objects": because you actually appended three references to the same object. And when you ask to see the content of lists, you will read, three times, the list object in its current status.

In the code I provided above, I re-define the name lists to make it reference a brand new list every time it's been appended to lists:

        lines.append(line)  
        line  = [None]*N # Now `line` points to a different object  

This way, at the end of the script I have "three different cats" appended, and each one was conveniently named Fluffy just until I had appended it, to give room for a new Fluffy list after that.

Now, in your second script, you do something similar. The key instruction is:

    lines = [[None]*N]*N # Need to be initialized to be able to index it.

In this line, you are creating two objects:
- the list [None, None, None]
- the list named lines, which contains N references to the same list [None, None, None].

What you did was just to create straight away Fluffy and the three mirrors pointing at him.
In fact if you change lines[0][2], or lines[1][2], you're just changing the same item [2] of your same Fluffy.

What you actually wanted to do is,

    lines = [[None]*N for i in range(N)]  

which creates three different cats - I mean, lists, and have lines point to the three.

Roberto
  • 2,696
  • 18
  • 31
  • Thank you for the explanation. But why does "lines" get appended only the last "line" ? I am changing "line" each time, shouldn't it be appended each time (in the if statement) ? – Ouss4 Apr 13 '16 at 20:16
  • @Ouss4 That's not the way you should see it. There is a crucial difference (which will be the specific thing you'll have learned by solving this issue), between naming an object, and assigning individually to an item in an object. – Roberto Apr 13 '16 at 20:30
  • @Ouss4 try to see it like this: I have a cat called Fluffy. (1) I'll put a green hat on it. I'll have a mirror point at Fluffy. (2) Now I'm going to put a clown hat on it. I'll have a second mirror point at Fluffy. (3) Now I'm going to dress it like a ballerina. I'll have a third mirror point at Fluffy. (4) And curiously enough, all the three mirrors show Fluffy dressed as a ballerina now. ...so in order for this to work, you need to use three cats (three different objects) instead of changing the hat on the same cat. – Roberto Apr 13 '16 at 20:34
  • Sorry, how could we apply the same reasoning to the second code snippet I gave? Shouldn't it change each value of "lines"? – Ouss4 Apr 13 '16 at 20:39
  • @Ouss4 it's the same basically. I'll edit my answer when I get home, it's too lengthy to write here, but you're basically just building up front the three mirrors that point to the same cat. – Roberto Apr 13 '16 at 20:59
  • @Ouss4 for added wow factor, try this: `a = [None]`, `a[0] = a` then check `a`. Infinite mirrors! – Roberto Apr 14 '16 at 16:27
  • 1
    It looks like Fluffy is facing a mirror and looking through this mirror to another mirror behind. (Well I get [[...]], some googling tells that it represents an infinite data structures with ellipses. Is that accurate?) Edit: It looks like we have some explanations [here](http://stackoverflow.com/questions/17160162/what-is-in-a-python-list) – Ouss4 Apr 15 '16 at 01:15