如何对具有两个键但其中一个键按相反顺序的列表进行排序?
- 2025-01-09 08:47:00
- admin 原创
- 88
问题描述:
我想知道用 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)
但是这将使用两个键按相反的顺序排序。
解决方案 1:
您可以创建一个反转器类并使用它来修饰相关键。此类可用于反转任何可比较的字段。
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])))
解决方案 2:
当我们需要对具有两个约束的列表进行排序时,将使用两个键:一个按升序排列,另一个按降序排列,在同一个列表或任何
在你的例子中,
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]))
解决方案 3:
方法 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))
方法 2
如果你的元素是数字,你可以稍微作弊一下:
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
)
解决方案 4:
使用 Python 3 时,@KellyBundy做出了一个很好的观察,即当前Python 文档中列出的多排序方法速度非常快,可用于完成具有离散顺序的多列排序。这是一个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)
笔记:
<variable> 为 None,检查是防御性检查,以便搜索不会因 None 值而失败
尽管,这进行了多次排序调用,但它无疑是最快的多重排序方法!
我创建了一个名为multisort的新 Python 项目,它公开了三种方法:
方法 | 描述 | 笔记 | 速度 |
---|---|---|---|
多重排序 | 根据Python 文档中的示例设计的简单单行代码multisort | 速度第二快但配置最灵活且易于阅读。 | 0.0035 |
cmp_func | 模型中的多列排序java.util.Comparator | 合理的速度 | 0.0138 |
逆转器 | 反转器的实现-参见Black Panda的答案 | 方法论相当缓慢 | 0.0370 |
供参考:
方法 | 速度 |
---|---|
KellyBundy 的 Multisort | 0.0005 |
熊猫 | 0.0079 |
注意:速度是 1000 行(4 列)运行 10 次的平均值。
multisort
来自multisort
图书馆的示例:
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))
解决方案 5:
有时,除了使用比较函数外别无选择。在 Python 2.4 中引入时,有一个cmp
参数,但它在 Python 3 中被删除,以支持更高效的函数。在 Python 3.2 中,被添加到;它通过将原始对象包装在一个对象中来创建键,该对象的比较函数基于该函数。(您可以在“排序方法”的末尾看到的简单定义sorted
`keycmp_to_key
functoolscmp
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)
解决方案 6:
也许是优雅但不是有效的方法:
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]))
解决方案 7:
基本理论
以下所有内容均适用于内置sorted
函数和.sort
列表方法。
一般来说,key
排序函数可以简单地生成一个元组,其中每个元素对应于我们想要用于排序的“键”之一。这些元组将按字典顺序排序,因此会产生所需的结果 - 元素根据第一个键结果排序,按第二个键结果打破平局,依此类推。
同时,reverse
sorting的关键字参数可以指定按相反的顺序进行排序。这相当于正常排序,然后反转结果,但效率更高。
但是,此reverse
设置适用于整个排序。它不允许按一个键升序排序,然后按另一个键降序排序,反之亦然。
示例设置
可以对包含任何类型的对象的列表进行排序,而不仅仅是嵌套列表/元组;并且可以编写以任何方式处理这些对象的关键函数 - 例如,根据特定属性的值对类的实例进行排序。为了清楚起见(即为了使用属性名称),我将设置一个简单的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)
使用包装器来否定值
更通用的方法是创建一个包装类negated
,其语义为negated(x) < negated(y)
当且仅当。这是黑熊猫的答案中x >= y
采用的方法。因此:
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 的内置排序保证稳定,我们可以简单地对第二个键进行排序,然后对第一个键进行排序:
# 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)
这个想法是,在应用主排序时,子排序将被保留。记住以相反的顺序执行此操作有点棘手,因此可能需要包装函数。例如:
# 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)
)
解决方案 8:
至少在我的例子中,可以简单地调用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"]
解决方案 9:
data = [
{"name": "Alok", "status": "offline"},
{"name": "Bablu", "status": "online"},
{"name": "ravi", "status": "offline"},
{"name": "Rani", "status": "online"},
{"name": "John", "status": "offline"},
{"name": "Alice", "status": "online"},
{"name": "smith", "status": "offline"},
{"name": "Emma", "status": "online"},
{"name": "David", "status": "offline"},
{"name": "Olivia", "status": "online"}
]
sorted_data = sorted(data, key=lambda x: (-1 if x['status'] == 'online' else 1, x['name'].lower()))
print(sorted_data)
输出
[{'name': 'Alice', 'status': 'online'}, {'name': 'Bablu', 'status': 'online'}, {'name': 'Emma', 'status': 'online'}, {'name': 'Olivia', 'status': 'online'}, {'name': 'Rani', 'status': 'online'}, {'name': 'Alok', 'status': 'offline'}, {'name': 'David', 'status': 'offline'}, {'name': 'John', 'status': 'offline'}, {'name': 'ravi', 'status': 'offline'}, {'name': 'smith', 'status': 'offline'}]
此代码根据两个标准对字典列表进行排序:
首先,它按每个词典条目的状态进行排序。状态为“在线”的条目排在状态为“离线”的条目之前。
如果状态相同,则按名称字段的字母顺序排序,忽略大小写。
因此,此代码的输出将是一个列表,其中状态为“在线”的项目位于状态为“离线”的项目之前,并且在每个状态组中,名称按字母顺序排序。