我有一个项目清单

item = [a,a,a,b,b,b,b,c,c,c,c,c,e,e,e,e,e,e]


我想按混合顺序对其进行排序,因此相邻对象最多允许重复两次,例如

[a,a,b,a,b,b,c,c,b,b,c,e,c,c,e,e,e,e,e]


因为没有更多的项目可以与e一起洗牌,所以e与相邻项目将保持重复。

有什么快速的方法可以解决这个问题吗?

编辑

为了清楚起见,举一个真实的例子,在笔记本电脑类别中,我有100种IBM产品,10种Acer产品,6种Apple产品,我希望对相同的品牌进行分类以尽可能地混淆。

例如,
我有未排序的清单

[{brand:"ibm", "id":1},{brand:"ibm", "id":2},{brand:"ibm", "id":3},{brand:"ibm", "id":4},{brand:"ibm", "id":5},{brand:"ibm", "id":6},{brand:"acer", "id":7},{brand:"acer", "id":8},{brand:"acer", "id":9},{brand:"acer", "id":10},{brand:"apple", "id":11},{brand:"apple", "id":12}]


目标结果,只要同一个品牌彼此不相邻,例如前10个都来自同一个品牌,但可以2-3个同一个品牌相邻,

[{brand:"ibm", "id":1},,{brand:"acer", "id":7},{brand:"ibm", "id":2},{brand:"ibm", "id":3},{brand:"acer", "id":8},{brand:"apple", "id":12}{brand:"ibm", "id":4},{brand:"acer", "id":9},{brand:"ibm", "id":5},{brand:"ibm", "id":6},{brand:"acer", "id":10}]


最好不使用随机数,而是使用确定性排序,因此每次用户仍然看到相同的顺序时,但这不是必须的,因为可以将其保存到缓存中。

谢谢

最佳答案

第二编辑

好吧,现在我明白了。当您并非真正喜欢这种声音时,您将其听起来像混洗。这是一个答案,涉及更多。

首先,我要介绍pprint。这只是print的一个版本,可以很好地格式化内容:

from pprint import pprint
pprint(items)
#>>> [{'brand': 'ibm', 'id': 1},
#>>>  {'brand': 'ibm', 'id': 2},
#>>>  {'brand': 'ibm', 'id': 3},
#>>>  {'brand': 'ibm', 'id': 4},
#>>>  {'brand': 'ibm', 'id': 5},
#>>>  {'brand': 'ibm', 'id': 6},
#>>>  {'brand': 'acer', 'id': 7},
#>>>  {'brand': 'acer', 'id': 8},
#>>>  {'brand': 'acer', 'id': 9},
#>>>  {'brand': 'acer', 'id': 10},
#>>>  {'brand': 'apple', 'id': 11},
#>>>  {'brand': 'apple', 'id': 12}]


有了这些,我们就可以开始了。

我们要按品牌分组项目:

from collections import defaultdict

brand2items = defaultdict(list)

for item in items:
    brand2items[item["brand"]].append(item)

pprint(brand2items)
#>>> {'acer': [{'brand': 'acer', 'id': 7},
#>>>           {'brand': 'acer', 'id': 8},
#>>>           {'brand': 'acer', 'id': 9},
#>>>           {'brand': 'acer', 'id': 10}],
#>>>  'apple': [{'brand': 'apple', 'id': 11}, {'brand': 'apple', 'id': 12}],
#>>>  'ibm': [{'brand': 'ibm', 'id': 1},
#>>>          {'brand': 'ibm', 'id': 2},
#>>>          {'brand': 'ibm', 'id': 3},
#>>>          {'brand': 'ibm', 'id': 4},
#>>>          {'brand': 'ibm', 'id': 5},
#>>>          {'brand': 'ibm', 'id': 6}]}


然后我们可以获取值,因为我们不在乎密钥:

items_by_brand = list(brand2items.values())

pprint(items_by_brand)
#>>> [[{'brand': 'apple', 'id': 11}, {'brand': 'apple', 'id': 12}],
#>>>  [{'brand': 'ibm', 'id': 1},
#>>>   {'brand': 'ibm', 'id': 2},
#>>>   {'brand': 'ibm', 'id': 3},
#>>>   {'brand': 'ibm', 'id': 4},
#>>>   {'brand': 'ibm', 'id': 5},
#>>>   {'brand': 'ibm', 'id': 6}],
#>>>  [{'brand': 'acer', 'id': 7},
#>>>   {'brand': 'acer', 'id': 8},
#>>>   {'brand': 'acer', 'id': 9},
#>>>   {'brand': 'acer', 'id': 10}]]


现在我们要对结果进行交织。基本思想是,我们希望更多地从最大的资源池中提取资源,因为这将花费最长的时间。因此,每次迭代我们都希望采用最长且pop其中一项。只是我们不想重复。我们可以采用两个不同的组(两个最大的组)并对其结果进行交织来做到这一点。

当所有组中都没有剩余项目时,我们停止。

from heapq import nlargest

shufflatored = []
while any(items_by_brand):
    items1, items2 = nlargest(2, items_by_brand, key=len)

    if items1: shufflatored.append(items1.pop())
    if items2: shufflatored.append(items2.pop())


heapq模块是一个鲜为人知但血腥的出色模块。实际上,只需付出一些努力,就可以通过将items_by_brand保留为堆来提高效率。但是,这样做并不值得,因为其他用于堆的工具不需要使用key,这需要晦涩的解决方法。

就是这样了。如果要允许加倍,可以替换

    if items1: shufflatored.append(items1.pop())
    if items2: shufflatored.append(items2.pop())




    if items1: shufflatored.append(items1.pop())
    if items1: shufflatored.append(items1.pop())
    if items2: shufflatored.append(items2.pop())
    if items2: shufflatored.append(items2.pop())




编辑

您想要确定性的东西吗?那么你为什么不这样说呢?

lst = list(range(20))

lst[::2], lst[1::2] = lst[1::2], lst[::2]

lst
#>>> [1, 0, 3, 2, 5, 4, 7, 6, 9, 8, 11, 10, 13, 12, 15, 14, 17, 16, 19, 18]


魔术,不是吗?

希望您了解就地交换值的此方法:

a = 1
b = 2

a, b = b, a

a
#>>> 2

b
#>>> 1


好吧,lst[::2]是其他所有值

lst[::2]
#>>> [0, 2, 4, 6, 8, 10, 12, 14, 16, 18]


lst[1::2]是其他所有其他值,

lst[1::2]
#>>> [1, 3, 5, 7, 9, 11, 13, 15, 17, 19]


因此lst[::2], lst[1::2] = lst[1::2], lst[::2]会彼此交换所有其他值!



import random

items = [1,1,1,2,2,2,2,3,3,3,3,3,4,4,4,4,4,4]

[
    iv[1] for iv in
    sorted(
        enumerate(items),
        key=lambda iv: iv[0]+random.choice([-1, 1])
    )
]

#>>> [1, 1, 2, 1, 2, 2, 3, 2, 3, 3, 3, 3, 4, 4, 4, 4, 4, 4]

[
    iv[1] for iv in
    sorted(
        enumerate(range(20)),
        key=lambda iv: iv[0]+random.choice([-1, 1])
    )
]
#>>> [0, 2, 1, 4, 3, 5, 6, 7, 9, 8, 11, 10, 12, 14, 13, 15, 17, 16, 18, 19]


这是随机混洗,因此第一个列表不会显示大多数混洗。所选择的结果是由所有可能性手工挑选的。

基本上,此算法获取一个列表并为其建立索引:

  items a b c d e f g h i j
indexes 0 1 2 3 4 5 6 7 8 9


然后,它按索引和[-1, 1]中的随机选择进行排序:

  items a b c d e f g h i j
indexes 0 1 2 3 4 5 6 7 8 9
sort by 1 0 3 2 5 4 5 6 9 8


结果是

  items b a d c f e g h j i
indexes 1 0 3 2 5 4 6 7 9 8
sort by 0 1 2 3 4 5 5 6 8 9


它被洗牌了。要更改随机播放的类型,例如说要使其或多或少地随机播放,请更改列表[-1, 1]的详细信息。您也可以尝试[-1, 0, 1][0, 1]和其他变体。



该算法分步骤进行:

indexed = enumerate(items)

shuffled = sorted(indexed, key=lambda iv: iv[0]+random.choice([-1, 1]))

# Remove the index, extract the values out again
result = [iv[1] for iv in shuffled]




现在,提高效率。

如果您很精明,您可能会意识到排序通常是O(n log n)。 Python使用TimSort,一种出色的排序算法。尽管任何比较排序(也就是比较值的排序)必须具有至少O(n log n)的上限,但是它们的下限也可以低至O(n)

这是因为对已排序列表进行排序很简单,只要您检查列表是否已排序即可。 TimSort具有“已排序”的本地化概念,当对值进行排序时,它将很快检测到。这意味着,由于它们只是经过某种程度的改组,因此TimSort会执行更接近O(kn)的操作,其中k是列表的“改组性质”,远小于log n

10-02 17:31
查看更多