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?