Python:查找两个列表中的第一个不匹配项

5

如果给定两个相同长度的列表,如何高效地找到这些列表对应元素第一次不相等的位置?我需要索引或两个不相等的元素。

我想知道是否存在简洁的“Python风格”的解决方案,而不需要明显地对列表进行迭代。


1
那么,你排除了enumerate(zip(a, b))的可能性吗? - Jon Clements
我对“简洁的Python解决方案”非常感兴趣。有什么反对使用无聊的排序、去重、比较方法或者枚举的理由吗? - Najzero
操作“位置”或“索引”(大多数情况下)不符合Pythonic。您能解释一下如何使用该索引吗? - georg
@thg435:嗯,事实上我想要这两个不同的元素。我会进行编辑。 - Grigor Gevorgyan
1
如果列表相等,结果会是什么?如果一个列表比另一个短呢? - georg
显示剩余2条评论
5个回答

5

您无法避免对列表的迭代,但可以使用推导式来实现,并获得一种优美的解决方案:

next( (idx, x, y) for idx, (x, y) in enumerate(zip(list1, list2)) if x!=y )

如果您不喜欢一行代码过长的情况,可以按照以下方式进行拆分:
coupled_idx = enumerate(zip(list1, list2))
res = next( idx for idx, (x, y) in coupled_idx if x!=y )

编辑:

另外,如果您需要检查两个列表完全相等的情况,可以向下一个函数添加第二个参数,告诉它在未找到索引时返回什么。最常见的选项是返回None:

coupled_idx = enumerate(zip(list1, list2))
res = next( (idx for idx, (x, y) in coupled_idx if x!=y), None )

请注意,您需要将生成器表达式括在括号中,因为在此调用中它不是函数的唯一参数。
为了增加一些乐趣,您还可以通过链接表达式来询问第n个不同的夫妇。例如,这会给您所有的夫妇,直到第五个(如果夫妇缺失,则填充None)。
coupled_idx = enumerate(zip(list1, list2))
coupler = (idx for idx, (x, y) in coupled_idx if x!=y)
res = [ next(coupler, None) for _ in range(5) ]

编辑2:

这种解决方案实际上通过zip函数创建了两个列表的副本。如果您需要避免这种情况,可以改用itertools模块中的函数izip

至于有趣的部分,您可以使用同一模块中的islice函数仅选择某些解决方案。


那是一个快速的答案,不幸的是,如果两个列表相等,它会出错。 - georg
res = [ (idx, x, y) for idx, (x, y) in enumerate(zip(list1, list2)) if x!=y ] 会生成一个包含 (idx, x, y) 三元素的列表,其中只有当两个列表间存在不同之处时才会有结果被加入此列表,若两个列表完全相同则返回一个空列表。 - StuGrey

3

一种解决第一个问题的功能性方法:返回第一个不匹配项的索引

>>> from operator import ne
>>> from itertools import compress, count
>>> a = [1, 2, 4, 3]
>>> b = [1, 2, 3, 4]
>>> next(compress(count(), map(ne, a, b)))
2

针对第二个问题,采用功能性方法,返回第一个不匹配的一对而非索引

>>> from operator import ne
>>> from itertools import compress
>>> a = [1, 2, 4, 3]
>>> b = [1, 2, 3, 4]
>>> next(compress(zip(a, b), map(ne, a, b)))
(4, 3)

https://docs.python.org/zh-cn/3/library/itertools.html#itertools.compress

生成一个迭代器,从数据中过滤元素,仅返回那些对应选择器的元素计算结果为 True 的元素。在数据或选择器迭代器已耗尽时停止。大致等效于:

def compress(data, selectors):
    # compress('ABCDEF', [1,0,1,0,1,1]) --> A C E F
    return (d for d, s in zip(data, selectors) if s)

自版本3.1起新增功能。


+1. 这是问题的一个有趣变体。它对于相等列表有点脆弱,因为它会引发异常。 - EnricoGiampieri
@EnricoGiampieri OP没有说明如何处理没有匹配项的情况。我认为异常实际上非常清楚,所以可以直接检查。实际问题是对于大型列表来说这将是低效的。 - jamylak
对于Python3,您需要使用list(map(eq,a,b)).index(False)。 - Fabio Dalla Libera
@FabioDallaLibera 我更新了我的答案,采用了适用于Python 3的新方法。 - jamylak

0
In [1]: l1=[1,2,3]

In [2]: l2=[1,4,5]

In [4]: next(i for i, (el1, el2) in enumerate(zip(l1, l2)) if el1 != el2)
Out[4]: 1

这里,1l1l2 第一个不同的索引。


0

试试这个:

next(i for i, (el1,el2) in enumerate(zip(li1,li2)) if el1 != el2)

或者它的等价函数:

def first_diff(li1, li2):
    for i, (el1,el2) in enumerate(zip(li1,li2)):
        if el1 != el2:
            return i
    return False

一个例子

>>> li1 = range(32)
>>> li2 = range(32)
>>> li2[10] = 2
>>> next(i for i, (el1,el2) in enumerate(zip(li1,li2)) if el1 != el2)
10
>>> first_diff(li1, li2)
10

我认为这个问题是“做事情应该只有一种方法”的原则的绝佳例证。到目前为止,每个人都用不同的变量名称回复了相同的答案 :) - EnricoGiampieri

0

同时,在下一个命令中允许有额外的默认参数。由于过滤器(filter)和zip都是生成器,以下构造仅涉及两个列表的最少元素,以确定这两个列表在相同索引处被定义但该索引处的值不同,或在不存在这样的值时返回默认值。

只要可以找到差异,就会返回不同的值:

a=[1,2,3,4,5,6]

b=[1,2,7,4,'Hugo']

next(filter(lambda x: x[0]!=x[1], zip(a,b)),"Value that you choose to represent failure")

Out[91]: (3, 7)

当两个列表相等时,返回默认值:
b[2]=3; b[4]=5; b.append(6)

next(filter(lambda x: x[0]!=x[1], zip(a,b)),"Value that you choose to represent failure")

Out[93]: 'Value that you choose to represent failure'

当任何其他问题导致搜索失败时,将返回默认值:

a=[]

next(filter(lambda x: x[0]!=x[1], zip(a,b)),"Value that you choose to represent failure")

Out[95]: 'Value that you choose to represent failure'

当然,您可以将生成器分配给变量,并重复使用next来查找第二个或其他出现次数。
a=[1,2]

b=['fred',1,2.2]

f=filter(lambda x: x[0]!=x[1], zip(a,b))

next(f,'error')

Out[110]: (1, 'fred')

next(f,'error')

Out[111]: (2, 1)

next(f,'error')

Out[112]: 'error'

您可以将 None 作为默认值,以便在失败时静默处理或用于测试

next(f,None)

在列表末尾之后的后续调用不会引发异常或错误。

next(f,'No more')

Out[114]: 'No more'

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