1

Today I had an interview and I was asked to print a list of list into one single list without using any for or while loop but you can use other built in function.

Here is the list:

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

The output will be [1, 2, 3, 4, 5, 6, 7, 8, 9].

Any idea how to go about this?

Martijn Pieters
  • 1,048,767
  • 296
  • 4,058
  • 3,343
James Sapam
  • 16,036
  • 12
  • 50
  • 73
  • 1
    This is rare: *you can use other built in function* ;-) – Ashwini Chaudhary Dec 05 '13 at 14:30
  • +1, @AshwiniChaudhary. Also, I'm curious what will answer to this question give to an interviewer. – alko Dec 05 '13 at 14:32
  • Hi Ashwini, which built in function. – James Sapam Dec 05 '13 at 14:32
  • related: [Flattening a shallow list in Python](http://stackoverflow.com/q/406121/4279) – jfs Dec 05 '13 at 14:39
  • I would like to choose everyone answer as my solution but stackoverflow doesnt' allow me to do that :( – James Sapam Dec 05 '13 at 14:39
  • related: [Flatten (an irregular) list of lists in Python](http://stackoverflow.com/q/2158395/4279) – jfs Dec 05 '13 at 14:40
  • @yopy, If I were you, I will vote for William Denman as an answer for the following reason: This is an interview Question, It is more appropriate to answer interview question without any particular library. Reduce is not a library but rather a primitive for functional programming. One has to think deep enough to understand how that works. – Yeo Dec 05 '13 at 14:51
  • @Yeo, totally agree, I would love to see some more answer and i ll do as you said. – James Sapam Dec 05 '13 at 14:52
  • @yopi, Let me further clarify what your interviewer means by no for or while loop. It means no iteration tools is allowed. That's what I interpret from the interviewer. If that's the case that means whoever answer with itertools library is disqualified. – Yeo Dec 05 '13 at 14:53
  • 1
    Yes that was the requirement. So, itertool was not allowed, but sum and reduce is allowed. – James Sapam Dec 05 '13 at 14:54

5 Answers5

5

Three options:

  1. You could sum the nested lists; sum() takes a second argument, a starting value, set that to an empty list:

    >>> sum(myList[0], [])
    [1, 2, 3, 4, 5, 6, 7, 8, 9]
    

    This works because sum() is essentially implemented as a loop:

     def sum(values, start=0):
         total = start
         for value in values:
             total += value
         return total
    

    which works with list concatenation, provided the start value is a list object itself. 0 + [1, 2, 3] would not work, but [] + [1, 2, 3] works just fine.

  2. You could use reduce() with operator.add(), which is essentially the same as sum(), minus the requirement to give a start value:

    from operator import add
    
    reduce(add, myList[0])
    

    operator.add() could be replaced with lambda a, b: a + b or with list.__add__ if imports are to be avoided at all cost.

    As the nested input list grows, operator.iadd() (in-place add, for lists the equivalent of list.extend()) will rapidly become a faster option:

    from operator import iadd
    
    reduce(add, myList[0], [])
    

    but this does need an empty list to start with.

  3. You could chain the lists using itertools.chain.from_iterable():

    >>> from itertools import chain
    >>> list(chain.from_iterable(myList[0]))
    [1, 2, 3, 4, 5, 6, 7, 8, 9]
    

All three solutions require that you use indexing to remove the outermost list, although you can also pass the one element in myList as a single argument to chain.from_iterable() with list(chain.from_iterable(*myList)) as well.

Of these options, reduce(add, ...) is the fastest:

>>> timeit.timeit("sum(myList[0], [])", 'from __main__ import myList')
1.2761731147766113
>>> timeit.timeit("reduce(add, myList[0])", 'from __main__ import myList; from operator import add')
1.0545191764831543
>>> timeit.timeit("reduce(lambda a, b: a.extend(b) or a, myList[0], [])", 'from __main__ import myList')
2.225532054901123
>>> timeit.timeit("list(chain.from_iterable(myList[0]))", 'from __main__ import myList; from itertools import chain')
2.0208170413970947

and comparing iadd versus add:

>>> timeit.timeit("reduce(add, myList[0])", 'from __main__ import myList; from operator import add')
0.9298770427703857
>>> timeit.timeit("reduce(iadd, myList[0], [])", 'from __main__ import myList; from operator import iadd')
1.178157091140747
>>> timeit.timeit("reduce(add, myListDoubled)", 'from __main__ import myList; myListDoubled = myList[0] + myList[0]; from operator import add')
2.3597090244293213
>>> timeit.timeit("reduce(iadd, myListDoubled, [])", 'from __main__ import myList; myListDoubled = myList[0] + myList[0]; from operator import iadd')
1.730151891708374

You could use recursion to avoid using a loop, to make this work for arbitrarily nested lists:

def flatten(lst):
    try:
        return flatten(sum(lst, []))
    except TypeError:
        return lst

Demo:

>>> flatten(myList)
[1, 2, 3, 4, 5, 6, 7, 8, 9]
>>> flatten(myList + myList)
[1, 2, 3, 4, 5, 6, 7, 8, 9, 1, 2, 3, 4, 5, 6, 7, 8, 9]
Martijn Pieters
  • 1,048,767
  • 296
  • 4,058
  • 3,343
  • 1
    for exactly this list, for chain approach, one can use *myList instead of mylist[0] – alko Dec 05 '13 at 14:34
  • Hi Martinjn, could you please explain me how sum solve this from the help(sum) , sum(...) sum(sequence[, start]) -> value Returns the sum of a sequence of numbers (NOT strings) plus the value of parameter 'start' (which defaults to 0). When the sequence is empty, returns start. – James Sapam Dec 05 '13 at 14:35
  • use `reduce(operator.iadd, lst[0], [])` to make it linear (`O(n)`-time) instead of quadratic (`O(n**2)`-time) algorithm. Initial value is provided to avoid changing `lst` inplace. – jfs Dec 05 '13 at 21:36
  • @J.F.Sebastian: Oooh, good point. The alternative non-import version is then `lambda a, b: a.extend(b) or a`. – Martijn Pieters Dec 05 '13 at 21:55
  • if imports are not allowed, you could use `list.__iadd__` instead of `operator.iadd`. – jfs Dec 05 '13 at 22:05
  • @J.F.Sebastian: except that `reduce(operator.add, myList[0])` blows the socks of `reduce(operator.iadd, myList[0])`, speed wise. – Martijn Pieters Dec 05 '13 at 22:05
  • @MartijnPieters: could you provide a script to try? How have you measured it (given that the code changes `myList` inplace)? – jfs Dec 05 '13 at 22:07
  • @J.F.Sebastian: the code does *not* change `myList` in place. Test with `timeit.timeit("reduce(iadd, myList[0], [])", 'from __main__ import myList; from operator import iadd')` versus `timeit.timeit("reduce(add, myList[0])", 'from __main__ import myList; from operator import add')`. – Martijn Pieters Dec 05 '13 at 22:09
  • @J.F.Sebastian: `add` gives me 1.0545191764831543, `iadd` 1.2767219543457031. – Martijn Pieters Dec 05 '13 at 22:10
  • `reduce(operator.iadd, myList[0])` from [your comment](http://stackoverflow.com/questions/20402558/how-to-print-list-of-list-into-one-single-list-in-python-without-using-any-for-o/20402688?noredirect=1#comment30484313_20402688) *does* change the list inplace. Are you really measuring the speed for `n==3`? Then you are measuring the difference between `reduce(a,b,c)` and `reduce(a,b)`. btw, it is 0.423ns vs. 0.559ns vs. 0.491ns for `(add,L)` vs. `(add, L, [])` vs. `(iadd, L, [])` where `ns` is nanoseconds. – jfs Dec 05 '13 at 22:20
  • @J.F.Sebastian: Sorry, yes, that comment is incomplete. I did test with the additional start argument. Because `add` only requires two arguments to `reduce()`, and `iadd` requires three, that is the comparison you have to make. – Martijn Pieters Dec 05 '13 at 22:24
  • @J.F.Sebastian: But indeed, `timeit.timeit("reduce(add, myList[0], [])", 'from __main__ import myList; from operator import add')`, so with additional third argument, is slower still, at 1.5228779315948486. – Martijn Pieters Dec 05 '13 at 22:26
  • correction: it is obviously `microseconds` in the previous comment: us, not ns. – jfs Dec 05 '13 at 22:27
  • @J.F.Sebastian: Try `>>> timeit.timeit("reduce(iadd, chain([], myList[0]))", 'from __main__ import myList; from operator import iadd; from itertools import chain')` next, it's competitive with `operator.add`; perhaps more so as more elements nested lists are added to `myList`. – Martijn Pieters Dec 05 '13 at 22:36
  • @MartijnPieters: it is misleading to talk about the fastest solution with a tiny input without explicitly mentioning it (sooner or later `O(n)` beats `O(n**2)` algorithm, it is already faster for `n==6` instead of `n==3` in this case). – jfs Dec 05 '13 at 23:00
4

If we assume no imports allowed and that we are still on 2.7.x,

reduce(lambda x,y:x+y,*myList)

A quick search show that this question, making a flat list out of lists, has been analyzed in depth: Making a flat list out of list of lists in Python and although in that thread there is no restriction on what functions you can use, they answer goes into great detail about the time complexity of using different methods. This is quite important, as it could be the follow up question in an interview.

Community
  • 1
  • 1
William Denman
  • 3,046
  • 32
  • 34
  • this is another awesome answer, – James Sapam Dec 05 '13 at 14:36
  • 1
    And Another assumption is running on Python2 Because in Python3 you need to import the [reduce function](http://docs.python.org/3.0/library/functools.html#functools.reduce) – Yeo Dec 05 '13 at 14:37
  • @Yeo I didn't know that. Very good to know. Still on 2.7.x because of some scientific tools I am using. – William Denman Dec 05 '13 at 14:40
  • 2
    @yopy, If I were you, I will vote this one as an answer for the following reason: This is an interview Question, It is more appropriate to answer this without any particular library. Reduce is not a library but rather a primitive functional programming. One has to think deep enough to understand how this works. :) +1 – Yeo Dec 05 '13 at 14:48
  • @Yeo gee thanks! Functional programming is indeed a strange, yet beautiful beast. – William Denman Dec 05 '13 at 14:52
2
myList = [[[1,2,3],[4,5],[6,7,8,9]]]

sum(myList[0], [])

Output

[1, 2, 3, 4, 5, 6, 7, 8, 9]
koffein
  • 1,792
  • 13
  • 21
1

Use itertools.chain.from_iterable:

In [34]: from itertools import chain

In [35]: list(chain.from_iterable(myList[0]))
Out[35]: [1, 2, 3, 4, 5, 6, 7, 8, 9]
Ashwini Chaudhary
  • 244,495
  • 58
  • 464
  • 504
1

try this

import itertools

list(itertools.chain(*mylist))
fahad
  • 2,999
  • 3
  • 17
  • 20