【问题标题】:Efficient way to find missing elements in an integer sequence在整数序列中查找缺失元素的有效方法
【发布时间】:2021-09-29 15:51:33
【问题描述】:

假设我们在一个连续整数序列中缺少两个项目,并且缺少的元素位于第一个和最后一个元素之间。我确实编写了一个完成任务的代码。但是,如果可能的话,我想使用更少的循环来提高效率。任何帮助将不胜感激。另外,当我们必须找到更多丢失的项目(比如接近 n/4)而不是 2 时,情况会怎样。我认为我的代码应该是高效的,因为我更早地脱离了循环?

def missing_elements(L,start,end,missing_num):
    complete_list = range(start,end+1)
    count = 0
    input_index = 0
    for item  in  complete_list:
        if item != L[input_index]:
            print item
            count += 1
        else :
            input_index += 1
        if count > missing_num:
            break



def main():
    L = [10,11,13,14,15,16,17,18,20]
    start = 10
    end = 20
    missing_elements(L,start,end,2)



if __name__ == "__main__":
    main()

【问题讨论】:

  • 如果我不知道我要查找的缺失值,在这种情况下我该如何使用二分查找?
  • 不完全是二分查找,但是如果 L[index] == L[bottom] + (index - bottom),你可以推断出底部和索引之间的列表部分是完全连续的。这与将列表分成两部分相结合应该给出亚线性解决方案。
  • 分而治之,我喜欢这个主意。
  • @LieRyan 谢谢似乎是个好主意。
  • @vkaul11:我已经发布了一个完整的解决方案,扩展了我上面的评论。

标签: python indexing


【解决方案1】:

如果输入序列是排序的,你可以在这里使用集合。从输入列表中获取开始值和结束值:

def missing_elements(L):
    start, end = L[0], L[-1]
    return sorted(set(range(start, end + 1)).difference(L))

这假设 Python 3;对于 Python 2,请使用 xrange() 避免先构建列表。

sorted() 调用是可选的;没有它,将返回缺失值的set(),使用它你会得到一个排序列表。

演示:

>>> L = [10,11,13,14,15,16,17,18,20]
>>> missing_elements(L)
[12, 19]

另一种方法是检测后续数字之间的差距;使用较旧的itertools library sliding window recipe

from itertools import islice, chain

def window(seq, n=2):
    "Returns a sliding window (of width n) over data from the iterable"
    "   s -> (s0,s1,...s[n-1]), (s1,s2,...,sn), ...                   "
    it = iter(seq)
    result = tuple(islice(it, n))
    if len(result) == n:
        yield result    
    for elem in it:
        result = result[1:] + (elem,)
        yield result

def missing_elements(L):
    missing = chain.from_iterable(range(x + 1, y) for x, y in window(L) if (y - x) > 1)
    return list(missing)

这是一个纯粹的 O(n) 操作,如果您知道丢失项目的数量,您可以确保它只产生这些然后停止:

def missing_elements(L, count):
    missing = chain.from_iterable(range(x + 1, y) for x, y in window(L) if (y - x) > 1)
    return list(islice(missing, 0, count))

这也将处理更大的间隙;如果您在 11 点和 12 点缺少 2 个项目,它仍然可以工作:

>>> missing_elements([10, 13, 14, 15], 2)
[11, 12]

上面的示例只需要遍历[10, 13] 就可以解决这个问题。

【讨论】:

  • 我很确定输入列表已经排序,因为他将其命名为序列。根据我从 OP 的问题中得到的信息,他正试图使其低于 O(n)
  • @Rubens:集合之间的差异输出是一个集合,它没有定义的顺序。但是,在集合上返回 sorted() 会返回一个排序列表。
  • @Rubens:除非我看到明确说明,否则我怀疑 OP 是否足够了解 big-Oh 符号以要求优于 O(n)。
  • 是的鲁本斯,我正在尝试获得更有效的算法。 Martijn,所以我的方法在复杂性方面是尽可能高效的,或者你认为有一些技巧可以帮助提高效率?如果我必须找到 5 个丢失的项目而不是 2 个,那么您的方法是否会受到额外的惩罚,因为我有时可以更早地脱离循环并且我不需要在我的代码中进行排序。我知道如果我们有一个数字缺失总和差异会有所帮助。
  • @vkaul11:更新为您提供 O(n) 算法,如果预先知道缺失元素的数量,可以选择提前退出。
【解决方案2】:

假设 L 是一个没有重复的整数列表,你可以推断出 start 和 index 之间的列表部分是完全连续的当且仅当 L[index] == L[start] + (index - start) 并且类似地 index 和 end 是完全连续的当且仅如果L[index] == L[end] - (end - index)。这结合递归地将列表分成两部分给出了一个次线性的解决方案。

# python 3.3 and up, in older versions, replace "yield from" with yield loop

def missing_elements(L, start, end):
    if end - start <= 1: 
        if L[end] - L[start] > 1:
            yield from range(L[start] + 1, L[end])
        return

    index = start + (end - start) // 2

    # is the lower half consecutive?
    consecutive_low =  L[index] == L[start] + (index - start)
    if not consecutive_low:
        yield from missing_elements(L, start, index)

    # is the upper part consecutive?
    consecutive_high =  L[index] == L[end] - (end - index)
    if not consecutive_high:
        yield from missing_elements(L, index, end)

def main():
    L = [10,11,13,14,15,16,17,18,20]
    print(list(missing_elements(L,0,len(L)-1)))
    L = range(10, 21)
    print(list(missing_elements(L,0,len(L)-1)))

main()

【讨论】:

  • 感谢@Lie Ryan。我将尝试查看我不熟悉的函数的产量。
  • 我喜欢早上递归的味道
  • 这会比在一个简单的 for 循环中检查每个项目更快吗?你仍然需要检查每个相邻的项目,你只是在其中抛​​出一堆递归,这会进一步减慢它。除非我错过了什么。
  • @rcronk "仍然必须检查每个相邻项目" 不,这是不正确的,使用递归解决方案,只需要检查有洞的范围,这就是为什么这是次线性的。如果有k 孔,我的递归解决方案将找到孔O(k*log(n)) where k &lt;= n,如果k 为1,或者如果所有孔都组合在一个块中,那么这将在O(log(n)) 中找到孔。或者为了简化,它是O(log(n))。最好的情况是当没有漏洞时,它将在O(1) 中验证这一点,因为它只需要准确地检查两个数字。
  • @rcronk 现在,如果您尝试在现实生活中的机器上实现这一点,确实会出现一些复杂情况,现实生活中的机器喜欢线性循环,因为它们很容易用于分支预测器,内存预取器喜欢内存局部性,现实生活中的机器也可以进行并行处理。因此,现实生活中的实现可能会使用混合方法,从该算法开始,将范围排队等待并行处理池,然后在范围划分得足够小时切换到线性搜索。但在纯粹的算法世界中,您无需为最优解做任何事情。
【解决方案3】:
missingItems = [x for x in complete_list if not x in L]

【讨论】:

  • 除非将L 变成一个集合,否则效率并不高。潜在地,每个in 测试都是 O(n) 复杂度。
  • 这能做到吗:[x for x in complete_list if not x in set(L)] ?
  • 是的,虽然代码较短,但它是 O(n^2),而我的代码虽然较长,但是 O(n)。
  • 最好在单独的代码行中使 L 成为一个集合。
  • @JasonSperske:set(L) 表达式将为循环中的每次迭代执行,所以不会。先set_L = set(L),然后在列表推导中使用if x not in set_L
【解决方案4】:

使用collections.Counter

from collections import Counter

dic = Counter([10, 11, 13, 14, 15, 16, 17, 18, 20])
print([i for i in range(10, 20) if dic[i] == 0])

输出:

[12, 19]

【讨论】:

    【解决方案5】:
    
     a=[1,2,3,7,5,11,20]
     b=[]
     def miss(a,b):
         for x in range (a[0],a[-1]):
            if x not in a:
                b.append(x)
         return b
     print (miss(a,b))
    
    

    回答:[4, 6, 8, 9, 10, 12, 13, 14, 15, 16, 17, 18, 19]

    适用于 sorted,unsorted ,也适用于 duplicates

    【讨论】:

    • 如果您希望它包含初始数字列表的最后一个元素,则范围为 range(a[0],a[-1]+1)。但总的来说,我非常喜欢这个解决方案,与其他一些解决方案相比,它看起来很简单。
    【解决方案6】:

    使用scipy库:

    import math
    from scipy.optimize import fsolve
    
    def mullist(a):
        mul = 1
        for i in a:
            mul = mul*i
        return mul
    
    a = [1,2,3,4,5,6,9,10]
    s = sum(a)
    so = sum(range(1,11))
    mulo = mullist(range(1,11))
    mul = mullist(a)
    over = mulo/mul
    delta = so -s
    # y = so - s -x
    # xy = mulo/mul
    def func(x):
        return (so -s -x)*x-over
    
    print int(round(fsolve(func, 0))), int(round(delta - fsolve(func, 0)))
    

    时间安排:

    $ python -mtimeit -s "$(cat with_scipy.py)" 
    
    7 8
    
    100000000 loops, best of 3: 0.0181 usec per loop
    

    其他选项是:

    >>> from sets import Set
    >>> a = Set(range(1,11))
    >>> b = Set([1,2,3,4,5,6,9,10])
    >>> a-b
    Set([8, 7])
    

    时间是:

    Set([8, 7])
    100000000 loops, best of 3: 0.0178 usec per loop
    

    【讨论】:

      【解决方案7】:
      arr = [1, 2, 5, 6, 10, 12]
      diff = []
      
      """zip will return array of tuples (1, 2) (2, 5) (5, 6) (6, 10) (10, 12) """
      for a, b in zip(arr , arr[1:]):
          if a + 1 != b:
              diff.extend(range(a+1, b))
      
      print(diff)
      

      [3, 4, 7, 8, 9, 11]

      如果列表已排序,我们可以查找任何间隙。然后在当前(+1)和下一个值(不包括)之间生成一个范围对象,并将其扩展到差异列表。

      【讨论】:

        【解决方案8】:

        我的想法是不使用循环和设置操作:

        def find_missing(in_list):
            complete_set = set(range(in_list[0], in_list[-1] + 1))
            return complete_set - set(in_list)
        
        def main():
            sample = [10, 11, 13, 14, 15, 16, 17, 18, 20]
            print find_missing(sample)
        
        if __name__ == "__main__":
            main()
        
        # => set([19, 12])
        

        【讨论】:

        • 为了构建集合并计算差异,Python 仍然需要使用循环。这些只是隐藏在 C 代码中。它们会比 Python 循环更快,并且设置差异是一种有效的操作,但循环仍然存在。
        • @MartijnPieters 确实如此,我看到你还是打败了我。感谢您的详细说明。
        【解决方案9】:

        只需遍历列表并查找不连续的数字:

        prev = L[0]
        for this in L[1:]:
            if this > prev+1:
                for item in range(prev+1, this):    # this handles gaps of 1 or more
                    print item
            prev = this
        

        【讨论】:

        • 这就是我在没有 Python 语法能力的语言中这样做的方式,但在 Python 中这样做会相当乏味。
        • 同意。我更喜欢你的单线。
        • OTOH,这段代码是最容易阅读的,它不需要任何其他库、集合或句法工具。添加一个计数器来跳出循环是微不足道的。
        【解决方案10】:

        这是一个单行:

        In [10]: l = [10,11,13,14,15,16,17,18,20]
        
        In [11]: [i for i, (n1, n2) in enumerate(zip(l[:-1], l[1:])) if n1 + 1 != n2]
        Out[11]: [1, 7]
        

        我使用列表,切片将副本偏移一,并使用枚举来获取缺失项的索引。

        对于长列表,这不是很好,因为它不是 O(log(n)),但我认为与使用 set 进行小输入相比,它应该非常有效。来自 itertools 的 izip 可能会更快。

        【讨论】:

        • 那不可读吗?这都是非常基本的内置 Python。在我要签入的代码中,我可能会选择更好的标识符名称,但话又说回来,如果它在一行中都是自包含的,可能不会。
        • 我会做几行,但那只是我:)。
        • 我同意,如果我溢出超过 80 个字符,我通常会在我的列表理解中打破 inif
        • 如果丢失的数字是连续的,这可能会产生意想不到的结果。需要第二遍来检查。 PS:使用语言的基本特性并不等同于良好的可读性。
        • @LieRyan 对连续号码的好呼叫。我真的不认为我写它的方式如此深奥。如果你理解 enumerate 和 zip,它几乎是从左到右读取的。在我看来,您的解决方案(虽然很棒,次线性且更正确)并不是真正更具可读性
        【解决方案11】:
        >>> l = [10,11,13,14,15,16,17,18,20]
        >>> [l[i]+1 for i, j in enumerate(l) if (l+[0])[i+1] - l[i] > 1]
        [12, 19]
        

        【讨论】:

          【解决方案12】:

          如果两个连续数字之间的差大于1,我们发现了一个缺失值:

          >>> L = [10,11,13,14,15,16,17,18,20]
          >>> [x + 1 for x, y in zip(L[:-1], L[1:]) if y - x > 1]
          [12, 19]
          

          注意:Python 3。在 Python 2 中使用itertools.izip

          针对连续缺失多个值的改进版本:

          >>> import itertools as it
          >>> L = [10,11,14,15,16,17,18,20] # 12, 13 and 19 missing
          >>> [x + diff for x, y in zip(it.islice(L, None, len(L) - 1),
                                        it.islice(L, 1, None)) 
               for diff in range(1, y - x) if diff]
          [12, 13, 19]
          

          【讨论】:

          • 这确实创建了输入列表的两个副本;这些副本也需要循环。您可以使用不需要创建副本的迭代器(参见itertools 模块文档的示例)来执行此操作。
          • Python 3 不会将切片制作成迭代器;它们仍然是副本。
          • 好的。我的意思是zip
          • 这会错过更大的差距。
          • 连续不是意味着步长为 1 吗?
          【解决方案13】:
          def missing_elements(inlist):
              if len(inlist) <= 1:
                  return []
              else:
                  if inlist[1]-inlist[0] > 1:
                      return [inlist[0]+1] + missing_elements([inlist[0]+1] + inlist[1:])
                  else:
                      return missing_elements(inlist[1:])
          

          【讨论】:

            【解决方案14】:

            首先我们应该对列表进行排序,然后我们检查每个元素,除了最后一个,如果下一个值在列表中。注意不要在列表中重复!

            l.sort()
            
            [l[i]+1 for i in range(len(l)-1) if l[i]+1 not in l]
            

            【讨论】:

            • 纯代码答案通常无助于指出问题发生的为什么。您应该解释为什么它可以解决问题。请阅读How do I write a good answer?
            【解决方案15】:

            我偶然发现了这个寻找不同类型的效率 - 给定一个唯一序列号列表,可能非常稀疏,yield下一个可用序列号,而不在内存中创建整个集合。 (想想库存中物品频繁进出,但有些是长期存在的。)

            def get_serial(string_ids, longtail=False):
              int_list = map(int, string_ids)
              int_list.sort()
              n = len(int_list)
              for i in range(0, n-1):
                nextserial = int_list[i]+1
                while nextserial < int_list[i+1]:
                  yield nextserial
                  nextserial+=1
              while longtail:
                nextserial+=1
                yield nextserial
            [...]
            def main():
              [...]
              serialgenerator = get_serial(list1, longtail=True)
              while somecondition:
                newserial = next(serialgenerator)
            

            (输入是整数的字符串表示列表,yield 是整数,因此不是完全通用的代码。如果我们超出范围,longtail 会提供外推。)

            a similar question 也有一个答案,它建议使用位数组来有效地处理大量整数。

            我的一些代码版本使用了来自 itertools 的函数,但我最终放弃了这种方法。

            【讨论】:

              【解决方案16】:

              一点数学,我们得到一个简单的解决方案。以下解决方案适用于从 m 到 n 的整数。 适用于已排序和未排序的正数和负数。

              #numbers = [-1,-2,0,1,2,3,5]
              numbers = [-2,0,1,2,5,-1,3]
              
              sum_of_nums =  0
              max = numbers[0]
              min = numbers[0]
              for i in numbers:
                  if i > max:
                      max = i
                  if i < min:
                      min = i
                  sum_of_nums += i
              
              # Total : sum of numbers from m to n    
              total = ((max - min + 1) * (max + min)) / 2
              
              # Subtract total with sum of numbers which will give the missing value
              print total - sum_of_nums
              

              【讨论】:

                【解决方案17】:

                使用此代码,您可以找到序列中的任何缺失值,最后一个数字除外。只需将您的数据输入到列名为“numbers”的excel文件中。

                import pandas as pd
                import numpy as np
                
                data = pd.read_excel("numbers.xlsx")
                
                data_sort=data.sort_values('numbers',ascending=True)
                index=list(range(len(data_sort)))
                data_sort['index']=index
                data_sort['index']=data_sort['index']+1
                missing=[]
                
                for i in range (len(data_sort)-1):
                    if data_sort['numbers'].iloc[i+1]-data_sort['numbers'].iloc[i]>1:
                        gap=data_sort['numbers'].iloc[i+1]-data_sort['numbers'].iloc[i]
                        numerator=1
                        for j in range (1,gap):          
                            mis_value=data_sort['numbers'].iloc[i+1]-numerator
                            missing.append(mis_value)
                            numerator=numerator+1
                print(np.sort(missing))
                

                【讨论】:

                • 问题是要求一个通用算法。您的答案过于依赖实施。
                猜你喜欢
                • 2014-01-31
                • 2010-11-09
                • 2015-10-18
                • 1970-01-01
                • 1970-01-01
                • 1970-01-01
                • 1970-01-01
                • 1970-01-01
                • 2010-10-13
                相关资源
                最近更新 更多