这个循环的函数式替换是什么?

What is the funtional-style replacement of this loop?

我有以下循环:

import random
distribution = []
sum = 0
while True:
  item = random.randint(1, 8)
  if sum + item == 812:
    distribution.append(item)
    break
  elif sum + item > 812:
    distribution.append(812 - sum)
    break
  else:
    distribution.append(item)
    sum = sum + item

这里的想法是创建一个包含项目的列表,其中每个项目都是 1 到 8 之间的随机数,列表中项目的总和必须正好是 812。挑战是,最后一个项目可以随机过冲,所以简单的停止条件是行不通的。

我正在绞尽脑汁思考如何在功能上表达这一点,但到目前为止还是一片空白。有什么想法吗?

一种解决方案是生成不交叉的随机数 812。例如,当您的总和为 806 时,您只能生成 [1, 6].

之间的数字

因此您应该将随机数生成器更改为

item = random.randint(1, min(8, 812 - sum))

你的整个程序也变得更短了。 注意不要使用变量名sum,因为它是一个内置函数

import random
distribution = []
list_sum = 0
while list_sum < 812:
    item = random.randint(1, min(8, 812 - list_sum ))
    distribution.append(item)
    list_sum += item

这正是您的 if 语句所做的 - 只是用 min:

重写了
MAX = 812
distribution = []
s = 0
while s < MAX:
    item = min(MAX - s, random.randint(1, 8))
    distribution.append(item)
    s += item

请注意,这会略微扭曲您的分布。最后一个数字将始终小于其他数字...隐藏一点的一种方法是之后对列表进行洗牌。

为了获得真正均匀的分布,您必须重新开始,以防超出范围...

一个与您相似的答案

import random
distribution = []
lsum = 0
while True:
    item = random.randint(1, 8)
    lsum = lsum + item
    distribution.append(item)
    if lsum > 812:
        lsum = lsum - item
        distribution.pop()
    if lsum == 812:
        break

因为您知道添加的项目的最大值,所以您是否可以确定性地在最后一个项目处于该最大值的范围内时以最后一个项目结束?在这种情况下,您可以执行以下操作:

import random

def creat_random_list(total_sum, min_value=1, max_value=8):
    distribution = []
    current_sum = 0
    done = False
    while not done:
        if total_sum - current_sum <= max_value:
            new_item = total_sum - current_sum
            done = True
        else:
            new_item = random.randint(min_value, max_value)
        distribution.append(new_item)
        current_sum += new_item
    return distribution


random_numers = creat_random_list(total_sum=812)
print(f"sum {sum(random_numers)} of list {random_numers}")

请注意,我已将您的变量 sum 重命名为 current sum,因为 sum 是一个内置函数,所以不是一个好的变量名可以选择

结果如下:

sum 812 of list [7, 5, 7, 4, 7, 7, 7, 1, 2, 6, 6, 4, 5, 3, 1, 4, 4, 2, 8, 7, 5, 5, 5, 5, 1, 1, 1, 4, 5, 5, 1, 1, 8, 8, 6, 5, 5, 5, 8, 2, 3, 7, 8, 6, 2, 6, 7, 4, 7, 7, 8, 7, 1, 4, 7, 2, 2, 6, 4, 4, 3, 4, 2, 6, 3, 3, 3, 4, 1, 3, 6, 6, 8, 5, 6, 3, 3, 7, 6, 8, 5, 3, 5, 4, 1, 7, 6, 5, 4, 1, 7, 1, 5, 1, 7, 3, 4, 2, 3, 3, 1, 4, 6, 5, 4, 1, 1, 1, 3, 7, 1, 3, 8, 8, 7, 4, 4, 8, 8, 8, 5, 6, 5, 6, 8, 5, 7, 2, 2, 8, 5, 1, 5, 4, 3, 2, 1, 1, 8, 8, 8, 8, 2, 1, 1, 4, 8, 4, 1, 2, 2, 2, 8, 5, 6, 5, 4, 1, 3, 4, 3, 3, 2, 2, 5, 7, 8, 1, 1, 8, 2, 7, 7, 2, 5, 1, 7, 7, 2, 3, 7, 7]

我正在回答这个问题的标题:

What is the functional-style replacement of this loop?

def random_list(sum_rest):
    item = random.randint(1, min(8, sum_rest))
    return ([] if sum_rest - item == 0 else random_list(sum_rest - item)) + [item]

这个解决方案基于@DollarAkshay 提出的改进,一旦你明白了,它就非常简单:我正在递归调用 random_list,但是随机减少 sum_rest 参数的项目已选择。

您可以通过调用

获取列表
random_list(812)

一种规范(且实用)的方法是生成序列,当总和至少为 812 时停止,如果总和高于 812 则重新开始。这称为“拒绝采样”。这可以保证您公平地选择序列,而不是扭曲随机性。

您预计大约八分之一的样本是好的,因此您希望在找到好的样本之前生成 8 个样本。

import random

def seqsum(n):
    while True:
        S = 0
        s = []
        while S < n:
            s.append(random.randrange(8)+1)
            S += s[-1]
        if S == n:
            return s

for _ in range(10):
    print(seqsum(812))

显然,这比倾斜序列的最后一个或多个元素以强制求和要慢,但这是避免倾斜的 trade-off。

这里有一些代码使用@hiro-protagonist 和@DollarAkshay 的方法打印出序列最后一个元素的分布。它显示了大小为 10000 的样本中数字 1 到 8 的频率,打印为数组。您可以在这些方法中看到对低数字的严重偏斜。

def seqhiro(n):
    S = 0
    s = []
    while S < n:
        s.append(min(812 - S, random.randint(1, 8)))
        S += s[-1]
    return s

def seqdollar(n):
    S = 0
    s = []
    while S < n:
        s.append(random.randint(1, min(8, 812 - S)))
        S += s[-1]
    return s

def sample(m):
    last = [0] * 9
    for _ in range(10000):
        s = m(812)
        last[s[-1]] += 1
    return last[1:]

print('rejection', sample(seqsum))
print('hiro', sample(seqhiro))
print('dollar', sample(seqdollar))

输出:

rejection [1234, 1308, 1280, 1178, 1246, 1247, 1257, 1250]
hiro [2226, 1904, 1727, 1319, 1077, 895, 560, 292]
dollar [5220, 1803, 938, 595, 475, 393, 319, 257]

Dollar Akshay 的方法产生的最终数字 1 大约是 8 的 20 倍,而 hiro 的方法更好,但 1 的出现频率仍然是最终输入 8 的大约 7.5 倍。

此答案的方法(拒绝抽样)为这个 10000 次运行的样本中的所有最终数字提供了大致相等的分布。

显然可以隐藏快速方法的偏差,例如在返回数组之前对数组进行洗牌,但此答案中统计上正确的方法没有什么可隐藏的。