Как мне создать список из n уникальных случайных чисел в Ruby?

Вот что у меня есть на данный момент:

myArray.map!{ rand(max) }

Однако очевидно, что иногда числа в списке не уникальны. Как я могу убедиться, что мой список содержит только уникальные номера, без необходимости создавать более крупный список, из которого я затем просто выбираю n уникальных номеров?

Редактировать:
Я бы очень хотел, чтобы это было сделано без цикла - если это вообще возможно.

К вашему сведению, мой ответ показывает шаблон, который работает без цикла

Sam Saffron 13.10.2008 03:11
Пошаговое руководство по созданию собственного Slackbot: От установки до развертывания
Пошаговое руководство по созданию собственного Slackbot: От установки до развертывания
Шаг 1: Создание приложения Slack Чтобы создать Slackbot, вам необходимо создать приложение Slack. Войдите в свою учетную запись Slack и перейдите на...
35
1
34 540
15
Перейти к ответу Данный вопрос помечен как решенный

Ответы 15

Вы можете использовать хеш для отслеживания случайных чисел, которые вы использовали до сих пор:

seen = {}
max = 100
(1..10).map { |n|
  x = rand(max)
  while (seen[x]) 
    x = rand(max)
  end
  x
}

Вместо того, чтобы добавлять элементы в список / массив, добавьте их в Set.

Ответ принят как подходящий

Это использует Set:

require 'set'

def rand_n(n, max)
    randoms = Set.new
    loop do
        randoms << rand(max)
        return randoms.to_a if randoms.size >= n
    end
end

Любой способ сделать это без цикла? Есть ли способ сделать это с помощью карты?

Esteban Araya 23.09.2008 09:10

Предполагая, что Ruby's Set не позволяет вставлять дубликаты, randoms будет менее случайным, чем rand(max), поскольку вы просто выбрасываете числа, которые «вам не нравятся».

Allen 30.06.2014 17:34

@ Аллен, это неверно. Алгоритм не выбрасывает числа «вам не нравятся». Примером этого может быть отбрасывание числа, потому что оно больше или меньше определенного значения. Он пропускает номер, если он уже был включен в набор, что является требованием варианта использования. Это не делает набор чисел менее случайным. Он останавливается, когда набирает достаточно номеров.

Tony 18.09.2017 18:33

@ Тони, ты прав, что я был неправ. Не уверен, что я курил в 2014 году.

Allen 18.09.2017 21:47

Вот одно из решений:

Предположим, вы хотите, чтобы эти случайные числа находились между r_min и r_max. Для каждого элемента в вашем списке сгенерируйте случайное число r и сделайте list[i]=list[i-1]+r. Это даст вам случайные числа, которые монотонно увеличиваются, что гарантирует уникальность при условии, что

  • r+list[i-1] не переливается
  • r> 0

Для первого элемента вы должны использовать r_min вместо list[i-1]. Как только вы закончите, вы можете перетасовать список, чтобы элементы не были так очевидны в порядке.

Единственная проблема с этим методом заключается в том, что вы переходите через r_max и все еще имеете дополнительные элементы для генерации. В этом случае вы можете сбросить r_min и r_max на 2 соседних элемента, которые вы уже вычислили, и просто повторить процесс. Это эффективно запускает тот же алгоритм в интервале, где еще не используются числа. Вы можете продолжать делать это, пока не заполните список.

(0..50).to_a.sort{ rand() - 0.5 }[0..x] 

(0..50).to_a можно заменить любым массивом. 0 - минимальное значение, 50 - максимальное значение. x - это «сколько значений я хочу получить»

конечно, невозможно, чтобы x было больше max-min :)

В расширении того, как это работает

(0..5).to_a  ==> [0,1,2,3,4,5]
[0,1,2,3,4,5].sort{ -1 }  ==>  [0, 1, 2, 4, 3, 5]  # constant
[0,1,2,3,4,5].sort{  1 }  ==>  [5, 3, 0, 4, 2, 1]  # constant
[0,1,2,3,4,5].sort{ rand() - 0.5 }   ==>  [1, 5, 0, 3, 4, 2 ]  # random
[1, 5, 0, 3, 4, 2 ][ 0..2 ]   ==>  [1, 5, 0 ]

Сноски:

Стоит отметить, что на момент первоначального ответа на этот вопрос, сентябрь 2008 г., Array#shuffle был либо недоступен, либо еще не известен мне, следовательно, приближение в Array#sort

И в результате к этому есть масса предлагаемых правок.

Так:

.sort{ rand() - 0.5 }

Может быть лучше и короче выражено в современных реализациях Ruby с использованием

.shuffle

Кроме того,

[0..x]

Более очевидно, можно записать Array#take как:

.take(x)

Таким образом, самый простой способ создать последовательность случайных чисел на современном рубине:

(0..50).to_a.shuffle.take(x)

разве не [0,1,2,3,4,5] .shuffle проще?

Federico 22.04.2012 17:30

хехехе :) Я должен был сказать @Federico. Молодец :))

Aleks 13.03.2014 18:52

Посмотрите на дату @Federico, у ruby ​​не было такого метода в начале 2008 года с 1.8.6 ruby-doc.org/core-1.8.6/Array.html, было добавлено в 1.8.7, насколько я могу судить. svn.ruby-lang.org/repos/ruby/tags/v1_8_7/ChangeLog

Kent Fredric 17.03.2014 11:26

(1.8.7, вероятно, в то время вышла, но я, вероятно, еще не обновился до нее или узнал о добавлении .shuffle)

Kent Fredric 17.03.2014 11:27

(0..50) .to_a.shuffle.take (x) молодец !!

Juanin 10.02.2015 11:38

Если у вас есть динамический верхний предел, этот метод может создавать очень большие массивы. Думаю, лучше бы использовать n.times.map { rand(lower_limit..upper_limit) }

Daniel Richter 05.03.2015 19:11

@DanielRichter, и такой подход, конечно же, не дает никаких гарантий уникальности.

Kent Fredric 05.03.2015 19:14

Если у вас есть конечный список возможных случайных чисел (например, от 1 до 100), то решение Кента подойдет.

В противном случае нет другого хорошего способа сделать это без цикла. Проблема в том, что вы ДОЛЖНЫ выполнить цикл, если у вас есть дубликат. Мое решение должно быть эффективным, а цикл не должен быть намного больше, чем размер вашего массива (т.е. если вам нужно 20 уникальных случайных чисел, это может занять в среднем 25 итераций). Хотя количество итераций становится тем хуже, чем больше чисел вы нужно, а меньший макс. Вот мой приведенный выше код, измененный, чтобы показать, сколько итераций необходимо для данного ввода:

require 'set'

def rand_n(n, max)
    randoms = Set.new
    i = 0
    loop do
        randoms << rand(max)
        break if randoms.size > n
        i += 1
    end
    puts "Took #{i} iterations for #{n} random numbers to a max of #{max}"
    return randoms.to_a
end

Я мог бы написать этот код, чтобы он больше походил на Array.map, если хотите :)

Насколько приятно заранее знать максимальное значение, вы можете сделать это следующим образом:

class NoLoopRand
  def initialize(max)
    @deck = (0..max).to_a
  end

  def getrnd
    return @deck.delete_at(rand(@deck.length - 1))
  end
end

и вы можете получить случайные данные таким образом:

aRndNum = NoLoopRand.new(10)
puts aRndNum.getrnd

вы получите nil, когда все значения будут исчерпаны из колоды.

Чтобы дать вам представление о скорости, я выполнил четыре версии этого:

  1. Использование наборов, как предложение Райана.
  2. Используя массив немного большего размера, чем необходимо, затем выполните uniq! в конце.
  3. Использование хэша, как предложил Кайл.
  4. Создание массива необходимого размера, а затем его случайная сортировка, как предлагает Кент (но без постороннего «- 0,5», который ничего не делает).

Все они быстры в небольших масштабах, поэтому я попросил их создать список из 1 000 000 номеров. Вот время в секундах:

  1. Подходит: 628
  2. Массив + uniq: 629
  3. Хэш: 645
  4. фиксированный массив + сортировка: 8

И нет, это не опечатка. Итак, если вам важна скорость и это нормально, если числа должны быть целыми числами от 0 до любого другого, то мой точный код был:

a = (0...1000000).sort_by{rand}

Регистр сдвига с линейной обратной связью должен завершиться менее чем за секунду.

Bryan Larsen 15.12.2011 17:47

`посторонний" - 0,5 ", который ничего не делает` Пробовали? Без - 0.5 rand () всегда возвращает> 0, а когда> 0, a всегда меньше, чем b, а затем вместо перетасовки ... вы просто получаете список в том порядке, в котором их сравнивал базовый алгоритм. Что в некоторых случаях возвращает список в указанном порядке. (0..10).to­_a.sort{ Rando­m.rand() } на tryruby.org/levels/1/challenges/0 дает мне вход == выход. Итак, вам нужно ДЕЙСТВИТЕЛЬНО - 0,5 или сам случайный ничего не делает

Kent Fredric 27.11.2014 11:50

Кроме того, вы действительно отвечаете на крошечные отступления в SO-ветке через шесть лет после того, как это произошло ?!

glenn mcdonald 06.12.2014 07:40

Как насчет того, чтобы поиграть на этом? Уникальные случайные числа без использования Set или Hash.

x = 0
(1..100).map{|iter| x += rand(100)}.shuffle

Мне почему-то кажется, что эти числа будут значительно менее случайными, чем при выборе 100 уникальных чисел из диапазона от 0 до 10000.

Claudiu 22.10.2008 04:32

yerp, его нужно улучшить, чем выше число, тем меньше шансов, что вы его получите. Но, безусловно, есть способ заставить что-то в этом роде работать.

Sam Saffron 23.10.2008 06:03

Да, это можно сделать без цикла и без отслеживания выбранных номеров. Это называется регистром сдвига с линейной обратной связью: Создать последовательность случайных чисел без повторов

Основываясь на решении Кента Фредрика, приведенном выше, я в конечном итоге использовал следующее:

def n_unique_rand(number_to_generate, rand_upper_limit)
  return (0..rand_upper_limit - 1).sort_by{rand}[0..number_to_generate - 1]
end

Спасибо, Кент.

Ruby 1.9 предлагает метод Array # sample, который возвращает элемент или элементы, случайно выбранные из массива. Результаты #sample не будут включать один и тот же элемент массива дважды.

(1..999).to_a.sample 5 # => [389, 30, 326, 946, 746]

По сравнению с подходом to_a.sort_by, метод sample оказывается значительно быстрее. В простом сценарии я сравнил sort_by с sample и получил следующие результаты.

require 'benchmark'
range = 0...1000000
how_many = 5

Benchmark.realtime do
  range.to_a.sample(how_many)
end
=> 0.081083

Benchmark.realtime do
  (range).sort_by{rand}[0...how_many]
end
=> 2.907445

Есть идеи относительно сроков по сравнению с тем, что сообщает гленн макдональд?

Hedgehog 09.06.2012 05:47

Способ 1

Используя подход Кента, можно сгенерировать массив произвольной длины, сохраняя все значения в ограниченном диапазоне:

# Generates a random array of length n.
#
# @param n     length of the desired array
# @param lower minimum number in the array
# @param upper maximum number in the array
def ary_rand(n, lower, upper)
    values_set = (lower..upper).to_a
    repetition = n/(upper-lower+1) + 1
    (values_set*repetition).sample n
end

Способ 2

Другой, возможно более эффективным, метод, модифицированный из другой ответ того же Кента:

def ary_rand2(n, lower, upper)
    v = (lower..upper).to_a
    (0...n).map{ v[rand(v.length)] }
end

Выход

puts (ary_rand 5, 0, 9).to_s # [0, 8, 2, 5, 6] expected
puts (ary_rand 5, 0, 9).to_s # [7, 8, 2, 4, 3] different result for same params
puts (ary_rand 5, 0, 1).to_s # [0, 0, 1, 0, 1] repeated values from limited range
puts (ary_rand 5, 9, 0).to_s # []              no such range :)

Никаких циклов с этим методом

Array.new(size) { rand(max) }

require 'benchmark'
max = 1000000
size = 5
Benchmark.realtime do
  Array.new(size) { rand(max) }
end

=> 1.9114e-05 

[*1..99].sample(4) #=> [64, 99, 29, 49]

Согласно документам Array#sample,

The elements are chosen by using random and unique indices

Если вам нужен SecureRandom (который использует компьютерный шум вместо псевдослучайных чисел):

require 'securerandom'

[*1..99].sample(4, random: SecureRandom) #=> [2, 75, 95, 37]

Другие вопросы по теме