如何在Python中搜索元组列表

103

我有一个元组列表,例如这样:

[(1,"juca"),(22,"james"),(53,"xuxa"),(44,"delicia")]

我想要一个针对于number值等于某个值的元组的索引列表。

这样,如果我执行 search(53),它将返回 2 的索引值。

有没有简单的方法可以做到这一点?

8个回答

109
[i for i, v in enumerate(L) if v[0] == 53]

19
对于L的枚举列表中的每个i和v(其中i是元素在枚举列表中的位置,v是原始元组),检查元组的第一个元素是否为53,如果是,则将'for'之前代码的结果附加到新创建的列表中,这里是:i。这也可以是my_function(i, v)或另一个列表推导式。由于您的元组列表只有一个以53作为第一个值的元组,因此您将获得一个具有一个元素的列表。 - djangonaut
9
为了得到整数值,我只需要添加[i for i, v in enumerate(L) if v[0] == 53].pop()。 - alemol

69

简述

生成器表达式可能是解决您问题的最高效和简单的方法:

l = [(1,"juca"),(22,"james"),(53,"xuxa"),(44,"delicia")]

result = next((i for i, v in enumerate(l) if v[0] == 53), None)
# 2

解释

有几个答案提供了使用列表推导式的简单解决方案。虽然这些答案是完全正确的,但它们并不是最优的。根据您的用例,进行一些简单的修改可能会带来显着的好处。

我认为在这种用例中使用列表推导式的主要问题是整个列表将被处理,尽管您只想找到一个元素。

Python提供了一个简单的构造,非常适合这里。它称为生成器表达式。以下是一个示例:

# Our input list, same as before
l = [(1,"juca"),(22,"james"),(53,"xuxa"),(44,"delicia")]

# Call next on our generator expression.
next((i for i, v in enumerate(l) if v[0] == 53), None)

我们可以预期,在我们的简单示例中,这种方法的执行基本上与列表推导式相同,但如果我们正在处理更大的数据集呢? 这就是使用生成器方法的优势所在。 我们将使用您现有的列表作为可迭代对象,并使用next()来从生成器中获取第一个项目,而不是构建新列表。
让我们看看这些方法在一些较大的数据集上如何表现不同。 这些是由10000000 + 1个元素组成的大型列表,其中目标在开头(最佳)或结尾(最差)。 我们可以使用以下列表推导式验证这两个列表将执行相等:

列表推导式

"最坏情况"

worst_case = ([(False, 'F')] * 10000000) + [(True, 'T')]
print [i for i, v in enumerate(worst_case) if v[0] is True]

# [10000000]
#          2 function calls in 3.885 seconds
#
#    Ordered by: standard name
#
#    ncalls  tottime  percall  cumtime  percall filename:lineno(function)
#         1    3.885    3.885    3.885    3.885 so_lc.py:1(<module>)
#         1    0.000    0.000    0.000    0.000 {method 'disable' of '_lsprof.Profiler' objects}

“最佳情况”

best_case = [(True, 'T')] + ([(False, 'F')] * 10000000)
print [i for i, v in enumerate(best_case) if v[0] is True]

# [0]
#          2 function calls in 3.864 seconds
#
#    Ordered by: standard name
#
#    ncalls  tottime  percall  cumtime  percall filename:lineno(function)
#         1    3.864    3.864    3.864    3.864 so_lc.py:1(<module>)
#         1    0.000    0.000    0.000    0.000 {method 'disable' of '_lsprof.Profiler' objects}

生成器表达式

我的假设是,生成器在最好的情况下将显着提高性能,但在最坏的情况下也会有类似的表现。 这种性能提升主要是因为生成器是惰性求值的,这意味着它只会计算产生值所需的内容。

最坏情况

# 10000000
#          5 function calls in 1.733 seconds
#
#    Ordered by: standard name
#
#    ncalls  tottime  percall  cumtime  percall filename:lineno(function)
#         2    1.455    0.727    1.455    0.727 so_lc.py:10(<genexpr>)
#         1    0.278    0.278    1.733    1.733 so_lc.py:9(<module>)
#         1    0.000    0.000    0.000    0.000 {method 'disable' of '_lsprof.Profiler' objects}
#         1    0.000    0.000    1.455    1.455 {next}

最佳情况

best_case  = [(True, 'T')] + ([(False, 'F')] * 10000000)
print next((i for i, v in enumerate(best_case) if v[0] == True), None)

# 0
#          5 function calls in 0.316 seconds
#
#    Ordered by: standard name
#
#    ncalls  tottime  percall  cumtime  percall filename:lineno(function)
#         1    0.316    0.316    0.316    0.316 so_lc.py:6(<module>)
#         2    0.000    0.000    0.000    0.000 so_lc.py:7(<genexpr>)
#         1    0.000    0.000    0.000    0.000 {method 'disable' of '_lsprof.Profiler' objects}
#         1    0.000    0.000    0.000    0.000 {next}

什么?最好情况下的生成器表达式比列表推导更好,但我没想到我们最坏情况下的性能能超过列表推导这么多。

为什么呢?老实说,如果没有进一步的研究,我只能猜测。

这些只是一些非常基本的测试,不是严格的分析,所以请持怀疑态度。但这足以证明一个生成器表达式在这种类型的列表搜索中更加高效。

请注意,这全部都是Python的基本内置功能,我们不需要导入任何东西或使用任何库。

我第一次看到这种搜索技巧是在Udacity cs212课程中的Peter Norvig老师介绍的。


2
有趣的是,我进行了测试并发现它确实非常快。 - Grijesh Chauhan
4
这应该是被接受的答案。 生成器表达式在运行时不会将整个输出序列完全实现,而是评估为一个迭代器,该迭代器从表达式中逐个产生一个项。 - BoltzmannBrain
2
这太棒了,在我的情况下比列表推导式快得多,谢谢! - mindm49907

50
你可以使用一个列表推导式:list comprehension
>>> a = [(1,"juca"),(22,"james"),(53,"xuxa"),(44,"delicia")]
>>> [x[0] for x in a]
[1, 22, 53, 44]
>>> [x[0] for x in a].index(53)
2

33

你的元组基本上是键值对,相当于Python中的dict,因此:

l = [(1,"juca"),(22,"james"),(53,"xuxa"),(44,"delicia")]
val = dict(l)[53]

编辑 -- 啊哈,你说你想要 (53, "xuxa") 的索引值。如果这确实是你想要的,你将不得不遍历原始列表,或者可能创建一个更复杂的字典:

d = dict((n,i) for (i,n) in enumerate(e[0] for e in l))
idx = d[53]

4
如果我们忽略原问题的要求,我认为你最初的答案是回答“如何在Python中搜索元组列表”的最佳答案。 - Rick Westera
你的第一个回答对我的目的很有用。也许最好使用.get(),以防该项不在字典中。l = [(1,"juca"),(22,"james"),(53,"xuxa"),(44,"delicia")] val = dict(l).get(53) - user1503941

12

嗯... 好的,我想到的简单方法是将其转换为字典

d = dict(thelist)

并访问d[53]

编辑:糟糕,我第一次误读了你的问题。听起来你实际上想要获取存储给定数字的索引。在这种情况下,请尝试

dict((t[0], i) for i, t in enumerate(thelist))

使用一个不同于普通的dict转换。那么d[53]将为2。


7

假设列表可能很长,而且数字可能会重复出现,考虑使用Python sortedcontainers模块中的SortedList类型。SortedList类型将自动按数字顺序维护元组,并允许快速搜索。

例如:

from sortedcontainers import SortedList
sl = SortedList([(1,"juca"),(22,"james"),(53,"xuxa"),(44,"delicia")])

# Get the index of 53:

index = sl.bisect((53,))

# With the index, get the tuple:

tup = sl[index]

使用二分查找会比列表推导更快。使用字典会更快,但如果有可能出现不同字符串的重复数字,则无法正常工作。

如果有重复数字和不同的字符串,则需要再添加一步:

end = sl.bisect((53 + 1,))

results = sl[index:end]

通过二分查找,我们将找到切片的结束索引为54。与已接受的答案相比,在长列表上这将显着提高速度。


2

另一种方式。

zip(*a)[0].index(53)

-2

[k for k,v in l if v =='delicia']

这里l是一个元组列表-[(1,"juca"),(22,"james"),(53,"xuxa"),(44,"delicia")]

而我们不是将其转换为字典,而是使用了列表推导。

*Key* in Key,Value in list, where value = **delicia**


好的,谢谢 @cosmoonot。 - Mantej Singh
这里l是元组列表-[(1,“juca”),(22,“james”),(53,“xuxa”),(44,“delicia”)],我们使用列表推导式而不是将其转换为字典。 _Key_ in Key,Value in list, where value = **delicia** - Mantej Singh

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