次のように仮定します。
>>> s = set([1, 2, 3])
s
を行わずに値 (任意の値) を取得するにはどうすればよいs.pop()
ですか? アイテムを削除できると確信できるまで、アイテムをセットに残しておきたいと思います。これは、別のホストへの非同期呼び出しの後でのみ確認できます。
クイック&ダーティ:
>>> elem = s.pop()
>>> s.add(elem)
しかし、もっと良い方法を知っていますか?理想的には一定時間。
セット全体をコピーする必要のない 2 つのオプション:
for e in s:
break
# e is now an element from s
または...
e = next(iter(s))
ただし、一般に、セットはインデックス作成またはスライスをサポートしていません。
最小のコードは次のとおりです。
>>> s = set([1, 2, 3])
>>> list(s)[0]
1
明らかに、これはセットの各メンバーを含む新しいリストを作成するため、セットが非常に大きい場合はあまり役に立ちません.
関数がさまざまなセットでどのように機能するのか疑問に思ったので、ベンチマークを行いました。
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()
このプロットは、一部のアプローチ ( RandomSample
、SetUnpacking
およびListIndex
) がセットのサイズに依存し、一般的なケースでは避けるべきであることを明確に示しています (少なくともパフォーマンスが重要な場合)。他の回答ですでに示されているように、最速の方法はForLoop
.
ただし、一定時間アプローチのいずれかが使用されている限り、パフォーマンスの違いはごくわずかです。
iteration_utilities
(免責事項: 私は著者です) このユースケースのための便利な関数が含まれています: first
:
>>> from iteration_utilities import first
>>> first({1,2,3,4})
1
上記のベンチマークにも含めました。他の 2 つの「高速」ソリューションと競合する可能性がありますが、どちらの方法でも違いはあまりありません。
for first_item in muh_set: break
Python 3.x でも最適なアプローチです。のろいます、グイド。
wrから推定された Python 3.x タイミングのさらに別のセットへようこそ。の優れたPython 2.x 固有の応答. AChampionの同様に役立つPython 3.x 固有の応答とは異なり、以下のタイミングは、上記で提案されたタイム アウトライアー ソリューションでもあります。
list(s)[0]
、ジョンの新しいシーケンスベースのソリューション。random.sample(s, 1)
、dF。の折衷的なRNG ベースのソリューションです。電源を入れて、チューニングして、時間を計る:
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()
見よ!最速のスニペットから最も遅いスニペットへの順序:
$ ./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
当然のことながら、手動の反復は、2 番目に高速なソリューションよりも少なくとも 2 倍高速です。ギャップは Bad Old Python 2.x の時代 (手作業の反復が少なくとも 4 倍速かった) からは減少しましたが、最も冗長なソリューションが最善であるという私のPEP 20熱狂者を失望させています。少なくとも、セットの最初の要素を抽出するためだけにセットをリストに変換することは、予想どおり恐ろしいことです。グイドに感謝します。彼の光が私たちを導き続けますように。
驚くべきことに、RNG ベースのソリューションはまったくひどいものです。リストの変換は良くありませんが、random
本当に大変です。乱数の神については以上です。
アモルファスがset.get_first()
すでに私たちのために方法を準備してくれることを願っています。あなたがこれを読んでいるなら、彼らは:「お願いします。何かしてください。」
さまざまなアプローチの背後にあるタイミング図を提供するために、次のコードを検討してください。 get() は、Python の setobject.c への私のカスタム追加であり、要素を削除せずにただの pop() です。
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()
出力は次のとおりです。
$ ./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
これは、for/breakソリューションが最も高速であることを意味します (カスタムの get() ソリューションよりも高速な場合もあります)。
ランダムな要素が必要なため、これも機能します。
>>> import random
>>> s = set([1,2,3])
>>> random.sample(s, 1)
[2]
ドキュメントには、のパフォーマンスについては言及されていないようですrandom.sample
。巨大なリストと巨大なセットを使った非常に簡単な経験的テストから、リストでは一定の時間であるように見えますが、セットではそうではありません。また、セットの反復はランダムではありません。順序は未定義ですが、予測可能です:
>>> list(set(range(10))) == range(10)
True
ランダム性が重要で、一定時間内に多数の要素 (大きなセット) が必要な場合は、random.sample
最初にリストを使用して変換します。
>>> lst = list(s) # once, O(len(s))?
...
>>> e = random.sample(lst, 1)[0] # constant time
私が書いたユーティリティ関数を使用します。その名前は、ランダムなアイテムまたはそのようなものである可能性があることを暗示しているため、誤解を招く可能性があります.
def anyitem(iterable):
try:
return iter(iterable).next()
except StopIteration:
return None
@wr をフォローしています。投稿すると、同様の結果が得られます(Python3.5の場合)
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()
出力:
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
ただし、基礎となるセットを変更すると (たとえば、 への呼び出し)、反復可能な例 ( 、)remove()
はうまくいきません。for
iter
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()
結果:
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
値をアンパックして要素にアクセスできます。
s = set([1, 2, 3])
v1, v2, v3 = s
print(v1,v2,v3)
#1 2 3
別のオプションは、気にしない値で辞書を使用することです。例えば、
poor_man_set = {}
poor_man_set[1] = None
poor_man_set[2] = None
poor_man_set[3] = None
...
キーが単なる配列であることを除いて、キーをセットとして扱うことができます。
keys = poor_man_set.keys()
print "Some key = %s" % keys[0]
この選択の副作用は、コードが以前set
のバージョンの Python と下位互換性を持つことです。最善の答えではないかもしれませんが、別の選択肢です。
編集:配列またはセットの代わりにdictを使用したという事実を隠すために、次のようなことを行うこともできます:
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()