【问题标题】:Sort a list with longest items first首先对具有最长项目的列表进行排序
【发布时间】:2017-08-11 11:36:13
【问题描述】:

我正在使用 lambda 来修改排序的行为。

sorted(list, key=lambda item:(item.lower(),len(item)))

对包含A1,A2,A3,A,B1,B2,B3,B元素的列表进行排序,结果为A,A1,A2,A3,B,B1,B2,B3

我预期的排序列表是A1,A2,A3,A,B1,B2,B3,B

我已经尝试包含len(item) 进行排序,但没有成功。如何修改 lambda 以改为排序结果?

【问题讨论】:

  • 你真的需要lower这个案子吗?这是一个分散注意力的问题。
  • lower 完成了不区分大小写的排序。有没有更好的方法来实现这一点?
  • 但是你的数据都是大写的。
  • 这些实际上是字符串吗?像'A1','A2','A3', 等或变量?如果变量包含什么?
  • 我们需要更好地描述您的排序标准,或者至少提供更多示例输入和预期输出示例。

标签: python python-3.x sorting lambda


【解决方案1】:

这是一种方法:

>>> import functools
>>> def cmp(s, t):
    'Alter lexicographic sort order to make longer keys go *before* any of their prefixes'
    ls, lt = len(s), len(t)
    if ls < lt:   s += t[ls:] + 'x'
    elif lt < ls: t += s[lt:] + 'x'
    if s < t: return -1
    if s > t: return 1
    return 0

>>> sorted(l, key=functools.cmp_to_key(cmp))
['A1', 'A2', 'A3', 'A', 'B1', 'B2', 'B3', 'B']

传统上,按字典顺序排序较长的字符串它们其他相同的前缀之后(即“abc”在“abcd”之前)。

为了满足您的排序期望,我们首先通过添加较长字符串的剩余部分和另一个字符来“修复”较短的字符串,使其成为两者中较长的一个:

compare abc to defg     -->  compare abcgx to defg
compare a   to a2       -->  compare a2x to a2

functools.cmp_to_key() 工具然后将比较函数转换为关键函数。

这似乎需要做很多工作,但排序预期与内置的字典排序规则非常不一致。

FWIW,这是另一种写法,可能会或可能不会被认为更清晰:

def cmp(s, t):
    'Alter lexicographic sort order to make longer keys go *before* any of their prefixes'
    for p, q in zip(s, t):
        if p < q: return -1
        if q < p: return 1
    if len(s) > len(t): return -1
    elif len(t) > len(s): return 1
    return 0

逻辑是:

  • 逐个字符比较,直到找到不同的对
  • 不同的对以传统方式确定排序顺序
  • 如果没有不同的对,则最长的输入优先。
  • 如果没有不同的对且长度相等,则字符串相等。

【讨论】:

  • 糟糕,即使是@RaymondHettinger,也错过了代码缩进:D
  • @IronFist 我以我认为最清晰的方式编写代码。有时,数学家偏爱的谨慎陈述风格更清晰,更容易推理。即使在标准库中,我们有时也会将 test 和 action 语句放在同一行(例如,参见 Lib/bisect.py)。
  • 对于像这样奇怪的排序标准,很遗憾我们不再有 cmp arg sort / sorted。但我猜functools.cmp_to_key 执行的那种施瓦茨变换不会比直接使用cmp 增加太多开销。
  • @PM2Ring functools.cmp_to_key 有一个稍微简单和低开销的纯 python 实现和一个更低开销(几乎免费)的 C 实现。 functools.cmp_to_key 工具只是为了处理这种奇怪的 cmp 函数的特殊情况。在几乎所有其他情况下,cmp 函数都是错误的(三向比较通常比布尔键函数更复杂,并且 cmp 函数是 O(n log n) 与 O 的键函数相比(n). SQL 的 ORDER BY 子句使用键功能,它们解决了各种各样的现实问题。
  • 您可能会发现这个老问题中的各种 key & cmp 策略很有趣:Sort a list to form the largest possible number
【解决方案2】:

我的第一个答案是:只需否定 len 标准,以仅在该标准上反转。

sorted(list, key=lambda item:(item.lower(),-len(item)))   # doesn't work!

但这不起作用,因为 alpha 排序和长度之间存在冲突。 Alpha 排序将小字符串放在首位。所以长度标准不起作用。

您需要合并这两个条件。彼此之间没有明确的优先级。

我找到了一种方法:首先计算字符串的最大长度,然后返回填充的chr(127)(最大的字符,前提是你只使用 ASCII)版本的字符串作为键,因此最小的字符串用大字符填充最后:他们总是最后。

l = ["A","B","A1","A2","A3","B1","B2","B3"]

maxlen = max(len(x) for x in l)
print(sorted(l, key=lambda item:item.lower()+chr(127)*(maxlen-len(item))))

结果:

['A1', 'A2', 'A3', 'A', 'B1', 'B2', 'B3', 'B']

顺便说一句,出于显而易见的原因,不要打电话给你的名单list

【讨论】:

  • 这也不起作用。我得到了相同的结果A,A1,A2,A3,B,B1,B2,B3
  • 我喜欢这个答案的地方在于它关注问题的症结,“alpha 排序和长度之间的冲突”。我不喜欢的是答案不笼统。它假定最大字符是ord(126),这不一定是一般 Python 3 unicode 输入的情况,并且它假定预先知道输入的最大长度。为了克服这些限制并制定通用解决方案,有必要制作一个自定义 cmp 函数并将其包装在 cmp_to_key() 中,以使其成为 Python 3 的 sorted() 可接受的键函数只接受键功能。
  • @RaymondHettinger:OP 确实指定字符串仅包含 [A-Za-z0-9\.]
  • @EricDuminil 更好的解决方案不会依赖于那个特定的。对于 SO,我们希望解决方案尽可能地可重用和健壮。
  • 我试图利用一些对象(例如元组)总是在字符串之后排序的事实。不过,我什么也没做。
【解决方案3】:

可以通过以下方式构造密钥:

  1. 每个项目的第一个字母
  2. 长度
  3. 项目本身

例如:

>>> L = ['A1', 'B2', 'A', 'A2', 'B1', 'A3', 'B3', 'B']
>>> print(sorted(L, key = lambda item: (item[0], -len(item), item)))
['A1', 'A2', 'A3', 'A', 'B1', 'B2', 'B3', 'B']

【讨论】:

  • 可能,但不区分大小写。为了解决这个问题,我建议使用正确的def 函数而不是lambda 以避免调用.lower 两次。但是,我们仍然没有明确的排序标准规范。谁知道呢,也许数字部分可能需要进行数字比较……
  • 应该有递归调用。否则,它只适用于 1 或 2 个字符的字符串。
  • @EricDuminil 确实,从最初的问题来看,输入的一般情况并不是很清楚......
【解决方案4】:

我喜欢 Trie,所以只是为了好玩,我写了一个基于 Trie 的解决方案:

class Trie():

    def __init__(self):
        self.data = {}

    def add(self, word):
        ref = self.data
        for char in word:
            ref[char] = char in ref and ref[char] or {}
            ref = ref[char]
        ref[''] = 1


def sorted_print(dct, prefix=''):
    sorted_keys = sorted(filter(bool, dct.keys()), key=str.lower)
    for key in sorted_keys:
        v = dct[key]
        if isinstance(v, dict):
            sorted_print(v, prefix + key)
    if '' in dct:
        print(prefix)

my_list = ["B1", "B3", "B2", "A1", "A2", "A3", "A", "B"]
t = Trie()
for w in my_list:
    t.add(w)


sorted_print(t.data)
# A1
# A2
# A3
# A
# B1
# B2
# B3
# B

这应该适用于任何长度的任何字符串。

请注意,结果只是打印到屏幕上,而不是写回新列表中。你没有写太多代码,所以我把它留作练习;)

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2019-02-14
    • 2016-05-20
    • 2011-02-18
    • 1970-01-01
    • 1970-01-01
    • 2018-10-04
    • 1970-01-01
    • 2012-05-21
    相关资源
    最近更新 更多