python 如何对一个有两个键但有一个键的列表进行倒序排序?

g2ieeal7  于 2023-01-16  发布在  Python
关注(0)|答案(8)|浏览(81)

我想知道用两个键对元组列表进行排序的Python方法是什么,其中用一个键(并且只有一个键)排序将是逆序的,而用另一个键排序将不区分大小写。更具体地说,我有一个包含如下元组的列表:

myList = [(ele1A, ele2A),(ele1B, ele2B),(ele1C, ele2C)]

我可以使用下面的代码用两个键对它进行排序:

sortedList = sorted(myList, key = lambda y: (y[0].lower(), y[1]))

要按逆序排序,我可以使用

sortedList = sorted(myList, key = lambda y: (y[0].lower(), y[1]), reverse = True)

但是这将用两个键以相反的顺序排序。

mzsu5hc0

mzsu5hc01#

当我们需要对一个有两个约束的列表进行排序时,将使用两个键:一个按升序排列,另一个按降序排列,在同一列表或任何
在你的例子中,

sortedList = sorted(myList, key = lambda y: (y[0].lower(), y[1]))

你可以排序整个列表只在一个顺序.
您可以尝试以下方法并检查发生了什么:

sortedList = sorted(myList, key = lambda y: (y[0].lower(), -y[1]))
sortedList = sorted(myList, key = lambda y: (-y[0].lower(), y[1]))
sortedList = sorted(myList, key = lambda y: (-y[0].lower(), -y[1]))
csga3l58

csga3l582#

你可以创建一个reversor类,用它来修饰所讨论的键,这个类可以用来反转任何可比较的字段。

class reversor:
    def __init__(self, obj):
        self.obj = obj

    def __eq__(self, other):
        return other.obj == self.obj

    def __lt__(self, other):
           return other.obj < self.obj

像这样使用它:

sortedList = sorted(myList, key=lambda y: (y[0].lower(), reversor(y[1])))
wn9m85ua

wn9m85ua3#

有时候除了使用比较器函数之外,几乎没有别的选择。在Python 2.4的介绍中,sorted有一个cmp参数,但是为了使用更高效的key函数,这个参数从Python 3中被删除了。在Python 3.2中,cmp_to_key被添加到functools中;它通过将原始对象 Package 在一个对象中来创建键,该对象的比较函数基于cmp函数。(您可以在SortingHow-To的末尾看到cmp_to_key的简单定义
在您的情况下,由于小写相对昂贵,您可能希望组合使用:

class case_insensitive_and_2nd_reversed:
    def __init__(self, obj, *args):
        self.first = obj[0].lower()
        self.second = obj[1]
    def __lt__(self, other):
        return self.first < other.first or self.first == other.first and other.second < self.second
    def __gt__(self, other):
        return self.first > other.first or self.first == other.first and other.second > self.second
    def __le__(self, other):
        return self.first < other.first or self.first == other.first and other.second <= self.second
    def __ge__(self, other):
        return self.first > other.first or self.first == other.first and other.second >= self.second
    def __eq__(self, other):
        return self.first == other.first and self.second == other.second
    def __ne__(self, other):
        return self.first != other.first and self.second != other.second

sortedList = sorted(myList, key = case_insensitive_and_2nd_reversed)
hfyxw5xn

hfyxw5xn4#

方法1

一个简单但可能不是最有效的解决方案是两次排序:第一次使用第二元素,第二次使用第一元素:

sortedList = sorted(sorted(myList, key=lambda (a,b):b, reverse=True), key=lambda(a,b):a)

或分解:

tempList = sorted(myList, key=lambda (a,b):b, reverse=True)
sortedList = sorted(tempList, key=lambda(a,b):a))

方法二

如果你的元素是数字,你可以稍微作弊一下:

sorted(myList, key=lambda(a,b):(a,1.0/b))

方法3

  • 我建议不要使用这种方法,因为它很混乱,而且cmp关键字在Python 3中不可用。*

另一种方法是在比较元素时交换元素:

def compare_func(x, y):
    tup1 = (x[0], y[1])
    tup2 = (x[1], y[0])
    if tup1 == tup2:
        return 0
    elif tup1 > tup2:
        return 1
    else:
        return -1

sortedList = sorted(myList, cmp=compare_func)

或者,使用lambda来避免编写函数:

sortedList = sorted(
    myList,
    cmp=lambda (a1, b1), (a2, b2): 0 if (a1, b2) == (a2, b1) else 1 if (a1, b2) > (a2, b1) else -1
    )
umuewwlo

umuewwlo5#

在使用Python 3的时候,@KellyBundy做了一个很好的观察,在当前的Python文档中列出的multisort方法非常快,可以用来完成离散排序的多列排序,下面是NoneType安全版本:

students = [
     {'idx': 0, 'name': 'john', 'grade': 'A', 'attend': 100}
    ,{'idx': 1, 'name': 'jane', 'grade': 'B', 'attend': 80}
    ,{'idx': 2, 'name': 'dave', 'grade': 'B', 'attend': 85}
    ,{'idx': 3, 'name': 'stu' , 'grade': None, 'attend': 85}
]

def key_grade(student):
    grade = student['grade']
    return grade is None, grade
def key_attend(student):
    attend = student['attend']
    return attend is None, attend
students_sorted = sorted(students, key=key_attend)
students_sorted.sort(key=key_grade, reverse=True)

注:

  • 为None,检查是防御性检查,因此搜索不会在None值上失败 is None, check is defensive check so that search does not fail on None values
  • 虽然,这会执行多个排序调用,但它无疑是最快的多排序方法!

我创建了一个新的Python项目multisort,它公开了三种方法:
| 方法|描述|附注|速率|
| - ------|- ------|- ------|- ------|
| 多分类|根据python文档中的multisort示例设计的简单一行程序|第二快的一堆,但最可配置和易于阅读。|0.0035分|
| cmp_函数|模型java.util.Comparator中的多列排序|合理速度|零点零一三八|
| 反向器|反向器的实现-参见Black Panda的答案|方法相当缓慢|0.0370|
供参考:
| 方法|速率|
| - ------|- ------|
| KellyBundy多重排序|0.0005分|
| Pandas|0.0079|
注:速度是1000行4列10次运行的平均值。
来自multisortlibrarymultisort示例:

from multisort import multisort
rows_sorted = multisort(rows_dict, [
        ('grade', True, lambda s:None if s is None else s.upper()),
        'attend',
], reverse=True)

然而,对于来自Java的开发人员来说,这里有一个类似于java.util.Comparator的例子,可以在Python 3中使用:

from multisort import cmp_func

def cmp_student(a,b):
    k='grade'; va=a[k]; vb=b[k]
    if va != vb:
        if va is None: return -1
        if vb is None: return 1
        return -1 if va > vb else 1
    k='attend'; va=a[k]; vb=b[k]; 
    if va != vb:
        return -1 if va < vb else 1
    return 0

students_sorted = sorted(students, key=cmp_func(cmp_student))
xdyibdwo

xdyibdwo6#

也许是优雅但不是有效的方式:

reverse_key = functools.cmp_to_key(lambda a, b: (a < b) - (a > b))
sortedList = sorted(myList, key = lambda y: (reverse_key(y[0].lower()), y[1]))
carvr3hs

carvr3hs7#

至少在我的例子中,使用不同的参数调用X.sort()两次是可能的,一次是相反的,另一次不是。我所要做的就是注意排序的优先级, -最后进行优先级较高的排序。
举个例子,我有一个字符串列表,我想按长度从长到短排序,如果字符串长度相同,再按字母顺序排序。
翻译过来就是:

lst = ["Bbbb", "Aaaa", "Ddd", "Cc"]
lst.sort()  # no extra arguments necessary for alphabetical sorting
# lst = ["Aaaa", "Bbbb", "Cc", "Ddd"]
lst.sort(key=len, reverse=True) # sort by length, which is higher priority, so last
# lst = ["Aaaa", "Bbbb", "Ddd", "Cc"]
o3imoua4

o3imoua48#

基本理论

以下所有内容都适用于内置的sorted函数和列表的.sort方法。
一般来说,一个key排序函数可以简单地生成一个元组,其中每个元素对应一个我们想要用来排序的“键”,这些元组将是sort lexicographically,所以这会生成想要的结果--元素根据第一个键结果排序,第二个键结果打破平局,等等。
同时,用于排序的reverse关键字参数可以指定按逆序排序,这相当于正常排序,然后将结果反转,但效率更高。
但是,此reverse设置适用于整个排序。它不允许先按一个键升序排序,然后按另一个键降序排序,反之亦然。

示例设置

可以对包含任何类型对象的列表进行排序,而不仅仅是嵌套列表/元组;并且可以编写以任何方式处理这些对象的关键函数-例如,编写到sort instances of a class according to the value of a specific attribute。为了清楚起见(即,为了使用属性名称),我将设置一个简单的namedtuple并演示排序示例列表的技术。

from collections import namedtuple
datum = namedtuple('datum', 'id age first last')
data = [
    datum(1, 23, 'Foo', 'Bar'),
    datum(2, 42, 'Baz', 'Quux'),
    # etc.
]

特殊情况:按两个数字键排序

要模拟反向排序,取一个数值的负数就足够了。

# sort ascending by id, then descending by age
data.sort(key=lambda d: (d.id, -d.age))
# equivalent, but more complex:
data.sort(key=lambda d: (-d.id, d.age), reverse=True)

特殊情况:最多按一个非数字键排序

如果只有一个非数字键,选择是否使用reverse可以避免这样的问题,即只有数字键可以取反:

# sort ascending by first name, then descending by id
data.sort(key=lambda d: (d.first, -d.id))
# sort ascending by age, then descending by last name
# since the name can't be negated, `reverse` is needed;
# this implies in turn that the age values should be negated.
data.sort(key=lambda d: (-d.age, d.last), reverse=True)

使用 Package 对值求反

一个更通用的方法是创建一个 Package 类negated,语义为negated(x) < negated(y)当且仅当x >= y。这是in black panda's answer所采用的方法。因此:

class negated: # name changed; otherwise the same
    def __init__(self, obj):
        self.obj = obj

    def __eq__(self, other):
        return other.obj == self.obj

    def __lt__(self, other):
        return other.obj < self.obj

# Sort descending by last name, then ascending by first name.
data.sort(lambda d: (negated(d.last), d.first))

更复杂:调整函数而不是值

假设已有某个键函数my_key,我们想先按其结果降序排序,再按其他键升序排序,我们不需要重写my_key,可以这样修改它:

def negated_result(func):
    return lambda x: negated(func(x))

# Which now allows:
data.sort(lambda d: (negated_result(my_key)(d), d.id))

因为negated_result接受一个函数并返回一个函数,所以它也可以用作装饰器。

如果所有其他方法都失败:按键重复排序

由于Python的内置排序是guaranteed stable,我们可以简单地对第二个键排序,然后对第一个键排序:

# Sort "by my_key descending, then id ascending", by doing the steps
# the other way around.
data.sort(lambda d: d.id)
data.sort(my_key, reverse=True)

其思想是在应用主排序时保留子排序。记住以相反顺序执行此操作有点棘手,因此可能需要一个 Package 函数。例如:

# Use the `operator` module to avoid writing lambdas for simple accesses.
# This is not much simpler, but arguably more explicit.
from operator import attrgetter

# Give the sort orderings nicer names.
# See: https://stackoverflow.com/questions/31509401
from enum import Flag

class SortOrder(Flag):
    DESCENDING = True
    ASCENDING = False

def multi_sort(a_list, *specs):
    '''Sort by multiple, optionally reversed keys.
    specs -> a sequence of (func, bool) tuples.
             Each tuple specifies a key func to use for sorting,
             and whether or not to reverse the sort.'''
    for key, reverse in reversed(specs):
        # The enum value must be converted explicitly to work.
        a_list.sort(key=key, reverse=bool(reverse))

# Now the same sort looks like:
multi_sort(
    data, 
    (my_key, SortOrder.DESCENDING),
    (attrgetter('id'), SortOrder.ASCENDING)
)

相关问题