Pythonic способ создания словаря из списка, где ключи являются элементами, которые находятся в другом списке, а значения - это элементы между ключами
Учитывая, что у меня есть два списка:
l1 = ['a', 'c', 'b', 'e', 'f', 'd']
l2 = [
'x','q','we','da','po',
'a', 'el1', 'el2', 'el3', 'el4',
'b', 'some_other_el_1', 'some_other_el_2',
'c', 'another_element_1', 'another_element_2',
'd', '', '', 'another_element_3', 'd4'
]
и мне нужно создать словарь, где ключи - это те элементы из второго списка, которые находятся в первом, а значения - это списки элементов, найденных между "ключами":
result = {
'a': ['el1', 'el2', 'el3', 'el4'],
'b': ['some_other_el_1', 'some_other_el_2'],
'c': ['another_element_1', 'another_element_2'],
'd': ['', '', 'another_element_3', 'd4']
}
Какой еще более питонический способ сделать это?
В настоящее время я делаю это:
# I'm not sure that the first element in the second list
# will also be in the first so I have to create a key
k = ''
d[k] = []
for x in l2:
if x in l1:
k = x
d[k] = []
else:
d[k].append(x)
Но я вполне уверен, что это не лучший способ сделать это, и он тоже не выглядит красивым :)
Изменение: Мне также нужно указать, что список не требуется, и ни второй список не должен начинаться с элемента из первого.
Ответы
Ответ 1
Я не думаю, что вы сделаете намного лучше, если это наиболее конкретное утверждение проблемы. Я имею в виду, я бы сделал это так, но это не намного лучше.
import collections
d = collections.defaultdict(list)
k = ''
for x in l2:
if x in l1:
k = x
else:
d[k].append(x)
Ответ 2
Для удовольствия вы также можете сделать это с помощью itertools
и стороннего numpy
:
import numpy as np
from itertools import zip_longest, islice
arr = np.where(np.in1d(l2, l1))[0]
res = {l2[i]: l2[i+1: j] for i, j in zip_longest(arr, islice(arr, 1, None))}
print(res)
{'a': ['el1', 'el2', 'el3', 'el4'],
'b': ['some_other_el_1', 'some_other_el_2'],
'c': ['another_element_1', 'another_element_2'],
'd': ['', '', 'another_element_3', 'd4']}
Ответ 3
Вот версия, использующая itertools.groupby
. Это может быть или не быть более эффективным, чем простая версия вашего сообщения, в зависимости от того, как groupby
, поскольку цикл for
имеет меньшее количество итераций.
from itertools import groupby
from collections import defaultdict, deque
def group_by_keys(keys, values):
"""
>>> sorted(group_by_keys('abcdef', [
... 1, 2, 3,
... 'b', 4, 5,
... 'd',
... 'a', 6, 7,
... 'c', 8, 9,
... 'a', 10, 11, 12
... ]).items())
[('a', [6, 7, 10, 11, 12]), ('b', [4, 5]), ('c', [8, 9])]
"""
keys = set(keys)
result = defaultdict(list)
current_key = None
for is_key, items in groupby(values, key=lambda x: x in keys):
if is_key:
current_key = deque(items, maxlen=1).pop() # last of items
elif current_key is not None:
result[current_key].extend(items)
return result
Это не различает ключи, которые вообще не встречаются в values
(например, e
и f
), и клавиши, для которых нет соответствующих значений (например, d
). Если эта информация необходима, одно из других решений может быть лучше подходит.
Ответ 4
Обновлено... Опять
Я неверно истолковал этот вопрос. Если вы используете большие списки, то список понятий - это путь, и они довольно просты, когда вы узнаете, как их использовать.
Я собираюсь использовать два понимания списка.
idxs = [i for i, val in enumerate(l2) if val in l1] + [len(l2)+1]
res = {l2[idxs[i]]: list(l2[idxs[i]+1: idxs[i+1]]) for i in range(len(idxs)-1)}
print(res)
Результаты:
{'a': ['el1', 'el2', 'el3', 'el4'],
'b': ['some_other_el_1', 'some_other_el_2'],
'c': ['another_element_1', 'another_element_2'],
'd': ['', '', 'another_element_3', 'd4']}
Тестирование скорости для больших списков:
import collections
l1 = ['a', 'c', 'b', 'e', 'f', 'd']
l2 = [
'x','q','we','da','po',
'a', 'el1', 'el2', 'el3', 'el4', *(str(i) for i in range(300)),
'b', 'some_other_el_1', 'some_other_el_2', *(str(i) for i in range(100)),
'c', 'another_element_1', 'another_element_2', *(str(i) for i in range(200)),
'd', '', '', 'another_element_3', 'd4'
]
def run_comp():
idxs = [i for i, val in enumerate(l2) if val in l1] + [len(l2)+1]
res = {l2[idxs[i]]: list(l2[idxs[i]+1: idxs[i+1]]) for i in range(len(idxs)-1)}
def run_other():
d = collections.defaultdict(list)
k = ''
for x in l2:
if x in l1:
k = x
else:
d[k].append(x)
import timeit
print('For Loop:', timeit.timeit(run_other, number=1000))
print("List Comprehension:", timeit.timeit(run_comp, number=1000))
Результаты:
For Loop: 0.1327093063242541
List Comprehension: 0.09343156142774986
старый материал ниже
Это довольно просто со списком.
{key: [val for val in l2 if key in val] for key in l1}
Результаты:
{'a': ['a', 'a1', 'a2', 'a3', 'a4'],
'b': ['b', 'b1', 'b2', 'b3', 'b4'],
'c': ['c', 'c1', 'c2', 'c3', 'c4'],
'd': ['d', 'd1', 'd2', 'd3', 'd4'],
'e': [],
'f': []}
В приведенном ниже коде показано, что происходит выше.
d = {}
for key in l1:
d[key] = []
for val in l2:
if key in val:
d[key].append(val)
Понимание понимания языка/словаря (первая часть кода) на самом деле быстрее. Перечисление списков создает список, который намного быстрее, чем прохождение и добавление в список. Добавление позволяет программе ходить по списку, выделять больше памяти и добавлять данные в список, который может быть очень медленным для больших списков.
Рекомендации:
Ответ 5
Вы можете использовать itertools.groupby
:
import itertools
l1 = ['a', 'c', 'b', 'e', 'f', 'd']
l2 = ['x', 'q', 'we', 'da', 'po', 'a', 'el1', 'el2', 'el3', 'el4', 'b', 'some_other_el_1', 'some_other_el_2', 'c', 'another_element_1', 'another_element_2', 'd', '', '', 'another_element_3', 'd4']
groups = [[a, list(b)] for a, b in itertools.groupby(l2, key=lambda x:x in l1)]
final_dict = {groups[i][-1][-1]:groups[i+1][-1] for i in range(len(groups)-1) if groups[i][0]}
Выход:
{'a': ['el1', 'el2', 'el3', 'el4'], 'b': ['some_other_el_1', 'some_other_el_2'], 'c': ['another_element_1', 'another_element_2'], 'd': ['', '', 'another_element_3', 'd4']}
Ответ 6
Ваш код читаем, выполняет работу и является достаточно эффективным. Нет необходимости многое менять!
Вы можете использовать более описательные имена переменных и заменить l1
набором для более быстрого поиска:
keys = ('a', 'c', 'b', 'e', 'f', 'd')
keys_and_values = [
'x','q','we','da','po',
'a', 'el1', 'el2', 'el3', 'el4',
'b', 'some_other_el_1', 'some_other_el_2',
'c', 'another_element_1', 'another_element_2',
'd', '', '', 'another_element_3', 'd4'
]
current_key = None
result = {}
for x in keys_and_values:
if x in keys:
current_key = x
result[current_key] = []
elif current_key:
result[current_key].append(x)
print(result)
# {'a': ['el1', 'el2', 'el3', 'el4'],
# 'c': ['another_element_1', 'another_element_2'],
# 'b': ['some_other_el_1', 'some_other_el_2'],
# 'd': ['', '', 'another_element_3', 'd4']}
Ответ 7
def find_index():
idxs = [l2.index(i) for i in set(l1).intersection(set(l2))]
idxs.sort()
idxs+= [len(l2)+1]
res = {l2[idxs[i]]: list(l2[idxs[i]+1: idxs[i+1]]) for i in range(len(idxs)-1)}
return(res)
Сравнение методов с использованием теста justengel:
justengel
run_comp:.455
run_other:.244
mkrieger1
group_by_keys:.160
мне
find_index:.068
Обратите внимание, что мой метод игнорирует ключи, которые не отображаются l2
, и не обрабатывает случаи, когда ключи появляются более одного раза в l2
. Добавление в пустые списки для ключей, которые не отображаются в l2
может быть выполнено с помощью {**res, **{key: [] for key in set(l1).difference(set(l2))}}
, что повышает время до.105.
Ответ 8
Даже чище, чем поворачивать l1
в set
, используйте клавиши словаря, который вы строите. Как это
d = {x: [] for x in l1}
k = None
for x in l2:
if x in d:
k = x
elif k is not None:
d[k].append(x)
Это связано с тем, что (в худшем случае) ваш код будет итерировать по всем значениям в l1
для каждого значения в l2
if x in l1:
строке if x in l1:
строке if x in l1:
поскольку проверка того, имеет ли значение in
списке, занимает линейное время. Проверка того, является ли значение in
словарных клавишах постоянным временем в среднем случае (то же самое с set
s, как уже было предложено Эриком Думинилом).
Я устанавливаю k
в None
и проверяю его, потому что ваш код вернул d
с помощью '': ['x','q','we','da','po']
, который предположительно не является тем, что вы хотите, Это предполагает, что l1
не может содержать None
.
Мое решение также предполагает, что результирующий словарь содержит ключи с пустыми списками, если в l1
нет элементов, которые никогда не появляются в l2
. Если это не нормально, вы можете удалить их в конце с помощью
final_d = {k: v for k, v in d.items() if v}