相对于多级键进行排序,其中每个级别的顺序不同



我有一个项目集合,这些项目具有多个属性,这些属性由元组表示:

items = [(a11, a12, ..., a1N), (a21, a22, ..., a2N), ...]

现在我想对这些项目进行排序,但是每个级别的顺序可能会有所不同(例如aX1上升,aX2下降,...,aXN上升)。关键是这些属性不是可逆的(例如文本;否则key可以构造为key=lambda a: (a[0], -a[1], ..., a[N]))。

例如,对于 2 元组:

items = [('a', 'c'), ('b', 'a'), ('a', 'b'), ('b', 'b'), ('a', 'a')]
reverse = (True, False)  # first level descending, second level ascending
expected = [('b', 'a'), ('b', 'b'), ('a', 'a'), ('a', 'b'), ('a', 'c')]

其他具有 3 元组的示例:

items = [('a', 'b', 'c'), ('a', 'c', 'b'), ('a', 'b', 'b'), ('b', 'a', 'a'), ('b', 'a', 'b'), ('b', 'b', 'b')]
reverse = (True, False, True)
expected = [('b', 'a', 'b'), ('b', 'a', 'a'), ('b', 'b', 'b'), ('a', 'b', 'c'), ('a', 'b', 'b'), ('a', 'c', 'b')]

我想过创建一个伪逆,例如通过str.translate,但显然这也有其缺点,因为需要跟踪所有边缘情况。

假设元组中的所有项目都是字符串字符,则可以将它们编码为十六进制字符串,其中字符c将设置为format(ord(c), 'x')(如果reverseTrue则)和format(127 - order(c), 'x')如果reverseTrue。现在您已经有了元组的编码字符串,您可以使用sorted执行常规字符串排序

items = [('a', 'b', 'c'), ('a', 'c', 'b'), ('a', 'b', 'b'), ('b', 'a', 'a'), ('b', 'a', 'b'), ('b', 'b', 'b')]
reverse = (True, False, True)
items_sorted = sorted(items, key = lambda x: "".join([format(127 - ord(x[i]), "x") if reverse[i] else format(ord(x[i]), "x") for i in range(len(x))]))

这是一个解决方案,它将分别TrueFalsereverse作为-11应用于每个属性的序号版本 - 因为字符串属性很可能不止一个字符。这个想法是创建一个可以传递给sorted()key函数。

编辑:这是multilevel_order应用不同反转规则的版本(请注意名称更改):

def multilevel_order(reverse):
multipliers = tuple(-1 if trufal else 1 for trufal in reverse)
def key_func(item):
return tuple(tuple(map(m.__mul__, map(ord, attr))) for attr, m in zip(item, multipliers))
return key_func

结果:

items = [('a', 'b', 'c'), ('a', 'c', 'b'), ('a', 'b', 'b'),
('b', 'a', 'a'), ('b', 'a', 'b'), ('b', 'b', 'b')]
reverse = (True, False, True)
expected = [('b', 'a', 'b'), ('b', 'a', 'a'), ('b', 'b', 'b'),
('a', 'b', 'c'), ('a', 'b', 'b'), ('a', 'c', 'b')]
print(sorted(items, key=multilevel_order(reverse)) == expected)
# True
items = [('a', 'c'), ('b', 'a'), ('a', 'b'), ('b', 'b'), ('a', 'a')]
reverse = (True, False)  # first level descending, second level ascending
expected = [('b', 'a'), ('b', 'b'), ('a', 'a'), ('a', 'b'), ('a', 'c')]
print(sorted(items, key=multilevel_order(reverse)) == expected)
# True

原始答案:我还使用了稍微修改的items值,以使ordinaled的效果更清晰。

items = [('a', 'bb', 'ccc'), ('a', 'cc', 'bbb'), ('a', 'bb', 'bbb'),
('b', 'aa', 'aaa'), ('b', 'aa', 'bbb'), ('b', 'bb', 'bbb')]
reverse = (True, False, True)
multipliers = tuple(-1 if trufal else 1 for trufal in reverse)
def multi_level_key(item):
ordinaled = (map(ord, attr) for attr in item)
# this multiplication step could have been included above
return tuple(tuple(map(m.__mul__, o)) for m, o in zip(multipliers, ordinaled))

multi_level_key()的输出示例:

[multi_level_key(item) for item in items]
# output:
[((-97,), (98, 98), (-99, -99, -99)),
((-97,), (99, 99), (-98, -98, -98)),
((-97,), (98, 98), (-98, -98, -98)),
((-98,), (97, 97), (-97, -97, -97)),
((-98,), (97, 97), (-98, -98, -98)),
((-98,), (98, 98), (-98, -98, -98))]

使用它作为sorted()key

sorted(items, key=multi_level_key)
# output:
[('b', 'aa', 'bbb'),
('b', 'aa', 'aaa'),
('b', 'bb', 'bbb'),
('a', 'bb', 'ccc'),
('a', 'bb', 'bbb'),
('a', 'cc', 'bbb')]

潜在的改进:我multipliers移出了键函数,以便只计算一次。理想情况下,应该将其放在闭包中以multi_level_key()以便它可以与其他反转规则一起使用,而无需编写新函数。

最终,我最终使用以下实现,该实现使用itertools.groupby来识别已排序级别中的联系,然后递归地对相应的组进行排序。这样做的好处是它完全依赖于内置的字符串比较方法。

import itertools as it
import operator as op
def sort_one_level(data, *, reverse, level=0):
result = sorted(data, reverse=reverse[level])
if level + 1 < len(reverse):
result = it.chain.from_iterable(
sort_one_level(group, reverse=reverse, level=level+1)
for __, group in it.groupby(result, key=op.itemgetter(level))
)
yield from result

使用反向字母来反转字符串。

法典

from string import printable     # all the printable characters
def tuple_sort(items, rev):
# Create translation table for printable characters
trans = str.maketrans(printable, printable[::-1], ' ') 
# key function forms list with inverted tuple items based upon rev
return sorted(items, key = lambda item: [s.translate(trans) if flag else s for s, flag in zip(item, rev)])

测试

items = [('a', 'c'), ('b', 'a'), ('a', 'b'), ('b', 'b'), ('a', 'a')]
reverse = (True, False) 
tuple_sort(items, reverse)
# Result: [('b', 'a'), ('b', 'b'), ('a', 'a'), ('a', 'b'), ('a', 'c')]

最新更新