列出 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 个空格,那么:
【讨论】:
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...
您可以通过选择不同的子范围大小(例如 15
或 7
,尽管这些引入了无法均匀划分原始范围的额外复杂性)来对此进行简单的变化。或者,您可以按照永远不能使序列无效的规则(即,您不能引入相距小于 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 以内的主要内容,如果未能解决你的问题,请参考以下文章