Comment trier deux listes (qui se référencent) de la même manière

139

Disons que j'ai deux listes:

list1 = [3, 2, 4, 1, 1]
list2 = ['three', 'two', 'four', 'one', 'one2']

Si je cours list1.sort(), il le triera, [1,1,2,3,4]mais y a-t-il un moyen de se list2synchroniser également (pour que je puisse dire que l'élément 4appartient à 'three')? Ainsi, le résultat attendu serait:

list1 = [1, 1, 2, 3, 4]
list2 = ['one', 'one2', 'two', 'three', 'four']

Mon problème est que j'ai un programme assez complexe qui fonctionne bien avec les listes, mais j'ai en quelque sorte besoin de commencer à référencer certaines données. Je sais que c'est une situation parfaite pour les dictionnaires, mais j'essaie d'éviter les dictionnaires dans mon traitement car j'ai besoin de trier les valeurs clés (si je dois utiliser des dictionnaires, je sais comment les utiliser).

Fondamentalement, la nature de ce programme est que les données sont présentées dans un ordre aléatoire (comme ci-dessus), je dois les trier, les traiter puis envoyer les résultats (l'ordre n'a pas d'importance mais les utilisateurs doivent savoir quel résultat appartient à quel clé). J'ai pensé à le mettre d'abord dans un dictionnaire, puis à trier la liste un mais je n'aurais aucun moyen de différencier les éléments dans le avec la même valeur si l'ordre n'est pas maintenu (cela peut avoir un impact lors de la communication des résultats aux utilisateurs). Donc, idéalement, une fois que j'aurais les listes, je préfère trouver un moyen de trier les deux listes ensemble. Est-ce possible?

Erreur 404
la source
Je dois souligner que vos variables dans list2 ne pointent pas vers les entiers de list1. Par exemple, si vous modifiez une valeur telle que list1 [0] = 9 et regardez list2, list2 [0] sera toujours 3. Avec des entiers en python, il n'utilise pas la référence / pointeur, il copie la valeur. Vous auriez été mieux d'aller list2 = list1 [:]
robert king

Réponses:

242

Une approche classique de ce problème consiste à utiliser l'idiome "décorer, trier, décorer", qui est particulièrement simple en utilisant la fonction intégrée de python zip:

>>> list1 = [3,2,4,1, 1]
>>> list2 = ['three', 'two', 'four', 'one', 'one2']
>>> list1, list2 = zip(*sorted(zip(list1, list2)))
>>> list1
(1, 1, 2, 3, 4)
>>> list2 
('one', 'one2', 'two', 'three', 'four')

Ce ne sont bien sûr plus des listes, mais c'est facilement résolu, si cela compte:

>>> list1, list2 = (list(t) for t in zip(*sorted(zip(list1, list2))))
>>> list1
[1, 1, 2, 3, 4]
>>> list2
['one', 'one2', 'two', 'three', 'four']

Il convient de noter que ce qui précède peut sacrifier la vitesse au profit de la concision; la version en place, qui prend 3 lignes, est un peu plus rapide sur ma machine pour les petites listes:

>>> %timeit zip(*sorted(zip(list1, list2)))
100000 loops, best of 3: 3.3 us per loop
>>> %timeit tups = zip(list1, list2); tups.sort(); zip(*tups)
100000 loops, best of 3: 2.84 us per loop

En revanche, pour les listes plus volumineuses, la version une ligne pourrait être plus rapide:

>>> %timeit zip(*sorted(zip(list1, list2)))
100 loops, best of 3: 8.09 ms per loop
>>> %timeit tups = zip(list1, list2); tups.sort(); zip(*tups)
100 loops, best of 3: 8.51 ms per loop

Comme le souligne Quantum7, la suggestion de JSF est encore un peu plus rapide, mais ce ne sera probablement qu'un peu plus rapide, car Python utilise le même idiome DSU en interne pour tous les types basés sur des clés. Cela se passe juste un peu plus près du métal nu. (Cela montre à quel point les ziproutines sont optimisées !)

Je pense que l' zipapproche basée sur la base est plus flexible et un peu plus lisible, donc je la préfère.

expéditeur
la source
6
que représente l'astérisque de la troisième ligne?
Jeffrey
8
Pour élaborer sur ce qui précède, l' *opérateur effectue le déballage des arguments ,
senderle
1
Le paradigme d'index / carte trié suggéré par JF Sebastian est environ 10% plus rapide que l'une ou l'autre des solutions zip pour moi (en utilisant des listes de 10000 entiers aléatoires):% timeit index = range (len (l1)); index.sort (clé = l1 .__ getitem__); map (l1 .__ getitem__, index); map (l2 .__ getitem__, index) 100 boucles, meilleur de 3: 8,04 ms par boucle (contre 9,17 ms, 9,07 ms pour les timits de l'expéditeur)
Quantum7
1
Le premier et le deuxième zip de list1, list2 = zip (* sorted (zip (list1, list2))) font des choses très différentes. Le * fait toute la différence.
ashu
1
@ashu, dans un sens, oui! Mais dans un autre sens, ils ne sont pas du tout différents. zip(*x)a la propriété intéressante qu'il est son propre inverse: l = [(1, 2), (3, 4)]; list(zip(*zip(*l))) == lretourne True. C'est effectivement un opérateur de transposition. zip()seul est le même opérateur, mais suppose que vous avez décompressé la séquence d'entrée manuellement.
senderle
30

Vous pouvez trier les index en utilisant des valeurs comme clés:

indexes = range(len(list1))
indexes.sort(key=list1.__getitem__)

Pour obtenir des listes triées avec des index triés:

sorted_list1 = map(list1.__getitem__, indexes)
sorted_list2 = map(list2.__getitem__, indexes)

Dans votre cas, vous ne devriez pas avoir list1, list2mais plutôt une seule liste de paires:

data = [(3, 'three'), (2, 'two'), (4, 'four'), (1, 'one'), (1, 'one2')]

C'est facile à créer; il est facile de trier en Python:

data.sort() # sort using a pair as a key

Trier uniquement par la première valeur:

data.sort(key=lambda pair: pair[0])
jfs
la source
Ce qui est cool à ce sujet, c'est que je peux garder des index et trier d'autres choses plus tard, au cas où list1 est une coordonnée importante qui affecte plusieurs autres tableaux.
EL_DON
3
indexes = list (range (len (list1))) pour python 3
DonQuiKong
@DonQuiKong, vous devez également vous list() déplacer map()si vous souhaitez utiliser ce code en Python 3.
jfs
Ou, au lieu d' sorted_list1 = list(map(list1.__getitem__, indexes))un pourrait faire sorted_list1 = [list1[i] for i in indexes].
Nathan le
20

J'ai utilisé la réponse donnée par senderle pendant longtemps jusqu'à ce que je découvre np.argsort. Voici comment cela fonctionne.

# idx works on np.array and not lists.
list1 = np.array([3,2,4,1])
list2 = np.array(["three","two","four","one"])
idx   = np.argsort(list1)

list1 = np.array(list1)[idx]
list2 = np.array(list2)[idx]

Je trouve cette solution plus intuitive et ça marche vraiment bien. La performance:

def sorting(l1, l2):
    # l1 and l2 has to be numpy arrays
    idx = np.argsort(l1)
    return l1[idx], l2[idx]

# list1 and list2 are np.arrays here...
%timeit sorting(list1, list2)
100000 loops, best of 3: 3.53 us per loop

# This works best when the lists are NOT np.array
%timeit zip(*sorted(zip(list1, list2)))
100000 loops, best of 3: 2.41 us per loop

# 0.01us better for np.array (I think this is negligible)
%timeit tups = zip(list1, list2); tups.sort(); zip(*tups)
100000 loops, best for 3 loops: 1.96 us per loop

Même si ce np.argsortn'est pas le plus rapide, je le trouve plus facile à utiliser.

Daniel Thaagaard Andreasen
la source
1
TypeError: only integer arrays with one element can be converted to an indexJ'obtiens une erreur lors de l'exécution de votre exemple: (Python 2.7.6, numpy 1.8.2). Pour résoudre ce problème, list1 et list2 doivent être déclarés comme des tableaux numpy.
BenB
Merci. N'est-ce pas ce que j'écris dans le commentaire de la fonction? Quoi qu'il en soit, je pense que c'est idiot de np.argsortne pas essayer de se convertir en np.arrayinterne.
Daniel Thaagaard Andreasen
Je faisais référence au premier extrait de code car il ne fonctionne pas comme écrit :)
BenB
Je l'ai corrigé en convertissant les listes lorsqu'elles sont affectées à des tableaux numpy. Merci pour le commentaire :)
Daniel Thaagaard Andreasen
Maintenant, ils sont convertis deux fois en tableaux Numpy;)
BenB
13

Transformée schwartzienne . Le tri Python intégré est stable, donc les deux 1s ne posent pas de problème.

>>> l1 = [3, 2, 4, 1, 1]
>>> l2 = ['three', 'two', 'four', 'one', 'second one']
>>> zip(*sorted(zip(l1, l2)))
[(1, 1, 2, 3, 4), ('one', 'second one', 'two', 'three', 'four')]
Karl Knechtel
la source
2
Cependant, si vous trouvez que vous avez besoin de le faire, vous devriez fortement reconsidérer avoir les deux listes de données "parallèles", par opposition à garder une liste de 2-tuples (paires) ... ou peut-être même créer réellement une classe .
Karl Knechtel
3

Qu'en est-il de:

list1 = [3,2,4,1, 1]
list2 = ['three', 'two', 'four', 'one', 'one2']

sortedRes = sorted(zip(list1, list2), key=lambda x: x[0]) # use 0 or 1 depending on what you want to sort
>>> [(1, 'one'), (1, 'one2'), (2, 'two'), (3, 'three'), (4, 'four')]
Artsiom Rudzenka
la source
2

Vous pouvez utiliser les fonctions zip()et sort()pour ce faire:

Python 2.6.5 (r265:79063, Jun 12 2010, 17:07:01)
[GCC 4.3.4 20090804 (release) 1] on cygwin
>>> list1 = [3,2,4,1,1]
>>> list2 = ['three', 'two', 'four', 'one', 'one2']
>>> zipped = zip(list1, list2)
>>> zipped.sort()
>>> slist1 = [i for (i, s) in zipped]
>>> slist1
[1, 1, 2, 3, 4]
>>> slist2 = [s for (i, s) in zipped]
>>> slist2
['one', 'one2', 'two', 'three', 'four']

J'espère que cela t'aides

Hunter McMillen
la source
2

Vous pouvez utiliser l'argument clé dans la méthode sorted () sauf si vous avez deux mêmes valeurs dans list2.

Le code est donné ci-dessous:

sorted(list2, key = lambda x: list1[list2.index(x)]) 

Il trie list2 en fonction des valeurs correspondantes dans list1, mais assurez-vous que lors de son utilisation, deux valeurs de list2 ne sont pas évaluées égales car la fonction list.index () donne la première valeur

Saurav Yadav
la source
trié est un peu lent dans certaines conditions bien que cela fonctionne.
tyan
2

Une façon est de suivre où chaque index va en triant l'identité [0,1,2, .. n]

Cela fonctionne pour n'importe quel nombre de listes.

Déplacez ensuite chaque élément à sa position. Il est préférable d'utiliser des épissures.

list1 = [3,2,4,1, 1]
list2 = ['three', 'two', 'four', 'one', 'one2']

index = list(range(len(list1)))
print(index)
'[0, 1, 2, 3, 4]'

index.sort(key = list1.__getitem__)
print(index)
'[3, 4, 1, 0, 2]'

list1[:] = [list1[i] for i in index]
list2[:] = [list2[i] for i in index]

print(list1)
print(list2)
'[1, 1, 2, 3, 4]'
"['one', 'one2', 'two', 'three', 'four']"

Notez que nous aurions pu itérer les listes sans même les trier:

list1_iter = (list1[i] for i in index)
Robert King
la source
1

Si vous utilisez numpy, vous pouvez utiliser np.argsortpour obtenir les indices triés et appliquer ces indices à la liste. Cela fonctionne pour n'importe quel nombre de listes que vous souhaitez trier.

import numpy as np

arr1 = np.array([4,3,1,32,21])
arr2 = arr1 * 10
sorted_idxs = np.argsort(arr1)

print(sorted_idxs)
>>> array([2, 1, 0, 4, 3])

print(arr1[sorted_idxs])
>>> array([ 1,  3,  4, 21, 32])

print(arr2[sorted_idxs])
>>> array([ 10,  30,  40, 210, 320])
Kurtis Streutker
la source
0

une solution algorithmique:

list1 = [3,2,4,1, 1]
list2 = ['three', 'two', 'four', 'one', 'one2']


lis = [(list1[i], list2[i]) for i in range(len(list1))]
list1.sort()
list2 = [x[1] for i in range(len(list1)) for x in lis if x[0] == i]

Sorties: -> Vitesse de sortie: 0.2s

>>>list1
>>>[1, 1, 2, 3, 4]
>>>list2
>>>['one', 'one2', 'two', 'three', 'four']
Jundullah
la source
0

Une autre approche pour conserver l'ordre d'une liste de chaînes lors du tri par rapport à une autre liste est la suivante:

list1 = [3,2,4,1, 1]
list2 = ['three', 'two', 'four', 'one', 'one2']

# sort on list1 while retaining order of string list
sorted_list1 = [y for _,y in sorted(zip(list1,list2),key=lambda x: x[0])]
sorted_list2 = sorted(list1)

print(sorted_list1)
print(sorted_list2)

production

['one', 'one2', 'two', 'three', 'four']
[1, 1, 2, 3, 4]
Brock
la source
0

Je voudrais développer la réponse d' Open jfs , qui a très bien fonctionné pour mon problème: trier deux listes par une troisième liste décorée :

Nous pouvons créer notre liste décorée de n'importe quelle manière, mais dans ce cas, nous la créerons à partir des éléments de l'une des deux listes originales que nous voulons trier:

# say we have the following list and we want to sort both by the algorithms name 
# (if we were to sort by the string_list, it would sort by the numerical 
# value in the strings)
string_list = ["0.123 Algo. XYZ", "0.345 Algo. BCD", "0.987 Algo. ABC"]
dict_list = [{"dict_xyz": "XYZ"}, {"dict_bcd": "BCD"}, {"dict_abc": "ABC"}]

# thus we need to create the decorator list, which we can now use to sort
decorated = [text[6:] for text in string_list]  
# decorated list to sort
>>> decorated
['Algo. XYZ', 'Algo. BCD', 'Algo. ABC']

Nous pouvons maintenant appliquer la solution de jfs pour trier nos deux listes par la troisième

# create and sort the list of indices
sorted_indices = list(range(len(string_list)))
sorted_indices.sort(key=decorated.__getitem__)

# map sorted indices to the two, original lists
sorted_stringList = list(map(string_list.__getitem__, sorted_indices))
sorted_dictList = list(map(dict_list.__getitem__, sorted_indices))

# output
>>> sorted_stringList
['0.987 Algo. ABC', '0.345 Algo. BCD', '0.123 Algo. XYZ']
>>> sorted_dictList
[{'dict_abc': 'ABC'}, {'dict_bcd': 'BCD'}, {'dict_xyz': 'XYZ'}]

Edit: Hé les gars, j'ai fait un article de blocage à ce sujet, vérifiez-le si vous en avez envie :) 🐍🐍🐍

frietz58
la source
-1
newsource=[];newtarget=[]
for valueT in targetFiles:
    for valueS in sourceFiles:
            l1=len(valueS);l2=len(valueT);
            j=0
            while (j< l1):
                    if (str(valueT) == valueS[j:l1]) :
                            newsource.append(valueS)
                            newtarget.append(valueT)
                    j+=1
user10340258
la source
2
quelques lignes d'explication seraient utiles
saiedmomen
@saiedmomen Je l'ai posté en référence à stackoverflow.com/questions/53829160/… Ici, la chaîne cible est recherchée sur la chaîne source.
user10340258