创建列表的许多受约束的随机排列
我需要随机排列排列的列表。元素可以是任何东西,但前提是它们是从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