2012-03-17 8 views
8

Sto cercando paese per intervallo IP per decine di milioni di righe. Sto cercando un modo più veloce per fare la ricerca.Qual è un modo più rapido per cercare un valore in un elenco di tuple?

devo 180K tuple in questa forma:

>>> data = ((0, 16777215, 'ZZ'), 
...   (1000013824, 1000079359, 'CN'), 
...   (1000079360, 1000210431, 'JP'), 
...   (1000210432, 1000341503, 'JP'), 
...   (1000341504, 1000603647, 'IN')) 

(Gli interi sono indirizzi IP convertiti in numeri semplici.)

Questo fa il lavoro giusto, ma semplicemente vuole troppo tempo:

>>> ip_to_lookup = 999 
>>> country_result = [country 
...     for (from, to, country) in data 
...     if (ip_to_lookup >= from) and 
...      (ip_to_lookup <= to)][0] 
>>> print country_result 
ZZ 

Qualcuno può indicarmi la direzione giusta per un modo più veloce di fare questa ricerca? Utilizzando il metodo sopra, 100 ricerche impiegano 3 secondi. Significa, credo, 10 milioni di righe richiederanno diversi giorni.

+1

prima ovvia micro-ottimizzazione: 'country_result = prossimo (paese per da, paese nei dati se ip_to_lookup> = da e ip_to_lookup <= a)' – agf

+0

Seconda: Ordina il 'data' modo che solo bisogno per testare il limite inferiore - non appena lo hai attraversato, sei nella giusta fascia. – agf

+0

'from' è una parola chiave e non può essere utilizzato come nome di variabile. –

risposta

8

È possibile utilizzare il modulo bisect per effettuare una ricerca binaria dopo aver risolto il set di dati:

from operator import itemgetter 
import bisect 

data = ((0, 16777215, 'ZZ'), (1000013824, 1000079359, 'CN'), (1000079360, 1000210431, 'JP'), (1000210432, 1000341503, 'JP'), (1000341504, 1000603647, 'IN')) 
sorted_data = sorted(data, key=itemgetter(0)) 
lower_bounds = [lower for lower,_,_ in data] 

def lookup_ip(ip): 
    index = bisect.bisect(lower_bounds, ip) - 1 
    if index < 0: 
     return None 
    _, upper, country = sorted_data[index] 
    return country if ip <= upper else None 

print(lookup_ip(-1))   # => None 
print(lookup_ip(999))   # => 'ZZ' 
print(lookup_ip(16777216)) # => None 
print(lookup_ip(1000013824)) # => 'CN' 
print(lookup_ip(1000400000)) # => 'IN' 

La complessità algoritmica della ricerca è O(log n) qui, invece di O(n) per una passeggiata lista completa.

+0

Ho lavorato su alcuni wrapper per questo genere di cose, per creare l'astrazione di una mappatura con intervalli (eventualmente semi-infiniti a entrambe le estremità) non sovrapposti per le chiavi invece dei singoli valori. –

+0

Grazie, Niklas, mi ha davvero aiutato. Il collo di bottiglia non è più la ricerca. Ho un nuovo collo di bottiglia: caricare le 17 milioni di righe in memoria per effettuare le ricerche. L'ho suddiviso in blocchi di 8000 righe, eseguendo le ricerche e formando una sola affermazione di inserimento. L'inserto è veloce. Ma ottenere le righe da elaborare in memoria è lento. Otterrà il lavoro entro la mattina. Ma sono curioso di fare qualcosa di sbagliato. Ci vogliono 15-20 secondi per caricare 8000 righe. – exzackley

+0

@ user567784: questo è un problema totalmente non correlato. Per favore considera di accettare una delle domande qui e crea una nuova domanda per il tuo nuovo problema. –

1

Supponendo che la propria situazione soddisfi alcuni requisiti, esiste un modo per ottenere la complessità di runtime su O(1) in media, ma la complessità dello spazio ne risente.

  1. I dati devono essere statici; tutti i dati devono essere elaborati prima di qualsiasi ricerca.
  2. Dato un IP arbitrario, ci deve essere un modo per determinare il suo significant octets.
  3. Deve esserci spazio sufficiente per aggiungere una chiave per ogni valore significativo per ogni paese.

Di seguito è un'implementazione molto ingenuo. Seleziona i primi due ottetti dell'IP come significativi indipendentemente da cosa, quindi concatena gli ottetti significativi come numeri interi e aggiunge in modo incrementale una chiave per ogni valore compreso tra il minimo e il massimo. Come probabilmente puoi dire, c'è molto margine di miglioramento.

from socket import inet_ntoa 
from struct import pack 

data = ((0,    16777215, 'ZZ'), 
     (1000013824, 1000079359, 'CN'), 
     (1000079360, 1000210431, 'JP'), 
     (1000210432, 1000341503, 'JP'), 
     (1000341504, 1000603647, 'IN')) 

def makedict(minip, maxip, country): 
    d = {} 
    for i in xrange(key(minip), key(maxip)+1): 
     d[i] = country 
    return d 

def key(ip): 
    octets = inet_ntoa(pack('>L', ip)).split('.') 
    return int("".join(octets[0:2])); 

lookup = {} 
for lo, hi, cnt in data: 
    lookup.update(makedict(lo, hi, cnt)) 

print lookup[key(999)]   # ZZ 
print lookup[key(1000215555)] # JP 
+0

Questo può essere reso un po 'più generico tenendo un elenco di possibili paesi con i relativi intervalli all'interno del dict, anziché un singolo codice paese. Se viene cercata una chiave, si può semplicemente percorrere l'elenco dei paesi (si spera non troppo lungo) e trovare quello corretto. Inoltre, la funzione 'chiave' può essere migliorata in' (ip & 0xffff0000) >> 16', senza bisogno di struct decomprimere. –

Problemi correlati