排序算法保持相等的值分开

时间:2013-05-27 05:00:55

标签: python algorithm sorting

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

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

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,它必须非常低效。基本上是:

  • 您将整个列表分成包含属性的所有排列的块:如果将display_list分解为长度为24的块,则每个块的每种颜色都与每个大小配对。让我们假设试用列表总是可以分解为这些排列块,因为你知道实验设计的排列是什么。
  • 您选择每个块的最大游程长度
  • 您将每个块重新洗牌,直到每个块的运行长度低于最大值(这实际上意味着在整个试用列表中,您的运行速度可能是该长度的两倍,因为您可以在最后运行此长度一个块和下一个的开始)

5 个答案:

答案 0 :(得分:3)

  

问题:你知道的算法是否允许最小化   连续运行其中一个或两个属性,或至少保留这些属性   低于指定的长度?

是。有一个简单的算法可以通过简单地降低颜色或大小的选择概率(如果它已经在运行中发生)来实现。

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

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

答案 1 :(得分:1)

你显然不关心真正的随机性,所以如果你定义一个距离度量,并随机绘制你的序列,你可以拒绝任何新的绘制,如果它的距离“太接近”上一次绘制,并且只是再画一次。

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

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

答案 2 :(得分:1)

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

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

    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(未在代码段中实现)

    修改

    • 您可以在has_consecutive_keys中使用groupby(但不进行排序!)

答案 3 :(得分:1)

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

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

答案 4 :(得分:0)

抱歉,这不是答案,但很难在评论中发布代码。这是编写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)))

当我理解你的问题时,我会尝试将其转化为答案:)