【发布时间】:2019-06-18 13:38:51
【问题描述】:
我试图找到一种优雅的方式来实现将金额分配到 python 中的给定插槽集。
例如:
7 个橙子分布在 4 个盘子上会返回:
[2, 2, 2, 1]
4 个盘子中的 10 个橙子将是:
[3, 3, 2, 2]
【问题讨论】:
标签: python
我试图找到一种优雅的方式来实现将金额分配到 python 中的给定插槽集。
例如:
7 个橙子分布在 4 个盘子上会返回:
[2, 2, 2, 1]
4 个盘子中的 10 个橙子将是:
[3, 3, 2, 2]
【问题讨论】:
标签: python
从概念上讲,您要做的是计算 7 // 4 = 1 和 7 % 4 = 3。这意味着所有盘子都有 1 个完整的橙色。 3 的其余部分告诉您,其中三个盘子会得到一个额外的橙色。
divmod 内置函数是同时获取两个数量的快捷方式:
def distribute(oranges, plates):
base, extra = divmod(oranges, plates)
return [base + (i < extra) for i in range(plates)]
用你的例子:
>>> distribute(oranges=7, plates=4)
[2, 2, 2, 1]
为了完整起见,您可能需要检查oranges 是否为非负数,plates 是否为正数。鉴于这些条件,这里有一些额外的测试用例:
>>> distribute(oranges=7, plates=1)
[7]
>>> distribute(oranges=0, plates=4)
[0, 0, 0, 0]
>>> distribute(oranges=20, plates=2)
[10, 10]
>>> distribute(oranges=19, plates=4)
[5, 5, 5, 4]
>>> distribute(oranges=10, plates=4)
[3, 3, 2, 2]
【讨论】:
您想查看Bresenham's algorithm 以绘制线条(即,在 Y 范围内尽可能“直接”分布 X 像素;将其应用于分布问题很简单)。
这是我在here找到的一个实现:
def get_line(start, end):
"""Bresenham's Line Algorithm
Produces a list of tuples from start and end
>>> points1 = get_line((0, 0), (3, 4))
>>> points2 = get_line((3, 4), (0, 0))
>>> assert(set(points1) == set(points2))
>>> print points1
[(0, 0), (1, 1), (1, 2), (2, 3), (3, 4)]
>>> print points2
[(3, 4), (2, 3), (1, 2), (1, 1), (0, 0)]
"""
# Setup initial conditions
x1, y1 = start
x2, y2 = end
dx = x2 - x1
dy = y2 - y1
# Determine how steep the line is
is_steep = abs(dy) > abs(dx)
# Rotate line
if is_steep:
x1, y1 = y1, x1
x2, y2 = y2, x2
# Swap start and end points if necessary and store swap state
swapped = False
if x1 > x2:
x1, x2 = x2, x1
y1, y2 = y2, y1
swapped = True
# Recalculate differentials
dx = x2 - x1
dy = y2 - y1
# Calculate error
error = int(dx / 2.0)
ystep = 1 if y1 < y2 else -1
# Iterate over bounding box generating points between start and end
y = y1
points = []
for x in range(x1, x2 + 1):
coord = (y, x) if is_steep else (x, y)
points.append(coord)
error -= abs(dy)
if error < 0:
y += ystep
error += dx
# Reverse the list if the coordinates were swapped
if swapped:
points.reverse()
return points
【讨论】:
疯狂物理学家的答案是完美的。但是,如果您想将橙子均匀分布在盘子上(例如,2 3 2 3 与 2 2 3 3 在 7 个橙子和 4 个盘子的示例中),这是一个简单的想法。
以 31 个橙子和 7 个盘子为例。
第 1 步:您像疯狂的物理学家一样开始使用欧几里得除法:31 = 4*7 + 3。每个盘子里放 4 个橙子,剩下的 3 个保留。
[4, 4, 4, 4, 4, 4, 4]
第 2 步:现在,您的盘子比橙子多,这完全不同:您必须在橙子之间分配盘子。您还剩 7 个盘子和 3 个橙子:7 = 2*3 + 1。每个橙子有 2 个盘子(你还有一个盘子,但没关系)。让我们称之为2leap。从leap/2 开始会很漂亮:
[4, 5, 4, 5, 4, 5, 4]
这很简单。 34 个橙子和 7 个盘子会发生什么?
第 1 步:您仍然像疯狂的物理学家一样从欧几里得除法开始:34 = 4*7 + 6。每个盘子里放 4 个橙子,剩下的 6 个留着。
[4, 4, 4, 4, 4, 4, 4]
第 2 步:现在,您还剩下 7 个盘子和 6 个橙子:7 = 1*6 + 1。每个橙子会有一个盘子。但是等等..我没有 7 个橙子!别怕,我借给你一个苹果:
[5, 5, 5, 5, 5, 5, 4+apple]
但如果你想要一些统一性,你必须把那个苹果放在别处!为什么不尝试在第一种情况下像橙子一样分发苹果呢? 7 个盘子,1 个苹果:7 = 1*7 + 0。 leap是7,从leap/2开始,也就是3:
[5, 5, 5, 4+apple, 5, 5, 5]
第 3 步。你欠我一个苹果。请把我的苹果还给我:
[5, 5, 5, 4, 5, 5, 5]
总结一下:如果剩下的橙子很少,则分配高峰,否则分配低谷。 (免责声明:我是这个“算法”的作者,我希望它是正确的,但如果我错了,请纠正我!)
废话不多说,代码:
def distribute(oranges, plates):
base, extra = divmod(oranges, plates) # extra < plates
if extra == 0:
L = [base for _ in range(plates)]
elif extra <= plates//2:
leap = plates // extra
L = [base + (i%leap == leap//2) for i in range(plates)]
else: # plates/2 < extra < plates
leap = plates // (plates-extra) # plates - extra is the number of apples I lent you
L = [base + (1 - (i%leap == leap//2)) for i in range(plates)]
return L
一些测试:
>>> distribute(oranges=28, plates=7)
[4, 4, 4, 4, 4, 4, 4]
>>> distribute(oranges=29, plates=7)
[4, 4, 4, 5, 4, 4, 4]
>>> distribute(oranges=30, plates=7)
[4, 5, 4, 4, 5, 4, 4]
>>> distribute(oranges=31, plates=7)
[4, 5, 4, 5, 4, 5, 4]
>>> distribute(oranges=32, plates=7)
[5, 4, 5, 4, 5, 4, 5]
>>> distribute(oranges=33, plates=7)
[5, 4, 5, 5, 4, 5, 5]
>>> distribute(oranges=34, plates=7)
[5, 5, 5, 4, 5, 5, 5]
>>> distribute(oranges=35, plates=7)
[5, 5, 5, 5, 5, 5, 5]
【讨论】:
另见more_itertools.distribute,第三方工具及其source code。
代码
在这里,我们将m 的项目一个接一个地分配到n 箱中,并对每个箱进行计数。
import more_itertools as mit
def sum_distrib(m, n):
"""Return an iterable of m items distributed across n spaces."""
return [sum(x) for x in mit.distribute(n, [1]*m)]
演示
sum_distrib(10, 4)
# [3, 3, 2, 2]
sum_distrib(7, 4)
# [2, 2, 2, 1]
sum_distrib(23, 17)
# [2, 2, 2, 2, 2, 2, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1]
示例
这个想法类似于在玩家之间分配一副牌。这是Slapjack的初始游戏
import random
import itertools as it
players = 8
suits = list("♠♡♢♣")
ranks = list(range(2, 11)) + list("JQKA")
deck = list(it.product(ranks, suits))
random.shuffle(deck)
hands = [list(hand) for hand in mit.distribute(players, deck)]
hands
# [[('A', '♣'), (9, '♠'), ('K', '♣'), (7, '♢'), ('A', '♠'), (5, '♠'), (2, '♠')],
# [(6, '♣'), ('Q', '♠'), (5, '♢'), (5, '♡'), (3, '♡'), (8, '♡'), (7, '♣')],
# [(7, '♡'), (9, '♢'), (2, '♢'), (9, '♡'), (7, '♠'), ('K', '♠')],
# ...]
卡片“在所有 [8] 名玩家之间尽可能平均地分配”:
[len(hand) for hand in hands]
# [7, 7, 7, 7, 6, 6, 6, 6]
【讨论】:
不确定这是如何工作的。但它返回完全相同的结果
a = 23
b = 17
s = pd.Series(pd.cut(mylist, b), index=mylist)
s.groupby(s).size().values
【讨论】: