【问题标题】:Pythonic way to determine whether not null list entries are 'continuous'确定空列表条目是否“连续”的 Pythonic 方法
【发布时间】:2013-01-21 04:46:18
【问题描述】:

我正在寻找一种方法来轻松确定列表中的所有 not None 项目是否都出现在单个连续切片中。我将使用整数作为 not None 项目的示例。

例如,列表[None, None, 1, 2, 3, None, None] 满足我对连续整数条目的要求。相比之下,[1, 2, None, None, 3, None]连续的,因为整数之间有 None 条目。

再举一些例子来说明这一点。

连续
[1, 2, 3, None, None]
[None, None, 1, 2, 3]
[None, 1, 2, 3, None]

不连续
[None, 1, None, 2, None, 3]
[None, None, 1, None, 2, 3]
[1, 2, None, 3, None, None]

我的第一个方法是使用变量来跟踪我们是否遇到过None,以及我们是否遇到过int——这最终会得到一个高度嵌套的和很难遵循嵌入在 for 循环中的一系列 if/else 语句。 (除了丑陋之外,我承认我并没有让它在所有情况下都能正常工作)。

有谁知道一种更简单的方法来确定列表中的非无项是否出现在单个连续切片中?

【问题讨论】:

  • 对于 None ([None, None, None, None]) 序列,您希望得到什么结果?
  • 对于一般问题,这是一个重要的问题,但对于我的具体需求,[None, None, None] 是否确定为连续并不重要。我实际上在做的是抓取第一组 not None 项目,然后用它们做一些事情——我所做的究竟是什么取决于它是否是唯一的连续切片。所以——所有的 None 甚至都不会让我进入这个过程的第一步。但是,理论上,它应该可能返回 True,对吧?
  • 如果保证列表至少包含一个非无项目,请将其编辑到问题中。那么列表长度为零[] 或无的情况呢?它应该给出 True、False、None、引发异常还是你完全不在乎?为简单起见,我们不妨(复古)根据最符合 Pythonic 的内容来定义不关心行为。
  • 顺便说一句,合适的词是“连续”而不是“连续”。
  • [3, 1, 2, None, None] 需要什么结果?

标签: python list null slice


【解决方案1】:

这是一种仅使用 numpy 的方法:

a = np.array([1, 2, 3, np.nan, 4, 5, np.nan, 6, 7])

# This returns indices of nans
# eg. [[3], [6]]
# use .squeeze() to convert to [3, 6]
aa = np.argwhere(a != a).squeeze()

# use a diff on your array , if the nans
# are continuous, the diff will always be 1
# if not, diff will be > 1 , and using any() will return True
any(np.diff(aa) > 1) 

【讨论】:

    【解决方案2】:

    我的第一个方法是使用变量来跟踪...

    ...这最终会导致嵌套在 for 循环中的一系列 if/else 语句高度嵌套且非常难以遵循...

    不!实际上你只需要一个变量。用你的方法从有限状态机(FSM)的角度思考这个问题将导致一个非常好的解决方案。

    我们称状态为p。一开始,p 为 0。然后我们开始在状态之间行走。

    当检查列表中的所有元素并且仍然没有失败时,答案是True

    一个在字典中编码翻译表的版本

    def contiguous(s, _D={(0,0):0, (0,1):1, (1,0):2, (1,1):1, (2,0):2, (2,1):3}):
        p = 0
        for x in s:
            p = _D[p, int(x is not None)]
            if p >= 3: return False
        return True
    

    另一个使用 if 语句的版本:

    def contiguous(s):
        p = 0
        for x in s:
            if x is None and p == 1 or x is not None and (p == 0 or p == 2):
                p += 1
            if p >= 3: return False
        return True
    

    所以我的意思是使用iffor 仍然是pythonic。

    更新

    我找到了另一种编码 FSM 的方法。我们可以把翻译表打包成一个12位的整数。

    def contiguous(s):
        p = 0
        for x in s:
            p = (3684 >> (4*p + 2*(x!=None))) & 3
            if p >= 3: return False
        return True
    

    这里 3684 是幻数,可以通过以下方式获得:

        _D[p,a]     3  2  1  2  1  0
             p      2  2  1  1  0  0
             a      1  0  1  0  1  0
    bin(3684) = 0b 11 10 01 10 01 00 
    

    可读性不如其他版本,但它更快,因为它避免了字典查找。 第二个版本和这个一样快,但是这个编码思想可以推广到解决更多的问题。

    【讨论】:

      【解决方案3】:

      一个班轮:

      contiguous = lambda l: ' ' not in ''.join('x '[x is None] for x in l).strip()
      

      真正的工作是由strip 函数完成的。如果剥离的字符串中有空格,则它们不是前导/尾随。该函数的其余部分将列表转换为字符串,其中每个 None 都有一个空格。

      【讨论】:

      • 虽然如果列表中的数据类型不能强制转换为字符串可能会有问题?问题可能不清楚,但我严格使用整数作为一个简单的例子——我需要该算法适用于所有非 None 数据类型。
      • @ClayWardell 如果您将x==None 更改为x is None,那么它将适用于任何值类型(并且可能会更快一些)。
      • 感谢@EdwardLoper,我实际上并不知道is 运算符,并认为== 在这里已经足够好了。
      • contiguous = lambda l:?为什么要命名一个匿名函数而不是声明一个函数def contiguous(l):
      • @Johnsyweb,所以你可以看到这一切,没有水平滚动条。我承认这不是一个很好的理由(我可能在codegolf.SE 上花了太多时间)。
      【解决方案4】:

      我做了一些分析来比较 @gnibbler 的方法和 groupby 的方法。 @gnibber 的方法始终更快,尤其是。对于更长的列表。例如,对于长度为 3-100 的随机输入,我看到大约 50% 的性能增益,有 50% 的机会包含单个 int 序列(随机选择),否则包含随机值。测试代码如下。我穿插了这两种方法(随机选择先使用哪一种)以确保消除任何缓存效果。基于此,我想说虽然 groupby 方法更直观,但如果分析表明这是要优化的整体代码的重要部分,@gnibber 的方法可能是合适的——在这种情况下,应该使用适当的 cmets指示使用 all/any 到消费者迭代器值的情况。

      from itertools import groupby
      import random, time
      
      def contiguous1(seq):
          # gnibber's approach
          seq = iter(seq)
          all(x is None for x in seq)        # Burn through any Nones at the beginning
          any(x is None for x in seq)        # and the first group
          return all(x is None for x in seq) # everthing else (if any) should be None.
      
      def contiguous2(seq):
          return sum(1 for k,g in groupby(seq, lambda x: x is not None) if k) == 1
      
      times = {'contiguous1':0,'contiguous2':0}
      
      for i in range(400000):
          n = random.randint(3,100)
          items = [None] * n
          if random.randint(0,1):
              s = random.randint(0,n-1)
              e = random.randint(0,n-s)
              for i in range(s,e):
                  items[i] = 3
          else:
              for i in range(n):
                  if not random.randint(0,2):
                      items[i] = 3
          if random.randint(0,1):
              funcs = [contiguous1, contiguous2]
          else:
              funcs = [contiguous2, contiguous1]
          for func in funcs:
              t0 = time.time()
              func(items)
              times[func.__name__] += (time.time()-t0)
      
      print
      for f,t in times.items():
          print '%10.7f %s' % (t, f)
      

      【讨论】:

        【解决方案5】:

        这是一个受 numpy.获取所有非空元素的数组索引。然后,将每个索引与其后面的索引进行比较。如果差值大于 1,则非空值之间存在空值。如果没有下一个索引大于 1 的索引,则没有间隙。

        def is_continuous(seq):
            non_null_indices = [i for i, obj in enumerate(seq) if obj is not None]
            for i, index in enumerate(non_null_indices[:-1]):
                if non_null_indices[i+1] - index > 1:
                    return False
            return True
        

        【讨论】:

          【解决方案6】:

          好'ol itertools.groupby 来救援:

          from itertools import groupby
          
          def contiguous(seq):
              return sum(1 for k,g in groupby(seq, lambda x: x is not None) if k) == 1
          

          给予

          >>> contiguous([1,2,3,None,None])
          True
          >>> contiguous([None, 1,2,3,None])
          True
          >>> contiguous([None, None, 1,2,3])
          True
          >>> contiguous([None, 1, None, 2,3])
          False
          >>> contiguous([None, None, 1, None, 2,3])
          False
          >>> contiguous([None, 1, None, 2, None, 3])
          False
          >>> contiguous([1, 2, None, 3, None, None])
          False
          

          [编辑]

          由于 cmets 中似乎有一些讨论,我将解释为什么我比其他一些方法更喜欢这种方法。

          我们正在尝试找出是否存在一组连续的非无对象,并且

          sum(1 for k,g in groupby(seq, lambda x: x is not None) if k)
          

          使用stdlib中的函数计算连续的非None对象的数量,该函数旨在收集连续的组。一旦我们看到groupby,我们就会认为是“连续组”,反之亦然。从这个意义上说,它是自我记录的。这基本上是我目标的定义

          恕我直言,唯一的弱点是它不会短路,而且可以修复,但经过考虑后,我仍然更喜欢它,因为它使用了我喜欢的原语——“计算连续非无组”——我更喜欢简单地“尽快告诉我是否有多个连续的非无组”。

          实现最后一种方法的许多方法都依赖于对问题的巧妙观察,例如“如果只有一组连续的非空对象,那么如果我们扫描直到找到第一个非空对象,然后扫描对象,直到我们找到第一个非 None 组(如果存在),那么剩下的是否是 None 给了我们答案。” (或者类似的东西,这是我的问题的一部分:我必须考虑一下。)对我来说,这感觉就像使用有关问题的“实施细节”来解决它,并专注于我们可以用来解决的问题的属性它,而不是简单地将问题指定给 Python 并让 Python 完成工作。

          正如俗话说的那样,我是一只头脑很少的熊,我喜欢避免变得聪明,因为根据我的经验,这是一条充满失败的路线。

          与往常一样,当然,每个人的里程可能会有所不同,而且可能与他们的聪明程度成正比。

          【讨论】:

          • 你不能用anyall 代替sum 吗?如果您不需要,查看整个列表似乎很浪费。
          • @gnibbler:我无法立即看到如何使用anyall。我想你可以使用len(list(islice((k for k,g in groupby(seq, lambda x: x is not None) if k), 0, 2))) == 1 之类的,但我不太喜欢它的外观。
          • 这个比我的好。 +1
          • @DSM,我找到了使用all 的方法。令人惊讶的是它不使用 groupby
          【解决方案7】:
          def contiguous(seq):
              seq = iter(seq)
              all(x is None for x in seq)        # Burn through any Nones at the beginning
              any(x is None for x in seq)        # and the first group
              return all(x is None for x in seq) # everthing else (if any) should be None.
          

          这里有几个例子。您可以使用next(seq) 从迭代器中获取下一项。我会在每个之后放置一个指向下一个项目的标记

          例子1:

          seq = iter([None, 1, 2, 3, None])        #  [None, 1, 2, 3, None]
                                                   # next^
          all(x is None for x in seq)            
                                                   #        next^
          any(x is None for x in seq)            
                                                   #                    next^ (off the end)
          return all(x is None for x in seq)       # all returns True for the empty sequence
          

          例子2:

          seq = iter([1, 2, None, 3, None, None])  #    [1, 2, None, 3, None, None]
                                                   # next^
          all(x is None for x in seq)            
                                                   #    next^
          any(x is None for x in seq)            
                                                   #             next^  
          return all(x is None for x in seq)       # all returns False when 3 is encountered
          

          【讨论】:

          • 如此简单......然而如此正确。该死的......希望我能想到它。
          • +1,因为这很聪明,我从来没有想过。我并不完全赞同使用 anyall 来推进迭代器的副作用,但我没有很好的理由反对它。
          • 在尝试查看它是如何工作的之后,我有点难过——根据文档, all() 和 any() 根据输入的内容返回 True 或 False。 .但他们似乎被用来修改他们的输入。你知道我在哪里可以找到一些解释其工作原理的文档吗?
          • @Simon,它使用 all 和 any 的 defined 行为。为什么你认为它是隐含的?你说的stl是什么意思?
          • @Simon 它明确声明的。文档声明 anyall 与这些代码等效。等效意味着使用内置函数或示例代码应始终产生相同的结果。由于示例代码是惰性的,因此内置的 anyall 也必须是惰性的。该文档的作者只是简单地决定,与其一一指出所有极端情况和有关这些简单函数的事情,不如用一小段可读代码更好地解释它们的属性。
          【解决方案8】:

          使用序列元素的自然方式是使用dropwhile

          from itertools import dropwhile
          def continuous(seq):
              return all(x is None for x in dropwhile(lambda x: x is not None,
                                                      dropwhile(lambda x: x is None, seq)))
          

          我们可以在没有嵌套函数调用的情况下表达这一点:

          from itertools import dropwhile
          def continuous(seq):
              core = dropwhile(lambda x: x is None, seq)
              remainder = dropwhile(lambda x: x is not None, core)
              return all(x is None for x in remainder)
          

          【讨论】:

          • IIUC,这是@gnibbler 的dropwhile 版本?
          【解决方案9】:

          这个算法有一些缺点(它从列表中删除项目)。但这是一个解决方案。

          基本上,如果您从开头和结尾删除所有连续的None。如果您在列表中找到了一些None,则整数不是连续形式。

          def is_continuous(seq):
              while seq and seq[0] is None: del seq[0]
              while seq and seq[-1] is None: del seq[-1]
          
              return None not in seq
          
          assert is_continuous([1,2,3,None,None]) == True
          assert is_continuous([None, 1,2,3,None]) == True
          assert is_continuous([None, None, 1,2,3]) == True
          assert is_continuous([None, 1, None, 2,3]) == False
          assert is_continuous([None, None, 1, None, 2,3]) == False
          assert is_continuous([None, 1, None, 2, None, 3]) == False
          assert is_continuous([1, 2, None, 3, None, None]) == False
          

          然而,小代码如何变得邪恶的另一个例子。

          我希望strip() 方法可用于list

          【讨论】:

            【解决方案10】:

            这可能不是最好的方法,但您可以查找第一个非无条目和最后一个 non-None 条目,然后检查切片是否有 None。例如:

            def is_continuous(seq):
                try:
                    first_none_pos = next(i for i,x in enumerate(seq) if x is not None)
                    #need the or None on the next line to handle the case where the last index is `None`.
                    last_none_pos = -next(i for i,x in enumerate(reversed(seq)) if x is not None) or None
                except StopIteration: #list entirely of `Nones`
                    return False
                return None not in seq[first_none_pos:last_none_pos]
            
            assert is_continuous([1,2,3,None,None]) == True
            assert is_continuous([None, 1,2,3,None]) == True
            assert is_continuous([None, None, 1,2,3]) == True
            assert is_continuous([None, 1, None, 2,3]) == False
            assert is_continuous([None, None, 1, None, 2,3]) == False
            assert is_continuous([None, 1, None, 2, None, 3]) == False
            assert is_continuous([1, 2, None, 3, None, None]) == False
            

            这适用于任何序列类型。

            【讨论】:

              【解决方案11】:

              你可以使用类似itertools.groupby:

              from itertools import groupby
              
              def are_continuous(items):
                  saw_group = False
              
                  for group, values in groupby(items, lambda i: i is not None):
                      if group:
                          if saw_group:
                              return False
                          else:
                              saw_group = True
              
                  return True
              

              这只会迭代直到它看到一个组两次。我不确定您是否考虑[None, None],因此请根据您的需要进行调整。

              【讨论】:

              • 我喜欢这个,因为它比@DSM 的更清晰,并且尽可能地“短路”。
              猜你喜欢
              • 1970-01-01
              • 2019-08-16
              • 2010-11-18
              • 2011-09-15
              • 2016-12-07
              • 2011-04-14
              • 1970-01-01
              • 2022-06-22
              • 1970-01-01
              相关资源
              最近更新 更多