Dizionario Python con più chiavi che punta allo stesso elenco in modo efficiente della memoria


9

Ho questo requisito unico che può essere spiegato da questo codice. Questo è un codice funzionante ma non efficiente in termini di memoria.

data = [[
        "A 5408599",
        "B 8126880",
        "A 2003529",
    ],
    [
        "C 9925336",
        "C 3705674",
        "A 823678571",
        "C 3205170186",
    ],
    [
        "C 9772980",
        "B 8960327",
        "C 4185139021",
        "D 1226285245",
        "C 2523866271",
        "D 2940954504",
        "D 5083193",
    ]]

temp_dict = {
    item: index for index, sublist in enumerate(data)
        for item in sublist
}

print(data[temp_dict["A 2003529"]])

out: ['A 5408599', 'B 8126880', 'A 2003529']

In breve, desidero che ogni elemento dell'elenco secondario sia indicizzabile e che debba restituire l'elenco secondario.

Il metodo sopra funziona ma richiede molta memoria quando i dati sono grandi. Esiste un modo migliore per la memoria e la CPU? I dati sono archiviati come file JSON.

Modifica Ho provato le risposte per lo scenario di utilizzo più ampio possibile (1000 elenchi secondari, 100 elementi in ciascun elenco secondario, 1 milione di query) e qui sono riportati i risultati (media di 10 esecuzioni):

Method,    Time (seconds),    Extra Memory used
my,        0.637              40 Mb
deceze,    0.63               40 Mb
James,     0.78               200 kb
Pant,      > 300              0 kb
mcsoini,   forever            0 kb

{item: sublist for sublist in data for item in sublist}potrebbe essere leggermente più efficiente e diretto ... ?!
Inganno

Sì. per il mio esempio. Nel mio caso reale, l'elenco secondario contiene centinaia di articoli e migliaia di tali elenchi. l'utente del codice ha poca memoria (<2 GB), quindi quando è in esecuzione un'altra app pesante, si lamentano che lo script è lento.
Rahul,

Quale problema stai cercando di risolvere esattamente? Forse funzionerebbe un approccio ibrido, in cui indicizzi con la prima lettera, e poi scorri attraverso alcune liste di candidati per trovare il tuo valore esatto, un po 'come un algoritmo di risoluzione della collisione della tabella hash.
Inganno

Per un modo efficiente utilizzare generatori come yield ().
Saisiva A

Grazie. Imparerò cosa significa "risoluzione della collisione della tabella hash".
Rahul,

Risposte:


2

Sei davvero in uno spazio di scambio tra il tempo / memoria necessaria per generare il dizionario rispetto al tempo necessario per scansionare tutti i dati per un metodo al volo.

Se si desidera un metodo di memoria insufficiente, è possibile utilizzare una funzione che ricerca il valore in ciascun elenco secondario. L'uso di un generatore otterrà i risultati iniziali più velocemente per l'utente, ma per i set di dati di grandi dimensioni, questo sarà lento tra i ritorni.

data = [[
        "A 5408599",
        "B 8126880",
        "A 2003529",
    ],
    [
        "C 9925336",
        "C 3705674",
        "A 823678571",
        "C 3205170186",
    ],
    [
        "C 9772980",
        "B 8960327",
        "C 4185139021",
        "D 1226285245",
        "C 2523866271",
        "D 2940954504",
        "D 5083193",
    ]]


def find_list_by_value(v, data):
    for sublist in data:
        if v in sublist:
            yield sublist

for s in find_list_by_value("C 9772980", data):
    print(s)

Come menzionato nei commenti, la creazione di una tabella hash basata solo sulla prima lettera o sui primi 2 o 3 caratteri potrebbe essere un buon punto di partenza. Ciò ti consentirà di creare un elenco candidato di elenchi secondari, quindi scansionarli per vedere se il valore è nell'elenco secondario.

from collections import defaultdict

def get_key(v, size=3):
    return v[:size]

def get_keys(sublist, size=3):
    return set(get_key(v, size) for v in sublist)

def find_list_by_hash(v, data, hash_table, size=3):
    key = get_key(v, size)
    candidate_indices = hash_table.get(key, set())
    for ix in candidates:
        if v in data[ix]:
            yield data[ix]

# generate the small hash table
quick_hash = defaultdict(set)
for i, sublist in enumerate(data):
    for k in get_keys(sublist, 3):
        quick_hash[k].add(i)

# lookup a value by the small hash
for s in find_list_by_hash("C 9772980", data, quick_hash, 3):
    print(s)

In questo codice quick_hashoccorrerà del tempo per la compilazione, poiché si esegue la scansione dell'intera struttura dei dati. Tuttavia, il footprint della memoria sarà molto più piccolo. Il parametro principale per ottimizzare le prestazioni è size. Le dimensioni più ridotte avranno un footprint di memoria più piccolo, ma impiegheranno più tempo durante l'esecuzione find_list_by_hashperché il pool dei candidati sarà più grande. Puoi fare alcuni test per vedere quale sizedovrebbe essere il diritto per i tuoi dati. Basta essere consapevoli del fatto che tutti i tuoi valori sono almeno lunghi size.


E ho pensato di conoscere Python e la programmazione. Grazie. C'è molto da imparare.
Rahul,

2

Puoi provare qualcosa del genere:

list(filter(lambda x: any(["C 9772980" in x]),data))

Non è necessario creare una struttura di mappatura.


Grazie amico. Dovrò verificare se questo è più veloce.
Rahul,

1
sarà molto più veloce all'inizio perché non c'è comprensione da calcolare, ma molto più lento all'utilizzo perché per ogni elemento da trovare, questo metodo eseguirà nuovamente la scansione di tutti i dati.
Edouard Thiel

Fammi sapere se questo funziona per te.
Bhushan Pant,

@EdouardThiel: anche io provo lo stesso. Il mio uso effettivo è avere più casi d'uso che casi iniziali.
Rahul,

@EdouardThiel true. Ma non sono sicuro dell'esatto caso d'uso.
Bhushan Pant,

2

prova questo, usando i panda

import pandas as pd
df=pd.DataFrame(data)
rows = df.shape[0]
for row in range(rows):
    print[[row]]    #Do something with your data

questa sembra una soluzione semplice, anche se i tuoi dati crescono in grandi quantità, questo lo gestirà in modo efficiente


controlla le dimensioni del tuo df: è considerevolmente più grande dell'elenco data(> x12) e del dict temp_dict(~ x2) per i dati di esempio forniti - non esattamente efficiente in termini di memoria direi
MrFuppes

@MrFuppes Non penso che questo argomento sia valido, poiché i panda non copiano fisicamente le stringhe in questo caso
mcsoini

@mcsoini, ammetto che il mio commento è un po 'superficiale - sarebbe necessaria un'analisi più dettagliata per determinare se pandasgestisce questo problema in modo più efficiente rispetto alla funzionalità integrata di Python.
MrFuppes,

@MrFuppes: sono d'accordo. Perché usare pandasse si può fare usando stdlib. Solo perché è elegante?
Rahul,

1
Ma non hai indicato come interrogherò il frame di dati. Puoi mostrarmi come la tua soluzione risolverà il mio problema. Ho provato la soluzione di @ mcsoini per i panda, ma ci vuole sempre per 1 milione di query. Non so perché. Si prega di consultare la mia domanda aggiornata per i risultati di vari metodi.
Rahul,

0

Non sono del tutto sicuro di come ciò si comporterebbe per grandi quantità di dati, ma potresti provare qualcosa sulla falsariga di:

import pandas as pd
df = pd.DataFrame(data).T
df.loc[:, (df == 'A 2003529').any(axis=0)]
Out[39]: 
           0
0  A 5408599
1  B 8126880
2  A 2003529
3       None
4       None
5       None
6       None

Modifica: non sembra essere utile in termini di tempo, basato su un test rapido con alcuni dati falsi su larga scala.

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.