将自定义排序功能推广到多列(升序/降序)

时间:2018-07-10 12:24:51

标签: python sorting itertools

该问题基于previous Stackoverflow answer。这个问题对我来说似乎太大了。

我现在有了排序功能sort_multi(lst, index_normal, index_reversed),它接受​​输入列表,还有两个参数,指示哪一列以正常顺序排序,哪一列反向:

from pprint import pprint
from itertools import groupby, chain

l = ((1, 'b'),
     (1, 'd'),
     (2, 'a'),
     (1, 'a'))

def sort_multi(lst, index_normal, index_reversed):
    return list(chain.from_iterable([sorted(list(j), key=lambda v:v[index_reversed], reverse=True) for i, j in groupby(sorted(lst), key=lambda v:v[index_normal])]))

pprint(sort_multi(l, 0, 1), width=10)  # index 0 is sorted normally, 1 in reversed order.

输出:

[(1, 'd'),
 (1, 'b'),
 (1, 'a'),
 (2, 'a')]

我想要的是扩展该功能以适用于任意个索引。例如

例如输入3列:

l = ((1, 'c', 'aa'),
     (2, 'b', 'bb'),
     (1, 'c', 'cc'),
     (1, 'd', 'dd'))

sort_multi(l, reversed=(False, True, True))

给出输出:

[(1, 'd', 'dd'),
 (1, 'c', 'cc'),
 (1, 'c', 'aa'),
 (2, 'b', 'bb')]

元组可以包含字符串,整数,...

2 个答案:

答案 0 :(得分:2)

递归方法:按第一个条件对列表进行排序。然后按该标准分组,然后使用其余标准对每个子组进行递归排序。

from operator import itemgetter
import itertools

def multisorted(seq, indices, reversed_states):
    if len(indices) == 0:
        return seq
    index = indices[0]
    reversed = reversed_states[0]
    partially_sorted_seq = sorted(seq, key = itemgetter(indices[0]), reverse=reversed_states[0])
    result = []
    for key, group in itertools.groupby(partially_sorted_seq, key=itemgetter(indices[0])):
        result.extend(multisorted(group, indices[1:], reversed_states[1:]))
    return result

d = ((1, 'c', 'aa'),
     (2, 'b', 'bb'),
     (1, 'c', 'cc'),
     (1, 'd', 'dd'))

print(multisorted(d, (0,1,2), (False, True, True)))

结果:

[(1, 'd', 'dd'), (1, 'c', 'cc'), (1, 'c', 'aa'), (2, 'b', 'bb')]

方法2。 sortsorted可以按多种标准进行开箱即用。我们只需要更改键,以使每个元组的某些元素在不平等比较方面具有相反的逻辑即可。

from functools import total_ordering

@total_ordering
class Negated:
    def __init__(self, value):
        self.value = value
    def __eq__(self, other):
        return self.value == other.value
    def __lt__(self, other):
        return self.value > other.value

def multisorted(seq, ordering):
    return sorted(seq, key = lambda t: [Negated(x) if reversed else x for x, reversed in zip(t, ordering)])

d = ((1, 'c', 'aa'),
     (2, 'b', 'bb'),
     (1, 'c', 'cc'),
     (1, 'd', 'dd'))

print(multisorted(d, (False, True, True)))

答案 1 :(得分:2)

您可以做一些更简单的事情:

from operator import itemgetter

def sort_any_multi(lst, index_order_reversed):
    for index, reverse in reversed(index_order_reversed):
       lst = sorted(lst, key=itemgetter(index), reverse=reverse)
    return lst

r = ((1, 'c', 'aa'),
     (2, 'b', 'bb'),
     (1, 'c', 'cc'),
     (1, 'd', 'dd'))

print(sort_any_multi(r, [[0, False], [1, True], [2, True]]))

送礼:

[(1, 'd', 'dd'), (1, 'c', 'cc'), (1, 'c', 'aa'), (2, 'b', 'bb')]

技巧是从最后一个优先级索引到最高优先级索引进行排序。