通过匹配字典的值来找到列表中字典的索引


130

我有一些字典:

list = [{'id':'1234','name':'Jason'},
        {'id':'2345','name':'Tom'},
        {'id':'3456','name':'Art'}]

如何通过匹配name ='Tom'来有效地找到索引位置[0],[1]或[2]?

如果这是一维列表,则可以执行list.index(),但是我不确定如何通过搜索列表中的dict的值来进行操作。


6
“列表”是列表构造函数,您最好为列表选择另一个名称(即使在示例中)。如果找不到任何元素,响应应该是什么?提出例外?不返回?
tokland 2010年

7
如果您将需要大量数据,请使用更合适的数据结构(也许{ 'Jason': {'id': '1234'}, 'Tom': {'id': '1245'}, ...}吗?)

3
@delnan因为那是灾难的秘诀!如果有的话,应该是{'1234': {'name': 'Jason'}, ...}。但这并不会对这个用例有所帮助。
OJFord

Answers:


145
tom_index = next((index for (index, d) in enumerate(lst) if d["name"] == "Tom"), None)
# 1

如果需要从名称重复获取,则应按名称对它们进行索引(使用字典),这样get操作的时间为O(1)。一个想法:

def build_dict(seq, key):
    return dict((d[key], dict(d, index=index)) for (index, d) in enumerate(seq))

info_by_name = build_dict(lst, key="name")
tom_info = info_by_name.get("Tom")
# {'index': 1, 'id': '2345', 'name': 'Tom'}

2
恕我直言,这不是那么可读或Pythonic是@Emile的答案。因为实际上并不是要创建一个生成器(next()对于我来说使用它似乎很奇怪),所以目的仅仅是获得索引。同样,这会引发StopIteration,而Python lst.index()方法会引发ValueError。
Ben Hoyt 2010年

@benhoyt:我也不喜欢StopIteration异常,但是虽然可以更改next()的默认值,但它引发的异常是固定的。pythonicity有点主观,所以我不会对此表示怀疑,因为for循环可能更pythonic。另一方面,有些人为next()别名next(),这听起来确实更好:first(index for(index,d)in ...)。
tokland 2010年

first()听起来更好。您可以始终尝试/除StopIteration之外并引发ValueError,以便调用方具有一致性。或者将next()的默认设置为-1。
Ben Hoyt 2010年

1
@ gdw2:我SyntaxError: Generator expression must be parenthesized if not sole argument这样做的时候就知道了。
avoliva

2
@avoliva在下一个周围添加括号,如下所示next((index for (index, d) in enumerate(lst) if d["name"] == "Tom"), None)
HussienK

45

一个简单的可读版本是

def find(lst, key, value):
    for i, dic in enumerate(lst):
        if dic[key] == value:
            return i
    return -1

8
这似乎是最易读和Pythonic的。它还模仿了str.find()很好的行为。如果可以的话,您也可以调用它index()并引发a ValueError而不是返回-1。
Ben Hoyt 2010年

6
同意-如果找不到匹配项,则返回-1,您将始终获得列表中的最后一个字典,这可能不是您想要的。最好返回None并检查调用代码中是否存在匹配项。
hacker

9

效率不高,因为您需要遍历列表检查其中的每个项目(O(n))。如果要提高效率,可以使用dict of dicts。关于这个问题,这是找到它的一种可能方法(不过,如果您要坚持使用这种数据结构,实际上使用 Brent Newey在评论中所写的生成器会更有效;另请参阅tokland的答案):

>>> L = [{'id':'1234','name':'Jason'},
...         {'id':'2345','name':'Tom'},
...         {'id':'3456','name':'Art'}]
>>> [i for i,_ in enumerate(L) if _['name'] == 'Tom'][0]
1

1
您可以使用发电机来获得所需的效率。请参阅tokland的答案。
布伦特·纽维

2
@Brent Newey:生成器并没有改变这一事实,您必须遍历整个列表,如aeter所说,进行搜索O(n)...根据该列表的长度,使用生成器与使用生成器之间的区别for循环或任何可能的疏忽,可能无法使用字典与使用列表之间的区别
Dirk 2010年

@布伦特:您是对的,但是如果搜索到的项目在列表的末尾,它还能胜过字典中的O(1)查找吗?
aeter 2010年

1
@Dirk当找到匹配项时,生成器上的next()调用将停止,因此它不必遍历整个列表。
布伦特·纽维

@aeter你说的很对。我指的是找到比赛时能够停下来。
布伦特·纽维

2

这是一个查找字典索引位置(如果存在)的函数。

dicts = [{'id':'1234','name':'Jason'},
         {'id':'2345','name':'Tom'},
         {'id':'3456','name':'Art'}]

def find_index(dicts, key, value):
    class Null: pass
    for i, d in enumerate(dicts):
        if d.get(key, Null) == value:
            return i
    else:
        raise ValueError('no dict with the key and value combination found')

print find_index(dicts, 'name', 'Tom')
# 1
find_index(dicts, 'name', 'Ensnare')
# ValueError: no dict with the key and value combination found

2

似乎最合乎逻辑的是使用过滤器/索引组合:

names=[{}, {'name': 'Tom'},{'name': 'Tony'}]
names.index(filter(lambda n: n.get('name') == 'Tom', names)[0])
1

如果您认为可能存在多个匹配项:

[names.index(n) for item in filter(lambda n: n.get('name') == 'Tom', names)]
[1]

2

@faham提供的答案很不错,但是它不会将索引返回包含该值的字典。而是返回字典本身。这是一种简单的获取方法:如果有多个索引,则一个或多个索引列表;如果不存在,则为空列表:

list = [{'id':'1234','name':'Jason'},
        {'id':'2345','name':'Tom'},
        {'id':'3456','name':'Art'}]

[i for i, d in enumerate(list) if 'Tom' in d.values()]

输出:

>>> [1]

我喜欢这种方法的地方是,通过简单的编辑,您既可以将索引又将字典作为元组得到一个列表。这是我需要解决并找到这些答案的问题。在下面的代码中,我在另一个字典中添加了重复的值以显示其工作方式:

list = [{'id':'1234','name':'Jason'},
        {'id':'2345','name':'Tom'},
        {'id':'3456','name':'Art'},
        {'id':'4567','name':'Tom'}]

[(i, d) for i, d in enumerate(list) if 'Tom' in d.values()]

输出:

>>> [(1, {'id': '2345', 'name': 'Tom'}), (3, {'id': '4567', 'name': 'Tom'})]

该解决方案可查找所有值中包含“ Tom”的所有词典。


1

一个班轮!!

elm = ([i for i in mylist if i['name'] == 'Tom'] or [None])[0]

0

对于给定的可迭代项,more_itertools.locate产生满足谓词的项的位置。

import more_itertools as mit


iterable = [
    {"id": "1234", "name": "Jason"},
    {"id": "2345", "name": "Tom"},
    {"id": "3456", "name": "Art"}
]

list(mit.locate(iterable, pred=lambda d: d["name"] == "Tom"))
# [1]

more_itertools是在其他有用工具中实现itertools配方的第三方库。


By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.