通过匹配字典的值,在列表中查找字典的索引

171

我有一个字典列表:

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

我该如何高效地通过匹配名称为'Tom'来查找索引位置[0]、[1]或[2]?

如果这是一个一维列表,我可以使用list.index(),但我不知道如何通过搜索列表中的字典值来继续进行。


7
"list"是列表构造器,最好选择另一个名称来命名列表(甚至在示例中也是如此)。如果没有找到元素应该做出什么响应?抛出异常?返回None? - tokland
8
如果你需要频繁使用这个数据结构,建议使用更为合适的数据结构(比如 { 'Jason': {'id': '1234'}, 'Tom': {'id': '1245'}, ...})。 - user395760
3
因为那会导致灾难!如果非要这样做,应该是{'1234': {'name': 'Jason'}, ...},但这并不能帮助解决当前的问题。 - OJFord
12个回答

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

tom_index = next((index for (index, d) in enumerate(lst) if d["name"] == "Tom"), None)
# 1
如果需要反复从名称中获取值,你应该使用字典将它们按名称索引,这样获取操作的时间复杂度将为O(1)。一个想法:
def build_dict(seq, key):
    return dict((d[key], dict(d, index=index)) for (index, d) in enumerate(seq))

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

3
依据我个人看法,@Emile的答案不如可读性好,也不像Pythonic。因为真正的目的并不是创建生成器(而使用next()似乎对我来说很奇怪),目的只是获取索引。此外,这会引发StopIteration,而Python的lst.index()方法会引发ValueError。 - Ben Hoyt
如果没有找到任何匹配项,next(index for (index, d) in enumerate(lst) if d["name"] == "Tom", None) 将返回 None 而不是引发异常。 - gdw2
1
当我这样做时,我会收到“SyntaxError:如果不是唯一参数,则生成器表达式必须用括号括起来”的错误提示。 - avoliva
2
@avoliva 在 next((index for (index, d) in enumerate(lst) if d["name"] == "Tom"), None) 中的 next 周围加上括号,如下所示:(next((index for (index, d) in enumerate(lst) if d["name"] == "Tom"), None)) - HussienK
我会将d["name"]这部分改为d.get("name"),因为在第一种情况下我遇到了KeyError错误。 - undefined
显示剩余5条评论

72

一个简单易读的版本是

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

9
这似乎是最易读、最符合Python风格的。它还很好地模仿了str.find()的行为。如果喜欢的话,您还可以将其命名为index()并引发一个ValueError,而不是返回-1。 - Ben Hoyt
14
同意 - 返回-1表示未找到匹配,这将始终返回列表中的最后一个字典,这可能不是您想要的。最好返回None,并在调用代码中检查是否存在匹配。 - shacker

13

如果你需要遍历列表中的每个元素(O(n)),这样做并不高效。 如果你想要高效,可以使用字典嵌套字典

关于问题,以下是一种可能的解决方法(不过,如果你想坚持使用这种数据结构,使用生成器实际上更高效,如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的答案。 - Brent Newey
2
发电机并不改变事实,你必须遍历整个列表,使搜索成为O(n),就像aeter所说的那样...根据列表的长度,使用生成器与使用for循环或其他方式之间的差异可能是微不足道的,而使用字典与使用列表之间的差异则可能不是。 - Dirk
1
@Dirk 在生成器上的next()调用会在找到匹配项时停止,因此它不必遍历整个列表。 - Brent Newey
@aeter 你说得很有道理。我指的是能够在找到匹配项时停止。 - Brent Newey
字典的嵌套解决了我一个类似的问题,非常好的方式 +1 :) - Simon Nicholls
显示剩余2条评论

4

似乎最合理的方法是使用过滤器/索引组合:

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

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

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

3

@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”的字典及其任何值。


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

One liner!?

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 recipes 等其他有用的工具。


0

我需要一个更通用的解决方案,以考虑列表中有多个字典具有相同键值的可能性。使用列表推导式实现直接明了:

dict_indices = [i for i, d in enumerate(dict_list) if d[dict_key] == key_value] 

-1

我的答案更好,使用一个字典就可以了。

food_time_dict = {"Lina": 312400, "Tom": 360054, "Den": 245800}
print(list(food_time_dict.keys()).index("Lina"))

我从字典中请求键,如果列表未添加,则翻译该列表,否则将出现错误,然后将其用作列表。但在您的代码中:

lists = [{'id': '1234', 'name': 'Jason'},
         {'id': '2345', 'name': 'Tom'},
         {'id': '3456', 'name': 'Art'}]
    
    
def dict_in_lists_index(lists, search):  # function for convenience
    j = 0  # [j][i]
    for i in lists:
        try:  # try our varible search if not found in list
            return f"[{j}][{list(i.values()).index(search)}]"
            # small decor
        except ValueError: # error was ValueError
            pass # aa... what must was what you want to do
        j += 1 # not found? ok j++
    return "Not Found"
    
    
def dict_cropped_index(lists, search):
    for i in lists:
        try:
            return list(i.values()).index(search)
        except ValueError:
            pass
    return "Not Found"
    
    
print(dict_in_lists_index(lists, 'Tom')) # and end
print(dict_cropped_index(lists, 'Tom')) # now for sure end

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接