Pythonic从列表创建字典的方法,其中键是在另一个列表中找到的元素,值是键

时间:2018-05-15 12:40:05

标签: python list dictionary

考虑到我有两个列表,如:

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)

但我非常肯定这不是最好的方法,而且看起来也不是很好:)

编辑: 我还必须提到,没有必要排序列表,第二个列表也不必以第一个列表中的元素开头。

8 个答案:

答案 0 :(得分:10)

如果这是问题的最具体陈述,我认为你会做得更好。我的意思是我这样做,但情况并不好。

import collections

d = collections.defaultdict(list)
k = ''

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

答案 1 :(得分:5)

为了好玩,您也可以使用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']}

答案 2 :(得分: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中发生的密钥(如ef),以及没有对应的密钥值(如d)。如果需要此信息,其他解决方案之一可能更适合。

答案 3 :(得分:2)

更新......再次

我误解了这个问题。如果您使用的是大型列表,那么列表推导就是可行的方法,一旦您学会了如何使用它们,它们就相当简单。

我将使用两个列表推导。

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)

列表理解/字典理解(第一段代码)实际上更快。列表推导正在创建列表,这比遍历和附加到列表要快得多。追加使程序遍历列表,分配更多内存,并将数据添加到列表中,这对于大型列表来说可能非常慢。

参考文献:

答案 4 :(得分:1)

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

答案 5 :(得分:1)

您的代码是可读的,完成工作且效率相当高。没有必要改变太多!

您可以使用更多描述性变量名称,并将l1替换为set,以便更快地查找:

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

答案 6 :(得分:1)

 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))}}中的密钥的空列表可以通过const enum SomeEnum { Val0, Val1 } 完成,这会将时间提高到.105。

答案 7 :(得分:1)

比将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:中的每个值迭代in中的所有值,因为{{3} }。检查值是否为k字典的密钥在一般情况下是checking if a value is in a list takes linear time的常量时间,因为已经是same with sets。)

我将None设置为d并检查它,因为您的代码将'': ['x','q','we','da','po']返回l1,这可能不是您想要的。这假定None不能包含l1

我的解决方案还假设,如果l2中的项目从未出现在final_d = {k: v for k, v in d.items() if v} 中,则生成的字典可以包含带有空列表的键。如果这不合适,您可以在

结束时删除它们
<dependency>
    <groupId>org.projectlombok</groupId>
    <artifactId>lombok</artifactId>
    <version>1.16.16</version>
    <scope>provided</scope>
</dependency>