【问题标题】:How to test if a list contains another list as a contiguous subsequence?如何测试一个列表是否包含另一个列表作为连续子序列?
【发布时间】:2011-04-20 07:54:36
【问题描述】:

如何测试一个列表是否包含另一个列表(即它是一个连续的子序列)。假设有一个名为 contains 的函数:

contains([1,2], [-1, 0, 1, 2]) # Returns [2, 3] (contains returns [start, end])
contains([1,3], [-1, 0, 1, 2]) # Returns False
contains([1, 2], [[1, 2], 3]) # Returns False
contains([[1, 2]], [[1, 2], 3]) # Returns [0, 0]

编辑:

contains([2, 1], [-1, 0, 1, 2]) # Returns False
contains([-1, 1, 2], [-1, 0, 1, 2]) # Returns False
contains([0, 1, 2], [-1, 0, 1, 2]) # Returns [1, 3]

【问题讨论】:

  • 不管怎样,返回 [start, end+1] 更符合 Python 风格,因为它看起来像一个切片 -- (end+1)-start 给出了找到的长度。
  • 这看起来是个糟糕的设计——有时函数返回一个布尔值,有时它返回一个列表。这使得它很难使用,因为您必须先检查返回类型,然后才能对结果执行任何操作。恕我直言,一个名为“包含”的函数应该只返回 True 或 False。
  • 很遗憾,列表没有内置所需的功能,但字符串有 (str.find)。
  • 为什么这会出于某种原因返回一个列表而不是一个元组!?

标签: python


【解决方案1】:

如果所有项目都是唯一的,您可以使用集合。

>>> items = set([-1, 0, 1, 2])
>>> set([1, 2]).issubset(items)
True
>>> set([1, 3]).issubset(items)
False

【讨论】:

  • 这不是 op 想要的
  • 即使所有项目都是唯一的,它仍然不起作用,除非它们也处于相同的顺序,没有混合项目。例如在[1, 2, 3] 中找到[1, 3][2, 1] 将给出误报。假设我们正在寻找序列本身,而不仅仅是序列中包含的值。
  • 我认为最好表明一个想法可能是错误的(以便将来避免它),而不是完全抹去它。
  • 是的,特别是因为对于一些不关心序列的人(比如我)来说,这是正确的解决方案。
  • 查看者注意:此示例中提供的结果不准确。话虽如此,这个例子帮助了我。
【解决方案2】:

OP 编辑​​后:

def contains(small, big):
    for i in xrange(1 + len(big) - len(small)):
        if small == big[i:i+len(small)]:
            return i, i + len(small) - 1
    return False

【讨论】:

  • 但它失败了 contains([1,2], [-1, 0, 1, 1, 2]) ,它返回 [2,4] 而不是我认为是预期的 [3 ,4]
  • 这对于大列表来说效率非常低,因为它每次都会不断创建和销毁临时列表big[i:i+len(small)]
  • @Dave:您是否根据您的解决方案计时?
  • 根据我的测试,即使在大型列表(100 万个元素,匹配的子集在末尾)上,这也比 Dave Kirby 的解决方案性能略好:10 次重复为 4.1 秒,而 5.6 秒为戴夫的。我很想发布我的测试代码,但没有一个简单的方法可以做到这一点。
  • 更新:我说得太早了——我的小名单太小了。一旦我将它们的大小增加到 1000,这个算法就会爆炸,而其他的则保持不变。毕竟,看起来戴夫柯比在大名单上的胜利。 pastebin.com/NZwU6PUx
【解决方案3】:

这是我的版本:

def contains(small, big):
    for i in xrange(len(big)-len(small)+1):
        for j in xrange(len(small)):
            if big[i+j] != small[j]:
                break
        else:
            return i, i+len(small)
    return False

它返回一个 (start, end+1) 的元组,因为我认为这更像是 pythonic,正如 Andrew Jaffe 在他的评论中指出的那样。它不会对任何子列表进行切片,因此应该相当有效。

新手感兴趣的一点是它使用else clause on the for statement - 这不是我经常使用的东西,但在这种情况下可能非常宝贵。

这与在字符串中查找子字符串相同,因此对于大型列表,实现Boyer-Moore algorithm 之类的方法可能更有效。

注意:如果您使用的是 Python3,请将 xrange 更改为 range

【讨论】:

  • +1 有关高效字符串搜索算法的说明。您的一个缺点是添加了一个解释的内部循环(我想切片比较更快,尽管副本可能会抵消这一点)。我将尝试进行性能比较。
  • 经过进一步的测试,你的迄今为止最好的大子序列。我会选择这个,即使在较小的数据集上存在小缺点。
  • +1:不知道forelse 子句!就在今天,我创建了一个尴尬的结构,其中涉及设置一个布尔值来完成此操作。
  • 我觉得用切片比较简单,类似于martineau's answer:if big[i:i+len(small)] == small: return i, i+len(small)
  • 您可以通过在顶部定义len(small) 来节省一遍又一遍的计算,例如n = len(small)
【解决方案4】:

这很有效,而且相当快,因为​​它使用内置的 list.index() 方法和 == 运算符进行线性搜索:

def contains(sub, pri):
    M, N = len(pri), len(sub)
    i, LAST = 0, M-N+1
    while True:
        try:
            found = pri.index(sub[0], i, LAST) # find first elem in sub
        except ValueError:
            return False
        if pri[found:found+N] == sub:
            return [found, found+N-1]
        else:
            i = found+1

【讨论】:

  • 不确定是否有缺陷,您可以 matineau 仔细检查一下。我尝试了 print (contains((1,2,3),(1,2,3))) 得到 [0,2] 而不是 [0,1,2]
  • @user-asterix:函数按照设计(和 OP 想要的)有效地返回 [start, end]
【解决方案5】:

我试图让它尽可能高效。

它使用生成器;建议不熟悉这些野兽的人查看their documentationyield expressions

基本上,它从子序列中创建一个值生成器,可以通过发送一个真值来重置它。如果生成器被重置,它会从sub 的开头重新开始生成。

然后它只是将sequence 的连续值与生成器产量进行比较,如果它们不匹配则重置生成器。

当生成器的值用完时,即到达sub 的末尾而没有被重置,这意味着我们找到了匹配项。

由于它适用于任何序列,您甚至可以在字符串上使用它,在这种情况下,它的行为类似于str.find,只是它返回False 而不是-1

进一步说明:根据 Python 标准,我认为返回元组的第二个值通常应该高一个。即"string"[0:2] == "st"。但规范另有规定,所以这就是它的工作原理。

这取决于这是一个通用的例程,还是实现某个特定的目标;在后一种情况下,最好实现一个通用例程,然后将其包装在一个函数中,该函数会调整返回值以适应规范。

def reiterator(sub):
    """Yield elements of a sequence, resetting if sent ``True``."""
    it = iter(sub)
    while True:
        if (yield it.next()):
            it = iter(sub)

def find_in_sequence(sub, sequence):
    """Find a subsequence in a sequence.

    >>> find_in_sequence([2, 1], [-1, 0, 1, 2])
    False
    >>> find_in_sequence([-1, 1, 2], [-1, 0, 1, 2])
    False
    >>> find_in_sequence([0, 1, 2], [-1, 0, 1, 2])
    (1, 3)
    >>> find_in_sequence("subsequence",
    ...                  "This sequence contains a subsequence.")
    (25, 35)
    >>> find_in_sequence("subsequence", "This one doesn't.")
    False

    """
    start = None
    sub_items = reiterator(sub)
    sub_item = sub_items.next()
    for index, item in enumerate(sequence):
        if item == sub_item:
            if start is None: start = index
        else:
            start = None
        try:
            sub_item = sub_items.send(start is None)
        except StopIteration:
            # If the subsequence is depleted, we win!
            return (start, index)
    return False

【讨论】:

  • 一项勇敢的努力,但这比 eumiro 或 Dave Kirby 的解决方案性能更差。在我在其他 cmets 中描述的基准上为 8.2 秒。
  • 有趣的是看到原生代码的速度差异。对于较长的子序列,该算法似乎会相对更快。您在测试中使用的子序列有多长?
  • 你是对的。这比具有更大子序列的 eumiro 的解决方案表现得更好,但它的表现仍然不如 Dave 的。
【解决方案6】:

这是一个使用列表方法的简单算法:

#!/usr/bin/env python

def list_find(what, where):
    """Find `what` list in the `where` list.

    Return index in `where` where `what` starts
    or -1 if no such index.

    >>> f = list_find
    >>> f([2, 1], [-1, 0, 1, 2])
    -1
    >>> f([-1, 1, 2], [-1, 0, 1, 2])
    -1
    >>> f([0, 1, 2], [-1, 0, 1, 2])
    1
    >>> f([1,2], [-1, 0, 1, 2])
    2
    >>> f([1,3], [-1, 0, 1, 2])
    -1
    >>> f([1, 2], [[1, 2], 3])
    -1
    >>> f([[1, 2]], [[1, 2], 3])
    0
    """
    if not what: # empty list is always found
        return 0
    try:
        index = 0
        while True:
            index = where.index(what[0], index)
            if where[index:index+len(what)] == what:
                return index # found
            index += 1 # try next position
    except ValueError:
        return -1 # not found

def contains(what, where):
    """Return [start, end+1] if found else empty list."""
    i = list_find(what, where)
    return [i, i + len(what)] if i >= 0 else [] #NOTE: bool([]) == False

if __name__=="__main__":
    import doctest; doctest.testmod()

【讨论】:

    【解决方案7】:

    我觉得这个很快……

    def issublist(subList, myList, start=0):
        if not subList: return 0
        lenList, lensubList = len(myList), len(subList)
        try:
            while lenList - start >= lensubList:
                start = myList.index(subList[0], start)
                for i in xrange(lensubList):
                    if myList[start+i] != subList[i]:
                        break
                else:
                    return start, start + lensubList - 1
                start += 1
            return False
        except:
            return False
    

    【讨论】:

      【解决方案8】:

      如果big 列表真的很大,我可以谦虚地建议Rabin-Karp algorithm。该链接甚至包含几乎可用的 Python 代码。

      【讨论】:

        【解决方案9】:

        如果我们细化关于测试一个列表是否包含另一个列表作为序列的问题,答案可能是下一个单行:

        def contains(subseq, inseq):
            return any(inseq[pos:pos + len(subseq)] == subseq for pos in range(0, len(inseq) - len(subseq) + 1))
        

        这里是我用来调整单行的单元测试:

        https://gist.github.com/anonymous/6910a85b4978daee137f

        【讨论】:

        • 我想这就是我的答案!漂亮的单线,看起来不错。
        • 顺便说一句,我认为return True 可能会通过所有这些单元测试。
        【解决方案10】:

        最小的代码:

        def contains(a,b):
            str(a)[1:-1].find(str(b)[1:-1])>=0
        

        【讨论】:

          【解决方案11】:

          这是我的答案。这个函数将帮助您找出 B 是否是 A 的子列表。时间复杂度为 O(n)。

          `def does_A_contain_B(A, B): #remember now A is the larger list
              b_size = len(B)
              for a_index in range(0, len(A)):
                  if A[a_index : a_index+b_size]==B:
                      return True
              else:
                  return False`
          

          【讨论】:

            【解决方案12】:

            有一个 all()any() 函数可以做到这一点。 检查big 是否包含small 中的所有元素

            result = all(elem in big for elem in small)
            

            检查small 是否包含big 中的任何元素

            result = any(elem in big for elem in small)
            

            变量结果将是布尔值 (TRUE/FALSE)。

            【讨论】:

            • 如果我没记错的话 all(elem in list1 for elem in list2) 正在测试 list2 中的所有项目与 list1 中的项目,所以它是相反的。这会检查 list2 是否包含 list1 中的所有项目
            • all(elem in big for elem in small) 无论位置如何都会返回 true,这不是 OP 想要的。他们要求“连续子序列”。
            • 如果有人感到困惑,这里是示例ColumnToCheck = [1,2,3,4] ListofItems =[1,2,3,4,251,54,8,65,5,85,25] #if all items from ColumnToCheck exist in ListofItems print(all(x in ListofItems for x in ColumnToCheck))
            【解决方案13】:

            大多数答案的问题是,它们对列表中的唯一项目有好处。如果项目不是唯一的,并且您仍然想知道是否存在交叉点,则应该对项目进行计数:

            from collections import Counter as count
            
            def listContains(l1, l2):
              list1 = count(l1)
              list2 = count(l2)
            
              return list1&list2 == list1
            
            print( listContains([1,1,2,5], [1,2,3,5,1,2,1]) ) # Returns True
            print( listContains([1,1,2,8], [1,2,3,5,1,2,1]) ) # Returns False
            

            您也可以使用''.join(list1&list2)返回交集

            【讨论】:

              【解决方案14】:
              a=[[1,2] , [3,4] , [0,5,4]]
              print(a.__contains__([0,5,4]))
              

              它提供真正的输出。

              a=[[1,2] , [3,4] , [0,5,4]]
              print(a.__contains__([1,3]))
              

              它提供了错误的输出。

              【讨论】:

              • 这似乎是特定于版本的。例如,它在 LeetCode 上失败。
              【解决方案15】:

              这是一个代码行更少且易于理解的解决方案(或者至少我喜欢这样认为)。

              如果你想保持顺序(仅当较小的列表在较大的列表中以相同的顺序找到时才匹配):

              def is_ordered_subset(l1, l2):
                  # First check to see if all element of l1 are in l2 (without checking order)
                  if not set(l1).issubset(l2): 
                      return False
              
                  length = len(l1)
                  # Make sublist of same size than l1
                  list_of_sublist = [l2[i:i+length] for i, x in enumerate(l2)]
                  #Check if one of this sublist is l1
                  return l1 in list_of_sublist 
              

              【讨论】:

                【解决方案16】:

                戴夫的回答很好。但我建议这种实现更高效并且不使用嵌套循环。

                def contains(small_list, big_list):
                    """
                    Returns index of start of small_list in big_list if big_list
                    contains small_list, otherwise -1.
                    """
                    loop = True
                    i, curr_id_small= 0, 0
                    while loop and i<len(big_list):
                        if big_list[i]==small_list[curr_id_small]:
                            if curr_id_small==len(small_list)-1:
                                loop = False
                            else:
                                curr_id_small += 1
                        else:
                            curr_id_small = 0
                        i=i+1
                    if not loop:
                        return i-len(small_list)
                    else:
                        return -1
                

                【讨论】:

                  【解决方案17】:

                  你可以使用numpy:

                  def contains(l1, l2):
                     """ returns True if l2 conatins l1 and False otherwise """
                  
                     if len(np.intersect1d(l1,l2))==len(l1):
                        return = True
                     else:
                        return = False
                  

                  【讨论】:

                    【解决方案18】:

                    我总结并评估了不同技术所花费的时间

                    使用的方法有:

                    def containsUsingStr(sequence, element:list):
                        return str(element)[1:-1] in str(sequence)[1:-1]
                    
                    
                    def containsUsingIndexing(sequence, element:list):
                        lS, lE = len(sequence), len(element)
                        for i in range(lS - lE + 1):
                            for j in range(lE):
                                if sequence[i+j] != element[j]: break
                            else: return True
                        return False
                    
                    
                    def containsUsingSlicing(sequence, element:list):
                        lS, lE = len(sequence), len(element)
                        for i in range(lS - lE + 1):
                            if sequence[i : i+lE] == element: return True
                        return False
                    
                    
                    def containsUsingAny(sequence:list, element:list):
                        lE = len(element)
                        return any(element == sequence[i:i+lE] for i in range(len(sequence)-lE+1))
                    

                    时间分析代码(平均超过 1000 次迭代):

                    from time import perf_counter
                    
                    functions = (containsUsingStr, containsUsingIndexing, containsUsingSlicing, containsUsingAny)
                    fCount = len(functions)
                    
                    
                    for func in functions:
                        print(str.ljust(f'Function : {func.__name__}', 32), end='   ::   Return Values: ')
                        print(func([1,2,3,4,5,5], [3,4,5,5]) , end=', ')
                        print(func([1,2,3,4,5,5], [1,3,4,5]))
                    
                    
                    
                    avg_times = [0]*fCount
                    for _ in range(1000):
                        perf_times = []
                        for func in functions:
                            startTime = perf_counter()
                            func([1,2,3,4,5,5], [3,4,5,5])
                            timeTaken = perf_counter()-startTime
                            perf_times.append(timeTaken)
                            
                    
                        for t in range(fCount): avg_times[t] += perf_times[t]
                    
                    minTime = min(avg_times)
                    print("\n\n Ratio of Time of Executions : ", ' : '.join(map(lambda x: str(round(x/minTime, 4)), avg_times)))
                    

                    输出:

                    结论:在这种情况下,切片操作被证明是最快的

                    【讨论】:

                      猜你喜欢
                      • 2012-05-31
                      • 1970-01-01
                      • 1970-01-01
                      • 1970-01-01
                      • 2013-11-05
                      • 1970-01-01
                      • 1970-01-01
                      • 1970-01-01
                      • 2022-11-21
                      相关资源
                      最近更新 更多