항목 순서를 유지하면서 목록에서 무작위 샘플을 얻습니까?


84

정렬 된 목록이 있습니다. (실제로 숫자가 아니라 복잡한 시간 소모적 인 알고리즘으로 정렬 된 객체 목록입니다)

mylist = [ 1 , 2 , 3 , 4 , 5 , 6 , 7 , 8 ,9  , 10 ]

N 개의 항목을 제공하지만 주문을 유지하는 파이썬 함수가 있습니까?

예:

randomList = getRandom(mylist,4)
# randomList = [ 3 , 6 ,7 , 9 ]
randomList = getRandom(mylist,4)
# randomList = [ 1 , 2 , 4 , 8 ]

기타...


1
random.sample정렬 하고 싶지 않습니까?
Daniel Lubarov 2011 년

그것은 아닌 사소한 알고리즘으로 분류되어있다 ... 그것은 정말 그냥 숫자 아니에요
Yochai 티 메르

4
Daniel의 의견에 대한 매우 약간의 변경 : 범위를 [0,count)샘플링하고 샘플을 정렬 한 다음 (범위의 숫자는 자연스러운 순서를 가짐) mylist인덱스 를 기반으로 값을 추출합니다 . 사용하면 zip약간 다른 메커니즘으로 동일한 효과를 얻을 수 있습니다.

1
좋아, 내가 받아 들일 무언가가 있도록 대답 + 예제를 얻을 수 있습니까? :)
Yochai 티 메르

답변:


121

다음 코드는 크기 4의 무작위 샘플을 생성합니다.

import random

sample_size = 4
sorted_sample = [
    mylist[i] for i in sorted(random.sample(range(len(mylist)), sample_size))
]

(참고 : Python 2에서는 ) xrange대신 사용 하는 것이 좋습니다.range

설명

random.sample(range(len(mylist)), sample_size)

원래 목록 의 인덱스 에 대한 무작위 샘플을 생성 합니다.

이러한 인덱스는 원래 목록의 요소 순서를 유지하기 위해 정렬됩니다.

마지막으로, 목록 이해력은 샘플링 된 인덱스가 주어지면 원래 목록에서 실제 요소를 가져옵니다.


89

코딩이 간단한 O (N + K * log (K)) 방식

인덱스를 교체하지 않고 무작위 샘플을 가져 와서 인덱스를 정렬하고 원본에서 가져옵니다.

indices = random.sample(range(len(myList)), K)
[myList[i] for i in sorted(indices)]

또는 더 간결하게 :

[x[1] for x in sorted(random.sample(enumerate(myList),K))]

최적화 된 O (N)-시간, O (1)-보조 공간 방식

대안으로 수학 트릭을 사용하고 myList동적으로 변화하는 확률로 숫자를 선택하면서 왼쪽에서 오른쪽으로 반복적으로 이동할 수 있습니다 (N-numbersPicked)/(total-numbersVisited). 이 접근 방식의 장점 O(N)은 정렬을 포함하지 않기 때문에 알고리즘 이라는 것입니다 !

from __future__ import division

def orderedSampleWithoutReplacement(seq, k):
    if not 0<=k<=len(seq):
        raise ValueError('Required that 0 <= sample_size <= population_size')

    numbersPicked = 0
    for i,number in enumerate(seq):
        prob = (k-numbersPicked)/(len(seq)-i)
        if random.random() < prob:
            yield number
            numbersPicked += 1

개념 증명 및 확률이 올바른지 테스트 :

5 시간 동안 1 조 개의 의사 난수 샘플로 시뮬레이션 :

>>> Counter(
        tuple(orderedSampleWithoutReplacement([0,1,2,3], 2))
        for _ in range(10**9)
    )
Counter({
    (0, 3): 166680161, 
    (1, 2): 166672608, 
    (0, 2): 166669915, 
    (2, 3): 166667390, 
    (1, 3): 166660630, 
    (0, 1): 166649296
})

확률은 실제 확률에서 1.0001보다 적게 차이가납니다. 이 테스트를 다시 실행하면 순서가 달라서 하나의 순서로 편향되지 않습니다. 더 적은을위한 샘플 테스트를 실행 [0,1,2,3,4], k=3하고[0,1,2,3,4,5], k=4 있었다 유사한 결과를.

편집 : 사람들이 왜 잘못된 댓글을 투표하거나 찬성하는 것을 두려워하는지 잘 모르겠습니다 ... 아니요,이 방법에는 잘못된 것이 없습니다. =)

(또한 주석에있는 사용자 tegan의 유용한 참고 사항 : 이것이 python2 인 경우 여분의 공간에 대해 정말로 관심이 있다면 평소처럼 xrange를 사용하고 싶을 것입니다.)

edit : Proof : 크기 k의 모집단 에서 하위 집합을 선택하는 균일 분포 (대체 없음)를 고려하면 임의의 지점 에서 'left'(0,1, ..., i-1)로 분할을 고려할 수 있습니다. 그리고 'right'(i, i + 1, ..., len (seq)). 왼쪽 알려진 하위 집합에서 선택했음을 감안할 때 나머지 매개 변수는 이제는 다르지만 오른쪽 알 수없는 하위 집합의 동일한 균일 분포에서 가져와야합니다. 특히, 선택한 요소 를 포함하는 확률 은 , 또는seqlen(seq)inumbersPickedseq[i]#remainingToChoose/#remainingToChooseFrom(k-numbersPicked)/(len(seq)-i), 그래서 우리는 그것을 시뮬레이션하고 결과에 대해 반복합니다. (#remainingToChoose == #remainingToChooseFrom이면 나머지 모든 확률은 1이기 때문에 종료되어야합니다.) 이것은 동적으로 생성되는 확률 트리와 유사합니다. 기본적으로 이전 선택에 대한 조건을 지정하여 균일 한 확률 분포를 시뮬레이션 할 수 있습니다 (확률 트리를 성장할 때 현재 분기의 확률을 선택하여 이전 잎과 동일하게, 즉 이전 선택에 조건을 지정합니다. 이 확률은 균일하게 정확히 N / k입니다).

편집 : 티모시 방어막이 언급 저수지 샘플링 이 방법을 때의 일반화,len(seq) 알 수없는 경우 (예 : 생성기 표현식) . 특히 "알고리즘 R"로 표시된 것은 제자리에서 수행되는 경우 O (N) 및 O (1) 공간입니다. 첫 번째 N 요소를 가져 와서 천천히 교체하는 것이 포함됩니다 (유도 증명에 대한 힌트도 제공됨). 또한 wikipedia 페이지에서 찾을 수있는 유용한 분산 변형 및 저수지 샘플링의 기타 변형이 있습니다.

편집 : 아래에 더 의미 상 명백한 방식으로 코드화하는 또 다른 방법이 있습니다.

from __future__ import division
import random

def orderedSampleWithoutReplacement(seq, sampleSize):
    totalElems = len(seq)
    if not 0<=sampleSize<=totalElems:
        raise ValueError('Required that 0 <= sample_size <= population_size')

    picksRemaining = sampleSize
    for elemsSeen,element in enumerate(seq):
        elemsRemaining = totalElems - elemsSeen
        prob = picksRemaining/elemsRemaining
        if random.random() < prob:
            yield element
            picksRemaining -= 1

from collections import Counter         
Counter(
    tuple(orderedSampleWithoutReplacement([0,1,2,3], 2))
    for _ in range(10**5)

)


1
@pst : 더 불리하지, 단지 속도 향상 O(N)이 아니라O(N log(N))
ninjagecko

1
매우 훌륭합니다.이 선형 접근 방법도 궁금합니다. 이 공식에 wikipedia 페이지가 있습니까? :)
Jochen Ritzel 2011-06-26

2
나는이 답변에 더 많은 찬성 투표가 없다는 것에 놀랐습니다. 솔루션이 실제로 어떻게 작동하는지 설명합니다 (그리고 다른 솔루션을 제공합니다!). 단지 한 줄 스 니펫 인 첫 번째 답변과 달리 이유 또는 이유를 알 수 없습니다. 작동 방식.
crazy2be

1
좋은 솔루션 ninjagecko. 누군가가 그것을 작성하는 데 관심이 있다면 솔루션에 대한 좋은 귀납적 증거가 있습니다.
Neil G

3
좋은 해결책! 추가하는 것을 잊지 마십시오 from __future__ import division파이썬 2. 실행중인 사람들을 위해
xApple

7

인덱스 샘플을 생성 한 다음 목록에서 항목을 수집 할 수 있습니다.

randIndex = random.sample(range(len(mylist)), sample_size)
randIndex.sort()
rand = [mylist[i] for i in randIndex]

4

분명히 random.sample 파이썬 2.3에서 소개되었습니다.

따라서 그 아래 버전의 경우 셔플을 사용할 수 있습니다 (예 : 4 개 항목).

myRange =  range(0,len(mylist)) 
shuffle(myRange)
coupons = [ bestCoupons[i] for i in sorted(myRange[:4]) ]

4
파이썬 2.2를 사용하고 계십니까?! 업그레이드해야합니다 ... 그건 구식입니다.
Katriel 2011-06-26

1
물론, 그 우리가 .. 서버에서이 시스템 전체의 갱신을하는 것은 너무 많은 관료이다
Yochai 티 메르

-2

random.sample은 그것을 구현합니다.

>>> random.sample([1, 2, 3, 4, 5],  3)   # Three samples without replacement
[4, 1, 5]

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