Dicionário Python com várias chaves apontando para a mesma lista de maneira eficiente em memória

9

Eu tenho esse requisito exclusivo que pode ser explicado por este código. Este é um código funcional, mas não economiza memória.

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

Em suma, quero que cada item da sub-lista seja indexável e retorne a sub-lista.

O método acima funciona, mas requer muita memória quando os dados são grandes. Existe alguma maneira melhor para a memória e a CPU? Os dados são armazenados como um arquivo JSON.

Editar Tentei as respostas para o maior cenário de caso de uso possível (1000 sub-listas, 100 itens em cada sub-lista, 1 milhão de consultas) e aqui estão os resultados (média de 10 execuções):

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
fonte
{item: sublist for sublist in data for item in sublist}pode ser um pouco mais eficiente e direto… ?!
deceze
Sim. para o meu caso de amostra. No meu cenário real, a sublista está tendo centenas de itens e milhares de sublistas. o usuário do código está com pouca memória (<2gb); portanto, quando outro aplicativo pesado está sendo executado, eles reclamam que seu script está lento.
Rahul
Que problema você está tentando resolver exatamente? Talvez uma abordagem híbrida funcione, na qual você indexa pela primeira letra e, em seguida, percorre algumas listas de candidatos para encontrar seu valor exato, como um algoritmo de resolução de colisão de tabela de hash.
deceze
Para uma maneira eficiente, use geradores como yield ().
SaisivaA
Obrigado. Vou aprender o que significa "resolução de colisão de tabela de hash".
Rahul

Respostas:

2

Você está realmente em um espaço de troca entre o tempo / memória necessário para gerar o dicionário e o tempo necessário para verificar todos os dados em busca de um método rápido.

Se você deseja um método com pouca memória, pode usar uma função que procura em cada sub-lista o valor. O uso de um gerador obterá resultados iniciais mais rapidamente para o usuário, mas para conjuntos de dados grandes, isso será lento entre os 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)

Conforme mencionado nos comentários, a criação de uma tabela de hash baseada apenas na primeira letra ou no primeiro caractere 2 ou 3 pode ser um bom ponto de partida. Isso permitirá que você crie uma lista de sublistas candidatas e, em seguida, verifique-as para ver se o valor está na sub-lista.

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)

Nesse código quick_hash, levará algum tempo para criar, porque você está verificando toda a sua estrutura de dados. No entanto, a pegada de memória será muito menor. Você parâmetro principal para ajustar o desempenho é size. Um tamanho menor terá uma área ocupada de memória menor, mas levará mais tempo durante a execução, find_list_by_hashporque o pool de candidatos será maior. Você pode fazer alguns testes para ver qual sizedeve ser o direito dos seus dados. Apenas tenha em mente que todos os seus valores são pelo menos enquanto size.

James
fonte
E eu pensei que conhecia python e programação. Obrigado. Há muito o que aprender.
Rahul
2

Você pode tentar algo como isto:

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

Não há necessidade de fazer uma estrutura de mapeamento.

Bhushan Pant
fonte
Obrigado, cara. Vou ter que verificar se isso é mais rápido.
Rahul
11
será muito mais rápido no início, porque não há compreensão para calcular, mas muito mais lento no uso, pois para cada elemento a ser encontrado, esse método fará uma nova varredura nos dados inteiros.
Edouard Thiel
Claro, deixe-me saber se isso funciona para você.
Bhushan Pant
@ EdouardThiel: Eu também sinto o mesmo. Meu uso real é ter mais casos de uso do que casos iniciais.
Rahul
@EdouardThiel true. Mas não tenho certeza sobre o caso de uso exato.
Bhushan Pant
2

tente isso, 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

isso parece uma solução simples, mesmo que seus dados cresçam muito, isso será eficiente

vgp2018
fonte
verificar o tamanho do seu df: é consideravelmente maior do que a lista data(> x12) eo dict temp_dict(~ x2) para os exemplos dados fornecidos - não exatamente eficiente para a memória eu diria
MrFuppes
@MrFuppes Eu não acho que este argumento é válido, desde pandas não copia fisicamente as cordas neste caso
mcsoini
@ Mcsoini, admito que meu comentário é um pouco superficial - uma análise mais detalhada seria necessária para determinar se pandaslida com esse problema mais eficiente do que a funcionalidade interna de python.
MrFuppes
@ MrFuppes: eu concordo. Por que usar pandasse isso pode ser feito usando stdlib. Só porque parece chique?
Rahul
11
Mas você não forneceu como consultarei o quadro de dados. Você pode me mostrar como sua solução resolverá meu problema. Tentei a solução do @ mcsoini para pandas, mas está demorando uma eternidade para 1 milhão de consultas. Não sei porque. Consulte minha pergunta atualizada para obter resultados de vários métodos.
Rahul
0

Não tenho muita certeza de como isso se comportaria para quantidades maiores de dados, mas você pode tentar algo como:

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: não parece ser benéfico em termos de tempo, com base em um teste rápido com alguns dados falsos de maior escala.

Mcsoini
fonte