从具有权重的列表中选择k个随机元素


71

没有任何权重(均等概率)的选择在此处进行了详细描述。

我想知道是否有一种方法可以将这种方法转换为加权方法。

我也对其他方法感兴趣。

更新:无需更换的采样


2
抽样有无替换?
Amro 2010年


1
@Jason我在问一种方法,可以将这种优雅的方法转换为加权方法,它并不是完全重复的
nimcap 2010年

nimcap:我链接到的问题是关于加权随机选择。
杰森·奥伦多夫

4
以权重与每个元素的包含概率成比例的方式进行采样而不用权重进行替换并不是一件容易的事,并且对此有很好的最新研究。例如,请参阅:books.google.com.br/books/about/…–
Ferdinand.kraft

Answers:


28

我知道这是一个很老的问题,但是我认为,如果您应用一些数学运算,可以在O(n)时间内完成此操作!

指数分布有两个非常有用的特性。

  1. 给定n个具有不同速率参数的不同指数分布的样本,给定样本为最小值的概率等于其速率参数除以所有速率参数之和。

  2. 这是“无记忆的”。因此,如果您已经知道最小值,那么其余元素中的第二个元素到第二个元素的概率就等于如果删除了真正的最小值(并且从未生成),则该元素将是新元素。分钟 这似乎很明显,但是我认为由于某些条件概率问题,其他分布可能不正确。

使用事实1,我们知道选择单个元素可以通过以下方法来完成:生成速率参数等于权重的指数分布样本,然后选择最小值。

使用事实2,我们知道我们不必重新生成指数样本。相反,只需为每个元素生成一个,然后取k个元素的样本数最少。

可以在O(n)中找到最低的k。使用Quickselect算法找到的第k个元素,则简单地采取另一路经比第k个下的所有元素和输出所有。

一个有用的注意事项:如果您没有立即访问库以生成指数分布样本的方法,则可以通过以下方法轻松完成: -ln(rand())/weight


1
我认为这是这么多年以来唯一正确的答案。我曾经找到这种正确的方法,但从未想过要在这里输入。我会仔细阅读您的回答并接受。
nimcap

3
是否有对此方法的参考?我发现加权随机抽样有类似的想法。虽然我看到的逻辑是我想念的只是参考,但这是正确的分布。
罗伊

不,对不起,我没有一个。这是我自己想到的。因此,保持怀疑态度是正确的。但是,如果有一个比我聪明的人以前没有发现过它,并且给予了比堆栈溢出帖子更严格的对待,我会感到惊讶。
乔K

我在JavaScript中实现了它,但没有得到预期的结果:jsfiddle.net/gasparl/kamub5rq/18-为什么有任何想法?
加斯帕'19

69

如果采样是替换的,则可以使用此算法(在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..v都恰好小于z的概率为P =(z / vn。求解z,得到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是我以前从未见过的Python控件结构变化的绝佳用法
Sparr,2010年

见我回答另一个问题的Python实现二叉树方法:stackoverflow.com/questions/526255/...
大流士培根

1
“修复”策略可能是最快的。您可以通过将原始值存储在每个Node而不是单独的字典中来加快速度。
杰森·奥伦多夫

1
JavaScript端口(以防人们需要):gist.github.com/seejohnrun/5291246
John

1
h!你完全正确。第二组为0.5。好的,我很酷>=
杰森·奥伦多夫

42

如果采样是替换采样,请使用轮盘赌选择技术(通常在遗传算法中使用):

  1. 权重排序
  2. 计算累计权重
  3. 选择一个随机数 [0,1]*totalWeight
  4. 查找该数字落入的间隔
  5. 选择具有相应间隔的元素
  6. 重复k次数

替代文字

如果采样是不可替换的,则可以通过在每次迭代后从列表中删除选定的元素,然后重新对权重进行标准化以使权重之和为1(有效的概率分布函数),来采用上述技术。


15
+1,这在清晰度上大获成功。但是请注意,轮盘赌算法需要O(n log m + m)时间,其中n是样本数,m是项目数。就是说如果您省略了排序(这是不必要的),然后在步骤4中进行了二进制搜索,那么,它就需要O(m)空间作为累积权重。在我的答案中,有一个14行函数在O(n + m)时间和O(1)空间中执行相同的操作。
杰森·奥伦多夫

1
如果必须删除所选元素,则需要复制整个列表,我假设我们不允许对输入列表进行任何修改,这是很昂贵的。
nimcap

您需要对重量进行排序吗?这是必要的吗?

1
您认为使用分域树可以帮助您吗?
sw。

2
提出无替换方法不好。Google支持“概率不相等的系统抽样”。有一个O(n)算法,无需重新计算权重。
Pawel 2012年

3

我已经在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"

与Jason Orendorff的发布相比,此版本返回错误的答案。具体来说,在权重如[1,1,1,1,1,9996]中的pick(4,unique)上,低权重项目的结果不均匀。
robbat2 '16

1

如果要生成带有替换的随机整数大数组,则可以使用分段线性插值。例如,使用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)

如果您想不更换而取样,则此方法无效。


1

这是来自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
}

1

如果要从加权集中选取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

我认为您可以通过weighted_set在开头复制并对其进行混排来消除所选元素之间的相关性。
杰森·奥伦多夫

经过反思,我不确定如何证明这一点。
杰森·奥伦多夫

两点

0

在您所链接的问题中,Kyle的解决方案适用于简单的概括。扫描列表并对总重量求和。那么选择元素的概率应该是:

1-(1-(#needed /(left weight)))/(n的权重)。访问节点后,从总数中减去其权重。另外,如果您需要n并剩下n,则必须明确停止。

您可以检查所有重量为1的东西,从而简化了kyle的解决方案。

编辑:(必须重新考虑可能意味着两倍的含义)


1
假设我在列表中有4个元素,权重为{2,1,1,1}。我将从列表中选择3。根据您的公式,对于第一个元素3 * 2/5 = 1.2哪个> 1,我在这里缺少什么?
nimcap 2010年

现在,{2,1,1,1}选择3,则选择第一个元素的概率为1-(1-(3/5))/ 2 = 1-(2/5)/ 2 = 1-1 / 5 = 4/5,符合预期。
凯尔·巴特

我相信您的公式有问题,我现在没有时间编写,有时间的话我会写。如果您尝试以不同的顺序对前两个元素应用公式,则会看到它不会产生相同的结果。无论顺序如何,它都应提供相同的结果。
nimcap 2010年

假设有4个元素的权重为{7,1,1,1},我们将选择2。让我们计算选择前2个元素的机会:P(1st)* P(2nd)=(1-(1- 2/10)/ 7)*(1-(1-1 / 3))= 31/105。让我们将列表更改为{1,7,1,1},选择前2个元素的概率应保持不变P(1st)* P(2nd)=(1-(1-2 / 10))*(1- (1-1 / 9)/ 7)= 11/63。他们不一样。
nimcap 2010年

更简单地说,假设有2个元素的权重为{1/2,1/2},我们将选择2。概率应该为1;否则为1。我们必须两者兼而有之。但式给出1 - (1 - (2/1)))/(1/2)= 3
詹森Orendorff

0

这与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;
}

0

我在这里放置了一个简单的解决方案来选择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;
    }
}

0

我在此处的Rust中实现了类似于Jason Orendorff的想法的算法。我的版本还支持批量操作:O(m + log n)及时从数据结构中插入和删除(当您要删除由其ID给定的一堆项目时,而不是通过加权选择路径),其中m是要删除的项目数,n是要删除的项目数存储的项目数。


0

用递归替换采样-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);

    }
}

0

我只花了几个小时就尝试不进行替换而落后于基本采样算法,因此这个话题比我最初想象的要复杂。真令人兴奋!为了将来的读者受益(祝您有美好的一天!),我在这里记录我的见解,其中包括可以立即使用的功能,该功能将在下面进一步考虑给定的包含概率。可以在这里找到各种方法的快速准确的数学概述:Tillé:概率相等或不相等的采样算法。例如,可以在第46页上找到Jason的方法。他的方法的警告是权重为与包含概率成正比,如文档中所述。其实,-th包含概率可以如下递归计算:

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

指定包含概率的一种方法(也在上面的文档中建议)是从它们中计算权重。这个问题的全部复杂性源于这样一个事实,即人们不能直接做到这一点,因为基本上必须将递推公式取反,我象征性地认为这是不可能的。在数值上可以使用各种方法来完成,例如牛顿法。但是,使用普通Python反转Jacobian的复杂性很快变得难以忍受,我真的建议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:
        /programming/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')]

b两倍,经常不能出现a因为两者都必须始终被选中。还有更多微妙的例子。为了避免生产中出现异常,只需使用best_effort = True,它可以重新平衡包含概率的质量,从而使我们始终具有有效的分布。显然,这可能会更改包含概率。


-2

我使用了一个关联图(权重,对象)。例如:

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

total=10110

窥视一个介于0到“ total”之间的随机数,并在键上进行迭代,直到该数字适合给定范围。


1
我的看法是,问题在于一次通过选择多个项目(请参阅链接的问题)。您的方法要求每个选择都通过。
橡树
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.