首先,请注意,您的任务至少在两个方面没有明确说明:
- 未指定生成值的允许范围。特别是,您没有指定结果是否可能包含负整数。
- 未指定生成值的所需distribution。
通常,如果未指定,人们可能会假设在方程的一组可能解上的 uniform distribution 是预期的(因为它是 in a certain sense,是给定集合上最随机的可能分布)。但是(离散)均匀分布只有在解集是有限的情况下才有可能,如果结果范围不受限制,则不会。 (特别是,如果 (a, b, c) 是一个解,那么 (a, b + 3k, c - 5k) 对于任意整数 k。 ) 因此,如果我们将任务解释为要求无限范围的均匀分布,那实际上是不可能的!
另一方面,如果我们被允许选择任何分布和范围,任务就变得微不足道了:只要让生成器总是返回a = -n,b = n,c = n。显然这是方程的解(因为 -7n + 5n + 3n = (-7 + 5 + 3)n = 1n),并且将所有概率质量分配给单个点的退化分布仍然是有效的概率分布!
如果您想要一个稍微不那么简并的解决方案,您可以选择一个随机整数 k(使用您选择的任何分布)并返回 a = -n , b = n + 3k, c = n − 5k。如上所述,这也是任何 k 方程的解。当然,这个分布还是有点退化,因为 a 的值是固定的。
如果你想让所有返回值至少有点随机,你也可以选择一个随机的 h 并返回 a = -n + h, b = n - 2h + 3k 和 c = n + h - 5k。同样,这保证是任何 h 和 k 的有效解,因为它清楚地满足 h = k 的方程 = 0,而且很容易看出增加或减少 h 或 k 将使等式左侧的值保持不变。
其实可以证明,这种方法可以生成方程的所有种可能的解,并且每个解都会对应一个唯一的(h,k) 对! (看到这一点的一种相当直观的方法是在 3D 空间中绘制解,并观察它们在 2D 平面上形成规则的点阵,并且向量 (+1, -2, +1) 和 (0, + 3, -5) 跨越这个格子。)如果我们从某个(至少在理论上)为每个整数分配非零概率的分布中选择 h 和 k,那么我们将有一个非零概率返回任何有效的解决方案。因此,至少对于任务的某种合理解释(无限范围,任何具有完整support 的分布),以下代码应solve the task efficiently:
from random import gauss
def random_solution(n):
h = int(gauss(0, 1000)) # any distribution with full support on the integers will do
k = int(gauss(0, 1000))
return (-n + h, n - 2*h + 3*k, n + h - 5*k)
如果可能值的范围受到限制,问题就会变得有点棘手。积极的一面是,如果所有值都在以下(或以上)范围内,则可能的解集是有限的,因此存在均匀分布。另一方面,有效地对这种均匀分布进行采样并非易事。
您自己使用过的一种可能方法是首先生成所有可能的解决方案(假设它们的数量有限),然后从解决方案列表中进行抽样。我们可以像这样相当有效地生成解决方案:
- 找出方程可能有解的a的所有可能值,
- 对于每一个这样的 a,找出所有可能的 b 值,它们仍然有解决方案,
- 对于每个这样的 (a, b) 对,求解 c 的方程并检查它是否有效(即指定范围),以及
- 如果是,则将 (a, b, c) 添加到解决方案集中。
棘手的部分是第 2 步,我们要计算可能的 b 值的范围。为此,我们可以利用以下观察结果:对于给定的 a,将 c 设置为其最小允许值并求解方程给出 的上限b(反之亦然)。
特别是分别求解a、b和c的方程,我们得到:
-
a = (n - 5b - 3c) / 7
-
b = (n - 7a - 3c) / 5
-
c = (n - 7a - 5b) / 3
给定某些值的下限,我们可以使用这些解决方案来计算其他值的对应上限。例如,以下代码将有效地生成所有非负解(如果需要,可以轻松修改为使用 0 以外的下限):
def all_nonnegative_solutions(n):
a_min = b_min = c_min = 0
a_max = (n - 5*b_min - 3*c_min) // 7
for a in range(a_min, a_max + 1):
b_max = (n - 7*a - 3*c_min) // 5
for b in range(b_min, b_max + 1):
if (n - 7*a - 5*b) % 3 == 0:
c = (n - 7*a - 5*b) // 3
yield (a, b, c)
然后我们可以将解决方案存储在列表或元组中,sample from that list:
from random import choice
solutions = tuple(all_nonnegative_solutions(30))
a, b, c = choice(solutions)
附言。显然 Python 的 random.choice 不够聪明,无法使用 reservoir sampling 从任意迭代中采样,因此我们确实需要存储完整的解决方案列表,即使我们只想从中采样一次。或者,当然,我们总是可以implement our own sampler:
def reservoir_choice(iterable):
r = None
n = 0
for x in iterable:
n += 1
if randrange(n) == 0:
r = x
return r
a, b, c = reservoir_choice(all_nonnegative_solutions(30))
顺便说一句,我们可以通过观察 (n - 7*a - 5*b) % 3 == 0 条件(检查 c = (n - 7 a - 5b) / 3 是一个整数,因此一个有效的解) 对于 b 的每三个值都为真。因此,如果我们首先计算出满足给定 a 条件的 b 的最小值(这可以通过一点modular arithmetic 来完成),我们可以迭代在 b 上,从该最小值开始步长为 3 并完全跳过整除性检查。我将把实现优化留作练习。