Mengurutkan daftar berdasarkan nilai dari daftar lain?

370

Saya punya daftar string seperti ini:

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

Apa cara terpendek untuk menyortir X menggunakan nilai dari Y untuk mendapatkan output berikut?

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

Urutan elemen yang memiliki "kunci" yang sama tidak masalah. Saya dapat menggunakan forkonstruksi tetapi saya ingin tahu apakah ada cara yang lebih pendek. Ada saran?

Legenda
sumber
Jawaban riza mungkin berguna ketika memplot data, karena zip (* diurutkan (zip (X, Y), kunci = pasangan lambda: pasangan [0])) mengembalikan X dan Y yang diurutkan dengan nilai X.
jojo

Jawaban:

479

Kode Terpendek

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

Contoh:

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

Secara umum

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

Dijelaskan:

  1. zipkeduanya list.
  2. buat yang baru, diurutkan listberdasarkan zippenggunaan sorted().
  3. menggunakan daftar pemahaman ekstrak elemen pertama dari setiap pasangan dari diurutkan, di-zip list.

Untuk informasi lebih lanjut tentang cara mengatur \ gunakan keyparameter serta sortedfungsi secara umum, lihat ini .


Whatang
sumber
117
Ini benar, tetapi saya akan menambahkan catatan bahwa jika Anda mencoba mengurutkan banyak array dengan array yang sama, ini tidak akan bekerja seperti yang diharapkan, karena kunci yang digunakan untuk mengurutkan adalah (y, x) , bukan hanya y. Anda sebaiknya menggunakan [x untuk (y, x) diurutkan (zip (Y, X), kunci = pasangan lambda: pasangan [0])]
gms7777
1
solusi bagus! Tetapi seharusnya: Daftar tersebut disusun mengenai elemen pertama dari pasangan, dan pemahaman mengekstraksi elemen 'kedua' dari pasangan.
MasterControlProgram
Solusi ini buruk untuk penyimpanan. Sortir di tempat lebih disukai bila memungkinkan.
Hatefiend
107

Masukkan kedua daftar menjadi satu, atur, lalu ambil bagian yang Anda inginkan:

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

Gabungkan ini bersama untuk mendapatkan:

[x for y, x in sorted(zip(Y, X))]
Ned Batchelder
sumber
1
Ini baik-baik saja jika Xdaftar str, tetapi hati-hati jika ada kemungkinan yang <tidak ditentukan untuk beberapa pasang barang di X, misalnya - jika beberapa dari merekaNone
John La Rooy
1
Ketika kami mencoba menggunakan sortir pada objek zip, AttributeError: 'zip' object has no attribute 'sort'itulah yang saya dapatkan sampai sekarang.
Ash Upadhyay
2
Anda menggunakan Python 3. Dalam Python 2, zip menghasilkan daftar. Sekarang ia menghasilkan objek yang dapat diubah. sorted(zip(...))seharusnya masih berfungsi, atau: them = list(zip(...)); them.sort()
Ned Batchelder
77

Juga, jika Anda tidak keberatan menggunakan numpy array (atau bahkan sudah berurusan dengan numpy array ...), berikut ini adalah solusi bagus:

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]

Saya menemukannya di sini: http://scienceoss.com/sort-one-list-by-another-list/

Tom
sumber
1
Untuk array / vektor yang lebih besar, solusi dengan numpy ini bermanfaat!
MasterControlProgram
1
Jika mereka sudah array numpy, maka itu sederhana sortedArray1= array1[array2.argsort()]. Dan ini juga membuatnya mudah untuk mengurutkan banyak daftar dengan kolom tertentu dari array 2D: misalnya sortedArray1= array1[array2[:,2].argsort()]untuk mengurutkan array1 (yang mungkin memiliki banyak kolom) dengan nilai-nilai di kolom ketiga array2.
Aaron Bramson
40

Solusi yang paling jelas bagi saya adalah menggunakan key kata kunci 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']

Perhatikan bahwa Anda dapat mempersingkat ini menjadi satu liner jika Anda ingin:

>>> X.sort(key=dict(zip(X, Y)).get)
pengirim
sumber
2
Apakah ini mensyaratkan bahwa nilai-nilai dalam X tidak benar?
Jack Peng
15

Saya sebenarnya datang ke sini mencari untuk mengurutkan daftar dengan daftar di mana nilai-nilai cocok.

list_a = ['foo', 'bar', 'baz']
list_b = ['baz', 'bar', 'foo']
sorted(list_b, key=lambda x: list_a.index(x))
# ['foo', 'bar', 'baz']
nackjicholson
sumber
1
Apakah pemain ini?
AFP_555
Tidak tahu. Laporkan kembali apa yang Anda temukan.
nackjicholson
1
Ini ide yang buruk. indexakan melakukan pencarian O (N) untuk list_amenghasilkan O(N² log N)semacam.
Richard
Terima kasih, jangan lakukan ini saat kinerja penting!
nackjicholson
15

more_itertools memiliki alat untuk mengurutkan iterables secara paralel:

Diberikan

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]

Demo

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

Saya suka memiliki daftar indeks yang diurutkan. Dengan begitu, saya bisa mengurutkan daftar apa pun dalam urutan yang sama dengan daftar sumber. Setelah Anda memiliki daftar indeks yang diurutkan, pemahaman daftar sederhana akan melakukan trik:

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

Perhatikan bahwa daftar indeks yang diurutkan juga bisa didapatkan numpy.argsort().

1-ijk
sumber
12

Alternatif lain, menggabungkan beberapa jawaban.

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

Agar dapat bekerja untuk python3:

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

zip, urutkan berdasarkan kolom kedua, kembalikan kolom pertama.

zip(*sorted(zip(X,Y), key=operator.itemgetter(1)))[0]
riza
sumber
Catatan: kunci = operator.itemgetter (1) memecahkan masalah duplikat
Keith
zip tidak dapat disubkripsikan ... Anda harus benar-benar menggunakanlist(zip(*sorted(zip(X,Y), key=operator.itemgetter(1))))[0]
raphael
@ Perhatikan masalah duplikat apa?
Josh
Jika ada lebih dari satu pencocokan, pencocokan pertama
Keith
3

Satu kalimat cepat.

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

Katakanlah Anda ingin daftar a untuk mencocokkan daftar b.

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

Ini membantu ketika perlu memesan daftar yang lebih kecil untuk nilai yang lebih besar. Dengan asumsi bahwa daftar yang lebih besar berisi semua nilai dalam daftar yang lebih kecil, itu bisa dilakukan.

Evan Lalo
sumber
Ini tidak menyelesaikan pertanyaan OP. Apakah Anda mencobanya dengan daftar sampel Xdan Y?
Aryeh Leib Taurog
Ini ide yang buruk. indexakan melakukan pencarian O (N) untuk list_bmenghasilkan O(N² log N)semacam.
Richard
1

Anda bisa membuat pandas Series, menggunakan daftar utama sebagai datadan daftar lainnya sebagai index, dan kemudian hanya mengurutkan berdasarkan indeks:

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

keluaran:

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

Inilah jawaban Whatangs jika Anda ingin mendapatkan kedua daftar yang diurutkan (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']

Ingat Zx dan Zy adalah tupel. Saya juga berkeliaran jika ada cara yang lebih baik untuk melakukan itu.

Peringatan: Jika Anda menjalankannya dengan daftar kosong itu macet.

Iraklis Moutidis
sumber
1

Saya telah membuat fungsi yang lebih umum, yang mengurutkan lebih dari dua daftar berdasarkan yang lain, terinspirasi oleh jawaban @ 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
sumber
0
list1 = ['a','b','c','d','e','f','g','h','i']
list2 = [0,1,1,0,1,2,2,0,1]

output=[]
cur_loclist = []

Untuk mendapatkan nilai unik yang ada di list2

list_set = set(list2)

Untuk menemukan lokasi indeks di list2

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

Lokasi indeks list2dilacak menggunakancur_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
sumber
0

Ini adalah pertanyaan lama tetapi beberapa jawaban yang saya lihat tidak benar-benar berfungsi karena ziptidak dapat skrip. Jawaban lain tidak menggangguimport operator dan memberikan lebih banyak info tentang modul ini dan manfaatnya di sini.

Setidaknya ada dua idiom yang bagus untuk masalah ini. Dimulai dengan contoh input yang Anda berikan:

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

Menggunakan " Hiasi-Sort-Undecorate idiom "

Ini juga dikenal sebagai Schwartzian_transform setelah R. Schwartz yang mempopulerkan pola ini di Perl pada tahun 90-an:

# 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')

Perhatikan bahwa dalam hal ini Ydan Xdiurutkan dan dibandingkan secara leksikografis. Artinya, item pertama (dari Y) dibandingkan; dan jika mereka sama maka item kedua (dari X) dibandingkan, dan seterusnya. Ini dapat membuat tidak stabil output yang kecuali jika Anda memasukkan indeks daftar asli untuk pemesanan leksikografis untuk menjaga duplikat dalam urutan aslinya.

Menggunakan operatormodul

Ini memberi Anda lebih banyak kontrol langsung tentang cara mengurutkan input, sehingga Anda bisa mendapatkan stabilitas pengurutan dengan hanya menyatakan kunci spesifik untuk mengurutkan berdasarkan. Lihat lebih banyak contoh di sini .

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
sumber