【问题标题】:Randomly put elements into N buckets将元素随机放入 N 个桶中
【发布时间】:2018-10-06 02:35:15
【问题描述】:

我有一个装有 100 个土豆的袋子。我需要将土豆分成 N 个桶。在每个桶中,我必须有 15 到 60 个土豆。

显然,我需要一个逐步的解决方案来将它变成代码。

目前为止我最好的方法:

最小桶数:100/60 = 1 (40) => 向上取整 => 2
最大桶数:100/15 = 6 (10) => 向下舍入 => 6

因此,您可以拥有最少 2 个、最多 6 个存储桶。现在我们选择一个随机数(因为我只需要一个解决方案,而不是所有解决方案)。

让我们选择 3。
每桶土豆:100/3 = 33 (1)
桶数:33、33、34。

现在是棘手的部分。虽然这是对原始问题的解决方案,但它对我不起作用,因为我需要数字比这更随机。在问题中,条件是 15-60,但在这里我们只得到 33-34,这对于我需要的来说太统一了。

这里的解决方案之一可能是开始在每个存储桶中添加和减去数字。可能要进行 10 次左右的迭代,但我认为必须有更好的方法来做到这一点。

有什么想法吗?

【问题讨论】:

  • 从数学的角度来看,“随机”分配将对应于可能结果的概率分布,但您似乎会对“看起来随机”的东西感到满意。预先知道桶的数量吗?桶是否不同? IE。作为解决方案,33-33-34 与 33-34-33 有何不同?听起来土豆不是很明显,所以这确实是一个关于 partition of an integer 最大加数限制的问题。

标签: algorithm math


【解决方案1】:

首先,分配所需的最少数量。在您的示例中,将 15 个放入每个存储桶中。如果您有 3 个桶,您将平均将 45 放入 3。 Remaining (R): 55. 每个桶的剩余容量 (C1,C2,C3):45.

选择一个数字 k(有关如何选择 k,请参见脚注)。如果它大于 R,则将其设置为 R (k=min(k,R) )。随机挑选一个桶。如果 Ci 小于,则 k 将 k 设置为 Ci ( k=min(k,Ci) )。现在将 k 个土豆放入桶 i。更新 R 和 Ci (R=R-k, Ci=Ci-k)。重复此操作,直到所有土豆都吃完(R=0)。

脚注:选择 k

您可以设置 k=1 或从任何适当分布中选择 k(例如:从 1 到 10 中随机选择 k)。

代码

import random
def distPotato(R, N, minP, maxP):
    C = [maxP-minP for i in range(N)]
    V = [minP for i in range(N)]
    R-=sum(V)    
    while(R>0):
        k = random.choice(range(10)) + 1
        i = random.choice(range(N))
        k = min(k,R)
        k = min(k, C[i])
        C[i]-=k
        R-=k
        V[i]+=k
    return V

distPotato(100,3,15,60)

【讨论】:

  • 这听起来像是一个解决方案,但我需要一些时间来解决这个问题:) 一个问题 - 什么是“每个存储桶的剩余容量 (C1,C2,C3):30”?
  • @NikolayDyankov。哎呀!我以为你想要每个桶里有 15-45 个土豆。现在我看到它是15-60。答案相应改变(它是最大容量 - 最小容量)
  • 嘿,我又试了一下,做了一个 jsfiddle - jsfiddle.net/6pMeE ...我得到随机数,但最后总和不是 100。我试图将尽可能多的输出记录到控制台。请检查一下。
  • @NikolayDyankov 查看python代码。我已经测试过了,它可以工作。
  • 一个重要的警告!这很可能会以不同于预期的方式分布元素。这会将每个元素视为一个单独的元素,因此例如将 2 个土豆放在 2 个袋子中的问题处理为有 4 种可能的解决方案(袋子 1 中的两个土豆,袋子 2 中的两个土豆,或第一个中的土豆 A 或第一个中的土豆 B),而实际上只有 3 种可能性,因为土豆是无法区分的。 - 这将过度代表平均分布的案例,而低于代表堆叠的案例。
【解决方案2】:

我想象在肉店里有一段牛肉,屠夫被要求在尽可能短的时间内把它切成 N 块随机大小。

他的第一个动作可能是随机敲击肉,确保在切 N-1 次之前不要将其切得太靠近他之前的切片或边缘。

为了您的目的,这块牛肉可以是一排土豆。

所以把它翻译成伪代码:

initiate a list of chosen potatoes to empty
while you have chosen less than N-1 potatos
    pick a potato
    if the potato is less than 15 potatoes away from a side
        continue
    if the potato is less than 15 potatoes away from a chosen potato
        continue
    add this potato to the chosen list
sort the chosen list
split the line of potatoes using the chosen potatoes as delimeters

【讨论】:

  • 这个算法不会将线分成 15 等份吗?
  • 不,检查防止块小于 15,但允许它大于 15。它唯一没有做的是将其限制为 60 个土豆,我正在尝试想办法解决这部分问题。
【解决方案3】:

我建议只是在桶中随机装满 15 到 60 个土豆,直到剩下的土豆少于 15 个。然后拿起你装满的最后一个桶,试着把剩下的土豆放进那个桶里。如果低于60,那就太好了!如果超过 60,则将桶分成两半。

假设您这样做了几次,最终得到了 74 个土豆。你现在用一个随机数的土豆装满一个桶。假设它是 60 个。您现在还剩下 14 个土豆,对于另一个桶来说太少了,但对于您的最后一个桶来说太多了。很简单,倒出前一个桶(74 个土豆,再次),然后做两个桶,每个桶 37 个。

问题是你的最后两个桶不会像其他桶那样随机(或者至少不是“随机”,如果你愿意的话),但是因为你不能在一个桶里放少于 14 个土豆,所以对此你无能为力。

【讨论】:

  • 我没有弄错 - 我们不能有 7 个桶,因为 15 个桶。 * 6 个桶 = 90,我们还剩下 10 个,这对于一个桶来说是不够的。因此,最多有 6 个桶,这就是我将数字四舍五入的原因。如果我需要从前一个桶中减去,我们就会遇到问题,结果是 15。
  • 您没有从存储桶中减去。如果最后一个桶 + 余数为 60 或更少,您只需将余数转储到桶中。因此,只有在最后一个桶和其余部分之间至少有 61 个土豆时,您才会拆分桶。另外,我明白你为什么现在说 6 个桶了。
【解决方案4】:

首先,请注意,您可以稍微简化您的问题。而不是“每个桶需要包含 15 到 60 个土豆并且桶中的总数应该是 100”,您可以重述为“我需要 0 到 45 (60 - 15) 之间的 n 个数字,这样这些数字的总和为 100 - n x 15"。换句话说,用 15 个土豆填满你所有的桶,现在你正试图决定如何使用剩余的土豆来填满桶中剩余的空间。

假设你有 n 个桶要装,还有 p 个土豆。生成一个介于 0 到 45 之间的随机数 r,你现在剩下 p - r 个土豆。你现在能填满剩下的 (n-1) 个桶吗?

  • 如果 (p - r)
  • 如果 (p - r) > 45 * (n - 1),这是不可行的:您的马铃薯太多了,并且必须超出您的桶的容量。
  • 否则,您知道如果将 r 个土豆放入当前存储桶中,您仍然可以为下一个存储桶找到解决方案。

您可以将其写为递归解决方案:对于每个连续的桶,生成一个随机数的土豆,直到该数字不会阻止您在下一步找到解决方案。

下面是一个非常粗略的 F# 实现,显然可以对其进行改进/优化,但说明了算法的要点。 allocation 是一个 int 列表,其中包含已分配给存储桶的数量,qty 是剩余要分配的土豆数量,buckets 是剩余存储桶的数量,min,max 是存储桶的容量。例如,有 100 个土豆和 3 个桶,您可以调用 solve 100 3 15 60。

产生的分配应该是“尽可能随机”,即它应该以相同的概率产生任何可能的分配,但有一个警告:我假设桶的顺序无关紧要。鉴于该算法根据到目前为止分配的内容分配剩余的土豆,根据路径,列表的“头部”受到限制,并且分配可能会在第一个桶上分配更多。如果您想进行“真正的”随机分配,则需要在分配完成后对存储桶进行洗牌。

希望这会有所帮助!

let rng = new System.Random() 

let rec allocate allocation qty buckets min max =
      match buckets with
      | 0 -> allocation
      | 1 -> qty :: allocation
      | _ ->
         let candidate = rng.Next(min, max + 1) // try a number in [min,max]
         let rest = qty - candidate
         if rest < (buckets - 1) * min // not enough left
         then allocate allocation qty buckets min max // try again
         else if rest > (buckets - 1) * max // too much left
         then allocate allocation qty buckets min max // try again
         else allocate (candidate :: allocation) rest (buckets - 1) min max

let solve qty buckets min max =
   if min * buckets > qty then failwith "unfeasible"
   if max * buckets < qty then failwith "unfeasible"
   allocate [] qty buckets min max

【讨论】:

    【解决方案5】:

    ElKamina 的回答可能是正确的,但对于数学上的挑战,这里是另一种土豆排序算法。

    from random import randint, choice
    
    def random_distribution(num_potatoes, num_buckets, min_num_potatoes, max_num_potatoes):
        """
        Distributes a set number of potatoes randomly across a number of buckets
        such that each bucket has a number of potatoes within a certain range.
        """
        # Cache the starting value
        _num_potatoes = num_potatoes
    
        # Create some buckets to put potatoes in, initial value 0
        buckets = [0 for x in range(num_buckets)]
    
        # Get a list of which buckets are not too empty or too full--
        # All of them are valid choices at this point
        indices = [i for (i, value) in enumerate(buckets)]
    
        # Distribution loop-- continue until we run out of potatoes or the
        # buckets are too full to keep adding to them.
        while indices and num_potatoes and sum(buckets) <= (max_num_potatoes * num_buckets):
    
            # Pick a random bucket from the list of eligible buckets
            index = choice(indices)
    
            # Add a potato to it
            buckets[index] = buckets[index] + 1
            num_potatoes = num_potatoes - 1
    
            # Refresh the list of eligible buckets
            indices = [i for (i, value) in enumerate(buckets) if (value < min_num_potatoes) or (value < max_num_potatoes)]
    
        print('Of %(potatoes)i potatoes distributed across %(buckets)i buckets, each having at least %(min)i and at most %(max)i, our spread looks like %(spread)s (total bucketed: %(sum)i).' % {
            'potatoes': _num_potatoes,
            'buckets': num_buckets,
            'min': min_num_potatoes,
            'max': max_num_potatoes,
            'spread': buckets,
            'sum': sum(buckets)
        })
        return buckets
    
    for x in range(10):
        random_distribution(100, 3, 15, 60)
    

    示例输出:

    Of 100 potatoes distributed across 3 buckets, each having at least 15 and at most 60, our spread looks like [35, 37, 28] (total bucketed: 100).
    Of 100 potatoes distributed across 3 buckets, each having at least 15 and at most 60, our spread looks like [29, 36, 35] (total bucketed: 100).
    Of 100 potatoes distributed across 3 buckets, each having at least 15 and at most 60, our spread looks like [32, 27, 41] (total bucketed: 100).
    Of 100 potatoes distributed across 3 buckets, each having at least 15 and at most 60, our spread looks like [33, 33, 34] (total bucketed: 100).
    Of 100 potatoes distributed across 3 buckets, each having at least 15 and at most 60, our spread looks like [32, 32, 36] (total bucketed: 100).
    Of 100 potatoes distributed across 3 buckets, each having at least 15 and at most 60, our spread looks like [37, 28, 35] (total bucketed: 100).
    Of 100 potatoes distributed across 3 buckets, each having at least 15 and at most 60, our spread looks like [32, 27, 41] (total bucketed: 100).
    Of 100 potatoes distributed across 3 buckets, each having at least 15 and at most 60, our spread looks like [39, 27, 34] (total bucketed: 100).
    Of 100 potatoes distributed across 3 buckets, each having at least 15 and at most 60, our spread looks like [32, 30, 38] (total bucketed: 100).
    Of 100 potatoes distributed across 3 buckets, each having at least 15 and at most 60, our spread looks like [32, 30, 38] (total bucketed: 100).
    

    在你给定的例子中,如果你想要更多的价值差异,你需要从更少的土豆开始或使用更多的桶。

    【讨论】:

      猜你喜欢
      • 2016-01-28
      • 2015-11-09
      • 2011-06-09
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2016-09-09
      • 2012-03-09
      相关资源
      最近更新 更多