【问题标题】:Searching values of a list in another List using Python使用 Python 在另一个列表中搜索列表的值
【发布时间】:2009-11-08 05:20:21
【问题描述】:

我正在尝试查找列表的子列表。意思是如果 list1 说 [1,5] 在 list2 中说 [1,4,3,5,6],那么它应该返回 True。我到目前为止是这样的:

for nums in l1:
    if nums in l2:
        return True
    else:
        return False

这将是真的,但我试图仅当 list1 按相应顺序在 list2 中时才返回 True。所以如果 list2 是 [5,2,3,4,1],它应该返回 False。我正在考虑使用

【问题讨论】:

  • 只有当list2 的值是唯一的时,您比较索引值的想法才有效。例如,如果 list2[5,2,3,4,1,5]

标签: python list


【解决方案1】:
try:
  last_found = -1
  for num in L1:
     last_found = L2.index(num, last_found + 1)
  return True
except ValueError:
  return False

列表L2的index方法返回第一个参数(num)在列表中的位置;像这里一样,使用第二个参数调用,它开始在该位置查找列表。如果index 没有找到它要查找的内容,则会引发ValueError 异常。

因此,此代码使用此方法在L2 中按顺序查找num 中的每个项目num。第一次需要从位置0开始查找;接下来的每一次,它需要从它找到上一个项目的最后一个位置之后的位置开始查找,即last_found + 1(所以在开始时我们必须设置last_found = -1第一次从位置0开始查找)。

如果以这种方式找到 L1 中的每个项目(即在 L2 中找到上一个项目的位置之后),则两个列表满足给定条件,代码返回 True。如果未找到 L1 的任何项目,代码将捕获生成的 ValueError 异常并仅返回 False

另一种方法是在两个列表上使用迭代器,这可以通过iter 内置函数形成。您可以通过调用内置的 next 来“推进”迭代器;如果没有“下一项”,这将引发StopIteration,即迭代器已用尽。如果适用,您还可以在迭代器上使用for 以获得更平滑的界面。使用 iter/next 思想的底层方法:

i1 = iter(L1)
i2 = iter(L2)
while True:
  try:
    lookfor = next(i1)
  except StopIteration:
    # no more items to look for == all good!
    return True
  while True:
    try:
      maybe = next(i2)
    except StopIteration:
      # item lookfor never matched == nope!
      return False
    if maybe == lookfor:
      break

或者,更高级一点:

i1 = iter(L1)
i2 = iter(L2)
for lookfor in i1:
  for maybe in i2:
    if maybe == lookfor:
      break
  else:
    # item lookfor never matched == nope!
    return False
# no more items to look for == all good!
return True  

事实上,iter 的唯一关键用途是获取 i2 —— 将内循环设为 for maybe in i2 保证内循环不会每次都从头开始查找,而是,它会继续寻找上次停止的地方。 for lookfor in L1: 也可以使用外部循环,因为它没有“重启”问题。

这里的关键是循环的else: 子句,当且仅当循环没有被break 中断而是自然退出时才会触发。

进一步研究这个想法,我们再次想起了in 运算符,它也可以通过使用迭代器从上次中断的地方继续。大简化:

i2 = iter(L2)
for lookfor in L1:
  if lookfor not in i2:
    return False
# no more items to look for == all good!
return True  

但现在我们认识到这正是短路 anyall 内置“短路累加器”函数所抽象的模式,所以...:

i2 = iter(L2)
return all(lookfor in i2 for lookfor in L1)

我相信这和你能得到的一样简单。这里剩下的唯一非基本位是:您需要明确地使用 iter(L2) 一次,以确保 in 运算符(本质上是一个内部循环)不会从头开始重新开始搜索,而是继续每个上次停止的时间。

【讨论】:

  • 可以通过单行 last_found = L2.index(num, last_found + 1) 并将 try/except 移出循环来稍微改进
  • ...实际上我想到了一个基于迭代器的替代解决方案并将其推到我认为它绝对看起来最佳的位置,请查看我为该开发编辑的答案。
  • 我已将最终解决方案放在答案的顶部。
  • 我不知道你可以做elem in iterator,它会遍历迭代器。有点不一致,因为len() 拒绝这样做。
  • len(y) 被定义为“使用”它的迭代器参数y(如果这样做,它必须完全这样做); if x in y(如for x in y定义为消耗它需要的y的任何部分——事实上它可以部分完成,因此有用 是理解为什么这部分规范应该不同的关键,​​而且是。
【解决方案2】:

这个问题看起来有点像家庭作业,因此我想花时间讨论一下问题中显示的 sn-p 可能出了什么问题。

虽然您使用的是复数形式的单词,但对于 nums 变量,您需要了解 Python 将使用此变量一次存储 l1 中的一项,并遍历此“for”中的代码块块”,每个不同的项目一次。

因此,您当前 sn-p 的结果将在第一次迭代时退出,True 或 False 取决于列表中的第一个项目是否碰巧匹配。

编辑:是的,A1,正如您所说:逻辑在第一次迭代后以 True 退出。这是因为在 l2 中找到 nums 时“返回”。
如果您在“找到”的情况下什么都不做,则循环逻辑将继续完成块中的任何逻辑(此处没有),然后它将开始下一次迭代。因此,它只会以“False”返回值退出,在没有找到 l1 中的项目 l2 的情况下(实际上是在第一个这样的未找到项目之后)。因此你的逻辑几乎是正确的(如果它在“找到的情况”中什么都不做),缺少的一件事是返回“True”,系统地在for 循环(因为如果它没有在循环中以 False 值退出,那么 l2 的所有项目都在 l1 中......)。

有两种方法可以修改代码,因此它对“找到的案例”没有任何作用。
- 通过使用 pass,这是一种方便的方式来指示 Python 什么都不做;
“通过”通常用于“某事”,即在语法上需要某些操作但我们不想做任何事情,但它也可以在调试等时使用。
- 通过将测试改写为“不在”

if nums not in l2:
   return False
#no else:, i.e. do nothing at all if found

现在...进入更多细节。
您的程序可能存在缺陷(带有建议的更改),即它会将 l1 视为 l2 的子列表,即使 l1 有说 2 个值为 5 的项目,而 l2 只有一个这样的值。我不确定这种考虑是否是问题的一部分(可能的理解是两个列表都是“集合”,没有可能的重复项)。但是,如果允许重复,您将不得不使逻辑稍微复杂化(一种可能的方法是最初制作 l2 的副本,并且每次在 l2 副本中找到“nums”时,删除此项。

另一个考虑是,如果一个列表的项目与另一个列表中的项目的顺序相同,则可能只能说它是一个子列表...同样,这完全取决于问题的定义方式...顺便说一句,提出的一些解决方案(例如 Alex Martelli 的解决方案)是以这种方式编写的,因为它们解决问题的方式是列表中项目的顺序很重要。

【讨论】:

  • 是的,这是给家庭作业的,你是对的。给我答案并没有真正帮助我,因为我刚开始学习 python,所以我真的不知道给出的代码是如何工作的。至于您对我的代码的评论 mjv,您是说它只会在 list1 中查看 1,返回 true,然后跳过查看 5?我应该使用 range(len(list1)) 之类的东西吗?
【解决方案3】:

我认为这个解决方案是最快的,因为它只迭代一次,尽管在较长的列表中,如果找到匹配项,则在完成迭代之前退出。 (编辑:但是,它不像 Alex 的最新解决方案那样简洁或快速)

def ck(l1,l2):
    i,j = 0,len(l1)
    for e in l2:
        if e == l1[i]:
            i += 1
        if i == j:
            return True
    return False

Anurag Uniyal 提出了改进建议(见评论),并反映在下面的摊牌中。

以下是一系列列表大小比率的一些速度结果(列表 l1 是一个 10 元素列表,其中包含 1-10 的随机值。列表 l2 的长度范围为 10-1000(还包含随机值从 1 到 10)。

比较运行时间并绘制结果的代码:

import random
import os
import pylab
import timeit

def paul(l1,l2):
    i = 0
    j = len(l1)
    try:
        for e in l2:
            if e == l1[i]:
                i += 1
    except IndexError: # thanks Anurag
        return True
    return False

def jed(list1, list2):
    try:
        for num in list1:
            list2 = list2[list2.index(num):]
    except: return False
    else: return True

def alex(L1,L2):  # wow!
    i2 = iter(L2)
    return all(lookfor in i2 for lookfor in L1)

from itertools import dropwhile
from operator import ne
from functools import partial

def thc4k_andrea(l1, l2):
    it = iter(l2)
    try:
        for e in l1:
            dropwhile(partial(ne, e), it).next()
        return True
    except StopIteration:
        return False


ct = 100
ss = range(10,1000,100)
nms = 'paul alex jed thc4k_andrea'.split()
ls = dict.fromkeys(nms)
for nm in nms:
    ls[nm] = []

setup = 'import test_sublist as x'
for s in ss:
    l1 = [random.randint(1,10) for i in range(10)]
    l2 = [random.randint(1,10) for i in range(s)]
    for nm in nms:
        stmt = 'x.'+nm+'(%s,%s)'%(str(l1),str(l2))
        t = timeit.Timer(setup=setup, stmt=stmt).timeit(ct)
        ls[nm].append( t )

pylab.clf()
for nm in nms:
    print len(ss), len(ls[nm])
    pylab.plot(ss,ls[nm],label=nm)
    pylab.legend(loc=0)

    pylab.xlabel('length of l2')
    pylab.ylabel('time')

pylab.savefig('cmp_lsts.png')
os.startfile('cmp_lsts.png')

结果:

【讨论】:

  • i,j = 0,len(l1) 这条线是否意味着 i=0 和 j=len(l1)?
  • 亚历克斯 = 1.72043895721,你 = 1.70730209351。没有足够的差异来更好地呈现它,因为它的可读性要差很多。
  • 对不起,杰德。我是个速度狂另外,如果在 l2 早期发现与 l1 的匹配,则迭代 l2 会以显着优势获胜。
  • @bpowah,如果您捕获 IndexError 异常而不是 i==j 检查,您可能会获得更多收益
  • "简单胜于复杂,干得好 alex!还有很好的测试代码 bpowah|
【解决方案4】:

这应该很容易理解并很好地避免极端情况,因为您不需要使用索引:

def compare(l1, l2):
    it = iter(l2)
    for e in l1:
        try:
            while it.next() != e: pass
        except StopIteration: return False
    return True

它尝试将 l1 的每个 e 元素与 l2 中的下一个元素进行比较。
如果没有下一个元素(StopIteration)它返回 false(它访问了整个 l2 并且没有找到当前的 e)否则它找到它,所以它返回 true .

为了更快的执行,将try块放在for之外可能会有所帮助:

def compare(l1, l2):
    it = iter(l2)
    try: 
        for e in l1:
            while it.next() != e: pass
    except StopIteration: return False
    return True

【讨论】:

  • +1 我也想写同样的东西,你的while it.next() != e: pass 很不错。
【解决方案5】:

我很难看到这样的问题并且不希望 Python 的列表处理更像 Haskell 的。这似乎比我在 Python 中想出的任何解决方案都更直接:

contains_inorder :: Eq a => [a] -> [a] -> Bool
contains_inorder [] _ = True
contains_inorder _ [] = False
contains_inorder (x:xs) (y:ys) | x == y    = contains_inorder xs ys
                               | otherwise = contains_inorder (x:xs) ys

【讨论】:

  • 更多的是一个旁白,而不是一个答案,真的。
【解决方案6】:

Andrea 解决方案的超优化版本:

from itertools import dropwhile
from operator import ne
from functools import partial

def compare(l1, l2):
    it = iter(l2)
    try:
        for e in l1:
            dropwhile(partial(ne, e), it).next()
        return True
    except StopIteration:
        return False

这可以写成更加函数式的风格:

def compare(l1,l2):
    it = iter(l2)
    # any( True for .. ) because any([0]) is False, which we don't want here
    return all( any(True for _ in dropwhile(partial(ne, e), it)) for e in l1 )

【讨论】:

  • 我不确定原因,但是使用 bpowah 的测试代码,不使用 itertools(我发布的那个)的函数的曲线是 99% 的时间在曲线下使用 dropwhile 的代码。不知道partial和dropwhile是干什么的,我去google一下。
  • partial(ne, e) 基本上是lambda x: x != edropwhile( pred, seq) 是你的while pred(seq.next()): pass
【解决方案7】:

我觉得这比亚历克斯的回答更深入,但这是我的第一个想法:

def test(list1, list2):
    try:
        for num in list1:
            list2 = list2[list2.index(num):]
    except: return False
    else: return True

编辑:刚刚试了一下。他的速度更快。很近了。

编辑 2: 将 try/except 移出循环(这就是其他人应该查看您的代码的原因)。谢谢大佬。

【讨论】:

  • 你也可以将 try/except 移出循环
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2021-05-31
  • 2018-07-15
  • 2022-01-21
  • 1970-01-01
  • 2020-03-27
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多