Flatten lists. Is a more concise solution possible?

Asked

Viewed 320 times

6

I solved the following Python exercise:

Implement a function that receives a list of length lists any and return a list of a dimension.

The solution I was able to make was this::

#!/usr/bin/env python
#-*- coding: utf-8 -*-

def list_unifier(lista_de_listas):
    lista_unificada = []
    for i in range(0, len(lista_de_listas)):
        for j in range(0, len(lista_de_listas[i])):
            lista_unificada.append(lista_de_listas[i][j])

    return lista_unificada

print list_unifier([[1, 2], [3, 4, 5], [], [6, 7]])

Exit:

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

The question is, would this be the best way to do it? I found it a little long.

I was thinking something involving for i in lista_de_listas or something like that.

  • Several examples here: https://stackoverflow.com/questions/952914/making-a-flat-list-out-of-list-of-lists-in-python

  • @Bruno Missed specify on question but it would be something without any advanced feature, only basic flow control. I’m still on language ABC. :)

4 answers

7


One idea that shortens your code without calling in additional packages is:

def list_unifier(lista_de_listas):
    lista_unificada = []
    for lista in lista_de_listas:
        for elemento in lista:
            lista_unificada.append(elemento)
    return lista_unificada

Calling the function:

print(list_unifier([[1, 2], [3, 4, 5], [], [6, 7]])) # Sintaxe válida para Python 3 ou 2 
print list_unifier([[1, 2], [3, 4, 5], [], [6, 7]]) # Sintaxe válida apenas para Python 2

Upshot:

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

I renamed the iterators of each for loop to make it easier to understand. First we use a for to select each list from the list list. Then we use a second for to take each element from the selected list. Since we select the element and not its position, we do not need to use indexes to do the append, just call lista_unificada.append(elemento).

  • That’s what I was looking for! Thank you!

5

In the style of antigravity and, in general, more pythonic, you can use the native module itertools to solve the problem, more specifically the function chain, that does exactly what is desired (ie, already exists natively the function that is asked to implement).

from itertools import chain

listas = [[1, 2], [3, 4, 5], [], [6, 7]]
resultado = list(chain(*listas))

print(resultado)

Will be printed [1, 2, 3, 4, 5, 6, 7]. It is important to note that this solution uses deconstruction of lists, with the operator * preceding the object and that the return of chain is a generator, which can greatly optimize the solution, since it would not be necessary to have a complete copy of all values in memory.

I am aware that the objective of the exercise was precisely to define the function to apply the basic concepts, mainly loops, but I believe that studying the concepts applied to the present solution are fundamental and therefore I decided to post the answer.

  • I went to try to understand what the chain() makes and then went into generators and into the magical behavior of the keyword yield and then it was too advanced for me (for a second day of Python at least)... not to mention I didn’t understand the operator *. What I understood was that list() converts an iterable to a list and that therefore chain() is returning an iterable that runs through one by one the list elements (which is an iterable of eternal). Is that it? Ah, and thank you for the explanation :)

  • 1

    Exact. The generator is an iterable with a pointer-like behavior of a iterator, if you are already familiar with these names. In other words, it represents only one value of what is being iterated at a time. Even if the list had millions of values, only one value would be stored in memory at a time, so the optimization. The operator * Deconstruct the list, maybe it’s easier to search for those terms. Basically what it does, in this case, each item in the list in a function parameter. Do foo(*[x, y]) would be the same as foo(x, y).

4

reduce!

Python2:

listas = [[1, 2], [3, 4, 5], [], [6, 7]]
reduce(lambda prev, next: prev + next, listas)
[1, 2, 3, 4, 5, 6, 7]

This is for list lists. But what about list lists for list lists...anyway, and for arbitrary depth? reduce recursive!

lista = [[10], [20, 30], [20, [40, 50, 60, [79, [40, [59, 66, 77]]]]]]
def makeItFlat(lista):
    return [lista] if not isinstance(lista, list) else reduce(lambda x, y: makeItFlat(x) + makeItFlat(y), lista, [])
res = makeItFlat(lista)
print(res)

[10, 20, 30, 20, 40, 50, 60, 79, 40, 59, 66, 77]

Note: in Python3 reduce is in the functools module.

3

The current responses "leave shallow" lists of lists. In this case, leaving shallow is all in the same depth. Objects that were previously at a depth of 2 lists are at the depth of a list only. What if it was possible to inform an arbitrary depth to "make it shallow"? For example, the deep list...

[1, 2, [[[3, [4], 5, [6, 7, [[8, 9], 10, [11]], 12], 13], 14], 15], 16, [17, [[[18]]], 19], 20]

... would be turned into this shallow list:

[1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20]

The idea to solve this problem is to have a recursive function shallowfy (i believe that "rasificar" does not have the same impact, so I preferred to use the version of the name in English same) that does the service. The entry of shallowfy is a list and its return is a shallow list. Its operation is as follows:

  1. start with an empty list shallow
  2. for each element el of the entry list
    • if el is not a list, adds el at the end of shallow
    • if el is a list, get shallow_el = shallowfy(el)
    • add all elements of shallow_el at the end of shallow
  3. returns shallow

The function is like this:

def shallowfy(lista):
  shallow = []
  for el in lista:
    if (isinstance(el, list)):
      shallow_el = shallowfy(el)
      for subel in shallow_el:
        shallow.append(subel)
    else:
      shallow.append(el)
  return shallow

See working on ideone.

References:

  • I think the English term is "Flatten".

Browser other questions tagged

You are not signed in. Login or sign up in order to post.