Bir listeden rastgele bir öğeyi çıkarmanın en pitonik yolu nedir?


90

Diyelim xki, listenin daha sonra öğeyi içermemesi için bir öğeyi rasgele açmak istediğim, bilinmeyen uzunlukta bir listem var . Bunu yapmanın en pitonik yolu nedir?

Ben oldukça kullanışsız combincation kullanarak bunu yapabilirsiniz pop, random.randintve len, ve daha kısa ya da daha güzel çözümler görmek istiyorum:

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

Elde etmeye çalıştığım şey, bir listeden rastgele öğeleri art arda çıkarmak. (yani rastgele bir öğeyi açıp bir sözlüğe taşıyın, rastgele başka bir öğeyi açın ve başka bir sözlüğe taşıyın, ...)

Python 2.6 kullandığımı ve arama işlevi aracılığıyla herhangi bir çözüm bulamadığımı unutmayın.


3
Pek Pythonista değilim, ama bu bana oldukça iyi görünüyor.
Matt Ball

tarafımdan detaylı bir zaman karmaşıklığı analizi yapıldı, cevabımı yolun aşağısında bir yerde görün. SHUFFLE ETKİLİ DEĞİLDİR! ancak öğelerin sırasını bir şekilde değiştirmeniz gerekirse, yine de kullanabilirsiniz. pop (0) sizi ilgilendiriyorsa, analizimde bahsedilen dequeue'u kullanın.
nikhil swami

O (2) yazdığım cevap için zaman karmaşıklığı. hızlı kullanım için bir fonksiyona sarın. list.pop (-1) dışındaki herhangi bir list.pop (n) 'nin O (n) aldığını lütfen unutmayın.
nikhil swami

Yanıtlar:


95

Yaptığın gibi göründüğün şey ilk başta pek Pythonic görünmüyor. Listenin ortasından bir şeyler çıkarmamalısınız çünkü listeler, bildiğim tüm Python uygulamalarında diziler olarak uygulanır, bu yüzden bu bir O(n)işlemdir.

Bir algoritmanın parçası olarak bu işleve gerçekten ihtiyacınız varsa blist, ortadan etkili silmeyi destekleyen gibi bir veri yapısına göz atmalısınız .

Saf Python'da, kalan öğelere erişmeniz gerekmiyorsa yapabileceğiniz şey, önce listeyi karıştırıp ardından üzerinde yinelemektir:

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

Geri kalanına gerçekten ihtiyacınız varsa (bu biraz kod kokusudur, IMHO), en azından pop()listenin sonundan şimdi yapabilirsiniz (ki bu hızlıdır!):

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

Genel olarak, durumu değiştirmek yerine daha işlevsel bir stil kullanırsanız (listeyle yaptığınız gibi) programlarınızı daha zarif bir şekilde ifade edebilirsiniz.


3
Daha iyi (daha hızlı) bir fikir kullanmak olacaktır Yani random.shuffle(x)o zaman ve x.pop()? Bu "işlevsel" i nasıl yapacağımı anlamıyorum?
Henrik

1
@Henrik: İki koleksiyonunuz varsa (örneğin, bir sözlükler listesi ve rastgele sayılar listesi) ve bunları aynı anda yinelemek istiyorsanız zip, (dikte, sayı) çiftlerinin bir listesini almak için bunları yapabilirsiniz . Her birini rastgele bir sayıyla ilişkilendirmek istediğiniz birden çok sözlük hakkında bir şeyler söylediniz. zipbunun için mükemmel
Niklas B.

2
Eksik oy kullandığımda bir gönderi eklemem gerekiyor. Listenin ortasından bir öğeyi kaldırmanız gereken zamanlar vardır ... Bunu hemen şimdi yapmalıyım. Seçenek yok: Sıralı bir listem var, ortadaki bir öğeyi kaldırmam gerekiyor. Bu berbat, ancak diğer seçenek, yarı nadir bir işlem için ağır bir kod yeniden düzenleme yapmaktır. Sorun, bu tür işlemler için verimli olması GEREKEN, ancak etkili olmayan [] uygulamasından biridir.
Mark Gerolimatos

5
@NiklasB. OP, örnek olarak rastgele kullanıyordu (açıkçası, bırakılması gerekiyordu, sorunu bulanıklaştırdı). "Bunu yapma" yetersizdir. Daha iyi bir cevap, YETERLİ erişim hızı sağlarken bu tür işlemleri destekleyen bir Python veri yapısı önermek olabilirdi (açıkça arra ... er ... listesi kadar iyi değil). Python 2'de bir tane bulamadım. Yaparsam bununla cevap vereceğim. Bir tarayıcı kazası nedeniyle, bunu orijinal yorumuma ekleyemediğime, ikincil bir yorum eklemem gerektiğini unutmayın. Beni dürüst tuttuğunuz için teşekkür ederim :)
Mark Gerolimatos

1
@MarkGerolimatos Standart kitaplıkta hem verimli rasgele erişim hem de ekleme / silme ile veri yapısı yoktur. Muhtemelen pypi.python.org/pypi/blist gibi bir şey kullanmak istiyorsunuz , yine de birçok kullanım durumunda bunun önlenebileceğini iddia ediyorum
Niklas B.

51

Bundan daha iyi olmayacaksın, ama işte ufak bir gelişme:

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

Belgeler random.randrange():

random.randrange ([start], stop [, step]) içinden
rastgele seçilen bir elemanı döndür range(start, stop, step). Bu, eşdeğerdir choice(range(start, stop, step)), ancak aslında bir aralık nesnesi oluşturmaz.


14

Geri kalan liste öğelerinin sıralaması önemli değilse, bir listeden rastgele dizindeki tek bir öğeyi kaldırmak için :

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)

Değişim, bir listenin ortasından silinirken O (n) davranışını önlemek için kullanılır.


9

Burada başka bir alternatiftir: niye listeyi karıştırmak yok ilk artık elemanları kalır kadar bu unsurları çıkmaya başladığını, sonra ve? bunun gibi:

import random

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

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

3
@NiklasB. çünkü listeden öğeleri kaldırıyoruz. Öğeleri kaldırmak kesinlikle gerekli değilse, evet size katılıyorum:[for p in x]
Óscar López

Çünkü listeyi değiştirir ve öğelerin yarısını şimdi ve diğer yarısını daha sonra seçmek isterseniz, kalan seti daha sonra alacaksınız.
Henrik

@Henrik: Tamam, bu yüzden kalan listeye ihtiyacın olup olmadığını sordum. Cevap vermedin.
Niklas B.

2

Bunu yapmanın bir yolu şudur:

x.remove(random.choice(x))

7
Öğeler bir kereden fazla ortaya çıkarsa bu sorunlu hale gelebilir.
Niklas B.

2
Bu, kopyalar olduğunda en soldaki öğeyi kaldıracak ve tamamen rastgele olmayan bir sonuca neden olacaktır.
FogleBird

İle popkaldırılan öğeye bir ad gösterebilirsiniz, bununla yapamazsınız.
agf

Yeterince adil, unsurlar bir kereden fazla ortaya çıktığında bunun çok rastgele olmadığına katılıyorum.
Simeon Visser

1
Dağıtımınızı çarpıtma sorununun yanı sıra remove, listenin doğrusal bir taramasını gerektirir. Bir dizine bakmaya kıyasla bu çok verimsiz.
aaronasterling

2

Listeden çıkmadığım halde, bu soruyla Google'da yinelenmemiş bir listeden rastgele X öğe almaya çalışırken karşılaştım. İşte sonunda kullandığım şey:

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

Listenin tamamını karıştırdığınız için bu biraz verimsiz olabilir, ancak sadece küçük bir kısmını kullanırsınız, ancak optimizasyon uzmanı değilim, bu yüzden yanılıyor olabilirim.


3
random.sample(items, items_needed)
jfs

2

Bunun eski bir soru olduğunu biliyorum, ama sadece dokümantasyon aşkına:

Siz (aynı soruyu soran kişi) yaptığınızı düşündüğüm şeyi yapıyorsanız, bu da bir listeden rastgele k sayıda öğe seçmektir (burada k <= len (listeniz)), ancak her bir öğenin daha fazla seçilmediğinden emin olmaktır. birden fazla kez (= değiştirmeden örnekleme) @ jf-sebastian'ın önerdiği gibi random.sample kullanabilirsiniz . Ancak kullanım durumu hakkında daha fazla bilgi sahibi olmadan, ihtiyacınız olanın bu olup olmadığını bilmiyorum.


2

kullanımı öneren birçok yanıta rağmen random.shuffle(x)ve x.pop()büyük verilerde çok yavaş. ve shuffle etkinleştirildiğinde geçen 10000öğeler listesi için gereken süre 6 seconds. shuffle devre dışı bırakıldığında hız0.2s

Yukarıda verilen tüm yöntemleri test ettikten sonra en hızlı yöntem @jfs tarafından yazılmıştır.

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)

iddiamı desteklemek için bu kaynaktan alınan zaman karmaşıklığı çizelgesi görüntü açıklamasını buraya girin


Listede kopya yoksa,

setler kullanarak da amacınıza ulaşabilirsiniz. Küme kopyalarına yapılan liste bir kez kaldırılacaktır. remove by valueve remove randommaliyet O(1), yani çok verimli. bu bulabileceğim en temiz yöntem.

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.

listsHangi destek A+Bseçeneğinin aksine , ve ile birlikte setsdestekleyin . veriler üzerinde mantıksal işlemler gerçekleştirmek istediğinizde çok kullanışlıdır.A-B (A minus B)A+B (A union B)A.intersection(B,C,D)


İSTEĞE BAĞLI

Listenin başında ve sonunda yapılan işlemler sırasında hız istiyorsanız, benim iddiamı desteklemek için python dequeue (çift uçlu kuyruk) kullanın. bir görüntü bin kelimedir.

görüntü açıklamasını buraya girin


1

Bu cevap @ niklas-b izniyle geliyor :

" Muhtemelen pypi.python.org/pypi/blist gibi bir şey kullanmak istersiniz "

PYPI sayfasından alıntı yapmak için :

... daha iyi asimptotik performansa ve küçük listelerde benzer performansa sahip liste benzeri bir tür

Blist, büyük listeleri değiştirirken daha iyi performans sağlayan Python listesinin yerine geçer. Blist paketi ayrıca sıralı liste, sıralı küme, zayıf sıralı liste, zayıf sıralı küme, sıralı dikt ve btuple türleri sağlar.

Rastgele erişim / rastgele çalıştırma ucunda , "yazılırken kopya" veri yapısı olduğundan düşük performans varsayılabilir . Bu, Python listelerindeki birçok kullanım durumu varsayımını ihlal eder, bu yüzden dikkatli kullanın .

ANCAK, ana kullanım durumunuz bir liste ile tuhaf ve doğal olmayan bir şey yapmaksa (@OP tarafından verilen zorunlu örnekte olduğu gibi veya benim Python 2.6 FIFO geçişli kuyruğumda olduğu gibi), o zaman bu, faturaya güzel bir şekilde uyacaktır. .

Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.