【问题标题】:Determine if all elements in a list are present and in the same order in another list确定列表中的所有元素是否存在并且在另一个列表中是否以相同的顺序出现
【发布时间】:2014-07-03 06:43:56
【问题描述】:

如何创建一个函数sublist(),它接受两个列表list1list2,如果list1list2 的子列表,则返回True,否则返回False。如果list1 中的数字出现在list2 中的顺序与它们出现在list1 中的顺序相同,则list1list2 的子列表,但不一定是连续的。例如,

>>> sublist([1, 12, 3],[25, 1, 30, 12, 3, 40])
True

>>> sublist([5, 90, 2],[90, 20, 5, 2, 17])
False

【问题讨论】:

  • 在某些复杂性要求内?
  • 列表中的项目可以重复吗?
  • 我最初认为您的问题与这个问题几乎相同:stackoverflow.com/questions/3847386/…,但经过检查,事实并非如此。请考虑修改您的问题,以避免它作为重复而被关闭,并使其他尝试做同样事情的用户更容易找到。
  • @user3638865,我注意到您发布了 6 个问题,其中没有一个被标记为“已回答”。点击您认为合适的答案旁边的“复选标记”来选择答案。

标签: python list sublist


【解决方案1】:

这是使用迭代器在线性时间(和恒定空间)内完成此操作的一种方法:

def sublist(a, b):
    seq = iter(b)
    try:
        for x in a:
            while next(seq) != x: pass
        else:
            return True
    except StopIteration:
        pass
    return False

基本上,它会遍历子列表的每个元素,并查看它是否可以在它尚未查看的完整列表部分中找到相同的元素。如果它通过整个子列表,则意味着我们有一个匹配项(因此 for 循环中的 else 语句)。如果我们在完整列表中用完了要查看的元素,则意味着我们没有匹配项。

编辑:我已更新我的解决方案,使其适用于 Python 3。对于 Python 2.5 及更早版本,next(seq) 需要替换为 seq.next()

【讨论】:

  • 至少在 Python 3 上,这需要是 seq.__next__() - iter 在列表上返回一个没有 next 方法的对象,只是 __next__
  • 查看我的答案以获得一些不太严格的性能测试,表明这比我的答案或递归版本更快。我没有测试@sshashank124,但正如你指出的那样,复制列表会产生成本。
  • 哦,当然 - next(seq) 而不是 seq.__next__()。是的,这是要走的路。
  • 如果有人需要对此进行微优化,我怀疑 itertools.dropwhile(x.__ne__, seq) 可能比显式循环(因为它在 C 中迭代)或带有 breakfor 循环更快(因为它使用更少的字节码进行迭代)可能比围绕nextwhile 循环更快,尤其是在CPython 2.x 中。我怀疑它对于大多数真实代码是否会很重要,但如果确实如此,则有两件事需要测试。 :)
【解决方案2】:

一个非常粗略的解决方案:

def sublist(a, b):
    if not a:
        return True
    for k in range(len(b)):
        if a[0] == b[k]:
            return sublist(a[1:], b[k+1:])
    return False

print sublist([1, 12, 3], [25, 1, 30, 12, 3, 40]) # True
print sublist([12, 1, 3], [25, 1, 30, 12, 3, 40]) # False

编辑:速度升级

【讨论】:

  • 非常粗糙,但我相信这里的第一个对于所有情况都是正确的。但是,那个执行时间! :O。仍然 +1,它在全面性方面胜过我的。
  • 我认为我的解决方案最多会读取每个数组一次。如果我错了,请纠正我,但这是正确的最快速度吗?
  • 我想分析一下这个和我的迭代实现,但这看起来既正确又优雅。
  • 不要太努力,否则你会遇到递归深度限制:P
  • 是的,这是一个很酷的演示,但对于实际应用来说不是很实用。这里的大问题首先是,它正在创建子列表的副本,这将使其变得非常慢并且输入越大,内存就会越大。其次,您指出的递归深度限制;)
【解决方案3】:

这是一个应该具有最佳渐近性的迭代解决方案:

def sublist(x, y):
    if x and not y:
        return False
    i, lim = 0, len(y)
    for e in x:
        while e != y[i]:
            i += 1
            if i == lim:
                return False
        i += 1
    return True

@sshashank124 的解决方案具有相同的复杂性,但动态会有所不同:他的版本多次遍历第二个参数,但由于它将更多工作推入 C 层,因此在较小的输入上可能会更快。

编辑:@hetman 的解决方案具有基本相同的逻辑,但更加 Pythonic,尽管与我的预期相反,它似乎稍微慢一些。 (我对@sshashan124 解决方案的性能也有误;递归调用的开销似乎超过了在 C 中做更多工作的好处。)

【讨论】:

  • 您确定@sshashank124 的解决方案具有相同的复杂性吗?它正在创建子列表的副本,这意味着空间复杂度肯定会更高。
  • 我只考虑时间复杂度。
【解决方案4】:

这是一个简化版:

def sublist(a,b):
    try:
        return a[0] in b and sublist(a[1:],b[1+b.index(a[0]):])
    except IndexError:
        return True

>>> print sublist([1, 12, 3],[25, 1, 30, 12, 3, 40])
True

>>> print sublist([5, 90, 2],[90, 20, 5, 2, 17])
False

【讨论】:

  • 如果允许重复则将无法工作。喜欢在sublist([1, 2, 2], [1, 2, 3]) 上返回“True”。
  • 在递归调用中应该是b[1+b.index(a[0]):],不是吗?然后它将正确处理重复。
  • @Alp,是的,非常感谢。我已经更新了我的答案。
【解决方案5】:

恭喜你提出了一个看似困难的问题。我认为这会起作用,但如果我错过了一个极端案例,尤其是重复元素,我不会感到震惊。受Hgu Nguyen的递归解决方案启发的修订版:

def sublist(a, b):
    index_a = 0
    index_b = 0
    len_a = len(a)
    len_b = len(b)
    while index_a < len_a and index_b < len_b:
        if a[index_a] == b[index_b]:
            index_a += 1
            index_b += 1
        else:
            index_b += 1
    return index_a == len_a

一些粗略的分析:

鉴于需要遍历大部分或全部 b 的列表,我的算法会受到影响:

a = [1, 3, 999999]
b = list(range(1000000))

在我的 PC 上,Huu Nguyen 或 Hetman 的算法运行 100 次检查迭代大约需要 10 秒。我的算法需要 20 秒。

鉴于较早的成功,Huu 的算法大大落后:

a = [1, 3, 5]

Hetman 的算法或我的算法可以在一秒钟内完成 10 万次检查 - 在我的 PC 上,Hetman 的 0.13 秒,我的 0.19 秒。 Huu 需要 16 秒才能完成 1k 次检查。坦率地说,我对这种程度的差异感到震惊 - 如果没有编译器优化,递归可能会很慢,我知道,但比我预期的要差 4 个数量级。

给定一个失败列表a,当需要遍历整个第二个列表时,性能会回到我所看到的 - 可以理解,因为无法知道最后不会有一个序列匹配否则无法匹配的列表。

a = [3, 1, 5]

同样,Huu Nguyen 或 Hetman 的算法进行 100 次测试大约需要 10 秒,我的测试是 20 秒。

较长的有序列表保持了我看到的早期成功模式。例如:

a = range(0, 1000, 20)

Hetman 的算法需要 10.99 秒才能完成 100k 次测试,而我的算法需要 24.08 秒。 Huu 用了 28.88 完成了 100 次测试。

诚然,这些并不是您可以运行的全部测试,但在所有情况下,Hetman 的算法都表现最好。

【讨论】:

  • 出色的分析。是的,我有一种感觉,我的不是很理想。很高兴在这里看到不同案例的全面比较。
  • @HuuNguyen - 我只能认为在早期的成功案例中,复制大部分 b 所花费的时间正在削弱递归版本。在我的简单测试用例中(在匹配 1、3 和 5 之后)它应该只需要执行 3 次,但似乎加起来。您可以通过将起始索引 k 传递给 b 并将未复制的列表引用传递给递归调用,然后执行 for k in range(starting_index, len(b)): 来优化一些 - 但我希望 @Hetman 仍然胜过它。一长串b 突出了复制成本,但总的来说,我认为它是 O(a*b),因为你复制了 b len(a) 次的部分。
【解决方案6】:

这个怎么样:让我们从另一边来解决这个问题:

def sublist(a,b):
    """returns True if a is contained in b and in the same order"""
    return a == [ch for ch in b if ch in a]

这在某些情况下会失败(例如,[1,2,3] 是否应该是 [1,1,8,2,3] 的子集),但很难确切说明您希望如何实现...

【讨论】:

    【解决方案7】:

    对于运行缓慢但对于您显示的大小的数组完全足够的快速而肮脏的解决方案:

    def sublist(a,b):
        last = 0
        for el_a in a:
            if el_a in b[last:]:
                 last = b[last:].index(el_a)
            else:
                 return False
        return True
    

    **已编辑以适用于非连续元素

    【讨论】:

    • 关闭但没有。他的第一个例子在这里测试为假。
    • 我也没有第一次'round并做了set.issubset解决方案
    【解决方案8】:

    这里有另一个解决方案,新手可能比Hetman's 更容易理解。 (注意,它非常接近this duplicate question中OP的实现,但避免了每次从b开头重新开始搜索的问题。)

    def sublist(a, b):
        i = -1
        try:
            for e in a:
                i = b.index(e, i+1)
        except ValueError:
            return False
        else:
            return True
    

    当然,这要求 blist,而 Hetman 的回答允许任何可迭代的。而且我认为(对于足够了解 Python 的人来说)它也没有 Hetman 的答案那么简单。

    从算法上讲,它与 Hetman 的回答做同样的事情,所以它是 O(N) 时间和 O(1) 空间。但实际上,它可能会更快,至少在 CPython 中是这样,因为我们正在将内部循环从 Python while 围绕迭代器移动到 C 快速getindex 循环(在 list.index 内) .再说一次,它可能会更慢,因为我们正在复制 i 值,而不是将所有状态嵌入(C 实现的)迭代器中。如果重要,请使用您的真实数据对它们进行测试。 :)

    【讨论】:

      【解决方案9】:

      这是使用regex 的更好解决方案:

      import re
      
      
      def exiSt(short,long):
          r = ''.join(["(.*"+str[x]+")" for x in short])
          return re.match(r,','.join([str(x) for x in long])) == None
      
      long = [1, 2, 3, 4, 5]
      short1 = [1,2,5]
      short2 = [1,5,3]
      
      exiSt(short1,long)
      >> True
      
      exiSt(short2,long)
      >> False
      

      【讨论】:

        【解决方案10】:
        def sublist(a, b):
            "if set_a is not subset of set_b then obvious answer is False"
            if not set(a).issubset(set(b)):
                return False
            n = 0
            for i in a:
                if i in b[n:]:
                    "+1 is needed to skip consecutive duplicates, i.e. sublist([2,1,1],[2,1]) = False"
                    "if +1 is absent then sublist([2,1,1],[2,1]) = True"
                    "choose to use or not to use +1 according to your needs"
                    n += b[n:].index(i) + 1
                else:
                    return False
            return True
        

        【讨论】:

          猜你喜欢
          • 1970-01-01
          • 2023-03-18
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 2022-01-06
          • 1970-01-01
          相关资源
          最近更新 更多