【问题标题】:Python all()/any() like method for a portion/part of list?Python all()/any() 之类的方法用于列表的一部分/部分?
【发布时间】:2019-11-19 05:46:04
【问题描述】:

什么是最优雅/pythonic 的实现方式:“如果列表中总值的 x% 大于 y,则返回 true”。我目前已经实现了一个功能:

def check(listItems, val):
   '''A method to check all elements of a list against a given value.
   Returns true if all items of list are greater than value.'''
   return all(x>val for x in listItems)

但是对于我的用例来说,等待这个特定条件的代价是相当大的,而且有些无用。如果列表中约 80% 的项目大于给定值,我想继续。 我想到的一种方法是按降序对列表进行排序,创建另一个列表并将列表中 80% 的元素复制到新列表中,然后为该新列表运行函数。但是,我希望必须有一种更优雅的方式来做到这一点。有什么建议吗?

【问题讨论】:

  • 如果您进行任何排名,您将立即受到nlog(n) 复杂性的打击。相反,像你正在做的那样迭代循环中的每个元素(线性时间),检查有多少大于val,然后进行 80% 的检查。

标签: python loops for-in-loop


【解决方案1】:

您可以为此使用filter。到目前为止,这是最快的方法。请参考我的其他答案,因为这比其中的方法更快。

def check(listItems, val, goal=0.8):
    return len((*filter(val.__lt__, listItems),)) >= len(listItems) * goal

与我的另一个问题中的方法一起运行的测试结果时间是:

1.684135717988247

【讨论】:

    【解决方案2】:

    我不想将 Mark Meyer 的回答归功于他,因为他提出了使用累积的概念,以及他们的更 Pythonic/可读性,但如果您正在寻找“最快”的方法,那么使用map 修改他的方法与使用理解相比更快。

    any(map(goal.__le__, accumulate(map(val.__lt__, listItems))))
    

    只是为了测试:

    from timeit import timeit
    from itertools import accumulate
    
    def check1(listItems, val):
        goal = len(listItems)*0.8
        return any(x > goal for x in accumulate(n > val for n in listItems))
    
    def check2(listItems, val):
        goal = len(listItems)*0.8
        return any(map(goal.__le__, accumulate(map(val.__lt__, listItems))))
    
    items = [1, 2, 2, 3, 4, 2, 4, 1, 1, 1]
    
    for t in (check1, check2):
        print(timeit(lambda: t(items, 1)))
    

    结果是:

    3.2596251670038328
    2.0594907909980975
    

    【讨论】:

      【解决方案3】:

      按顺序检查每个项目。

      • 如果达到满意的程度,请尽早返回 True。

      • 如果你达到了永远无法满足的地步,即使未来的每个项目都通过了测试,那么请尽早返回 False。

      • 否则继续(以防后面的元素帮助您满足要求)。

      这与上述 cmets 中的 FatihAkici 的想法相同,但进行了进一步优化。

      def check(list_items, ratio, val):
          passing = 0
          satisfied = ratio * len(list_items)
          for index, item in enumerate(list_items):
              if item > val:
                  passing += 1
              if passing >= satisfied:
                  return True
              remaining_items = len(list_items) - index - 1
              if passing + remaining_items < satisfied:
                  return False
      

      【讨论】:

        【解决方案4】:

        听起来您正在处理长列表,这就是成本高昂的原因。如果您可以在满足条件后立即退出,那就太好了。 any() 会这样做,但在将其传递给 any() 之前,您需要避免阅读整个列表。一种选择可能是使用itertools.accumulate 来保持True 值的运行总数并将其传递给任何值。比如:

        from itertools import accumulate
        
        a = [1, 2, 2, 3, 4, 2, 4, 1, 1, 1]
        
        # true if 50% are greater than 1
        goal = .5 * len(a) # at least 5 out of 10   
        any( x > goal for x in accumulate(n > 1 for n in a))
        

        accumulate 不需要读取整个列表——它只会开始传递到目前为止看到的 True 值的数量。 any 应在找到真值后立即短路,在上述情况下为索引 5。

        【讨论】:

        • 非常感谢(y)
        • 很好的答案!虽然如果你使用 map vs comprehensions (虽然可读性较差),它会更快地看到我的答案。
        • 有什么方法可以使用accumulate 在我的答案中执行优化? (如果无法实现目标,请尽早停止)
        • 嘿@ZevChonoles,是的,我打赌你可以。您可以将accumulate() 传递给enumerate,这将为您提供(index, number_true_so_far) 的元组,例如for index, number_true in enumerate(accumulate(n &gt; val for n in a)): 然后您不需要第一个测试或计数器,但其余的逻辑应该可以工作。这是 IMO 的好主意。
        【解决方案5】:

        这个呢:

        def check(listItems, val, threshold=0.8):
            return sum(x > val for x in listItems) > len(listItems) * threshold
        

        它指出:如果listItems 中的元素大于val,则check 大于threshold%(默认为0.80)时checkTrue

        【讨论】:

        • 这种方法很棒,因为它不需要任何额外的库,这意味着它也适用于 python 2(更通用的解决方案)。谢谢(y)
        猜你喜欢
        • 2012-01-30
        • 2011-06-30
        • 2020-01-27
        • 1970-01-01
        • 1970-01-01
        • 2010-12-19
        • 2016-04-27
        • 1970-01-01
        • 2021-03-11
        相关资源
        最近更新 更多