如何在不更换的情况下逐步取样?

时间:2013-09-20 16:13:44

标签: python algorithm statistics probability sample

Python有my_sample = random.sample(range(100), 10)随机抽样,无需替换[0, 100)

假设我已经对n这样的数字进行了采样,现在我想再抽样一次而不进行替换(不包括任何先前采样的n),如何超高效地进行这样的操作?

更新:从“合理有效”变为“超级有效”(但忽略了常数因素)

13 个答案:

答案 0 :(得分:19)

如果你事先知道你想要多个样本而没有重叠,最简单的方法就是random.shuffle() list(range(100))(Python 3 - 可以跳过Python中的list() 2),然后根据需要剥离切片。

s = list(range(100))
random.shuffle(s)
first_sample = s[-10:]
del s[-10:]
second_sample = s[-10:]
del s[-10:]
# etc

Else @ Chronial的答案相当有效。

答案 1 :(得分:9)

简短的方法

如果抽样的数量远远少于人口,只需抽样,检查是否已选择并重复。这可能听起来很愚蠢,但是你选择相同的数字会有指数衰减的可能性,所以如果你有一小部分没有选择,它会比O(n)快得多。< / p>


漫长的路

Python使用Mersenne Twister作为其PRNG, good adequate。我们可以完全使用其他东西来以可预测的方式生成非重叠数字。

Here's the secret:

  • x² mod p2x < p为素数时,二次残差p是唯一的。

  • 如果你&#34;翻转&#34;残余p - (x² % p),此时也是p = 3 mod 4,结果将是剩余的空格。

  • 这不是一个非常令人信服的数字传播,所以你可以增加力量,添加一些软糖常数,然后分布非常好。


首先我们需要生成素数:

from itertools import count
from math import ceil
from random import randrange

def modprime_at_least(number):
    if number <= 2:
        return 2

    number = (number // 4 * 4) + 3
    for number in count(number, 4):
        if all(number % factor for factor in range(3, ceil(number ** 0.5)+1, 2)):
            return number

您可能会担心生成素数的成本。对于10⁶元素,这需要十分之一毫秒。运行[None] * 10**6需要的时间比此长,并且因为它只计算一次,所以这不是一个真正的问题。

此外,该算法不需要精确值;只需要一个最多是大于输入数的常数因子的东西。这可以通过保存值列表并搜索它们来实现。如果进行线性扫描,则为O(log number),如果进行二分查找,则为O(log number of cached primes)。事实上,如果你使用galloping,你可以将其降低到O(log log number),这基本上是不变的(log log googol = 2)。

然后我们实现了生成器

def sample_generator(up_to):
    prime = modprime_at_least(up_to+1)

    # Fudge to make it less predictable
    fudge_power = 2**randrange(7, 11)
    fudge_constant = randrange(prime//2, prime)
    fudge_factor = randrange(prime//2, prime)

    def permute(x):
        permuted = pow(x, fudge_power, prime) 
        return permuted if 2*x <= prime else prime - permuted

    for x in range(prime):
        res = (permute(x) + fudge_constant) % prime
        res = permute((res * fudge_factor) % prime)

        if res < up_to:
            yield res

并检查它是否有效:

set(sample_generator(10000)) ^ set(range(10000))
#>>> set()

现在,关于这一点的可爱之处在于,如果您忽略了首要性测试(大约O(√n),其中n是元素的数量,则此算法具有时间复杂度O(k),其中k是样本sizeit&{39}和O(1)内存使用情况!从技术上讲,这是O(√n + k),但实际上它是O(k)


要求:

  1. 您不需要经过验证的PRNG。这个PRNG比线性同余生成器(它很受欢迎; Java uses it)要好得多,但它不像Mersenne Twister那样经过验证。

  2. 您不首先生成具有不同功能的任何项目。这可以通过数学避免重复,而不是检查。下一节我将介绍如何删除此限制。

  3. 简短方法必须不足(k必须接近n)。如果k仅为n的一半,请按照我原来的建议行事。

  4. 优点:

    1. 极节省内存。这需要不断的记忆......甚至不是O(k)

    2. 生成下一个项目的恒定时间。实际上,这在实际上也是相当快的:它不像内置的Mersenne Twister一样 ,但它在2倍的范围内。

      < / LI>
    3. 凉意。


    4. 要删除此要求:

        

      您不首先生成具有不同功能的任何项目。这可以避免重复数学,而不是检查。

      我及时制作了最好的算法空间复杂度,这是我以前的生成器的简单扩展。

      这里是纲要(n是数字池的长度,k是&#34;外国&#34;键的数量:

      初始化时间O(√n);所有合理输入的O(log log n)

      由于O(√n)费用,这是我的算法中唯一在算法复杂性方面不完善的因素。实际上,这不会有问题,因为预先计算会将其降低到O(log log n),这与不变的时间无法接近。

      如果您将迭代耗尽任何固定百分比,则费用可以免费摊销。

      这不是一个实际问题。

      分摊O(1)密钥生成时间

      显然这无法改进。

      最差情况O(k)密钥生成时间

      如果你有从外部生成的密钥,只要求它不能是这个生成器已经生成的密钥,那么这些密钥将被称为&#34;外键&#34;。外键被假定为完全随机的。因此,任何能够从池中选择项目的功能都可以这样做。

      因为可以有任意数量的外键并且它们可以是完全随机的,所以完美算法的最坏情况是O(k)

      最坏情况空间复杂度O(k)

      如果假设外键完全独立,则每个外键代表不同的信息项。因此必须存储所有密钥。该算法恰好在看到密钥时丢弃密钥,因此内存成本将在生成器的生命周期内清除。

      算法

      嗯,这是我的算法。它实际上很简单:

      def sample_generator(up_to, previously_chosen=set(), *, prune=True):
          prime = modprime_at_least(up_to+1)
      
          # Fudge to make it less predictable
          fudge_power = 2**randrange(7, 11)
          fudge_constant = randrange(prime//2, prime)
          fudge_factor = randrange(prime//2, prime)
      
          def permute(x):
              permuted = pow(x, fudge_power, prime) 
              return permuted if 2*x <= prime else prime - permuted
      
          for x in range(prime):
              res = (permute(x) + fudge_constant) % prime
              res = permute((res * fudge_factor) % prime)
      
              if res in previously_chosen:
                  if prune:
                      previously_chosen.remove(res)
      
              elif res < up_to:
                  yield res
      

      更改就像添加:

      一样简单
      if res in previously_chosen:
          previously_chosen.remove(res)
      

      您可以通过添加到您传入的previously_chosen随时添加到set。实际上,您也可以从集合中删除以添加回潜在的资源池,尽管这仅在sample_generator尚未产生或prune=False跳过它时才有效。

      所以有。很容易看出它满足所有要求,并且很容易看出要求是绝对的。请注意,如果您没有设置,它仍会通过将输入转换为集合来满足最坏情况,但会增加开销。


      测试RNG的质量

      从统计学角度来说,我很好奇这个PRNG实际上有多好。

      一些快速搜索引导我创建这三个测试,这些测试似乎都显示出良好的效果!

      首先,一些随机数字:

      N = 1000000
      
      my_gen = list(sample_generator(N))
      
      target = list(range(N))
      random.shuffle(target)
      
      control = list(range(N))
      random.shuffle(control)
      

      这些&#34;洗牌&#34;从010⁶-1的10个数字列表,一个使用我们有趣的Fudged PRNG,另一个使用Mersenne Twister作为基线。第三是控制。


      这是一个测试,它查看沿线两个随机数之间的平均距离。将差异与对照进行比较:

      from collections import Counter
      
      def birthdat_calc(randoms):
          return Counter(abs(r1-r2)//10000 for r1, r2 in zip(randoms, randoms[1:]))
      
      def birthday_compare(randoms_1, randoms_2):
          birthday_1 = sorted(birthdat_calc(randoms_1).items())
          birthday_2 = sorted(birthdat_calc(randoms_2).items())
      
          return sum(abs(n1 - n2) for (i1, n1), (i2, n2) in zip(birthday_1, birthday_2))
      
      print(birthday_compare(my_gen, target), birthday_compare(control, target))
      #>>> 9514 10136
      

      这比每个的方差小。


      这是一个测试,它依次获取5个数字并查看元素的顺序。它们应该在所有120个可能的订单之间平均分配。

      def permutations_calc(randoms):
          permutations = Counter()        
      
          for items in zip(*[iter(randoms)]*5):
              sorteditems = sorted(items)
              permutations[tuple(sorteditems.index(item) for item in items)] += 1
      
          return permutations
      
      def permutations_compare(randoms_1, randoms_2):
          permutations_1 = permutations_calc(randoms_1)
          permutations_2 = permutations_calc(randoms_2)
      
          keys = sorted(permutations_1.keys() | permutations_2.keys())
      
          return sum(abs(permutations_1[key] - permutations_2[key]) for key in keys)
      
      print(permutations_compare(my_gen, target), permutations_compare(control, target))
      #>>> 5324 5368
      

      这再次小于每个方差。


      这是一项测试,可以看到&#34;运行多久&#34;是,又名。连续增加或减少的部分。

      def runs_calc(randoms):
          runs = Counter()
      
          run = 0
          for item in randoms:
              if run == 0:
                  run = 1
      
              elif run == 1:
                  run = 2
                  increasing = item > last
      
              else:
                  if (item > last) == increasing:
                      run += 1
      
                  else:
                      runs[run] += 1
                      run = 0
      
              last = item
      
          return runs
      
      def runs_compare(randoms_1, randoms_2):
          runs_1 = runs_calc(randoms_1)
          runs_2 = runs_calc(randoms_2)
      
          keys = sorted(runs_1.keys() | runs_2.keys())
      
          return sum(abs(runs_1[key] - runs_2[key]) for key in keys)
      
      print(runs_compare(my_gen, target), runs_compare(control, target))
      #>>> 1270 975
      

      这里的差异非常大,而且在几次执行中,我看起来两者都是均匀的。因此,该测试通过了。


      我提到了一个线性同余生成器,因为可能更有成效&#34;。我自己制作了一个严格执行的LCG,看看这是否是一个准确的陈述。

      LCG,AFAICT,就像普通的生成器一样,它们不会成为循环。因此,我看过的大多数参考资料,又名。维基百科,仅涵盖了定义期间的内容,而不是如何在特定时期内制定强大的LCG。这可能会影响结果。

      这里是:

      from operator import mul
      from functools import reduce
      
      # Credit http://stackoverflow.com/a/16996439/1763356
      # Meta: Also Tobias Kienzler seems to have credit for my
      #       edit to the post, what's up with that?
      def factors(n):
          d = 2
          while d**2 <= n:
              while not n % d:
                  yield d
                  n //= d
              d += 1
          if n > 1:
             yield n
      
      def sample_generator3(up_to):
          for modulier in count(up_to):
              modulier_factors = set(factors(modulier))
              multiplier = reduce(mul, modulier_factors)
              if not modulier % 4:
                  multiplier *= 2
      
              if multiplier < modulier - 1:
                  multiplier += 1
                  break
      
          x = randrange(0, up_to)
      
          fudge_constant = random.randrange(0, modulier)
          for modfact in modulier_factors:
              while not fudge_constant % modfact:
                  fudge_constant //= modfact
      
          for _ in range(modulier):
              if x < up_to:
                  yield x
      
              x = (x * multiplier + fudge_constant) % modulier
      

      我们不再检查质数,但我们确实需要做一些奇怪的事情。

      • modulier ≥ up_to > multiplier, fudge_constant > 0
      • a - 1必须可以被modulier ...
      • 中的每个因素整除
      • ... fudge_constant必须 coprime modulier

      请注意,这些不是LCG的规则,而是具有完整周期的LCG,显然等于mod ulier。

      我是这样做的:

      • 至少modulier up_to尝试,在条件满足时停止
        • 制作一系列因素
        • multiplier成为的产品,并删除重复项
        • 如果multiplier不小于modulier,请继续下一个modulier
        • fudge_constant小于modulier,随机选择
        • fudge_constant
        • 中删除中的因子

      这不是生成它的好方法,但我不知道为什么它会影响数字的质量,除了低fudge_constant s和{{1}这一事实比这些可能造成的完美发电机更常见。

      无论如何,结果是令人震惊的

      multiplier

      总之,我的RNG很好,线性同余生成器不是。考虑到Java使用线性同余生成器(尽管它只使用较低的位),我希望我的版本绰绰有余。

答案 2 :(得分:9)

好的,我们走了。这应该是最快的非概率算法。它的运行时间为O(k⋅log²(s) + f⋅log(f)) ⊂ O(k⋅log²(f+k) + f⋅log(f)))和空格O(k+f)f是禁止数字的数量,s是禁止数字最长条纹的长度。对此的期望更复杂,但明显受f约束。如果您认为s^log₂(s)大于f或者对s再一次是概率性的事实感到不满意,您可以将日志部分更改为{{1}中的二分搜索得到forbidden[pos:]

此处的实际实施是O(k⋅log(f+k) + f⋅log(f)),因为列表O(k⋅(k+f)+f⋅log(f))中的插入是forbid。通过使用blist sortedlist替换该列表,可以轻松解决此问题。

我还添加了一些评论,因为这个算法非常复杂。 O(n)部分与lin部分相同,但需要log而不是s时间。

log²(s)

现在将它与Veedrac提出的“hack”(以及python中的默认实现)进行比较,其中空格import bisect import random def sample(k, end, forbid): forbidden = sorted(forbid) out = [] # remove the last block from forbidden if it touches end for end in reversed(xrange(end+1)): if len(forbidden) > 0 and forbidden[-1] == end: del forbidden[-1] else: break for i in xrange(k): v = random.randrange(end - len(forbidden) + 1) # increase v by the number of values < v pos = bisect.bisect(forbidden, v) v += pos # this number might also be already taken, find the # first free spot ##### linear #while pos < len(forbidden) and forbidden[pos] <=v: # pos += 1 # v += 1 ##### log while pos < len(forbidden) and forbidden[pos] <= v: step = 2 # when this is finished, we know that: # • forbidden[pos + step/2] <= v + step/2 # • forbidden[pos + step] > v + step # so repeat until (checked by outer loop): # forbidden[pos + step/2] == v + step/2 while (pos + step <= len(forbidden)) and \ (forbidden[pos + step - 1] <= v + step - 1): step = step << 1 pos += step >> 1 v += step >> 1 if v == end: end -= 1 else: bisect.insort(forbidden, v) out.append(v) return out 和(O(f+k)expected number of “guesses”)时间:< / p>

O(f+k*(n/(n-(f+k)))

n/(n-(f+k))plotted thisk=10 n=10000n只会变得更加极端)。我不得不说:我只是实施了这个,因为它似乎是一个有趣的挑战,但即使我对这是多么极端感到惊讶:

enter image description here

让我们放大一下,看看发生了什么:

enter image description here

是的 - 对于您生成的第9998个数字,猜测更快。请注意,正如您在第一个图中看到的那样,对于较大的f/n,即使我的单行也可能更快(但对于大n仍然有相当可怕的空间要求。)

为了让点回家:你在这里花费时间的唯一事情是生成集合,因为这是Veedrac方法中的f因素。

enter image description here

所以我希望我的时间没有浪费,我设法让你相信Veedrac的方法就是这样。我可以理解为什么概率部分会给你带来麻烦,但也许可以想到这样一个事实:hashmaps(= python dict s)和其他许多算法都使用类似的方法,它们似乎做得很好。

你可能会害怕重复次数的变化。如上所述,这是geometric distribution p=n-f/n之后的结果。因此,标准偏差(=您应该“期望”的结果偏离预期平均值)是

enter image description here

这与平均值(√f⋅n < √n² = n)基本相同。

****编辑**:
我刚才意识到s实际上也是n/(n-(f+k))。因此,我的算法的更精确的运行时为O(k⋅log²(n/(n-(f+k))) + f⋅log(f))。这很好,因为给出了上面的图表,它证明了我的直觉,这比O(k⋅log(f+k) + f⋅log(f))快得多。但请放心,这也不会改变上述结果,因为f⋅log(f)是运行时中绝对主导的部分。

答案 3 :(得分:8)

来自OP的读者注意:请考虑查看originally accepted answer以了解逻辑,然后了解此答案。

Aaaaaand为了完整起见:这是necromancer’s answer的概念,但经过调整,因此需要一个禁止数字列表作为输入。这与my previous answer中的代码相同,但我们会在生成数字之前从forbid构建状态。

  • 这是时间O(f+k)和记忆O(f+k)。显然,如果没有forbid(排序/设置)格式的要求,这是最快的事情。我认为这使得它在某种程度上成为赢家^^。
  • 如果forbid是一个集合,则O(k⋅n/(n-(f+k)))的重复猜测方法会更快,O(k)非常接近f+k而非非常接近n
  • 如果对forbid进行了排序,则my ridiculous algorithm会更快: O(k⋅(log(f+k)+log²(n/(n-(f+k))))
import random
def sample_gen(n, forbid):
    state = dict()
    track = dict()
    for (i, o) in enumerate(forbid):
        x = track.get(o, o)
        t = state.get(n-i-1, n-i-1)
        state[x] = t
        track[t] = x
        state.pop(n-i-1, None)
        track.pop(o, None)
    del track
    for remaining in xrange(n-len(forbid), 0, -1):
        i = random.randrange(remaining)
        yield state.get(i, i)
        state[i] = state.get(remaining - 1, remaining - 1)
        state.pop(remaining - 1, None)

用法:

gen = sample_gen(10, [1, 2, 4, 8])
print gen.next()
print gen.next()
print gen.next()
print gen.next()

答案 4 :(得分:7)

这是一种不明确构建差异集的方法。但它确实使用了@ Veedrac的“接受/拒绝”逻辑形式。如果你不愿意随意改变基础序列,我担心这是不可避免的:

def sample(n, base, forbidden):
    # base is iterable, forbidden is a set.
    # Every element of forbidden must be in base.
    # forbidden is updated.
    from random import random
    nusable = len(base) - len(forbidden)
    assert nusable >= n
    result = []
    if n == 0:
        return result
    for elt in base:
        if elt in forbidden:
            continue
        if nusable * random() < n:
            result.append(elt)
            forbidden.add(elt)
            n -= 1
            if n == 0:
                return result
        nusable -= 1
    assert False, "oops!"

这是一个小司机:

base = list(range(100))
forbidden = set()
for i in range(10):
    print sample(10, base, forbidden)

答案 5 :(得分:7)

好的,最后一次尝试;-)以改变基本序列为代价,这不需要额外的空间,并且每个n调用需要与sample(n)成比例的时间:

class Sampler(object):
    def __init__(self, base):
        self.base = base
        self.navail = len(base)
    def sample(self, n):
        from random import randrange
        if n < 0:
            raise ValueError("n must be >= 0")
        if n > self.navail:
            raise ValueError("fewer than %s unused remain" % n)
        base = self.base
        for _ in range(n):
            i = randrange(self.navail)
            self.navail -= 1
            base[i], base[self.navail] = base[self.navail], base[i]
        return base[self.navail : self.navail + n]

小司机:

s = Sampler(list(range(100)))
for i in range(9):
    print s.sample(10)
    print s.sample(1)
print s.sample(1)

实际上,这会实现一个可恢复的random.shuffle(),在选择n个元素后暂停。 base没有被破坏,但是被置换了。

答案 6 :(得分:6)

您可以根据Wikipedia's "Fisher--Yates shuffle#Modern method"

实施改组生成器
def shuffle_gen(src):
    """ yields random items from base without repetition. Clobbers `src`. """
    for remaining in xrange(len(src), 0, -1):
        i = random.randrange(remaining)
        yield src[i]
        src[i] = src[remaining - 1]

然后可以使用itertools.islice切片:

>>> import itertools
>>> sampler = shuffle_gen(range(100))
>>> sample1 = list(itertools.islice(sampler, 10))
>>> sample1
[37, 1, 51, 82, 83, 12, 31, 56, 15, 92]
>>> sample2 = list(itertools.islice(sampler, 80))
>>> sample2
[79, 66, 65, 23, 63, 14, 30, 38, 41, 3, 47, 42, 22, 11, 91, 16, 58, 20, 96, 32, 76, 55, 59, 53, 94, 88, 21, 9, 90, 75, 74, 29, 48, 28, 0, 89, 46, 70, 60, 73, 71, 72, 93, 24, 34, 26, 99, 97, 39, 17, 86, 52, 44, 40, 49, 77, 8, 61, 18, 87, 13, 78, 62, 25, 36, 7, 84, 2, 6, 81, 10, 80, 45, 57, 5, 64, 33, 95, 43, 68]
>>> sample3 = list(itertools.islice(sampler, 20))
>>> sample3
[85, 19, 54, 27, 35, 4, 98, 50, 67, 69]

答案 7 :(得分:6)

这是我的Knuth shuffle版本,由Tim Peters首次发布,由Eric美化,然后由necromancer进行空间优化。

这是基于Eric的版本,因为我确实发现他的代码很漂亮:)。

import random
def shuffle_gen(n):
    # this is used like a range(n) list, but we don’t store
    # those entries where state[i] = i.
    state = dict()
    for remaining in xrange(n, 0, -1):
        i = random.randrange(remaining)
        yield state.get(i,i)
        state[i] = state.get(remaining - 1,remaining - 1)
        # Cleanup – we don’t need this information anymore
        state.pop(remaining - 1, None)

用法:

out = []
gen = shuffle_gen(100)
for n in range(100):
    out.append(gen.next())
print out, len(set(out))

答案 8 :(得分:5)

编辑:请参阅@TimPeters和@Chronial下面的更清晰版本。一个小编辑将其推到了顶部。

以下是我认为最有效的增量采样解决方案。调用者要维护的状态包括可供增量采样器使用的字典,以及保留在该范围内的数字计数,而不是先前采样的数字列表。

以下是示范性实施。与其他解决方案相比:

  • 没有循环(没有标准的Python / Veedrac黑客; Python impl和Veedrac之间的共享信用)
  • 时间复杂度为O(log(number_previously_sampled))
  • 空间复杂度为O(number_previously_sampled)

代码:

import random

def remove (i, n, state):
  if i == n - 1:
    if i in state:
      t = state[i]
      del state[i]
      return t
    else:
      return i
  else:
    if i in state:
      t = state[i]
      if n - 1 in state:
        state[i] = state[n - 1]
        del state[n - 1]
      else:
        state[i] = n - 1
      return t
    else:
      if n - 1 in state:
        state[i] = state[n - 1]
        del state[n - 1]
      else:
        state[i] = n - 1
      return i

s = dict()
for n in range(100, 0, -1):
  print remove(random.randrange(n), n, s)

答案 9 :(得分:5)

这是@ necromancer的酷解决方案的重写版本。将它包装在一个类中以使其更容易正确使用,并使用更多的dict方法来删除代码行。

from random import randrange

class Sampler:
    def __init__(self, n):
        self.n = n # number remaining from original range(n)
        # i is a key iff i < n and i already returned;
        # in that case, state[i] is a value to return
        # instead of i.
        self.state = dict()

    def get(self):
        n = self.n
        if n <= 0:
            raise ValueError("range exhausted")
        result = i = randrange(n)
        state = self.state
        # Most of the fiddling here is just to get
        # rid of state[n-1] (if it exists).  It's a
        # space optimization.
        if i == n - 1:
            if i in state:
                result = state.pop(i)
        elif i in state:
            result = state[i]
            if n - 1 in state:
                state[i] = state.pop(n - 1)
            else:
                state[i] = n - 1
        elif n - 1 in state:
            state[i] = state.pop(n - 1)
        else:
            state[i] = n - 1
        self.n = n-1
        return result

这是一个基本的驱动因素:

s = Sampler(100)
allx = [s.get() for _ in range(100)]
assert sorted(allx) == list(range(100))

from collections import Counter
c = Counter()
for i in range(6000):
    s = Sampler(3)
    one = tuple(s.get() for _ in range(3))
    c[one] += 1
for k, v in sorted(c.items()):
    print(k, v)

和样本输出:

(0, 1, 2) 1001
(0, 2, 1) 991
(1, 0, 2) 995
(1, 2, 0) 1044
(2, 0, 1) 950
(2, 1, 0) 1019

通过眼球,这种分布很好(如果你持怀疑态度则进行卡方检验)。这里的一些解决方案并没有给出每个置换的概率相等(即使它们以相等的概率返回n的每个k子集),因此在这方面不同于random.sample()

答案 10 :(得分:4)

合理快速的单行(O(n + m),n =范围,m =旧样本化):

next_sample = random.sample(set(range(100)).difference(my_sample), 10)

答案 11 :(得分:1)

令人惊讶的是,它还没有在核心功能之一中实现,但是这里是干净的版本,它返回采样值和列表而不进行替换:

def sample_n_points_without_replacement(n, set_of_points):
    sampled_point_indices = random.sample(range(len(set_of_points)), n)
    sampled_point_indices.sort(reverse=True)
    sampled_points = [set_of_points[sampled_point_index] for sampled_point_index in sampled_point_indices]
    for sampled_point_index in sampled_point_indices:
        del(set_of_points[sampled_point_index])
    return sampled_points, set_of_points

答案 12 :(得分:0)

这是一个旁注:假设您要解决完全相同的采样问题,而不在列表上进行替换(我将称其为['a', 'a', 'c', 'ca', 'a', 'b', 'c', 'b', 'a'] 1 ),但不是在您拥有的所有元素上进行统一采样尚未进行抽样,您将获得初始概率分布sample_space,该初始概率分布告诉您在整个空间中进行抽样时对分布的p元素进行抽样的概率。

然后使用numpy的以下实现在数值上是稳定的:

i^th

简短而简洁,我喜欢它。让我知道你们的想法!