【问题标题】:Removing consecutive occurrences from end of list python从列表python的末尾删除连续出现的事件
【发布时间】:2014-06-17 11:43:02
【问题描述】:

我有一个 numpy 数组:

ar = np.array([True, False, True, True, True])

如果最后一个元素为真,我想删除数组末尾所有连续的真元素。比如

magic_func(ar) => [True, False]

如果ar = [True, False, True, False, True]。那么

magic_func(ar) => [True, False, True, False]

如果ar = [True, False, False],函数什么也不做,因为最后一个元素是False

python 中是否有一个单行来执行此操作?使用 numpy 库或其他东西

【问题讨论】:

  • 如果ar = np.array([True, True)函数应该返回np.array([], dtype=bool)吗?
  • 更正函数应该返回一个空数组

标签: python list python-2.7 numpy


【解决方案1】:

写一个省时的单行字不是那么容易,但这里是基于 askewchan 删除的答案的代码:

argmin = ar[::-1].argmin()
result = np.array([], dtype=bool) if ar[argmin] else ar[:len(ar)-argmin]

对于ar = np.full(1000000, True, dtype=bool),这比 Jaime 基于 Numpy 的解决方案的速度快两倍以上,然后:

  • ar[-10] = False,
  • ar[10] = False

(这是代表最好和最坏情况的两种情况)。但是,就像在 Jaime 的解决方案中一样,查找最后一个 False 的位置会使 NumPy (1.8.1) 通过 whole 数组,这是低效的。然而,原则上,Numpy 不必对 argmin() 执行此操作,因为它可以在遇到第一个 False 时停止。

在我看来,这就像使用 Numpy 中最好的执行剪切 给出最终数组的解决方案。原则上,NumPy 也可以非常有效地找到剪切位置。

【讨论】:

  • 但是,但是……这个解决方案不必要地低效! ;-) 然而,这可能是可以做到的最好的,这证明了我的观点,+1。
  • 感谢您的慷慨。我同意在当前实现argmin() 的情况下,在数组中查找最后一个False 效率低下。不过,我想它不一定非得如此,这会使这个解决方案变得高效(不会浪费时间做不必要的操作):我为此打开了一个 NumPy 问题 (github.com/numpy/numpy/issues/4659)。
【解决方案2】:

这里几乎是单行的(结果不为空的情况下一行),应该很快(只看必要的元素):

def magic_func(arr):
    try:       
        # The first element starting from the end which differs from the last one sets the limit:
        return arr[:next(index for index in xrange(len(arr)-1, -1, -1) if arr[index] == False)+1]
    except StopIteration:
        return numpy.empty_like(arr)

next() 只返回第一个元素的索引,即False。最后的+1 用于将其包含在结果中。 StopIteration 适用于所有元素都为 True 的情况。

一个真正的单线,它有点慢(对产生 true 的索引的附加测试是所有元​​素不是 False)是:

ar[:next(index for index in xrange(len(ar)-1, -2, -1) if index == -1 or ar[index] == False)+1]

它相对清晰:潜在的最后一个元素的索引被扫描,从结尾到开头,如果没有找到 False 或者一旦找到,我们就会停止。

请注意,此解决方案的缺点是在不使用 Numpy 的情况下循环遍历原始数组(如果数组末尾有许多 True 值,这不好)。与许多其他解决方案相比,它有几个优点:

  • 扫描的唯一元素是必要的元素(无需在找到第一个 False 之前查看)——但同样,这是使用较慢(非 Numpy)的方法完成的。李>
  • 结果是通过 Numpy 切片获得的,快速

因此,当数组末尾的 True 值数量较少时,此解决方案非常快

【讨论】:

    【解决方案3】:

    这对我来说有点太神奇了,但它看起来确实很好用:

    >>> ar = np.array([True, False, True, True, True])
    >>> ar[np.bitwise_or.accumulate(~ar[::-1])[::-1]]
    array([ True, False], dtype=bool)
    

    要了解发生了什么,首先我们对数组求反,将Trues 转换为Falses,反之亦然,然后我们颠倒顺序,然后我们累积对数组进行 OR 运算的结果:这将是False 直到第一个 True,之后将保持 True。反转这个数组,我们有一个布尔索引数组,它将去掉所有尾随的Trues。

    【讨论】:

    • 这不必要地低效:不需要遍历数组的所有元素。
    • 但它的运行速度是 OPs 示例的“高效”解决方案的两倍......您不妨继续投票,并在 numpy 问题的所有答案中调用“不必要的低效”80% ,因为这种类型的“低效率”是矢量化的基础。
    • 我将问题中的示例作为玩具示例。真正的问题是真正的用例是什么。例如,对于ar = np.full(1000000, True, dtype=bool); ar[-10] = False,您的解决方案比我的解决方案慢 50 倍(不是 2 倍!)。我应该明确表示我确实假设(1)该函数将用于比示例中更长的数组,以及(2)最后不太可能出现很长的 True 序列。我删除了我的反对票,因为当最终数组很小时,您的解决方案很快,而且问题并未严格排除这一点。
    • 我添加了另一个基于 Numpy 的解决方案,它可以节省两次时间:首先是在寻找切割限制时(无需遍历所有元素),然后是在创建最终数组时(通过使用我们只需要一个切片,而不是一般的布尔选择)。因此,我的纯 Numpy 解决方案的速度是这个解决方案的两倍(对于两种最佳和最坏情况)。
    【解决方案4】:

    像这样使用itertools.dropwhilenp.fromiter

    from itertools import dropwhile
    np.fromiter(dropwhile(lambda x: x, ar[::-1]), dtype=bool)[::-1]
    

    编辑

    这是更快的方法。(只需使用itertools.takewhile

    from itertools import takewhile
    ar[:-sum(1 for i in takewhile(lambda x: x, reversed(ar)))]
    

    时间:

    ar = np.array([True, False, True, True, True])
    
    #mine
    %timeit ar[:-sum(1 for i in takewhile(lambda x: x, reversed(ar)))]
    1000000 loops, best of 3: 1.84 us per loop
    
    #mine
    %timeit np.fromiter(dropwhile(lambda x: x, ar[::-1]), dtype=bool)[::-1]
    100000 loops, best of 3: 2.93 us per loop
    
    #@Jaime
    %timeit ar[np.bitwise_or.accumulate(~ar[::-1])[::-1]]
    100000 loops, best of 3: 3.63 us per loop
    
    #@askewchan
    %timeit ar[:len(ar)-np.argmin(ar[::-1])]
    100000 loops, best of 3: 6.24 us per loop
    
    #@xbb
    %timeit ar[:len(ar)-np.where(ar[::-1]==False)[0][0]] if np.where(ar[::-1]==False)[0].size>0 else ar[:0]
    100000 loops, best of 3: 7.61 us per loop
    

    PS:

    这是没有魔法的功能。

    def no_magic_func(ar):
        for i in xrange(ar.size-1, -1, -1):
            if not ar[i]:
                return ar[:i+1]
        return ar[0:0]
    

    时间:

    ar = np.array([True, False, True, True, True])
    
    %timeit no_magic_func(ar)
    1000000 loops, best of 3: 954 ns per loop
    

    【讨论】:

    • 这样逐个元素重建数组,效率相当低。
    • @EOL:是的,但是,这个怎么样? np.fromiter(dropwhile(lambda x: x, reversed(ar)), dtype=bool)[::-1]
    • 问题是一样的:fromiter() 逐个元素地构建结果数组:这比对原始数组进行切片效率低得多:ar[:…]。 (目前有两种解决方案对原始数组进行切片。)
    • @EOL:好的……我明白了。现在我找到了更快的方法...ar[:-sum(1 for i in takewhile(lambda x: x, reversed(ar)))]
    • 好主意。也许有可能超过总和:我很想知道我的(askewchan 真的)numpy.argmin() 解决方案有多快(argmin() 本质上是一种不同的计算总和的方法,但没有加法)。
    【解决方案5】:

    编辑:更新实现以处理 numpy 数组中缺少 .pop()

    def chop_array(ar, condition):
        i = len(ar)
        while (ar[i - 1] == condition and i > 0):
            i = i - 1
        return ar[0:i]
    
    chop_array([True, False, True, False, True], True)
    

    【讨论】:

    • 这是一个numpy数组,没有pop方法。
    【解决方案6】:

    这一行功能应该可以工作,但看起来很讨厌,可能效率不高,哈哈。基本上想法是找到最正确的False并返回False之前的所有值

    def magic_func(a):
        return a[:len(a)-np.where(a[::-1]==False)[0][0]] if np.where(a[::-1]==False)[0].size>0 else a[:0]
    
    >>> a = np.array([False, True, True, True, True])
    >>> magic_func(a)
    array([False], dtype=bool)
    

    【讨论】:

    • 这非常低效:where() 循环整个数组。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2016-01-06
    • 2019-09-16
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多