Словарь Python с несколькими ключами, указывающими на один и тот же список в памяти эффективным способом

9

У меня есть это уникальное требование, которое можно объяснить этим кодом. Это рабочий код, но не эффективный для памяти.

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

Короче говоря, я хочу, чтобы каждый элемент подсписка был индексируемым и должен возвращать подсписок.

Вышеуказанный метод работает, но он занимает много памяти, когда данные большие. Есть ли лучший способ памяти и процессора? Данные хранятся в виде файла JSON.

Редактировать Я попробовал ответы для максимально возможного сценария использования (1000 подсписков, 100 элементов в каждом подсписке, 1 миллион запросов), и вот результаты (среднее из 10 прогонов):

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}может быть немного более эффективным и прямым ... ?!
deceze
Да. для моего примера. В моем реальном сценарии подсписка содержит сотни элементов и тысячи таких подсписков. У пользователя кода небольшая память (<2 ГБ), поэтому, когда запущено другое тяжелое приложение, он жалуется, что ваш скрипт работает медленно.
Рахул
Какую проблему вы пытаетесь решить точно? Возможно, подойдет гибридный подход, при котором вы индексируете по первой букве, а затем просматриваете несколько списков кандидатов, чтобы найти ваше точное значение, что-то вроде алгоритма разрешения коллизий в хеш-таблице.
deceze
Для эффективного использования используйте генераторы типа yield ().
Saisiva A
Спасибо. Я узнаю, что означает «разрешение коллизий хеш-таблицы».
Рахул

Ответы:

2

Вы действительно находитесь в компромиссе между временем и памятью, которые требуются для создания словаря, и временем, которое требуется для сканирования всех данных для оперативного метода.

Если вам нужен метод с нехваткой памяти, вы можете использовать функцию, которая ищет значение в каждом подсписке. Использование генератора быстрее даст начальные результаты пользователю, но для больших наборов данных это будет медленно между возвратами.

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)

Как уже упоминалось в комментариях, хорошим началом может стать построение хеш-таблицы, основанной только на первой букве или первых 2 или 3 символах. Это позволит вам составить список кандидатов из подсписков, а затем отсканировать их, чтобы увидеть, есть ли значение в подсписке.

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)

Создание этого кода quick_hashзаймет некоторое время, поскольку вы сканируете всю структуру данных. Однако отпечаток памяти будет намного меньше. Вы основным параметром для настройки производительности является size. Меньший размер будет занимать меньше места в памяти, но займет больше времени при запуске, find_list_by_hashпотому что ваш пул кандидатов будет больше. Вы можете провести некоторое тестирование, чтобы увидеть, какое право sizeдолжно быть на ваши данные. Просто помните, что все ваши ценности по крайней мере так же долго, как и size.

Джеймс
источник
И я подумал, что знаю Python и программирование. Спасибо. Есть чему поучиться.
Рахул
2

Вы можете попробовать что-то вроде этого:

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

Не нужно составлять картографическую структуру.

Бхушан Пант
источник
Спасибо, мужик. Я должен проверить, если это быстрее.
Рахул
1
это будет намного быстрее при запуске, потому что нет никакого понимания для вычисления, но намного медленнее при использовании, потому что для каждого элемента, чтобы найти, этот метод повторно сканирует все данные.
Эдуард Тиль
Конечно, дайте мне знать, если это работает для вас.
Bhushan Pant
@EdouardThiel: я тоже чувствую то же самое. Мое фактическое использование - это больше вариантов использования, чем начальных вариантов.
Рахул
@ EdouardThiel правда. Но я не уверен насчет точного варианта использования.
Bhushan Pant
2

попробуйте это, используя панд

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

это выглядит простым решением, даже если ваши данные растут большими, это будет эффективно обрабатывать

vgp2018
источник
проверьте размер вашего df: он значительно больше, чем список data(> x12) и dict temp_dict(~ x2) для данных данного примера - не совсем эффективно с памятью, я бы сказал
MrFuppes
@MrFuppes Я не думаю, что этот аргумент действителен, так как pandas физически не копирует строки в этом случае
mcsoini
@mcsoini, я признаю, что мой комментарий немного поверхностен - необходим более детальный анализ, чтобы определить, эффективнее ли pandasобрабатывает эту проблему, чем встроенные функции Python.
MrFuppes
@MrFuppes: я согласен. Зачем использовать, pandasесли это можно сделать с помощью stdlib. Просто потому что это выглядит модно?
Рахул
1
Но вы не указали, как я буду запрашивать данные. Можете ли вы показать мне, как ваше решение решит мою проблему. Я попробовал решение @ mcsoini для панд, но оно требует вечности для 1 миллиона запросов. Я не знаю почему. Пожалуйста, смотрите мой обновленный вопрос о результатах различных методов.
Рахул
0

Я не совсем уверен, как это будет вести себя для больших объемов данных, но вы можете попробовать что-то вроде:

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

Изменить: не кажется полезным с точки зрения времени, на основе быстрого теста с некоторыми поддельными данными большего масштаба.

mcsoini
источник