创建列表的许多受约束的随机排列
我需要随机排列排列的列表。元素可以是任何东西,但前提是它们是从0到x-1的整数。我要制作y列表,每个列表包含z元素。规则是,列表中不能包含两次相同的元素,并且在所有列表中,每个元素的使用次数是相同的(或者尽可能接近)。例如,如果我的元素是0、1、2、3,y是6,z是2,那么一种可能的解决方案是:
0,3 1,2 3,0 2,1 0,1 2,3
每行只有唯一的元素,并且没有元素被使用超过3次。如果y为7,则2个元素将使用4次,其余3个。
解决方案
好的,一种近似的方法:
1随机播放列表
2将y个第一个元素作为下一行
只要列表中有数字,请重复4个(2)
5如果我们没有足够的数字来完成列表,请重新排列原始列表并删除缺少的元素,确保我们不重新获取数字。
6只要需要行,请从步骤(2)重新开始
我认为这应该尽可能地随机,并且一定会遵循标准。另外,我们对重复元素的测试很少。
首先,我们总是可以在最后对列表进行随机排序,因此不必担心进行"随机排列"(困难);只需担心1)进行排列(容易)和2)将它们随机化(容易)即可。
如果我们想要"真正的"随机组,则必须接受这种本质上的随机性实际上并没有约束结果"均匀分布"的约束-我们可能会得到,或者可能会看到一系列相似的结果。如果我们确实想要平均分配,请首先使集合均匀分配,然后将它们随机分组。
我们是否必须平均使用集合x中的每个元素?从规则尚不清楚,我不能仅仅做出以下解释:
请注意以下几点:"在所有列表中,每个元素的使用次数相同(或者尽可能接近)"
基于此标准和z <x *的规则,我假设我们可以简单地枚举所有列表中的所有项目。因此,我们会自动将y列出的项目列到位置z。示例没有像我的版本那样完全满足上述规则。使用x = {0,1,2,3} y = 6和z = 2的示例,我得到:
0,1 0,1 0,1 0,1 0,1 0,1
现在我没有使用2或者3,但是我们没有说我必须全部使用它们。如果我必须全部使用它们,而又不希望能够证明我"尽可能"接近甚至使用它们,那么我将列举列表中的所有项目,例如:
0,1 2,3 0,1 2,3 0,1 2,3
最后,假设我确实必须使用所有元素。为了计算每个元素可以重复多少次,我只取(y * z)/(x的数量)。这样,我不必坐下来担心如何划分列表中的项目。如果有余数,或者结果小于1,那么我知道我将无法获得确切的重复次数,因此,在那种情况下,尝试浪费计算能力以使其完美无所谓。我认为最快的结果仍然是如上所述,并且使用此处的计算来说明为什么达到或者没有达到完美的结果。一种花哨的算法可以从该计算中提取出多少个位置将重复,但是"太长了,无法在此处容纳空白"。
*每个列表具有相同的z个元素数,因此将不可能创建z大于x的列表,并且仍然满足以下规则:没有列表可以包含两次相同的元素。因此,此规则要求z不能大于x。
基于评论中的新细节,该解决方案可以简单地是标准随机排列生成算法的实现。这里对随机排列生成算法进行了冗长的讨论:
http://www.techuser.net/randpermgen.html
(来自Google搜索:随机排列生成)
可以改进,但是似乎可以完成此工作(Python):
import math, random
def get_pool(items, y, z):
slots = y*z
use_each_times = slots/len(items)
exceptions = slots - use_each_times*len(items)
if (use_each_times > y or
exceptions > 0 and use_each_times+1 > y):
raise Exception("Impossible.")
pool = {}
for n in items:
pool[n] = use_each_times
for n in random.sample(items, exceptions):
pool[n] += 1
return pool
def rebalance(ret, pool, z):
max_item = None
max_times = None
for item, times in pool.items():
if times > max_times:
max_item = item
max_times = times
next, times = max_item, max_times
candidates = []
for i in range(len(ret)):
item = ret[i]
if next not in item:
candidates.append( (item, i) )
swap, swap_index = random.choice(candidates)
swapi = []
for i in range(len(swap)):
if swap[i] not in pool:
swapi.append( (swap[i], i) )
which, i = random.choice(swapi)
pool[next] -= 1
pool[swap[i]] = 1
swap[i] = next
ret[swap_index] = swap
def plist(items, y, z):
pool = get_pool(items, y, z)
ret = []
while len(pool.keys()) > 0:
while len(pool.keys()) < z:
rebalance(ret, pool, z)
selections = random.sample(pool.keys(), z)
for i in selections:
pool[i] -= 1
if pool[i] == 0:
del pool[i]
ret.append( selections )
return ret
print plist([0,1,2,3], 6, 2)
这适用于Ruby:
# list is the elements to be permuted
# y is the number of results desired
# z is the number of elements per result
# equalizer keeps track of who got used how many times
def constrained_permutations list, y, z
list.uniq! # Never trust the user. We want no repetitions.
equalizer = {}
list.each { |element| equalizer[element] = 0 }
results = []
# Do this until we get as many results as desired
while results.size < y
pool = []
puts pool
least_used = equalizer.each_value.min
# Find how used the least used element was
while pool.size < z
# Do this until we have enough elements in this resultset
element = nil
while element.nil?
# If we run out of "least used elements", then we need to increment
# our definition of "least used" by 1 and keep going.
element = list.shuffle.find do |x|
!pool.include?(x) && equalizer[x] == least_used
end
least_used += 1 if element.nil?
end
equalizer[element] += 1
# This element has now been used one more time.
pool << element
end
results << pool
end
return results
end
用法示例:
constrained_permutations [0,1,2,3,4,5,6], 6, 2 => [[4, 0], [1, 3], [2, 5], [6, 0], [2, 5], [3, 6]] constrained_permutations [0,1,2,3,4,5,6], 6, 2 => [[4, 5], [6, 3], [0, 2], [1, 6], [5, 4], [3, 0]] enter code here
http://en.wikipedia.org/wiki/Fisher-Yates_shuffle

