26
loading...
This website collects cookies to deliver better user experience
[[1, 2], [3, 4], [5, 6, 7], [8]]
-> [1, 2, 3, 4, 5, 6, 7, 8]
?[[1, 2], [4, 5], [[[7]]], [[[[8]]]]]
-> [1, 2, 3, 4, 5, 6, 7, 8]
?[1, 2, 3, [4, 5, 6], [[7, 8]]]
-> [1, 2, 3, 4, 5, 6, 7, 8]
?[[1, 2], "three", ["four", "five"]]
-> [1, 2, "three", "four", "five"]
[[1, 2], (3, 4), (5, 6, 7), [8]]
-> [1, 2, 3, 4, 5, 6, 7, 8]
flatten()
function in Python. pytest
.sum
from the standard librarysum
functionitertools.chain
[[1, 3], [2, 5], [1]]
and we want to flatten it. [1, 3, 2, 5, 1]
. The first way of doing that is through list/generator comprehensions. We iterate through each sublist, then iterate over each one of them producing a single element each time. test_flatten
unit test.from typing import List, Any, Iterable
def flatten_gen_comp(lst: List[Any]) -> Iterable[Any]:
"""Flatten a list using generators comprehensions."""
return (item
for sublist in lst
for item in sublist)
def test_flatten():
lst = [[1, 3], [2, 5], [1]]
assert list(flatten_gen_comp(lst)) == [1, 3, 2, 5, 1]
This function returns a generator, to get a list back we need to convert the generator to list.
============================= test session starts ==============================
flatten.py::test_flatten PASSED [100%]
============================== 1 passed in 0.01s ===============================
Process finished with exit code 0
>>> flatten_lambda = lambda lst: (item for sublist in lst for item in sublist)
>>> lst = [[1, 3], [2, 5], [1]]
>>> list(flatten_lambda(lst))
[1, 3, 2, 5, 1]
from typing import List, Any, Iterable
def flatten_gen_comp(lst: List[Any]) -> Iterable[Any]:
"""Flatten a list using generators comprehensions."""
return (item
for sublist in lst
for item in sublist)
>>> lst = [['hello', 'world'], ['my', 'dear'], 'friend']
>>> list(flatten(lst))
['hello', 'world', 'my', 'dear', 'f', 'r', 'i', 'e', 'n', 'd']
>>> from typing import List, Any, Iterable
>>> def flatten(lst: List[Any]) -> Iterable[Any]:
"""Flatten a list using generators comprehensions.
Returns a flattened version of list lst.
"""
for sublist in lst:
if isinstance(sublist, list):
for item in sublist:
yield item
else:
yield sublist
>>> lst = [['hello', 'world'], ['my', 'dear'], 'friend']
>>> list(flatten(l))
['hello', 'world', 'my', 'dear', 'f', 'r', 'i', 'e', 'n', 'd']
>>> lst = [[1, 2], 3, (4, 5)]
>>> list(flatten(lst))
[1, 2, 3, (4, 5)]
>>> lst = [[1, 2], 3, (4, 5), ["string"], "hello"]
>>> list(flatten(lst))
[1, 2, 3, (4, 5), 'string', 'hello']
set
.set
using the generator, then convert set
to list
.>>> from typing import List, Any, Iterable
>>> def flatten(lst: List[Any]) -> Iterable[Any]:
"""Flatten a list using generators comprehensions.
Returns a flattened version of list lst.
"""
for sublist in lst:
if isinstance(sublist, list):
for item in sublist:
yield item
else:
yield sublist
>>> lst = [[1, 2], 3, (4, 5), ["string"], "hello", 3, 4, "hello"]
>>> list(set(flatten(lst)))
[1, 2, 3, 'hello', 4, (4, 5), 'string']
sum
to create flattened lists? from typing import List, Any, Iterable
def flatten_sum(lst: List[Any]) -> Iterable[Any]:
"""Flatten a list using sum."""
return sum(lst, [])
def test_flatten():
lst = [[1, 3], [2, 5], [1]]
assert list(flatten_sum(lst)) == [1, 3, 2, 5, 1]
flatten.py::test_flatten PASSED
chain
function from the itertools
module. In a nutshell, chain
creates a single iterator from a sequence of other iterables. This function is a perfect match for our use case.import itertools
def chain(*iterables):
# chain('ABC', 'DEF') --> A B C D E F
for it in iterables:
for element in it:
yield element
def flatten_chain(lst: List[Any]) -> Iterable[Any]:
"""Flatten a list using chain."""
return itertools.chain(*lst)
def test_flatten():
lst = [[1, 3], [2, 5], [1]]
assert list(flatten_chain(lst)) == [1, 3, 2, 5, 1]
[OMITTED]
....
flatten.py::test_flatten PASSED
...
[OMITTED]
numpy.concatenate
function to flatten a regular list of lists.>>> import numpy as np
>>> lst = [[1, 3], [2, 5], [1], [7, 8]]
>>> list(np.concatenate(lst))
[1, 3, 2, 5, 1, 7, 8]
[[1, 3], [2, 5], 1]
or this [1, [2, 3], [[4]], [], [[[[[[[[[5]]]]]]]]]]
? flatten_recursive
on it. But better than words is code, so let’s see some code.from typing import List, Any, Iterable
def flatten_recursive(lst: List[Any]) -> Iterable[Any]:
"""Flatten a list using recursion."""
for item in lst:
if isinstance(item, list):
yield from flatten_recursive(item)
else:
yield item
def test_flatten_recursive():
lst = [[1, 3], [2, 5], 1]
assert list(flatten_recursive(lst)) == [1, 3, 2, 5, 1]
isinstance(item, list)
means it only works with lists. On the other hand, it will flatten lists of mixed types with no trouble.>>> list(flatten_recursive(lst))
[1, 2, 3, (4, 5), 'string', 'hello']
deque
to flatten the irregular list. Quoting the official docs:"Deques are a generalization of stacks and queues (the name is pronounced “deck” and is short for “double-ended queue”). Deques support thread-safe, memory efficient appends and pops from either side of the deque with approximately the same O(1) performance in either direction."
deque
to simulate the stacking operation of the recursive solution.deque
. The appendleft
method append the element to the leftmost position of the deque
, for example:>>> from collections import deque
>>> l = deque()
>>> l.appendleft(2)
>>> l
deque([2])
>>> l.appendleft(7)
>>> l
deque([7, 2])
my_deque.extendleft(reversed(item))
. Again, similar to a list
, extendleft
adds each item to the leftmost position of the deque
in series. As a result, deque
will add the elements in a reverse order. That’s exactly why we need to reverse the sub-list before extending left. To make things clearer, let’s see an example.>>> l = deque()
>>> l.extendleft([1, 2, 3])
>>> l
deque([3, 2, 1])
deque
removing the leftmost element and yielding it if it’s not a list. If the element is a list, then we need to extend it left. The full implement goes like this:from typing import List, Any, Iterable
def flatten_deque(lst: List[Any]) -> Iterable[Any]:
"""Flatten a list using a deque."""
q = deque()
for item in lst:
if isinstance(item, list):
q.extendleft(reversed(item))
else:
q.appendleft(item)
while q:
elem = q.popleft()
if isinstance(elem, list):
q.extendleft(reversed(elem))
else:
yield elem
def test_flatten_super_irregular():
lst = [1, [2, 3], [4], [], [[[[[[[[[5]]]]]]]]]]
assert list(flatten_deque(lst)) == [1, 2, 3, 4, 5]
============================= test session starts ==============================
...
flatten.py::test_flatten_super_irregular PASSED [100%]
============================== 1 passed in 0.01s ===============================
>>> list(flatten_deque(lst))
[1, 2, 3, (4, 5), 'string', 'hello']
timeit
module, we can invoke it in IPython
by doing %timeit [code_to_measure]
. The following list is the timings for each one of them. As we can see, flatten_chain
is the fastest implementation of all. It flatted our list lst
in 267 µs
avg. The slowest implementation is flatten_sum
, taking around 42 ms
to flatten the same list.In [1]: lst = [[1, 2, 3], [4, 5, 6], [7], [8, 9]] * 1_000
In [2]: %timeit list(flatten_gen_comp(lst))
615 µs ± 2.74 µs per loop (mean ± std. dev. of 7 runs, 1000 loops each)
In [3]: In [19]: %timeit list(flatten_sum(lst))
42 ms ± 660 µs per loop (mean ± std. dev. of 7 runs, 10 loops each)
In [4]: %timeit list(flatten_chain(lst))
267 µs ± 517 ns per loop (mean ± std. dev. of 7 runs, 1000 loops each)
In [5]: %timeit list(flatten_numpy(lst))
4.65 ms ± 14.3 µs per loop (mean ± std. dev. of 7 runs, 100 loops each)
In [6]: %timeit list(flatten_recursive(lst))
3.02 ms ± 174 µs per loop (mean ± std. dev. of 7 runs, 100 loops each)
In [7]: %timeit list(flatten_deque(lst))
2.97 ms ± 21.9 µs per loop (mean ± std. dev. of 7 runs, 100 loops each)