2014-12-27 10 views
19

I documenti mancano di un esempio ... Come si utilizza bisect.insort_left)_ in base a una chiave?Come utilizzare bisect.insort_left con una chiave?

Tentativo di inserimento in base alla chiave.

bisect.insort_left(data, ('brown', 7)) 

mette inseriscono in data[0].

Da docs ...

bisect.insort_left(una, x, Lo = 0, hi = len (a))

        Inserire x in un in ordinate ordine. Questo è equivalente a a.insert(bisect.bisect_left(a, x, lo, hi), x) supponendo che a sia già ordinato. Tieni presente che la ricerca O (log n) è dominata dal passo di inserimento lento O (n).

utilizzo Esempio:

>>> data = [('red', 5), ('blue', 1), ('yellow', 8), ('black', 0)] 
>>> data.sort(key=lambda r: r[1]) 
>>> keys = [r[1] for r in data]   # precomputed list of keys 
>>> data[bisect_left(keys, 0)] 
('black', 0) 
>>> data[bisect_left(keys, 1)] 
('blue', 1) 
>>> data[bisect_left(keys, 5)] 
('red', 5) 
>>> data[bisect_left(keys, 8)] 
('yellow', 8) 
>>> 

voglio mettere ('brown', 7) dopo ('red', 5) sul elenco ordinato in data utilizzando bisect.insort_left. In questo momento bisect.insort_left(data, ('brown', 7)) mette ('brown', 7) a data[0] ... perché non sto usando i tasti per inserire ... i documenti non mostrano di fare inserimenti usando i tasti.

+2

Qual è la tua domanda? – BrenBarn

+0

Stai attento a questo è un'operazione O (N), controlla se ne hai veramente bisogno prima. Avete considerato 'heapq' o semplicemente chiamato' list.sort' prima di stampare se l'elenco non è ordinato – jamylak

+0

Sì, heapq non funziona bene per rimuovere un nodo mid tree. quindi, questo potrebbe essere il migliore per me – Merlin

risposta

13

Questo fa essenzialmente la stessa cosa la fa SortedCollection recipe che il bisect documentation menzioni nella Vedi anche: sezione alla fine, che supporta una funzione chiave.

Cosa si sta facendo è un separato ordinato keys elenco è mantenuto in parallelo con l'ordinato data lista per migliorare le prestazioni (è più veloce rispetto alla creazione della lista chiavi prima di ogni inserimento, ma mantenendo intorno e l'aggiornamento non è strettamente necessario) . La ricetta di ActiveState ha racchiuso questo per te all'interno di una classe, ma nel codice sottostante sono solo due elenchi indipendenti separati da passare (quindi sarebbe più facile per loro di uscire dalla sincronizzazione di quanto sarebbe se fossero entrambi tenuti in un'istanza della classe della ricetta).

from bisect import bisect_left 

def insert(seq, keys, item, keyfunc=lambda v: v): 
    """Insert an item into a sorted list using a separate corresponding 
     sorted keys list and a keyfunc() to extract the key from each item. 

    Based on insert() method in SortedCollection recipe: 
    http://code.activestate.com/recipes/577197-sortedcollection/ 
    """ 
    k = keyfunc(item) # Get key. 
    i = bisect_left(keys, k) # Determine where to insert item. 
    keys.insert(i, k) # Insert key of item to keys list. 
    seq.insert(i, item) # Insert the item itself in the corresponding place. 

# Initialize the sorted data and keys lists. 
data = [('red', 5), ('blue', 1), ('yellow', 8), ('black', 0)] 
data.sort(key=lambda r: r[1]) # Sort data by key value 
keys = [r[1] for r in data] # Initialize keys list 
print(data) # -> [('black', 0), ('blue', 1), ('red', 5), ('yellow', 8)] 

insert(data, keys, ('brown', 7), keyfunc=lambda x: x[1]) 
print(data) # -> [('black', 0), ('blue', 1), ('red', 5), ('brown', 7), ('yellow', 8)] 

Follow-on domanda:
        Può bisect.insort_left essere usato?

No, non è possibile utilizzare semplicemente la funzione bisect.insort_left() perché non è stata scritta in un modo che supporta una funzione chiave, ma confronta solo l'intero elemento passato ad essa per inserire, x, con uno degli elementi interi nell'array nella sua istruzione if a[mid] < x:. Puoi vedere cosa intendo guardando la sorgente del modulo bisect in Lib/bisect.py.

Ecco il brano in questione:

def insort_left(a, x, lo=0, hi=None): 
    """Insert item x in list a, and keep it sorted assuming a is sorted. 

    If x is already in a, insert it to the left of the leftmost x. 

    Optional args lo (default 0) and hi (default len(a)) bound the 
    slice of a to be searched. 
    """ 

    if lo < 0: 
     raise ValueError('lo must be non-negative') 
    if hi is None: 
     hi = len(a) 
    while lo < hi: 
     mid = (lo+hi)//2 
     if a[mid] < x: lo = mid+1 
     else: hi = mid 
    a.insert(lo, x) 

è possibile modificare quanto sopra per accettare un argomento-tasto funzione opzionale e usarlo:

def my_insort_left(a, x, lo=0, hi=None, keyfunc=lambda v: v): 
    x_key = keyfunc(x) # Get and save value comparison value. 
    . . . 
     if keyfunc(a[mid]) < x_key: # Compare key values. 
      lo = mid+1 
    . . . 

... e chiamarlo in questo modo:

In realtà, se hai intenzione di scrivere una funzione personalizzata, per maggiore efficienza a scapito di generalità non necessaria, è possibile fare a meno dell'aggiunta di un argomento di funzione chiave generico e basta eseguire l'hardcode di tutto per utilizzare la modalità necessaria con il formato dei dati che si ha. Ciò eviterà il sovraccarico di più chiamate a una funzione chiave durante gli inserimenti.

def my_insort_left(a, x, lo=0, hi=None): 
    x_key = x[1] # Key on second element of each item in sequence. 
    . . . 
     if a[mid][1] < x_key: lo = mid+1 # Compare second element to key. 
    . . . 

... chiamati in questo modo, senza passare keyfunc:

my_insort_left(data, ('brown', 7)) 
+0

GRAZIE, si può usare bisect.insort_left? – Merlin

+0

Probabilmente potresti usarlo per inserire la chiave del nuovo elemento nella lista 'keys', ma non l'elemento stesso nella lista' data' (perché non supporta una funzione chiave e userebbe l'intero elemento come la chiave, e dato che l'elemento è una tupla, si ordinerebbe prima per il valore della stringa). – martineau

+0

Confuso ... Come si inserirà nella lista "dati"? Puoi dare un esempio ... – Merlin

3

Se il vostro obiettivo è quello di mantenere un elenco ordinati per chiave, l'esecuzione di operazioni usuali come bisect inserire, eliminare e aggiornare , Penso che lo sortedcontainers dovrebbe essere adatto alle tue esigenze e tu eviterai inserimenti di tipo O (n).

+1

Specifico per questa domanda: sortedcontainers.SortedList include [metodi bisect_key *] (http://www.grantjenks.com/docs/sortedcontainers/sortedlistwithkey.html#sortedcontainers.SortedListWithKey.L.bisect_key) – GrantJ

3

Si può avvolgere il tuo iterable in una classe che implementa __getitem__ e __len__. Ciò ti consente di utilizzare una chiave con bisect_left. Se imposti la tua classe per prendere l'iterabile e una funzione chiave come argomenti.

Per estendere questo utilizzo con insort_left è necessario implementare il metodo insert. Il problema qui è che se lo fai è che insort_left proverà ad inserire il tuo argomento chiave nella lista contenente gli oggetti di cui la chiave è un membro.

Un esempio è chiaro

from bisect import bisect_left, insort_left 


class KeyWrapper: 
    def __init__(self, iterable, key): 
     self.it = iterable 
     self.key = key 

    def __getitem__(self, i): 
     return self.key(self.it[i]) 

    def __len__(self): 
     return len(self.it) 

    def insert(self, index, item): 
     print('asked to insert %s at index%d' % (item, index)) 
     self.it.insert(index, {"time":item}) 

timetable = [{"time": "0150"}, {"time": "0250"}, {"time": "0350"}, {"time": "0450"}, {"time": "0550"}, {"time": "0650"}, {"time": "0750"}] 

bslindex = bisect_left(KeyWrapper(timetable, key=lambda t: t["time"]), "0359") 

islindex = insort_left(KeyWrapper(timetable, key=lambda t: t["time"]), "0359") 

Guarda come nel mio metodo insert ho dovuto fare è specifico per il dizionario calendario altrimenti insort_left avrebbero cercato inserto "0359" dove dovrebbe inserire {"time": "0359"}?

I modi attorno a questo potrebbero essere la costruzione di un oggetto fittizio per il confronto, ereditato da KeyWrapper e l'override di insert o il passaggio di una sorta di funzione di fabbrica per creare l'oggetto. Nessuno di questi modi non è particolarmente desiderabile da un punto di vista idiomatico di pitone.

Quindi il modo più semplice è utilizzare lo KeyWrapper con bisect_left, che restituisce l'indice di inserimento e quindi inserire l'inserto. Si potrebbe facilmente avvolgere in una funzione dedicata.

ad es.

bslindex = bisect_left(KeyWrapper(timetable, key=lambda t: t["time"]), "0359") 
timetable.insert(bslindex, {"time":"0359"}) 

In questo caso assicurarsi di non implementare insert, in modo da essere immediatamente conto se si passa accidentalmente una KeyWrapper a una funzione mutante come insort_left che probabilmente non avrebbe fatto la cosa giusta.

di usare i vostri dati di esempio

from bisect import bisect_left 


class KeyWrapper: 
    def __init__(self, iterable, key): 
     self.it = iterable 
     self.key = key 

    def __getitem__(self, i): 
     return self.key(self.it[i]) 

    def __len__(self): 
     return len(self.it) 

data = [('red', 5), ('blue', 1), ('yellow', 8), ('black', 0)] 
data.sort(key=lambda c: c[1]) 

newcol = ('brown', 7) 

bslindex = bisect_left(KeyWrapper(data, key=lambda c: c[1]), newcol[1]) 
data.insert(bslindex, newcol) 

print(data) 
+0

Questo è eccellente e non ha ottenere l'amore che merita. È conciso e molto più efficiente di qualsiasi altra alternativa che ho visto. Se sai che 'data' è già ordinato nell'ordine corretto, non è necessario calcolare la chiave per ogni elemento. L'intero punto di una ricerca binaria è di ottenere 'O (log n)' invece di 'O (n)'. Qual è il punto se devi calcolare prima la chiave per ogni elemento? –

Problemi correlati