Bir öğeyi kaldırmadan bir gruptan nasıl alınır?


427

Varsayalım:

>>> s = set([1, 2, 3])

Yapmadan bir değeri (herhangi bir değeri) nasıl elde sedebilirim s.pop()? Kaldırabileceğimden emin olana kadar öğeyi sette bırakmak istiyorum - ancak başka bir ana bilgisayara eşzamansız bir çağrıdan sonra emin olabileceğim bir şey.

Hızlı ve kirli:

>>> elem = s.pop()
>>> s.add(elem)

Ama daha iyi bir yol biliyor musun? İdeal olarak sabit zamanda.


8
Python'un neden zaten bu işlevi yerine getirmediğini bilen var mı?
hlin117

Kullanım durumu nedir? Set'in böyle bir nedeni yok. Onu yinelemeniz ve unionondan eleman almamak vb. Gibi ilgili işlemler yapmanız gerekiyordu. Örneğin, next(iter({3,2,1}))her zaman geri döner 1, bu rastgele öğe döndüreceğini düşündüyseniz - dönmeyecektir. Belki de sadece yanlış veri yapısını kullanıyorsunuz? Kullanım durumu nedir?
user1685095

1
İlgili: stackoverflow.com/questions/20625579/… (Biliyorum, aynı soru değil, ama orada değerli alternatifler ve anlayışlar var.)
John Y

@ hlin117 Çünkü set sıralanmamış bir koleksiyon . Herhangi bir düzen beklenmediğinden, belirli bir konumda bir elemanın alınması mantıklı değildir - rastgele olması beklenir.
Jeyekomon

Yanıtlar:


545

Tüm seti kopyalamayı gerektirmeyen iki seçenek:

for e in s:
    break
# e is now an element from s

Veya...

e = next(iter(s))

Ancak genel olarak, kümeler dizin oluşturmayı veya dilimlemeyi desteklemez.


4
Bu sorumu cevaplıyor. Ne yazık ki, yineleme öğeleri sıraladığı için pop () kullanacağımı tahmin ediyorum. Onları rastgele sırayla tercih ederim ...
Daren Thomas

9
İter () öğelerinin sıralandığını düşünmüyorum - boş olana kadar bir set ve pop () oluşturduğumda, tutarlı (örnekte sıralı) sipariş alıyorum ve yineleyici ile aynı - pop ( ) "Ben hiçbir şey vaat etmiyorum" da olduğu gibi, rastgele keyfi, rastgele bir söz vaat etmiyor.
Blair Conrad

2
+1 iter(s).next()brüt değil harika. Tamamen genel herhangi bir nesneden keyfi eleman almak için genel. Eğer koleksiyon boş olsa dikkatli olmak istiyorsanız seçim.
u0b34a0f6ae

8
sonraki (iter (s)) da sorun yok ve daha iyi okuduğunu düşünüyorum. Ayrıca, s boş olduğunda davanın üstesinden gelmek için bir nöbetçi kullanabilirsiniz. Örneğin bir sonraki (iter (ler), set ()).
ja

5
next(iter(your_list or []), None)kümeleri ve boş kümeleri ele almak için
MrE

111

En az kod şöyle olurdu:

>>> s = set([1, 2, 3])
>>> list(s)[0]
1

Açıkçası bu, kümenin her bir üyesini içeren yeni bir liste oluşturacaktır, bu nedenle kümeniz çok büyükse harika değil.


96
next(iter(s))Sadece aşıyor list(s)[0]tarafından üç karakterden ve aksi takdirde zaman ve uzay karmaşıklığı hem de dramatik üstündür. Dolayısıyla, "en az kod" iddiası önemsiz olmakla birlikte, bunun mümkün olan en kötü yaklaşım olduğu da önemsizdir. Elle çıkarılan ve daha sonra çıkarılan öğeyi orijinal sete yeniden eklemek bile, "delice olarak ilk öğeyi çıkarmak için tamamen yeni bir kap oluşturmak" üstündür. Beni daha çok ilgilendiren şey, 38 Stackoverflowers'ın bunu gerçekten kaldırması. Bunu üretim kodunda göreceğimi biliyorum.
Cecil Curry

19
@augurar: Çünkü işi nispeten basit bir şekilde yapıyor. Ve bazen hızlı bir senaryoda önemli olan budur.
tonysdg

4
@Vicrobot Evet ama tüm koleksiyonu kopyalayıp bir O (1) işlemini O (n) işlemine dönüştürerek bunu yapar. Bu, hiç kimsenin kullanmaması gereken korkunç bir çözümdür.
augurar

9
Ayrıca, sadece "en az kod" (aptal) hedefliyorsanız min(s), bu kadar korkunç ve verimsiz kalırken daha az karakter kullanır.
augurar

5
Ben "korkunç ve verimsiz" min(s)olduğu için pratik bir karşı örnek var kod golf kazanan için +1: next(iter(s))boyut 1 setleri için biraz daha hızlıdır ve ben özellikle bu sete özel durumda setlerden sadece eleman çıkarma isteyen geldi büyüklüğü 1'in
lehiester

49

İşlevlerin farklı setler için nasıl performans göstereceğini merak ettim, bu yüzden bir kıyaslama yaptım:

from random import sample

def ForLoop(s):
    for e in s:
        break
    return e

def IterNext(s):
    return next(iter(s))

def ListIndex(s):
    return list(s)[0]

def PopAdd(s):
    e = s.pop()
    s.add(e)
    return e

def RandomSample(s):
    return sample(s, 1)

def SetUnpacking(s):
    e, *_ = s
    return e

from simple_benchmark import benchmark

b = benchmark([ForLoop, IterNext, ListIndex, PopAdd, RandomSample, SetUnpacking],
              {2**i: set(range(2**i)) for i in range(1, 20)},
              argument_name='set size',
              function_aliases={first: 'First'})

b.plot()

resim açıklamasını buraya girin

Bu arsa açıkça bazı yaklaşımlar olduğunu gösterir ( RandomSample, SetUnpackingve ListIndex) setin büyüklüğüne bağlıdır ve (en azından performans eğer genel durumda kaçınılmalıdır olabilir önemli). Zaten diğer cevaplar tarafından gösterildiği gibi en hızlı yoludur ForLoop.

Ancak, sabit zaman yaklaşımlarından biri kullanıldığı sürece performans farkı göz ardı edilebilir.


iteration_utilities(Feragatname: Yazarım) bu kullanım durumu için bir kolaylık işlevi içerir first:

>>> from iteration_utilities import first
>>> first({1,2,3,4})
1

Ben de yukarıdaki karşılaştırmaya dahil ettim. Diğer iki "hızlı" çözümle rekabet edebilir, ancak fark her iki şekilde de fazla değildir.


43

tl; Dr.

for first_item in muh_set: breakPython 3.x'te optimum yaklaşım olmaya devam ediyor. Lanet olsun, Guido.

bunu yap

Wr'den çıkarılan başka bir Python 3.x zamanlama setine hoş geldiniz . 'ın mükemmel Python 2.x'e özel yanıtı . Aksine AChampion 'eşit yardımcı s Python 3.x özgü tepki , zamanlama aşağıda ayrıca yukarıda önerilen çözümlerin uç değer süresi dahil olmak üzere -:

Büyük Sevinç için Kod Parçacıkları

Açın, ayarlayın, zamanlayın:

from timeit import Timer

stats = [
    "for i in range(1000): \n\tfor x in s: \n\t\tbreak",
    "for i in range(1000): next(iter(s))",
    "for i in range(1000): s.add(s.pop())",
    "for i in range(1000): list(s)[0]",
    "for i in range(1000): random.sample(s, 1)",
]

for stat in stats:
    t = Timer(stat, setup="import random\ns=set(range(100))")
    try:
        print("Time for %s:\t %f"%(stat, t.timeit(number=1000)))
    except:
        t.print_exc()

Hızla Eskimiş Zamansız Zamanlamalar

Seyretmek! En hızlıdan en yavaş snippet'lere göre sıralanır:

$ ./test_get.py
Time for for i in range(1000): 
    for x in s: 
        break:   0.249871
Time for for i in range(1000): next(iter(s)):    0.526266
Time for for i in range(1000): s.add(s.pop()):   0.658832
Time for for i in range(1000): list(s)[0]:   4.117106
Time for for i in range(1000): random.sample(s, 1):  21.851104

Tüm Aile İçin Faceplantlar

Şaşırtıcı olmayan bir şekilde, manuel yineleme bir sonraki en hızlı çözümün en az iki katı kadar hızlı kalır . Boşluk Bad Old Python 2.x günlerinden (manuel yinelemenin en az dört kat daha hızlı olduğu) azalmasına rağmen, içimdeki PEP 20 zealotunu en ayrıntılı çözümün en iyi olduğu konusunda hayal kırıklığına uğrattı . En azından bir seti sadece setin ilk elemanını çıkarmak için bir listeye dönüştürmek beklendiği kadar korkunçtur. Teşekkürler Guido, ışığı bize rehberlik etmeye devam etsin.

Şaşırtıcı bir şekilde, RNG tabanlı çözüm kesinlikle korkunç. Liste dönüşüm kötü, ama random gerçekten korkunç soslu kek alır. Rastgele Sayı Tanrı için çok fazla .

Ben sadece amorf diliyorum set.get_first()Bizim için zaten bir yöntem PEP olurdu . Bunu okuyorsanız, Onlar: "Lütfen. Bir şeyler yapın."


2
Bunu şikayet düşünüyorum next(iter(s)) daha yavaş iki katı olduğu for x in s: breakiçinde CPythongarip türüdür. Yani öyle CPython. Aynı şeyi yapan C veya Haskell'den yaklaşık 50-100 kez (veya bunun gibi bir şey) daha yavaş olacaktır (çoğu zaman, özellikle yinelemede, kuyruk çağrısı eliminasyonu ve hiçbir optimizasyon yoktur). Bazı mikrosaniyeleri kaybetmek gerçek bir fark yaratmaz. Düşünmüyor musun? Ve ayrıca PyPy var
user1685095

39

Farklı yaklaşımların arkasında bazı zamanlama rakamları sağlamak için aşağıdaki kodu göz önünde bulundurun. Get (), öğeyi kaldırmadan sadece bir pop () olan Python'un setobject.c'sine yaptığım özel eklentidir.

from timeit import *

stats = ["for i in xrange(1000): iter(s).next()   ",
         "for i in xrange(1000): \n\tfor x in s: \n\t\tbreak",
         "for i in xrange(1000): s.add(s.pop())   ",
         "for i in xrange(1000): s.get()          "]

for stat in stats:
    t = Timer(stat, setup="s=set(range(100))")
    try:
        print "Time for %s:\t %f"%(stat, t.timeit(number=1000))
    except:
        t.print_exc()

Çıktı:

$ ./test_get.py
Time for for i in xrange(1000): iter(s).next()   :       0.433080
Time for for i in xrange(1000):
        for x in s:
                break:   0.148695
Time for for i in xrange(1000): s.add(s.pop())   :       0.317418
Time for for i in xrange(1000): s.get()          :       0.146673

Bu, for / break çözümünün en hızlı (bazen özel get () çözümünden daha hızlı) olduğu anlamına gelir .


Herkes iter (s) .next () 'in diğer olasılıklardan çok daha yavaş, s.add (s.pop ())' den bile daha yavaş olduğu hakkında bir fikri var mı? Benim için zamanlamalar böyle görünüyorsa iter () ve next () çok kötü bir tasarım gibi geliyor.
peschü

Bir satır için her yineleme yeni bir iter nesnesi oluşturur.
Ryan

3
@Ryan: Bir yineleyici nesnesi for x in sde dolaylı olarak oluşturulmuş değil mi? "Sonuç için bir yineleyici oluşturulur expression_list."
musiphil

2
@musiphil Bu doğrudur; Başlangıçta 0.14 olan "mola" bir özledim, bu gerçekten karşı sezgisel. Zamanım olduğunda buna derin bir dalış yapmak istiyorum.
Ryan

1
Bu eski olduğunu biliyorum ama eklerken s.remove()içine karıştırmak iterörnekler hem forve iterkatastrofik bir aksilik olabilir.
AChampion

28

Rastgele bir öğe istediğiniz için, bu da işe yarayacaktır:

>>> import random
>>> s = set([1,2,3])
>>> random.sample(s, 1)
[2]

Belgeler performansından bahsetmiyor gibi görünüyor random.sample. Büyük bir liste ve büyük bir set ile gerçekten hızlı bir ampirik testten, bir liste için sabit bir zaman gibi görünüyor, ancak set için değil. Ayrıca, bir kümedeki yineleme rastgele değildir; sipariş tanımsız ancak öngörülebilir:

>>> list(set(range(10))) == range(10)
True 

Rasgelelik önemliyse ve sabit zamanda (büyük setler) bir grup öğeye ihtiyacınız varsa, önce random.samplebir listeye dönüştürürüm:

>>> lst = list(s) # once, O(len(s))?
...
>>> e = random.sample(lst, 1)[0] # constant time

14
Sadece bir eleman istiyorsanız, random.choice daha mantıklı.
Gregg Lind

Hangi öğeyi alacağınızı umursamıyorsanız list (s) .pop () işlemi yapılacaktır.
Evgeny

8
@Gregg: Kullanamazsınız choice(), çünkü Python setinizi dizine eklemeye çalışır ve bu çalışmaz.
Kevin

3
Akıllı olsa da, bu aslında bir büyüklük sırası tarafından önerilen en yavaş çözümdür. Evet, o kadar yavaş. Hatta bu listenin ilk elemanını çıkarmak için seti bir listeye dönüştürmek bile daha hızlıdır. Aramızdaki inanmayanlar için ( ... merhaba! ), Bu muhteşem zamanlamalara bakın .
Cecil Curry

9

Görünüşte en kompakt (6 sembol) bir set elemanı elde etmek için çok yavaş bir yol olsa da ( PEP 3132 ile mümkün kılındı ):

e,*_=s

Python 3.5+ ile bu 7 sembollü ifadeyi de kullanabilirsiniz ( PEP 448 sayesinde ):

[*s][0]

Her iki seçenek de makinemde for-loop yönteminden yaklaşık 1000 kat daha yavaş.


1
For loop yöntemi (veya daha doğru bir şekilde yineleyici yöntemi) O (1) zaman karmaşıklığına sahipken, bu yöntemler O (N) 'dir. Bunlar özlü olsa. :)
ForeverWintr

6

Yazdığım bir yardımcı program işlevi kullanıyorum. Adı biraz yanıltıcıdır çünkü rastgele bir öğe veya bunun gibi bir şey olabileceğini ima eder.

def anyitem(iterable):
    try:
        return iter(iterable).next()
    except StopIteration:
        return None

2
Ayrıca mürekkep tasarrufu için bir sonraki (iter (iterable), None) ile gidebilirsiniz :)
1 ''

3

@Wr. sonrası, benzer sonuçlar alıyorum (Python3.5 için)

from timeit import *

stats = ["for i in range(1000): next(iter(s))",
         "for i in range(1000): \n\tfor x in s: \n\t\tbreak",
         "for i in range(1000): s.add(s.pop())"]

for stat in stats:
    t = Timer(stat, setup="s=set(range(100000))")
    try:
        print("Time for %s:\t %f"%(stat, t.timeit(number=1000)))
    except:
        t.print_exc()

Çıktı:

Time for for i in range(1000): next(iter(s)):    0.205888
Time for for i in range(1000): 
    for x in s: 
        break:                                   0.083397
Time for for i in range(1000): s.add(s.pop()):   0.226570

Bununla birlikte, temel kümeyi değiştirirken (örn. Çağırma remove()), yinelenebilir örnekler ( for, iter) için işler kötü gider :

from timeit import *

stats = ["while s:\n\ta = next(iter(s))\n\ts.remove(a)",
         "while s:\n\tfor x in s: break\n\ts.remove(x)",
         "while s:\n\tx=s.pop()\n\ts.add(x)\n\ts.remove(x)"]

for stat in stats:
    t = Timer(stat, setup="s=set(range(100000))")
    try:
        print("Time for %s:\t %f"%(stat, t.timeit(number=1000)))
    except:
        t.print_exc()

Sonuçlar:

Time for while s:
    a = next(iter(s))
    s.remove(a):             2.938494
Time for while s:
    for x in s: break
    s.remove(x):             2.728367
Time for while s:
    x=s.pop()
    s.add(x)
    s.remove(x):             0.030272

1

Genellikle küçük koleksiyonlar için yaptığım şey, böyle bir ayrıştırıcı / dönüştürücü yöntemi oluşturmaktır

def convertSetToList(setName):
return list(setName)

Sonra yeni listeyi kullanabilir ve dizin numarasına göre erişebilirim

userFields = convertSetToList(user)
name = request.json[userFields[0]]

Liste olarak çalışmak için ihtiyaç duyabileceğiniz diğer tüm yöntemlere sahip olacaksınız.


neden sadece listbir dönüştürücü yöntemi oluşturmak yerine kullanmıyorsunuz ?
Daren Thomas

-1

Nasıl s.copy().pop()? Zamanlamadım, ama işe yaramalı ve basit. Bununla birlikte, tüm seti kopyaladığı için küçük setler için en iyi sonucu verir.


-6

Başka bir seçenek, umursadığınız değerlere sahip bir sözlük kullanmaktır. Örneğin,


poor_man_set = {}
poor_man_set[1] = None
poor_man_set[2] = None
poor_man_set[3] = None
...

Anahtarlara yalnızca bir dizi olmaları dışında bir küme olarak davranabilirsiniz:


keys = poor_man_set.keys()
print "Some key = %s" % keys[0]

Bu seçimin bir yan etkisi, kodunuzun eski setPython ön sürümleriyle geriye dönük olarak uyumlu olmasıdır . Belki de en iyi cevap değil ama başka bir seçenek.

Düzenleme: Hatta bir dizi veya küme yerine bir dikte kullandığınız gerçeğini gizlemek için böyle bir şey yapabilirsiniz:


poor_man_set = {}
poor_man_set[1] = None
poor_man_set[2] = None
poor_man_set[3] = None
poor_man_set = poor_man_set.keys()

3
Bu, umduğunuz gibi çalışmaz. Python 2 tuşlarında () bir O (n) işlemidir, bu yüzden artık sabit zamanınız yoktur, ancak en azından [0] tuşları beklediğiniz değeri döndürür. Python 3 tuşları () bir O (1) işlemleri, yani yay! Ancak, artık bir liste nesnesi döndürmez, dizine eklenemeyen set benzeri bir nesne döndürür, bu nedenle [0] tuşları TypeError atar. stackoverflow.com/questions/39219065/…
sage88
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.