Эффективный поиск в списке списков для списков, содержащих заданное значение

#python #list #performance

#питон #Список #Производительность

Вопрос:

У меня есть большой список из ~ 10000 списков из трех элементов, и мне нужно найти в нем значение. Мне приходится проделывать эту операцию много раз — над одними и теми же данными — только с другим значением.

Например, если список:

 l = [[1, 4, 5], [3, 7, 1], [2, 6, 8]]
  

и значение, которое я ищу 1 , я могу искать списки, содержащие значение, выполнив:

 [sublist for sublist in l if value in sublist]
  

Для этого примера ожидаемый результат будет:

 [[1, 4, 5], [3, 7, 1]]
  

Есть ли более быстрый способ сделать это?

Комментарии:

1. Насколько велик внешний список? Вам нужно выполнить эту операцию один раз или повторять ее много раз? Если много раз — это одни и те же данные (просто другое значение)? Если это так, возможно, вы могли бы сохранить набор уникальных значений в любом месте списка списков, а затем просто выполнить поиск в нем. Создание такого набора все равно будет O(n) , но поиск значения в нем будет O(1)

2. Внешний список содержит около 10 000 внутренних списков. Мне приходится выполнять эту операцию много раз — над одними и теми же данными — только с другим значением. Что вы подразумеваете под «сохранением набора уникальных значений в любом месте списка списков»?

3. каков ожидаемый результат, например 1 ? Предполагая, что значения являются хешируемыми — преобразуйте его в dict с ключами, являющимися значениями в подсписках, а затем значениями — сбор индексов подсписков во внешнем списке.

4. хорошо, но мне также нужен список, содержащий значение, не только если есть список, содержащий значение. Несомненно, что некоторые внутренние списки внешнего списка содержат значение. Желаемый результат будет [[1, 4, 5], [3, 7, 1]]

Ответ №1:

Предполагая, что значения в подсписках являются хешируемыми, вы можете создать таблицу поиска — dict, где ключами являются значения в подсписках, а значениями dict — коллекция индексов подсписков во внешнем списке.

 from collections import defaultdict
spam = [[1, 4, 5], [3, 7, 1], [2, 6, 8]]
lookup = defaultdict(list)
for idx, item in enumerate(spam):
    for key in item:
        lookup[key].append(idx)

def get_sublists(array, lookup_table, value):
    return [array[idx] for idx in lookup_table[value]]


print(get_sublists(spam, lookup, 1))
print(get_sublists(spam, lookup, 7))
  

вывод

 [[1, 4, 5], [3, 7, 1]]
[[3, 7, 1]]
  

РЕДАКТИРОВАТЬ: некоторое сравнение с ответом @skarit

 from collections import defaultdict
from random import sample

spam = [list(sample(range(10), 3)) for _ in range(10000)]

lookup = defaultdict(list)
for idx, item in enumerate(spam):
    for key in item:
        lookup[key].append(idx)


def get_sublists(array, lookup_table, value):
    return [array[idx] for idx in lookup_table[value]]

import pandas as pd
import timeit


df = pd.DataFrame(spam)

print(timeit.timeit('[sublist for sublist in spam if 1 in sublist]', setup="from __main__ import spam", number=10000))
print(timeit.timeit('df = df[(df == 1).any(1)]', setup="from __main__ import df", number=10000))
print(timeit.timeit('get_sublists(spam, lookup, 1)', setup="from __main__ import get_sublists, spam, lookup", number=10000))
  

вывод

 10.568637077001767
10.028736718999426
2.4226377830018464
  

EDIT2: следуя комментарию @HeapOverfow, вот версия, в которой таблица поиска будет хранить соответствующие подсписки вместо их индексов. В зависимости от размера списка, следите за объемом памяти. Приведенный ниже фрагмент также отражает комментарий OP под ответом, чтобы исключить значение поиска из подсписков.

 from collections import defaultdict
from random import sample

spam = [list(sample(range(10), 3)) for _ in range(1000)]

lookup = defaultdict(list)
for item in spam:
    for key in item:
        lookup[key].append([num for num in item if num != key])


def get_sublists(array, lookup_table, value):
    return lookup_table[value]
  

Комментарии:

1. Хорошо, и одна маленькая вещь. Я бы хотел сохранить подсписки без самого значения. Например. для значения 1 это должно быть просто [[4, 5], [3, 7]]

2. просто измените return оператор, чтобы исключить значение

3. @Domi1908 return [[num for num in array[idx] if num != value] for idx in lookup_table[value]] . Но это как бы меняет ваш вопрос и не нравится тем, кто отвечает

4. Почему вы храните списки индексов соответствующих подсписков, а не просто списки соответствующих подсписков? Последнее было бы быстрее и стоило бы меньше памяти.

5. Это другое. Должно быть просто return lookup_table[value] .

Ответ №2:

Попробуйте это:

 import pandas as pd
value = 1 # The value you want to search

l = [[1, 4, 5], [3, 7, 1], [2, 6, 8]]
df = pd.DataFrame(l)
df = df[(df == value).any(1)]
  

Теперь этот фрейм данных будет содержать только «списки», которые имеют значение в виде строк