Python Itertools: Effektive verktøy for iteratorer

Ifølge Pythons dokumentasjon, utgjør Itertools en modul i Python som tilbyr en rekke raske og minneeffektive verktøy for håndtering av Python-iteratorer. Disse verktøyene kan anvendes både separat og i kombinasjon, og de legger til rette for en konsis og effektiv måte å generere og arbeide med iteratorer på, med minimalt minneforbruk.

Itertools-modulen inkluderer funksjoner som forenkler arbeidet med iteratorer, spesielt i situasjoner der store mengder data skal behandles. Itertools-funksjoner kan operere på eksisterende iteratorer for å danne enda mer avanserte Python-iteratorer.

Videre kan Itertools bistå utviklere med å redusere feil ved håndtering av iteratorer, og bidra til å skrive kode som er renere, mer lesbar og enklere å vedlikeholde.

Basert på de funksjonene som iteratorene i Itertools-modulen tilbyr, kan de deles inn i følgende kategorier:

#1. Uendelige iteratorer

Dette er iteratorer som gjør det mulig å jobbe med uendelige sekvenser, og som vil fortsette i en løkke uten ende, med mindre det er angitt en betingelse for å avslutte løkken. Slike iteratorer er nyttige når man simulerer endeløse løkker eller genererer en ubegrenset serie. Itertools tilbyr tre typer uendelige iteratorer: count(), cycle() og repeat().

#2. Kombinatoriske iteratorer

Kombinatoriske iteratorer inneholder funksjoner som kan brukes til å utforske kartesiske produkter og utføre kombinasjoner og permutasjoner av elementer som finnes i en iterabel. Disse funksjonene er sentrale når man forsøker å finne alle mulige måter å ordne eller kombinere elementer i en iterabel. Itertools har fire kombinatoriske iteratorer: product(), permutations(), combinations(), og combinations_with_replacement().

#3. Iteratorer som termineres ved den korteste inndatasekvensen

Dette er terminerende iteratorer som benyttes på endelige sekvenser, og som genererer et resultat basert på typen funksjon som anvendes. Eksempler på slike avsluttende iteratorer er: accumulate(), chain(), chain.from_iterable(), compress(), dropwhile(), filterfalse(), groupby(), islice(), pairwise(), starmap(), takewhile(), tee() og zip_longest().

La oss undersøke hvordan de forskjellige Itertools-funksjonene opererer, basert på deres kategori:

Uendelige iteratorer

De tre uendelige iteratorene inkluderer:

#1. count()

Funksjonen count(start, step) produserer en uendelig tallrekke som begynner med startverdien. Funksjonen aksepterer to valgfrie argumenter: start og step. Argumentet start angir hvor tallrekken skal begynne. Som standard begynner den på 0 dersom ingen startverdi er spesifisert. step fastsetter forskjellen mellom hvert påfølgende tall. Standard step-verdi er 1.

import itertools
# Teller fra 4 med et steg på 2  
for i in itertools.count(4, 2):
    # Betingelse for å avslutte løkken og unngå uendelig løkking
    if i == 14:
        break
    else:
        print(i) # Output: 4, 6, 8, 10, 12

Output:

4
6
8
10
12

#2. cycle()

Funksjonen cycle(iterable) mottar en iterabel som argument, og går deretter gjennom den iterable, og gir tilgang til elementene i den iterable i den rekkefølgen de forekommer.

For eksempel, dersom vi legger inn ["rød", "grønn", "gul"] i cycle(), vil vi i den første syklusen få tilgang til «rød»; i den andre syklusen vil vi få tilgang til «grønn», deretter «gul». I den fjerde syklusen, ettersom alle elementene i den iterable er brukt opp, starter vi på nytt med «rød» og fortsetter slik i det uendelige.

Når du kaller cycle(), lagrer du resultatet i en variabel for å danne en iterator som bevarer sin tilstand. Dette sørger for at syklusen ikke begynner på nytt hver gang, og gir deg tilgang til kun det neste elementet.

import itertools

colors = ["rød", "grønn", "gul"]
# Legger fargene inn i cycle()
color_cycle = itertools.cycle(colors)
print(color_cycle)

# range brukes for å stoppe den uendelige løkken etter 7 utskrifter
# next() brukes for å returnere neste element fra iteratoren
for i in range(7):
    print(next(color_cycle))

Output:

rød
grønn
gul
rød
grønn
gul
rød

#3. repeat()

repeat(elem, n) tar to argumenter, et element som skal repeteres (elem), og antall ganger elementet skal repeteres (n). Elementet som skal repeteres kan være en enkelt verdi eller en iterabel. Hvis n ikke oppgis, vil elementet gjentas i det uendelige.

import itertools
   
for i in itertools.repeat(10, 3):
    print(i)

Output:

10 
10
10

Kombinatoriske iteratorer

De kombinatoriske iteratorene inkluderer:

#1. product()

product() er en funksjon som benyttes for å beregne det kartesiske produktet av den iterable som sendes inn til den. Hvis vi har to iterables eller mengder, for eksempel x = {7, 8} og y = {1, 2, 3}, vil det kartesiske produktet av x og y bestå av alle mulige kombinasjoner av elementer fra x og y, der det første elementet kommer fra x og det andre fra y. Det kartesiske produktet av x og y i dette tilfellet er [(7, 1), (7, 2), (7, 3), (8, 1), (8, 2), (8, 3)].

product() har en valgfri parameter kalt repeat som brukes for å beregne det kartesiske produktet av en iterabel med seg selv. repeat angir antall repetisjoner for hvert element fra input iterables ved beregning av det kartesiske produktet.

For eksempel, vil et kall som product("ABCD", repeat=2) generere kombinasjoner som ("A", "A"), ("A", "B"), ("A", "C"), og så videre. Hvis repeat var satt til 3, ville funksjonen generere kombinasjoner som ("A", "A", "A"), ("A", "A", "B"), ("A", "A", "C"), ("A", "A", "D"), og så videre.

from itertools import product
# product() med det valgfrie repeat-argumentet
print("product() med det valgfrie repeat-argumentet ")
print(list(product('ABC', repeat = 2)))

# product uten repeat
print("product() UTEN et valgfritt repeat-argument")
print(list(product([7,8], [1,2,3])))

Output:

product() med det valgfrie repeat-argumentet 
[('A', 'A'), ('A', 'B'), ('A', 'C'), ('B', 'A'), ('B', 'B'), ('B', 'C'), ('C', 'A'), ('C', 'B'), ('C', 'C')]
product() UTEN et valgfritt repeat-argument
[(7, 1), (7, 2), (7, 3), (8, 1), (8, 2), (8, 3)]

#2. permutations()

permutations(iterable, group_size) returnerer alle mulige permutasjoner av den iterable som er sendt inn til den. En permutasjon representerer antall måter elementer i en mengde kan ordnes på. permutations() aksepterer et valgfritt argument group_size. Hvis group_size ikke er spesifisert, vil de genererte permutasjonene ha samme størrelse som lengden på den iterable som sendes inn til funksjonen.

import itertools
numbers = [1, 2, 3]
sized_permutations = list(itertools.permutations(numbers,2))
unsized_permuatations = list(itertools.permutations(numbers))

print("Permutasjoner med en størrelse på 2")
print(sized_permutations)
print("Permutasjoner UTEN størrelsesargument")
print(unsized_permuatations)

Output:

Permutasjoner med en størrelse på 2
[(1, 2), (1, 3), (2, 1), (2, 3), (3, 1), (3, 2)]
Permutasjoner UTEN størrelsesargument
[(1, 2, 3), (1, 3, 2), (2, 1, 3), (2, 3, 1), (3, 1, 2), (3, 2, 1)]

#3. combinations()

combinations(iterable, size) returnerer alle mulige kombinasjoner av en iterabel av en gitt lengde fra elementene i den iterable som sendes inn til funksjonen. Størrelsesargumentet spesifiserer lengden på hver kombinasjon.

Resultatene er sortert. Kombinasjon skiller seg litt fra permutasjoner. Med permutasjon er rekkefølgen viktig, men med kombinasjon spiller rekkefølgen ingen rolle. For eksempel, i [A, B, C] er det 6 permutasjoner: AB, AC, BA, BC, CA, CB, men kun 3 kombinasjoner AB, AC, BC.

import itertools
numbers = [1, 2, 3,4]
size2_combination = list(itertools.combinations(numbers,2))
size3_combination = list(itertools.combinations(numbers, 3))

print("Kombinasjoner med en størrelse på 2")
print(size2_combination)
print("Kombinasjoner med en størrelse på 3")
print(size3_combination)

Output:

Kombinasjoner med en størrelse på 2
[(1, 2), (1, 3), (1, 4), (2, 3), (2, 4), (3, 4)]
Kombinasjoner med en størrelse på 3
[(1, 2, 3), (1, 2, 4), (1, 3, 4), (2, 3, 4)]

#4. combinations_with_replacement()

combinations_with_replacement(iterable, size) genererer alle mulige kombinasjoner av en iterabel av en gitt lengde fra den iterable som sendes inn til funksjonen, og tillater gjentatte elementer i outputkombinasjonene. Størrelsen avgjør lengden på kombinasjonene som genereres.

Denne funksjonen er forskjellig fra combinations() ved at den gir kombinasjoner der et element kan gjentas mer enn én gang. For eksempel kan du få en kombinasjon som (1, 1), som ikke er mulig med combinations().

import itertools
numbers = [1, 2, 3,4]

size2_combination = list(itertools.combinations_with_replacement(numbers,2))
print("Combinations_with_replacement => størrelse 2")
print(size2_combination)

Output:

Combinations_with_replacement => størrelse 2
[(1, 1), (1, 2), (1, 3), (1, 4), (2, 2), (2, 3), (2, 4), (3, 3), (3, 4), (4, 4)]

Avsluttende iteratorer

Dette omfatter iteratorer som:

#1. accumulate()

accumulate(iterable, function) mottar en iterabel og et valgfritt andre argument som er en funksjon. Den returnerer deretter det akkumulerte resultatet av å bruke funksjonen i hver iterasjon på elementer i den iterable. Hvis ingen funksjon er angitt, foretas en addisjon, og de akkumulerte resultatene returneres.

import itertools
import operator
numbers = [1, 2, 3, 4, 5]

# Akkumulerer summen av tall
accumulated_val = itertools.accumulate(numbers)
accumulated_mul = itertools.accumulate(numbers, operator.mul)
print("Akkumulering uten funksjon")
print(list(accumulated_val))
print("Akkumulering med multiplikasjon")
print(list(accumulated_mul))

Output:

Akkumulering uten funksjon
[1, 3, 6, 10, 15]
Akkumulering med multiplikasjon
[1, 2, 6, 24, 120]

#2. chain()

chain(iterable_1, iterable_2, ...) aksepterer flere iterables, lenker dem sammen og produserer en enkelt iterabel som inneholder verdier fra de iterables som er sendt inn i chain()-funksjonen.

import itertools

letters = ['A', 'B', 'C', 'D']
numbers = [1, 2, 3]
colors = ['rød', 'grønn', 'gul']

# Lenker bokstaver og tall sammen
chained_iterable = list(itertools.chain(letters, numbers, colors))
print(chained_iterable)

Output:

['A', 'B', 'C', 'D', 1, 2, 3, 'rød', 'grønn', 'gul']

#3. chain.from_iterable()

chain.from_iterable(iterable) Denne funksjonen ligner på chain(). Den skiller seg imidlertid fra chain ved at den kun aksepterer en enkelt iterabel som inneholder sub-iterables, og lenker disse sammen.

import itertools

letters = ['A', 'B', 'C', 'D']
numbers = [1, 2, 3]
colors = ['rød', 'grønn', 'gul']

iterable = ['hello',colors, letters, numbers]
chain = list(itertools.chain.from_iterable(iterable))
print(chain)

Output:

['h', 'e', 'l', 'l', 'o', 'rød', 'grønn', 'gul', 'A', 'B', 'C', 'D', 1, 2, 3]

#4. compress()

compress(data, selectors) tar inn to argumenter, data som er en iterabel, og selectors som er en iterabel som inneholder boolske verdier True og False. 1 og 0 kan også brukes som alternativer til de boolske verdiene True og False. compress() filtrerer deretter de inngitte dataene ved å bruke de korresponderende elementene som sendes inn i selectors.

Verdier i data som korresponderer til verdien True eller 1 i selectors blir valgt, mens resten som korresponderer til False eller 0 ignoreres. Hvis du sender inn færre boolske verdier i selectors enn antall elementer i data, ignoreres alle elementene utover de inngitte boolske verdiene i selectors.

import itertools

# data inneholder 10 elementer
data = ['A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J']
# sender inn 9 selector-elementer
selectors = [True, False, 1, False, 0, 1, True, False, 1]

# Velger elementer fra data basert på selectors
filtered_data = list(itertools.compress(data, selectors))
print(filtered_data)

Output:

['A', 'C', 'F', 'G', 'I']

#5. dropwhile()

dropwhile(function, sequence) tar inn en funksjon med betingelsen som returnerer True eller False, og en sekvens med verdier. Den utelater deretter alle verdier inntil betingelsen som ble sendt inn returnerer False. Når betingelsen returnerer False, inkluderes resten av elementene i resultatene, uavhengig av om de returnerer True eller False.

import itertools

numbers = [1, 2, 3, 4, 5, 1, 6, 7, 2, 1, 8, 9, 0, 7]

# Utelater elementer til betingelsen er False
filtered_numbers = list(itertools.dropwhile(lambda x: x < 5, numbers))
print(filtered_numbers)

Output:

[5, 1, 6, 7, 2, 1, 8, 9, 0, 7]

#6. filterfalse()

filterfalse(function, sequence) tar inn en funksjon med en betingelse som evalueres til True eller False, og en sekvens. Den returnerer deretter verdier fra sekvensen som ikke tilfredsstiller betingelsen i funksjonen.

import itertools

numbers = [1, 2, 3, 4, 2, 3, 5, 6, 5, 8, 1, 2, 3, 6, 2, 7, 4, 3]

# Filtrerer elementer som betingelsen er False for
filtered_numbers = list(itertools.filterfalse(lambda x: x < 4, numbers))
print(filtered_numbers)

Output:

[4, 5, 6, 5, 8, 6, 7, 4]

#7. groupby()

groupby(iterable, key) tar inn en iterabel og en nøkkel, og oppretter deretter en iterator som returnerer påfølgende nøkler og grupper. For at den skal fungere, må den iterable som sendes inn til den, være sortert på den samme nøkkelfunksjonen. Nøkkelfunksjonen utregner en nøkkelverdi for hvert element i iterable.

import itertools

input_list = [("Domestic", "Cow"), ("Domestic", "Dog"), ("Domestic", "Cat"),("Wild", "Lion"), ("Wild", "Zebra"), ("Wild", "Elephant")]
classification = itertools.groupby(input_list,lambda x: x[0])
for key,value in classification:
  print(key,":",list(value))

Output:

Domestic : [('Domestic', 'Cow'), ('Domestic', 'Dog'), ('Domestic', 'Cat')]
Wild : [('Wild', 'Lion'), ('Wild', 'Zebra'), ('Wild', 'Elephant')]

#8. islice()

islice(iterable, start, stop, step) lar deg dele opp en iterabel ved å bruke start-, stop– og step-verdiene som er sendt inn. Argumentet step er valgfritt. Tellingen begynner fra 0 og elementet på stop-nummeret inkluderes ikke.

import itertools

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

# Velger elementer innenfor et område
selected_numbers = list(itertools.islice(numbers, 2, 10))
selected_numbers_step= list(itertools.islice(numbers, 2, 10,2))
print("islice uten stegverdi")
print(selected_numbers)
print("islice med en stegverdi på 2")
print(selected_numbers_step)

Output:

islice uten stegverdi
[3, 4, 5, 6, 7, 8, 9, 10]
islice med en stegverdi på 2
[3, 5, 7, 9]

#9. pairwise()

pairwise(iterable) returnerer påfølgende overlappende par hentet fra den iterable som er sendt inn, i den rekkefølgen de forekommer i den iterable. Hvis den iterable som sendes inn til den har færre enn to verdier, vil resultatet fra pairwise() være tomt.

from itertools import pairwise

numbers = [1, 2, 3, 4, 5, 6, 7, 8]
word = 'WORLD'
single = ['A']

print(list(pairwise(numbers)))
print(list(pairwise(word)))
print(list(pairwise(single)))

Output:

[(1, 2), (2, 3), (3, 4), (4, 5), (5, 6), (6, 7), (7, 8)]
[('W', 'O'), ('O', 'R'), ('R', 'L'), ('L', 'D')]
[]

#10. starmap()

starmap(function, iterable) er en funksjon som brukes i stedet for map() når argumentparametre allerede er gruppert i tupler. starmap() bruker en funksjon på elementene i den iterable som er sendt inn til den. Den iterable skal ha elementer gruppert i tupler.

import itertools

iter_starmap = [(123, 63, 13), (5, 6, 52), (824, 51, 9), (26, 24, 16), (14, 15, 11)]
print (list(itertools.starmap(min, iter_starmap)))

Output:

[13, 5, 9, 16, 11]

#11. takewhile()

takewhile(function, iterable) fungerer på motsatt måte av dropwhile(). takewhile() tar inn en funksjon med en betingelse som skal evalueres, og en iterabel. Den inkluderer deretter alle elementene i iterablen som tilfredsstiller betingelsen i funksjonen til False returneres. Når False returneres, ignoreres alle påfølgende elementer i iterablen.

import itertools

numbers = [1, 2, 3, 4, 5, 1, 6, 7, 2, 1, 8, 9, 0, 7]

# Utelater elementer til betingelsen er False
filtered_numbers = list(itertools.takewhile(lambda x: x < 5, numbers))
print(filtered_numbers)

Output:

[1, 2, 3, 4]

#12. tee()

tee(iterable, n) mottar en iterabel og returnerer flere uavhengige iteratorer. Antall iteratorer som skal returneres bestemmes av n, som som standard er 2.

import itertools

numbers = [1, 2, 3, 4, 5]

# Oppretter to uavhengige iteratorer fra numbers
iter1, iter2 = itertools.tee(numbers, 2)
print(list(iter1))
print(list(iter2))

Output:

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

#13. zip_longest()

zip_longest(iterables, fillvalue) tar inn flere iteratorer og en fillvalue. Den returnerer deretter en iterator som samler elementer fra hver av iteratorene som er sendt inn til den. Hvis iteratorene ikke har samme lengde, erstattes de manglende verdiene med fillvalue som sendes inn til funksjonen inntil den lengste iterable er brukt opp.

import itertools

names = ['John', 'mathew', 'mary', 'Alice', 'Bob', 'Charlie', 'Fury']
ages = [25, 30, 12, 13, 42]

# Kombinerer navn og alder, og fyller manglende verdier med en strek
combined = itertools.zip_longest(names, ages, fillvalue="-")

for name, age in combined:
    print(name, age)

Output:

John 25
mathew 30
mary 12
Alice 13
Bob 42
Charlie -
Fury -

Konklusjon

Python itertools er et sentralt verktøysett for en Python-utvikler. Python itertools anvendes hyppig i funksjonell programmering, databehandling og transformasjon, datafiltrering og -seleksjon, gruppering og aggregering, kombinasjon av iterables, kombinatorikk, samt ved arbeid med uendelige sekvenser.

Som Python-utvikler vil du ha stor nytte av å lære om itertools, så sørg for å anvende denne artikkelen for å gjøre deg kjent med Python Itertools.