FlatMap - pythonic way

0

Programuję zaowodowo w javie,js i php, teraz piszę sobie domowy tool w pythonie żeby poznać język, jednak natrafiłem na problem, i chciałbym poznać pythonic-way.

Otóż mam sobie listę

lista = ['Foo', 'Bar', 'Lorem\nIpsum\ndolor\nsit\namet'] 

i chciałbym, to co na w javie możnaby zrobić flatMap(word-> word.split("\n")), żeby dostać efekt

result = ['Foo', 'Bar', 'Lorem', 'Ipsum', 'dolor', 'sit', 'amet']

Tylko nie chcę robić map+split, a potem jakoś flatten'ować tą listę, bo czuję że nie tak się to robi w pythonie.

To co zrobiłem i nie działa:
Wiem że jest operator destruct w pythonie, więc możaby zrobić tak:

lista = ['Foo', 'Bar', *(tutaj splitowanie tego stringa)]

ale zrobienie tego dla dynamicznych danych juź mi nie działa:

return [*(_split(name)) for name in params.values()]

spodziewam się że destruct nie może być użyty w taki dynamiczny sposób? Nie wiem.

Jak to zrobić?

PS: Oczywiście googlowałem "python flat map" ale dostawałem albo jakieś zagnieżdżone pętle (to już folę map+split+flatten), albo jakieś overengineeringi ze ściągnaiem dodatokwych modułów.

2

Dobra, znalazłem.

Pythonic way to nawidoczniej:

flat = [y for x in my_list for y in x]
0

Jeśli szukasz wyjaśnienia dlaczego nie można uzywać star-expressions w wyrażeniu listowym, jest tak dlatego, że jest to szczególny przypadek wywołania funkcji, gdzie chcesz wypełnić resztę argumentów, z tym że tutaj akurat tą funkcją jest konstruktor listy. Dlatego taki zapis w wyrażeniu listowym ma mały sens.

2

Warto też wspomnieć, że często nie ma sensu szukać na siłę cwanych one-linerów, bo liczy się czytelność, a nie zwięzłość. Jak napiszesz

output = []
for s in lista:
    output.extend(s.split('\n'))

to ci raczej żaden Pythonista głowy nie urwie

0
Spearhead napisał(a):

to ci raczej żaden Pythonista głowy nie urwie

Urwie, to aż się prosi o użycie list comprehension.

0
Spearhead napisał(a):

Warto też wspomnieć, że często nie ma sensu szukać na siłę cwanych one-linerów, bo liczy się czytelność, a nie zwięzłość. Jak napiszesz

output = []
for s in lista:
    output.extend(s.split('\n'))

to ci raczej żaden Pythonista głowy nie urwie

Nie użyjesz tego jako expression, będziesz musiał to wsadzić do funkcji żeby tego używać jakoś sensownie. Poza tym to wygląda bardzo proceduralnie (jak np w c++, javie lub innych). Jestem przekonany że w wyżej poziomowym języku takim jak python jest od tego dedykowana składnia.

1

@TomRiddle:
Alternatywą do list-comprehension może być jeszcze chain.

from itertools import chain
it = chain.from_iterable(map(_split, lista)) # zwraca generator
list(it)
# >> ['Foo', 'Bar', 'Lorem', 'Ipsum', 'dolor', 'sit', 'amet']

Edit: W sumie jest też taka biblioteczka toolz, która próbuje łatać dziury w bibliotece standardowej i ma mapcat.

2

Proponuję tak:

from functools import reduce
from operator import concat

sublist = ['Foo', 'Bar', 'Lorem\nIpsum\ndolor\nsit\namet']
flat = reduce(concat, [x.split('\n') for x in sublist])

albo:

from itertools import chain

sublist = ['Foo', 'Bar', 'Lorem\nIpsum\ndolor\nsit\namet']
flat = list(chain.from_iterable(x.split('\n') for x in sublist))

albo:

sublist = ['Foo', 'Bar', 'Lorem\nIpsum\ndolor\nsit\namet']
flat = sum([x.split('\n') for x in sublist], [])

Chociaż te ostatnie, to taki żart. Pierwsze chyba będzie najszybsze, jeżeli to ma jakieś znaczenie.

1
Haskell napisał(a):

Proponuję tak:

flat = reduce(concat, [x.split('\n') for x in sublist])
flat = sum([x.split('\n') for x in sublist], [])

Te propozycje są O(N**2).

0
Mózg napisał(a):

Te propozycje są O(N**2).

Zgadza się, ale póki co tylko takie się pojawiły, więc masz okazję się wykazać.

1

Pierwsze chyba będzie najszybsze

>>> L = ['a'] * 2**16

>>> %timeit reduce(concat, [x.split('\n') for x in L])
13.6 s ± 59.4 ms per loop (mean ± std. dev. of 7 runs, 1 loop each)

>>> %timeit sum([x.split('\n') for x in L], [])
13.5 s ± 22.1 ms per loop (mean ± std. dev. of 7 runs, 1 loop each)

>>> %timeit list(chain.from_iterable(x.split('\n') for x in L))
18.3 ms ± 53.3 µs per loop (mean ± std. dev. of 7 runs, 100 loops each)

(Hint: w sum i reduce dodajesz listę do listy.)

Wygrał Kalrais.

>>> %timeit list(chain.from_iterable(map(str.splitlines, L)))
10.3 ms ± 46.8 µs per loop (mean ± std. dev. of 7 runs, 100 loops each)

>>> 
1

Aby uzupełnić odpowiedź @Mózg , oto czasy wraz z tym zaakceptowanym rozwiązaniem:

In [1]: L = ['a'] * 2**16

In [2]: from itertools import chain

In [3]: from operator import concat

In [4]: from functools import reduce

In [5]: %timeit reduce(concat, [x.split('\n') for x in L])
9.14 s ± 153 ms per loop (mean ± std. dev. of 7 runs, 1 loop each)

In [6]: %timeit sum([x.split('\n') for x in L], [])
9.42 s ± 108 ms per loop (mean ± std. dev. of 7 runs, 1 loop each)

In [7]: %timeit list(chain.from_iterable(x.split('\n') for x in L))
15.4 ms ± 2.06 ms per loop (mean ± std. dev. of 7 runs, 100 loops each)

In [8]: %timeit [y for x in L for y in x.split('\n')]
13.1 ms ± 288 µs per loop (mean ± std. dev. of 7 runs, 100 loops each)

Jak widać, faktycznie jest to najlepsze rozwiązanie.

2

Da się jeszcze szybciej i jeszcze prościej:

'\n'.join(lista).split('\n') 

Na moim sprzęcie:

L = ['a'] * 2**16
%timeit [y for x in L for y in x.split('\n')]
# 11.7 ms ± 139 µs per loop (mean ± std. dev. of 7 runs, 100 loops each)
%timeit '\n'.join(L).split('\n')
# 1.08 ms ± 14.3 µs per loop (mean ± std. dev. of 7 runs, 1000 loops each)

1 użytkowników online, w tym zalogowanych: 0, gości: 1