随机抽样,无需更换


10

创建一个函数,该函数将输出从一个范围中得出的一组不同的随机数。集合中元素的顺序无关紧要(甚至可以排序),但是每次调用函数时,集合的内容都必须不同。

该函数将以您想要的任何顺序接收3个参数:

  1. 输出集中的数量计数
  2. 下限(含)
  3. 上限(含)

假设所有数字都是0(含)到2 31(不含)之间的整数。可以将输出传递回任何您想要的方式(写入控制台,作为数组等)。

评判

条件包括3个R

  1. 运行时 -在Windows 7四核计算机上使用免费或易于使用的编译器进行了测试(必要时提供链接)
  2. 稳健性 -函数是否处理极端情况,或者陷入无限循环或产生无效结果-无效输入中的异常或错误有效
  3. 随机性 -它应该产生随机结果,使用随机分布很难预测。使用内置的随机数生成器就可以了。但是,不应有明显的偏差或明显的可预测模式。需要比Dilbert会计部门使用的随机数生成器更好

如果它是健壮且随机的,那么它就取决于运行时间。缺乏鲁棒性或随机性会极大地损害其地位。


输出是否应该通过DIEHARDTestU01测试之类的东西,或者您将如何判断其随机性?哦,代码应在32位还是64位模式下运行?(这将对优化产生很大的影响。)
Ilmari Karonen 2012年

我想TestU01可能有点苛刻。标准3是否意味着均匀分布?另外,为什么要不重复?那不是特别随机。
乔伊(Joey)2012年

@乔伊,确定是。它是随机抽样,无需替换。只要没有人声称列表中的不同位置是独立的随机变量,就没有问题。
彼得·泰勒

的确如此。但是我不确定是否有完善的库和工具来测量抽样的随机性:-)
Joey

@IlmariKaronen:RE:随机性:在此之前,我已经看到过实施是非常随意的。他们要么有严重的偏见,要么缺乏在连续运行中产生不同结果的能力。因此,我们谈论的不是加密级别的随机性,而是比Dilbert中会计部的随机数生成器更随机
Jim McKeeth 2012年

Answers:


6

蟒蛇

import random

def sample(n, lower, upper):
    result = []
    pool = {}
    for _ in xrange(n):
        i = random.randint(lower, upper)
        x = pool.get(i, i)
        pool[i] = pool.get(lower, lower)
        lower += 1
        result.append(x)
    return result

我可能只是重新发明了一些著名的算法,但其想法是(概念上)执行该范围的部分Fisher-Yates混洗,lower..upper以获取n均匀混洗的范围的长度前缀。

当然,存储整个范围会非常昂贵,因此我只存储元素已交换的位置。

这样,该算法在从狭窄范围内采样数字(例如,在1..1000范围内的1000个数字)以及从较大范围内采样数字的情况下均应表现良好。

我不确定Python内置生成器的随机性质量,但是交换可以在某个范围内均匀生成整数的任何生成器相对简单。


1
Python使用Mersenne Twister,因此它相对不错。
ESultanik'3

1

python 2.7

import random
print(lambda x,y,z:random.sample(xrange(y,z),x))(input(),input(),input())

不确定使用内置随机方法的情况如何,但是无论如何您都可以使用。好又短

编辑:只是注意到range()不喜欢做大列表。导致内存错误。会看看是否还有其他方法可以...

edit2:range是错误的函数,xrange可以工作。最大整数实际上是2**31-1针对python的

测试:

python sample.py
10
0
2**31-1
[786475923, 2087214992, 951609341, 1894308203, 173531663, 211170399, 426989602, 1909298419, 1424337410, 2090382873]

1

C

返回一个数组,该数组包含介于min和max之间的x个唯一的随机整数。(呼叫者必须有空)

#include <stdlib.h>
#include <stdint.h>
#define MAX_ALLOC ((uint32_t)0x40000000)  //max allocated bytes, fix per platform
#define MAX_SAMPLES (MAX_ALLOC/sizeof(uint32_t))

int* randsamp(uint32_t x, uint32_t min, uint32_t max)
{
   uint32_t r,i=x,*a;
   if (!x||x>MAX_SAMPLES||x>(max-min+1)) return NULL;
   a=malloc(x*sizeof(uint32_t));
   while (i--) {
      r= (max-min+1-i);
      a[i]=min+=(r ? rand()%r : 0);
      min++;
   }
   while (x>1) {
      r=a[i=rand()%x--];
      a[i]=a[x];
      a[x]=r;
   }
   return a;
}

通过生成范围内的x个顺序随机整数,然后对其进行混洗来工作。seed(time)如果您不想每次运行都获得相同的结果,请在调用方中添加一个位置。


1

红宝石> = 1.8.7

def pick(num, min, max)
  (min..max).to_a.sample(num)
end

p pick(5, 10, 20) #=>[12, 18, 13, 11, 10]

1

[R

s <- function(n, lower, upper) sample(lower:upper,n); s(10,0,2^31-2)

1

这个问题是不正确的。是否需要统一采样?在需要统一采样的情况下,我在R中具有以下代码,其平均复杂度为Os log s),其中s是样本大小。

# The Tree growing algorithm for uniform sampling without replacement
# by Pavel Ruzankin 
quicksample = function (n,size)
# n - the number of items to choose from
# size - the sample size
{
  s=as.integer(size)
  if (s>n) {
    stop("Sample size is greater than the number of items to choose from")
  }
  # upv=integer(s) #level up edge is pointing to
  leftv=integer(s) #left edge is poiting to; must be filled with zeros
  rightv=integer(s) #right edge is pointig to; must be filled with zeros
  samp=integer(s) #the sample
  ordn=integer(s) #relative ordinal number

  ordn[1L]=1L #initial value for the root vertex
  samp[1L]=sample(n,1L) 
  if (s > 1L) for (j in 2L:s) {
    curn=sample(n-j+1L,1L) #current number sampled
    curordn=0L #currend ordinal number
    v=1L #current vertice
    from=1L #how have come here: 0 - by left edge, 1 - by right edge
    repeat {
      curordn=curordn+ordn[v]
      if (curn+curordn>samp[v]) { #going down by the right edge
        if (from == 0L) {
          ordn[v]=ordn[v]-1L
        }
        if (rightv[v]!=0L) {
          v=rightv[v]
          from=1L
        } else { #creating a new vertex
          samp[j]=curn+curordn
          ordn[j]=1L
          # upv[j]=v
          rightv[v]=j
          break
        }
      } else { #going down by the left edge
        if (from==1L) {
          ordn[v]=ordn[v]+1L
        }
        if (leftv[v]!=0L) {
          v=leftv[v]
          from=0L
        } else { #creating a new vertex
          samp[j]=curn+curordn-1L
          ordn[j]=-1L
          # upv[j]=v
          leftv[v]=j
          break
        }
      }
    }
  }
  return(samp)  
}

当然,可以用C重写它以获得更好的性能。该算法的复杂性将在下面讨论:Rouzankin,PS; Voytishek,AV关于随机选择算法的成本。蒙特卡洛方法应用 5(1999),没有。1,39-54。 http://dx.doi.org/10.1515/mcma.1999.5.1.39

您可能会在本文中寻找平均复杂度相同的另一种算法。

但是,如果您不需要统一采样,仅要求所有采样数都不同,那么情况将发生巨大变化。编写平均复杂度为Os)的算法并不难。

另请参见统一采样:P。Gupta,GP Bhattacharjee。(1984)一种有效的随机抽样算法,无需替换。国际计算机数学杂志16:4,第201-209页。DOI:10.1080 / 00207168408803438

Teuhola,J。和Nevalainen,O。1982。两种有效的随机抽样算法,无需替换。/ IJCM /,11(2):127–140。DOI:10.1080 / 00207168208803304

在最后一篇论文中,作者使用哈希表并声称其算法具有Os)复杂度。还有另一种快速哈希表算法,很快将在pqR(相当快的R)中实现:https ://stat.ethz.ch/pipermail/r-devel/2017-October/075012.html


1

APL,18 22字节

{⍵[0]+(1↑⍺)?⍵[1]-⍵[0]}

声明一个带有两个参数和的匿名函数是您想要的随机数的数量,是一个包含该上限和下限顺序的向量。

a?b选择a0-之间的随机数,b而不进行替换。通过采取⍵[1]-⍵[0]我们得到范围的大小。然后,我们从该范围中选择数字(请参见下文)并添加下限。在C中,这将是

lower + rand() * (upper - lower)

次无需更换。不需要括号,因为APL从右到左运行。

假设我正确地理解了条件,那么这将无法通过“健壮性”条件,因为如果给定了不正确的参数(例如,传递向量而不是标量),该函数将失败。

如果是向量而不是标量,则1↑⍺采用的第一个元素。对于标量,这就是标量本身。对于矢量,它是第一个元素。这应该使函数满足“鲁棒性”标准。

例:

Input: 100 {⍵[0]+⍺?⍵[1]-⍵[0]} 0 100
Output: 34 10 85 2 46 56 32 8 36 79 77 24 90 70 99 61 0 21 86 50 83 5 23 27 26 98 88 66 58 54 76 20 91 72 71 65 63 15 33 11 96 60 43 55 30 48 73 75 31 13 19 3 45 44 95 57 97 37 68 78 89 14 51 47 74 9 67 18 12 92 6 49 41 4 80 29 82 16 94 52 59 28 17 87 25 84 35 22 38 1 93 81 42 40 69 53 7 39 64 62

2
这不是打高尔夫球的代码,而是最快的方式,因此目标是生成执行任务的最快代码,而不是最短的代码。无论如何,您实际上并不需要从这样的参数中选择项目,并且可以确定它们的顺序,因此{⍵+⍺?⎕-⍵}就足够了,其中提示是上界,而右arg是下界
Uriel

0

斯卡拉

object RandSet {
  val random = util.Random 

  def rand (count: Int, lower: Int, upper: Int, sofar: Set[Int] = Set.empty): Set[Int] =
    if (count == sofar.size) sofar else 
    rand (count, lower, upper, sofar + (random.nextInt (upper-lower) + lower)) 
}

object RandSetRunner {

  def main (args: Array [String]) : Unit = {
    if (args.length == 4) 
      (0 until args (0).toInt).foreach { unused => 
      println (RandSet.rand (args (1).toInt, args (2).toInt, args (3).toInt).mkString (" "))
    }
    else Console.err.println ("usage: scala RandSetRunner OUTERCOUNT COUNT MIN MAX")
  }
}

编译并运行:

scalac RandSetRunner.scala 
scala RandSetRunner 200 15 0 100

第二行将使用0到100的15个值运行200个测试,因为Scala生成快速的字节码,但需要一些启动时间。因此200从15到0到100的值开始会消耗更多时间。

2 Ghz单核上的示例:

time scala RandSetRunner 100000 10 0 1000000 > /dev/null

real    0m2.728s
user    0m2.416s
sys     0m0.168s

逻辑:

使用内置的随机和递归方式选择范围(max-min)中的数字,添加min并检查集合的大小是否为预期大小。

批判:

  • 对于大范围的小样本来说,这将很快,但是如果任务是从样本中选择几乎所有元素(1000个中的999个数字),它将重复选择集合中已经存在的数字。
  • 从这个问题上,我不确定是否需要针对无法满足的请求进行清理,例如将10个不同的数字从4转换为8。这将导致一个无限循环,但是可以通过事先检查来轻松避免,如果有的话我会附加要求。

0

方案

不知道为什么您需要传递3个参数,也不知道为什么我需要假设任何范围...

(import srfi-1) ;; for iota
(import srfi-27) ;; randomness
(import srfi-43) ;; for vector-swap!

(define rand (random-source-make-integers
               default-random-source))

;; n: length, i: lower limit
(define (random-range n i)
  (let ([v (list->vector (iota n i))])
    (let f ([n n])
      (let* ([i (rand n)] [n (- n 1)])
        (if (zero? n) v
            (begin (vector-swap! v n i) (f n)))))))

0

[R

random <- function(count, from, to) {
  rand.range <- to - from

  vec <- c()

  for (i in 1:count) {
    t <- sample(rand.range, 1) + from
    while(i %in% vec) {
      t <- sample(rand.range, 1) + from
    }
    vec <- c(vec, t)
  }

  return(vec)
}

0

C ++

当从范围中抽取许多样本时,此代码是最佳的。

#include <exception>
#include <stdexcept>
#include <cstdlib>

template<typename OutputIterator>
 void sample(OutputIterator out, int n, int min, int max)
{
  if (n < 0)
    throw std::runtime_error("negative sample size");
  if (max < min)
    throw std::runtime_error("invalid range");
  if (n > max-min+1)
    throw std::runtime_error("sample size larger than range");

  while (n>0)
  {
    double r = std::rand()/(RAND_MAX+1.0);
    if (r*(max-min+1) < n)
    {
      *out++ = min;
      --n;
    }
    ++min;
  }
}

除非max-min它大于,否则很容易陷入无限循环n。此外,输出序列会单调增加,因此您获得的质量随机性非常低,但仍要为rand()每个结果支付多次调用的费用。随机随机排列数组可能值得额外的运行时间。
彼得·科德斯

0

Q(19个字符)

f:{(neg x)?y+til z}

然后使用f [x; y; z]作为[输出集中的数量计数;起点;范围的大小]

例如f [5; 10; 10]将输出5个介于10和19之间的不同随机数。

q)\ts do[100000;f[100;1;10000]]
2418 131456j

上面的结果显示了在100,000次迭代中选择1和10,000之间的100个随机数的性能。


0

R,31或40个字节(取决于单词“范围”的含义)

如果输入有3个数字,a[1], a[2], a[3]并且“范围”是指“从a [2]到a [3]的整数序列”,那么您将得到:

a=scan();sample(a[2]:a[3],a[1])

如果您有一个n要从中进行重采样的数组,但是受到上下限的限制,例如“ n从范围内对给定数组的值进行重采样a[1]...a[2]”,请使用以下方法:

a=scan();sample(n[n>=a[2]&n<=a[3]],a[1])

考虑到带有替换设备的内置样品,为什么以前的结果没有打高尔夫球,我感到非常惊讶!我们创建一个满足范围条件的向量,然后对其重新采样。

  • 稳健性:默认情况下处理极端情况(与要采样的范围具有相同长度的序列)。
  • 运行时间:内置,因此非常快。
  • 随机性:每次调用RNG时,种子都会自动更改。

至少在我的机器上,0:(2^31)导致Error: cannot allocate a vector of size 16.0 Gb
Giuseppe

@Giuseppe最近,我一直在处理大内存问题,而实际上,解决方案是在更好的计算机上运行它。任务制定中的限制与处理器有关,与内存无关,所以……规则滥用吗?啊,我是驴子。我认为这是一个代码高尔夫挑战,但实际上它是...最快的代码。我迷路了吗?
安德烈·科斯蒂卡(AndreïKostyrka),

0

Javascript(使用外部库)(64字节/ 104字节??)

(a,b,n)=>_.Range(0,n).Select(x=>Math.random()*(b-a)+a).ToArray()

链接到lib: https //github.com/mvegh1/Enumerable/

代码说明:Lambda表达式接受min,max,count作为args。创建一个大小为n的集合,并将每个元素映射到一个符合最小/最大标准的随机数。转换为本地JS数组并返回它。我也在输入5,000,000的大小上运行了此命令,并且在应用了独特的变换之后仍然显示了5,000,000个元素。如果一致认为这不足以保证独特性,我将更新答案

我在下图中包含了一些统计信息...

在此处输入图片说明

编辑:下图显示了代码/性能,保证每个元素都将有所不同。与上面的相同参数(0.012秒)的原始代码相比,它要慢得多(50,000个元素为6.65秒)

在此处输入图片说明


0

K(oK),14个字节

解:

{y+(-x)?1+z-y}

在线尝试!

例:

> {y+(-x)?1+z-y}. 10 10 20      / note: there are two ways to provide input, dot or
13 20 16 17 19 10 14 12 11 18
> {y+(-x)?1+z-y}[10;10;20]      / explicitly with [x;y;z]
12 11 13 19 15 17 18 20 14 10

说明:

每个规范接受3个隐式输入:

  • x,输出集中的数字计数,
  • y,下限(包括下限)
  • z,上限(含)

{y+(-x)?1+z-y} / the solution
{            } / lambda function with x, y and z as implicit inputs
          z-y  / subtract lower limit from upper limit
        1+     / add 1
   (-x)?       / take x many distinct items from 0..(1+z=y)
 y+            / add lower limit

笔记:

还有一个多语种,q/kdb+带有一组额外的括号:{y+((-)x)?1+z-y}(16个字节)。


0

公理及其图书馆

f(n:PI,a:INT,b:INT):List INT==
    r:List INT:=[]
    a>b or n>99999999 =>r
    d:=1+b-a
    for i in 1..n repeat
          r:=concat(r,a+random(d)$INT)
    r

在f(n,a,b)且a> b的情况下,上面的f()函数将空列表作为错误返回。在其他无效输入的情况下,它不会在Axiom窗口中运行并显示一条错误消息,因为参数将不是正确的类型。例子

(6) -> f(1,1,5)
   (6)  [2]
                                                       Type: List Integer
(7) -> f(1,1,1)
   (7)  [1]
                                                       Type: List Integer
(10) -> f(10,1,1)
   (10)  [1,1,1,1,1,1,1,1,1,1]
                                                       Type: List Integer
(11) -> f(10,-20,-1)
   (11)  [- 10,- 4,- 18,- 5,- 5,- 11,- 15,- 1,- 20,- 1]
                                                       Type: List Integer
(12) -> f(10,-20,-1)
   (12)  [- 4,- 5,- 3,- 4,- 18,- 1,- 2,- 14,- 19,- 8]
                                                       Type: List Integer
(13) -> f(10,-20,-1)
   (13)  [- 18,- 12,- 12,- 19,- 19,- 15,- 5,- 17,- 19,- 4]
                                                       Type: List Integer
(14) -> f(10,-20,-1)
   (14)  [- 8,- 11,- 20,- 10,- 4,- 8,- 11,- 3,- 10,- 16]
                                                       Type: List Integer
(15) -> f(10,9,-1)
   (15)  []
                                                       Type: List Integer
(16) -> f(10,0,100)
   (16)  [72,83,41,35,27,0,33,18,60,38]
                                                       Type: List Integer
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.