web-dev-qa-db-fra.com

Tri d'une liste: nombres en ordre croissant, lettres en ordre décroissant

Cette question est en fait adaptée de une question posée précédemment par Mat.S ( image ). Bien qu’elle ait été supprimée, j’ai pensé que c’était une bonne question et je la republie donc avec des exigences plus claires et ma propre solution.


Étant donné une liste de lettres et de chiffres, disons

['a', 2, 'b', 1, 'c', 3]

L’exigence est de trier les chiffres en ordre croissant et les lettres en ordre décroissant, sans modifier la position relative des lettres et des chiffres. Je veux dire par là si la liste non triée est:

[L, D, L, L, D]    # L -> letter; # D -> digit 

Ensuite, la liste triée doit également être 

[L, D, L, L, D] 
  1. Les lettres et les chiffres font pas alternent nécessairement de manière régulière - ils peuvent apparaître dans n’importe quel ordre.

  2. Après le tri - les chiffres sont croissants, les lettres décroissants.

Donc, pour l'exemple ci-dessus, la sortie est

['c', 1, 'b', 2, 'a', 3]

Un autre exemple:

 In[]: [5, 'a', 'x', 3, 6, 'b']
Out[]: [3, 'x', 'b', 5, 6, 'a']

Quel serait un bon moyen de faire cela?

12
coldspeed

Voici une approche optimisée utilisant defaultdict() et bisect():

In [14]: lst = [5, 'a', 'x', 3, 6, 'b']
In [15]: from collections import defaultdict       
In [16]: import bisect

In [17]: def use_dict_with_bisect(lst):
             d = defaultdict(list)
             for i in lst:
                 bisect.insort(d[type(i)], i)
             # since bisect doesn't accept key we need to reverse the sorted integers
             d[int].sort(reverse=True)
             return [d[type(i)].pop() for i in lst]
   .....:  

Démo:

In [18]: lst
Out[18]: [5, 'a', 'x', 3, 6, 'b']

In [19]: use_dict_with_bisect(lst)
Out[19]: [3, 'x', 'b', 5, 6, 'a']

Dans le cas où vous avez affaire à des listes plus volumineuses, il est plus optimisé d’abandonner avec bisect qui présente une complexité de O (n).2) et utilisez simplement la fonction sort() intégrée à Python avec la complexité Nlog (n).

In [26]: def use_dict(lst):
             d = defaultdict(list)
             for i in lst:
                 d[type(i)].append(i)
             d[int].sort(reverse=True); d[str].sort()
             return [d[type(i)].pop() for i in lst]

Le repère avec d’autres réponses montrant que la dernière approche utilisant dict et sort intégrée est presque 1 ms plus rapide que les autres approches:

In [29]: def use_sorted1(lst):
              letters = sorted(let for let in lst if isinstance(let,str))
              numbers = sorted((num for num in lst if not isinstance(num,str)), reverse = True)
              return [letters.pop() if isinstance(elt,str) else numbers.pop() for elt in lst]
   .....: 

In [31]: def use_sorted2(lst):
              f1 = iter(sorted(filter(lambda x: isinstance(x, str), lst), reverse=True))
              f2 = iter(sorted(filter(lambda x: not isinstance(x, str), lst)))
              return [next(f1) if isinstance(x, str) else next(f2) for x in lst]
   .....: 

In [32]: %timeit use_sorted1(lst * 1000)
100 loops, best of 3: 3.05 ms per loop

In [33]: %timeit use_sorted2(lst * 1000)
100 loops, best of 3: 3.63 ms per loop

In [34]: %timeit use_dict(lst * 1000)   # <-- WINNER
100 loops, best of 3: 2.15 ms per loop

Voici un point de repère qui montre comment utiliser bisect peut ralentir le processus pour les longues listes: 

In [37]: %timeit use_dict_with_bisect(lst * 1000)
100 loops, best of 3: 4.46 ms per loop
6
Kasrâmvd

Regardez ma, pas de iter:

lst = ['a', 2, 'b', 1, 'c', 3]
letters = sorted(let for let in lst if isinstance(let,str))
numbers = sorted((num for num in lst if not isinstance(num,str)), reverse = True)
lst = [(letters if isinstance(elt,str) else numbers).pop()for elt in lst]

Je cherche un moyen de transformer cela en un (horrible) one-liner, mais pas de chance jusqu'à présent - les suggestions sont les bienvenues!

5
perigon

J'ai tenté de résoudre ce problème en créant deux générateurs, puis en les prenant conditionnellement:

In [116]: f1 = iter(sorted(filter(lambda x: isinstance(x, str), lst), reverse=True))

In [117]: f2 = iter(sorted(filter(lambda x: not isinstance(x, str), lst)))

In [118]: [next(f1) if isinstance(x, str) else next(f2) for x in lst]
Out[118]: ['c', 1, 'b', 2, 'a', 3]
4
coldspeed

En une ligne:

list(map(list, sorted(Zip(lst[::2], lst[1::2]), key=lambda x: x[1] if hasattr(x[0], '__iter__') else x[0])))
1
AGN Gazer

Totalement déconseillé, mais je me suis amusé à le coder.

from collections import deque
from operator import itemgetter

lst = ['a', 2, 'b', 1, 'c', 3]
is_str = [isinstance(e, str) for e in lst]
two_heads = deque(map(itemgetter(1), sorted(Zip(is_str, lst))))
[two_heads.pop() if a_str else two_heads.popleft() for a_str in is_str]
0
frogcoder

Pourquoi ne pas simplement trier la liste par ordre croissant, mais veiller à ce que les nombres viennent avant les lettres:

[D, D, L, L, L]    # L -> letter; # D -> digit 

Nous pouvons y parvenir de la manière suivante:

>>> lst = [5, 'a', 'x', 3, 6, 'b']
>>> sorted(lst, key=lambda el: (isinstance(el, str), el))
[3, 5, 6, 'a', 'b', 'x']

Ensuite, nous examinons le tableau d'origine de gauche à droite et si nous rencontrons un nombre, nous sélectionnons l'élément au début du tableau trié, sinon à la fin. La solution complète verbeuse sera alors:

def one_sort(lst):
    s = sorted(lst, key=lambda el: (isinstance(el, str), el))
    res = []
    i, j = 0, len(s)
    for el in lst:
        if isinstance(el, str):
            j -= 1
            res.append(s[j])
        else:
            res.append(s[i])
            i += 1
    return res

lst = [5, 'a', 'x', 3, 6, 'b']
print(one_sort(lst)) # [3, 'x', 'b', 5, 6, 'a']

La solution beaucoup plus courte mais cryptique sera:

def one_sort_cryptic(lst):
    s = sorted(lst, key=lambda el: (isinstance(el, str), el))
    return [s.pop(-isinstance(el, str)) for el in lst]

lst = [5, 'a', 'x', 3, 6, 'b']
print(one_sort_cryptic(lst)) # [3, 'x', 'b', 5, 6, 'a']
0
cltif