Python 的类似于 PHP natsort 函数的功能(使用“自然顺序”算法对列表进行排序)

24

我想知道Python中是否有类似于PHP natsort函数的东西?

l = ['image1.jpg', 'image15.jpg', 'image12.jpg', 'image3.jpg']
l.sort()

提供:

['image1.jpg', 'image12.jpg', 'image15.jpg', 'image3.jpg']

但是我希望能够获得:

['image1.jpg', 'image3.jpg', 'image12.jpg', 'image15.jpg']

更新

基于此链接的解决方案。

def try_int(s):
    "Convert to integer if possible."
    try: return int(s)
    except: return s

def natsort_key(s):
    "Used internally to get a tuple by which s is sorted."
    import re
    return map(try_int, re.findall(r'(\d+|\D+)', s))

def natcmp(a, b):
    "Natural string comparison, case sensitive."
    return cmp(natsort_key(a), natsort_key(b))

def natcasecmp(a, b):
    "Natural string comparison, ignores case."
    return natcmp(a.lower(), b.lower())

l.sort(natcasecmp);

据我所知,这不是内置的,也不在标准库中。这里有一个配方(http://code.activestate.com/recipes/285264-natural-string-sorting/),可以通过谷歌找到其他实现。 - Eli Bendersky
你可以查看这个链接:Python紧凑的人类排序 - sankoz
这是自然的顺序,image3.jpg在它应该在的位置。 - Silver Light
3个回答

52

我的答案自然排序算法

import re
def natural_key(string_):
    """See https://blog.codinghorror.com/sorting-for-humans-natural-sort-order/"""
    return [int(s) if s.isdigit() else s for s in re.split(r'(\d+)', string_)]

例子:

>>> L = ['image1.jpg', 'image15.jpg', 'image12.jpg', 'image3.jpg']
>>> sorted(L)
['image1.jpg', 'image12.jpg', 'image15.jpg', 'image3.jpg']
>>> sorted(L, key=natural_key)
['image1.jpg', 'image3.jpg', 'image12.jpg', 'image15.jpg']

为了支持Unicode字符串,应该使用.isdecimal()而不是.isdigit()。请参见@phihag的评论中的示例。相关:如何揭示Unicode的数字值属性
在Python 2中的某些语言环境中(例如Windows上的cp1252语言环境中),.isdigit()也可能失败(返回不能被int()接受的值)例如'\xb2' ('²') in cp1252 locale on Windows

@phihag:它在Python 3上运行良好。 - jfs
1
哎呀,你说得对。我搞砸了测试用例 - 这个错误与Python 3无关。\disdigit只匹配int不接受的值。请观察[u'²'].sort(key=natural_key)(http://ideone.com/iMEmv)。 - phihag
注意:该方法适用于特定示例,但对于 ['elm1','Elm2'] 和 ['0.501','0.55'] 和 [0.01,0.1,1] 等情况会失败...请参见 https://dev59.com/aG445IYBdhLWcg3we6V9#27430141 以获取 lower() 和我更通用的 Python 自然排序解决方案。 - Scott Lawton
1
@ScottLawton:它按预期工作。使用不同的“自然排序”定义是可以的。但不能说其他(广泛使用的)定义是错误的。 - jfs
谢谢@jfs!我阅读了示例,并将lambda sublist: natural_key(sublist[0])更改为key=lambda sublist: natural_key(sublist[0]),以便代码可以运行,但似乎子列表的顺序根本没有改变。我会继续尝试并在这里提供反馈:D PS:repl在这里https://repl.it/@hanglearning/testSortSublists - Hang
显示剩余5条评论

18

您可以在PyPI上查看第三方natsort库:

>>> import natsort
>>> l = ['image1.jpg', 'image15.jpg', 'image12.jpg', 'image3.jpg']
>>> natsort.natsorted(l)
['image1.jpg', 'image3.jpg', 'image12.jpg', 'image15.jpg']

完全公开,我是作者。


我想使用它,但我没有在Python 3.5中找到它。 - FiReTiTi
@FiReTiTi 它与Python 2和Python 3都兼容。我很好奇你是如何得出它不支持Python 3的结论的。 - SethMMorton
我尝试使用natsort,但它不可用。所以我请求MacPort安装它,但它想强制我安装python 3.4或2.7和natsort,而我不想这样做,因为已经安装了python 3.5。 - FiReTiTi
1
@FiReTiTi 看起来应该向 MacPort 的开发人员报告此问题。natsort 可在所有现代版本的 Python 上运行。您可以使用 pip,或者如果您使用的是 Mac,则可以考虑切换到 Homebrew。 - SethMMorton

2

这个函数可以作为Python 2.x和3.x中sortedkey=参数使用:

def sortkey_natural(s):
    return tuple(int(part) if re.match(r'[0-9]+$', part) else part
                for part in re.split(r'([0-9]+)', s))

.isdecimal()是仅适用于Unicode的方法。它不能在字节串上工作。.isdecimal()匹配与\d相同的字符集([Nd]),在Unicode情况下比[0-9]更大。 - jfs
我不知道对于排序两个字节字符串的语义是什么,所以我没有考虑它。但你是对的,这个测试有问题。已经切换到 re.match - phihag
+1. 你没有使用正确的Unicode排序,所以我不明白为什么你会拒绝字节串。顺便说一下,在*nix中,文件名只是字节。你不希望ls因为目录中有一个有趣的文件名而崩溃。 - jfs

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接