Python a-t-il une fonction intégrée pour le tri naturel des cordes?

Utilisant Python 3.x, j'ai une liste de chaînes pour lesquelles je voudrais effectuer un tri alphabétique naturel.

Natural sort: L'ordre dans lequel les fichiers sous Windows sont triés.

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'ai):

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

je cherche un fonction de tri qui se comporte comme le premier.

187
demandé sur rp. 2011-01-29 14:55:53

15 réponses

il y a une bibliothèque tierce pour cela sur PyPI appelée natsort (divulgation complète, je suis l'auteur du paquet). Pour votre cas, vous pouvez faire l'une des deux 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 devriez noter que natsort utilise un algorithme général donc il devrait fonctionner pour à peu près n'importe quelle entrée que vous lancez à elle. Si vous voulez plus de détails sur la raison pour laquelle vous pourriez choisir une bibliothèque pour faire cela plutôt que de lancer votre propre fonction, consultez la natsort documentation Comment Ça marche de la page, en particulier le Cas Spéciaux 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']
152
répondu SethMMorton 2017-08-22 23:39:39

essayez ceci:

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']

Voir en ligne: ideone .

code adapté d'ici: tri pour humains : ordre de tri naturel .

146
répondu Mark Byers 2011-01-29 13:56:47

Voici une version beaucoup plus pythonique 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 une lambda:

lambda s: [int(t) if t.isdigit() else t.lower() for t in re.split('(\d+)', s)]
73
répondu Claudiu 2018-10-05 12:42:48

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 encore dans votre propre paramètre 'key'. J'en ai besoin pour exécuter une sorte de liste naturelle qui contient des objets plus complexes (pas seulement des chaînes).

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'}]
19
répondu beauburrier 2012-01-20 10:53:53
data = ['elm13', 'elm9', 'elm0', 'elm1', 'Elm11', 'Elm2', 'elm10']

analysons les données. La capacité numérique de tous les éléments est de 2. Et il y a 3 lettres dans la partie commune littérale 'elm' .

Ainsi, la longueur maximale de l'élément est de 5. Nous pouvons augmenter cette valeur pour être sûr (par exemple, à 8).

compte tenu de cela, nous avons une solution en une seule ligne:

data.sort(key=lambda x: '{0:0>8}'.format(x).lower())

sans expressions régulières et externes les bibliothèques!

print(data)

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

explication:

for elm in data:
    print('{0:0>8}'.format(elm).lower())

>>>
0000elm0
0000elm1
0000elm2
0000elm9
000elm10
000elm11
000elm13
15
répondu SergO 2016-04-19 11:56:58

:

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

similaire à la solution de SergO, un 1-liner sans bibliothèques externes serait :

data.sort(key=lambda x : int(x[3:]))

ou

sorted_data=sorted(data, key=lambda x : int(x[3:]))

explication:

cette solution utilise la" clé 1519150920 "caractéristique de tri pour définir une fonction qui sera utilisée pour le tri. Parce que nous savons que chaque entrée de données précédée par' elm', la fonction de tri convertit en entier la partie de la chaîne qui suit le 3ème caractère (i.e. int(x[3:])). Si la partie numérique de données est dans un emplacement différent, cette partie de la fonction serait de changer.

Cheers

7
répondu Camilo 2017-03-14 15:16:56
Et maintenant, quelque chose de plus* élégant (pythonic) -juste une touche

Il existe de nombreuses implémentations, et alors que certains sont proches, aucun bien capturé l'élégance moderne python offre.

  • testé en utilisant python (3.5.1)
  • incluait une liste supplémentaire pour démontrer que cela fonctionne lorsque le les nombres sont mid string
  • N'a pas testé, cependant, je suis en supposant que si votre liste était importante, il serait plus efficace de compiler le regex à l'avance.
    • je suis sûr que quelqu'un me corrigera s'il s'agit d'une hypothèse erronée

Quicky
from re import compile, split    
dre = compile(r'(\d+)')
mylist.sort(key=lambda l: [int(s) if s.isdigit() else s.lower() for s in split(dre, l)])
Full-Code
#!/usr/bin/python3
# coding=utf-8
"""
Natural-Sort Test
"""

from re import compile, split

dre = compile(r'(\d+)')
mylist = ['elm0', 'elm1', 'Elm2', 'elm9', 'elm10', 'Elm11', 'Elm12', 'elm13', 'elm']
mylist2 = ['e0lm', 'e1lm', 'E2lm', 'e9lm', 'e10lm', 'E12lm', 'e13lm', 'elm', 'e01lm']

mylist.sort(key=lambda l: [int(s) if s.isdigit() else s.lower() for s in split(dre, l)])
mylist2.sort(key=lambda l: [int(s) if s.isdigit() else s.lower() for s in split(dre, l)])

print(mylist)  
  # ['elm', 'elm0', 'elm1', 'Elm2', 'elm9', 'elm10', 'Elm11', 'Elm12', 'elm13']
print(mylist2)  
  # ['e0lm', 'e1lm', 'e01lm', 'E2lm', 'e9lm', 'e10lm', 'E12lm', 'e13lm', 'elm']

attention lors de l'utilisation de

  • from os.path import split
    • vous aurez besoin de différencier les importations

Inspiration à partir de

5
répondu Jerod 2016-05-04 19:30:47

une option est de transformer la chaîne en un tuple et de remplacer les chiffres en utilisant la forme élargie http://wiki.answers.com/Q/What_does_expanded_form_mean

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

ci-dessous, il y a un exemple de code (qui n'est pas très efficace en raison de la façon dont il supprime les 0 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']
3
répondu robert king 2016-10-25 15:39:40

La Valeur De Ce Post

mon but est d'offrir une solution non regex qui peut être appliquée de façon générale.

Je vais créer trois fonctions:

  1. find_first_digit que j'ai emprunté à @AnuragUniyal . Il trouvera la position du premier chiffre ou du non-chiffre dans une chaîne.
  2. split_digits qui est un générateur qui sépare une corde en chiffre et non chiffres morceaux. Il sera aussi yield entiers quand il est un chiffre.
  3. natural_key juste enveloppe split_digits dans un tuple . C'est ce que nous utilisons comme une clé pour sorted , max , min .

fonctions

def find_first_digit(s, non=False):
    for i, x in enumerate(s):
        if x.isdigit() ^ non:
            return i
    return -1

def split_digits(s, case=False):
    non = True
    while s:
        i = find_first_digit(s, non)
        if i == 0:
            non = not non
        elif i == -1:
            yield int(s) if s.isdigit() else s if case else s.lower()
            s = ''
        else:
            x, s = s[:i], s[i:]
            yield int(x) if x.isdigit() else x if case else x.lower()

def natural_key(s, *args, **kwargs):
    return tuple(split_digits(s, *args, **kwargs))

Nous pouvons voir qu'il est général que nous pouvons avoir plusieurs chiffres morceaux:

# Note that the key has lower case letters
natural_key('asl;dkfDFKJ:sdlkfjdf809lkasdjfa_543_hh')

('asl;dkfdfkj:sdlkfjdf', 809, 'lkasdjfa_', 543, '_hh')

Ou laisser comme sensible à la casse:

natural_key('asl;dkfDFKJ:sdlkfjdf809lkasdjfa_543_hh', True)

('asl;dkfDFKJ:sdlkfjdf', 809, 'lkasdjfa_', 543, '_hh')

nous pouvons voir qu'il trie la liste des OP dans l'ordre approprié

sorted(
    ['elm0', 'elm1', 'Elm2', 'elm9', 'elm10', 'Elm11', 'Elm12', 'elm13'],
    key=natural_key
)

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

mais il peut traiter des listes plus compliquées aussi bien:

sorted(
    ['f_1', 'e_1', 'a_2', 'g_0', 'd_0_12:2', 'd_0_1_:2'],
    key=natural_key
)

['a_2', 'd_0_1_:2', 'd_0_12:2', 'e_1', 'f_1', 'g_0']

mon équivalent regex serait

def int_maybe(x):
    return int(x) if str(x).isdigit() else x

def split_digits_re(s, case=False):
    parts = re.findall('\d+|\D+', s)
    if not case:
        return map(int_maybe, (x.lower() for x in parts))
    else:
        return map(int_maybe, parts)

def natural_key_re(s, *args, **kwargs):
    return tuple(split_digits_re(s, *args, **kwargs))
3
répondu piRSquared 2018-02-19 23:59:33

les réponses ci-dessus sont bonnes pour le exemple spécifique qui a été montré, mais manque plusieurs cas utiles pour la question plus générale de la sorte naturelle. Je viens de me faire mordre par un de ces cas, donc créé une solution plus complète:

def natural_sort_key(string_or_number):
    """
    by Scott S. Lawton <scott@ProductArchitect.com> 2014-12-11; public domain and/or CC0 license

    handles cases where simple 'int' approach fails, e.g.
        ['0.501', '0.55'] floating point with different number of significant digits
        [0.01, 0.1, 1]    already numeric so regex and other string functions won't work (and aren't required)
        ['elm1', 'Elm2']  ASCII vs. letters (not case sensitive)
    """

    def try_float(astring):
        try:
            return float(astring)
        except:
            return astring

    if isinstance(string_or_number, basestring):
        string_or_number = string_or_number.lower()

        if len(re.findall('[.]\d', string_or_number)) <= 1:
            # assume a floating point value, e.g. to correctly sort ['0.501', '0.55']
            # '.' for decimal is locale-specific, e.g. correct for the Anglosphere and Asia but not continental Europe
            return [try_float(s) for s in re.split(r'([\d.]+)', string_or_number)]
        else:
            # assume distinct fields, e.g. IP address, phone number with '.', etc.
            # caveat: might want to first split by whitespace
            # TBD: for unicode, replace isdigit with isdecimal
            return [int(s) if s.isdigit() else s for s in re.split(r'(\d+)', string_or_number)]
    else:
        # consider: add code to recurse for lists/tuples and perhaps other iterables
        return string_or_number
Code D'essai

et plusieurs liens (on et off de StackOverflow) sont ici: http://productarchitect.com/code/better-natural-sort.py

Bienvenue. Ce n'est pas une solution définitive, c'est juste un pas en avant.

2
répondu Scott Lawton 2014-12-11 18:42:56

très probablement functools.cmp_to_key() est étroitement lié à l'implémentation sous-jacente du sort de python. En outre, le paramètre cmp est legacy. La façon moderne est de transformer les éléments d'entrée en objets qui soutiennent les riches opérations de comparaison désirées.

Sous CPython 2.x, les objets de types disparates peuvent être commandés même si les opérateurs de comparaison rich respectifs n'ont pas été implémentés. Sous CPython 3.x, objets de différents types doit appuyer explicitement la comparaison. Voir comment Python compare-t-il string et int? qui renvoie à la documentation officielle . La plupart des réponses dépendent de cet ordre implicite. De commutation pour Python 3.x aura besoin d'un nouveau type pour implémenter et unifier les comparaisons entre les nombres et les chaînes.

Python 2.7.12 (default, Sep 29 2016, 13:30:34) 
>>> (0,"foo") < ("foo",0)
True  
Python 3.5.2 (default, Oct 14 2016, 12:54:53) 
>>> (0,"foo") < ("foo",0)
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  TypeError: unorderable types: int() < str()

Il y a trois approches différentes. La première utilise des classes imbriquées pour tirer profit de L'algorithme de comparaison Iterable de Python. La seconde décompose ce nid en une seule classe. Le troisième renonce au sous-groupe str pour se concentrer sur la performance. Tous sont chronométrés; le deuxième est deux fois plus rapide que le troisième presque six fois plus rapide. Le sous-classement str n'est pas nécessaire, et était probablement une mauvaise idée en premier lieu, mais il vient avec certaines commodités.

les caractères de tri sont dupliqués pour forcer l'ordre par cas, et cas-swapped à forcez la lettre minuscule à trier en premier; c'est la définition typique de "tri naturel". Je ne pouvais pas décider du type de regroupement; certains préféreraient peut-être ce qui suit, ce qui apporte aussi des avantages de rendement importants:

d = lambda s: s.lower()+s.swapcase()

Lorsqu'ils sont utilisés, les opérateurs de comparaison sont définis à celui de object de sorte qu'ils ne seront pas ignorés par functools.total_ordering .

import functools
import itertools


@functools.total_ordering
class NaturalStringA(str):
    def __repr__(self):
        return "{}({})".format\
            ( type(self).__name__
            , super().__repr__()
            )
    d = lambda c, s: [ c.NaturalStringPart("".join(v))
                        for k,v in
                       itertools.groupby(s, c.isdigit)
                     ]
    d = classmethod(d)
    @functools.total_ordering
    class NaturalStringPart(str):
        d = lambda s: "".join(c.lower()+c.swapcase() for c in s)
        d = staticmethod(d)
        def __lt__(self, other):
            if not isinstance(self, type(other)):
                return NotImplemented
            try:
                return int(self) < int(other)
            except ValueError:
                if self.isdigit():
                    return True
                elif other.isdigit():
                    return False
                else:
                    return self.d(self) < self.d(other)
        def __eq__(self, other):
            if not isinstance(self, type(other)):
                return NotImplemented
            try:
                return int(self) == int(other)
            except ValueError:
                if self.isdigit() or other.isdigit():
                    return False
                else:
                    return self.d(self) == self.d(other)
        __le__ = object.__le__
        __ne__ = object.__ne__
        __gt__ = object.__gt__
        __ge__ = object.__ge__
    def __lt__(self, other):
        return self.d(self) < self.d(other)
    def __eq__(self, other):
        return self.d(self) == self.d(other)
    __le__ = object.__le__
    __ne__ = object.__ne__
    __gt__ = object.__gt__
    __ge__ = object.__ge__
import functools
import itertools


@functools.total_ordering
class NaturalStringB(str):
    def __repr__(self):
        return "{}({})".format\
            ( type(self).__name__
            , super().__repr__()
            )
    d = lambda s: "".join(c.lower()+c.swapcase() for c in s)
    d = staticmethod(d)
    def __lt__(self, other):
        if not isinstance(self, type(other)):
            return NotImplemented
        groups = map(lambda i: itertools.groupby(i, type(self).isdigit), (self, other))
        zipped = itertools.zip_longest(*groups)
        for s,o in zipped:
            if s is None:
                return True
            if o is None:
                return False
            s_k, s_v = s[0], "".join(s[1])
            o_k, o_v = o[0], "".join(o[1])
            if s_k and o_k:
                s_v, o_v = int(s_v), int(o_v)
                if s_v == o_v:
                    continue
                return s_v < o_v
            elif s_k:
                return True
            elif o_k:
                return False
            else:
                s_v, o_v = self.d(s_v), self.d(o_v)
                if s_v == o_v:
                    continue
                return s_v < o_v
        return False
    def __eq__(self, other):
        if not isinstance(self, type(other)):
            return NotImplemented
        groups = map(lambda i: itertools.groupby(i, type(self).isdigit), (self, other))
        zipped = itertools.zip_longest(*groups)
        for s,o in zipped:
            if s is None or o is None:
                return False
            s_k, s_v = s[0], "".join(s[1])
            o_k, o_v = o[0], "".join(o[1])
            if s_k and o_k:
                s_v, o_v = int(s_v), int(o_v)
                if s_v == o_v:
                    continue
                return False
            elif s_k or o_k:
                return False
            else:
                s_v, o_v = self.d(s_v), self.d(o_v)
                if s_v == o_v:
                    continue
                return False
        return True
    __le__ = object.__le__
    __ne__ = object.__ne__
    __gt__ = object.__gt__
    __ge__ = object.__ge__
import functools
import itertools
import enum


class OrderingType(enum.Enum):
    PerWordSwapCase         = lambda s: s.lower()+s.swapcase()
    PerCharacterSwapCase    = lambda s: "".join(c.lower()+c.swapcase() for c in s)


class NaturalOrdering:
    @classmethod
    def by(cls, ordering):
        def wrapper(string):
            return cls(string, ordering)
        return wrapper
    def __init__(self, string, ordering=OrderingType.PerCharacterSwapCase):
        self.string = string
        self.groups = [ (k,int("".join(v)))
                            if k else
                        (k,ordering("".join(v)))
                            for k,v in
                        itertools.groupby(string, str.isdigit)
                      ]
    def __repr__(self):
        return "{}({})".format\
            ( type(self).__name__
            , self.string
            )
    def __lesser(self, other, default):
        if not isinstance(self, type(other)):
            return NotImplemented
        for s,o in itertools.zip_longest(self.groups, other.groups):
            if s is None:
                return True
            if o is None:
                return False
            s_k, s_v = s
            o_k, o_v = o
            if s_k and o_k:
                if s_v == o_v:
                    continue
                return s_v < o_v
            elif s_k:
                return True
            elif o_k:
                return False
            else:
                if s_v == o_v:
                    continue
                return s_v < o_v
        return default
    def __lt__(self, other):
        return self.__lesser(other, default=False)
    def __le__(self, other):
        return self.__lesser(other, default=True)
    def __eq__(self, other):
        if not isinstance(self, type(other)):
            return NotImplemented
        for s,o in itertools.zip_longest(self.groups, other.groups):
            if s is None or o is None:
                return False
            s_k, s_v = s
            o_k, o_v = o
            if s_k and o_k:
                if s_v == o_v:
                    continue
                return False
            elif s_k or o_k:
                return False
            else:
                if s_v == o_v:
                    continue
                return False
        return True
    # functools.total_ordering doesn't create single-call wrappers if both
    # __le__ and __lt__ exist, so do it manually.
    def __gt__(self, other):
        op_result = self.__le__(other)
        if op_result is NotImplemented:
            return op_result
        return not op_result
    def __ge__(self, other):
        op_result = self.__lt__(other)
        if op_result is NotImplemented:
            return op_result
        return not op_result
    # __ne__ is the only implied ordering relationship, it automatically
    # delegates to __eq__
>>> import natsort
>>> import timeit
>>> l1 = ['Apple', 'corn', 'apPlE', 'arbour', 'Corn', 'Banana', 'apple', 'banana']
>>> l2 = list(map(str, range(30)))
>>> l3 = ["{} {}".format(x,y) for x in l1 for y in l2]
>>> print(timeit.timeit('sorted(l3+["0"], key=NaturalStringA)', number=10000, globals=globals()))
362.4729259099986
>>> print(timeit.timeit('sorted(l3+["0"], key=NaturalStringB)', number=10000, globals=globals()))
189.7340817489967
>>> print(timeit.timeit('sorted(l3+["0"], key=NaturalOrdering.by(OrderingType.PerCharacterSwapCase))', number=10000, globals=globals()))
69.34636392899847
>>> print(timeit.timeit('natsort.natsorted(l3+["0"], alg=natsort.ns.GROUPLETTERS | natsort.ns.LOWERCASEFIRST)', number=10000, globals=globals()))
98.2531585780016

le tri naturel est à la fois assez compliqué et vaguement défini comme un problème. N'oubliez pas de lancer unicodedata.normalize(...) à l'avance, et pensez à utiliser str.casefold() plutôt que str.lower() . Il y a probablement des problèmes subtils d'encodage que je n'ai pas considérés. Je recommande donc provisoirement la bibliothèque natsort . J'ai jeté un coup d'oeil rapide au dépôt github; la maintenance du code a été excellente.

tous les algorithmes que j'ai vu dépendent de trucs comme la duplication et l'Abaissement des caractères, et l'échange de cas. Alors que cela double le temps de fonctionnement, une alternative exigerait un ordre naturel total sur le jeu de caractères d'entrée. Je ne pense pas que cela fasse partie de la spécification unicode, et comme il y a beaucoup plus de chiffres unicode que [0-9] , créer un tel tri serait tout aussi intimidant. Si vous voulez des comparaisons tenant compte de la localisation, préparez vos cordes avec locale.strxfrm par Python Tri COMMENT FAIRE .

2
répondu user19087 2017-05-23 11:47:31

basé sur les réponses ici, j'ai écrit une fonction natural_sorted qui se comporte comme la fonction intégrée sorted :

# Copyright (C) 2018, Benjamin Drung <bdrung@posteo.de>
#
# Permission to use, copy, modify, and/or distribute this software for any
# purpose with or without fee is hereby granted, provided that the above
# copyright notice and this permission notice appear in all copies.
#
# THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
# WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
# MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
# ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
# WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
# ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
# OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.

import re

def natural_sorted(iterable, key=None, reverse=False):
    """Return a new naturally sorted list from the items in *iterable*.

    The returned list is in natural sort order. The string is ordered
    lexicographically (using the Unicode code point number to order individual
    characters), except that multi-digit numbers are ordered as a single
    character.

    Has two optional arguments which must be specified as keyword arguments.

    *key* specifies a function of one argument that is used to extract a
    comparison key from each list element: ``key=str.lower``.  The default value
    is ``None`` (compare the elements directly).

    *reverse* is a boolean value.  If set to ``True``, then the list elements are
    sorted as if each comparison were reversed.

    The :func:`natural_sorted` function is guaranteed to be stable. A sort is
    stable if it guarantees not to change the relative order of elements that
    compare equal --- this is helpful for sorting in multiple passes (for
    example, sort by department, then by salary grade).
    """
    prog = re.compile(r"(\d+)")

    def alphanum_key(element):
        """Split given key in list of strings and digits"""
        return [int(c) if c.isdigit() else c for c in prog.split(key(element)
                if key else element)]

    return sorted(iterable, key=alphanum_key, reverse=reverse)

le code source est également disponible dans mon dépôt de snippets GitHub: https://github.com/bdrung/snippets/blob/master/natural_sorted.py

2
répondu Benjamin Drung 2018-02-08 14:56:28

je vous suggère simplement d'utiliser le key argument de mot clé de sorted pour atteindre votre liste désirée

Par exemple:

to_order= [e2,E1,e5,E4,e3]
ordered= sorted(to_order, key= lambda x: x.lower())
    # ordered should be [E1,e2,e3,E4,e5]
0
répondu Johny Vaknin 2017-08-14 14:49:34
a = ['H1', 'H100', 'H10', 'H3', 'H2', 'H6', 'H11', 'H50', 'H5', 'H99', 'H8']
b = ''
c = []

def bubble(bad_list):#bubble sort method
        length = len(bad_list) - 1
        sorted = False

        while not sorted:
                sorted = True
                for i in range(length):
                        if bad_list[i] > bad_list[i+1]:
                                sorted = False
                                bad_list[i], bad_list[i+1] = bad_list[i+1], bad_list[i] #sort the integer list 
                                a[i], a[i+1] = a[i+1], a[i] #sort the main list based on the integer list index value

for a_string in a: #extract the number in the string character by character
        for letter in a_string:
                if letter.isdigit():
                        #print letter
                        b += letter
        c.append(b)
        b = ''

print 'Before sorting....'
print a
c = map(int, c) #converting string list into number list
print c
bubble(c)

print 'After sorting....'
print c
print a

Remerciements :

"Bubble Sort Homeless

comment lire une chaîne une lettre à la fois en python

0
répondu Varadaraju G 2018-03-12 12:10:14
>>> import re
>>> sorted(lst, key=lambda x: int(re.findall(r'\d+$', x)[0]))
['elm0', 'elm1', 'Elm2', 'elm9', 'elm10', 'Elm11', 'Elm12', 'elm13']
-2
répondu SilentGhost 2011-01-29 12:01:33