Словник 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}може бути трохи ефективнішим і прямим… ?!
деге

Так. для мого зразка. У моєму реальному випадку сценарій містить 100 тисяч елементів та тисячі таких списків. Користувач коду має мало пам'яті (<2 Гб), тому коли працює інший важкий додаток, вони скаржаться на те, що ваш сценарій повільний.
Рахул

Яку проблему ви намагаєтеся вирішити саме? Можливо, спрацює гібридний підхід, в якому ви індексуєте першу букву, а потім повторіть кілька списків кандидатів, щоб знайти своє точне значення, подібне до алгоритму вирішення хеш-таблиць.
деге

Для ефективного використання використовують такі генератори, як урожай ().
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.


І я подумав, що знаю пітон і програмування. Дякую. Є чому навчитися.
Рахул

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

Це виглядає простим рішенням, навіть якщо ваші дані зростають, це допоможе ефективно


перевірте розмір вашого df: він значно більший за список data(> x12) та диктант temp_dict(~ x2) для наведених
прикладних

@MrFuppes Я не думаю, що цей аргумент є дійсним, оскільки панди фізично не копіюють рядки в цьому випадку
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

Редагування: Не здається корисним за часом, ґрунтуючись на швидкому тесті з деякими підробленими масштабними даними.

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.