Diccionario de Python con varias teclas apuntando a la misma lista de manera eficiente en memoria

9

Tengo este requisito único que puede explicarse con este código. Este es un código de trabajo pero no es eficiente en 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']

En resumen, quiero que cada elemento de la sublista sea indexable y debería devolver la sublista.

El método anterior funciona pero requiere mucha memoria cuando los datos son grandes. ¿Hay alguna forma mejor para la memoria y la CPU? Los datos se almacenan como un archivo JSON.

Editar Probé las respuestas para el escenario de caso de uso más grande posible (1000 sublistas, 100 elementos en cada sublista, 1 millón de consultas) y aquí están los resultados (media de 10 ejecuciones):

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
fuente
{item: sublist for sublist in data for item in sublist}¿podría ser un poco más eficiente y directo ...?
deceze
Si. para mi caso de muestra En mi caso real, la sublista tiene cientos de elementos y miles de esas sublistas. El usuario del código tiene poca memoria (<2 gb), por lo que cuando se está ejecutando otra aplicación pesada, se queja de que su script es lento.
Rahul
¿Qué problema estás tratando de resolver exactamente? Quizás un enfoque híbrido funcionaría, en el que indexaría por la primera letra y luego iteraría a través de algunas listas de candidatos para encontrar su valor exacto, algo así como un algoritmo de resolución de colisión de tabla hash.
deceze
De manera eficiente, use generadores como yield ().
Saisiva A
Gracias. Aprenderé lo que significa "resolución de colisión de tabla hash".
Rahul

Respuestas:

2

Realmente se encuentra en un espacio de compensación entre el tiempo / memoria que lleva generar el diccionario y el tiempo que lleva escanear todos los datos para obtener un método sobre la marcha.

Si desea un método con poca memoria, puede usar una función que busque el valor en cada sublista. El uso de un generador obtendrá resultados iniciales más rápido para el usuario, pero para grandes conjuntos de datos, esto será lento entre los retornos.

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)

Como se mencionó en los comentarios, construir una tabla hash basada solo en la primera letra o los primeros 2 o 3 caracteres podría ser un buen lugar para comenzar. Esto le permitirá crear una lista de sublistas candidatas, luego escanearlas para ver si el valor está en la sublista.

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)

La quick_hashcreación de este código llevará algún tiempo, ya que está escaneando toda su estructura de datos. Sin embargo, la huella de memoria será mucho más pequeña. Su parámetro principal para ajustar el rendimiento es size. Un tamaño más pequeño tendrá una huella de memoria más pequeña, pero llevará más tiempo cuando se ejecute find_list_by_hashporque su grupo de candidatos será más grande. Puede hacer algunas pruebas para ver cuál sizedebería ser el derecho para sus datos. Solo tenga en cuenta que todos sus valores son al menos tan largos como sea posible size.

James
fuente
Y pensé que sé python y programación. Gracias. Hay mucho que aprender
Rahul
2

Puedes probar algo como esto:

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

No es necesario hacer una estructura de mapeo.

Pantalón Bhushan
fuente
Gracias hombre. Tendré que comprobar si esto es más rápido.
Rahul
1
será mucho más rápido al comienzo porque no hay comprensión para calcular, pero mucho más lento en el uso porque para cada elemento que se encuentre, este método volverá a escanear todos los datos.
Edouard Thiel
Claro, avíseme si esto funciona para usted.
Bhushan Pant
@EdouardThiel: Yo también siento lo mismo. Mi uso real es tener más casos de uso que los casos de inicio.
Rahul
@EdouardThiel cierto. Pero no estoy seguro sobre el caso de uso exacto.
Bhushan Pant
2

prueba esto, usando 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

Esto parece una solución simple, incluso si sus datos crecen, esto lo manejará de manera eficiente

vgp2018
fuente
comprobar el tamaño de su df: es considerablemente más grande que la lista data(> x12) y el dict temp_dict(~ x2) para los datos de ejemplo que se da - no es exactamente la memoria eficientes diría
MrFuppes
@ MrFuppes No creo que este argumento sea válido, ya que los pandas no copian físicamente las cadenas en este caso
mcsoini
@mcsoini, admito que mi comentario es un poco superficial: sería necesario un análisis más detallado para determinar si pandasmaneja este problema de manera más eficiente que la funcionalidad incorporada de Python.
MrFuppes
@ MrFuppes: estoy de acuerdo. Por qué usar pandassi se puede hacer usando stdlib. ¿Solo porque parece elegante?
Rahul
1
Pero no proporcionó cómo consultaré el marco de datos. ¿Me puede mostrar cómo su solución resolverá mi problema? Intenté la solución de @ mcsoini para pandas, pero lleva un millón de consultas para siempre. No se porque. Consulte mi pregunta actualizada para obtener resultados de varios métodos.
Rahul
0

No estoy completamente seguro de cómo se comportaría esto para datos de cantidades más grandes, pero podría intentar algo en la línea de:

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

Editar: no parece ser beneficioso en términos de tiempo, basado en una prueba rápida con algunos datos falsos a mayor escala.

mcsoini
fuente