python排序与相邻差异

时间:2013-09-24 10:36:33

标签: python sorting

我有一个项目列表

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将与邻居保持重复。

有没有快速的方法对此进行排序?

修改

为了说清楚,给它一个真实的例子,在笔记本电脑类别中,我有来自IBM的100个产品,来自Acer的10个产品,来自Apple的6个产品,我想要将相同的品牌排序为混合可能的。

例如,

未列出的列表我有

[{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}]

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

由于

1 个答案:

答案 0 :(得分:5)

第二次编辑

好的,现在我明白了。当它真的不那样时,你就把这听起来像是一个洗牌。这是一个答案,更多的参与。

首先,我想介绍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())

<!/ P>

修改

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

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