목록에서 임의의 요소를 팝하는 가장 비단뱀적인 방법은 무엇입니까?


88

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)이 염려된다면 내 분석에서 언급 한 대기열 제거를 사용하십시오.
nikhil swami

답변:


94

당신이하고있는 것처럼 보이는 것은 처음에는 그다지 Pythonic으로 보이지 않습니다. 목록은 내가 아는 모든 파이썬 구현에서 배열로 구현되므로 목록 중간에서 항목을 제거해서는 안됩니다 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이것에 대한 완벽한
니클라스 B.

2
다운 투표 할 때 게시물을 추가해야합니다. 목록 중간에서 항목을 제거해야하는 경우가 있습니다. 지금 당장해야합니다. 선택의 여지가 없음 : 주문 목록이 있습니다. 중간에있는 항목을 제거해야합니다. 안타깝지만 유일한 다른 선택은 반 드문 작업에 대해 무거운 코드 리팩토링을 수행하는 것입니다. 문제는 이러한 작업에 효율적이어야하지만 그렇지 않은 []의 구현 중 하나입니다.
Mark Gerolimatos 2011

5
@NiklasB. OP는 무작위를 예로 사용했습니다 (솔직히 말해서 중단 했어야했고 문제를 흐리게했습니다). "그렇게 하지마"는 충분하지 않습니다. 더 나은 대답은 충분한 액세스 속도를 제공하면서 이러한 작업을 지원하는 Python 데이터 구조를 제안하는 것입니다 (분명히 arra ... er ... list만큼 좋지 않음). 파이썬 2에서는 찾을 수 없습니다. 내가 그렇게한다면 나는 그것으로 대답 할 것이다. 브라우저 사고로 인해 원래 댓글에 추가 할 수 없었으므로 2 차 댓글을 추가해야했습니다. 정직하게 해주셔서 감사합니다. :)
Mark Gerolimatos

1
@MarkGerolimatos 표준 라이브러리에는 효율적인 랜덤 액세스와 삽입 / 삭제가 모두 가능한 데이터 구조가 없습니다. 아마도 pypi.python.org/pypi/blist 같은 것을 사용하고 싶을 것입니다. 저는 여전히 많은 사용 사례에서 이것이 피할 수 있다고 주장합니다
Niklas B.

49

그보다 훨씬 나아지지는 않을 것이지만 여기에 약간의 개선이 있습니다.

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

에 대한 문서 random.randrange():

random.randrange ([start], stop [, step])
에서 임의로 선택된 요소를 반환합니다 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

또 다른 대안이 있습니다. 먼저 목록을 섞은 다음 더 이상 요소가 남아 있지 않을 때까지 목록의 요소를 터뜨리는 것이 어떻습니까? 이렇게 :

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]
오스카르 로페스

목록을 변경하기 때문에 지금 요소의 절반 만 선택하고 나머지 절반은 나중에 선택하려는 경우 나머지 요소는 나중에 설정하게됩니다.
Henrik

@Henrik : 좋아요, 그래서 나머지 목록이 필요한지 물어 보았습니다. 당신은 대답하지 않았습니다.
Niklas B.

2

이를 수행하는 한 가지 방법은 다음과 같습니다.

x.remove(random.choice(x))

7
요소가 한 번 더 발생하면 문제가 될 수 있습니다.
Niklas B.

2
이렇게하면 중복이있을 때 가장 왼쪽에있는 요소가 제거되어 완벽하게 무작위가 아닌 결과가 발생합니다.
FogleBird

으로 pop당신이 할 수없는이 함께 제거 된 요소에 이름을 가리킬 수 있습니다.
agf

공평하게, 요소가 두 번 이상 발생할 때 이것이 매우 무작위가 아니라는 데 동의합니다.
Simeon Visser 2012

1
분포를 왜곡하는 문제 외에도 remove목록의 선형 스캔이 필요합니다. 인덱스를 조회하는 것과 비교하면 매우 비효율적입니다.
aaronasterling 2012

2

목록에서 나오지 않는 동안 중복없이 목록에서 X 개의 임의 항목을 가져 오려고 시도하는 동안 Google에서이 질문이 발생했습니다. 내가 결국 사용한 것은 다음과 같습니다.

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 (yourlist)), 그러나 각 항목이 더 이상 선택되지 않도록 확인 한 번 이상 (= 대체없이 샘플링) @ jf-sebastian이 제안한 것처럼 random.sample을 사용할 수 있습니다 . 그러나 유스 케이스에 대해 더 많이 알지 못하면 이것이 필요한지 모르겠습니다.


1

이 답변은 @ niklas-b의 호의입니다 .

" pypi.python.org/pypi/blist 같은 것을 사용하고 싶을 것입니다. "

PYPI 페이지 를 인용하려면 :

... 더 나은 점근 적 성능과 작은 목록에서 유사한 성능을 가진 목록 형 유형

blist는 큰 목록을 수정할 때 더 나은 성능을 제공하는 Python 목록의 드롭 인 대체입니다. blist 패키지는 sortedlist, sortedset, weaksortedlist, weaksortedset, sorteddict 및 btuple 유형도 제공합니다.

"쓰기시 복사"데이터 구조이므로 임의 액세스 / 무작위 실행 끝 에서 성능이 저하된다고 가정 합니다. 이것은 Python 목록에 대한 많은 사용 사례 가정을 위반 하므로주의해서 사용하십시오 .

그러나 주요 사용 사례가 목록으로 이상하고 부 자연스러운 작업을 수행하는 것이라면 (@OP 또는 Python 2.6 FIFO 대기열 (pass-over 문제 포함)에서 제공 한 강제 예제에서와 같이), 이것은 청구서에 잘 맞을 것입니다. .


1

사용 제안 많은 답변에도 불구 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)

내 주장을 뒷받침하는 것은 이 소스 의 시간 복잡도 차트 입니다. 여기에 이미지 설명 입력

목록의 앞부분과 끝 부분에서 작업을 수행 할 때 속도를 원한다면 여기 이미지가 내 주장을 뒷받침하기 위해 python dequeue (double ended queue)를 사용하십시오. 이미지는 천 단어입니다.

여기에 이미지 설명 입력

당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.