如何查找列表中某个元素的所有出现位置
- 2024-11-22 08:47:00
- admin 原创
- 192
问题描述:
index()
将返回列表中某项的第一次出现。是否有一个巧妙的技巧可以返回列表中某个元素的所有索引?
解决方案 1:
您可以使用列表推导式enumerate
:
indices = [i for i, x in enumerate(my_list) if x == "whatever"]
迭代器为列表中的每个项目enumerate(my_list)
生成对。使用循环变量目标将这些对解包到索引和列表项中。我们筛选出所有符合我们标准的项,然后选择这些元素的索引。(index, item)
`i, xi
xx
i`
解决方案 2:
虽然这不是一个直接解决列表问题的方法,但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])
与其他一些解决方案相比,这对于具有大量元素的列表(数组)来说可以更快。
解决方案 3:
使用以下解决方案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 个整数列表的测试中),则列表理解更快。
解决方案 4:
怎么样:
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]
解决方案 5:
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
。
解决方案 6:
def occurrences(s, lst):
return (i for i,e in enumerate(lst) if e == s)
list(occurrences(1, [1,2,3,1])) # = [0, 3]
解决方案 7:
这个答案是
while-loop
经过测试的最快的实现。它比下面接受的答案快26%
test2()
。
有一个答案用于
np.where
查找单个值的索引,如果将列表转换为数组的时间包括在内,它并不比列表理解更快numpy
导入并将 a 转换list
为 a的开销numpy.array
可能使numpy
在大多数情况下使用效率较低的选项。需要进行仔细的时间分析。如果需要对 执行多个函数/操作
list
,则将 转换list
为array
,然后使用numpy
函数可能会成为更快的选择。
该解决方案使用
np.where
和来查找列表中
所有唯一元素np.unique
的索引。在数组上使用
np.where
(包括将列表转换为数组的时间)比在列表上使用列表理解稍慢,以查找所有唯一元素的所有索引。这已在具有 4 个唯一值的 2M 元素列表上进行了测试,并且列表/数组的大小和唯一元素的数量会产生影响。
在数组上使用的其他解决方案可以在获取 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
`str`在包含 4 个唯一元素的 2M 元素列表中
# 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)}
def get_indices1(x: list, value: int) -> list:
indices = list()
for i in range(len(x)):
if x[i] == value:
indices.append(i)
return indices
def get_indices2(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
函数调用
%timeit test1() # list of indices for specified value
%timeit test2() # list of indices for specified value
%timeit test3() # list of indices for specified value
%timeit test4() # dict of indices of all values
%timeit test5() # dict of indices of all values
%timeit get_indices1(l, 's1') # list of indices for specified value
%timeit get_indices2(l, 's1') # list of indices for specified value
结果python 3.12.0
209 ms ± 2.93 ms per loop (mean ± std. dev. of 7 runs, 1 loop each)
78.5 ms ± 733 µs per loop (mean ± std. dev. of 7 runs, 10 loops each)
125 ms ± 757 µs per loop (mean ± std. dev. of 7 runs, 10 loops each)
340 ms ± 8.16 ms per loop (mean ± std. dev. of 7 runs, 1 loop each)
319 ms ± 2.97 ms per loop (mean ± std. dev. of 7 runs, 1 loop each)
74.9 ms ± 1.99 ms per loop (mean ± std. dev. of 7 runs, 10 loops each)
58.2 ms ± 1.03 ms per loop (mean ± std. dev. of 7 runs, 10 loops each)
解决方案 8:
或者使用range
(python 3):
l=[i for i in range(len(lst)) if lst[i]=='something...']
对于(python 2):
l=[i for i in xrange(len(lst)) if lst[i]=='something...']
然后(两种情况):
print(l)
和预想的一样。
解决方案 9:
获取列表中一个或多个(相同)项目的所有出现位置
使用 enumerate(alist),当元素 x 等于您要查找的元素时,您可以存储作为列表索引的第一个元素 (n)。
>>> 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
解决方案 10:
针对所有情况还有一个解决方案(如果有重复请见谅):
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)
解决方案 11:
在 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]
解决方案 12:
如果需要搜索某些索引之间的所有元素的位置,可以这样声明:
[i for i,x in enumerate([1,2,3,2]) if x==2 & 2<= i <=3] # -> [3]
解决方案 13:
您可以创建一个默认字典
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)
解决方案 14:
如果我们事先不知道哪个元素,则基于动态列表理解的解决方案:
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()
尽管您可以自己设置出现次数,但您只能找到一个索引。
解决方案 15:
使用for-loop
:
enumerate
带有列表推导的答案更符合 Python 风格,但速度不一定更快。不过,这个答案针对的是那些可能不允许使用某些内置函数的学生。创建一个空列表,
indices
使用 创建循环
for i in range(len(x)):
,本质上是遍历索引位置列表[0, 1, 2, 3, ..., len(x)-1]
在循环中,添加任何
i
与x[i]
匹配value
的indices
x[i]
通过索引访问列表
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
s,因此我们搜索value
,也定义为int
。
使用while-loop
和.index
:
这是该答案中测试过的最快的实现。
它比接受的答案快 26% 。
使用进行错误处理,因为如果不在 中,
.index
就会发生。try-except
`ValueErrorvalue
list`
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]
解决方案 16:
如果您使用的是 Python 2,您可以使用以下命令实现相同的功能:
def f(my_list, value):
return filter(lambda x: my_list[x] == value, range(len(my_list)))
my_list
您要获取其索引的列表在哪里,以及value
搜索的值在哪里。用法:
f(some_list, some_element)
解决方案 17:
创建一个生成器
生成器速度快,占用的内存很小。它们让您可以灵活地使用结果。
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 循环中,当您调用时,您将获得下一个索引,而无需等待先计算所有其他索引。这意味着:如果您出于某种原因退出循环,您将节省查找您不需要的索引所需的时间。
工作原理
调用
.index
输入iter
来查找下一个出现的val
使用第二个参数从最后一次发现的事件之后的
.index
位置开始收益率指数
重复直到
index
提高ValueError
其他版本
我尝试了四个不同版本的流量控制;两个 EAFP (使用try - except
) 和两个 TBYL (在语句中使用逻辑测试while
):
“WhileTrueBreak”:
while True:
...except ValueError: break
。令人惊讶的是,这通常比选项 2 慢一点,而且(IMV)可读性较差“WhileErrFalse”:使用布尔变量
err
来标识何时ValueError
引发。这通常是最快且比 1更易读的“RemainingSlice”:使用切片检查 val 是否位于输入的剩余部分:
while val in iter[i:]
。不出所料,这不能很好地扩展“LastOccurrence”:首先检查最后一次出现的位置,然后继续
while i < last
1、2 和 4 之间的整体性能差异可以忽略不计,因此这取决于个人风格和偏好。考虑到.index
使用ValueError
来让您知道它没有找到任何东西,而不是例如返回None
,EAFP 方法对我来说似乎很合适。
以下是 4 种代码变体及其结果timeit
(以毫秒为单位),针对不同的输入长度和匹配稀疏度
@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}
解决方案 18:
这是一个非常古老的问题,但我没有在答案中看到这个问题,尽管它被暗示了。
使用可接受的答案enumerate
但带有in
选项,允许对列表进行字符串搜索,并且如果您选择的话,还可以获得部分命中和不区分大小写的额外好处。
提供索引位置或找到的字符串本身。
给出一个 ISO 国家名称列表,下面给出了其用法的概念
>>> query = "Tre".casefold()
>>>
>>> print([i for i, x in enumerate(country_list) if query in x.casefold()])
[280, 303, 352, 489]
>>>
>>> print([x for i, x in enumerate(country_list) if query in x.casefold()])
['Eritrea', 'Spain Extremadura', 'France Centre-Val de Loire', 'Italy Trentino-South Tyrol']
>>>
>>> country_list[280]
'Eritrea'
>>>
>>> country_list[489]
'Italy Trentino-South Tyrol'
>>> query = "out".casefold()
>>> print([i for i, x in enumerate(country_list) if query in x.casefold()])
[48, 54, 253, 421, 489, 651, 1075, 1099, 1147, 1240, 1242, 1248, 1306]
>>> print([x for i, x in enumerate(country_list) if query in x.casefold()])
['Australia New South Wales', 'Australia South Australia', 'Djibouti', 'South Georgia and Sandwich Islands', 'Italy Trentino-South Tyrol', 'South Korea', 'South Sudan', 'French Southern Territories', 'United States Outlying Islands', 'United States of America South Carolina', 'United States of America South Dakota', 'United States of America Outlying Islands', 'South Africa']
这是个简单的不区分大小写的部分字符串查找器。
我怀疑它在大规模搜索中效率极低,但对于小规模搜索来说,它很简单、很干净。
解决方案 19:
如果您需要对列表执行多次查找,则编写一个包含每个不同元素的所有位置的字典可能更有效。此时,任何单次查找都是一个常量时间操作。
为此,我们可以:
将索引映射到每个元素上。
enumerate
可以很容易地提供这一点。根据元素排序,忽略索引。首先进行排序很重要,这样我们才能...
用于
itertools.groupby
将那些匹配的元素组合在一起。遍历该
groupby
对象来构建字典。
from itertools import groupby
from operator import itemgetter
def build_table(lst):
snd = itemgetter(1)
e = enumerate(lst)
s = sorted(e, key=snd)
g = groupby(s, key=snd)
return {
k: [x[0] for x in v]
for k, v in g
}
解决方案 20:
np.where
以下是使用与 的时间性能比较list_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
解决方案 21:
我发现最简单、最快的方法是重新创建一个可以循环的字典,即
l_full_list_w_duplicates=[...]
l_full_list_wo_duplicates=list(set(l_full_list_w_duplicates))
dict_location={}
for i in l_full_list_wo_duplicates:
dict_location[i]=[]
i_counter=0
for iin l_full_list_w_duplicates:
dict_location[i]+=[i_counter]
i_counter+=1
它以线性时间运行,并且可扩展性强