列出 1-50 范围内的 50 个随机数,这样相邻的数字之间的距离不在 15 以内

Posted

技术标签:

【中文标题】列出 1-50 范围内的 50 个随机数,这样相邻的数字之间的距离不在 15 以内【英文标题】:Make a list of 50 random numbers in the range 1-50, such that adjacent numbers are not within 15 of each other 【发布时间】:2016-01-01 08:30:54 【问题描述】:

这是我目前所拥有的,但如果我超过前一个数字的正负 11,它就会中断。因此,如果前一个数字是 1,则下一个数字必须大于 16。另外我尝试只使用每个数字一次。

示例输出:

[3,45, 1, 16, 33, 3, 23.....]

到目前为止,我的脚本是:

import random
new_array=[]
counter = 0

array=range(51)
array=array[1:51]

while len(new_array)<50:
    y=random.choice(array)
    if y not in new_array and counter!=0 and y not in (range(new_array[counter-1]-11,new_array[counter-1]+11)):
        new_array.append(y)
        counter+=1
    elif counter == 0:
        new_array.append(y)
        counter+=1
    else:
        pass

【问题讨论】:

提示:不要玩range(),只需将两个数字相减即可。 您的算法的有趣问题是它的执行时间没有限制。如果运气不好,可能需要 42 年才能执行。 问题实际上比看起来要复杂得多。我所做的尝试忘记了每个数字只能选择一次这一事实。仅此约束就不​​可能一次性解决。 这真是一个脑筋急转弯。在这一点上,我最好的想法需要评估最坏的〜10 ^ 32个案例。我想知道如何将这个数字减少到合理的程度。 我认为解决该问题的另一种方法是将 50 个数字划分为子列表,您只能将 50 个数字排列这么多种方式,以使连续数字之间至少有 16 个差异 【参考方案1】:

下面的方法怎么样。从一个打乱列表开始。对于每一对,如果距离小于 15,则旋转剩余的数字。

import random

def get_shuffled_list(length):
    output = range(1, length+1)
    i = 0
    sanity = 0

    while i < length and sanity == 0:
        random.shuffle(output)
        for i in range(1, length):
            sanity = length - i + 1

            while abs(output[i-1] - output[i]) <= 15 and sanity:
                output.append(output[i])
                del output[i]
                sanity -= 1

            if sanity == 0:
                break

    return output

print get_shuffled_list(50)

给出以下类型的输出:

[1, 38, 3, 33, 16, 43, 10, 41, 9, 35, 4, 50, 29, 8, 44, 28, 5, 32, 14, 40, 21, 47, 25, 48, 30, 12, 34, 18, 42, 15, 36, 13, 31, 2, 24, 7, 23, 39, 22, 6, 26, 49, 27, 11, 45, 19, 46, 17, 37, 20]

如果您没有任何剩余的数字满足条件,问题就会出现,在这种情况下,请重新开始。对于 10,000 次随机播放的测试,我管理的最坏情况是 196 次尝试。不理想,但确实有效。

【讨论】:

【参考方案2】:

试试这样的:

last 的初始值开始。 此值应为字符串 ('start')。 根据需要循环多次: 检查是否last == 'start'。 如果是,请在列表中添加一个随机数。 如果不是,请创建一个随机数。 直到abs(number - last) 小于15,创建一个新的随机数。 将号码添加到列表中。 将last 设置为列表的最后一个元素。

【讨论】:

将初始化放在循环中确实没有意义。无论如何,这将选择重复的数字。【参考方案3】:

编辑:不回答这个问题,因为它可以并且会多次选择一些数字。留下它以防此变体对其他人有用。

您使用的算法和 JF 建议的算法都有一个有趣的缺陷:它们所用的时间没有限制。如果你真倒霉,在宇宙终结之前,函数是无法返回的。

这是一个将在限定时间内执行的,即O(n)

import random

def spaced_randoms(min_val, max_val, number, space):
    if max_val - min_val <= 2 * space:
        raise ValueError('interval not large enough for space')

    last = random.randint(min_val, max_val)
    result = [last]
    for _ in range(number - 1):
        skip_low = max(0, min(space, last - min_val))
        skip_high = max(0, min(space, max_val - last))
        value = random.randint(min_val + skip_low, max_val - skip_high - 1)

        last = value - skip_low if value < last else value + skip_high + 1
        result.append(last)

    return result

>>> print(spaced_randoms(0, 50, 50, 15))
[35, 10, 44, 26, 47, 22, 49, 29, 3, 28, 44, 17, 40, 3, 39, 7, 48, 22, 41, 5, 21, 4, 32, 9, 34, 3, 46, 3, 20, 38, 10, 49, 32, 16, 38, 5, 26, 45, 26, 49, 13, 44, 1, 30, 12, 30, 0, 46, 15, 42]

这个想法很简单:我们减少新数字的可能间隔以考虑禁止值。我们重新映射结果。例如,假设在某个点last == 32 有 15 个空格,那么:

我们选择一个介于 15 到 34 之间的数字。 如果选择的号码在 [15 到 31] 中,我们重新映射到 [0 到 16] 如果选择的号码在 [32 到 34] 中,我们重新映射到 [48 到 50]

【讨论】:

OP 也希望每个号码只使用一次。【参考方案4】:

嗯,差不多是伪代码

sampled = []

n = 50

x = 15

curr = n + x + 1

for k in range(1, 1000):

    # build exclusion range tuple, where you cannot sample,
    # say for calc_ex_range(50, 15, 27) it should return
    # (12, 42)
    exclude_range = calc_ex_range(n, x, curr)

    # build new sampling array, given the exclusion range
    # result should be [1,2,3,4,5,6,7,8,9,10,11,43,44,45,46,47,48,49,50]
    sampling_array = calc_sarray(n, exclude_range)

    # remove already sampled numbers from sampling array
    sampling_array = filter_sarray(sampling_array[:], sampled)
    if len(sampling_array) == 0:
        break

    # sample one item from the array
    curr = random.sample(sampling_array, 1)

    sampled.append(curr)

我猜复杂度会是 O(N^2),但它总是会结束

更新

为了采样一次,过滤掉采样数组

【讨论】:

其实和我自己的回答一样,这会选择多次相同的数字,都达不到问题的目的。 @spectras 啊哈!应更仔细地阅读要求。查看编辑后的答案 我一开始也是这么想的,可惜一旦这样修改,算法很有可能会失败。例如:假设您选择了 48 个数字,但最后两个未选择的数字是 49 和 50(或任意两个在 15 以内的数字)。 @spectras 好吧,我认为很难证明从序列中的任何数字开始你总是会选择所有元素(当然,满足所有条件)【参考方案5】:

在另一个未能满足“每个项目必须出现一次”要求的答案之后,这里有一些想法:

如果我把 spacing 称为两个连续数字之间的最小绝对差,那么:

选择数组的开头很容易。您可以使用 Severin Pappadeux 的代码做到这一点。 一旦剩下 2 * spacing 个未选择的项目,您必须切换算法。 选择最后一项的问题似乎是 P-complete。

不幸的是,我在算法理论方面还不够好,无法建立正式的证明。另外,也许只是我没有找到更好的方法。但在这一点上,除了将最后一个项目的选取作为图形搜索之外,我没有看到任何其他方法。

尽管这很有趣,但我不能花更多时间在上面。但如果你回来回答你自己的问题,我会喜欢阅读它。

【讨论】:

【参考方案6】:

如果任意排除许多符合您标准的排列是可以接受的,您可以很容易地创建一个算法来生成有效的排列。

我能想到的最简单的例子如下:

1,50 范围划分为五个数字的 10 个子范围:1,5,6,10,11,15...。 选择这些子范围的序列,以便对于彼此相邻的任何两个子范围,最接近的元素至少相隔 15 个。最简单的方法是选择每四个子范围:1,5,21,25,41,45,11,15... 输出序列从每个范围中随机选择的一个数字开始:3,22,41,15... 继续按顺序从子范围中选择数字,直到您从每个子范围中选择了所有 5 个数字(即,直到您选择了从 0 到 50 的整个范围中的每个数字):3,22,41,15...1,24,42,14...4,21...

您可以通过选择不同的子范围大小(例如 157,尽管这些引入了无法均匀划分原始范围的额外复杂性)来对此进行简单的变化。或者,您可以按照永远不能使序列无效的规则(即,您不能引入相距小于 15 的元素对)随机转置输出序列的元素(或以其他方式改变序列)。您还可以将子范围的排序设为准随机,或者在通过子范围的每次迭代中更改排序。

即使有这些变化,这显然不会是对任何可能的有效排列的完全随机选择。但在实践中,我认为这很有可能无关紧要。

【讨论】:

【参考方案7】:

这个怎么样?我使用 randint 轻松获得所需范围内的整数:

import random

my_list = []
# This number should not be within 15 units from each
# new random integer:
previous = random.randint(1, 50)

# Loop until my_list' length is 50; see at the bottom
while True:
    # Get a new random integer from 1 to 50
    rand_int = random.randint(1, 50)

    # If the new integer is NOT at a range within 15 of the previous number,
    # append to the list; otherwise skip and try again
    if not rand_int - 15 < previous < rand_int + 15:
        my_list.append(rand_int)
        # This new integer becomes the new previous
        previous = rand_int

    if len(my_list) >= 50:
        # Break from the loop and print the new list
        break

print(my_list)

【讨论】:

这会选择重复项。

以上是关于列出 1-50 范围内的 50 个随机数,这样相邻的数字之间的距离不在 15 以内的主要内容,如果未能解决你的问题,请参考以下文章

包含范围内的随机浮点双精度

shell生成指定范围内的随机数

java生成指定范围内随机秒数

在范围内生成'n'个唯一随机数[重复]

怎么用python生成随机数?

邮递员 - 如何生成特定范围内的随机数?