Python 如何高效地找到两个列表中匹配元素的索引

Python 如何高效地找到两个列表中匹配元素的索引,python,algorithm,matching,Python,Algorithm,Matching,我正在研究两个大型数据集,我的问题如下 假设我有两个列表: list1=[A,B,C,D] list2=[B,D,A,G] 除了O(n2)搜索之外,如何使用Python高效地查找匹配索引?结果应该如下所示: 匹配索引(列表1,列表2)->[(0,2)、(1,0)、(3,1)]无重复项 如果对象是可散列的,并且列表没有重复项,则可以创建第一个列表的反向索引,然后遍历第二个列表。这只遍历每个列表一次,因此是O(n) 复制品 您可以扩展上一个解决方案以考虑重复项。您可以使用集跟踪多个索引 def fi

我正在研究两个大型数据集,我的问题如下

假设我有两个列表:

list1=[A,B,C,D]

list2=[B,D,A,G]

除了O(n2)搜索之外,如何使用Python高效地查找匹配索引?结果应该如下所示:

匹配索引(列表1,列表2)->[(0,2)、(1,0)、(3,1)]

无重复项 如果对象是可散列的,并且列表没有重复项,则可以创建第一个列表的反向索引,然后遍历第二个列表。这只遍历每个列表一次,因此是
O(n)

复制品 您可以扩展上一个解决方案以考虑重复项。您可以使用
跟踪多个索引

def find_matching_index(list1, list2):

    # Create an inverse index which keys are now sets
    inverse_index = {}

    for index, element in enumerate(list1):

        if element not in inverse_index:
            inverse_index[element] = {index}

        else:
            inverse_index[element].add(index)

    # Traverse the second list    
    matching_index = []

    for index, element in enumerate(list2):

        # We have to create one pair by element in the set of the inverse index
        if element in inverse_index:
            matching_index.extend([(x, index) for x in inverse_index[element]])

    return matching_index

find_matching_index([1, 1, 2], [2, 2, 1]) # [(2, 0), (2, 1), (0, 2), (1, 2)]
不幸的是,这不再是O(n)。考虑输入“代码> > [ 1, 1 ] < /代码>和<代码> [ 1, 1 ] < /代码>的情况,输出为<代码> [(0, 0),(0, 1),(1, 0),(1, 1)] < /代码>。因此,根据输出的大小,最坏的情况不可能比
O(n^2)
更好

尽管如此,如果没有重复项,此解决方案仍然是
O(n)

非散列对象 现在出现了这样的情况:对象不是可散列的,而是可比较的。这里的想法是以保留每个元素的原始索引的方式对列表进行排序。然后我们可以对相等的元素序列进行分组,以获得匹配索引

由于我们在下面的代码中大量使用了
groupby
product
,因此我让
find\u matching\u index
返回一个生成器,以提高长列表的内存效率

from itertools import groupby, product

def find_matching_index(list1, list2):
    sorted_list1 = sorted((element, index) for index, element in enumerate(list1))
    sorted_list2 = sorted((element, index) for index, element in enumerate(list2))

    list1_groups = groupby(sorted_list1, key=lambda pair: pair[0])
    list2_groups = groupby(sorted_list2, key=lambda pair: pair[0])

    for element1, group1 in list1_groups:
        try:
            element2, group2 = next(list2_groups)
            while element1 > element2:
                (element2, _), group2 = next(list2_groups)

        except StopIteration:
            break

        if element2 > element1:
            continue

        indices_product = product((i for _, i in group1), (i for _, i in group2), repeat=1)

        yield from indices_product

        # In version prior to 3.3, the above line must be
        # for x in indices_product:
        #     yield x

list1 = [[], [1, 2], []]
list2 = [[1, 2], []]

list(find_matching_index(list1, list2)) # [(0, 1), (2, 1), (1, 0)]

事实证明,时间复杂性不会受到太大影响。排序当然需要
O(n log(n))
,但是
groupby
提供的生成器只需遍历列表两次就可以恢复所有元素。结论是,我们的复杂性主要受
产品
输出的大小限制。因此,使用
dict
可以减少查找时间和
集合。defaultdict
专门化可以帮助簿记。目标是一个
dict
,它的值是您要查找的索引对。重复的值将覆盖列表中较早的值

import collections

# make a test list
list1 = list('ABCDEFGHIJKLMNOP')
list2 = list1[len(list1)//2:] + list1[:len(list1)//2]

# Map list items to positions as in: [list1_index, list2_index]
# by creating a defaultdict that fills in items not in list1,
# then adding list1 items and updating with with list2 items. 
list_indexer = collections.defaultdict(lambda: [None, None],
 ((item, [i, None]) for i, item in enumerate(list1)))
for i, val in enumerate(list2):
    list_indexer[val][1] = i

print(list(list_indexer.values()))

如果出于验证任何解决方案之外的其他原因,该问题的一个强力答案如下:

[(xi, xp) for (xi, x) in enumerate(list1) for (xp, y) in enumerate(list2) if x==y]
如何优化这一点在很大程度上取决于数据量和内存容量,因此了解这些列表的大小可能会有所帮助。我想我下面讨论的方法至少适用于具有数百万个值的列表

因为字典访问是O(1),所以尝试将第二个列表中的元素映射到它们的位置似乎是值得的。假设相同的元素可以重复,那么一个
collections.defaultdict
将允许我们轻松地构造必要的dict

l2_pos = defaultdict(list)
for (p, k) in enumerate(list2):
    l2_pos[k].append(p)
表达式
l2_pos[k]
现在是
list2
中出现元素
k
的位置列表。只需将每个键与列表1中相应键的位置配对即可。列表形式的结果是

[(p1, p2) for (p1, k) in enumerate(list1) for p2 in l2_pos[k]]
但是,如果这些结构很大,那么使用生成器表达式可能会更好。要将名称绑定到上面列表中的表达式,请编写

values = ((p1, p2) for (p1, k) in enumerate(list1) for p2 in l2_pos[k])
如果您随后迭代
,则可以避免创建包含所有值的列表的开销,从而减少Python内存管理和垃圾收集的负载,而就解决问题而言,这几乎是所有开销

当您开始处理大数据量时,了解生成器可能意味着是否有足够的内存来解决问题。在许多情况下,它们比列表理解有明显的优势


编辑:这种技术可以通过使用集合而不是列表来保持位置来进一步加速,除非顺序的改变是有害的。这个变化留给读者作为练习。

如果你的对象不是可哈希的,但仍然是可排序的,你可能想考虑使用<代码>排序<<代码>来匹配两个列表

假设两个列表中的所有元素都匹配 您可以对列表索引进行排序并将结果配对

indexes1 = sorted(range(len(list1)), key=lambda x: list1[x])
indexes2 = sorted(range(len(list2)), key=lambda x: list2[x])
matches = zip(indexes1, indexes2)
如果不是所有元素都匹配,但每个列表中没有重复项 您可以同时对两者进行排序,并在排序时保留索引。然后,如果您捕捉到任何连续的重复项,您就知道它们来自不同的列表

biglist = list(enumerate(list1)) + list(enumerate(list2))
biglist.sort(key=lambda x: x[1])
matches = [(biglist[i][0], biglist[i + 1][0]) for i in range(len(biglist) - 1) if biglist[i][1] == biglist[i + 1][1]]

下面是一个使用
defaultdict
的简单方法

给定的

import collections as ct


lst1 = list("ABCD")
lst2 = list("BDAG")
lst3 = list("EAB")
str1 = "ABCD"
代码

def find_matching_indices(*iterables, pred=None):
    """Return a list of matched indices across `m` iterables."""
    if pred is None:
        pred = lambda x: x[0]

    # Dict insertion
    dd = ct.defaultdict(list)
    for lst in iterables:                                          # O(m)
        for i, x in enumerate(lst):                                # O(n)
            dd[x].append(i)                                        # O(1)

    # Filter + sort
    vals = (x for x in dd.values() if len(x) > 1)                  # O(n)
    return sorted(vals, key=pred)                                  # O(n log n)
演示

在两个列表中查找匹配项(每个操作):

按不同的结果索引排序:

find_matching_indices(lst1, lst2, pred=lambda x: x[1])
# [[1, 0], [3, 1], [0, 2]]
匹配两个以上ITerable(可选可变长度)中的项目:


详细信息

词典插入

每个项目都会附加到defaultdict的列表中。结果如下所示,稍后将对其进行过滤:

defaultdict(list, {'A': [0, 2], 'B': [1, 0], 'C': [2], 'D': [3, 1], 'G': [3]})
乍一看,从双
for
循环中,我们可能会说时间复杂度是O(n²)。但是,外部循环中的容器列表的长度为
m
。内部循环处理长度为
n
的每个容器的元素。我不确定最终的复杂度是什么,但基于这个,我怀疑它是O(n*m)或至少低于O(n²)

过滤

过滤掉不匹配项(长度为1的列表),并对结果进行排序(主要针对Python<3.6中的无序dict)

使用算法via
sorted
按某个索引对dict值(列表)进行排序,最坏的情况是O(n logn)。由于在Python 3.6+中保留了dict键插入,因此预先排序的项降低了复杂性O(n)find_matching_indices(lst1, lst2, pred=lambda x: x[1]) # [[1, 0], [3, 1], [0, 2]]
find_matching_indices(lst1, lst2, lst3, str1)
# [[0, 2, 1, 0], [1, 0, 2, 1], [2, 2], [3, 1, 3]]
defaultdict(list, {'A': [0, 2], 'B': [1, 0], 'C': [2], 'D': [3, 1], 'G': [3]})