自然地对列表进行排序,将字母数字值移动到末尾

Naturally sort a list moving alphanumeric values to the end

我有一个要自然排序的字符串列表:

c = ['0', '1', '10', '11', '2', '2Y', '3', '3Y', '4', '4Y', '5', '5Y', '6', '7', '8', '9', '9Y']

除了自然排序之外,我想把所有不是纯数字字符串的条目都移到最后。我的预期输出是这样的:

['0', '1', '2', '3', '4', '5', '6', '7', '8', '9', '10', '11', '2Y', '3Y', '4Y', '5Y', '9Y']

请注意,所有内容都必须进行 natsort - 甚至是字母数字字符串。

我知道我可以使用 natsort 包来获得我想要的东西,但仅此一项并不能满足我的需要。我需要使用 两个 排序调用来执行此操作 - 一个用于自然排序,另一个用于将非纯数字字符串移动到末尾。

import natsort as ns
r = sorted(ns.natsorted(c), key=lambda x: not x.isdigit())

print(r)
['0', '1', '2', '3', '4', '5', '6', '7', '8', '9', '10', '11', '2Y', '3Y', '4Y', '5Y', '9Y']

我想知道是否可以巧妙地使用 natsort 并将其简化为单个排序调用。

natsort 有一个函数 natsort_key 可以根据完成的排序将项目转换为元组。

因此您可以将其用作:

sorted(c, key=lambda x: <b>(</b>not x.isdigit()<b>, *ns.natsort_key(x))</b>)

这会产生:

>>> sorted(c, key=lambda x: (not x.isdigit(), *ns.natsort_key(x)))
['0', '1', '2', '3', '4', '5', '6', '7', '8', '9', '10', '11', '2Y', '3Y', '4Y', '5Y', '9Y']

你也可以在没有迭代解包的情况下使用它,因为在那种情况下我们有两个 2 元组,如果第一个项目出现平局,它将比较 natsort_key 通话:

sorted(c, key=lambda x: (not x.isdigit(), ns.natsort_key(x)))

非常感谢 Willem Van Onsem 发布了他的回答。但是,我应该在这里指出,原始函数的性能要快一个数量级。考虑到 PM2 Ring 的建议,以下是两种方法之间的一些基准:

设置

c = \
['0',
 '1',
 '10',
 '11',
 '2',
 '2Y',
 '3',
 '3Y',
 '4',
 '4Y',
 '5',
 '5Y',
 '6',
 '7',
 '8',
 '9',
 '9Y']
d = c * (1000000 // len(c) + 1)  # approximately 1M elements

%timeit sorted(d, key=lambda x: (not x.isdigit(), ns.natsort_key(x)))
1 loop, best of 3: 2.78 s per loop

%timeit sorted(ns.natsorted(d), key=str.isdigit, reverse=True)
1 loop, best of 3: 796 ms per loop

原来的高性能的解释是因为 Tim Sort 似乎针对接近排序的列表进行了高度优化。


完整性检查

x = sorted(d, key=lambda x: (not x.isdigit(), ns.natsort_key(x)))
y = sorted(ns.natsorted(d), key=str.isdigit, reverse=True)

all(i == j for i, j in zip(x, y))
True

您实际上可以使用 natsortedkey 的正确选择来执行此操作。

>>> ns.natsorted(d, key=lambda x: (not x.isdigit(), x))
['0',
 '1',
 '2',
 '3',
 '4',
 '5',
 '6',
 '7',
 '8',
 '9',
 '10',
 '11',
 '2Y',
 '3Y',
 '4Y',
 '5Y',
 '9Y']

键returns一个以原始输入作为第二个元素的元组。数字字符串放在前面,所有其他字符串放在后面,然后子集单独排序。

作为旁注, 使用 natsort_key,从 natsort 版本 3.0.4 开始已弃用(如果您在解释器中打开 DeprecationWarning你会看到的,并且该功能现在没有记录)。它实际上效率很低……最好使用 natort_keygen,其中 returns 是自然排序键。 natsort_key 在幕后调用它,因此对于每个输入,您都在创建一个新函数,然后调用它一次。

下面我重复显示的测试 ,我使用 natsorted 方法添加我的解决方案以及使用 natsort_keygen 而不是 [=14] 的其他解决方案的时间=].

In [13]: %timeit sorted(d, key=lambda x: (not x.isdigit(), ns.natsort_key(x)))
1 loop, best of 3: 33.3 s per loop

In [14]: natsort_key = ns.natsort_keygen()

In [15]: %timeit sorted(d, key=lambda x: (not x.isdigit(), natsort_key(x)))
1 loop, best of 3: 11.2 s per loop

In [16]: %timeit sorted(ns.natsorted(d), key=str.isdigit, reverse=True)
1 loop, best of 3: 9.77 s per loop

In [17]: %timeit ns.natsorted(d, key=lambda x: (not x.isdigit(), x))
1 loop, best of 3: 23.8 s per loop