요소를 제거하지 않고 세트에서 요소를 검색하는 방법은 무엇입니까?


427

다음을 가정하십시오.

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

내가 s하지 않고 어떻게 가치 (값)를 얻 s.pop()습니까? 항목을 제거 할 수있을 때까지 항목을 세트에 남겨두고 싶습니다. 다른 호스트에 대한 비동기 호출 후에 만 ​​확인할 수있는 것입니다.

빠르고 더러운 :

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

그러나 더 나은 방법을 알고 있습니까? 일정한 시간에 이상적입니다.


8
왜 파이썬 에이 기능이 아직 구현되어 있지 않은지 아는 사람이 있습니까?
hlin117

사용 사례는 무엇입니까? 세트에는이 기능이 없습니다. 당신은 그것을 반복하고 union요소를 가져 가지 않는 등 관련 작업을 설정 해야했습니다. 예를 들어 무작위 요소를 반환한다고 생각하면 next(iter({3,2,1}))항상 반환 1되므로 그렇지 않습니다. 아마도 잘못된 데이터 구조를 사용하고 있습니까? 사용 사례는 무엇입니까?
user1685095

1
관련 : stackoverflow.com/questions/20625579/... (. 나는 그것이 같은 질문이 아니에요, 알아,하지만 가치있는 대안과 통찰력이가)
존 Y

@ hlin117 set은 정렬되지 않은 컬렉션 이기 때문 입니다 . 순서가 예상되지 않으므로 주어진 위치에서 요소를 검색하는 것은 의미가 없습니다. 무작위 일 것으로 예상됩니다.
Jeyekomon

답변:


545

전체 세트를 복사하지 않아도되는 두 가지 옵션 :

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

또는...

e = next(iter(s))

그러나 일반적으로 세트는 인덱싱 또는 슬라이싱을 지원하지 않습니다.


4
이것은 내 질문에 대답합니다. 아아, 반복은 요소를 정렬하는 것처럼 보이기 때문에 여전히 pop ()을 사용한다고 생각합니다. 난 무작위 순서대로 그들을 선호합니다 ...
대런 토마스

9
iter ()가 요소를 정렬한다고 생각하지 않습니다. 빈이 될 때까지 set 및 pop ()을 만들 때 일관성있는 (예제에서 정렬 된) 순서가 있으며 반복자와 동일합니다-pop ( )는 "I promise nothing"에서와 같이 임의의 순서로 임의 순서를 약속하지 않습니다.
블레어 콘래드

2
+1 iter(s).next()은 거칠지 않고 훌륭합니다. 반복 가능한 객체에서 임의의 요소를 취하는 것이 일반적입니다. 컬렉션이 비어있는 경우 조심하고 싶다면 선택하십시오.
u0b34a0f6ae

8
next (iter (s))도 괜찮으며 더 잘 읽는다고 생각하는 경향이 있습니다. 또한 센티넬을 사용하여 s가 비어있는 경우를 처리 할 수 ​​있습니다. 예를 들어 다음 (iter (s), set ()).
ja

5
next(iter(your_list or []), None)세트와 빈 세트를 처리하는 방법
MrE

111

가장 작은 코드는 다음과 같습니다.

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

분명히 이것은 세트의 각 멤버를 포함하는 새 목록을 생성하므로 세트가 매우 큰 경우 좋지 않습니다.


96
next(iter(s))단 초과 list(s)[0]하여 세 개의 문자 와 달리 시간과 공간의 복잡성 모두에서 극적으로 우수하다. 따라서 "최소 코드"에 대한 주장은 사소한 사실이지만, 이것이 최악의 접근법이라는 것도 사소한 사실입니다. 제거 된 요소를 원래 세트로 수동으로 제거했다가 다시 추가하는 것조차도 "제 1 요소를 추출하기 위해 완전히 새로운 용기를 구성하는 것"보다 우수합니다. 더 중요한 것은 38 개의 Stackoverflowers가 실제로 이것을 찬성했습니다. 나는 이것을 프로덕션 코드에서 볼 것이라고 알고 있습니다.
Cecil Curry

19
@augurar : 작업이 비교적 간단한 방식으로 수행되기 때문입니다. 그리고 때로는 이것이 빠른 스크립트에서 중요한 전부입니다.
tonysdg

4
@Vicrobot 예. 그러나 전체 컬렉션을 복사하고 O (1) 연산을 O (n) 연산으로 바꾸면됩니다. 이것은 아무도 사용해서는 안되는 끔찍한 솔루션입니다.
augurar

9
또한 "최소 코드"(멍청한)를 목표로 min(s)하는 경우, 이만큼 끔찍하고 비효율적 인 문자를 더 적게 사용합니다.
augurar

5
"끔찍하고 비효율적"이라는 실제 사례를 가지고있는 코드 골프 우승자에 대해 +1 : 크기 1 min(s)보다 약간 빠르며 next(iter(s)),이 답변은 특별히 세트에서 유일한 요소를 추출하는 특수 사례를 찾고 있습니다.
lehiester

49

함수가 다른 세트에서 어떻게 수행되는지 궁금해하여 벤치 마크를 수행했습니다.

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, SetUnpackingListIndex)이 세트의 크기에 따라 다르며 일반적인 경우 (적어도 성능 중요 할 수 있는 경우) 피해야 함을 분명히 보여줍니다 . 이미 다른 답변에서 볼 수 있듯이 가장 빠른 방법은 ForLoop입니다.

그러나 일정한 시간 접근법 중 하나를 사용하는 한 성능 차이는 무시할 수 있습니다.


iteration_utilities(면책 조항 : 저자입니다)이 사용 사례에 대한 편의 기능이 포함되어 있습니다. first:

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

또한 위의 벤치 마크에 포함 시켰습니다. 다른 두 가지 "빠른"솔루션과 경쟁 할 수 있지만 그 차이는 그리 크지 않습니다.


43

tl; dr

for first_item in muh_set: breakPython 3.x에서 최적의 접근 방식으로 남아 있습니다. 저주, 귀도

너 이거 해

wr 에서 추정 한 또 다른 Python 3.x 타이밍 세트에 오신 것을 환영합니다 . 탁월한 Python 2.x 전용 응답 . AChampion 의 똑같이 유용한 Python 3.x 관련 응답 과는 달리 아래의 타이밍 은 위에서 제안한 이상치 해결책 포함합니다.

큰 기쁨을위한 코드 스 니펫

켜고 조정하고 시간을 정하십시오.

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 배 이상 빠릅니다 . 수동 반복이 4 배 이상 빠르 던 Bad Old Python 2.x 일과의 격차가 줄어들었지만 가장 장황한 솔루션이 최고라고 PEP 20 열성에 실망했습니다 . 집합의 첫 번째 요소를 추출하기 위해 집합을 목록으로 변환하는 것은 예상만큼 끔찍합니다. 귀도에게 감사합니다. 그의 빛이 우리를 계속 인도 해 주시길 바랍니다.

놀랍게도 RNG 기반 솔루션은 끔찍합니다. 목록 변환은 좋지 않지만 random 실제로 는 끔찍한 소스 케이크가 필요합니다. 난수 신을 위해 너무 많은 .

나는 단지 비정질을 원합니다. 그들은 set.get_first()이미 우리를 위해 방법을 PEP 할 것 입니다. 이 글을 읽고 있다면, "제발. 뭔가 해봐."


2
나는 그 불평 생각 next(iter(s)) 보다 느린 두 배 for x in s: breakCPython이상한의 종류이다. 내 말은 CPython. C 또는 Haskell이 동일한 작업을 수행하는 것보다 (대부분의 경우 특히 반복, 테일 콜 제거 및 최적화 없음) 약 50-100 배 (또는 이와 유사한 것)가 느려집니다. 약간의 마이크로 초를 잃어도 실제 차이는 없습니다. 당신은 생각하지 않습니까? 그리고 PyPy도 있습니다
user1685095

39

서로 다른 접근 방식의 일부 타이밍 수치를 제공하려면 다음 코드를 고려하십시오. 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 () 솔루션보다 빠름).


누구든지 iter (s) .next ()가 다른 가능성보다 너무 느리고 s.add (s.pop ())보다 느린 이유를 알고 있습니까? 나에게 그것은 타이밍이 그렇게 보인다면 iter ()와 next ()의 디자인이 매우 좋지 않다고 느낀다.
peschü

한 줄에 대해 각 반복마다 새로운 iter 객체를 만듭니다.
Ryan

3
@Ryan : iterator 객체가 암시 적으로 생성되지 않았 for x in s습니까? "의 결과를 위해 반복자가 생성됩니다 expression_list."
musiphil

2
@musiphil 사실입니다. 원래 저는 0.14 인 "break"를 놓쳤습니다. 그것은 실제로 반 직관적입니다. 시간이있을 때 이것에 대해 자세히 알아보고 싶습니다.
Ryan

1
나는 이것이 오래 알고 있지만 추가 할 때 s.remove()로하여 혼합 iter사례를 모두 foriter격변 나쁜 이동합니다.
AChampion

28

임의의 요소를 원하므로 다음과 같이 작동합니다.

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

14
하나의 요소 만 원하면 random.choice가 더 합리적입니다.
Gregg Lind

어떤 요소를 가져 가야할지 신경 쓰지 않으면 list (s) .pop ()이 수행합니다.
Evgeny

8
@Gregg : choice()Python 은 집합을 색인화하려고 시도 하지만 작동하지 않기 때문에 사용할 수 없습니다 .
Kevin

3
영리하지만, 이것은 실제로 가장 느린 솔루션이지만 아직 규모가 큰 것으로 제안되었습니다. 예, 그건 천천히. 해당 목록의 첫 번째 요소를 추출하기 위해 세트를 목록으로 변환하는 것조차 더 빠릅니다. 우리 사이의 불신자들을 위해 ( ... hi! ),이 멋진 타이밍을보십시오 .
Cecil Curry

9

설정 요소를 얻는 데 매우 느린 방법 이지만 가장 컴팩트 한 (6 개의 기호) 겉보기 ( PEP 3132 로 가능 ) :

e,*_=s

Python 3.5 이상에서는이 7- 심볼 표현식을 사용할 수 있습니다 ( PEP 448 덕분에 ).

[*s][0]

두 옵션 모두 for-loop 방법보다 약 1000 배 느립니다.


1
for 루프 방법 (또는보다 정확하게 반복자 방법)에는 O (1) 시간 복잡성이 있지만 이러한 방법은 O (N)입니다. 그들은 간결 합니다. :)
ForeverWintr

6

내가 작성한 유틸리티 기능을 사용합니다. 이름이 임의의 항목이거나 그와 비슷한 것을 암시하기 때문에 다소 오해의 소지가 있습니다.

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

2
당신은 또한 잉크 절약을 위해 next (iter (iterable), None)와 함께 갈 수 있습니다 :)
1 ''

3

팔로우 @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

그러나 기본 집합 (예 : call)을 변경 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

1

작은 컬렉션에서 일반적으로하는 일은 다음과 같은 종류의 파서 / 변환기 방법을 만드는 것

def convertSetToList(setName):
return list(setName)

그런 다음 새 목록을 사용하고 색인 번호로 액세스 할 수 있습니다

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

목록으로 작업해야 할 다른 모든 방법이 있습니다.


list컨버터 메소드를 만드는 대신 사용 하지 않습니까?
대런 토마스

-1

어때요 s.copy().pop()? 시간을 정하지는 않았지만 작동해야하며 간단합니다. 그러나 전체 세트를 복사하므로 작은 세트에 가장 적합합니다.


-6

다른 옵션은 상관없는 값을 가진 사전을 사용하는 것입니다. 예 :


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

3
원하는 방식으로 작동하지 않습니다. 파이썬 2에서 keys ()는 O (n) 연산이므로 더 이상 일정한 시간은 아니지만 적어도 keys [0]은 예상 값을 반환합니다. 파이썬에서 3 keys ()는 O (1) 연산입니다. 그러나 더 이상 목록 객체를 반환하지 않고 인덱싱 할 수없는 세트와 유사한 객체를 반환하므로 keys [0]은 TypeError를 발생시킵니다.stackoverflow.com/questions/39219065/…
sage88
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.