bisect-moduulin vaativuus¶
Moduuli bisect etsii sijainteja jonosta, jonka kutsuja pitää järjestyksessä. Se ei koskaan
järjestä eikä koskaan tarkista järjestystä: jokainen haku puolittaa sille annetun välin ja lukee
yhden alkion kierrosta kohden vakiomääräisellä lisämuistilla. Lisäysfunktiot yhdistävät tämän haun
jonon omaan insert()-metodiin, ja listalla kustannus syntyy lisäyksestä, ei hausta.
n on jonon pituus. Hakujen vaativuus lasketaan koetuksina: kukin on yksi alkion luku, yksi key-kutsu,
kun avainfunktio on annettu, ja yksi <-vertailu, ja jokainen hinnoitellaan O(1):ksi. Avainfunktio
tai vertailu, joka tekee enemmän työtä, kertoo haun kustannuksellaan. Indeksointi on O(1), kuten
listalla. Kaksi alkiota ovat tässä yhtä suuret, kun kumpikaan ei ole < toista; kun key on
annettu, verrataan avaimia.
Vaativuusviite¶
Haku¶
| Operaatio | Aika | Tila | Huomiot |
|---|---|---|---|
bisect.bisect_left(a, x, lo=0, hi=len(a), *, key=None) |
O(log n) | O(1) | Sijainti ennen x:n kanssa yhtä suurten alkioiden jaksoa; key sovelletaan jokaiseen koetettuun alkioon, ei koskaan x:ään |
bisect.bisect_right(a, x, lo=0, hi=len(a), *, key=None) |
O(log n) | O(1) | Sijainti x:n kanssa yhtä suurten alkioiden jakson jälkeen |
bisect.bisect(a, x, lo=0, hi=len(a), *, key=None) |
O(log n) | O(1) | Sama funktio kuin bisect_right |
Lisäys¶
| Operaatio | Aika | Tila | Huomiot |
|---|---|---|---|
bisect.insort_left(a, x, lo=0, hi=len(a), *, key=None) |
O(n) | O(1) | O(log n) haku, sitten a.insert(), joka listalla siirtää loppuosaa; key sovelletaan x:ään kerran |
bisect.insort_right(a, x, lo=0, hi=len(a), *, key=None) |
O(n) | O(1) | Lisää x:n kanssa yhtä suurten alkioiden jakson jälkeen |
bisect.insort(a, x, lo=0, hi=len(a), *, key=None) |
O(n) | O(1) | Sama funktio kuin insort_right |
Haku järjestetystä listasta¶
Vasen ja oikea¶
Haut eroavat vain siinä, mihin ne osuvat yhtä suurten alkioiden jaksossa: bisect_left ennen sitä,
bisect_right sen jälkeen. Kumpikin puolittaa silti välin, joten toistuvien arvojen jakson haku on
O(log n) kuten mikä tahansa muu haku, ja yhdessä ne rajaavat jakson, oli se kuinka pitkä tahansa.
import bisect
values = [1, 3, 3, 3, 5, 7, 9]
left = bisect.bisect_left(values, 3) # O(log n)
right = bisect.bisect_right(values, 3) # O(log n)
assert (left, right) == (1, 4)
assert values[left:right] == [3, 3, 3]
assert right - left == 3 # occurrences counted without a scan
def contains(sorted_list, x):
i = bisect.bisect_left(sorted_list, x) # O(log n), against O(n) for `x in sorted_list`
return i < len(sorted_list) and sorted_list[i] == x
assert contains(values, 5)
assert not contains(values, 4)
Rajaus parametreilla lo ja hi¶
lo ja hi rajaavat haettavan viipaleen, ja haku maksaa O(log(hi - lo)) jonon pituudesta
riippumatta. Lisäys insort-funktiolla maksaa silti koko listan lisäyksen. Negatiivinen lo
nostaa ValueError-poikkeuksen.
import bisect
values = list(range(0, 1_000, 2))
assert bisect.bisect_left(values, 100, lo=40, hi=60) == 50 # O(log 20)
try:
bisect.bisect_left(values, 100, lo=-1)
except ValueError:
pass
else:
raise AssertionError('a negative lo was accepted')
Haku avaimella¶
key kutsutaan kerran koetusta kohden koetetulle alkiolle, joten avaimella tehty haku tekee
O(log n) avainkutsua eikä tarvitse rinnakkaista listaa. Sitä ei kutsuta x:lle: haut ottavat x:n
valmiiksi avainarvona. insort on poikkeus - se lisää itse tietueen, joten se kutsuu key(x)
kerran löytääkseen paikan.
import bisect
records = [('a', 1), ('b', 3), ('c', 5)]
by_count = lambda record: record[1]
pos = bisect.bisect_right(records, 4, key=by_count) # O(log n) key calls; x is the key value
assert pos == 2
bisect.insort(records, ('d', 4), key=by_count) # O(n); key(x) is called here
assert records == [('a', 1), ('b', 3), ('d', 4), ('c', 5)]
Rinnakkainen avainlista on vaihtoehto, kun monta hakua jakaa saman listan: sen rakentaminen maksaa O(n), ja se on pidettävä ajan tasalla jokaisen lisäyksen yhteydessä, mutta silloin yksikään haku ei tee avainkutsuja.
import bisect
records = [('a', 1), ('b', 3), ('c', 5)]
keys = [record[1] for record in records] # O(n) once
pos = bisect.bisect_right(keys, 4) # O(log n), no key calls
records.insert(pos, ('d', 4)) # O(n)
keys.insert(pos, 4) # O(n) - keys must follow every insert
assert keys == [record[1] for record in records] == [1, 3, 4, 5]
Järjestämätön syöte¶
Mikään ei tarkista, että viipale on järjestyksessä. Järjestämättömällä syötteellä haku palauttaa silti sijainnin nostamatta poikkeusta, mutta mikään ei takaa sen olevan oikea: siihen lisääminen voi jättää listan epäjärjestykseen.
import bisect
unsorted = [3, 1, 4, 1, 5]
pos = bisect.bisect(unsorted, 2) # O(log n), no error
result = unsorted[:pos] + [2] + unsorted[pos:]
assert result != sorted(result)
Listan pitäminen järjestyksessä¶
Yksi lisäys vai erä¶
Jokainen insort listaan on O(n), joten k lisäystä n alkion listaan maksaa O(k·(n + k)).
Kun lisäykset tulevat yhdessä eikä niiden välissä ole hakuja, niiden lisääminen loppuun ja yksi
järjestäminen maksaa sen sijaan O((n + k) log(n + k)). insort on oikea työkalu, kun haut ja
lisäykset vuorottelevat.
import bisect
values = [1, 3, 5, 7]
bisect.insort(values, 4) # O(n) - the tail shifts
assert values == [1, 3, 4, 5, 7]
# Many inserts at once: O(k·(n + k)) one at a time
one_at_a_time = [1, 3, 5, 7, 9]
for item in [8, 2, 6, 4]:
bisect.insort(one_at_a_time, item) # O(n) each
# ... or O((n + k) log(n + k)) as one sort
batch = [1, 3, 5, 7, 9]
batch.extend([8, 2, 6, 4])
batch.sort()
assert batch == one_at_a_time
Yleisiä malleja¶
Pistemäärän kuvaaminen arvosanaväliin¶
import bisect
breakpoints = [60, 70, 80, 90]
grades = 'FDCBA'
def grade(score):
return grades[bisect.bisect(breakpoints, score)] # O(log b), b = breakpoints
assert [grade(score) for score in (33, 60, 77, 89, 90, 100)] == list('FDCBAA')
Aikaikkunan tapahtumat¶
import bisect
from datetime import datetime
events = [
(datetime(2024, 1, 1, 10), 'event1'),
(datetime(2024, 1, 1, 12), 'event2'),
(datetime(2024, 1, 1, 15), 'event3'),
(datetime(2024, 1, 1, 18), 'event4'),
]
when = lambda event: event[0]
start = bisect.bisect_left(events, datetime(2024, 1, 1, 11), key=when) # O(log n)
end = bisect.bisect_right(events, datetime(2024, 1, 1, 15), key=when) # O(log n)
assert [name for _, name in events[start:end]] == ['event2', 'event3'] # O(m), m = matches
Suorituskyvyn parhaat käytännöt¶
✅ Tee näin:
- Käytä jäsenyyden tarkistamiseen järjestetystä listasta
bisect_left-funktiota ja yhtä vertailua O(n)in-operaation sijaan - Käytä erotusta
bisect_right - bisect_leftyhtä suurten alkioiden jakson pituuden laskemiseen ajassa O(log n) - Anna
lojahi, kun vastauksen tiedetään olevan listan tietyssä osassa - Käytä
key-parametria satunnaiseen hakuun tietueista; pidä rinnakkaista avainlistaa, kun monta hakua jakaa sen
❌ Vältä:
- Avainlistan rakentamista yhtä hakua varten - se on O(n) O(log n) -vastauksen takia
- Toistuvaa
insort-kutsua erän lataamiseen listaan - lisää loppuun ja järjestä kerran - Hakua listasta, jota et ole pitänyt järjestyksessä - vastaus on epäluotettava, eikä mikään nosta poikkeusta
Versiohuomautukset¶
- Python 3.10+: Kaikkiin kuuteen funktioon lisättiin
key-parametri