Masz czasami tak, że używasz czegoś od lat, gdy nagle trafisz na coś przypadkiem. Co sprawia, że myślisz: „Hmmm… To jednak jest prostszy sposób”. Podpowiedź? To wbudowane funkcje Pythona.

Przeglądałem książkę.

Python w Analizie Danych – Wes McKinney

Rzut oka na spis treści. Moją uwagę przykuł poniższy fragment.

Fragment Spisu Treści

Jest rozdział o wbudowanych w Pythona strukturach danych: Krotkach, Listach, Słownikach i Zbiorach.

Dziwne dla mnie jest to, że autor rozdzielił je na pół jakimiś wbudowanymi funkcjami.

Ponieważ jestem ciekawski, sprawdzam więc, o co chodzi. Co tutaj jest takiego istotnego, żeby przerwać opowiadanie o intrygujących strukturach danych.

Małe rozeznanie. Już wiem.

Te 4 funkcje, które za chwile poznasz, ułatwiają pracę. Nie jest to jakaś olbrzymia zmiana. Bardziej chodzi o to, że dla mnie to było małe odkrycie.

Bo pewne rzeczy robiłem dookoła. Do tej pory nie wyszedłem poza pewne podstawy. Gdy więc widzę fragment kodu, gdzie ktoś zamiast moich kilku linijek kodu robi jedną (i nie chodzi o usuwanie spacji). Myślę sobie wtedy: „Wiedziałem, że musi być prostszy sposób”.

Nie ważne czy jesteś „świeżakiem” czy „wymiataczem”. Założę się, że co jakiś czas ciebie też coś zaskakuje.

Enumerate

Często przy pracy z sekwencjami/kolekcjami takimi jak listy czy krotki jest potrzeba wiedzieć, który element właśnie jest na tapecie.

Robiłem do tej pory tak:

randomlist= [1, 9, 88, 73, 71, 29, 79, 22, 48, 80]

# mój standardowy sposób rozwiązania problemu
i = 0
for x in randomlist:
    print('{}: {}'.format(i,x))
    i += 1
0: 1
1: 9
2: 88
3: 73
4: 71
5: 29
6: 79
7: 22
8: 48
9: 80

Później zacząłem wykorzystywać range(), w ten sposób:

# wykorzystanie range()
for x in range(len(randomlist)):
    print('{}: {}'.format(x,randomlist[x]))

Co zatem robi funkcja enumerate? I jak nam to ułatwia pracę?

Enumerate zwraca sekwencję krotek w postaci (i, x) — para iterator i wartość.

# wykorzystanie enumerate
for i, x in enumerate(randomlist,1):
    print('{}: {}'.format(i,x))

Elegancko mamy opakowaną kolekcję z podaniem numeru elementu.

Sorted

Funkcji sorted w ogóle nie używałem. Stosowałemsort działającą na listach. Czyli jak była krotka to zamiana na listę później sort itd. W dodatku sort zmienia istniejącą listę. Dużo zachodu.

Funkcja sorted zwraca nową posortowaną sekwencję. Przyjmuje te same argumenty co funkcja sort w tym reversejeśli chcemy sortować malejąco.

print(sorted(randomlist))
[1, 9, 22, 29, 48, 71, 73, 79, 80, 88]
print(sorted(randomlist,reverse=True))
[88, 80, 79, 73, 71, 48, 29, 22, 9, 1]

Reversed

Jak chciałem odwrócić sekwencję. To robiłem mniej więcej tak.

randomlist = [1, 9, 88, 73, 71, 29, 79, 22, 48, 80]
odwrocona_lista = []
for x in range(len(randomlist)-1,-1,-1):
    odwrocona_lista.append(random_list[x])
print(odwrocona_lista)

Właśnie wymyśliłem koło na nowo.

Jaka jest alternatywa?

Funkcja reversed, która jest generatorem (wiąże się to z oszczędzaniem pamięci). Nie tworzy odwróconej sekwencji do czasu jej materializacji, czyli dopiero jak pojawi się jako lista lub w pętli.

print(reversed(randomlist))
<list_reverseiterator object at 0x000002C7E8F10D60>
print(list(reversed(randomlist)))
[80, 48, 22, 79, 29, 71, 73, 88, 9, 1]

Zip

Nie chcę już opisywać, jak robiłem, bo szkoda na to czasu i miejsca. W każdym razie dzięki tej funkcji możemy np. na raz przetwarzać kilka sekwencji, robiąc jeden obrót pętlą.

Funkcja zip „paruje” elementy wielu list, krotek lub innych sekwencji w celu utworzenia listy krotek. Co ciekawe dzięki tej funkcji możemy też rozpakować listy krotek. Należy jednak pamiętać, że lista będzie miała długość najkrótszej sekwencji. Zobacz sam, jak to działa.

# dwie urocze maksymy związane z czasem, sekwencje z sentencjami :)
sentencja = 'Ucz się z wczoraj, żyj dla dzisiaj, miej wiarę w jutro'
sentencja2 = 'Czas płynie zbyt szybko. Myśli dręczą zbyt długo. Piękne chwile mijają bezpowrotnie.'
zzipowane_sentencje = zip(sentencja.split(), sentencja2.split())
type(zzipowane_sentencje)
zip
list(zzipowane_sentencje)
[('Ucz', 'Czas'),
 ('się', 'płynie'),
 ('z', 'zbyt'),
 ('wczoraj,', 'szybko.'),
 ('żyj', 'Myśli'),
 ('dla', 'dręczą'),
 ('dzisiaj,', 'zbyt'),
 ('miej', 'długo.'),
 ('wiarę', 'Piękne'),
 ('w', 'chwile'),
 ('jutro', 'mijają')]

Jak widać, o liczbie elementów w liście decyduje najkrótsza sekwencja. Sprawdźmy to, dodając jeszcze jedną sentencję?

# nie przejmujemy się już czasem
sentencja3 = 'Carpe Diem'
list(zip(sentencja.split(), sentencja2.split(), sentencja3.split()))
[('Ucz', 'Czas', 'Carpe'), ('się', 'płynie', 'Diem')]

Tak się ‚pakuje’ funkcją zip. Jest jednak możliwość rozpakowania listy krotek za pomocą tej funkcji.

# 3 sentencje łacińskie
sentencja1 = 'Ab alio expectes, alteri quod feceris'
sentencja2 = 'Actus hominis, non dignitas iudicetur'
sentencja3 = 'Ad quas res aptissimi erimus, in iis potissimum elaborabimus'

# zipowanie
zzipowane = zip(sentencja1.split(), sentencja2.split(), sentencja3.split())

# obiekt zip zamieniamy na listę krotek
sentencje = list(zzipowane)

print(sentencje)
[('Ab', 'Actus', 'Ad'),
 ('alio', 'hominis,', 'quas'),
 ('expectes,', 'non', 'res'),
 ('alteri', 'dignitas', 'aptissimi'),
 ('quod', 'iudicetur', 'erimus,')]
# rozpakowywanie listy krotek za pomocą zip(*sentencje)
a,b,c = zip(*sentencje)
' '.join(a)
'Ab alio expectes, alteri quod'
' '.join(b)
'Actus hominis, non dignitas iudicetur'
' '.join(c)
'Ad quas res aptissimi erimus,'

Tylko druga najkrótsza została taka jak na początku.

Podsumowanie

Ciekaw jestem czy też miałeś jakieś momenty „Aha”. Być może nie. Bo zmiany, jakie tutaj widzisz, nie są olbrzymie.

Nic spektakularnego. Nic seksownego.

Jeśli jednak spojrzymy na to tak. Że jeśli codziennie, powiedzmy nawet o 0.01%, ulepszalibyśmy coś w naszej pracy. Efekt kumulowałby się niczym kula śnieżna. Taki procent składany.

Po czasie ze zdumieniem spojrzymy na początki. Sami nie uwierzymy, jak udało nam się dojść tu do tego miejsca, w którym właśnie jesteśmy.

Z taką myślą chciałbym Cię zostawić.

Taktyka Killera
Kategorie: Narzędzia