Випадкова вибірка без заміни


10

Створіть функцію, яка виведе набір чітких випадкових чисел, виведених з діапазону. Порядок елементів у наборі є неважливим (вони навіть можуть бути відсортовані), але повинно бути можливим, щоб вміст набору був різним щоразу, коли функція викликається.

Функція отримає 3 параметри в будь-якому порядку:

  1. Підрахунок чисел у наборі виводу
  2. Нижня межа (включно)
  3. Верхня межа (включно)

Припустимо, що всі числа є цілими числами в діапазоні від 0 (включно) до 2 31 (виключно). Вихід може бути переданий назад будь-яким способом (записувати на консоль, як масив тощо)

Судження

Критерії включають 3 R

  1. Час виконання - тестується на чотирьохядерній машині Windows 7 з будь-яким компілятором, який є у вільному чи легкому доступі (надайте посилання, якщо необхідно)
  2. Надійність - чи функція обробляє кутові випадки, чи потрапить у нескінченний цикл, чи призведе до недійсних результатів - виняток або помилка при неправильному введенні є дійсним
  3. Випадковість - вона повинна давати випадкові результати, які непросто передбачувані при випадковому розподілі. Користуватися вбудованим генератором випадкових чисел чудово. Але не повинно бути явних упереджень чи очевидних передбачуваних зразків. Потрібно бути кращим, ніж той генератор випадкових чисел, який використовується Департаментом бухгалтерського обліку в Ділберті

Якщо він надійний і випадковий, він зводиться до часу виконання. Якщо не бути надійним або випадковим, сильно шкодить його оцінкам.


Вихід повинен пройти щось на кшталт тестів DIEHARD або TestU01 , або як ви будете судити про його випадковість? О, і чи повинен код працювати в 32 або 64 бітовому режимі? (Це призведе до великої різниці в оптимізації.)
Ільмарі Каронен

Мабуть, TestU01 трохи суворий. Чи передбачає критерій 3 рівномірний розподіл? Крім того, чому не повторювана вимога? То це не особливо випадково.
Джої

@Joey, впевнений, що так. Це випадкова вибірка без заміни. Поки ніхто не стверджує, що різні позиції у списку є незалежними випадковими змінними, немає жодної проблеми.
Пітер Тейлор

А, справді. Але я не впевнений, чи є налагоджені бібліотеки та інструменти для вимірювання випадковості вибірки :-)
Joey

@IlmariKaronen: RE: Випадковість: Я бачив, що реалізації раніше були жахливо не випадковими. Або вони мали сильний ухил, або не мали можливості давати різні результати на послідовних пробігах. Тому ми говоримо не про випадковість криптографічного рівня, а про більш випадкову, ніж генератор випадкових чисел Департаменту бухгалтерії у Ділберта .
Джим Маккіт

Відповіді:


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

Я, мабуть, просто заново винайшов якийсь добре відомий алгоритм, але ідея полягає в тому, щоб (концептуально) виконати часткове переміщення Фішера-Йейта діапазону, lower..upperщоб отримати nпрефікс довжини рівномірно перетасованого діапазону.

Звичайно, зберігання всього асортименту було б досить дорогим, тому я зберігаю лише ті місця, де елементи були замінені.

Таким чином, алгоритм повинен добре працювати як у випадку, коли вибираєте номери з чіткого діапазону (наприклад, 1000 чисел у діапазоні 1..1000), так і у випадку, коли ви відбираєте числа з великого діапазону .

Я не впевнений у якості випадковості вбудованого генератора в Python, але порівняно просто замінити будь-який генератор, який може генерувати цілі числа рівномірно з деякого діапазону.


1
Python використовує Mersenne Twister , тому він відносно пристойний.
ESultanik

1

пітон 2.7

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

не впевнений, на чому стоїть ваше становище, використовуючи вбудовані випадкові методи, але тут ви все одно. приємний і короткий

редагувати: щойно помітив, що діапазон () не любить складати великі списки. призводить до помилки пам'яті. побачить, чи є інший спосіб зробити це ...

edit2: діапазон був неправильною функцією, xrange працює. Максимальне ціле число насправді 2**31-1для python

тест:

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

1

С

Повертає масив, що містить x унікальних випадкових точок між min та max. (абонент повинен безкоштовно)

#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, який має середню складність O ( s 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. Складність цього алгоритму обговорюється в: Рузанкін, П.С .; Войтішек, А. В. Про вартість алгоритмів для випадкового відбору. Методи Монте-Карло 5 (1999), вип. 1, 39-54. http://dx.doi.org/10.1515/mcma.1999.5.1.39

Ви можете переглянути цей документ для іншого алгоритму з такою ж середньою складністю.

Але якщо вам не потрібен рівномірний вибірки, вимагаючи лише, щоб усі вибіркові номери були різними, ситуація різко змінюється. Не важко написати алгоритм із середньою складністю O ( s ).

Дивіться також щодо рівномірного відбору проб: П. Гупта, Г. П. Бхаттачарджі. (1984) Ефективний алгоритм випадкової вибірки без заміни. Міжнародний журнал комп’ютерної математики 16: 4, сторінки 201-209. DOI: 10.1080 / 00207168408803438

Teuhola, J. та Nevalainen, O. 1982. Два ефективні алгоритми випадкової вибірки без заміни. / IJCM /, 11 (2): 127–140. DOI: 10.1080 / 00207168208803304

В останній роботі автори використовують хеш-таблиці і стверджують, що їх алгоритми мають складність O ( s ). Є ще один швидкий алгоритм хеш-таблиці, який незабаром буде впроваджений у pqR (досить швидкий R): https://stat.ethz.ch/pipermail/r-devel/2017-O жовтня/075012.html


1

APL, 18 22 байти

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

Оголошує анонімну функцію, яка бере два аргументи і . - кількість випадкових чисел, які ви хочете, - вектор, що містить нижню та верхню межі, у тому порядку.

a?bвибирає aвипадкові числа між 0- bбез заміни. Беручи, ⍵[1]-⍵[0]ми отримуємо розмір діапазону. Потім вибираємо числа (див. Нижче) з цього діапазону і додаємо нижню межу. У С це було б

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
Це не кодовий гольф, а найшвидший козел, тому мета - створити швидкий код для виконання завдання, а не найкоротший. У будь-якому випадку, вам не потрібно вибирати елементи з подібних аргументів, і ви можете визначити їх порядок, тому {⍵+⍺?⎕-⍵}повинно бути достатньо, коли підказка для верхньої межі, а правий аргумент - нижня межа
Уріель

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

У другому рядку буде проведено 200 тестів із 15 значеннями від 0 до 100, оскільки Scala виробляє швидкий байт-код, але потребує певного часу запуску. Таким чином, 200 починається з 15 значень від 0 до 100 вимагало б більше часу.

Зразок на одноядерному 2 ГГц:

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

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

Логіка:

Використовуючи вбудований випадковий та рекурсивно вибираючи числа в діапазоні (max-min), додаючи min та перевіряючи, чи розмір набору є очікуваним розміром.

Критика:

  • Це буде швидко для невеликих зразків великих діапазонів, але якщо завдання полягає в тому, щоб забрати майже всі елементи вибірки (999 чисел з 1000), він буде неодноразово підбирати числа, вже в наборі.
  • З питання, я не впевнений, чи потрібно мені санітувати проти невиконаних запитів, таких як Візьміть 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 повторень вибору 100 випадкових чисел між 1 і 10 000.


0

R, 31 або 40 байт (залежно від значення слова "діапазон")

Якщо вхід має 3 числа, a[1], a[2], a[3]а під «діапазоном» ви маєте на увазі «цілу послідовність від [2] до [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 Останнім часом я працюю з проблемами з великою пам’яттю, і рішення цього насправді… запустити його на кращій машині. Обмеження у формулюванні завдання стосуються процесора, а не пам’яті, так це… зловживання правилами? Ах, я дупа. Я подумав, що це кодове завдання для гольфу , але насправді це ... найшвидший код. Я програю здогадуюсь?
Андрей Костирка

0

Javascript (за допомогою зовнішньої бібліотеки) (64 байт / 104 байти ??)

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

Посилання на lib: https://github.com/mvegh1/Enumerable/

Пояснення коду: вираз лямбда приймає min, max, рахується як аргументи. Створіть колекцію розміру n та позначте кожен елемент на випадкове число, що відповідає критеріям min / max. Перетворити на рідний масив JS та повернути його. Я запускав це також на вході розміром 5 000 000, і після застосування чіткої трансформації все-таки було показано 5 000 000 елементів. Якщо буде узгоджено, що це недостатньо безпечно гарантії відмінності, я відповіду оновлю

Я включив деяку статистику до зображення нижче ...

введіть тут опис зображення

РЕДАКТУВАННЯ: На зображенні нижче зображено код / ​​продуктивність, яка гарантує, що кожен елемент буде чітким. Це набагато повільніше (6,65 секунди для 50 000 елементів) проти вихідного коду вище для тих же аргументів (0,012 секунд)

введіть тут опис зображення


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 () повертає як помилку порожній список у випадку f (n, a, b) з a> b. В інших випадках недійсного введення воно не запускається з одним повідомленням про помилку у вікні 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
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.