通过连续的正数或负数的总和来缩短整数列表

时间:2018-02-06 21:18:02

标签: python list

我想编写一个函数来处理整数列表,最好的方法就是显示一个例子:

input [0,1,2,3, -1,-2,-3, 0,1,2,3, -1,-2,-3] will return [6,-6,6,-6]

我这里有一个实际可行的草案:

def group_pos_neg_list(nums):
    p_nums = []

    # to determine if the first element >=0 or <0
    # create pos_combined and neg_combined as a list to check the length in the future
    if nums[0] >= 0:
        pos_combined, neg_combined = [nums[0]], []
    elif nums[0] < 0:
        pos_combined, neg_combined = [], [nums[0]]

    # loop over each element from position 1 to the end
    # accumulate pos num and neg nums and set back to 0 if next element is different
    index = 1
    while index < len(nums):
        if nums[index] >= 0 and nums[index-1] >= 0: # both posivite
            pos_combined.append(nums[index])
            index += 1
        elif nums[index] < 0 and nums[index-1] < 0: # both negative
            neg_combined.append(nums[index])
            index += 1
        else:
            if len(pos_combined) > 0:
                p_nums.append(sum(pos_combined))
                pos_combined, neg_combined = [], [nums[index]]
            elif len(neg_combined) > 0:
                p_nums.append(sum(neg_combined))
                pos_combined, neg_combined = [nums[index]], []
            index += 1

    # finish the last combined group
    if len(pos_combined) > 0:
        p_nums.append(sum(pos_combined))
    elif len(neg_combined) > 0:
        p_nums.append(sum(neg_combined))

    return p_nums

但我对此并不满意,因为它看起来有点复杂。 特别是代码的重复部分:

if len(pos_combined) > 0:
    p_nums.append(sum(pos_combined))
    pos_combined, neg_combined = [], [nums[index]]
elif len(neg_combined) > 0:
    p_nums.append(sum(neg_combined))
    pos_combined, neg_combined = [nums[index]], []

我必须写两遍,因为最后一组整数不会计入循环中,所以需要额外的步骤。

有没有简化这个?

3 个答案:

答案 0 :(得分:4)

使用groupby

不需要那么复杂:我们可以先groupby签名,然后我们就可以计算总和,所以:

from itertools import groupby

[sum(g) for _, g in groupby(data, lambda x: x >= 0)]

然后产生:

>>> from itertools import groupby
>>> data = [0,1,2,3, -1,-2,-3, 0,1,2,3, -1,-2,-3]
>>> [sum(g) for _, g in groupby(data, lambda x: x >= 0)]
[6, -6, 6, -6]

所以groupby产生具有“键”的元组(我们用lambda计算的部分),以及“burst”的迭代(具有相同键的元素的连续子序列)。我们只对后者g感兴趣,然后计算sum(g)并将其添加到列表中。

自定义算法

我们也可以使用:

编写我们自己的版本
swap_idx = [0]
swap_idx += [i+1 for i, (v1, v2) in enumerate(zip(data, data[1:]))
             if (v1 >= 0) != (v2 >= 0)]
swap_idx.append(None)

our_sums = [sum(data[i:j]) for i, j in zip(swap_idx, swap_idx[1:])]

这里我们首先构造一个列表swap_idx,用于存储signum更改元素的索引。因此,对于您的示例代码:

>>> swap_idx
[0, 4, 7, 11, None]

代码会明确添加0None。现在我们确定了符号发生变化的点,我们可以将这些子序列加在一起,sum(data[i:j])。因此,我们使用zip(swap_idx, swap_idx[1:])来获得两个连续的索引,因此我们可以将该切片加在一起。

更详细的版本

以上内容不是很易读:是的,它有效,但需要一些推理。我们还可以生成更详细的版本,并使其更通用,例如:

def groupby_aggregate(iterable, key=lambda x: x, aggregate=list):
    itr = iter(iterable)
    nx = next(itr)
    kx = kxcur = key(nx)
    current = [nx]
    try:
        while True:
            nx = next(itr)
            kx = key(nx)
            if kx != kxcur:
                yield aggregate(current)
                current = [nx]
                kxcur = kx
            else:
                current.append(nx)
    except StopIteration:
         yield aggregate(current)

我们可以像以下一样使用它:

list(groupby_aggregate(data, lambda x: x >= 0, sum))

答案 1 :(得分:3)

您可以使用itertools.groupby,使用一个键按所有大于或等于零的值进行分组:

import itertools
s = [0,1,2,3, -1,-2,-3, 0,1,2,3, -1,-2,-3]
new_s = [sum(b) for a, b in itertools.groupby(s, key=lambda x: x >=0)]

输出:

[6, -6, 6, -6]

答案 2 :(得分:2)

以下是一种不使用任何外部导入的方法,仅使用reduce()

def same_sign(a, b):
    """Returns True if a and b have the same sign"""
    return (a*b>0) or (a>=0 and b>=0)

l = [0,1,2,3, -1,-2,-3, 0,1,2,3, -1,-2,-3] 
reduce(
    lambda x, y: (x+y if same_sign(x,y) else [x, y]) if not isinstance(x, list) else x[:-1] + [x[-1] + y] if same_sign(x[-1],y) else x + [y],
    l
)
#[6, -6, 6, -6]

<强>解释

这有点难以解释,但我会尝试。

来自docs来电reduce()将:

  

将两个参数的函数累加到可迭代的项目中,从左到右

在这种情况下,我从列表中取两个值(x和y)并执行以下操作:

  • 如果x不是list
    • 如果x和y具有相同的符号(product&gt; = 0),请将它们相加
    • 否则返回列表[x, y]
  • 如果x是list,则只修改x的最后一个元素。
    • 如果符号匹配,请添加y。
    • 否则将新元素附加到列表x

注意

你可能不应该这样做,因为代码很难阅读和理解。我只想表明它是可能的。

<强>更新

上述相同代码的更易读的版本:

def same_sign(a, b):
    """Returns True if a and b have the same sign"""
    return (a*b>0) or (a>=0 and b>=0)

l = [0,1,2,3, -1,-2,-3, 0,1,2,3, -1,-2,-3] 
def reducer(x, y):
    if isinstance(x, list):
        if same_sign(x[-1], y):
            return x[:-1] + [x[-1] + y]
        else:
            return x + [y]
    else:
        if same_sign(x, y):
            return x+y
        else:
            return [x, y]
reduce(reducer, l)
#[6, -6, 6, -6]