簡體   English   中英

從列表中創建字典的 Pythonic 方法,其中鍵是在另一個列表中找到的元素,值是鍵之間的元素

[英]Pythonic way to create a dictionary from a list where the keys are the elements that are found in another list and values are elements between keys

考慮到我有兩個列表,例如:

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

什么是更pythonic的方法來做到這一點?

目前我正在這樣做:

# 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)

但我很肯定這不是最好的方法,而且看起來也不好看:)

編輯:我還必須提到,沒有必要對列表進行排序,並且第二個列表都不能以第一個列表中的元素開頭。

如果這是對問題的最具體陳述,我認為您不會做得更好。 我的意思是我會這樣做,但也好不到哪里去。

import collections


d = collections.defaultdict(list)
s = set(l1)
k = ''

for x in l2:
    if x in s:
        k = x
    else:
        d[k].append(x)

為了好玩,你也可以用itertools和 3rd party 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']}

這是一個使用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中的鍵(如ef )和沒有對應值的鍵(如d )。 如果需要此信息,其他解決方案之一可能更適合。

更新...再次

我誤解了這個問題。 如果您使用大型列表,那么列表推導式是可行的方法,一旦您學會了如何使用它們,它們就相當簡單。

我將使用兩個列表推導式。

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)

列表理解/字典理解(第一段代碼)實際上要快得多。 列表推導式正在就地創建列表,這比遍歷並附加到列表要快得多。 追加使程序遍歷列表,分配更多內存,並將數據添加到列表中,這對於大型列表來說可能非常慢。

參考:

您可以使用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']}

您的代碼是可讀的,可以完成工作並且相當高效。 沒有必要改變太多!

您可以使用更具描述性的變量名稱並將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']}
 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 的測試:
正義天使
run_comp:.455
run_other: .244
mkrieger1
group_by_keys:.160

查找索引:.068

請注意,我的方法會忽略未出現l2鍵,並且不處理鍵在l2出現多次的情況。 可以通過{**res, **{key: [] for key in set(l1).difference(set(l2))}}為未出現在l2鍵添加空列表,這會引發時間到 0.105。

甚至比將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)

這是因為(在最壞的情況下)您的代碼將在if x in l1:行上針對l2中的每個值迭代l1中的所有值,因為檢查值是否in列表中需要線性時間 在一般情況下,檢查值是否in字典的鍵中是恆定時間(set s 相同,正如Eric Duminil已經建議的那樣)。

我將k設置為None並檢查它,因為您的代碼會以'': ['x','q','we','da','po']返回d ,這可能不是您想要的. 這假設l1不能包含None

我的解決方案還假設,如果l1中的項目從未出現在l2 ,則結果字典可以包含帶有空列表的鍵。 如果這不行,你可以在最后刪除它們

final_d = {k: v for k, v in d.items() if v}

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM