提问人:Marius 提问时间:5/27/2013 最后编辑:Marius 更新时间:5/27/2013 访问量:490
将相等值分开的排序算法
Sorting algorithm to keep equal values separated
问:
心理学实验通常要求你对试验顺序进行伪随机化,这样试验显然是随机的,但你不会连续得到太多类似的试验(这在纯随机排序中可能会发生)。
假设每个试验的视觉显示都有颜色和大小:
display_list = []
colours = {0: 'red', 1: 'blue', 2: 'green', 3: 'yellow'}
sizes = [1] * 20 + [2] * 20 + [3] * 20 + [4] * 20 + [5] * 20 + [6] * 20
for i in range(120):
display_list.append({'colour': colours[i % 4], 'size': sizes[i]})
print(display_list)
我们可以使用此函数查看任一属性具有相同值的最大连续试验数:
def consecutive_properties(seq, field):
longest_run = 0
prev_value = None
current_run = 0
for d in seq:
if d[field] == prev_value:
current_run += 1
else:
current_run = 1
if current_run > longest_run:
longest_run = current_run
prev_value = d[field]
return longest_run
输出:
>>> print("Consecutive colours: ", consecutive_properties(display_list, 'colour')
('Consecutive colours: ', 1)
>>> print("Consecutive sizes: ", consecutive_properties(display_list, 'size'))
('Consecutive sizes: ', 20)
您所知道的是否有任何算法可以最小化其中一个或两个属性的连续运行,或者至少将这些运行保持在指定的长度以下?如果是后者,假设在相同颜色或大小的一排不超过 4 个。
我尝试过的:
我现在的解决方案基本上是做一个稍微聪明的 bogosort,它必须非常低效。基本上:
- 将整个列表分解为包含所有属性排列的块:如果分解为长度为 24 的块,则每个块都有每种颜色与每种大小配对。让我们假设试验列表始终可以分解为这些排列块,因为您从实验设计中就知道排列是什么。
display_list
- 选择每个块的最大运行长度
- 对每个块进行随机排序,直到每个块的运行长度低于最大值(这实际上意味着在整个试验列表中,您的运行可能是该长度的两倍,因为您可以在一个块的末尾和下一个块的开始运行此长度)
答:
对不起,这不是一个答案,但很难在评论中发布代码。这是编写函数的更简单方法consecutive_properties
from operator import itemgetter
from itertools import groupby
def consecutive_properties(seq, field):
return max(sum(1 for x in g) for k,g in groupby(seq, key=itemgetter(field)))
当我正确理解你的问题时,我会试着把它变成一个答案:)
评论
consecutive_properties
你显然不关心真正的随机性,所以如果你定义了一个距离指标,并随机绘制你的序列,你可以拒绝任何新的绘制,如果它的距离与前一个绘制“太近”,然后再次绘制。
如果你从一个有限的集合(比如,一包纸牌)中抽取,那么整个集合可以 是平局,你的排序将包括在关闭时交换两个元素 找到 pair,但如果交换的元素变得不可接受,也会拒绝交换伙伴,因此每个交换步骤都会使整个集合得到改进。
如果您的标准不是太难满足,这将很快终止。
评论
正如 ddyer 所说,你对随机性感兴趣,而不是排序。我的解决方案是:
- 从源列表中随机选取一个 A 元素
- 从您的目的地列表中随机选择一个位置 I
- 在位置 I 处插入 A,以列出
- 检查目标列表是否有效。如果没有,请恢复以前的状态并重复
工作片段:
from random import randint
from operator import itemgetter
from itertools import islice
def reshuffle(_items, max_consequent):
items = _items[:]
new_order = []
while items:
src_pos = randint(0, len(items)-1)
dest_pos = randint(0, len(new_order))
item = items[src_pos]
new_order.insert(dest_pos, item)
if is_new_order_fine(new_order, max_consequent):
items.pop(src_pos)
else:
new_order.pop(dest_pos)
return new_order
def is_new_order_fine(items, n_max):
return (
not has_consecutive_keys(items, n_max, key=itemgetter('colour')) and
not has_consecutive_keys(items, n_max, key=itemgetter('size')))
# can be optimised - don't check all items, just surrounding N
def has_consecutive_keys(items, n_max, key):
_has_n_keys = False
if len(items) >= n_max:
last_value = key(items[0])
n_consequent = 1
for item in items[1:]: # can optimize by using iterator
if key(item) == last_value:
n_consequent += 1
else:
last_value = key(item)
n_consequent = 1
if n_consequent >= n_max:
_has_n_keys = True
break
return _has_n_keys
请注意,您不需要每次都检查目标列表中的所有项目,在插入的新项目周围的左侧和右侧显示 K(未在代码段中实现)
编辑:
- 您可以在中使用 groupby(但无需排序!
has_consecutive_keys
如果连续元素的可能性不是很高(如您的示例所示),如果不满足条件,我会简单地重新洗牌。正如你所看到的,大多数时候你只需尝试一次就可以逃脱,所以它非常有效。
In [1]: from random import shuffle
In [2]: from itertools import groupby
In [3]: from collections import Counter
In [4]: def pseudo_shuffle(lst, limit, tries=1):
...: temp = list(lst)
...: shuffle(temp)
...: if max(sum(1 for x in g) for k, g in groupby(temp)) <= limit:
...: return tries #return temp
...: return pseudo_shuffle(lst, limit, tries=tries+1)
In [5]: colors = 30*['red', 'blue', 'green', 'yellow']
In [6]: sizes = [1] * 20 + [2] * 20 + [3] * 20 + [4] * 20 + [5] * 20 + [6] * 20
In [7]: Counter([pseudo_shuffle(colors, 4) for _ in range(1000)])
Out[7]: Counter({1: 751, 2: 200, 3: 38, 4: 10, 5: 1})
In [8]: Counter([pseudo_shuffle(sizes, 4) for _ in range(1000)])
Out[8]: Counter({1: 954, 2: 44, 3: 2})
评论
问题:您知道是否有任何算法可以最小化 连续运行其中一个或两个属性,或者至少保留这些属性 低于指定长度?
是的。有一个简单的算法可以做到这一点,只需降低选择颜色或尺寸的概率(如果它已经在运行中发生)。
from random import randrange
def choose(colors, numselections, maxrun):
'Repeatedly choose colors. Gradually reduce selection probability to avoid runs.'
colors = list(colors)
n = len(colors)
total = n * maxrun
current_run = 0
for _ in range(numselections):
i = randrange(total - current_run) // maxrun
yield colors[i]
colors[i], colors[-1] = colors[-1], colors[i]
current_run = current_run + 1 if i==n-1 else 1
if __name__ == '__main__':
colors = ['red', 'blue', 'green', 'yellow']
for color in choose(colors, 100, maxrun=4):
print color
请注意,这种方法比其他使用重选技术来避免运行的答案需要更少的工作量。另外,请注意,运行是逐渐淡出的,而不是像其他答案那样一次性淡出。
上一个:pandas:填充组中的缺失值
下一个:Sympy:手动处理等式
评论