Trier la liste en fonction des valeurs d'une autre liste?

370

J'ai une liste de chaînes comme celle-ci:

X = ["a", "b", "c", "d", "e", "f", "g", "h", "i"]
Y = [ 0,   1,   1,   0,   1,   2,   2,   0,   1 ]

Quel est le moyen le plus court de trier X en utilisant les valeurs de Y pour obtenir la sortie suivante?

["a", "d", "h", "b", "c", "e", "i", "f", "g"]

L'ordre des éléments ayant la même "clé" n'a pas d'importance. Je peux recourir à l'utilisation de forconstructions mais je suis curieux de savoir s'il existe un chemin plus court. Aucune suggestion?

Légende
la source
La réponse de riza peut être utile lors du traçage des données, car zip (* trié (zip (X, Y), clé = lambda paire: paire [0])) renvoie à la fois les X et Y triés avec les valeurs de X.
jojo

Réponses:

479

Code le plus court

[x for _,x in sorted(zip(Y,X))]

Exemple:

X = ["a", "b", "c", "d", "e", "f", "g", "h", "i"]
Y = [ 0,   1,   1,    0,   1,   2,   2,   0,   1]

Z = [x for _,x in sorted(zip(Y,X))]
print(Z)  # ["a", "d", "h", "b", "c", "e", "i", "f", "g"]

En général

[x for _, x in sorted(zip(Y,X), key=lambda pair: pair[0])]

Expliqué:

  1. ziples deux listart.
  2. créer un nouveau, trié en listfonction de l' ziputilisation sorted().
  3. en utilisant une compréhension de liste, extrayez les premiers éléments de chaque paire des fichiers triés et zippés list.

Pour plus d'informations sur la façon de définir \ utiliser le keyparamètre ainsi que la sortedfonction en général, jetez un œil à ceci .


Whatang
la source
117
C'est correct, mais j'ajouterai que si vous essayez de trier plusieurs tableaux par le même tableau, cela ne fonctionnera pas nécessairement comme prévu, car la clé utilisée pour trier est (y, x) , pas seulement y. Vous devriez plutôt utiliser [x pour (y, x) dans trié (zip (Y, X), clé = lambda paire: paire [0])]
gms7777
1
bonne solution! Mais cela devrait être: la liste est ordonnée en ce qui concerne le premier élément des paires, et la compréhension extrait le «deuxième» élément des paires.
MasterControlProgram
Cette solution est médiocre en matière de stockage. Un tri sur place est préférable dans la mesure du possible.
Hatefiend
107

Compressez les deux listes, triez-les, puis prenez les parties que vous souhaitez:

>>> yx = zip(Y, X)
>>> yx
[(0, 'a'), (1, 'b'), (1, 'c'), (0, 'd'), (1, 'e'), (2, 'f'), (2, 'g'), (0, 'h'), (1, 'i')]
>>> yx.sort()
>>> yx
[(0, 'a'), (0, 'd'), (0, 'h'), (1, 'b'), (1, 'c'), (1, 'e'), (1, 'i'), (2, 'f'), (2, 'g')]
>>> x_sorted = [x for y, x in yx]
>>> x_sorted
['a', 'd', 'h', 'b', 'c', 'e', 'i', 'f', 'g']

Combinez-les ensemble pour obtenir:

[x for y, x in sorted(zip(Y, X))]
Ned Batchelder
la source
1
C'est bien si Xc'est une liste de str, mais soyez prudent s'il y a une possibilité qui <n'est pas définie pour certaines paires d'articles dans X, par exemple - si certains d'entre eux l'étaientNone
John La Rooy
1
Lorsque nous essayons d'utiliser le tri sur un objet zip, AttributeError: 'zip' object has no attribute 'sort'c'est ce que je reçois à partir de maintenant.
Ash Upadhyay
2
Vous utilisez Python 3. Dans Python 2, zip a produit une liste. Maintenant, il produit un objet itérable. sorted(zip(...))devrait toujours fonctionner, ou: them = list(zip(...)); them.sort()
Ned Batchelder
77

De plus, si cela ne vous dérange pas d'utiliser des tableaux numpy (ou si vous avez déjà affaire à des tableaux numpy ...), voici une autre bonne solution:

people = ['Jim', 'Pam', 'Micheal', 'Dwight']
ages = [27, 25, 4, 9]

import numpy
people = numpy.array(people)
ages = numpy.array(ages)
inds = ages.argsort()
sortedPeople = people[inds]

Je l'ai trouvé ici: http://scienceoss.com/sort-one-list-by-another-list/

À M
la source
1
Pour les tableaux / vecteurs plus gros, cette solution avec numpy est bénéfique!
MasterControlProgram
1
S'ils sont déjà des tableaux numpy, c'est tout simplement sortedArray1= array1[array2.argsort()]. Et cela facilite également le tri de plusieurs listes par une colonne particulière d'un tableau 2D: par exemple, sortedArray1= array1[array2[:,2].argsort()]pour trier le tableau1 (qui peut avoir plusieurs colonnes) par les valeurs de la troisième colonne du tableau2.
Aaron Bramson
40

La solution la plus évidente pour moi est d'utiliser le keymot - clé arg.

>>> X = ["a", "b", "c", "d", "e", "f", "g", "h", "i"]
>>> Y = [ 0,   1,   1,    0,   1,   2,   2,   0,   1]
>>> keydict = dict(zip(X, Y))
>>> X.sort(key=keydict.get)
>>> X
['a', 'd', 'h', 'b', 'c', 'e', 'i', 'f', 'g']

Notez que vous pouvez le raccourcir en une ligne si vous souhaitez:

>>> X.sort(key=dict(zip(X, Y)).get)
senderle
la source
2
Cela nécessite-t-il que les valeurs de X ne soient pas égales?
Jack Peng
15

En fait, je suis venu ici pour trier une liste par liste où les valeurs correspondaient.

list_a = ['foo', 'bar', 'baz']
list_b = ['baz', 'bar', 'foo']
sorted(list_b, key=lambda x: list_a.index(x))
# ['foo', 'bar', 'baz']
nackjicholson
la source
1
Est-ce performant?
AFP_555
Aucune idée. Rapportez ce que vous trouvez.
nackjicholson
1
C'est une mauvaise idée. indexeffectuera une recherche O (N) sur list_arésultant en un O(N² log N)tri.
Richard
Merci, ne faites pas ça quand les performances comptent!
nackjicholson
15

more_itertools dispose d'un outil pour trier les itérables en parallèle:

Donné

from more_itertools import sort_together


X = ["a", "b", "c", "d", "e", "f", "g", "h", "i"]
Y = [ 0,   1,   1,    0,   1,   2,   2,   0,   1]

Démo

sort_together([Y, X])[1]
# ('a', 'd', 'h', 'b', 'c', 'e', 'i', 'f', 'g')
pylang
la source
13

J'aime avoir une liste d'indices triés. De cette façon, je peux trier n'importe quelle liste dans le même ordre que la liste source. Une fois que vous avez une liste d'indices triés, une simple compréhension de liste fera l'affaire:

X = ["a", "b", "c", "d", "e", "f", "g", "h", "i"]
Y = [ 0,   1,   1,    0,   1,   2,   2,   0,   1]

sorted_y_idx_list = sorted(range(len(Y)),key=lambda x:Y[x])
Xs = [X[i] for i in sorted_y_idx_list ]

print( "Xs:", Xs )
# prints: Xs: ["a", "d", "h", "b", "c", "e", "i", "f", "g"]

Notez que la liste d'index triée peut également être obtenue en utilisant numpy.argsort().

1-ijk
la source
12

Une autre alternative, combinant plusieurs des réponses.

zip(*sorted(zip(Y,X)))[1]

Afin de travailler pour python3:

list(zip(*sorted(zip(B,A))))[1]
TMC
la source
7

zip, trier par la deuxième colonne, retourner la première colonne.

zip(*sorted(zip(X,Y), key=operator.itemgetter(1)))[0]
riza
la source
Remarque: la clé = operator.itemgetter (1) résout le problème en double
Keith
zip n'est pas indexable ... vous devez réellement utiliserlist(zip(*sorted(zip(X,Y), key=operator.itemgetter(1))))[0]
raphael
@Keith quel problème en double?
Josh
S'il y a plus d'un correspondant, il obtient le premier
Keith
3

Une doublure rapide.

list_a = [5,4,3,2,1]
list_b = [1,1.5,1.75,2,3,3.5,3.75,4,5]

Dites que vous voulez que la liste a corresponde à la liste b.

orderedList =  sorted(list_a, key=lambda x: list_b.index(x))

Cela est utile lorsque vous devez commander une liste plus petite avec des valeurs plus grandes. En supposant que la liste plus grande contient toutes les valeurs de la liste plus petite, cela peut être fait.

Evan Lalo
la source
Cela ne résout pas la question du PO. L'avez-vous essayé avec les exemples de listes Xet Y?
Aryeh Leib Taurog
C'est une mauvaise idée. indexeffectuera une recherche O (N) sur list_brésultant en un O(N² log N)tri.
Richard
1

Vous pouvez créer un pandas Series, en utilisant la liste principale as dataet l'autre liste as index, puis trier simplement par l'index:

import pandas as pd
pd.Series(data=X,index=Y).sort_index().tolist()

production:

['a', 'd', 'h', 'b', 'c', 'e', 'i', 'f', 'g']
Binyamin Even
la source
1

Voici la réponse de Whatangs si vous souhaitez obtenir les deux listes triées (python3).

X = ["a", "b", "c", "d", "e", "f", "g", "h", "i"]
Y = [ 0,   1,   1,    0,   1,   2,   2,   0,   1]

Zx, Zy = zip(*[(x, y) for x, y in sorted(zip(Y, X))])

print(list(Zx))  # [0, 0, 0, 1, 1, 1, 1, 2, 2]
print(list(Zy))  # ['a', 'd', 'h', 'b', 'c', 'e', 'i', 'f', 'g']

N'oubliez pas que Zx et Zy sont des tuples. Je me demande également s'il existe une meilleure façon de le faire.

Avertissement: si vous l'exécutez avec des listes vides, il se bloque.

Iraklis Moutidis
la source
1

J'ai créé une fonction plus générale, qui trie plus de deux listes en fonction d'une autre, inspirée de la réponse de @ Whatang.

def parallel_sort(*lists):
    """
    Sorts the given lists, based on the first one.
    :param lists: lists to be sorted

    :return: a tuple containing the sorted lists
    """

    # Create the initially empty lists to later store the sorted items
    sorted_lists = tuple([] for _ in range(len(lists)))

    # Unpack the lists, sort them, zip them and iterate over them
    for t in sorted(zip(*lists)):
        # list items are now sorted based on the first list
        for i, item in enumerate(t):    # for each item...
            sorted_lists[i].append(item)  # ...store it in the appropriate list

    return sorted_lists
pgmank
la source
0
list1 = ['a','b','c','d','e','f','g','h','i']
list2 = [0,1,1,0,1,2,2,0,1]

output=[]
cur_loclist = []

Pour obtenir des valeurs uniques présentes dans list2

list_set = set(list2)

Pour trouver l'emplacement de l'index dans list2

list_str = ''.join(str(s) for s in list2)

L'emplacement de l'index dans list2est suivi à l'aide decur_loclist

[0, 3, 7, 1, 2, 4, 8, 5, 6]

for i in list_set:
cur_loc = list_str.find(str(i))

while cur_loc >= 0:
    cur_loclist.append(cur_loc)
    cur_loc = list_str.find(str(i),cur_loc+1)

print(cur_loclist)

for i in range(0,len(cur_loclist)):
output.append(list1[cur_loclist[i]])
print(output)
VANI
la source
0

Il s'agit d'une vieille question, mais certaines des réponses que je vois publiées ne fonctionnent pas car elles zipne sont pas scriptables. Les autres réponses n'ont pas pris la peine de import operatorfournir plus d'informations sur ce module et ses avantages ici.

Il existe au moins deux bons idiomes pour ce problème. En commençant par l'exemple d'entrée que vous avez fourni:

X = ["a", "b", "c", "d", "e", "f", "g", "h", "i"]
Y = [ 0,   1,   1,   0,   1,   2,   2,   0,   1 ]

Utilisation de l' idiome " Décorer-Trier-Décorer "

Ceci est également connu sous le nom de Schwartzian_transform après R. Schwartz qui a popularisé ce modèle en Perl dans les années 90:

# Zip (decorate), sort and unzip (undecorate).
# Converting to list to script the output and extract X
list(zip(*(sorted(zip(Y,X)))))[1]                                                                                                                       
# Results in: ('a', 'd', 'h', 'b', 'c', 'e', 'i', 'f', 'g')

Notez que dans ce cas Yet Xsont triés et comparés lexicographiquement. Autrement dit, les premiers éléments (de Y) sont comparés; et s'ils sont identiques, les deuxièmes éléments (de X) sont comparés, etc. Cela peut créer des sorties instables sauf si vous incluez les index de liste d'origine pour l'ordre lexicographique afin de conserver les doublons dans leur ordre d'origine.

Utilisation du operatormodule

Cela vous donne un contrôle plus direct sur la façon de trier l'entrée, de sorte que vous pouvez obtenir une stabilité de tri en indiquant simplement la clé spécifique à trier. Voir plus d'exemples ici .

import operator    

# Sort by Y (1) and extract X [0]
list(zip(*sorted(zip(X,Y), key=operator.itemgetter(1))))[0]                                                                                                 
# Results in: ('a', 'd', 'h', 'b', 'c', 'e', 'i', 'f', 'g')
Amelio Vazquez-Reina
la source