将相等值分开的排序算法

Sorting algorithm to keep equal values separated

提问人:Marius 提问时间:5/27/2013 最后编辑:Marius 更新时间:5/27/2013 访问量:490

问:

心理学实验通常要求你对试验顺序进行伪随机化,这样试验显然是随机的,但你不会连续得到太多类似的试验(这在纯随机排序中可能会发生)。

假设每个试验的视觉显示都有颜色和大小:

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
  • 选择每个块的最大运行长度
  • 对每个块进行随机排序,直到每个块的运行长度低于最大值(这实际上意味着在整个试验列表中,您的运行可能是该长度的两倍,因为您可以在一个块的末尾和下一个块的开始运行此长度)
Python 算法 排序

评论

1赞 Patashu 5/27/2013
对于一个属性,1)按属性排序 2)在相等的值内,洗牌(洗牌所有红色,洗牌所有黄色等) 3)(稍微随机?交替将颜色添加到新列表中,以便您尝试保持每种颜色的百分比相等(例如,如果您有四个红色和两个蓝色,则取一个红色和一个蓝色,然后再取另一个红色以使其达到 50%,依此类推)。然后,对于第二个属性,您可以优先从颜色列表中获取,以尝试不运行第二个属性,类似的东西。
0赞 John La Rooy 5/27/2013
所以你要求每个排列的出现次数总体上相等?
0赞 Marius 5/27/2013
@gnibbler:是的,因为我认为这使问题更加明确。我试图对这个问题保持限制,以便它仍然是一个棘手但希望有趣的 CompSci 脑筋急转弯,而不仅仅是一个混乱。在现实世界中,我是心理学实验室的研究助理,限制通常是“教授想挤进实验的任何东西”。
0赞 root 5/27/2013
您的实际样本是否与示例中连续获得 4 个以上连续字段的可能性相同?
0赞 Marius 5/27/2013
@root:我目前正在做的实际实验有这个 4 x 6 的结构,这使得用我的基本 bogosort 方法更容易求解,但可能有很多实验中,有大量连续运行的概率会更高,例如,你可能有一个 3 x 4 的结构。

答:

0赞 John La Rooy 5/27/2013 #1

对不起,这不是一个答案,但很难在评论中发布代码。这是编写函数的更简单方法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)))

当我正确理解你的问题时,我会试着把它变成一个答案:)

评论

1赞 Patashu 5/27/2013
他想选择一个随机的随机洗牌,使得最大运行长度(其中运行长度可以是 p1、p2、p3 中的一个属性......连续元素的 pn 相等)低于 K。
0赞 Marius 5/27/2013
是的,如果问题的任何特定部分不清楚,请告诉我,但@Patashu的总结差不多就是这样。至于函数的pythonicity,我最近一直在MATLAB工作,当我试图编写示例时,我的大脑只是慢慢地重新打开。谢谢:)consecutive_properties
1赞 ddyer 5/27/2013 #2

你显然不关心真正的随机性,所以如果你定义了一个距离指标,并随机绘制你的序列,你可以拒绝任何新的绘制,如果它的距离与前一个绘制“太近”,然后再次绘制。

如果你从一个有限的集合(比如,一包纸牌)中抽取,那么整个集合可以 是平局,你的排序将包括在关闭时交换两个元素 找到 pair,但如果交换的元素变得不可接受,也会拒绝交换伙伴,因此每个交换步骤都会使整个集合得到改进。

如果您的标准不是太难满足,这将很快终止。

评论

0赞 Marius 5/27/2013
谢谢,这绝对是我还没有尝试过的方法。您能否举例说明颜色/尺寸示例的距离指标可能是什么?
0赞 Patashu 5/27/2013
Marius 在这种情况下,如果抽奖与之前的 K-1 抽签形成 K 的运行,您将拒绝抽奖。
0赞 ddyer 5/27/2013
像 (color_a==color_b)?0:1 + (size_a==size_b) ?0 : 1
1赞 Jakub M. 5/27/2013 #3

正如 ddyer 所说,你对随机性感兴趣,而不是排序。我的解决方案是:

  1. 从源列表中随机选取一个 A 元素
  2. 从您的目的地列表中随机选择一个位置 I
  3. 在位置 I 处插入 A,以列出
  4. 检查目标列表是否有效。如果没有,请恢复以前的状态并重复

工作片段:

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
1赞 root 5/27/2013 #4

如果连续元素的可能性不是很高(如您的示例所示),如果不满足条件,我会简单地重新洗牌。正如你所看到的,大多数时候你只需尝试一次就可以逃脱,所以它非常有效。

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})

评论

0赞 Marius 5/27/2013
谢谢,我知道我的特定情况可能不太难,但是对给定限制的约束满足难度进行这种分析非常有用。
3赞 Raymond Hettinger 5/27/2013 #5

问题:您知道是否有任何算法可以最小化 连续运行其中一个或两个属性,或者至少保留这些属性 低于指定长度?

是的。有一个简单的算法可以做到这一点,只需降低选择颜色或尺寸的概率(如果它已经在运行中发生)。

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

请注意,这种方法比其他使用重选技术来避免运行的答案需要更少的工作量。另外,请注意,运行是逐渐淡出的,而不是像其他答案那样一次性淡出。