Python-Wörterbuch mit mehreren Schlüsseln, die speichereffizient auf dieselbe Liste verweisen

9

Ich habe diese einzigartige Anforderung, die durch diesen Code erklärt werden kann. Dies ist Arbeitscode, aber nicht speichereffizient.

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']

Kurz gesagt, ich möchte, dass jedes Element der Unterliste indexierbar ist und die Unterliste zurückgibt.

Die obige Methode funktioniert, benötigt jedoch viel Speicher, wenn die Datenmenge groß ist. Gibt es einen besseren, speicher- und CPU-freundlichen Weg? Die Daten werden als JSON-Datei gespeichert.

Bearbeiten Ich habe die Antworten für das größtmögliche Anwendungsfall-Szenario ausprobiert (1000 Unterlisten, 100 Elemente in jeder Unterliste, 1 Million Abfragen). Hier sind die Ergebnisse (Mittelwert aus 10 Durchläufen):

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
Rahul
quelle
{item: sublist for sublist in data for item in sublist}könnte etwas effizienter und direkter sein ...?!
Täuschung
Ja. für meinen Beispielfall. In meinem realen Fall enthält die Unterliste Hunderte von Elementen und Tausende solcher Unterlisten. Benutzer des Codes haben einen kleinen Speicher (<2 GB). Wenn also eine andere schwere App ausgeführt wird, beschweren sie sich, dass Ihr Skript langsam ist.
Rahul
Welches Problem versuchen Sie genau zu lösen? Vielleicht würde ein hybrider Ansatz funktionieren, bei dem Sie nach dem ersten Buchstaben indizieren und dann einige Kandidatenlisten durchlaufen, um Ihren genauen Wert zu ermitteln, ähnlich einem Algorithmus zur Auflösung von Hash-Tabellenkollisionen.
Täuschung
Verwenden Sie für einen effizienten Weg Generatoren wieield ().
Saisiva A
Vielen Dank. Ich werde lernen, was "Auflösung von Hash-Tabellenkollisionen" bedeutet.
Rahul

Antworten:

2

Sie befinden sich wirklich in einem Kompromiss zwischen der Zeit / dem Speicher, die zum Generieren des Wörterbuchs benötigt wird, und der Zeit, die zum Scannen der gesamten Daten für eine On-the-Fly-Methode benötigt wird.

Wenn Sie eine Methode mit geringem Arbeitsspeicher wünschen, können Sie eine Funktion verwenden, die jede Unterliste nach dem Wert durchsucht. Die Verwendung eines Generators führt zu schnelleren ersten Ergebnissen für den Benutzer. Bei großen Datenmengen ist dies jedoch zwischen den Rückgaben langsam.

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)

Wie in den Kommentaren erwähnt, kann das Erstellen einer Hash-Tabelle, die nur auf dem ersten Buchstaben oder den ersten 2 oder 3 Zeichen basiert, ein guter Anfang sein. Auf diese Weise können Sie eine Kandidatenliste mit Unterlisten erstellen und diese dann scannen, um festzustellen, ob sich der Wert in der Unterliste befindet.

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)

Das quick_hashErstellen dieses Codes dauert einige Zeit, da Sie Ihre gesamte Datenstruktur scannen. Der Speicherbedarf wird jedoch viel kleiner sein. Ihr Hauptparameter für die Optimierung der Leistung ist size. Kleinere Größen haben einen geringeren Speicherbedarf, dauern jedoch beim Ausführen länger, find_list_by_hashda Ihr Kandidatenpool größer ist. Sie können einige Tests durchführen, um festzustellen, welches Recht sizefür Ihre Daten gelten sollte. Denken Sie nur daran, dass alle Ihre Werte mindestens so lang sind wie size.

James
quelle
Und ich dachte, ich kenne Python und Programmierung. Vielen Dank. Es gibt viel zu lernen.
Rahul
2

Sie können so etwas ausprobieren:

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

Es ist keine Mapping-Struktur erforderlich.

Bhushan Pant
quelle
Danke mann. Ich muss überprüfen, ob dies schneller ist.
Rahul
1
Es wird zu Beginn viel schneller sein, da es kein Verständnis für die Berechnung gibt, aber viel langsamer bei der Verwendung, da diese Methode für jedes zu findende Element die gesamten Daten erneut scannt.
Edouard Thiel
Klar, lass es mich wissen, wenn das für dich funktioniert.
Bhushan Pant
@EdouardThiel: Mir geht es auch genauso. Meine eigentliche Verwendung besteht darin, mehr Anwendungsfälle als Startfälle zu haben.
Rahul
@EdouardThiel wahr. Über den genauen Anwendungsfall bin ich mir jedoch nicht sicher.
Bhushan Pant
2

Versuchen Sie dies mit Pandas

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

Dies sieht nach einer einfachen Lösung aus, selbst wenn Ihre Daten groß werden, wird dies effizient erledigt

vgp2018
quelle
Überprüfen Sie die Größe Ihrer df: Es ist erheblich größer als die Liste data(> x12) und das Diktat temp_dict(~ x2) für die angegebenen Beispieldaten - nicht gerade speichereffizient, würde ich sagen
MrFuppes
@ MrFuppes Ich glaube nicht, dass dieses Argument gültig ist, da Pandas die Zeichenfolgen in diesem Fall nicht physisch kopiert
mcsoini
@mcsoini, ich gebe zu, mein Kommentar ist etwas oberflächlich - eine detailliertere Analyse wäre erforderlich, um festzustellen, ob pandasdieses Problem effizienter behandelt wird als die integrierte Python-Funktionalität.
MrFuppes
@ MrFuppes: Ich stimme zu. Warum verwenden, pandaswenn es mit verwendet werden kann stdlib. Nur weil es schick aussieht?
Rahul
1
Sie haben jedoch nicht angegeben, wie ich den Datenrahmen abfragen werde. Können Sie mir zeigen, wie Ihre Lösung mein Problem lösen wird? Ich habe @ mcsoinis Lösung für Pandas ausprobiert, aber für 1 Million Anfragen dauert es ewig. Ich weiß nicht warum. In meiner aktualisierten Frage finden Sie Ergebnisse verschiedener Methoden.
Rahul
0

Ich bin mir nicht ganz sicher, wie sich dies bei größeren Datenmengen verhalten würde, aber Sie könnten Folgendes ausprobieren:

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

Bearbeiten: Scheint zeitlich nicht vorteilhaft zu sein, basierend auf einem Schnelltest mit einigen gefälschten Daten in größerem Maßstab.

mcsoini
quelle