我想编写一个函数来处理整数列表,最好的方法就是显示一个例子:
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]], []
我必须写两遍,因为最后一组整数不会计入循环中,所以需要额外的步骤。
有没有简化这个?
答案 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]
代码会明确添加0
和None
。现在我们确定了符号发生变化的点,我们可以将这些子序列加在一起,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)并执行以下操作:
list
:
[x, y]
list
,则只修改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]