จัดเรียง 2 รายการใน Python ตามอัตราส่วนขององค์ประกอบที่เกี่ยวข้องแต่ละรายการหรือตามรายการที่สาม

ฉันกำลังพยายามเขียนการใช้งานที่แตกต่างกันสำหรับปัญหากระเป๋าเป้สะพายหลังที่เป็นเศษส่วน

สำหรับสิ่งนี้ฉันมี 2 อาร์เรย์:

  1. ค่านิยม
  2. ตุ้มน้ำหนัก

ค่าองค์ประกอบ[n] สอดคล้องกับน้ำหนักองค์ประกอบ[n] ดังนั้นเราจึงสามารถคำนวณ value_per_unit ได้ดังนี้:

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

ตอนนี้ฉันต้องการให้อาร์เรย์ 2 ตัว (ค่าและน้ำหนัก) เรียงลำดับตามอาร์เรย์ value_per_unit

eg: If

  • ค่า = [60, 100, 120]
  • น้ำหนัก = [20, 50, 30]

แล้ว

  • value_per_unit = [3.0, 2.0, 4.0]

  • และค่าต่างๆ_per_unit_sorted จะเป็น [2.0, 3.0, 4.0]

ฉันต้องการอาร์เรย์ค่าและน้ำหนักเพื่อให้เป็น:

  • ค่า_เรียงลำดับ = [100,60,120]
  • น้ำหนัก_เรียงลำดับ = [50,20,30]

มีวิธีใดที่จะบรรลุเป้าหมายนี้โดยใช้ฟังก์ชันแลมบ์ดาแบบง่าย ๆ หรือไม่?

ฉันยังสามารถทำอะไรแบบนี้ได้ แต่ดูเหมือนว่าจะไม่มีประสิทธิภาพอย่างมากทุกครั้งที่ต้องเข้าถึงองค์ประกอบต่างๆ:

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

person Yogesh    schedule 19.07.2017    source แหล่งที่มา


คำตอบ (4)


ในหนึ่งบรรทัด:

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

เพื่ออธิบาย: ขั้นแรก ให้บีบอัดรายการเพื่อจับคู่

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

จากนั้นจัดเรียงตามผลหารของค่าต่อน้ำหนัก

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

สุดท้ายให้คลายซิปกลับเข้าไปในรายการแยกกัน

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

อีกทางเลือกหนึ่งคือการสร้างสิ่งอันดับที่มีอัตราส่วนเป็นองค์ประกอบแรกอย่างชัดเจน

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

แบบแรกดูเหมือนจะเร็วกว่าเล็กน้อยในการทดสอบอย่างรวดเร็ว หากคุณกำลังมองหาอัลกอริธึมที่เหมาะสมที่สุด คุณอาจจะต้องคลี่คลายสิ่งต่าง ๆ และวิธีแก้ปัญหาจะไม่กระชับ

และเพื่อตอบความคิดเห็นจาก @TomWyllie หากคุณมีรายการอัตราส่วนอยู่แล้ว คุณสามารถใช้:

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

โปรดทราบว่าคำตอบสองข้อสุดท้ายนี้แตกต่างจากวิธีแก้ปัญหาเริ่มต้นในกรณีที่สองคู่มีอัตราส่วนเท่ากัน โซลูชันเหล่านี้จะเรียงลำดับตามค่าตามลำดับ ในขณะที่โซลูชันแรกจะเก็บรายการไว้ในลำดับเดียวกันกับรายการเดิม

person Jared Goguen    schedule 19.07.2017
comment
ค่อนข้างเป็นปัญหาเล็กน้อย แต่คุณกำลังคำนวณอัตราส่วนทั้งหมดใหม่ด้วยโซลูชันนี้ OP ได้เน้นด้วยตัวหนาในขณะนี้ว่าควรเรียงลำดับ ตามอาร์เรย์ value_per_unit ซึ่งฉันค่อนข้างแน่ใจว่าหมายถึงการใช้ อาร์เรย์ (รายการ) และไม่คำนวณค่าใหม่ คำตอบที่ดีแม้ว่า :) - person Tom Wyllie; 19.07.2017
comment
@Tom จากนั้นสามารถใช้โซลูชันที่สองได้และ OP สามารถข้ามการสร้างรายการอัตราส่วนได้ตั้งแต่แรก - person Jared Goguen; 19.07.2017
comment
ฉันยอมรับว่าอาจสมเหตุสมผล แต่ OP ไม่ได้ขอให้ข้ามขั้นตอนการสร้างอัตราส่วน เป็นไปได้ทั้งหมดที่เขาอาจต้องการสิ่งนั้น list สำหรับอย่างอื่นอยู่แล้ว และต้องการวิธีแก้ปัญหาที่หลีกเลี่ยงการคำนวณใหม่ - person Tom Wyllie; 19.07.2017
comment
@TomWyllie นั่นทำให้สิ่งต่าง ๆ ง่ายขึ้นฉันได้เพิ่มโซลูชันการค้นหาสำหรับสถานการณ์นั้น - person Jared Goguen; 19.07.2017

วิธีที่ยอดเยี่ยมในการทำเช่นนี้คือการสร้างรายการหลายมิติที่มีค่าและน้ำหนัก:

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

จากนั้นใช้วิธีเรียงลำดับโดยมีค่าหารด้วยน้ำหนักเป็นคีย์

values_and_weights.sort(key=(lambda x: x[0]/x[1]))
person jmcampbell    schedule 19.07.2017
comment
ส่วนแรกสามารถทำให้ง่ายขึ้นได้โดยใช้ zip แล้วลดขนาดเป็นคำแนะนำที่สองของฉัน - person Jared Goguen; 19.07.2017
comment
@JaredGoguen จิตใจที่ดีก็คิดเหมือนกัน! เหตุผลของฉันในการทำเช่นนี้ก็คือ ชัดเจนยิ่งขึ้น และชัดเจนยิ่งขึ้นว่าเกิดอะไรขึ้น - person jmcampbell; 19.07.2017
comment
ฉันขอยืนยันว่าการใช้ฟังก์ชันในตัว zip นั้นเป็น Pythonic มากกว่า แต่เป็นของแต่ละคนเอง - person Jared Goguen; 19.07.2017

สำหรับวิธีแก้ปัญหาที่ชัดเจนยิ่งขึ้น (แต่อาจมี pythonic น้อยกว่า) ให้สร้างรายการดัชนี จัดเรียงตามค่าที่ดัชนีนั้นใน value_per_unit และเรียงลำดับ values และ weights ใหม่ตามลำดับ

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)

เอาท์พุต:

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

คุณสามารถจัดระเบียบสิ่งนี้ได้ โดยกำจัดการวนซ้ำเพิ่มเติมที่ไม่จำเป็นโดยใช้ zip และด้วยนิพจน์ตัวสร้าง

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)

ซึ่งเอาท์พุต;

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

โปรดทราบว่าค่าสุดท้ายเหล่านี้คือ tuples ไม่ใช่ lists หากคุณต้องการให้ผลลัพธ์เป็นรายการจริงๆ การใช้ values, weights = map(list, (values, weights)) แบบธรรมดาก็เพียงพอแล้ว คุณสามารถรวมเรื่องนั้นไว้ในบรรทัดเดียวได้ แม้ว่าเมื่อถึงจุดนั้น คงจะยากที่จะติดตามสิ่งที่เกิดขึ้น

person Tom Wyllie    schedule 19.07.2017

ปัญหาที่คุณพบเกิดจากการใช้ฟิลด์จากการคำนวณเหนือแต่ละองค์ประกอบ (องค์ประกอบ I จะมีค่าจากการคำนวณ values[I]/weights[I]) เพื่อแก้ปัญหานี้โดยยังคงรักษาให้เข้าใจง่ายมาก คุณสามารถเปลี่ยนให้เป็นทูเพิลของแบบฟอร์มนี้ได้: ( calculated_value, (value, weight) ) ต่ออิลิเมนต์

แนวทางนี้ช่วยให้อ่านและทำความเข้าใจได้ง่าย ดูวิธีแก้ปัญหาต่อไปนี้:

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

โปรดทราบว่าฉันได้แก้ไขการวนซ้ำจากโค้ดต้นฉบับของคุณ:

range(values) เปลี่ยนเป็น range(len(values))

เนื่องจาก range ต้องการความยาวของรายการ ไม่ใช่ตัวรายการเอง

person Ori    schedule 19.07.2017