Python列表中重复项的索引

82

有没有人知道如何在python列表中获取重复项的索引位置? 我尝试过这样做,但它只给出了该项在列表中第一次出现的索引。

List = ['A', 'B', 'A', 'C', 'E']

我希望它能给我:

index 0: A   
index 2: A

26
请注意,Python风格指南指出,变量的名称不应使用大写字母,也要避免使用内置类的名称,如list。 - Lauritz V. Thaulow
6
“List”与“list”是不同的。 - martineau
13
@martineau:我知道,但我想确认他没有仅通过将变量转换为小写来解决大写问题。 - Lauritz V. Thaulow
这是在询问如何获取序列中重复出现项的索引,答案在这里;以及如何查找序列中的重复项,答案在这里 - mkrieger1
23个回答

70
你想要传递 index 可选的第二个参数,用于指定 index 开始查找的位置。在找到每个匹配项后,将该参数重置为刚刚找到的匹配项之后的位置。
def list_duplicates_of(seq,item):
    start_at = -1
    locs = []
    while True:
        try:
            loc = seq.index(item,start_at+1)
        except ValueError:
            break
        else:
            locs.append(loc)
            start_at = loc
    return locs

source = "ABABDBAAEDSBQEWBAFLSAFB"
print(list_duplicates_of(source, 'B'))

输出:

[1, 3, 5, 11, 15, 22]

你可以使用defaultdict来一次性找到所有重复项,并保持每个项目的所有已知位置列表,返回那些被看到超过一次的项目。

from collections import defaultdict

def list_duplicates(seq):
    tally = defaultdict(list)
    for i,item in enumerate(seq):
        tally[item].append(i)
    return ((key,locs) for key,locs in tally.items() 
                            if len(locs)>1)

for dup in sorted(list_duplicates(source)):
    print(dup)

输出:

('A', [0, 2, 6, 7, 16, 20])
('B', [1, 3, 5, 11, 15, 22])
('D', [4, 9])
('E', [8, 13])
('F', [17, 21])
('S', [10, 19])

如果您想对同一源进行不同键的重复测试,可以使用functools.partial创建一个新的函数变量,使用“部分完成”的参数列表,即指定seq,但省略要搜索的项:

from functools import partial
dups_in_source = partial(list_duplicates_of, source)

for c in "ABDEFS":
    print(c, dups_in_source(c))

输出:

A [0, 2, 6, 7, 16, 20]
B [1, 3, 5, 11, 15, 22]
D [4, 9]
E [8, 13]
F [17, 21]
S [10, 19]

2
只是想告诉你,你的解决方案是所有建议中最快的。 - Ruslan Bes
2
很遗憾,这是一个相当冗长且不必要的复杂解决方案。 - Iceflower S

43
>>> def indices(lst, item):
...   return [i for i, x in enumerate(lst) if x == item]
... 
>>> indices(List, "A")
[0, 2]

要获取所有重复项,您可以使用以下方法,但它不是非常高效的。如果效率很重要,您应该考虑使用Ignacio的解决方案。

>>> dict((x, indices(List, x)) for x in set(List) if List.count(x) > 1)
{'A': [0, 2]}

如果使用listindex方法来解决这个问题,该方法需要一个可选的第二个参数来指示从哪里开始搜索,因此您可以简单地反复调用它,并将前一个索引加1作为参数。

>>> List.index("A")
0
>>> List.index("A", 1)
2

有人能解释一下这个怎么是可读的吗?i for i??? [i for i, x in enumerate(lst) if x == item] - uberrebu

17

我对所有在这里提出的解决方案进行了基准测试,并在此答案的结尾添加了另一种解决方案。

基准测试

首先是基准测试。我初始化一个范围为[1,n / 2]n个随机整数列表,然后对所有算法调用timeit

@Paul McGuire和@Ignacio Vazquez-Abrams的解决方案在100个整数列表上的工作速度大约比列表中其他解决方案快两倍:

Testing algorithm on the list of 100 items using 10000 loops
Algorithm: dupl_eat
Timing: 1.46247477189
####################
Algorithm: dupl_utdemir
Timing: 2.93324529055
####################
Algorithm: dupl_lthaulow
Timing: 3.89198786645
####################
Algorithm: dupl_pmcguire
Timing: 0.583058259784
####################
Algorithm: dupl_ivazques_abrams
Timing: 0.645062989076
####################
Algorithm: dupl_rbespal
Timing: 1.06523873786
####################
如果你将物品数量更改为1000,差异会变得更大(顺便说一句,如果有人能解释为什么,我会很高兴):
Testing algorithm on the list of 1000 items using 1000 loops
Algorithm: dupl_eat
Timing: 5.46171654555
####################
Algorithm: dupl_utdemir
Timing: 25.5582547323
####################
Algorithm: dupl_lthaulow
Timing: 39.284285326
####################
Algorithm: dupl_pmcguire
Timing: 0.56558489513
####################
Algorithm: dupl_ivazques_abrams
Timing: 0.615980005148
####################
Algorithm: dupl_rbespal
Timing: 1.21610942322
####################

在更大的列表上,@Paul McGuire的解决方案仍然是效率最高的,而我的算法开始出现问题。

Testing algorithm on the list of 1000000 items using 1 loops
Algorithm: dupl_pmcguire
Timing: 1.5019953958
####################
Algorithm: dupl_ivazques_abrams
Timing: 1.70856155898
####################
Algorithm: dupl_rbespal
Timing: 3.95820421595
####################

基准测试的完整代码在这里

另一种算法

这是我对同样问题的解决方案:

def dupl_rbespal(c):
    alreadyAdded = False
    dupl_c = dict()
    sorted_ind_c = sorted(range(len(c)), key=lambda x: c[x]) # sort incoming list but save the indexes of sorted items

    for i in xrange(len(c) - 1): # loop over indexes of sorted items
        if c[sorted_ind_c[i]] == c[sorted_ind_c[i+1]]: # if two consecutive indexes point to the same value, add it to the duplicates
            if not alreadyAdded:
                dupl_c[c[sorted_ind_c[i]]] = [sorted_ind_c[i], sorted_ind_c[i+1]]
                alreadyAdded = True
            else:
                dupl_c[c[sorted_ind_c[i]]].append( sorted_ind_c[i+1] )
        else:
            alreadyAdded = False
    return dupl_c

虽然它不是最好的,但它让我生成了一个稍微不同的结构,这是我问题所需的(我需要像链表一样的相同值的索引)


1
注意,基准测试使用了Paul McGuire的list_duplicates(seq)函数,而不是list_duplicates_of(seq,item)函数。 - nmz787

16
dups = collections.defaultdict(list)
for i, e in enumerate(L):
  dups[e].append(i)
for k, v in sorted(dups.iteritems()):
  if len(v) >= 2:
    print '%s: %r' % (k, v)

从那里推断出更多的信息。


11

我认为在很多烦恼之后,我找到了一个简单的解决方案:

if elem in string_list:
    counter = 0
    elem_pos = []
    for i in string_list:
        if i == elem:
            elem_pos.append(counter)
        counter = counter + 1
    print(elem_pos)

这将打印一个列表,给出了特定元素("elem")的索引。


确实,这是处理重复列表的可靠方法。恭喜和感谢 :)。 - ivanleoncz
我会添加注释,使它更易于初学者阅读。非常好的解决方案! - Toma
谢谢,这个解决方案非常简单。 - SreehariGaddam

8
使用集合模块中的新“Counter”类,基于lazyr的答案:
>>> import collections
>>> def duplicates(n): #n="123123123"
...     counter=collections.Counter(n) #{'1': 3, '3': 3, '2': 3}
...     dups=[i for i in counter if counter[i]!=1] #['1','3','2']
...     result={}
...     for item in dups:
...             result[item]=[i for i,j in enumerate(n) if j==item] 
...     return result
... 
>>> duplicates("123123123")
{'1': [0, 3, 6], '3': [2, 5, 8], '2': [1, 4, 7]}

5
from collections import Counter, defaultdict

def duplicates(lst):
    cnt= Counter(lst)
    return [key for key in cnt.keys() if cnt[key]> 1]

def duplicates_indices(lst):
    dup, ind= duplicates(lst), defaultdict(list)
    for i, v in enumerate(lst):
        if v in dup: ind[v].append(i)
    return ind

lst= ['a', 'b', 'a', 'c', 'b', 'a', 'e']
print duplicates(lst) # ['a', 'b']
print duplicates_indices(lst) # ..., {'a': [0, 2, 5], 'b': [1, 4]})

稍微更加正交(因此更有用)的实现方式如下:
from collections import Counter, defaultdict

def duplicates(lst):
    cnt= Counter(lst)
    return [key for key in cnt.keys() if cnt[key]> 1]

def indices(lst, items= None):
    items, ind= set(lst) if items is None else items, defaultdict(list)
    for i, v in enumerate(lst):
        if v in items: ind[v].append(i)
    return ind

lst= ['a', 'b', 'a', 'c', 'b', 'a', 'e']
print indices(lst, duplicates(lst)) # ..., {'a': [0, 2, 5], 'b': [1, 4]})

4
使用 pandas 1.2.2 和 numpy,可以在一行代码中实现:
 import numpy as np
 import pandas as pd
 
 idx = np.where(pd.DataFrame(List).duplicated(keep=False))

参数keep=False将会把每个重复元素标记为True,而np.where()会返回一个数组,其中包含数组中元素为True的索引位置。

3
def index(arr, num):
    for i, x in enumerate(arr):
        if x == num:
            print(x, i)

#index(List, 'A')


3

哇,大家的回答都好长啊。我只是使用了一个pandas dataframe掩码重复项函数(keep=False会将所有重复项标记为True,而不仅仅是第一个或最后一个):

import pandas as pd
import numpy as np
np.random.seed(42)  # make results reproducible

int_df = pd.DataFrame({'int_list': np.random.randint(1, 20, size=10)})
dupes = int_df['int_list'].duplicated(keep=False)
print(int_df['int_list'][dupes].index)

这应该返回Int64Index([0, 2, 3, 4, 6, 7, 9], dtype='int64')

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