Urutkan 2 daftar dengan Python berdasarkan rasio masing-masing elemen yang sesuai atau berdasarkan daftar ketiga

Saya mencoba menulis implementasi berbeda untuk masalah ransel pecahan.

Untuk ini saya punya 2 array:

  1. Nilai-nilai
  2. beban

Nilai elemen[n] sesuai dengan bobot elemen[n]. Jadi kita dapat menghitung value_per_unit sebagai:

for I in range(values):
    value_per_unit.append(values[I]/weights[I])
value_per_unit.sort()

Saya sekarang membutuhkan 2 array (nilai dan bobot) untuk diurutkan berdasarkan array value_per_unit

eg: If

  • nilai = [60, 100, 120]
  • bobot = [20, 50, 30]

Kemudian

  • nilai_per_unit = [3.0, 2.0, 4.0]

  • dan nilai_per_unit_sorted akan menjadi [2.0, 3.0, 4.0]

Saya memerlukan array nilai dan bobot untuk menjadi:

  • nilai_diurutkan = [100,60,120]
  • bobot_diurutkan = [50,20,30]

Apakah ada cara untuk mencapai hal ini dengan menggunakan fungsi lambda sederhana?

Saya masih dapat melakukan hal seperti ini, tetapi tampaknya sangat tidak efisien setiap kali saya perlu mengakses elemen:

weights[(value_per_unit_sorted.index(max(value_per_unit_sorted)))]

person Yogesh    schedule 19.07.2017    source sumber


Jawaban (4)


Dalam satu baris:

values, weights = zip(*sorted(zip(values, weights), key=lambda t: t[0]/t[1]))

Untuk menjelaskan: Pertama, zip daftar untuk memasangkannya.

pairs = zip(values, weights) 
# [(60, 20), (100, 50), (120, 30)]

Kemudian, urutkan berdasarkan hasil bagi nilai terhadap bobot.

sorted_pairs = sorted(pairs, key=lambda t: t[0]/t[1]) 
# [(100, 50), (60, 20), (120, 30)]

Terakhir, ekstrak kembali ke dalam daftar terpisah.

values, weights = zip(*sorted_pairs)
# (100, 60, 120), (50, 20, 30)

Alternatifnya adalah dengan membuat tupel yang secara eksplisit memuat rasio sebagai elemen pertama.

ratios, values, weights = zip(*sorted((v/w, v, w) for v, w in zip(values, weights)))

Yang pertama tampaknya sedikit lebih cepat dalam beberapa pengujian cepat. Jika Anda mencari algoritme yang optimal, Anda mungkin harus membuka gulungannya dan solusinya tidak akan sesingkat itu.

Dan untuk menjawab komentar dari @TomWyllie, jika Anda sudah memiliki daftar rasionya, Anda dapat menggunakan:

ratios, values, weights = zip(*sorted(zip(ratios, values, weights)))

Perhatikan bahwa dua solusi terakhir ini berbeda dari solusi awal jika dua pasangan memiliki rasio yang sama. Solusi ini akan mengurutkan kedua berdasarkan nilai, sedangkan solusi pertama akan menjaga item dalam urutan yang sama seperti daftar aslinya.

person Jared Goguen    schedule 19.07.2017
comment
Masalah yang cukup kecil, tetapi Anda menghitung ulang semua rasio dengan solusi ini, OP telah menyorotnya dengan huruf tebal sekarang karena harus diurutkan menurut array value_per_unit, yang saya cukup yakin akan digunakan array (daftar) dan tidak menghitung ulang nilainya. Jawaban yang bagus :) - person Tom Wyllie; 19.07.2017
comment
@Tom Kemudian solusi kedua dapat digunakan dan OP dapat melewati pembuatan daftar rasio terlebih dahulu. - person Jared Goguen; 19.07.2017
comment
Saya setuju itu mungkin masuk akal, tetapi OP tidak meminta untuk melewatkan langkah membuat rasio; sangat mungkin dia membutuhkan list itu untuk hal lain, dan menginginkan solusi yang menghindari perhitungan ulang. - person Tom Wyllie; 19.07.2017
comment
@TomWyllie Itu membuat segalanya lebih sederhana, saya telah menambahkan solusi pencarian untuk skenario itu. - person Jared Goguen; 19.07.2017

Cara elegan untuk melakukannya adalah dengan membuat daftar multidimensi dengan nilai dan bobot:

for i in range(len(values)):
    values_and_weights.append([values[i], weights[i])
# The resulting list is [[60, 20], [100, 50], [120, 30]]

Kemudian, gunakan metode pengurutan dengan nilai dibagi bobot sebagai kuncinya.

values_and_weights.sort(key=(lambda x: x[0]/x[1]))
person jmcampbell    schedule 19.07.2017
comment
Bagian pertama dapat disederhanakan dengan menggunakan zip dan kemudian direduksi menjadi saran saya yang kedua. - person Jared Goguen; 19.07.2017
comment
@JaredGoguen Para pemikir hebat berpikiran sama! Alasan saya melakukannya dengan cara ini adalah agar lebih eksplisit dan lebih jelas apa yang terjadi. - person jmcampbell; 19.07.2017
comment
Saya berpendapat bahwa menggunakan fungsi bawaan zip lebih Pythonic, tetapi masing-masing memiliki fungsi tersendiri. - person Jared Goguen; 19.07.2017

Untuk solusi yang lebih eksplisit (tetapi bisa dibilang tidak terlalu pythonic), buatlah daftar indeks, diurutkan berdasarkan nilai pada indeks tersebut di value_per_unit, dan susun ulang values dan weights sesuai dengan itu.

sorted_indices = [index for index, value in 
                  sorted(enumerate(value_per_unit), key=lambda x: x[1])]
values = [values[i] for i in sorted_indices]
weights = [weights[i] for i in sorted_indices]

print(values, weights)

Keluaran:

([100, 60, 120], [50, 20, 30])

Anda dapat merapikannya, menghilangkan loop tambahan yang tidak perlu menggunakan zip, dan dengan ekspresi generator;

values, weights = zip(*((values[i], weights[i]) for i, value in
                  sorted(enumerate(value_per_unit), key=lambda x: x[1])))
print(values)
print(weights)

Keluaran yang mana;

(100, 60, 120)
(50, 20, 30)

Perhatikan bahwa nilai akhir ini adalah tuples bukan lists. Jika Anda benar-benar membutuhkan keluaran berupa daftar, values, weights = map(list, (values, weights)) sederhana sudah cukup. Anda bahkan dapat membungkusnya menjadi satu kesatuan, meskipun pada saat itu mungkin akan sangat sulit untuk mengikuti apa yang terjadi.

person Tom Wyllie    schedule 19.07.2017

Masalah yang Anda alami disebabkan oleh penggunaan bidang terhitung pada setiap elemen (elemen saya akan memiliki nilai terhitung values[I]/weights[I]). Untuk mengatasi hal ini dan tetap membuatnya sangat mudah dipahami, Anda dapat mengubahnya menjadi tupel dalam bentuk ini: ( calculated_value, (value, weight) ), per elemen.

Pendekatan ini membuatnya mudah dibaca dan dipahami. Lihatlah solusi berikut:

values = [60, 100, 120]
weights = [20, 50, 30]
value_per_unit = []

for I in range(len(values)):
    value_per_unit.append( (values[I]/weights[I], (values[I], weights[I])) )
sorted_value_per_unit = sorted(value_per_unit, key=lambda x: x[0])

sorted_values = []
sorted_weights = []
for I in range(len(values)):
    (value, weight) = sorted_value_per_unit[I][1]
    sorted_values.append(value)
    sorted_weights.append(weight)

print(str(sorted_values))
print(str(sorted_weights))

Perhatikan juga bahwa saya memodifikasi loop dari kode asli Anda:

range(values) diubah menjadi range(len(values))

Karena rentang memerlukan panjang daftar, bukan daftar itu sendiri.

person Ori    schedule 19.07.2017