【问题标题】:Algorithm to shuffle a list to minimise equal neighbours洗牌列表以最小化相等邻居的算法
【发布时间】:2015-06-17 02:08:36
【问题描述】:

我想改组这样的列表:-

to_shuffle = [ a, b, b, b, b, a, c, b, a, b ]

尽量减少重复元素的数量。最初,我 考虑从to_shuffle 顶部弹出元素,或者 如果元素是,则将它们推送到另一个列表 shuffled 与先前推送的元素不同,否则将其推送到 to_shuffle 的底部并尝试另一个元素。这将导致 在:-

shuffled = [ a, b, a, c, b, a, b, b, b, b ]

在这个例子中,也好不到哪里去 - 仍然有 4 个 b 连续(尽管这种方法有时减少了重复元素)。

我当时的想法是首先为每个类别制作一个桶 元素:-

buckets = [ (a, [a, a, a]), (b, [b, b, b, b, b, b]), (c, [c]) ]

按大小对桶排序,降序

buckets = [ (b, [b, b, b, b, b, b]), (a, [a, a, a]), (c, [c]) ]

跟踪最后一个洗牌的元素

last = None

在桶中循环,从最大的开始,然后弹出一个 如果不等于last 的元素,请使用存储桶并再次执行:-

sorted = [ b ]

buckets = [ (b, [b, b, b, b, b]), (a, [a, a, a]), (c, [c]) ] 
last = b

sorted = [ b, a ]

buckets = [ (b, [b, b, b, b, b]), (a, [a, a]), (c, [c]) ] 
last = a

sorted = [ b, a, b ]

buckets = [ (b, [b, b, b, b]), (a, [a, a]), (c, [c]) ] 
last = b

sorted = [ b, a, b, a ]

buckets = [ (b, [b, b, b, b]), (a, [a]), (c, [c]) ] 
.
.
.
sorted = [ b, a, b, a, b, a, b, c, b, b ]

这是一个更好的结果。

此算法有名称吗?如果有,是否有 python (2.7) 实现?

这是一些相当粗制滥造的代码:-

test = [ 'a', 'b', 'b', 'b', 'b', 'a', 'c', 'b', 'a', 'b' ]
expected = [ 'b', 'a', 'b', 'a', 'b', 'a', 'b', 'c', 'b', 'b' ]

def sort_buckets(buckets):
    return sorted(buckets, key=lambda x: len(x[1]), reverse=True)

def make_buckets(to_shuffle):
    h = {}
    buckets = []
    for e in to_shuffle:
        if e not in h:
            h[e] = []
        h[e].append(e)
    for k, elems in h.iteritems():
        buckets.append((k, elems))
    return buckets

def shuffle(to_shuffle):
    buckets = make_buckets(to_shuffle)
    shuffled = []
    last = ''
    while len(buckets) > 1:
        buckets = sort_buckets(buckets)
        for i in range(len(buckets)):
            candidate = buckets[i][0]
            if candidate == last:
                continue
            t = buckets.pop(i)
            last = candidate
            shuffled.append(t[1][-1])
            if len(t[1]) > 1:
                buckets.append((t[0], t[1][:-1]))
            break
    t = buckets.pop()
    shuffled += t[1]
    return shuffled

print expected
print shuffle(test)

【问题讨论】:

  • 随机性是要求的一部分吗?您要实现的并不是真正的改组,而是避免连续出现相同的字符...
  • 不,正如你所说,避免相同字符的连续出现是目标,而不是随机性。不是洗牌吗?你会怎么称呼它?
  • 我称之为“无重复重排”...当您“洗牌”一副牌时,您是否期望随机性,或者您是否会检查牌以确保牌是按你想要的顺序?

标签: python algorithm shuffle


【解决方案1】:

将它们按频率排序,然后将它们放入交替位置的新数组中,首先从左到右,然后从右到左。

所以在你给出的例子中,排序是{b,b,b,b,b,b,a,a,a,c},然后“所有其他地方”操作将它带到{b,_,b,_,b,_,b,_,b,_},然后我们继续从右边(如果结果是从左边)填充空白在更少的相同相邻项中):{b,c,b,a,b,a,b,a,b,b}。该答案中只有一对相同的相邻对。这确保了最频繁出现的项目至少出现在列表的开头(也可能是结尾),它不能在一侧出现。如果超过一半是一种类型,那么您将只有在彼此旁边有相同的项目。

在这种情况下,您希望从左侧开始填写第二遍:{a,a,b,b,c,c} => {a,_,a,_,b,_},现在如果我们从右侧填写,我们将得到一个双 b,所以我们重新开始左边:{a,b,a,c,b,c}.

【讨论】:

  • 这个算法有名字吗?它没有回答问题,但它确实很漂亮;谢谢。
  • 我很确定从左侧重新开始以填充偶数位置总是至少与从右侧“折回”一样好,有时甚至更好。使用从左开始重新启动,您将获得相邻相等对的唯一情况是如果有一个元素的频率 > RoundUp(n/2)(即严格多数),然后您将获得最小可能(即不可避免)数其中,因为任何频率较低的元素都无法绕得足够远以“触摸它的尾巴”。通过从右侧折叠,您可以获得更多。
  • 在我给出的第一个例子中,从左边开始给出两个相邻的相同对,但从左边开始只给出一个。不过,这似乎是唯一的例子。也许如果我从{_,b,_,b,_,b,_,b,_,b} 开始,则可以始终从左侧开始,但如果元素总数为奇数,则该策略也可能无法完美运行。
  • @jah 关于它叫什么的好问题。我不知道。然而,在纸牌魔术中使用了类似的排列,称为 in-shuffles 和 out-shuffles。 Persi Diaconis 和 Ron Graham 在他们的书中处理了这个主题。
  • 哦,我认为我从左边开始,后半部分从右边开始的排列被称为“牛奶洗牌”。
【解决方案2】:

你的算法很容易理解,我相信它是正确的。使用 Python 的内置 Counter 集合可以使其更易于阅读。

from collections import Counter

def careful_shuffle(lst):
    '''Returns a new list based on a given iterable, with the elements shuffled
    such that the number of duplicate consecutive elements are minimized.'''

    c = Counter(lst)
    if len(c) < 2:
        # Return early if it's trivial.
        return lst

    output = []
    last = None
    while True:
        # All we need are the current 2 most commonly occurring items. This
        # works even if there's only 1 or even 0 items left, because the
        # Counter object will still return the requested number of results,
        # with count == 0.
        common_items = c.most_common(2)
        avail_items = [key for key, count in common_items if count]

        if not avail_items:
            # No more items to process.
            break

        # Just reverse the list if we just saw the first item. This simplies
        # the logic in case we actually only have 1 type of item left (in which
        # case we have no choice but to choose it).
        if avail_items[0] == last:
            avail_items.reverse()

        # We've found the next item. Add it to the output and update the
        # counter.
        next_item = avail_items[0]
        output.append(next_item)
        c[next_item] -= 1
        last = next_item

    return output

【讨论】:

  • 谢谢,这确实更清楚了。您的实现确实产生了预期的元素序列,而最终的 ac 元素在我的中互换了。它有名字吗?
  • @jah:不幸的是,我不知道。
猜你喜欢
  • 2011-10-07
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2020-06-21
  • 2011-03-12
  • 1970-01-01
  • 2015-02-15
  • 2021-06-22
相关资源
最近更新 更多