Operații de agregare
O operație de agregare transformă o colecție întreagă într-o singură valoare. Exemple sunt numărul de elemente, suma lor, cel mai mare element sau toate elementele combinate într-un singur rezultat.
Python îți oferă direct pe cele mai des folosite, ca funcții încorporate. len() numără elementele, min() și max() găsesc cel mai mic și cel mai mare element, iar sum() le adună.
Atenție: min() și max() ridică o eroare ValueError pe o listă goală și programul se oprește. Verifică mai întâi len(s) > 0 când lista poate fi goală.
Pentru a combina elementele unul câte unul există functools.reduce(). Ea păstrează o valoare curentă, acumulatorul, și apelează funcția ta cu acumulatorul și, pe rând, cu fiecare element. O singură funcție acoperă ceea ce multe limbaje împart în reduce și fold: fără al treilea argument pornește de la primul element, iar cu el pornește de la acea valoare inițială.
Agregări de bază
Cele mai folosite agregări sunt numărul de elemente, minimul, maximul, suma și media.
len(numbers)dă numărul de elemente.min(numbers)șimax(numbers)dau cel mai mic și cel mai mare element. Funcționează pentru numere și pentru șiruri.sum(numbers)adună toate elementele. Media este suma împărțită la numărul de elemente; împărțirea cu/păstrează mereu partea zecimală.
Uneori cel mai mic sau cel mai mare element trebuie ales după o regulă, nu după valoarea lui. Pentru asta min() și max() primesc o funcție key=: câștigă elementul cu cea mai mică sau cea mai mare cheie, iar rezultatul este elementul însuși.
Uneori fiecare element trebuie mai întâi transformat cu o funcție, iar rezultatele adunate. sum() primește o expresie generator, așa că sum(f(n) for n in numbers) face asta într-o singură linie.
Număr, minim, maxim, sumă și medie
Pentru numerele 6, 42, 10 și 4 afișează numărul de elemente cu len(), cel mai mare și cel mai mic element cu max() și min(), și suma cu sum(). Media este 62 / 4 = 15.5.
Ieșire: program Python complet
numbers = [6, 42, 10, 4]
total = sum(numbers)
print("Număr:", len(numbers))
print("Maxim:", max(numbers))
print("Minim:", min(numbers))
print("Medie:", total / len(numbers))
print("Sumă:", total)
Minim și maxim după o regulă
min() cu key=lambda n: n % 3 compară resturile. Resturile pentru 5, 42, 10 și 4 sunt 2, 0, 1 și 1, deci câștigă 42. max() cu key=len compară lungimile șirurilor și găsește "three".
Ieșire: program Python complet
numbers = [5, 42, 10, 4]
words = ["one", "two", "three", "four"]
# Elementul al cărui rest la împărțirea cu 3 este cel mai mic.
print(min(numbers, key=lambda n: n % 3))
# Cel mai lung cuvânt, comparat după lungime.
print(max(words, key=len))
Suma unei funcții aplicate fiecărui element
sum() adună tot ce produce expresia generator. Dublarea lui 5, 42, 10 și 4 dă 122. Înjumătățirea lor cu / dă 30.5.
Ieșire: program Python complet
numbers = [5, 42, 10, 4]
# Mai întâi transformă fiecare element, apoi adună rezultatele.
print(sum(n * 2 for n in numbers))
print(sum(n / 2 for n in numbers))
Fold și Reduce
Fold și reduce combină toate elementele într-o singură valoare, pas cu pas. Ele păstrează o valoare curentă numită acumulator. Pentru fiecare element apelează o funcție cu acumulatorul și elementul, iar funcția returnează noul acumulator.
Diferența este valoarea de pornire, iar în Python ea este pur și simplu al treilea argument al functools.reduce():
reduce(f, items, initial)este un fold: pornește de la valoarea inițială pe care i-o dai. Funcția rulează o dată pentru fiecare element.reduce(f, items)pornește de la primul element. Funcția rulează de la al doilea element încolo, deci primul element nu trece niciodată prin ea.
Asta contează când funcția modifică elementul. Dacă ea dublează fiecare element, reduce() fără valoare inițială lasă primul element neschimbat.
Python nu are un reduce de la dreapta. Ca să faci fold de la dreapta, rulează reduce() peste reversed(items): elementele sosesc de la ultimul la primul, iar funcția primește tot acumulatorul primul și elementul al doilea.
Reduce
reduce() fără valoare inițială ia 5 ca punct de pornire și îi adună 2, 10 și 4. Rezultatul este 21.
Ieșire: program Python complet
from functools import reduce
numbers = [5, 2, 10, 4]
# Fără valoare inițială, reduce pornește de la primul element.
total = reduce(lambda acc, element: acc + element, numbers)
print(total)
Fold
Cu valoarea inițială 0, reduce() pornește de la 0 și adună fiecare element înmulțit cu 2. Fiecare element este dublat: 10 + 4 + 20 + 8 = 42.
Ieșire: program Python complet
from functools import reduce
numbers = [5, 2, 10, 4]
# Al treilea argument este valoarea inițială: fiecare element este dublat.
sum_doubled = reduce(lambda acc, element: acc + element * 2, numbers, 0)
print(sum_doubled)
Fold comparat cu Reduce
Ambele apeluri folosesc aceeași funcție: acumulatorul plus elementul înmulțit cu 2. Fără valoare inițială rezultatul este 37, pentru că primul element, 5, este punctul de pornire și nu este dublat. Cu valoarea inițială 0 rezultatul este 42.
Ieșire: program Python complet
from functools import reduce
def add_doubled(acc, element):
return acc + element * 2
numbers = [5, 2, 10, 4]
# Aici 5 este valoarea de pornire, deci nu este dublat: 5 + 4 + 20 + 8
print("Reduce:", reduce(add_doubled, numbers))
# Fiecare element este dublat: 10 + 4 + 20 + 8
print("Fold:", reduce(add_doubled, numbers, 0))
Fold de la dreapta
reversed(numbers) îi dă elementele lui reduce() de la ultimul la primul. La adunare ordinea nu contează, așa că rezultatul este din nou 42.
Ieșire: program Python complet
from functools import reduce
numbers = [5, 2, 10, 4]
# reversed() dă elementele de la ultimul la primul.
sum_doubled_right = reduce(lambda acc, element: acc + element * 2,
reversed(numbers), 0)
print(sum_doubled_right)
Fold cu index
Uneori contează poziția unui element, nu doar valoarea lui.
În Python o buclă simplă cu enumerate() îți dă indexul. for idx, element in enumerate(numbers) parcurge lista de la început. for idx, element in reversed(list(enumerate(numbers))) parcurge aceleași perechi index-element de la sfârșit. În interiorul buclei un if verifică indexul și decide dacă elementul se adună.
Fold cu index
Se adună doar elementele de la indexuri pare: la indexul 0 este 5, iar la indexul 2 este 10, deci suma este 15.
Ieșire: program Python complet
numbers = [5, 2, 10, 4]
# enumerate() dă și indexul, așa că condiția îl poate folosi.
sum_even = 0
for idx, element in enumerate(numbers):
if idx % 2 == 0:
sum_even += element
print(sum_even)
Fold cu index de la dreapta
Aceeași regulă, dar bucla pornește de la indexul 3 și coboară până la 0. Rezultatul este tot 15.
Ieșire: program Python complet
numbers = [5, 2, 10, 4]
# Parcurge perechile index-element de la ultima până la indexul 0.
sum_even = 0
for idx, element in reversed(list(enumerate(numbers))):
if idx % 2 == 0:
sum_even += element
print(sum_even)
Reduce sigur
reduce() are nevoie de un prim element de la care să pornească. Pe o listă goală ridică o eroare TypeError și programul se oprește.
În Python, „nu există rezultat” se exprimă prin None. O mică funcție ajutătoare verifică mai întâi lista: când este goală, returnează None în loc să apeleze reduce(). Apelantul verifică apoi dacă a primit None, la fel ca la dict.get() pentru o cheie care lipsește.
Reduce sau None
reduce_or_none returnează 21 pentru numerele 5, 2, 10 și 4, și None pentru o listă goală.
Ieșire: program Python complet
from functools import reduce
def reduce_or_none(items, f):
# reduce() ridică o eroare TypeError pe o listă goală; în schimb returnăm None.
if not items:
return None
return reduce(f, items)
def add(acc, element):
return acc + element
numbers = [5, 2, 10, 4]
empty = []
print(reduce_or_none(numbers, add))
print(reduce_or_none(empty, add))
Fold și Reduce cumulativ
Un fold sau reduce cumulativ nu aruncă pașii intermediari. El salvează acumulatorul după fiecare element, așa că rezultatul este o listă cu toate valorile intermediare.
Python are asta încorporat în itertools.accumulate(). Implicit adună și produce fiecare sumă intermediară. La un reduce cumulativ rezultatul începe cu primul element. Cu argumentul cu nume initial= devine un fold cumulativ: rezultatul începe cu valoarea inițială și are cu un element mai mult decât datele de intrare.
Reduce cumulativ
Numerele de la 0 la 5 sunt adunate unul câte unul. accumulate() păstrează fiecare sumă de pe parcurs: [0, 1, 3, 6, 10, 15].
Ieșire: program Python complet
from itertools import accumulate
numbers = [0, 1, 2, 3, 4, 5]
# accumulate() salvează fiecare sumă intermediară, nu doar ultima.
print(list(accumulate(numbers)))
Fold cumulativ
La fel, dar cu initial=10. Rezultatul începe cu 10 însuși și are cu un element mai mult decât datele de intrare: [10, 10, 11, 13, 16, 20, 25].
Ieșire: program Python complet
from itertools import accumulate
numbers = [0, 1, 2, 3, 4, 5]
# initial=10 pune valoarea de pornire în fața sumelor intermediare.
print(list(accumulate(numbers, initial=10)))