简体   繁体   English

如何创建具有复杂逻辑的自定义排序功能?

[英]How to create custom sort function with complicated logic?

I'm try to sort a list of objects based on some non-trivial comparison logic, but finding it difficult because in Python the custom sort function takes only 1 argument. 我尝试根据一些非平凡的比较逻辑对对象列表进行排序,但是发现比较困难,因为在Python中,自定义排序功能仅接受1个参数。 In Java, for example, the sort function would have references to object1 and object2 , making it straightforward to compare them. 例如,在Java中,sort函数将具有对object1object2引用,从而可以object2地进行比较。

class Point:
    def __init__(self, char, num, pt_type):
        self.char = char
        self.num = num
        self.pt_type = pt_type  # 'start' or 'end'

    def __str__(self):
        return str([self.char, str(self.num), self.pt_type])
    def __repr__(self):
        return str(self)

arr = [Point('C', 1, 'end'), Point('C', 9, 'start'),
       Point('B', 7, 'end'), Point('B', 2, 'end'),
       Point('A', 3, 'start'), Point('A', 6, 'start')]

def my_sort(key):
    # Sort by first element (letter). 
    #
    # If the letter is the same, fallback to sorting by the
    # 2nd element (number), but the logic of this comparison depends
    # on `pt_type`:
    #   -If Point1 and Point2 both have type 'start', pick the higher number first.
    #   -If Point1 and Point2 both have type 'end', pick the lower number first.
    #   -If Point1 and Point2 have different types, pick the 'start' type first.
    return key.char

print(sorted(arr, key=my_sort))

The expected sorted order should be: 预期的排序顺序应为:

[Point('A', 6, 'start'), Point('A', 3, 'start')
 Point('B', 2, 'end'), Point('B', 7, 'end'),
 Point('C', 9, 'start'), Point('C', 1, 'end')]

I don't know how to even start implementing the required logic, so I would be grateful for a push in the right direction. 我什至不知道如何开始实施所需的逻辑,所以我很感激朝着正确的方向前进。

I would use the following key function: 我将使用以下key功能:

class Point:
    def __init__(self, char, num, pt_type):
        self.char = char
        self.num = num
        self.pt_type = pt_type  # 'start' or 'end'

    def __str__(self):
        return str([self.char, str(self.num), self.pt_type])

    def __repr__(self):
        return str(self)


arr = [Point('C', 1, 'end'), Point('C', 9, 'start'),
       Point('B', 7, 'end'), Point('B', 2, 'end'),
       Point('A', 3, 'start'), Point('A', 6, 'start')]


def key(p):
    return p.char, int(p.pt_type != 'start'), p.num if p.pt_type == 'end' else -1 * p.num


result = sorted(arr, key=key)
print(result)

Output 输出量

[['A', '6', 'start'], ['A', '3', 'start'], ['B', '2', 'end'], ['B', '7', 'end'], ['C', '9', 'start'], ['C', '1', 'end']]

The key function creates a tuple to be used as key, the first element is the letter, the second element is 0 if the node is of type 'start', 1 if is of type 'end'. key函数创建一个元组用作键,如果节点的类型为“ start”,则第一个元素为字母,第二个元素为0;如果类型为“ end”,则第二个元素为1。 The last element is negative if it is of type 'start', positive if it is of type 'end'. 如果最后一个元素的类型为“开始”,则为负;如果最后一个元素的类型为“结束”,则为正。

You can make sorting a property of your class, then use sorted . 您可以对类的属性进行排序,然后使用sorted The benefit of this method: for no additional effort, you are able to compare objects with each other via comparison operators such as > , < , == . 这种方法的好处:无需付出额外的努力,就可以通过比较运算符(例如><==相互比较对象。

Specify __eq__ and __lt__ methods 指定__eq____lt__方法

At a minimum you should specify __eq__ and __lt__ methods: 至少应指定__eq____lt__方法:

class Point:
    def __init__(self, char, num, pt_type):
        self.char = char
        self.num = num
        self.pt_type = pt_type  # 'start' or 'end'

    def __str__(self):
        return str([self.char, str(self.num), self.pt_type])

    def __repr__(self):
        return str(self)

    def __eq__(self, other):
        return self.char == other.char and self.pt_type == other.pt_type

    def __lt__(self, other):
        if self.char != other.char:
            return self.char < other.char
        if (self.pt_type == 'start') and (other.pt_type == 'start'):
            return self.num > other.num
        elif (self.pt_type == 'end') and (other.pt_type == 'end'):
            return self.num < other.num
        else:
            return self.pt_type == 'start'

Adding other comparison methods such as __gt__ , __ge__ , etc, may be simplified via functools.total_ordering : 添加其他比较方法(例如__gt____ge__等)可以通过functools.total_ordering进行简化:

from functools import total_ordering

@total_ordering
class Point:
    def __init__(self, ...):
        # initialization logic
    def __eq__(self, other):
        # as before
    def __lt__(self, other):
        # as before

Example

arr = [Point('C', 1, 'end'), Point('C', 9, 'start'),
       Point('B', 7, 'end'), Point('B', 2, 'end'),
       Point('A', 3, 'start'), Point('A', 6, 'start')]

print(sorted(arr))

[['A', '6', 'start'],
 ['A', '3', 'start'],
 ['B', '2', 'end'],
 ['B', '7', 'end'],
 ['C', '9', 'start'],
 ['C', '1', 'end']]

You want to use the cmp argument to sorted which takes a comparison function of 2 arguments: https://docs.python.org/2/library/functions.html#sorted 您想使用cmp参数进行sorted ,该参数需要2个参数的比较函数: https : //docs.python.org/2/library/functions.html#sorted

For your reference, the key function would compute a derived value from each item being sorted and sort according to that value, eg to sort a list of pairs by the second value in the pair you could do: sorted(items, key=lambda x: x[1]) 供您参考, key函数将从每个要排序的项中计算出一个派生值,并根据该值进行排序,例如, sorted(items, key=lambda x: x[1])您可以执行的对中的第二个值对对列表进行排序: sorted(items, key=lambda x: x[1])

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM