如何在Python中搜索元组列表
我有一个包含多个元组的列表,像这样:
[(1,"juca"),(22,"james"),(53,"xuxa"),(44,"delicia")]
我想从这个列表中找到一个元组,它的数字值等于某个特定的值。
比如说,如果我输入 search(53)
,它应该返回索引值 2
。
有没有简单的方法可以做到这一点呢?
8 个回答
你可以使用一种叫做 列表推导式 的方法:
>>> 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
总结
一种叫做生成器表达式的方法,可能是解决你问题的最快和最简单的办法:
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个元素的大列表,目标元素在开头(最好情况)或结尾(最坏情况)。我们可以通过以下的列表推导来验证这两个列表的表现是一样的:
列表推导
最坏情况
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课程中,讲师是彼得·诺维格。
[i for i, v in enumerate(L) if v[0] == 53]
这段代码的意思是……
首先,它做了一个什么操作,然后又进行了另一个操作。接下来,它会检查一些条件,如果满足这些条件,就会执行特定的代码块。最后,它可能会返回一个结果或者继续进行其他的处理。
总之,这段代码的主要目的是……
希望这个解释能帮助你更好地理解这段代码的功能!