生成列表的所有排列,而没有相邻的相等元素


87

当我们对列表进行排序时,例如

a = [1,2,3,3,2,2,1]
sorted(a) => [1, 1, 2, 2, 2, 3, 3]

相等的元素在结果列表中始终相邻。

如何完成相反的任务-整理列表,使相等的元素永远不会(或尽可能少)相邻?

例如,对于上面的列表,一种可能的解决方案是

p = [1,3,2,3,2,1,2]

更正式地说,给定一个列表a,生成p它的排列,使对的数量最小化p[i]==p[i+1]

由于列表很大,因此不能选择生成和过滤所有排列。

额外的问题:如何有效地生成所有此类排列?

这是我用来测试解决方案的代码:https : //gist.github.com/gebrkn/9f550094b3d24a35aebd

UPD:在这里选择获胜者是一个艰难的选择,因为许多人都给出了很好的答案。@VincentvanderWeele@大卫Eisenstat@Coady@ enrico.bacis@srgerg提供完美产生最佳的置换函数。@tobias_k和David也回答了奖金问题(生成所有排列)。大卫还提供了正确性证明。

@VincentvanderWeele的代码似乎是最快的。


1
所以你只在乎平等吗?类似的东西[1, 2, 1, 3, 1, 4, 1, 5][1, 3, 1, 2, 1, 4, 1, 5]你的标准?
Bakuriu 2014年

1
不可能有“有效”的算法。取像一个列表[1, 1, 1, ..., 2, 3, 4, ..., N]2N元素。您可以n > 1在每对连续的数字之间放置一个数字1以获得良好的排列。然后,您对N/2元素进行置换并获得所有有效的置换(这意味着没有一个是不好的置换,但可能还有更多)。这样的排列数为O(N ^ 2),因此您做得不会比O(N ^ 2)好。但是仍然比幼稚方法的O(N ^ 3)好。
Bakuriu 2014年

6
@Bakuriu:两件事情:(1)要清楚,您的示例显示了关于奖金问题的有效算法。(2)列举示例的所有最优解是O((N / 2)!),它比O(N ^ 2)差得多(即示例比您意识到的要强得多:-)
j_random_hacker

11
@msw:我正在建立一个网站,并排有来自不同提供商的广告块。我想安排它们,以使来自同一提供者的任何块并排站立。
乔治2014年

2
我不会说这“甚至不接近重复项”,但是所谓的重复项是一个不同的问题,因为考虑了相同元素之间的距离。在WhyCry发表评论后投反对票的人:以后请多加注意。
David Eisenstat 2014年

Answers:


30

这与Thijser当前不完整的伪代码是一致的。这个想法是采取剩余物品类型中最频繁的一种,除非它是刚采取的。(另请参见Coady对该算法的实现。)

import collections
import heapq


class Sentinel:
    pass


def david_eisenstat(lst):
    counts = collections.Counter(lst)
    heap = [(-count, key) for key, count in counts.items()]
    heapq.heapify(heap)
    output = []
    last = Sentinel()
    while heap:
        minuscount1, key1 = heapq.heappop(heap)
        if key1 != last or not heap:
            last = key1
            minuscount1 += 1
        else:
            minuscount2, key2 = heapq.heappop(heap)
            last = key2
            minuscount2 += 1
            if minuscount2 != 0:
                heapq.heappush(heap, (minuscount2, key2))
        output.append(last)
        if minuscount1 != 0:
            heapq.heappush(heap, (minuscount1, key1))
    return output

正确性证明

对于计数为k1和k2的两种项目,最优解在k1 <k2时具有k2- k1-1缺陷,在k1 = k2时具有0缺陷,而在k1> k2时则具有k1- k2-1缺陷。=的情况很明显。其他是对称的。少数元素的每个实例最多可防止总共k1 + k2-1中的两个缺陷。

该贪婪算法通过以下逻辑返回最优解。如果前缀(部分解决方案)扩展到最佳解决方案,则称其为安全的。显然,空前缀是安全的,并且如果安全前缀是完整的解决方案,则该解决方案是最佳的。足以归纳地表明每个贪婪的步骤都可以确保安全。

贪婪步骤引入缺陷的唯一方法是仅保留一种物料类型,在这种情况下,只有一种方法可以继续,并且这种方法是安全的。否则,让P为所考虑步骤之前的(安全)前缀,让P'为紧随其后的前缀,让S为扩展P的最优解。如果S也扩展P',那么我们就完成了。否则,令P'= Px且S = PQ和Q = yQ',其中x和y为项目,Q和Q'为序列。

首先假设P不以y结尾。根据算法的选择,x在Q中的频率至少与y相同。考虑仅包含x和y的Q的最大子串。如果第一个子字符串至少具有与y一样多的x,则可以重写它而不会引入以x开头的其他缺陷。如果第一个子字符串的y比x的多,那么其他一些子字符串的x则比y的多,我们可以重写这些子字符串而没有其他缺陷,因此x优先。在两种情况下,我们都根据需要找到扩展P'的最优解T。

现在假设P以y结尾。通过将x的第一个出现位置移到最前面来修改Q。这样做时,我们最多引入一个缺陷(x曾经是)并消除一个缺陷(yy)。

生成所有解决方案

这是tobias_k的答案,加上有效的测试,以检测当前正在考虑的选择何时在某种程度上受到全局限制。渐近运行时间是最佳的,因为生成的开销大约是输出长度的数量。不幸的是,最坏情况下的延迟是二次的。可以使用更好的数据结构将其减少为线性(最佳)。

from collections import Counter
from itertools import permutations
from operator import itemgetter
from random import randrange


def get_mode(count):
    return max(count.items(), key=itemgetter(1))[0]


def enum2(prefix, x, count, total, mode):
    prefix.append(x)
    count_x = count[x]
    if count_x == 1:
        del count[x]
    else:
        count[x] = count_x - 1
    yield from enum1(prefix, count, total - 1, mode)
    count[x] = count_x
    del prefix[-1]


def enum1(prefix, count, total, mode):
    if total == 0:
        yield tuple(prefix)
        return
    if count[mode] * 2 - 1 >= total and [mode] != prefix[-1:]:
        yield from enum2(prefix, mode, count, total, mode)
    else:
        defect_okay = not prefix or count[prefix[-1]] * 2 > total
        mode = get_mode(count)
        for x in list(count.keys()):
            if defect_okay or [x] != prefix[-1:]:
                yield from enum2(prefix, x, count, total, mode)


def enum(seq):
    count = Counter(seq)
    if count:
        yield from enum1([], count, sum(count.values()), get_mode(count))
    else:
        yield ()


def defects(lst):
    return sum(lst[i - 1] == lst[i] for i in range(1, len(lst)))


def test(lst):
    perms = set(permutations(lst))
    opt = min(map(defects, perms))
    slow = {perm for perm in perms if defects(perm) == opt}
    fast = set(enum(lst))
    print(lst, fast, slow)
    assert slow == fast


for r in range(10000):
    test([randrange(3) for i in range(randrange(6))])

23

伪代码:

  1. 排序清单
  2. 循环遍历排序列表的前半部分,并填充结果列表的所有偶数索引
  3. 循环遍历排序列表的后半部分,并填充结果列表的所有奇数索引

p[i]==p[i+1]当输入的一半以上由相同元素组成时,您才可以使用,在这种情况下,除了将相同元素放置在连续的点之外,别无选择(通过皮氏孔原理)。


正如评论中指出的那样,如果其中一个元素至少发生n/2几次(或n/2+1为奇数n;这种情况一般(n+1)/2)适用于偶数和奇数),则这种方法可能会发生太多冲突。最多有两个这样的元素,如果有两个,该算法就可以正常工作。唯一有问题的情况是,至少有一半时间出现一个元素。我们可以通过找到元素并首先对其进行处理来简单地解决此问题。

我对python不够了解,无法正确编写此代码,因此我自由地从github复制了OP先前版本的实现:

# Sort the list
a = sorted(lst)

# Put the element occurring more than half of the times in front (if needed)
n = len(a)
m = (n + 1) // 2
for i in range(n - m + 1):
    if a[i] == a[i + m - 1]:
        a = a[i:] + a[:i]
        break

result = [None] * n

# Loop over the first half of the sorted list and fill all even indices of the result list
for i, elt in enumerate(a[:m]):
    result[2*i] = elt

# Loop over the second half of the sorted list and fill all odd indices of the result list
for i, elt in enumerate(a[m:]):
    result[2*i+1] = elt

return result

据我了解,这就是@jojo所做的-并非总是最佳的。
乔治2014年

10
[0, 1, 1]或失败[0, 0, 1],具体取决于您使用基于0还是基于1的索引。
flornquake 2014年

@georg确实,这与我的回答是相同的方法。(请注意,Heuster在我之前回答了!)。但是,在我的代码中,将步骤2和3.结合在一起,从而优化了效率。
jojo 2014年

3
@flornquake好收获!恐怕是一个老套的错误。因此,此方法不是最佳方法,因为它可能有太多冲突。
文森特·范·德·韦勒

1
@赫斯特:所有的灯都是绿色的!“ 0个故障”。
乔治,

10

已经给出的算法将剩下的不是前一项的最常见项取为正确。这是一个简单的实现,可以最佳地使用堆来跟踪最常见的堆。

import collections, heapq
def nonadjacent(keys):
    heap = [(-count, key) for key, count in collections.Counter(a).items()]
    heapq.heapify(heap)
    count, key = 0, None
    while heap:
        count, key = heapq.heapreplace(heap, (count, key)) if count else heapq.heappop(heap)
        yield key
        count += 1
    for index in xrange(-count):
        yield key

>>> a = [1,2,3,3,2,2,1]
>>> list(nonadjacent(a))
[2, 1, 2, 3, 1, 2, 3]

关于如何不使用Python编写算法的好例子。这很简单,但是需要30分钟才能消化语法。
alex904 '16

8

您可以使用递归回溯算法生成所有“完全未排序”的排列(在相邻位置没有两个相等的元素)。实际上,生成所有排列的唯一区别是您跟踪最后一个数字并相应地排除一些解决方案:

def unsort(lst, last=None):
    if lst:
        for i, e in enumerate(lst):
            if e != last:
                for perm in unsort(lst[:i] + lst[i+1:], e):
                    yield [e] + perm
    else:
        yield []

请注意,这种形式的功能不是很有效,因为它会创建很多子列表。另外,我们可以通过首先查看约束最大的数字(计数最高的数字)来加快速度。这是仅使用counts数字的更有效的版本。

def unsort_generator(lst, sort=False):
    counts = collections.Counter(lst)
    def unsort_inner(remaining, last=None):
        if remaining > 0:
            # most-constrained first, or sorted for pretty-printing?
            items = sorted(counts.items()) if sort else counts.most_common()
            for n, c in items:
                if n != last and c > 0:
                    counts[n] -= 1   # update counts
                    for perm in unsort_inner(remaining - 1, n):
                        yield [n] + perm
                    counts[n] += 1   # revert counts
        else:
            yield []
    return unsort_inner(len(lst))

您可以使用它来生成next完美的排列,或者list保留所有排列。但请注意,如果没有完全未排序的排列,则此生成器将因此不产生任何结果。

>>> lst = [1,2,3,3,2,2,1]
>>> next(unsort_generator(lst))
[2, 1, 2, 3, 1, 2, 3]
>>> list(unsort_generator(lst, sort=True))
[[1, 2, 1, 2, 3, 2, 3], 
 ... 36 more ...
 [3, 2, 3, 2, 1, 2, 1]]
>>> next(unsort_generator([1,1,1]))
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
StopIteration

为了规避此问题,您可以将其与其他答案中提出的一种算法一起用作备用。如果有的话,这将保证返回一个完全未排序的排列,否则将返回一个很好的近似值。

def unsort_safe(lst):
    try:
        return next(unsort_generator(lst))
    except StopIteration:
        return unsort_fallback(lst)

这将使用O(N ^ 2)内存...对于置换中的每个元素,您都在为递归调用创建列表的副本。而且,由于是递归的,因此失败的长度很小。
Bakuriu 2014年

@Bakuriu同意,这就是我的意思,“未针对效率进行优化” ...虽然我不得不承认我没有除O(n ^ 2)空间,但你是对的...我会尝试改善它。
tobias_k 2014年

当您有诸如之类的复活时,O(N ^ 2)总是落后T(n+1) = something + T(n)
Bakuriu 2014年

@tobias_k:您可以仅发布一个函数进行测试吗?
乔治2014年

@georg Sure:next(unsort2(collections.Counter(a)));-)但是,由于这种算法会产生所有可能性,所以为什么不检查所有可能性呢?对于这7个元素的测试列表,它只有38个。
tobias_k 2014年

5

在python中,您可以执行以下操作。

考虑到您有一个排序列表l,可以执行以下操作:

length = len(l)
odd_ind = length%2
odd_half = (length - odd_ind)/2
for i in range(odd_half)[::2]:
    my_list[i], my_list[odd_half+odd_ind+i] = my_list[odd_half+odd_ind+i], my_list[i]

这些只是就地操作,因此应该相当快(O(N))。请注意,您将从转换为l[i] == l[i+1]l[i] == l[i+2]因此最终获得的顺序不是随机的,而是从我的理解角度出发,这不是您要查找的随机性。

想法是在中间拆分已排序的列表,然后交换两部分中的所有其他元素。

为此l= [1, 1, 1, 2, 2, 3, 3, 4, 4, 5, 5]导致l = [3, 1, 4, 2, 5, 1, 3, 1, 4, 2, 5]

l[i] == l[i + 1]一旦一个元素的丰度大于或等于列表长度的一半,该方法就无法消除所有问题。

只要最频繁的元素的丰度小于列表大小的一半,上述方法就可以正常工作,但以下函数还可以处理极限情况(著名的一次性问题),在该情况下,其他所有元素都以第一个必须是最丰富的一个:

def no_adjacent(my_list):
    my_list.sort()
    length = len(my_list)
    odd_ind = length%2
    odd_half = (length - odd_ind)/2
    for i in range(odd_half)[::2]:
        my_list[i], my_list[odd_half+odd_ind+i] = my_list[odd_half+odd_ind+i], my_list[i]

    #this is just for the limit case where the abundance of the most frequent is half of the list length
    if max([my_list.count(val) for val in set(my_list)]) + 1 - odd_ind > odd_half:
        max_val = my_list[0]
        max_count = my_list.count(max_val)
        for val in set(my_list):
            if my_list.count(val) > max_count:
               max_val = val
               max_count = my_list.count(max_val)
        while max_val in my_list:
            my_list.remove(max_val)
        out = [max_val]
        max_count -= 1
        for val in my_list:
            out.append(val)
            if max_count:
                out.append(max_val)
                max_count -= 1
        if max_count:
            print 'this is not working'
            return my_list
            #raise Exception('not possible')
        return out
    else:
        return my_list

谢谢!这失败了[3, 2, 1, 2, 1, 3, 2](返回[2, 1, 3, 1, 2, 2, 3],应该是(3, 2, 1, 2, 1, 3, 2))-见要点
乔治

@georg抱歉,我不好意思我忘记了+1。现在再试一次。
jojo 2014年

仍然有问题,[1, 3, 3, 3, 3, 1, 1]=>[3, 1, 3, 3, 1, 3, 1]
乔治

正如我所指出的,@ georg可以工作,只要最丰富的列表少于列表长度的一半即可,在本示例中不是这种情况。
jojo 2014年

@georg因此,我添加了处理“一对一”错误的部分。尽管仅在极少数情况下会运行此部分,但它并不是特别快(与Thijser建议的算法大致相同)。
jojo 2014年

5

这是一个很好的算法:

  1. 首先,计算所有数字出现的频率。将答案放在地图上。

  2. 将此地图排序,以便最常出现的数字在前。

  3. 您的答案的第一个数字是排序图中的第一个数字。

  4. 缩小地图,现在缩小第一个。

如果要提高效率,请寻找提高分拣步骤效率的方法。


是的,这就是@tobias_k所做的。似乎运作良好!
乔治

@georg有点不同...我仅使用计数器来减少空间复杂性,但是我没有按任何特定顺序测试数字(以为这可能是另一种提速方法)。不同的是,我的解决方案始终会产生所有“完美”的排列(如果有的话),而这应该会产生最佳(?)解决方案(完美与否)。
tobias_k 2014年

3
这个伪代码不太正确;如果项目计数为5 x,2 y,2 z,那么它将不必要地将x放在一起。请参阅我的答案以解决问题。
David Eisenstat 2014年

1
同意 对于例如[1,1,1,2,3],这将产生例如[1,1,2,1,3],而不是[1,2,1,3,1]。
tobias_k

步骤3实际上适得其反。如果一个数字是通用的(至少比下一个最频繁的数字多两个条目),则第3步将连续两次使用该数字,而无需任何理由。
MSalters

5

回答红利问题:这是一种算法,它可以找到集合中所有相邻元素都不可能相同的置换。我认为从概念上讲,这是最有效的算法(尽管其他算法在实践中可能会更快,因为它们可以转换为更简单的代码)。它不使用蛮力,仅生成唯一的排列,并且不会导致解决方案的路径最早被切断。

我将使用术语“丰富元素”表示集合中某个元素的发生频率,该元素的出现频率高于所有其他元素的总和,而术语“丰富”则表示丰富元素的数量减去其他元素的数量。
例如,集合abac没有丰富的元素,集合abacaaabcaa具有a作为丰富的元素,分别为1和2。

  1. 从类似的集开始:

aaabbcd

  1. 将第一次出现与重复中分开:

第一:abcd
重复:aab

  1. 找到重复中的丰富元素(如果有的话),然后计算丰度:

丰富元素:丰富元素
:1

  1. 生成第一个的所有排列,其中在丰富元素之后的元素数量不少于丰度:(因此,在示例中,“ a”不能为倒数)

abcd,abdc,acbd,acdb,adbc,adcb,bacd,badc,bcad,bcda,bdac,bdca
cabd,cadb,cbad,cbda,cdab,cdba,dabc,dacb,abac,dbca,dcab,dcba

  1. 对于每个排列,请遵循以下规则逐个插入重复字符集:

5.1。如果集合的丰度大于到目前为止在排列中最后一次出现的丰富元素之后的元素数量,请跳到下一个排列。
例如,当到目前为止的排列是时abca只有当丰度为2或小于2时,才能插入元素丰富的集合,所以aaaabc可以,aaaaabc不是。

5.2。从集合中选择在排列中最后出现的元素排在最前面的元素。
例如,当到目前为止的排列是abcba并且设置为时ab,选择b

5.3。将所选元素插入到排列中最后一次出现的位置的至少2个位置。
例如,当插入b置换时babca,结果为babcbababcab

5.4。递归执行第5步,并获取每个生成的排列和其余的集合。

EXAMPLE:
set = abcaba
firsts = abc
repeats = aab

perm3  set    select perm4  set    select perm5  set    select perm6

abc    aab    a      abac   ab     b      ababc  a      a      ababac  
                                                               ababca  
                                          abacb  a      a      abacab  
                                                               abacba  
                     abca   ab     b      abcba  a      -
                                          abcab  a      a      abcaba  
acb    aab    a      acab   ab     a      acaba  b      b      acabab  
                     acba   ab     b      acbab  a      a      acbaba  
bac    aab    b      babc   aa     a      babac  a      a      babaca  
                                          babca  a      -
                     bacb   aa     a      bacab  a      a      bacaba  
                                          bacba  a      -  
bca    aab    -
cab    aab    a      caba   ab     b      cabab  a      a      cababa  
cba    aab    -

该算法生成唯一的排列。如果您想知道排列的总数(aba由于可以切换a的原因,在此计数两次),请将唯一排列的数目乘以一个因子:

F = N 1!* N 2!* ... * N n

其中N是集合中每个元素的出现次数。对于一套abcdabcaba这将是4!* 3!* 2!* 1!或288,说明了生成所有排列而不是唯一排列的算法的效率如何。要列出这种情况下的所有排列,只需列出唯一排列288次:-)

以下是Javascript中的(相当笨拙)实现;我怀疑像Python这样的语言可能更适合这种情况。运行代码片段以计算“ abracadabra”的分离排列。

// FIND ALL PERMUTATONS OF A SET WHERE NO ADJACENT ELEMENTS ARE IDENTICAL
function seperatedPermutations(set) {
    var unique = 0, factor = 1, firsts = [], repeats = [], abund;

    seperateRepeats(set);
    abund = abundance(repeats);
    permutateFirsts([], firsts);
    alert("Permutations of [" + set + "]\ntotal: " + (unique * factor) + ", unique: " + unique);

    // SEPERATE REPEATED CHARACTERS AND CALCULATE TOTAL/UNIQUE RATIO
    function seperateRepeats(set) {
        for (var i = 0; i < set.length; i++) {
            var first, elem = set[i];
            if (firsts.indexOf(elem) == -1) firsts.push(elem)
            else if ((first = repeats.indexOf(elem)) == -1) {
                repeats.push(elem);
                factor *= 2;
            } else {
                repeats.splice(first, 0, elem);
                factor *= repeats.lastIndexOf(elem) - first + 2;
            }
        }
    }

    // FIND ALL PERMUTATIONS OF THE FIRSTS USING RECURSION
    function permutateFirsts(perm, set) {
        if (set.length > 0) {
            for (var i = 0; i < set.length; i++) {
                var s = set.slice();
                var e = s.splice(i, 1);
                if (e[0] == abund.elem && s.length < abund.num) continue;
                permutateFirsts(perm.concat(e), s, abund);
            }
        }
        else if (repeats.length > 0) {
            insertRepeats(perm, repeats);
        }
        else {
            document.write(perm + "<BR>");
            ++unique;
        }
    }

    // INSERT REPEATS INTO THE PERMUTATIONS USING RECURSION
    function insertRepeats(perm, set) {
        var abund = abundance(set);
        if (perm.length - perm.lastIndexOf(abund.elem) > abund.num) {
            var sel = selectElement(perm, set);
            var s = set.slice();
            var elem = s.splice(sel, 1)[0];
            for (var i = perm.lastIndexOf(elem) + 2; i <= perm.length; i++) {
                var p = perm.slice();
                p.splice(i, 0, elem);
                if (set.length == 1) {
                    document.write(p + "<BR>");
                    ++unique;
                } else {
                    insertRepeats(p, s);
                }
            }
        }
    }

    // SELECT THE ELEMENT FROM THE SET WHOSE LAST OCCURANCE IN THE PERMUTATION COMES FIRST
    function selectElement(perm, set) {
        var sel, pos, min = perm.length;
        for (var i = 0; i < set.length; i++) {
            pos = perm.lastIndexOf(set[i]);
            if (pos < min) {
                min = pos;
                sel = i;
            }
        }
        return(sel);
    }

    // FIND ABUNDANT ELEMENT AND ABUNDANCE NUMBER
    function abundance(set) {
        if (set.length == 0) return ({elem: null, num: 0});
        var elem = set[0], max = 1, num = 1;
        for (var i = 1; i < set.length; i++) {
            if (set[i] != set[i - 1]) num = 1
            else if (++num > max) {
                max = num;
                elem = set[i];
            }
        }
        return ({elem: elem, num: 2 * max - set.length});
    }
}

seperatedPermutations(["a","b","r","a","c","a","d","a","b","r","a"]);


1
谢谢你!将在javascript中查看是否可以将其缩短。
stt106

4

想法是将元素从最常见到最不常见进行排序,采用最常见的元素,减少其数量,并按降序排列回到列表中(但尽可能避免将最后使用的元素放在首位以防止重复) 。

可以使用Counter和实现bisect

from collections import Counter
from bisect import bisect

def unsorted(lst):
    # use elements (-count, item) so bisect will put biggest counts first
    items = [(-count, item) for item, count in Counter(lst).most_common()]
    result = []

    while items:
        count, item = items.pop(0)
        result.append(item)
        if count != -1:
            element = (count + 1, item)
            index = bisect(items, element)
            # prevent insertion in position 0 if there are other items
            items.insert(index or (1 if items else 0), element)

    return result

>>> print unsorted([1, 1, 1, 2, 3, 3, 2, 2, 1])
[1, 2, 1, 2, 1, 3, 1, 2, 3]

>>> print unsorted([1, 2, 3, 2, 3, 2, 2])
[2, 3, 2, 1, 2, 3, 2]

例如,此操作将失败:[1, 1, 2, 3]存在诸如之类的解决方案[1, 2, 1, 3]
Bakuriu 2014年

是的,我才意识到,对不起
enrico.bacis 2014年

谢谢!这并不总是产生最佳结果,例如,当[1, 2, 3, 2, 3, 2, 2]它返回时[2, 3, 1, 2, 3, 2, 2](1个错误),而理想情况是(2, 1, 2, 3, 2, 3, 2))-请参见要点。
乔治2014年

@georg是的,不错,我已经对其进行了更新,保留了其使用的简单原理。
enrico.bacis 2014年

@ enrico.bacis:谢谢!新版本可完美运行。我已经更新了要点。太可惜了,我不能再支持你了。
2014年

2
  1. 排序列表。
  2. 使用此算法生成列表的“最佳混洗”

它将在列表中保留其原始位置中的最少项目(按项目值),因此,例如,您尝试将1、2和3远离其排序位置。


我已经尝试过best_shuffle并且生成了[1,1,1,2,3] -> [3, 1, 2, 1, 1]-不理想!
乔治,

2

从长度为n的排序列表开始。令m = n / 2。取值为0,然后是m,然后是1,然后是m + 1,然后是2,然后是m + 2,依此类推。除非您有一半以上的数字相同,否则您将永远无法获得连续顺序的等效值。


谢谢你的主意。我认为这是@Heuster实现的。
2014年

2

请原谅我的“我也是”风格的答案,但是Coady的答案不能简化为此吗?

from collections import Counter
from heapq import heapify, heappop, heapreplace
from itertools import repeat

def srgerg(data):
    heap = [(-freq+1, value) for value, freq in Counter(data).items()]
    heapify(heap)

    freq = 0
    while heap:
        freq, val = heapreplace(heap, (freq+1, val)) if freq else heappop(heap)
        yield val
    yield from repeat(val, -freq)

编辑:这是一个返回列表的python 2版本:

def srgergpy2(data):
    heap = [(-freq+1, value) for value, freq in Counter(data).items()]
    heapify(heap)

    freq = 0
    result = list()
    while heap:
        freq, val = heapreplace(heap, (freq+1, val)) if freq else heappop(heap)
        result.append(val)
    result.extend(repeat(val, -freq))
    return result

是的,这似乎工作正常(除了我在py2上,并且该函数应返回列表)。
乔治2014年

@georg好,我添加了一个返回列表的python 2版本。
srgerg 2014年

2
  1. 计算每个值出现的次数
  2. 按从最频繁到最不频繁的顺序选择值
  3. 将选择的值添加到最终输出中,每次使索引增加2
  4. 如果索引超出范围,则将索引重置为1
from heapq import heapify, heappop
def distribute(values):
    counts = defaultdict(int)
    for value in values:
        counts[value] += 1
    counts = [(-count, key) for key, count in counts.iteritems()]
    heapify(counts)
    index = 0
    length = len(values)
    distributed = [None] * length
    while counts:
        count, value = heappop(counts)
        for _ in xrange(-count):
            distributed[index] = value
            index = index + 2 if index + 2 < length else 1
    return distributed
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.