Why is this one wrong?

I read a comment on here and it stated combining the lists. So why won’t the exercise take this result instead of the solution given?

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

Add your function here

def flatten(lists):
results =
one_list = lists[0] + lists[1]
for numbers in range(len(one_list)):
results.append(one_list[numbers])
return results

print flatten(n)

On print it gives the same result. So, what is the difference? Why is this one wrong?

.append() can only take a single argument. If that argument is a list or any other sequence it will be typed and appended as that sequence. It does not unpack arguments.

Consider the following sketch…

>>> def flatten(alist):
	r = []
	for n in alist:
		r += n
	return r

>>> flatten([[1, 2, 3], [4, 5, 6, 7, 8, 9]])
[1, 2, 3, 4, 5, 6, 7, 8, 9]
>>> 

We call this a sketch as it is not exhaustive and comes with numerous edge cases, and will not flatten lists in lists. For that we need recursion that drills down into all the dimensions of the list, not just the first two. Observe that our function above suffices to flatten a two dimension list which means we can use it as the function we recurse.


First assault on the sketch…

>>> flatten(flatten([[1, 2, 3], [4, 5, 6, 7, 8, 9]]))
Traceback (most recent call last):
  File "<pyshell#106>", line 1, in <module>
    flatten(flatten([[1, 2, 3], [4, 5, 6, 7, 8, 9]]))
  File "<pyshell#104>", line 4, in flatten
    r += n
TypeError: 'int' object is not iterable
>>> 

Just looking at the code we can see it does not concatenate primitives with sequences. That would need .append(). The above error occurs because the list is already flat, and one dimensional. It should be returned before the loop.


Writing the preliminary escape route…

>>> def flatten(alist):
	r = []
	for n in alist:
		r += n if hasattr(n, '__iter__') else [n]
	return r

>>> flatten(flatten([[1, 2, 3], [4, 5, 6, 7, 8, 9]]))
[1, 2, 3, 4, 5, 6, 7, 8, 9]
>>> 

This still very obviously has some edge cases and cannot handle three or more dimensions, yet. We are working toward a base case, though. That’s promising.


A recursive sketch

>>> def flatten(alist):
	r = []
	for n in alist:
		r += n if hasattr(n, '__iter__') else [n]
	return r if len(r) == len(list(filter(lambda _: not hasattr(_, '__iter__'), r))) else flatten(r)

>>> flatten(flatten([[1, 2, 3], [4, 5, 6, [7, 8, 9]]]))
[1, 2, 3, 4, 5, 6, 7, 8, 9]
>>> flatten([[1, 2, [3]], [[4, 5], 6, [7, 8, [9]]]])
[1, 2, 3, 4, 5, 6, 7, 8, 9]
>>> 

How does all this tie into the topic of nested loops? Recursion is a simulation of nested loops, that’s how. Each recursion could be simulated with a nested loop. But what if there are thousands of nests? The code bloat would reach explosive proportions (macro). Using a recursive function keeps the line count down to a much smaller number (micro). Mind it is still using memory with its call stack (and in some cases, significant amounts of memory). But editing the code is simpler so it comes down to computing power and available memory. Nowadays we have that in spades, even by the 2012 standards of my workstation.

>>> flatten([[1, 2, [3]], [[4, 5], 6, [7, 8, [9]]], [[[[[[[[[[]]]]]]]]]]])
[1, 2, 3, 4, 5, 6, 7, 8, 9]
>>> 

Now to return to the posted code sample,

one_list = lists[0] + lists[1]

This line of code could exhibit some very weird behavior, depending upon the datatype of those elements. It could 6 + 7, or 'Wee' + 'Gillis'. If one is a list and the other not, it fails. The case is far too presumptive, barring sufficient pretests and validation.

The other concern is the immediate assumption that the list contains only two, already flat lists. This is akin to using a hammer to drive in a screw. It limits the usability of this function to only one case, two dimensions. We know there can possibly be more so we ought never constrain a function in so dedicated a manner.

def flatten(alist):
    r = []
    for n in alist:
        r += n if hasattr(n, '__iter__') else [n]
    return r if len(r) == len(list(filter(
        lambda _: not hasattr(_, '__iter__'), r
    ))) else flatten(r)

print (flatten([[1, 2, [3]], [[4, 5], 6, [7, 8, [9]]], [[[[[[[[[[0]]]]]]]]]]]))
print (flatten(((1, 2, (3)), ((4, 5), 6, (7, 8, (9))), ((((((((((0)))))))))))))
============== RESTART: C:/../flatten_alist.py ==============
[1, 2, 3, 4, 5, 6, 7, 8, 9, 0]
[1, 2, 3, 4, 5, 6, 7, 8, 9, 0]
>>> 

We can still make this crash in all kinds of ways That’s why it’s still a sketch. We’re still identifying quantifiable parameters. A key concern would be the type of object permitted in the argument package. We are not equipped to deal with dict objects, for instance. Or what if the argument is a primitive?

Do you mind explaining to me why using r += n returns the error: int object is not iterable, only when alist contains 1 list, (I’m confused on why it’s not iterable), but when alist contains 2 list’s within itself like so: alist = [[1,2,3,4],[5,6,7,8]], then your above function works and prints:

[1,2,3,4,5,6,7,8]

Because += works the same as .extend() which takes an iterable (a sequence).

Eg.

a = [1, 2, 3]
a += 4,
a
[1, 2, 3, 4]

Note the comma and how it casts a sequence.

a += 5, 6
a
[1, 2, 3, 4, 5, 6]

In the above we don’t need a comma at the end since we have more than one value in the sequence.

As an aside, this can be related to how tuples are declared.

a = (1)
a
1

Notice that a is not a tuple.

a = (1,)
a
(1,)

and now it is.

I don’t the python 2 course covers extend, but I understand the explanation as to what it’s purpose is, however as r is an empty list, surely it would just extend that empty list using the value held at index n within alist?

We cannot extend a list with a single value whether int or float.

a.extend([7, 8, 9])
a
[1, 2, 3, 4, 5, 6, 7, 8, 9]
a.extend([10])
a
[1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
a += [11]
a
[1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11]

Note that in the case a str, it is already a sequence.

a = ['a', 'b', 'c', 'd']
a += 'e'
a
['a', 'b', 'c', 'd', 'e']
a += 'fgh'
a
['a', 'b', 'c', 'd', 'e', 'f', 'g', 'h']

If it’s not possible to extend a list by a single value, why does it work when the index in which n represents is a whole list, ie when alist contains two separate list like so: [[1,2,3],[4,5,6]] n would represent [1,2,3] and then [4,5,6] ?

A single value is not a sequence unless it is a string or it is wrapped in [] or given as a tuple (1,), or simply as above, 1, Integers and floats are not iterable (not a sequence).

a = [1, 2, 3]
b = [4, 5, 6]
a += b
a
[1, 2, 3, 4, 5, 6]

Extending has nothing to do with indices. It literally extends the list.

I think I understand what you’re saying since an integer is not a comprised of a set of values, ie how a string is a list of letters, there’s nothing to iterate through within an integer. You also keep referring to a list which is two dimensional, are you referring to the fact said list only contains two separate lists within itself, like the example alist?

A simple list is one dimension. A list of lists is two dimensions.

alist = [
          [1, 2, 3],
          [4, 5, 6]
        ]

So does a three dimensional list look like:

alist = [ [1,2, [3,4] ], [5,6] ]

Yes. There is no limit to the number of dimensions of a list. Think in terms of nesting rather than dimensions if it makes it easier to grasp.