อะไรคือวิธีที่ pythonic ที่สุดในการแสดงองค์ประกอบแบบสุ่มจากรายการ?


90

สมมติว่าฉันมีรายการที่xมีความยาวที่ไม่รู้จักซึ่งฉันต้องการสุ่มให้ปรากฏองค์ประกอบหนึ่งเพื่อไม่ให้รายการมีองค์ประกอบในภายหลัง วิธีที่ยิ่งใหญ่ที่สุดในการทำเช่นนี้คืออะไร?

ฉันสามารถทำมันใช้ combincation ค่อนข้างไม่สะดวกของpop, random.randintและlenและอยากจะเห็นการแก้ปัญหาที่สั้นกว่าหรือดีกว่า:

import random
x = [1,2,3,4,5,6]
x.pop(random.randint(0,len(x)-1))

สิ่งที่ฉันพยายามทำให้สำเร็จคือป๊อปองค์ประกอบแบบสุ่มจากรายการติดต่อกัน (เช่นป๊อปองค์ประกอบหนึ่งแบบสุ่มและย้ายไปยังพจนานุกรมป๊อปองค์ประกอบอื่นแบบสุ่มแล้วย้ายไปยังพจนานุกรมอื่น ... )

โปรดทราบว่าฉันใช้ Python 2.6 และไม่พบวิธีแก้ปัญหาใด ๆ ผ่านฟังก์ชันการค้นหา


3
ฉันไม่ใช่ Pythonista มากนัก แต่นั่นก็ดูดีสำหรับฉัน
Matt Ball

ฉันทำการวิเคราะห์ความซับซ้อนของเวลาโดยละเอียดดูคำตอบของฉันที่ไหนสักแห่งบนถนน SHUFFLE ไม่มีประสิทธิภาพ! แต่คุณยังสามารถใช้ได้หากคุณต้องการเปลี่ยนลำดับของรายการ หาก pop (0) เกี่ยวข้องกับคุณให้ใช้ dequeue ที่กล่าวถึงในการวิเคราะห์ของฉัน
nikhil swami

ความซับซ้อนของเวลา O (2) สำหรับคำตอบ ive เขียน ห่อด้วยฟังก์ชันเพื่อการใช้งานที่รวดเร็ว โปรดทราบว่ารายการใด ๆ pop (n) นอกเหนือจาก list.pop (-1) ใช้เวลา O (n)
nikhil swami

คำตอบ:


95

สิ่งที่คุณดูเหมือนจะไม่ได้ดู Pythonic มากนักในตอนแรก คุณไม่ควรลบสิ่งต่างๆออกจากกลางรายการเนื่องจากรายการถูกนำไปใช้เป็นอาร์เรย์ในการใช้งาน Python ทั้งหมดที่ฉันรู้จักดังนั้นนี่คือการO(n)ดำเนินการ

หากคุณต้องการฟังก์ชันนี้เป็นส่วนหนึ่งของอัลกอริทึมคุณควรตรวจสอบโครงสร้างข้อมูลเช่นเดียวกับblistที่รองรับการลบอย่างมีประสิทธิภาพจากตรงกลาง

ใน Python แท้สิ่งที่คุณสามารถทำได้หากคุณไม่ต้องการเข้าถึงองค์ประกอบที่เหลือเพียงแค่สลับรายการก่อนแล้วจึงทำซ้ำ:

lst = [1,2,3]
random.shuffle(lst)
for x in lst:
  # ...

หากคุณต้องการส่วนที่เหลือจริงๆ (ซึ่งเป็นกลิ่นรหัสเล็กน้อย IMHO) อย่างน้อยคุณก็สามารถทำได้pop()จากตอนท้ายของรายการตอนนี้ (ซึ่งเร็วมาก!):

while lst:
  x = lst.pop()
  # do something with the element      

โดยทั่วไปคุณมักจะแสดงออกถึงโปรแกรมของคุณได้อย่างหรูหรากว่าหากคุณใช้รูปแบบที่ใช้งานได้มากกว่าแทนที่จะใช้สถานะการกลายพันธุ์ (เช่นเดียวกับที่คุณทำกับรายการ)


3
ดังนั้นความคิดที่ดีกว่า (เร็วกว่า) จะใช้random.shuffle(x)แล้วx.pop()? ฉันไม่เข้าใจวิธีการทำ "ฟังก์ชัน" นี้?
Henrik

1
@Henrik: หากคุณมีสองคอลเล็กชัน (ตัวอย่างเช่นรายการพจนานุกรมและรายการตัวเลขสุ่ม) และคุณต้องการวนซ้ำพร้อมกันคุณสามารถzipรับรายการคู่ (dict, number) ได้ คุณพูดบางอย่างเกี่ยวกับพจนานุกรมหลายเล่มที่คุณต้องการเชื่อมโยงแต่ละพจนานุกรมด้วยตัวเลขสุ่ม zipเหมาะสำหรับสิ่งนี้
Niklas B.

2
ฉันควรจะเพิ่มโพสต์เมื่อฉันลงคะแนน มีหลายครั้งที่คุณต้องลบรายการออกจากกลางรายการ ... ฉันต้องทำตอนนี้ ไม่มีทางเลือก: ฉันมีรายการสั่งซื้อฉันต้องลบรายการที่อยู่ตรงกลาง มันแย่ แต่ทางเลือกเดียวคือทำการ refactoring รหัสหนักสำหรับการดำเนินการกึ่งหายากหนึ่งครั้ง ปัญหานี้เป็นหนึ่งในการดำเนินการตาม [] ซึ่งควรจะมีประสิทธิภาพสำหรับการดำเนินการดังกล่าว แต่ไม่ใช่
Mark Gerolimatos

5
@NiklasB. OP ใช้การสุ่มเป็นตัวอย่าง (ตรงไปตรงมาควรถูกทิ้งไว้ทำให้ปัญหาฟุ้ง) “ อย่าทำอย่างนั้น” นั้นไม่เพียงพอ คำตอบที่ดีกว่าคือการแนะนำโครงสร้างข้อมูล Python ที่รองรับการดำเนินการดังกล่าวในขณะที่ให้ความเร็วในการเข้าถึงที่เพียงพอ (เห็นได้ชัดว่าไม่ดีเท่า arra ... er ... list) ใน python 2 ฉันหาไม่เจอ ถ้าฉันทำฉันจะตอบว่า โปรดทราบว่าเนื่องจากเบราว์เซอร์ผิดพลาดฉันไม่สามารถเพิ่มสิ่งนั้นในความคิดเห็นเดิมของฉันได้ฉันควรเพิ่มความคิดเห็นรอง ขอบคุณที่ทำให้ฉันซื่อสัตย์ :)
Mark Gerolimatos

1
@MarkGerolimatos ไม่มีโครงสร้างข้อมูลที่มีทั้งการเข้าถึงแบบสุ่มที่มีประสิทธิภาพและการแทรก / ลบในไลบรารีมาตรฐาน คุณอาจต้องการใช้บางอย่างเช่นpypi.python.org/pypi/blistฉันยังคงเถียงว่าในกรณีการใช้งานจำนวนมากสามารถหลีกเลี่ยงได้
Niklas B.

51

คุณจะไม่ดีไปกว่านั้น แต่นี่คือการปรับปรุงเล็กน้อย:

x.pop(random.randrange(len(x)))

เอกสารประกอบเกี่ยวกับrandom.randrange():

random.randrange ([Start] หยุด [ขั้นตอน]) กลับมาเป็นองค์ประกอบที่สุ่มเลือกจาก
range(start, stop, step)สิ่งนี้เทียบเท่ากับchoice(range(start, stop, step))แต่ไม่ได้สร้างวัตถุช่วง


14

หากต้องการลบองค์ประกอบเดียวที่ดัชนีสุ่มออกจากรายการหากลำดับขององค์ประกอบรายการที่เหลือไม่สำคัญ:

import random

L = [1,2,3,4,5,6]
i = random.randrange(len(L)) # get random index
L[i], L[-1] = L[-1], L[i]    # swap with the last element
x = L.pop()                  # pop last element O(1)

การแลกเปลี่ยนใช้เพื่อหลีกเลี่ยงพฤติกรรม O (n) ในการลบจากกลางรายการ


9

นี่เป็นอีกทางเลือกหนึ่ง: ทำไมคุณไม่สลับรายการก่อนจากนั้นเริ่ม popping องค์ประกอบของมันจนกว่าจะไม่มีองค์ประกอบเหลืออยู่ แบบนี้:

import random

x = [1,2,3,4,5,6]
random.shuffle(x)

while x:
    p = x.pop()
    # do your stuff with p

3
@NiklasB. เนื่องจากเรากำลังลบองค์ประกอบออกจากรายการ หากไม่จำเป็นอย่างยิ่งที่จะต้องลบองค์ประกอบออกใช่ฉันเห็นด้วยกับคุณ:[for p in x]
ÓscarLópez

เนื่องจากจะเปลี่ยนรายการและหากคุณต้องการเลือกครึ่งหนึ่งขององค์ประกอบในตอนนี้และอีกครึ่งหนึ่งในภายหลังคุณจะมีชุดที่เหลือในภายหลัง
Henrik

@ เฮนริก: โอเคนั่นคือเหตุผลที่ฉันถามคุณว่าคุณต้องการรายชื่อที่เหลือหรือไม่ คุณไม่ได้ตอบว่า
Niklas B.

2

วิธีหนึ่งที่ทำได้คือ:

x.remove(random.choice(x))

7
สิ่งนี้อาจทำให้เกิดปัญหาได้หากองค์ประกอบเกิดขึ้นอีกครั้ง
Niklas B.

2
สิ่งนี้จะลบองค์ประกอบทางซ้ายสุดเมื่อมีรายการที่ซ้ำกันทำให้ไม่ได้ผลลัพธ์แบบสุ่มที่สมบูรณ์แบบ
FogleBird

ด้วยpopคุณสามารถชี้ชื่อที่องค์ประกอบที่นำออกไปด้วยนี้คุณไม่สามารถ
agf

พอใช้ฉันยอมรับว่านี่ไม่ใช่การสุ่มเมื่อองค์ประกอบเกิดขึ้นมากกว่าหนึ่งครั้ง
Simeon Visser

1
นอกเหนือจากคำถามเกี่ยวกับการกระจายของคุณแล้วคุณremoveต้องสแกนเชิงเส้นของรายการ นั่นไร้ประสิทธิภาพอย่างมากเมื่อเทียบกับการค้นหาดัชนี
aaronasterling

2

ในขณะที่ไม่โผล่ออกมาจากรายการฉันพบคำถามนี้ใน Google ขณะพยายามรับ X สุ่มรายการจากรายการโดยไม่ซ้ำกัน นี่คือสิ่งที่ฉันใช้ในที่สุด:

items = [1, 2, 3, 4, 5]
items_needed = 2
from random import shuffle
shuffle(items)
for item in items[:items_needed]:
    print(item)

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


3
random.sample(items, items_needed)
jfs

2

ฉันรู้ว่านี่เป็นคำถามเก่า แต่เพื่อประโยชน์ในการจัดทำเอกสาร:

หากคุณ (บุคคลที่ถามคำถามเดียวกัน) กำลังทำในสิ่งที่ฉันคิดว่าคุณกำลังทำอยู่ซึ่งเป็นการเลือก k จำนวนรายการแบบสุ่มจากรายการ (โดยที่ k <= len (รายการของคุณ)) แต่ตรวจสอบให้แน่ใจว่าแต่ละรายการไม่ได้ถูกเลือกเพิ่มเติม มากกว่าหนึ่งครั้ง (= การสุ่มตัวอย่างโดยไม่ต้องเปลี่ยน) คุณสามารถใช้random.sampleเช่น @ jf-sebastian แนะนำ แต่โดยไม่ทราบข้อมูลเพิ่มเติมเกี่ยวกับกรณีการใช้งานฉันไม่รู้ว่านี่คือสิ่งที่คุณต้องการหรือไม่


2

แม้จะมีคำตอบมากมายที่แนะนำการใช้งานrandom.shuffle(x)และx.pop()ข้อมูลขนาดใหญ่ที่ช้ามาก และเวลาที่ต้องใช้ในรายการ10000องค์ประกอบที่ใช้6 secondsเมื่อเปิดใช้งานการสุ่ม เมื่อปิดใช้งานการสับเปลี่ยนความเร็วคือ0.2s

วิธีที่เร็วที่สุดหลังจากทดสอบวิธีการทั้งหมดข้างต้นถูกเขียนโดย @jfs

import random

L = ['1',2,3,'4'...1000] #you can take mixed or pure list
i = random.randrange(len(L)) # get random index
L[i], L[-1] = L[-1], L[i]    # swap with the last element
x = L.pop()                  # pop last element O(1)

เพื่อสนับสนุนการอ้างสิทธิ์ของฉันนี่คือแผนภูมิความซับซ้อนของเวลาจากแหล่งข้อมูลนี้ ใส่คำอธิบายภาพที่นี่


หากไม่มีรายการที่ซ้ำกัน

คุณสามารถบรรลุวัตถุประสงค์ของคุณโดยใช้ชุดด้วย เมื่อสร้างรายการที่ซ้ำกันแล้วจะถูกลบออก remove by valueและremove randomค่าใช้จ่ายO(1)นั่นคือประสิทธิภาพมาก นี่เป็นวิธีที่สะอาดที่สุดที่ฉันคิดได้

L=set([1,2,3,4,5,6...]) #directly input the list to inbuilt function set()
while 1:
    r=L.pop()
    #do something with r , r is random element of initial list L.

ซึ่งแตกต่างจากlistsที่สนับสนุนA+Bตัวเลือกsetsนอกจากนี้ยังสนับสนุนA-B (A minus B)พร้อมด้วยและA+B (A union B) A.intersection(B,C,D)มีประโยชน์มากเมื่อคุณต้องการดำเนินการเชิงตรรกะกับข้อมูล


ไม่จำเป็น

หากคุณต้องการความเร็วเมื่อดำเนินการที่ส่วนหัวและส่วนท้ายของรายการให้ใช้ python dequeue (คิวสิ้นสุดคู่) เพื่อสนับสนุนการอ้างสิทธิ์ของฉันนี่คือรูปภาพ ภาพเป็นพันคำ

ใส่คำอธิบายภาพที่นี่


1

คำตอบนี้ได้รับความอนุเคราะห์จาก@ niklas-b :

" คุณอาจต้องการใช้บางอย่างเช่นpypi.python.org/pypi/blist "

หากต้องการอ้างอิงหน้า PYPI :

... ประเภทคล้ายรายการที่มีประสิทธิภาพที่ดีกว่าและประสิทธิภาพที่คล้ายกันในรายการขนาดเล็ก

Blist คือการแทนที่รายการ Python แบบดรอปอินที่ให้ประสิทธิภาพที่ดีขึ้นเมื่อแก้ไขรายการขนาดใหญ่ แพคเกจ blist ยังมีรายการ sortedlist, sortedset, อ่อนแอsortedlist, อ่อนแอsortedset, sorteddict และ btuple

หนึ่งจะถือว่าประสิทธิภาพลดลงในการสิ้นสุดการเข้าถึงแบบสุ่ม / การเรียกใช้แบบสุ่มเนื่องจากเป็นโครงสร้างข้อมูลแบบ "คัดลอกเมื่อเขียน" นี้ละเมิดสมมติฐานกรณีใช้จำนวนมากในรายการหลามเพื่อใช้ด้วยความระมัดระวัง

อย่างไรก็ตามหากกรณีการใช้งานหลักของคุณคือการทำสิ่งที่แปลกและไม่เป็นธรรมชาติกับรายการ (เช่นในตัวอย่างบังคับที่กำหนดโดย @OP หรือปัญหา Python 2.6 FIFO ที่มีการส่งผ่านคิว) สิ่งนี้จะพอดีกับการเรียกเก็บเงินอย่างดี .

โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.