Случайная выборка без замены


10

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

Функция получит 3 параметра в любом порядке:

  1. Количество чисел в выходном наборе
  2. Нижний предел (включительно)
  3. Верхний предел (включительно)

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

судейство

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

  1. Время выполнения - протестировано на четырехъядерном компьютере с Windows 7 с любым свободно или легко доступным компилятором (при необходимости укажите ссылку)
  2. Надежность - обрабатывает ли функция угловые случаи или попадет в бесконечный цикл или выдаст неверные результаты - допустимо исключение или ошибка при неверном вводе
  3. Случайность - она ​​должна давать случайные результаты, которые трудно предсказать со случайным распределением. Использование встроенного генератора случайных чисел в порядке. Но не должно быть никаких явных предубеждений или очевидных предсказуемых закономерностей. Должен быть лучше, чем генератор случайных чисел, используемый бухгалтерией в Дилберте

Если он устойчивый и случайный, он сводится к времени выполнения. Неспособность быть устойчивым или случайным сильно вредит его положению.


Выход должен пройти что-то вроде тестов DIEHARD или TestU01 , или как вы будете оценивать его случайность? О, и должен ли код работать в 32- или 64-битном режиме? (Это будет иметь большое значение для оптимизации.)
Илмари Каронен

Я думаю, что TestU01 немного грубоват. Означает ли критерий 3 равномерное распределение? Кроме того, почему неповторяющееся требование? Это не особенно случайно, тогда.
Джои

@ Джои, конечно. Это случайная выборка без замены. Пока никто не утверждает, что разные позиции в списке являются независимыми случайными переменными, проблем нет.
Питер Тейлор

Ах, действительно. Но я не уверен, есть ли хорошо зарекомендовавшие себя библиотеки и инструменты для измерения случайности выборки :-)
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

Python 2.7

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

Не уверен, что вы стоите на использовании встроенных случайных методов, но все равно вы идете. хороший и короткий

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

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

тестовое задание:

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

Ruby> = 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 для лучшей производительности. Сложность этого алгоритма обсуждается в: Rouzankin, PS; Войтишек А.В. О стоимости алгоритмов случайного выбора. Применение методов Монте-Карло 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

Теухола, Дж. И Невалайнен, О. 1982. Два эффективных алгоритма для случайной выборки без замены. / IJCM /, 11 (2): 127–140. DOI: 10.1080 / 00207168208803304

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


1

APL, 18 22 байта

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

Объявляет анонимную функцию, которая принимает два аргумента и . число случайных чисел, которые вы хотите, это вектор, содержащий нижнюю и верхнюю границы, в этом порядке.

a?bвыбирает aслучайные числа от 0 до bзамены. Взяв ⍵[1]-⍵[0]мы получаем размер диапазона. Затем мы выбираем числа (см. Ниже) из этого диапазона и добавляем нижнюю границу. В C это будет

lower + rand() * (upper - lower)

раз без замены. Скобки не нужны, потому что APL работает справа налево.

Предполагая, что я правильно понял условия, это не соответствует критериям «робастности», потому что функция потерпит неудачу, если ей будут предоставлены неправильные аргументы (например, передача вектора вместо скаляра as ).

В случае, если это вектор, а не скаляр, 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
Это не кодовый гольф, а самый быстрый код, поэтому цель состоит в том, чтобы создать быстрый код для выполнения задачи, а не самый короткий. В любом случае, вам не нужно выбирать элементы из таких аргументов, и вы можете определить их порядок, поэтому этого {⍵+⍺?⎕-⍵}должно быть достаточно, когда подсказка для верхней границы, а правая аргумент для нижней
Uriel

0

Scala

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), он будет многократно выбирать числа, уже находящиеся в наборе.
  • Исходя из вопроса, я не уверен, должен ли я выполнять санитарную обработку против невыполненных запросов, таких как Take 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

р

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 до 10000.


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

Я весьма удивлен, почему предыдущий результат не был сыгран в гольф, учитывая встроенный образец с заменой оборудования! Мы создаем вектор, который удовлетворяет условию диапазона, и повторно выбираем его.

  • Надежность: угловые случаи (последовательности той же длины, что и диапазон выборки) обрабатываются по умолчанию.
  • Время выполнения: очень быстрое, потому что оно встроено.
  • Случайность: начальное число автоматически изменяется каждый раз, когда вызывается ГСЧ.

по крайней мере, на моей машине, 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, count в качестве аргументов. Создайте коллекцию размером n и отобразите каждый элемент на случайное число, соответствующее критерию мин / макс. Преобразовать в собственный массив JS и вернуть его. Я запустил это также на входе размером 5 000 000, и после применения отличного преобразования все еще показывал 5 000 000 элементов. Если будет достигнуто согласие о том, что это недостаточно безопасно для гарантии отличимости, я обновлю ответ

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

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

РЕДАКТИРОВАТЬ: ниже изображение показывает код / ​​производительность, которая гарантирует, что каждый элемент будет отличаться. Это намного медленнее (6,65 секунды для 50 000 элементов) по сравнению с исходным кодом выше для тех же аргументов (0,012 секунды)

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


0

K (ок) , 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.