ProgramingTip

요소에 어떤가있는 목록에서 k 개의 임의의 요소를 선택합니다.

bestdevel 2020. 11. 4. 08:10
반응형

요소에 어떤가있는 목록에서 k 개의 임의의 요소를 선택합니다.


(동등 확률)없이 선택하는 방법은 여기에 설명되어 있습니다 .

이 접근 방식을 적응가있는 접근 방식으로 변환하는 방법이 있는지 궁금합니다.

나는 또한 다른 접근 방식에도 관심이 있습니다.

업데이트 : 교체 없이 샘플링


나는 약간의 수학을 적용하면 O (n) 시간에 수행하는 깔끔한 트릭이 생각합니다!

지수 낭비는 두 가지 매우 유용한 속성이 있습니다.

  1. 주어진 샘플이 최소값 일 확률은 속도 매개 변수가 다른 서로 다른 지수의 n 개 샘플이 주어지면 주어진 샘플이 최소값 일 확률은 속도 변수를 모든 속도 매개 변수의 합으로 가변적이다.

  2. "메모리리스"입니다. 따라서 최소값을 이미 알고있는 경우 요소 중 하나가 최소 2 일 확률은 실제 최소값이 제거되고 없을 확률이 생성되므로 해당 요소가 새로운 요소가 될 확률과 있습니다. 최소 이것은 분명해 보이지만 조건부 확률 문제로 인해 다른 분포에서는 사실이 아닐 수도 있습니다.

사실 1을 사용하는 것을 단일 요소 선택은 사용하는 것입니다. 하나의 요소 선택은 사용하는 것을 사용하는 것을 선택할 수 있습니다.

2를 사용하면 지수 샘플을 다시 생성 할 필요가 있습니다. 대신 각 요소에 대해 하나씩 생성하고 샘플이 가장 낮은 요소를 가져옵니다.

가장 낮은 것을 찾는 것은 O (n)에서 할 수 있습니다. Quickselect 알고리즘을 사용하여 k 번째 요소를 다음 모든 요소를 ​​다시 통과하여 k 번째보다 낮은 값을 모두 출력합니다.

유용한 참고 사항 : 지수 활용 샘플을 생성하기 위해 라이브러리에 즉시 액세스 할 수없는 경우 다음 방법으로 쉽게 수행 할 수 있습니다. -ln(rand())/weight


샘플링이 대체 인 경우 다음 알고리즘을 사용할 수 있습니다 (여기 Python에서 구현 됨).

import random

items = [(10, "low"),
         (100, "mid"),
         (890, "large")]

def weighted_sample(items, n):
    total = float(sum(w for w, v in items))
    i = 0
    w, v = items[0]
    while n:
        x = total * (1 - random.random() ** (1.0 / n))
        total -= x
        while x > w:
            x -= w
            i += 1
            w, v = items[i]
        w -= x
        yield v
        n -= 1

이것은 O ( n + m )입니다. 여기서 m 은 항목의 수입니다.

왜 작동 작동합니까? 다음 알고리즘을 기반으로합니다.

def n_random_numbers_decreasing(v, n):
    """Like reversed(sorted(v * random() for i in range(n))),
    but faster because we avoid sorting."""
    while n:
        v *= random.random() ** (1.0 / n)
        yield v
        n -= 1

이 기능 weighted_sample은이 알고리즘이 items목록 의 산책과 융합 되어 그 난수로 선택한 항목을 골라냅니다.

이 차례로 작동하는 이유는 n 개의 난수 0 .. vz 보다 작을 확률 P = ( z / v ) n 이기 때문 입니다. 에 대한 해결 Z , 그리고 GET Z = VP 인 1 / N . P를 난수로 대체 하면 올바른 선택을 가진 가장 큰 숫자가됩니다. 이 과정을 반복하여 다른 모든 숫자를 선택할 수 있습니다.

샘플링이 대체되지 않은 경우 모든 항목을 이진 힙에 넣을 수 있습니다. 여기서 각 노드는 해당 하위 힙에있는 모든 항목의 총을 캐시합니다. 힙 빌드는 O ( m )입니다. 어떤 것을 고려하여 힙에서 임의의 항목을 선택하는 것은 O (log m )입니다. 해당 항목을 제거하고 캐시 된 총계를 업데이트하는 것도 O (log m )입니다. 따라서 O ( m + n log m ) 시간에 n 개의 항목을 선택할 수 있습니다 .

(참고 : 여기서 "가중치"는 요소가 선택 될 때마다 나머지 가능성이 될 가능성에 비례하는 확률로 선택됨을 의미합니다.

여기에 많은 주석이 달린 구현이 있습니다.

import random

class Node:
    # Each node in the heap has a weight, value, and total weight.
    # The total weight, self.tw, is self.w plus the weight of any children.
    __slots__ = ['w', 'v', 'tw']
    def __init__(self, w, v, tw):
        self.w, self.v, self.tw = w, v, tw

def rws_heap(items):
    # h is the heap. It's like a binary tree that lives in an array.
    # It has a Node for each pair in `items`. h[1] is the root. Each
    # other Node h[i] has a parent at h[i>>1]. Each node has up to 2
    # children, h[i<<1] and h[(i<<1)+1].  To get this nice simple
    # arithmetic, we have to leave h[0] vacant.
    h = [None]                          # leave h[0] vacant
    for w, v in items:
        h.append(Node(w, v, w))
    for i in range(len(h) - 1, 1, -1):  # total up the tws
        h[i>>1].tw += h[i].tw           # add h[i]'s total to its parent
    return h

def rws_heap_pop(h):
    gas = h[1].tw * random.random()     # start with a random amount of gas

    i = 1                     # start driving at the root
    while gas >= h[i].w:      # while we have enough gas to get past node i:
        gas -= h[i].w         #   drive past node i
        i <<= 1               #   move to first child
        if gas >= h[i].tw:    #   if we have enough gas:
            gas -= h[i].tw    #     drive past first child and descendants
            i += 1            #     move to second child
    w = h[i].w                # out of gas! h[i] is the selected node.
    v = h[i].v

    h[i].w = 0                # make sure this node isn't chosen again
    while i:                  # fix up total weights
        h[i].tw -= w
        i >>= 1
    return v

def random_weighted_sample_no_replacement(items, n):
    heap = rws_heap(items)              # just make a heap...
    for i in range(n):
        yield rws_heap_pop(heap)        # and pop n items off it.

샘플링이 대체 인 경우 룰렛 휠 선택 기술을 사용합니다 (유전 알고리즘에서 자주 사용됨).

  1. 경기 정렬
  2. 총 계산 계산
  3. 임의의 숫자를 고르다 [0,1]*totalWeight
  4. 이 숫자가 간격을 찾으십시오.
  5. 해당 간격으로 요소 선택
  6. 반복 k시간

대체 텍스트

샘플링이없는 경우 각 반복 후 목록에서 선택한 요소를 다음과 같은 대체 정규화하여 다음과 같은 경우에 (유효한 확률이있는 함수) 위의 기술을 적용 할 수 있습니다.


나는 관리 Ruby에서했다

https://github.com/fl00r/pickup

require 'pickup'
pond = {
  "selmon"  => 1,
  "carp" => 4,
  "crucian"  => 3,
  "herring" => 6,
  "sturgeon" => 8,
  "gudgeon" => 10,
  "minnow" => 20
}
pickup = Pickup.new(pond, uniq: true)
pickup.pick(3)
#=> [ "gudgeon", "herring", "minnow" ]
pickup.pick
#=> "herring"
pickup.pick
#=> "gudgeon"
pickup.pick
#=> "sturgeon"

대체로 임의의 정수 구성된 큰 배열을 생성하려는 경우 부분 선형 보간을 사용할 수 있습니다. 예를 들어 NumPy / SciPy를 사용하는 경우 :

import numpy
import scipy.interpolate

def weighted_randint(weights, size=None):
    """Given an n-element vector of weights, randomly sample
    integers up to n with probabilities proportional to weights"""
    n = weights.size
    # normalize so that the weights sum to unity
    weights = weights / numpy.linalg.norm(weights, 1)
    # cumulative sum of weights
    cumulative_weights = weights.cumsum()
    # piecewise-linear interpolating function whose domain is
    # the unit interval and whose range is the integers up to n
    f = scipy.interpolate.interp1d(
            numpy.hstack((0.0, weights)),
            numpy.arange(n + 1), kind='linear')
    return f(numpy.random.random(size=size)).astype(int)

교체하지 않고 샘플링하려는 경우


다음은 geodns 의 Go 구현입니다 .

package foo

import (
    "log"
    "math/rand"
)

type server struct {
    Weight int
    data   interface{}
}

func foo(servers []server) {
    // servers list is already sorted by the Weight attribute

    // number of items to pick
    max := 4

    result := make([]server, max)

    sum := 0
    for _, r := range servers {
        sum += r.Weight
    }

    for si := 0; si < max; si++ {
        n := rand.Intn(sum + 1)
        s := 0

        for i := range servers {
            s += int(servers[i].Weight)
            if s >= n {
                log.Println("Picked record", i, servers[i])
                sum -= servers[i].Weight
                result[si] = servers[i]

                // remove the server from the list
                servers = append(servers[:i], servers[i+1:]...)
                break
            }
        }
    }

    return result
}


어떤 경우에 비례하는 확률로 요소가 선택하거나 대체하지 않는 경우 집합에서 x 요소를 선택하려는 경우 :

import random

def weighted_choose_subset(weighted_set, count):
    """Return a random sample of count elements from a weighted set.

    weighted_set should be a sequence of tuples of the form 
    (item, weight), for example:  [('a', 1), ('b', 2), ('c', 3)]

    Each element from weighted_set shows up at most once in the
    result, and the relative likelihood of two particular elements
    showing up is equal to the ratio of their weights.

    This works as follows:

    1.) Line up the items along the number line from [0, the sum
    of all weights) such that each item occupies a segment of
    length equal to its weight.

    2.) Randomly pick a number "start" in the range [0, total
    weight / count).

    3.) Find all the points "start + n/count" (for all integers n
    such that the point is within our segments) and yield the set
    containing the items marked by those points.

    Note that this implementation may not return each possible
    subset.  For example, with the input ([('a': 1), ('b': 1),
    ('c': 1), ('d': 1)], 2), it may only produce the sets ['a',
    'c'] and ['b', 'd'], but it will do so such that the weights
    are respected.

    This implementation only works for nonnegative integral
    weights.  The highest weight in the input set must be less
    than the total weight divided by the count; otherwise it would
    be impossible to respect the weights while never returning
    that element more than once per invocation.
    """
    if count == 0:
        return []

    total_weight = 0
    max_weight = 0
    borders = []
    for item, weight in weighted_set:
        if weight < 0:
            raise RuntimeError("All weights must be positive integers")
        # Scale up weights so dividing total_weight / count doesn't truncate:
        weight *= count
        total_weight += weight
        borders.append(total_weight)
        max_weight = max(max_weight, weight)

    step = int(total_weight / count)

    if max_weight > step:
        raise RuntimeError(
            "Each weight must be less than total weight / count")

    next_stop = random.randint(0, step - 1)

    results = []
    current = 0
    for i in range(count):
        while borders[current] <= next_stop:
            current += 1
        results.append(weighted_set[current][0])
        next_stop += step

    return results

연결 한 질문에서 Kyle의 솔루션은 사소한 일반화로 작동합니다. 목록을 스캔하고 총 무게를 합산하십시오. 그런 다음 요소를 선택할 확률은 다음과 같아야합니다.

1- (1-(# 필요 / (왼쪽 무게))) / (n에서 무게). 노드를 방문한 후 전체에서 총을 듭니다. 또한 n이 필요하고 n이 남았다면 명시 적으로 중지해야합니다.

모든 항목이 kyle의 솔루션을 단순화하는지 확인할 수 있습니다.

수정 됨 : (무엇을 의미하는지 다시 생각해야 함)


이것은 O (n)과 정확히 일치하고 과도한 메모리 사용이 없습니다. 나는 쉬운 영리하고 쉬운 솔루션이라고 믿습니다. 처음 두 줄은 Drupal에 샘플 데이터를 채우는 것입니다.

function getNrandomGuysWithWeight($numitems){
  $q = db_query('SELECT id, weight FROM theTableWithTheData');
  $q = $q->fetchAll();

  $accum = 0;
  foreach($q as $r){
    $accum += $r->weight;
    $r->weight = $accum;
  }

  $out = array();

  while(count($out) < $numitems && count($q)){
    $n = rand(0,$accum);
    $lessaccum = NULL;
    $prevaccum = 0;
    $idxrm = 0;
    foreach($q as $i=>$r){
      if(($lessaccum == NULL) && ($n <= $r->weight)){
        $out[] = $r->id;
        $lessaccum = $r->weight- $prevaccum;
        $accum -= $lessaccum;
        $idxrm = $i;
      }else if($lessaccum){
        $r->weight -= $lessaccum;
      }
      $prevaccum = $r->weight;
    }
    unset($q[$idxrm]);
  }
  return $out;
}

여기에 항목 1 개를 선택하는 간단한 솔루션을 넣었습니다. k 항목 (Java 스타일)에서 쉽게 확장 할 수 있습니다.

double random = Math.random();
double sum = 0;
for (int i = 0; i < items.length; i++) {
    val = items[i];
    sum += val.getValue();
    if (sum > random) {
        selected = val;
        break;
    }
}

여기 Rust에서 Jason Orendorff의 아이디어와 알고리즘을 구현했습니다 . 내 버전은 추가로 대량 작업을 지원합니다 O(m + log n). m은 제거 할 항목의 수이고 n은 데이터 구조에서 데이터 구조에서 삽입 및 제거 (가중 선택 경로를 통하지 않고 ID로 삭제 항목을 제거하려는 경우) 항목 수.


재귀로 대체하지 않고 샘플링-우아하고 매우 짧은 C # 솔루션

// 60 명의 학생 중 4 명을 선택할 수있는 방법이 얼마나 많은지, 그래서 우리가 선택할 때마다 다른 4 명

class Program
{
    static void Main(string[] args)
    {
        int group = 60;
        int studentsToChoose = 4;

        Console.WriteLine(FindNumberOfStudents(studentsToChoose, group));
    }

    private static int FindNumberOfStudents(int studentsToChoose, int group)
    {
        if (studentsToChoose == group || studentsToChoose == 0)
            return 1;

        return FindNumberOfStudents(studentsToChoose, group - 1) + FindNumberOfStudents(studentsToChoose - 1, group - 1);

    }
}

나는 대체없이 샘플링의 기반이되는 알고리즘을 사용하여 몇 시간을 보냈고이 주제 는 처음에 생각했던 것보다 복잡합니다. 흥미 진진합니다! 미래의 독자들을 위해 (좋은 하루 되세요!)? 저는 아래에 주어진 포함 확률 을 존중하는 바로 사용할 수있는 기능 현관을 포함하여 여기에 내 통찰력을 문서화 합니다. 다양한 방법에 대한 훌륭하고 빠른 수학적 개요는 여기에서 수 있습니다. Tillé : 같거나 다른 확률을 가진 샘플링 알고리즘 . 예를 들어 Jason의 방법은 46 페이지에서 사용할 수 있습니다. 방법에서주의 그의 할 점은 문서에서도 언급 한 것처럼 가중치가 포함 확률에 비례 하지 않는다는을 구석으로 입니다. 사실, 나는 -번째 포함 확률은 다음과 같이 재귀로 계산할 수 있습니다.

def inclusion_probability(i, weights, k):
    """
        Computes the inclusion probability of the i-th element
        in a randomly sampled k-tuple using Jason's algorithm
        (see https://stackoverflow.com/a/2149533/7729124)
    """
    if k <= 0: return 0
    cum_p = 0
    for j, weight in enumerate(weights):
        # compute the probability of j being selected considering the weights
        p = weight / sum(weights)

        if i == j:
            # if this is the target element, we don't have to go deeper,
            # since we know that i is included
            cum_p += p
        else:
            # if this is not the target element, than we compute the conditional
            # inclusion probability of i under the constraint that j is included
            cond_i = i if i < j else i-1
            cond_weights = weights[:j] + weights[j+1:]
            cond_p = inclusion_probability(cond_i, cond_weights, k-1)
            cum_p += p * cond_p
    return cum_p

그리고 위 함수의 유효성을 비교하여 확인할 수 있습니다.

In : for i in range(3): print(i, inclusion_probability(i, [1,2,3], 2))
0 0.41666666666666663
1 0.7333333333333333
2 0.85

...에

In : import collections, itertools
In : sample_tester = lambda f: collections.Counter(itertools.chain(*(f() for _ in range(10000))))
In : sample_tester(lambda: random_weighted_sample_no_replacement([(1,'a'),(2,'b'),(3,'c')],2))
Out: Counter({'a': 4198, 'b': 7268, 'c': 8534})

포함 확률을 지정하는 한 가지 방법 (위 문서에서 안됨)은 계산하는 것입니다. 당면한 질문의 전체적인 기본적으로 재귀 공식을 뒤집어 야하기 때문에 직접 할 수 있습니다. 상징적으로 저는 이것이 불가능하다고 주장합니다. 수치 적으로는 모든 종류의 방법 (예 : Newton의 방법)을 사용하여 수행 할 수 있습니다. 그러나 평범한 파이썬을 사용하여 코비안을 반전시키는 복잡성은 금방 견딜 수 없게됩니다 . 경우를이 살펴 보는 것이 좋습니다 .numpy.random.choice

운 좋게도 사용자의 목적에 따라 충분히 수행 할 수도 있고 아닐 수도있는 일반 Python을 사용하는 방법이 있습니다. 다른 작동가 많지 정렬됩니다. 75 & 76 페이지에서 알고리즘을 사용할 수 있습니다. 샘플링 프로세스를 동일한 것과 확률을 가진 부분으로 분할하여 작동 random.sample합니다. 즉, 다시 사용할 수 있습니다 ! 기본 사항은 69 페이지에 잘 나와 있으므로 원칙을 설명하지 않습니다. 다음은 충분한 양의 주석이 포함 된 코드입니다.

def sample_no_replacement_exact(items, k, best_effort=False, random_=None, ε=1e-9):
    """
        Returns a random sample of k elements from items, where items is a list of
        tuples (weight, element). The inclusion probability of an element in the
        final sample is given by
           k * weight / sum(weights).

        Note that the function raises if a inclusion probability cannot be
        satisfied, e.g the following call is obviously illegal:
           sample_no_replacement_exact([(1,'a'),(2,'b')],2)
        Since selecting two elements means selecting both all the time,
        'b' cannot be selected twice as often as 'a'. In general it can be hard to
        spot if the weights are illegal and the function does *not* always raise
        an exception in that case. To remedy the situation you can pass
        best_effort=True which redistributes the inclusion probability mass
        if necessary. Note that the inclusion probabilities will change
        if deemed necessary.

        The algorithm is based on the splitting procedure on page 75/76 in:
        http://www.eustat.eus/productosServicios/52.1_Unequal_prob_sampling.pdf
        Additional information can be found here:
        https://stackoverflow.com/questions/2140787/

        :param items: list of tuples of type weight,element
        :param k: length of resulting sample
        :param best_effort: fix inclusion probabilities if necessary,
                            (optional, defaults to False)
        :param random_: random module to use (optional, defaults to the
                        standard random module)
        :param ε: fuzziness parameter when testing for zero in the context
                  of floating point arithmetic (optional, defaults to 1e-9)
        :return: random sample set of size k
        :exception: throws ValueError in case of bad parameters,
                    throws AssertionError in case of algorithmic impossibilities
    """
    # random_ defaults to the random submodule
    if not random_:
        random_ = random

    # special case empty return set
    if k <= 0:
        return set()

    if k > len(items):
        raise ValueError("resulting tuple length exceeds number of elements (k > n)")

    # sort items by weight
    items = sorted(items, key=lambda item: item[0])

    # extract the weights and elements
    weights, elements = list(zip(*items))

    # compute the inclusion probabilities (short: π) of the elements
    scaling_factor = k / sum(weights)
    π = [scaling_factor * weight for weight in weights]

    # in case of best_effort: if a inclusion probability exceeds 1,
    # try to rebalance the probabilities such that:
    # a) no probability exceeds 1,
    # b) the probabilities still sum to k, and
    # c) the probability masses flow from top to bottom:
    #    [0.2, 0.3, 1.5] -> [0.2, 0.8, 1]
    # (remember that π is sorted)
    if best_effort and π[-1] > 1 + ε:
        # probability mass we still we have to distribute
        debt = 0.
        for i in reversed(range(len(π))):
            if π[i] > 1.:
                # an 'offender', take away excess
                debt += π[i] - 1.
                π[i] = 1.
            else:
                # case π[i] < 1, i.e. 'save' element
                # maximum we can transfer from debt to π[i] and still not
                # exceed 1 is computed by the minimum of:
                # a) 1 - π[i], and
                # b) debt
                max_transfer = min(debt, 1. - π[i])
                debt -= max_transfer
                π[i] += max_transfer
        assert debt < ε, "best effort rebalancing failed (impossible)"

    # make sure we are talking about probabilities
    if any(not (0 - ε <= π_i <= 1 + ε) for π_i in π):
        raise ValueError("inclusion probabilities not satisfiable: {}" \
                         .format(list(zip(π, elements))))

    # special case equal probabilities
    # (up to fuzziness parameter, remember that π is sorted)
    if π[-1] < π[0] + ε:
        return set(random_.sample(elements, k))

    # compute the two possible lambda values, see formula 7 on page 75
    # (remember that π is sorted)
    λ1 = π[0] * len(π) / k
    λ2 = (1 - π[-1]) * len(π) / (len(π) - k)
    λ = min(λ1, λ2)

    # there are two cases now, see also page 69
    # CASE 1
    # with probability λ we are in the equal probability case
    # where all elements have the same inclusion probability
    if random_.random() < λ:
        return set(random_.sample(elements, k))

    # CASE 2:
    # with probability 1-λ we are in the case of a new sample without
    # replacement problem which is strictly simpler,
    # it has the following new probabilities (see page 75, π^{(2)}):
    new_π = [
        (π_i - λ * k / len(π))
        /
        (1 - λ)
        for π_i in π
    ]
    new_items = list(zip(new_π, elements))

    # the first few probabilities might be 0, remove them
    # NOTE: we make sure that floating point issues do not arise
    #       by using the fuzziness parameter
    while new_items and new_items[0][0] < ε:
        new_items = new_items[1:]

    # the last few probabilities might be 1, remove them and mark them as selected
    # NOTE: we make sure that floating point issues do not arise
    #       by using the fuzziness parameter
    selected_elements = set()
    while new_items and new_items[-1][0] > 1 - ε:
        selected_elements.add(new_items[-1][1])
        new_items = new_items[:-1]

    # the algorithm reduces the length of the sample problem,
    # it is guaranteed that:
    # if λ = λ1: the first item has probability 0
    # if λ = λ2: the last item has probability 1
    assert len(new_items) < len(items), "problem was not simplified (impossible)"

    # recursive call with the simpler sample problem
    # NOTE: we have to make sure that the selected elements are included
    return sample_no_replacement_exact(
        new_items,
        k - len(selected_elements),
        best_effort=best_effort,
        random_=random_,
        ε=ε
    ) | selected_elements

예 :

In : sample_no_replacement_exact([(1,'a'),(2,'b'),(3,'c')],2)
Out: {'b', 'c'}

In : import collections, itertools
In : sample_tester = lambda f: collections.Counter(itertools.chain(*(f() for _ in range(10000))))
In : sample_tester(lambda: sample_no_replacement_exact([(1,'a'),(2,'b'),(3,'c'),(4,'d')],2))
Out: Counter({'a': 2048, 'b': 4051, 'c': 5979, 'd': 7922})

10 포함 확률은 a→ 20 %, b→ 40 %, c→ 60 %, d→ 80 %로 계산됩니다. (합계 : 200 % = k.) 작동합니다!

이 함수의 생산적인 사용에 대해 한마디 만주의하면 가중치에 대한 잘못된 입력을 발견하기가 매우 어려울 수 있습니다. 명백한 불법 예는

In: sample_no_replacement_exact([(1,'a'),(2,'b')],2)
ValueError: inclusion probabilities not satisfiable: [(0.6666666666666666, 'a'), (1.3333333333333333, 'b')]

ba둘 다 항상 선택 해야하므로 두 번 자주 나타날 수 없습니다 . 더 미묘한 예가 있습니다. 프로덕션에서 예외를 방지하려면 best_effort = True를 사용하면됩니다. 이것은 포함 확률 질량을 재조정하여 항상 유효한 분포를 갖도록 합니다. 분명히 이것은 포함 확률을 변경할 수 있습니다.


연관 맵 (무게, 오브젝트)을 사용했습니다. 예를 들면 :

{
(10,"low"),
(100,"mid"),
(10000,"large")
}

total=10110

0과 '합계'사이의 임의의 숫자를 들여다보고이 숫자가 주어진 범위에 들어갈 때까지 키를 반복합니다.

참고 URL : https://stackoverflow.com/questions/2140787/select-k-random-elements-from-a-list-whose-elements-have-weights

반응형