【问题标题】:Distribute an integer amount by a set of slots as evenly as possible通过一组槽尽可能均匀地分配整数数量
【发布时间】:2019-06-18 13:38:51
【问题描述】:

我试图找到一种优雅的方式来实现将金额分配到 python 中的给定插槽集。

例如:

7 个橙子分布在 4 个盘子上会返回:

[2, 2, 2, 1]

4 个盘子中的 10 个橙子将是:

[3, 3, 2, 2]

【问题讨论】:

    标签: python


    【解决方案1】:

    从概念上讲,您要做的是计算 7 // 4 = 17 % 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]
    

    【讨论】:

    • @Lserni。这种方法保证最大和最小板之间的差异最多为1。您对均匀度有什么额外的标准?
    • 虽然我同意剩余部分的分布可以从“向左推”得到改善,但 OP 并没有让我相信这是他们想要的。
    • 实际上,OP 的第二个示例表明您的解决方案(除了简单性之外)是产生预期结果的解决方案(之前没有注意到)。
    【解决方案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
    

    【讨论】:

    • 对于更明确指定的问题,这将是更好的答案。
    • 疯狂物理学家:不确定我是否同意你的看法。优雅是一个有点主观的标准,但对于外行来说,这并不那么优雅:)
    • @gmagno。这不仅仅是关于优雅。该解决方案实际上分配垃圾箱的方式与我的不同。如果有一个额外的要求来保持峰之间的分布尽可能均匀,我的根本不会削减它。但是,是的,这更麻烦,我猜 OP 想要初学者版本。
    【解决方案3】:

    疯狂物理学家的答案是完美的。但是,如果您想将橙子均匀分布在盘子上(例如,2 3 2 32 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 + 0leap是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]
    

    【讨论】:

      【解决方案4】:

      另见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]
      

      【讨论】:

        【解决方案5】:

        不确定这是如何工作的。但它返回完全相同的结果

        a = 23
        b = 17
        s = pd.Series(pd.cut(mylist, b), index=mylist)
        s.groupby(s).size().values
        

        【讨论】:

          猜你喜欢
          • 2012-07-02
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          相关资源
          最近更新 更多