如何查找列表中元素的所有匹配项

index() 将给出列表中某项的第一个匹配项。是否有一个巧妙的技巧可以返回一个元素列表中的所有索引?

721615 次浏览

你可以使用enumerate的列表理解:

indices = [i for i, x in enumerate(my_list) if x == "whatever"]

迭代器enumerate(my_list)为列表中的每一项生成对(index, item)。使用i, x作为循环变量目标将这些对解包到索引i和列表项x中。我们向下筛选到所有符合条件的x,并选择这些元素的索引i

如何:

In [1]: l=[1,2,3,4,3,2,5,6,7]


In [2]: [i for i,val in enumerate(l) if val==3]
Out[2]: [2, 4]
occurrences = lambda s, lst: (i for i,e in enumerate(lst) if e == s)
list(occurrences(1, [1,2,3,1])) # = [0, 3]

对于所有发生的情况,还有一个解决方案(抱歉,如果重复):

values = [1,2,3,1,2,4,5,6,3,2,1]
map(lambda val: (val, [i for i in xrange(len(values)) if values[i] == val]), values)

虽然不是列表的直接解决方案,但numpy确实适用于这类事情:

import numpy as np
values = np.array([1,2,3,1,2,4,5,6,3,2,1])
searchval = 3
ii = np.where(values == searchval)[0]

返回:

ii ==>array([2, 8])

对于包含大量元素的列表(数组),这比其他解决方案要快得多。

使用list.index的解决方案:

def indices(lst, element):
result = []
offset = -1
while True:
try:
offset = lst.index(element, offset+1)
except ValueError:
return result
result.append(offset)

对于大型列表,它比使用enumerate的列表理解要快得多。它也比已经拥有数组的numpy解决方案如果慢得多,否则转换的成本超过了速度增益(在包含100、1000和10000个元素的整数列表上测试)。

根据Chris_Rands的评论注意:如果结果足够稀疏,这个解决方案比列表推导更快,但是如果列表有许多正在搜索的元素的实例(超过列表的15%,在1000个整数列表的测试中),列表推导更快。

如果你使用的是Python 2,你可以用这个实现相同的功能:

f = lambda my_list, value:filter(lambda x: my_list[x] == value, range(len(my_list)))

其中my_list是您想要获取索引的列表,value是搜索的值。用法:

f(some_list, some_element)

如果你需要搜索所有元素在某些指标之间的位置,你可以声明它们:

[i for i,x in enumerate([1,2,3,2]) if x==2 & 2<= i <=3] # -> [3]

您可以创建defaultdict

from collections import defaultdict
d1 = defaultdict(int)      # defaults to 0 values for keys
unq = set(lst1)              # lst1 = [1, 2, 2, 3, 4, 1, 2, 7]
for each in unq:
d1[each] = lst1.count(each)
else:
print(d1)

more_itertools.locate查找满足条件的所有项的索引。

from more_itertools import locate




list(locate([0, 1, 1, 0, 1, 0, 0]))
# [1, 2, 4]


list(locate(['a', 'b', 'c', 'b'], lambda x: x == 'b'))
# [1, 3]

more_itertools是第三方库> pip install more_itertools

或者使用range (python 3):

l=[i for i in range(len(lst)) if lst[i]=='something...']

For (python 2):

l=[i for i in xrange(len(lst)) if lst[i]=='something...']

然后(两种情况):

print(l)

不出所料。

获取列表中一个或多个(相同的)项的所有出现情况和位置

使用enumerate(alist),您可以存储第一个元素(n),当元素x等于您所寻找的元素时,它是列表的索引。

>>> alist = ['foo', 'spam', 'egg', 'foo']
>>> foo_indexes = [n for n,x in enumerate(alist) if x=='foo']
>>> foo_indexes
[0, 3]
>>>

让我们把函数命名为findindex

这个函数以项目和列表作为参数,并返回项目在列表中的位置,就像我们前面看到的那样。

def indexlist(item2find, list_or_string):
"Returns all indexes of an item in a list or a string"
return [n for n,item in enumerate(list_or_string) if item==item2find]


print(indexlist("1", "010101010"))

输出


[1, 3, 5, 7]

简单的

for n, i in enumerate([1, 2, 3, 4, 1]):
if i == 1:
print(n)

输出:

0
4

在python2中使用filter()。

>>> q = ['Yeehaw', 'Yeehaw', 'Googol', 'B9', 'Googol', 'NSM', 'B9', 'NSM', 'Dont Ask', 'Googol']
>>> filter(lambda i: q[i]=="Googol", range(len(q)))
[2, 4, 9]

使用for-loop:

  • 使用enumerate列表理解的回答更python化,但不一定更快。然而,这个答案是针对那些可能不被允许使用内置函数的学生。
  • 创建一个空列表indices
  • 使用for i in range(len(x)):创建循环,本质上是遍历索引位置[0, 1, 2, 3, ..., len(x)-1]的列表
  • 在循环中,添加任意i,其中x[i]匹配value,到indices
    • # EYZ0 # EYZ1
def get_indices(x: list, value: int) -> list:
indices = list()
for i in range(len(x)):
if x[i] == value:
indices.append(i)
return indices


n = [1, 2, 3, -50, -60, 0, 6, 9, -60, -60]
print(get_indices(n, -60))


>>> [4, 8, 9]
  • 函数get_indices类型提示实现。在本例中,列表n是一组int,因此我们搜索value,也定义为int

使用while-loop.index:

  • 对于.index,使用try-except代替错误处理,因为如果value不在list中,则会出现ValueError
def get_indices(x: list, value: int) -> list:
indices = list()
i = 0
while True:
try:
# find an occurrence of value and update i to that index
i = x.index(value, i)
# add i to the list
indices.append(i)
# advance i by 1
i += 1
except ValueError as e:
break
return indices


print(get_indices(n, -60))
>>> [4, 8, 9]

下面是使用np.wherelist_comprehension之间的时间性能比较。似乎np.where的平均速度更快。

# np.where
start_times = []
end_times = []
for i in range(10000):
start = time.time()
start_times.append(start)
temp_list = np.array([1,2,3,3,5])
ixs = np.where(temp_list==3)[0].tolist()
end = time.time()
end_times.append(end)
print("Took on average {} seconds".format(
np.mean(end_times)-np.mean(start_times)))
Took on average 3.81469726562e-06 seconds
# list_comprehension
start_times = []
end_times = []
for i in range(10000):
start = time.time()
start_times.append(start)
temp_list = np.array([1,2,3,3,5])
ixs = [i for i in range(len(temp_list)) if temp_list[i]==3]
end = time.time()
end_times.append(end)
print("Took on average {} seconds".format(
np.mean(end_times)-np.mean(start_times)))
Took on average 4.05311584473e-06 seconds
  • 有一个回答使用np.where查找单个值的下标,如果包括将列表转换为数组的时间,这并不比列表理解更快
  • 导入numpy并将list转换为numpy.array的开销可能会使使用numpy在大多数情况下是一个效率较低的选项。仔细的时间分析是必要的。
    • 在需要在list上执行多个函数/操作的情况下,将list转换为array,然后使用numpy函数可能是更快的选择。
  • 这个解决方案使用np.wherenp.unique在列表中查找所有独特元素的索引。
    • 在数组上使用np.where(包括将列表转换为数组的时间)比在列表上使用求所有唯一元素的所有下标的列表理解略慢。
    • 这已经在一个2M元素列表上进行了测试,有4个唯一值,列表/数组的大小和唯一元素的数量会有影响。
  • 在数组上使用numpy的其他解决方案可以在获取numpy数组中重复元素的所有下标的列表中找到
  • [python 3.10.4, numpy 1.23.1][python 3.11.0, numpy 1.23.4]测试
import numpy as np
import random  # to create test list


# create sample list
random.seed(365)
l = [random.choice(['s1', 's2', 's3', 's4']) for _ in range(20)]


# convert the list to an array for use with these numpy methods
a = np.array(l)


# create a dict of each unique entry and the associated indices
idx = {v: np.where(a == v)[0].tolist() for v in np.unique(a)}


# print(idx)
{'s1': [7, 9, 10, 11, 17],
's2': [1, 3, 6, 8, 14, 18, 19],
's3': [0, 2, 13, 16],
's4': [4, 5, 12, 15]}

%timeit在2M元素列表中,有4个唯一的str元素

# create 2M element list
random.seed(365)
l = [random.choice(['s1', 's2', 's3', 's4']) for _ in range(2000000)]

功能

def test1():
# np.where: convert list to array and find indices of a single element
a = np.array(l)
return np.where(a == 's1')
    



def test2():
# list-comprehension: on list l and find indices of a single element
return [i for i, x in enumerate(l) if x == "s1"]




def test3():
# filter: on list l and find indices of a single element
return list(filter(lambda i: l[i]=="s1", range(len(l))))




def test4():
# use np.where and np.unique to find indices of all unique elements: convert list to array
a = np.array(l)
return {v: np.where(a == v)[0].tolist() for v in np.unique(a)}




def test5():
# list comprehension inside dict comprehension: on list l and find indices of all unique elements
return {req_word: [idx for idx, word in enumerate(l) if word == req_word] for req_word in set(l)}

函数调用

%timeit test1()
%timeit test2()
%timeit test3()
%timeit test4()
%timeit test5()

结果# EYZ0

214 ms ± 19.9 ms per loop (mean ± std. dev. of 7 runs, 1 loop each)
85.1 ms ± 1.48 ms per loop (mean ± std. dev. of 7 runs, 10 loops each)
146 ms ± 1.65 ms per loop (mean ± std. dev. of 7 runs, 10 loops each)
365 ms ± 11.4 ms per loop (mean ± std. dev. of 7 runs, 1 loop each)
360 ms ± 5.82 ms per loop (mean ± std. dev. of 7 runs, 1 loop each)

结果# EYZ0

209 ms ± 15.7 ms per loop (mean ± std. dev. of 7 runs, 1 loop each)
70.4 ms ± 1.86 ms per loop (mean ± std. dev. of 7 runs, 10 loops each)
132 ms ± 4.65 ms per loop (mean ± std. dev. of 7 runs, 10 loops each)
371 ms ± 20.1 ms per loop (mean ± std. dev. of 7 runs, 1 loop each)
314 ms ± 15.9 ms per loop (mean ± std. dev. of 7 runs, 1 loop each)

一个基于动态列表理解的解决方案,以防我们事先不知道哪个元素:

lst = ['to', 'be', 'or', 'not', 'to', 'be']
{req_word: [idx for idx, word in enumerate(lst) if word == req_word] for req_word in set(lst)}

结果:

{'be': [1, 5], 'or': [2], 'to': [0, 4], 'not': [3]}

您可以沿着相同的路线考虑所有其他方法,但是使用index(),您只能找到一个索引,尽管您可以自己设置出现次数。

创建一个生成器

生成器速度很快,占用的内存很小。它们可以让你灵活地使用结果。

def indices(iter, val):
"""Generator: Returns all indices of val in iter
Raises a ValueError if no val does not occur in iter
Passes on the AttributeError if iter does not have an index method (e.g. is a set)
"""
i = -1
NotFound = False
while not NotFound:
try:
i = iter.index(val, i+1)
except ValueError:
NotFound = True
else:
yield i
if i == -1:
raise ValueError("No occurrences of {v} in {i}".format(v = val, i = iter))

上面的代码可以用来创建索引列表:list(indices(input,value));使用它们作为字典键:dict(indices(input,value));求和:sum(indices(input,value));在for循环中for index_ in indices(input,value):;等……没有创建临时列表/元组或类似的。

在for循环中,当你调用下一个索引时,你将得到它,而不需要等待所有其他索引先计算出来。这意味着:如果出于某种原因跳出循环,就可以节省查找根本不需要的索引所需的时间。

它是如何工作的

  • 在输入iter上调用.index以查找的下一个出现项 李# EYZ0 < / >
  • 使用第二个参数.index从该点开始 后最后发现的事件
  • 收益率指数
  • 重复,直到index引发ValueError

选择版本

我尝试了四种不同的流量控制版本;两个EAFP(使用try - except)和两个TBYL(在while语句中使用逻辑测试):

  1. “whiletruebreak”:while True:…# EYZ1。令人惊讶的是,这通常比选项2慢一点,(IMV)可读性更差
  2. 使用bool变量err来标识何时引发ValueError。这通常是可读性更强比1最快的
  3. “remainingslice”:使用切片:while val in iter[i:]检查val是否在输入的剩余部分。不出所料,这不能很好地扩展
  4. “lastcurrence":首先检查最后出现的位置,继续查找while i < last

1、2和4之间的整体表现差异可以忽略不计,所以这取决于个人风格和偏好。鉴于.index使用ValueError让你知道它没有找到任何东西,而不是例如返回None, eafp方法似乎适合我。

下面是来自timeit的4个代码变体和结果(以毫秒为单位),用于不同长度的输入和稀疏匹配

@version("WhileTrueBreak", versions)
def indices2(iter, val):
i = -1
while True:
try:
i = iter.index(val, i+1)
except ValueError:
break
else:
yield i


@version("WhileErrFalse", versions)
def indices5(iter, val):
i = -1
err = False
while not err:
try:
i = iter.index(val, i+1)
except ValueError:
err = True
else:
yield i


@version("RemainingSlice", versions)
def indices1(iter, val):
i = 0
while val in iter[i:]:
i = iter.index(val, i)
yield i
i += 1


@version("LastOccurrence", versions)
def indices4(iter,val):
i = 0
last = len(iter) - tuple(reversed(iter)).index(val)
while i < last:
i = iter.index(val, i)
yield i
i += 1
Length: 100, Ocurrences: 4.0%
{'WhileTrueBreak': 0.0074799987487494946, 'WhileErrFalse': 0.006440002471208572, 'RemainingSlice': 0.01221001148223877, 'LastOccurrence': 0.00801000278443098}
Length: 1000, Ocurrences: 1.2%
{'WhileTrueBreak': 0.03101000329479575, 'WhileErrFalse': 0.0278000021353364, 'RemainingSlice': 0.08278000168502331, 'LastOccurrence': 0.03986000083386898}
Length: 10000, Ocurrences: 2.05%
{'WhileTrueBreak': 0.18062000162899494, 'WhileErrFalse': 0.1810499932616949, 'RemainingSlice': 2.9145700042136014, 'LastOccurrence': 0.2049500006251037}
Length: 100000, Ocurrences: 1.977%
{'WhileTrueBreak': 1.9361200043931603, 'WhileErrFalse': 1.7280600033700466, 'RemainingSlice': 254.4725100044161, 'LastOccurrence': 1.9101499929092824}
Length: 100000, Ocurrences: 9.873%
{'WhileTrueBreak': 2.832529996521771, 'WhileErrFalse': 2.9984100023284554, 'RemainingSlice': 1132.4922299943864, 'LastOccurrence': 2.6660699979402125}
Length: 100000, Ocurrences: 25.058%
{'WhileTrueBreak': 5.119729996658862, 'WhileErrFalse': 5.2082200068980455, 'RemainingSlice': 2443.0577100021765, 'LastOccurrence': 4.75954000139609}
Length: 100000, Ocurrences: 49.698%
{'WhileTrueBreak': 9.372120001353323, 'WhileErrFalse': 8.447749994229525, 'RemainingSlice': 5042.717969999649, 'LastOccurrence': 8.050809998530895}