enumerate()-funktion vaativuus¶
Funktio enumerate(iterable, start=0) palauttaa iteraattorin, joka tuottaa (index, item)
-monikoita. Se on laiska: sen luominen kutsuu iter(iterable) eikä kuluta yhtään alkiota, ja
jokainen askel ottaa yhden alkion tuosta iteraattorista ja liittää siihen laskurin arvon.
n on enumerate-oliosta otettujen alkioiden määrä. Kunkin alkion hakeminen taustalla olevasta
iteraattorista hinnoitellaan O(1):ksi, kuten listoille, monikoille, merkkijonoille ja
range-olioille; iteraattori, joka tekee enemmän työtä alkiota kohden, lisää sen kustannuksen
jokaiseen askeleeseen. Jokainen indeksi hinnoitellaan O(1):ksi, mikä pätee, kun start mahtuu
konesanaan.
Vaativuusviite¶
enumerate¶
| Operaatio | Aika | Tila | Huomiot |
|---|---|---|---|
enumerate(iterable, start=0) |
O(1) | O(1) | Kutsuu iter(iterable) kerran eikä ota alkioita; iteroitumaton olio nostaa TypeError-poikkeuksen tässä |
next() enumerate-oliolle |
O(1) | O(1) | Ottaa yhden alkion taustalla olevasta iteraattorista |
| enumerate-olion iterointi | O(n) | O(1) | Monikot tuotetaan yksi kerrallaan, niitä ei kerätä talteen |
list(enumerate(iterable)) |
O(n) | O(n) | Säilyttää kaikki n monikkoa |
enumerate[T] |
O(1) | O(1) | Geneerinen alias tyyppivihjeitä varten |
Peruskäyttö¶
Yksinkertainen numerointi¶
items = ['a', 'b', 'c']
pairs = []
for index, item in enumerate(items): # O(n)
pairs.append((index, item))
assert pairs == [(0, 'a'), (1, 'b'), (2, 'c')]
assert list(enumerate(items)) == [(0, 'a'), (1, 'b'), (2, 'c')] # O(n) time and space
Oma aloitusindeksi¶
start asettaa ensimmäisen indeksin. Sen on oltava int tai olio, jolla on __index__.
items = ['a', 'b', 'c']
assert list(enumerate(items, start=1)) == [(1, 'a'), (2, 'b'), (3, 'c')]
assert list(enumerate(items, 10)) == [(10, 'a'), (11, 'b'), (12, 'c')]
assert list(enumerate(items, -1)) == [(-1, 'a'), (0, 'b'), (1, 'c')]
try:
enumerate(items, start=1.5)
except TypeError as exc:
assert "cannot be interpreted as an integer" in str(exc)
else:
raise AssertionError("a float start did not raise")
Laiska, kertakäyttöinen iterointi¶
enumerate-olion luominen ei ota alkioita, ja aikaisin pysähtyvä silmukka maksaa vain ottamistaan alkioista. Olio on iteraattori oman iteraattorinsa yli, joten sen voi kuluttaa vain kerran.
taken = []
def numbers():
for value in range(10**9):
taken.append(value)
yield value
pairs = enumerate(numbers()) # O(1) - nothing is taken yet
assert taken == []
for index, value in pairs: # O(k) for k items taken
if index == 3:
break
assert taken == [0, 1, 2, 3]
assert next(pairs) == (4, 4) # O(1) - resumes where the loop stopped
pairs = enumerate(['a', 'b'])
assert list(pairs) == [(0, 'a'), (1, 'b')]
assert list(pairs) == [] # already exhausted
try:
len(enumerate(['a', 'b']))
except TypeError as exc:
assert "has no len()" in str(exc)
else:
raise AssertionError("len() did not raise")
try:
enumerate(['a', 'b'])[0]
except TypeError as exc:
assert "not subscriptable" in str(exc)
else:
raise AssertionError("indexing did not raise")
Parien kääntämiseksi tai indeksoimiseksi rakenna ensin lista, mikä vie O(n) aikaa ja tilaa:
pairs = list(enumerate(['a', 'b', 'c'])) # O(n)
assert pairs[::-1] == [(2, 'c'), (1, 'b'), (0, 'a')]
assert pairs[1] == (1, 'b')
enumerate() vs range(len(...))¶
for i in range(len(seq)) yhdessä seq[i]:n kanssa vaatii sekvenssin, joka tukee len()-kutsua
ja indeksointia, ja jokainen seq[i] lisää sekvenssin indeksoinnin kustannuksen. Listalla se on
O(1), joten molemmat silmukat ovat O(n). deque-oliossa indeksointi kaukana päistä on O(n),
mikä tekee silmukasta O(n²), kun taas enumerate() käy dequen läpi kerran. enumerate() toimii
myös generaattoreilla, tiedostoilla ja muilla iteroituvilla olioilla, joita ei voi indeksoida.
from collections import deque
d = deque(range(1000))
by_index = [(i, d[i]) for i in range(len(d))] # O(n²) - each d[i] walks the deque
by_enumerate = list(enumerate(d)) # O(n)
assert by_index == by_enumerate
lines = iter(['first', 'second'])
assert list(enumerate(lines, start=1)) == [(1, 'first'), (2, 'second')]
Yleisiä malleja¶
Jokaisen sijainnin löytäminen¶
Yksi läpikäynti enumerate()-funktiolla löytää jokaisen osuman ajassa O(n). list.index()
palauttaa vain ensimmäisen osuman ja nostaa ValueError-poikkeuksen, jos osumaa ei ole.
items = ['apple', 'banana', 'cherry', 'banana']
positions = [i for i, item in enumerate(items) if item == 'banana'] # O(n)
assert positions == [1, 3]
assert items.index('banana') == 1 # O(n) - first match only
try:
items.index('date')
except ValueError:
pass
else:
raise AssertionError("index() found a missing item")
Yhdistäminen zip()-funktioon¶
names = ['Alice', 'Bob', 'Charlie']
ages = [30, 25, 35]
rows = [f"{i}: {name} is {age}" for i, (name, age) in enumerate(zip(names, ages))] # O(n)
assert rows == ["0: Alice is 30", "1: Bob is 25", "2: Charlie is 35"]
Hakutaulun rakentaminen¶
items = ['apple', 'banana', 'cherry']
by_number = dict(enumerate(items, start=1)) # O(n)
assert by_number == {1: 'apple', 2: 'banana', 3: 'cherry'}
position = {item: i for i, item in enumerate(items)} # O(n) once, then O(1) lookups
assert position['cherry'] == 2
Erikoistapaukset¶
assert list(enumerate([])) == []
assert list(enumerate('ab')) == [(0, 'a'), (1, 'b')]
assert list(enumerate({'x': 1, 'y': 2})) == [(0, 'x'), (1, 'y')] # dict keys
try:
enumerate(42)
except TypeError as exc: # raised when the object is built, not when it is iterated
assert "not iterable" in str(exc)
else:
raise AssertionError("enumerate(42) did not raise")
Suorituskyvyn parhaat käytännöt¶
✅ Tee näin:
- Käytä
enumerate()-funktiota, kun tarvitset sekä indeksin että alkion - yksi O(n) läpikäynti mistä tahansa iteroituvasta - Pysähdy aikaisin
break-lauseella - maksat vain ottamistasi alkioista - Rakenna
{item: index}-sanakirja kerran, kun haet erillisten, hajautuskelpoisten alkioiden sijainteja toistuvasti
❌ Vältä:
range(len(seq))jaseq[i]deque-oliolla tai muulla sekvenssillä, jonka indeksointi on kallista - O(n²)list(enumerate(...)), kun yksi silmukka parien yli riittää - O(n) tilaa turhaan- Jo loppuun kulutetun enumerate-olion iterointia uudelleen - toinen läpikäynti on tyhjä
Liittyvät funktiot¶
- zip() - Yhdistää useita iteroituvia
- range() - Tuottaa lukujonoja
- iter() - Luo iteraattoreita
- next() - Hakee iteraattorin seuraavan alkion
Versiohuomautukset¶
- Kaikki Python 3 -versiot:
enumerate()on laiska ja kertakäyttöinen, ja sen rajat ovat samat kaikissa tuetuissa versioissa