【问题标题】:How can I generate three random integers that satisfy some condition? [closed]如何生成满足某些条件的三个随机整数? [关闭]
【发布时间】:2021-01-26 02:56:32
【问题描述】:

我是一名编程初学者,我正在寻找如何生成三个满足条件的整数的好主意。

例子:

我们得到n = 30,我们被要求生成三个整数a、b和c,所以7*a + 5*b + 3*c = n。 我尝试使用for 循环,但它需要太多时间,而且我的最大测试时间为 1000 毫秒。

我正在使用 Python 3。

我的尝试:

x = int(input())
c = []
k = []
w = []
for i in range(x):
    for j in range(x):
        for h in range(x):
           if 7*i + 5*j + 3*h = x:
              c.append(i)
              k.append(j)
              w.append(h)
if len(c) == len(k) == len(w) 
    print(-1)
else: 
    print(str(k[0]) + ' ' + str(c[0]) + ' ' + str(w[0]))

【问题讨论】:

  • = x 在那里无效。你的意思可能是== xx 到底是什么?请edit这个问题。作为参考,您需要提供minimal reproducible example。顺便说一句,欢迎来到 SO!查看tour
  • 等等,为什么 ckw 的长度会不同?
  • 随机性在其中有什么作用?您的问题是要求您找到满足条件的 a、b、c,但您没有说要找到多个这样的三元组。这让我想知道随机性是什么,以及为什么你会费心在代码中使用列表,而不仅仅是if 7*i + 5*j + 3*h == x: print(i, j, h)。实际需求或最终目标是什么?
  • 另外,您是否只想找到一种解决方案而不是所有解决方案?从字面上看,您的问题暗示了前者,但您的代码实现了后者。
  • 为什么你的标题和标签中有“随机”?根据你的问题的文字,你只想要一个方程的解。 “随机”用于描述完全不同的概念。

标签: python python-3.x random


【解决方案1】:

首先,请注意,您的任务至少在两个方面没有明确说明:

  1. 未指定生成值的允许范围。特别是,您没有指定结果是否可能包含负整数。
  2. 未指定生成值的所需distribution

通常,如果未指定,人们可能会假设在方程的一组可能解上的 uniform distribution 是预期的(因为它是 in a certain sense,是给定集合上最随机的可能分布)。但是(离散)均匀分布只有在解集是有限的情况下才有可能,如果结果范围不受限制,则不会。 (特别是,如果 (a, b, c) 是一个解,那么 (a, b + 3k, c - 5k) 对于任意整数 k。 ) 因此,如果我们将任务解释为要求无限范围的均匀分布,那实际上是不可能的!


另一方面,如果我们被允许选择任何分布和范围,任务就变得微不足道了:只要让生成器总是返回a = -nb = nc = 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。同样,这保证是任何 hk 的有效解,因为它清楚地满足 h = k 的方程 = 0,而且很容易看出增加或减少 hk 将使等式左侧的值保持不变。

其实可以证明,这种方法可以生成方程的所有种可能的解,并且每个解都会对应一个唯一的(h,k) 对! (看到这一点的一种相当直观的方法是在 3D 空间中绘制解,并观察它们在 2D 平面上形成规则的点阵,并且向量 (+1, -2, +1) 和 (0, + 3, -5) 跨越这个格子。)如果我们从某个(至少在理论上)为每个整数分配非零概率的分布中选择 hk,那么我们将有一个非零概率返回任何有效的解决方案。因此,至少对于任务的某种合理解释(无限范围,任何具有完整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)

如果可能值的范围受到限制,问题就会变得有点棘手。积极的一面是,如果所有值都在以下(或以上)范围内,则可能的解集是有限的,因此存在均匀分布。另一方面,有效地对这种均匀分布进行采样并非易事。

您自己使用过的一种可能方法是首先生成所有可能的解决方案(假设它们的数量有限),然后从解决方案列表中进行抽样。我们可以像这样相当有效地生成解决方案:

  1. 找出方程可能有解的a的所有可能值,
  2. 对于每一个这样的 a,找出所有可能的 b 值,它们仍然有解决方案,
  3. 对于每个这样的 (a, b) 对,求解 c 的方程并检查它是否有效(即指定范围),以及
  4. 如果是,则将 (a, b, c) 添加到解决方案集中。

棘手的部分是第 2 步,我们要计算可能的 b 值的范围。为此,我们可以利用以下观察结果:对于给定的 a,将 c 设置为其最小允许值并求解方程给出 的上限b(反之亦然)。

特别是分别求解abc的方程,我们得到:

  • 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 并完全跳过整除性检查。我将把实现优化留作练习。

【讨论】:

  • 这是一个比当前接受的答案更好的解决方案。
  • 不清楚这个问题与“随机”有什么关系,只是他们不在乎返回哪个解决方案。
【解决方案2】:
import numpy as np


def generate_answer(n: int, low_limit:int, high_limit: int):
    while True:
        a = np.random.randint(low_limit, high_limit + 1, 1)[0]
        b = np.random.randint(low_limit, high_limit + 1, 1)[0]
        c = (n - 7 * a - 5 * b) / 3.0
        if int(c) == c and low_limit <= c <= high_limit:
            break

    return a, b, int(c)


if __name__ == "__main__":
    n = 30
    ans = generate_answer(low_limit=-5, high_limit=50, n=n)
    assert ans[0] * 7 + ans[1] * 5 + ans[2] * 3 == n
    print(ans)

如果您选择数字 a、b、c 中的两个,则您知道第三个。在这种情况下,我将 a、b 的整数随机化,然后通过c = (n - 7 * a - 5 * b) / 3.0 找到 c。

确保 c 是一个整数,并且在允许的范围内,我们就完成了。

如果不是,再次随机化。


如果你想产生所有的可能性,

def generate_all_answers(n: int, low_limit:int, high_limit: int):
    results = []
    for a in range(low_limit, high_limit + 1):
        for b in range(low_limit, high_limit + 1):
            c = (n - 7 * a - 5 * b) / 3.0
            if int(c) == c and low_limit <= c <= high_limit:
                results.append((a, b, int(c)))

    return results

【讨论】:

  • 我只想指出,在像这样的一些“三向”随机选择中,您(通常/经常)必须随机决定 三者中的哪一个是由其他人。
  • c 上的系数很大(如3139293)而不是3 时,您可能需要重新考虑使用这种方法。
  • 您使用numpy.random而不是标准random模块的任何原因?
  • 这个答案可以识别解决方案,但不能决定解决方案。也就是说,如果一个解决方案是不可能的,这个程序永远不会告诉你,并且永远循环。生成所有答案显然更胜一筹,即使您只是返回第一个结果。随机性只会增加不必要的复杂性,并且可能做太多工作而没有真正的好处
  • @Gulzar “有什么理由不这样做吗?” - 是的:ModuleNotFoundError: No module named 'numpy'。对于可以使用标准库同样出色地完成的事情,需要一个外部库是一种不好的做法。实际上,标准库会使程序稍微短一些(import random; random.randint(low_limit, high_limit + 1))。
【解决方案3】:

如果允许第三方库,可以使用 SymPy 的 diophantine.diop_linear linear Diophantine equations 求解器:

from sympy.solvers.diophantine.diophantine import diop_linear
from sympy import symbols
from numpy.random import randint

n = 30
N = 8 # Number of solutions needed

# Unknowns
a, b, c = symbols('a, b, c', integer=True)

# Coefficients
x, y, z = 7, 5, 3

# Parameters of parametric equation of solution
t_0, t_1 = symbols('t_0, t_1', integer=True)

solution = diop_linear(x * a + y * b + z * c - n)

if not (None in solution):
  for s in range(N):
    # -10000 and 10000 (max and min for t_0 and t_1)
    t_sub = [(t_0, randint(-10000, 10000)), (t_1, randint(-10000, 10000))]

    a_val, b_val, c_val = map(lambda t : t.subs(t_sub), solution)

    print('Solution #%d' % (s + 1))
    print('a =', a_val, ', b =', b_val, ', c =', c_val)
else:
  print('no solutions')

输出(随机):

Solution #1
a = -141 , b = -29187 , c = 48984
Solution #2
a = -8532 , b = -68757 , c = 134513
Solution #3
a = 5034 , b = 30729 , c = -62951
Solution #4
a = 7107 , b = 76638 , c = -144303
Solution #5
a = 4587 , b = 23721 , c = -50228
Solution #6
a = -9294 , b = -106269 , c = 198811
Solution #7
a = -1572 , b = -43224 , c = 75718
Solution #8
a = 4956 , b = 68097 , c = -125049

【讨论】:

    【解决方案4】:

    为什么您的解决方案无法处理 n 的大值

    你可能明白,for 循环中的所有内容,范围为i,都会运行i 次。所以它将乘以i所花费的时间。

    例如,让我们假设(为了简单起见)它在 4 毫秒内运行:

    if 7*a + 5*b + 3*c = n:
        c.append(a)
        k.append(b)
        w.append(c)
    

    那么这将在 4×n 毫秒内运行:

    for c in range(n):
        if 7*a + 5*b + 3*c = n:
            c.append(a)
            k.append(b)
            w.append(c)
    

    大约:

    • n = 100 需要 0.4 秒
    • n = 250 需要 1 秒
    • n = 15000 需要 60 秒

    如果你把它放在for 循环中,覆盖n 的范围,那么整个事情 将重复n 次。即

    for b in range(n):
        for c in range(n):
            if 7*a + 5*b + 3*c = n:
                c.append(a)
                k.append(b)
                w.append(c)
    

    将需要 4n² 毫秒。

    • n = 30 需要 4 秒
    • n = 50 需要 10 秒
    • n = 120 需要 60 秒

    将其放入第三个 for 循环将花费 4n³ 毫秒。

    • n = 10 需要 4 秒
    • n = 14 需要 10 秒。
    • n = 24 需要 60 秒。

    现在,如果将原始 if 减半为 2 毫秒会怎样? n 在第一种情况下可以增加 15000 ......在最后一种情况下可以增加 23。这里的教训是,更少的 for 循环通常比加快其中的速度更重要。正如您在 Gulzar 的回答第 2 部分中看到的那样,只有两个 for 循环会产生很大的不同。 (这仅适用于循环在彼此内部的情况;如果它们只是一个接一个,则不存在乘法问题。)

    【讨论】:

      【解决方案5】:

      在我看来,三个中的最后一个数字绝不是随机数。假设您首先生成ab 然后c 绝不是随机的,因为它应该从等式计算出来

      n = 7*a + 5*b + 3*c
      c = (7*a + 5*b - n) / -3
      

      这意味着我们需要生成两个随机值 (a,b) 7*a + 5*b - n 可以被 3 整除

      import random
      
      n = 30;
      max = 1000000;
      min = -1000000;
      
      while True:
        a = random.randint(min , max);
        b = random.randint(min , max);
        t = (7*a) + (5*b) - n;
        if (t % 3 == 0) :
          break;
      
      c = (t/-3);
      
      print("A = " + str(a));
      print("B = " + str(b));
      print("C = " + str(c));
      print("7A + 5B + 3C =>")
      print("(7 * " + str(a) + ") + (5 * " + str(b) + ") + (3 * " + str(c) + ") = ")
      print((7*a) + (5*b) + (3*c));
      

      REPL

      【讨论】:

      • 如果阅读提问者没有将“随机”放入的问题,则标题中的“随机”仅表示“任何”,并且没有随机意义上的随机性。在此页面上查看我的其他 cmets。
      • 我认为“any”和“random”在这种情况下是同一个意思
      • 调用随机例程对于找到方程的解是一件很糟糕的事情。
      猜你喜欢
      • 2020-12-06
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2018-05-15
      • 1970-01-01
      • 2020-11-12
      相关资源
      最近更新 更多