81 votes

Comment trier un ensemble alphanumérique en python ?

J'ai un ensemble

set(['booklet', '4 sheets', '48 sheets', '12 sheets'])

Après le tri, je veux que cela ressemble à

4 sheets,
12 sheets,
48 sheets,
booklet

Une idée, s'il vous plaît

140voto

Mark Byers Points 318575

Jeff Atwood parle du tri naturel et donne un exemple d'une façon de le faire en Python. Voici ma variation sur ce sujet :

import re 

def sorted_nicely( l ): 
    """ Sort the given iterable in the way that humans expect.""" 
    convert = lambda text: int(text) if text.isdigit() else text 
    alphanum_key = lambda key: [ convert(c) for c in re.split('([0-9]+)', key) ] 
    return sorted(l, key = alphanum_key)

Utilisez comme ça :

s = set(['booklet', '4 sheets', '48 sheets', '12 sheets'])
for x in sorted_nicely(s):
    print(x)

Sortie :

4 sheets
12 sheets
48 sheets
booklet

L'un des avantages de cette méthode est qu'elle ne fonctionne pas uniquement lorsque les chaînes de caractères sont séparées par des espaces. Elle fonctionne également pour d'autres séparateurs tels que le point dans les numéros de version (par exemple 1.9.1 vient avant 1.10.0).

0 votes

Bonjour Jeff, merci beaucoup. C'est exactement ce que je recherchais. Bonne chance.

2 votes

Est-il possible de modifier cela pour une liste de tuples basée sur la première valeur du tuple ? Exemple : [('b', 0), ('0', 1), ('a', 2)] est trié en [('0', 1), ('a', 2), ('b', 0)]

3 votes

Cette fonction est sensible à la casse. Les chaînes de caractères en majuscules sont prioritaires. Pour corriger cela, ajoutez .lower() a key sur re.split .

63voto

Daniel Stutzbach Points 20026

Court et doux :

sorted(data, key=lambda item: (int(item.partition(' ')[0])
                               if item[0].isdigit() else float('inf'), item))

Cette version :

  • Fonctionne dans Python 2 et Python 3, car :
    • Il ne suppose pas que vous comparez des chaînes de caractères et des entiers (ce qui ne fonctionnera pas en Python 3).
    • Il n'utilise pas le cmp pour sorted (qui n'existe pas dans Python 3)
  • Triera sur la partie chaîne si les quantités sont égales.

Si vous voulez que la sortie imprimée soit exactement comme décrite dans votre exemple, alors :

data = set(['booklet', '4 sheets', '48 sheets', '12 sheets'])
r = sorted(data, key=lambda item: (int(item.partition(' ')[0])
                                   if item[0].isdigit() else float('inf'), item))
print ',\n'.join(r)

0 votes

S'étrangle 4a sheets mais qui s'en soucie ? pour résoudre ce problème, il faudrait une vraie fonction au lieu d'une lambda.

1 votes

Cela pourrait fonctionner pour cet exemple trivial mais pas pour une liste comme ["1. bla", "2. blub"]. La séparation devrait probablement être une regex à la place, et aussi trier par la deuxième partie après, de sorte que ["1 bcd", "2 abc", "1 xyz"] sorte correctement.

0 votes

Malheureusement, @FrankyBoy a raison, cela ne fonctionne pas pour trier des ensembles de numéros de version par ordre alphanumérique, par exemple v1.0.1, v3.5.3, v3.2.4.

10voto

Ants Aasma Points 22921

Une méthode simple consiste à diviser les chaînes de caractères en parties numériques et non numériques et à utiliser l'ordre de tri des tuplets de Python pour trier les chaînes de caractères.

import re
tokenize = re.compile(r'(\d+)|(\D+)').findall
def natural_sortkey(string):          
    return tuple(int(num) if num else alpha for num, alpha in tokenize(string))

sorted(my_set, key=natural_sortkey)

8voto

gnibbler Points 103484

Il m'a été suggéré de reposter cette réponse ici, car il fonctionne bien pour ce cas aussi.

from itertools import groupby
def keyfunc(s):
    return [int(''.join(g)) if k else ''.join(g) for k, g in groupby(s, str.isdigit)]

sorted(my_list, key=keyfunc)

Démonstration :

>>> my_set = {'booklet', '4 sheets', '48 sheets', '12 sheets'}
>>> sorted(my_set, key=keyfunc)
['4 sheets', '12 sheets', '48 sheets', 'booklet']

Pour Python3, il est nécessaire de le modifier légèrement (cette version fonctionne également avec Python2).

def keyfunc(s):
    return [int(''.join(g)) if k else ''.join(g) for k, g in groupby('\0'+s, str.isdigit)]

0voto

Giacomo Lacava Points 923

Pour les personnes coincées avec une version de Python antérieure à la version 2.4, sans la merveilleuse fonction sorted() un moyen rapide de trier les ensembles :

l = list(yourSet)
l.sort() 

Cela ne répond pas à la question spécifique ci-dessus ( 12 sheets viendra avant 4 sheets ), mais il pourrait être utile aux personnes venant de Google.

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