399 votes

Existe-t-il une fonction intégrée pour le tri naturel des chaînes de caractères ?

J'ai une liste de chaînes de caractères pour lesquelles je voudrais effectuer un tri alphabétique naturel .

Par exemple, la liste suivante est naturellement triée (ce que je veux) :

['elm0', 'elm1', 'Elm2', 'elm9', 'elm10', 'Elm11', 'Elm12', 'elm13']

Et voici la version "triée" de la liste ci-dessus (ce que j'obtiens en utilisant sorted() ):

['Elm11', 'Elm12', 'Elm2', 'elm0', 'elm1', 'elm10', 'elm13', 'elm9']

Je cherche une fonction de tri qui se comporte comme la première.

1 votes

Toutes les réponses sur ce site vont produire incorrect résultats si vous voulez Windows-Explorer-like le tri dans plusieurs cas, par exemple le tri !1, 1, !a, a . La seule façon d'obtenir le tri comme Windows semble être d'utiliser le système de tri de Windows. StrCmpLogicalW La fonction elle-même, puisque personne ne semble avoir réimplémenté cette fonction correctement (une source serait appréciée). Solution : stackoverflow.com/a/48030307/2441026

355voto

SethMMorton Points 4704

Il existe une bibliothèque tierce pour cela sur PyPI appelée natsort (Pour tout vous dire, je suis l'auteur du paquet). Dans votre cas, vous pouvez faire l'une ou l'autre des choses suivantes :

>>> from natsort import natsorted, ns
>>> x = ['Elm11', 'Elm12', 'Elm2', 'elm0', 'elm1', 'elm10', 'elm13', 'elm9']
>>> natsorted(x, key=lambda y: y.lower())
['elm0', 'elm1', 'Elm2', 'elm9', 'elm10', 'Elm11', 'Elm12', 'elm13']
>>> natsorted(x, alg=ns.IGNORECASE)  # or alg=ns.IC
['elm0', 'elm1', 'Elm2', 'elm9', 'elm10', 'Elm11', 'Elm12', 'elm13']

Vous devez noter que natsort utilise un algorithme général qui devrait fonctionner pour pratiquement toutes les entrées que vous lui soumettez. Si vous voulez plus de détails sur les raisons pour lesquelles vous pourriez choisir une bibliothèque pour faire cela plutôt que de développer votre propre fonction, consultez l'article intitulé natsort de la documentation Comment ça marche en particulier la page Des cas particuliers partout ! section.


Si vous avez besoin d'une clé de tri au lieu d'une fonction de tri, utilisez l'une des formules ci-dessous.

>>> from natsort import natsort_keygen, ns
>>> l1 = ['elm0', 'elm1', 'Elm2', 'elm9', 'elm10', 'Elm11', 'Elm12', 'elm13']
>>> l2 = l1[:]
>>> natsort_key1 = natsort_keygen(key=lambda y: y.lower())
>>> l1.sort(key=natsort_key1)
>>> l1
['elm0', 'elm1', 'Elm2', 'elm9', 'elm10', 'Elm11', 'Elm12', 'elm13']
>>> natsort_key2 = natsort_keygen(alg=ns.IGNORECASE)
>>> l2.sort(key=natsort_key2)
>>> l2
['elm0', 'elm1', 'Elm2', 'elm9', 'elm10', 'Elm11', 'Elm12', 'elm13']

Mise à jour : novembre 2020

Étant donné qu'une demande/question populaire est "comment trier comme l'Explorateur Windows ?" (ou quel que soit le navigateur de système de fichiers de votre système d'exploitation), en date du natsort version 7.1.0, il existe une fonction appelée os_sorted pour faire exactement cela. Sous Windows, il triera dans le même ordre que l'explorateur Windows, et sur les autres systèmes d'exploitation, il devrait trier comme le navigateur du système de fichiers local.

>>> from natsort import os_sorted
>>> os_sorted(list_of_paths)
# your paths sorted like your file system browser

Pour ceux qui ont besoin d'une clé de tri, vous pouvez utiliser os_sort_keygen (ou os_sort_key si vous avez juste besoin des valeurs par défaut).

Caveat - Veuillez lire la documentation de l'API pour cette fonction avant de l'utiliser afin de comprendre les limites et la façon d'obtenir les meilleurs résultats.

7 votes

Je pense également qu'il est assez intéressant que natsort trie également correctement lorsque le numéro n'est pas à la fin : comme c'est souvent le cas pour les noms de fichiers. N'hésitez pas à inclure l'exemple suivant : pastebin.com/9cwCLdEK

4 votes

Natsort est une excellente bibliothèque, qui devrait être ajoutée à la bibliothèque standard de Python ! :-)

0 votes

natsort gère aussi "naturellement" le cas de plusieurs numéros distincts dans les chaînes de caractères. C'est génial !

253voto

Mark Byers Points 318575

Essayez ça :

import re

def natural_sort(l): 
    convert = lambda text: int(text) if text.isdigit() else text.lower()
    alphanum_key = lambda key: [convert(c) for c in re.split('([0-9]+)', key)]
    return sorted(l, key=alphanum_key)

Sortie :

['elm0', 'elm1', 'Elm2', 'elm9', 'elm10', 'Elm11', 'Elm12', 'elm13']

Code adapté d'ici : Tri pour les humains : Ordre de tri naturel .

3 votes

Pourquoi utilisez-vous return sorted(l, key) au lieu de l.sort(key) ? Est-ce pour un quelconque gain de performance ou simplement pour être plus pythique ?

15 votes

@jperelli Je pense que l'échelle modifierait la liste originale dans l'appelant. Mais il est plus probable que l'appelant veuille une autre copie superficielle de la liste.

3 votes

Pour mémoire, cette méthode ne peut pas gérer toutes les entrées : les séparations str/int doivent être alignées, sinon vous créerez des comparaisons telles que ["foo",0] < [0, "foo"] pour l'entrée ["foo0", "0foo"], ce qui entraîne une TypeError.

134voto

Claudiu Points 58398

Voici une version beaucoup plus pythique de la réponse de Mark Byer :

import re

def natural_sort_key(s, _nsre=re.compile('([0-9]+)')):
    return [int(text) if text.isdigit() else text.lower()
            for text in _nsre.split(s)]

Maintenant, cette fonction peut être utilisée comme une clé dans n'importe quelle fonction qui l'utilise, comme list.sort , sorted , max etc.

Comme un lambda :

lambda s: [int(t) if t.isdigit() else t.lower() for t in re.split('(\d+)', s)]

Code de démonstration entièrement reproductible :

import re
natsort = lambda s: [int(t) if t.isdigit() else t.lower() for t in re.split('(\d+)', s)]
L = ["a1", "a10", "a11", "a2", "a22", "a3"]   
print(sorted(L, key=natsort))  
# ['a1', 'a2', 'a3', 'a10', 'a11', 'a22']

11 votes

Re compile et met en cache les regex de manière automatique, il n'est donc pas nécessaire de précompiler le module

2 votes

@wim : il met en cache les X dernières utilisations, donc il est techniquement possible d'utiliser X+5 regex et ensuite de faire un tri naturel encore et encore, à quel point cela ne serait pas mis en cache. mais probablement négligeable sur le long terme.

0 votes

Je ne l'ai pas fait, mais peut-être que la raison est qu'il ne peut pas gérer les tuples, comme un tri python régulier.

20voto

beauburrier Points 364

J'ai écrit une fonction basée sur http://www.codinghorror.com/blog/2007/12/sorting-for-humans-natural-sort-order.html qui ajoute la possibilité de passer votre propre paramètre "clé". J'en ai besoin pour effectuer un tri naturel des listes qui contiennent des objets plus complexes (pas seulement des chaînes de caractères).

import re

def natural_sort(list, key=lambda s:s):
    """
    Sort the list into natural alphanumeric order.
    """
    def get_alphanum_key_func(key):
        convert = lambda text: int(text) if text.isdigit() else text 
        return lambda s: [convert(c) for c in re.split('([0-9]+)', key(s))]
    sort_key = get_alphanum_key_func(key)
    list.sort(key=sort_key)

Par exemple :

my_list = [{'name':'b'}, {'name':'10'}, {'name':'a'}, {'name':'1'}, {'name':'9'}]
natural_sort(my_list, key=lambda x: x['name'])
print my_list
[{'name': '1'}, {'name': '9'}, {'name': '10'}, {'name': 'a'}, {'name': 'b'}]

0 votes

Une manière plus simple de procéder serait de définir natural_sort_key et ensuite, lors du tri d'une liste, vous pouvez enchaîner vos clés, par exemple : list.sort(key=lambda el: natural_sort_key(el['name']))

0 votes

Vous pouvez également renvoyer list.sort(key=sort_key) si vous voulez, euh, le retourner (par exemple dans un lambda). De même, vous ne devriez pas vraiment nommer une var d'après la fonction intégrée list . Merci !

4voto

robert king Points 5369

Une option consiste à transformer la chaîne en un tuple et à remplacer les chiffres en utilisant la forme étendue http://wiki.answers.com/Q/What_does_expanded_form_mean

de cette façon, a90 deviendrait ("a",90,0) et a1 deviendrait ("a",1)

Voici un exemple de code (qui n'est pas très efficace en raison de la façon dont il supprime les 0 de tête des nombres).

alist=["something1",
    "something12",
    "something17",
    "something2",
    "something25and_then_33",
    "something25and_then_34",
    "something29",
    "beta1.1",
    "beta2.3.0",
    "beta2.33.1",
    "a001",
    "a2",
    "z002",
    "z1"]

def key(k):
    nums=set(list("0123456789"))
        chars=set(list(k))
    chars=chars-nums
    for i in range(len(k)):
        for c in chars:
            k=k.replace(c+"0",c)
    l=list(k)
    base=10
    j=0
    for i in range(len(l)-1,-1,-1):
        try:
            l[i]=int(l[i])*base**j
            j+=1
        except:
            j=0
    l=tuple(l)
    print l
    return l

print sorted(alist,key=key)

sortie :

('s', 'o', 'm', 'e', 't', 'h', 'i', 'n', 'g', 1)
('s', 'o', 'm', 'e', 't', 'h', 'i', 'n', 'g', 10, 2)
('s', 'o', 'm', 'e', 't', 'h', 'i', 'n', 'g', 10, 7)
('s', 'o', 'm', 'e', 't', 'h', 'i', 'n', 'g', 2)
('s', 'o', 'm', 'e', 't', 'h', 'i', 'n', 'g', 20, 5, 'a', 'n', 'd', '_', 't', 'h', 'e', 'n', '_', 30, 3)
('s', 'o', 'm', 'e', 't', 'h', 'i', 'n', 'g', 20, 5, 'a', 'n', 'd', '_', 't', 'h', 'e', 'n', '_', 30, 4)
('s', 'o', 'm', 'e', 't', 'h', 'i', 'n', 'g', 20, 9)
('b', 'e', 't', 'a', 1, '.', 1)
('b', 'e', 't', 'a', 2, '.', 3, '.')
('b', 'e', 't', 'a', 2, '.', 30, 3, '.', 1)
('a', 1)
('a', 2)
('z', 2)
('z', 1)
['a001', 'a2', 'beta1.1', 'beta2.3.0', 'beta2.33.1', 'something1', 'something2', 'something12', 'something17', 'something25and_then_33', 'something25and_then_34', 'something29', 'z1', 'z002']

1 votes

Malheureusement, cette solution ne fonctionne que pour Python 2.X. Pour Python 3, ('b', 1) < ('b', 'e', 't', 'a', 1, '.', 1) retournera TypeError: unorderable types: int() < str()

0 votes

@SethMMorgon a raison, ce code se casse facilement en Python 3. L'alternative naturelle semble être natsort , pypi.org/projet/natsort

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X