Расчет
mx = 10
n = 20
(0..(mx+1)**2-1).to_a.sample(n).map { |n| n.divmod(mx+1) }
#=> [[6, 9], [3, 8], [7, 10], [3, 3], [2, 0], [8, 9], [4, 1], [9, 4],
# [1, 0], [1, 8], [9, 6], [0, 10], [9, 0], [6, 8], [4, 9], [2, 10],
# [10, 0], [10, 5], [6, 10], [2, 9]]
Пояснение
Выборка пар чисел без замены аналогична выборке отдельных чисел без замены, когда между двумя есть карта 1-1.Думайте об одиночных числах как о двухзначных числах в базе mx+1
, поэтому каждая цифра может находиться в диапазоне от 0
до mx
, то есть соответствует одному элементу пары чисел, которые выбираются.Существуют (mx+1)**2
двузначные базовые mx+1
числа, которые в базе 10
варьируются от 0
до (mx+1)**2-1
.Поэтому нам нужно всего лишь n
раз от (0..(mx+1)**2-1).to_a
, а затем использовать Integer # divmod , чтобы преобразовать каждое выбранное десятичное число обратно в две цифры из основного mx+1
числа (в базе 10
).
Процедура явно объективна.
Альтернативный метод: генерировать пары и отбрасывать дубликаты
Если (mx+1)**2-1)
достаточно велико по отношению к n
, самый быстрый способ вполне может быть следующим (который также производит несмещенные сэмплы):
require 'set'
samp = Set.new
limit = mx+1
while samp.size < n
samp << [rand(limit), rand(limit)]
end
samp.to_a
#=> [[3, 6], [6, 2], [0, 3], [10, 0], [1, 8], [3, 4], [10, 3], [0, 4],
# [6, 7], [10, 7], [9, 1], [10, 5], [2, 7], [4, 8], [8, 4], [7, 3],
# [2, 4], [7, 10], [5, 3], [6, 3]]
Я обнаружил, что при рисовании случайных сэмплов 20
пар 100
раз (все для mx = 10
), в среднем было сформировано 21.86
пар, чтобы после удаления дубликатов было оставлено 20 уникальных пар.
Тесты
Я думал, что это может бытьИнтересно сравнить несколько предложенных методов.
require 'benchmark'
require 'set'
Удобно поместить тестируемые методы в модуль 1 .
module Candidates
def samp_with_difmod(mx, n)
(0..(mx+1)**2-1).to_a.sample(n).map { |n| n.divmod(mx+1) }
end
def discard_dups(mx, n)
samp = Set.new
limit = mx+1
while samp.size < n
samp << [rand(limit), rand(limit)]
end
samp.to_a
end
def sawa_repeated_perm(mx, n)
(0..mx).to_a.repeated_permutation(2).to_a.sample(n)
end
def sawa_product(mx, n)
(0..mx).to_a.product((0..mx).to_a).sample(n)
end
end
include Candidates
@candidates = Candidates.public_instance_methods(false)
#=> [:samp_with_difmod, :discard_dups, :sawa_repeated_perm, :sawa_product]
@indent = candidates.map { |m| m.to_s.size }.max
#=> 18
def bench(mx, n, reps)
puts "\n0-#{mx}, sample size = #{n}, #{reps} reps"
Benchmark.bm(@indent) do |bm|
@candidates.each do |m|
bm.report m.to_s do
reps.times { send(m, mx, n) }
end
end
end
end
bench(10, 20, 100)
0-10, sample size = 20, 100 reps
user system total real
samp_with_difmod 0.000000 0.000000 0.000000 ( 0.002536)
discard_dups 0.000000 0.000000 0.000000 ( 0.005312)
sawa_repeated_perm 0.000000 0.000000 0.000000 ( 0.004901)
sawa_product 0.000000 0.000000 0.000000 ( 0.004742)
bench(100, 20, 100)
0-100, sample size = 20, 100 reps
user system total real
samp_with_difmod 0.031250 0.015625 0.046875 ( 0.088003)
discard_dups 0.000000 0.000000 0.000000 ( 0.005618)
sawa_repeated_perm 0.093750 0.000000 0.093750 ( 0.136010)
sawa_product 0.125000 0.000000 0.125000 ( 0.138848)
bench(10, 121, 100)
0-10, sample size = 121, 100 reps
user system total real
samp_with_difmod 0.000000 0.000000 0.000000 ( 0.003283)
discard_dups 0.171875 0.015625 0.187500 ( 0.208459)
sawa_repeated_perm 0.000000 0.000000 0.000000 ( 0.004253)
sawa_product 0.000000 0.000000 0.000000 ( 0.002947)
В приведенном выше примере выборка производится из населения 11**2 #=> 121
.Получение выборки 121
без замены из совокупности 121
означает, что выборка состоит из всех пар в совокупности.Поэтому неудивительно, что discard_dups
работает относительно плохо.Например, набрав 120 уникальных пар, он будет непрерывно отклонять дубликаты, пока не наткнется на оставшуюся пару, не попавшую в образец.
bench(100, 100, 100)
0-100, sample size = 100, 100 reps
user system total real
samp_with_difmod 0.046875 0.000000 0.046875 ( 0.042177)
discard_dups 0.031250 0.000000 0.031250 ( 0.029467)
sawa_repeated_perm 0.109375 0.000000 0.109375 ( 0.132429)
sawa_product 0.125000 0.000000 0.125000 ( 0.140451)
bench(1000, 500, 10)
0-1000, sample size = 500, 10 reps
user system total real
samp_with_difmod 0.437500 0.140625 0.578125 ( 0.632434)
discard_dups 0.015625 0.000000 0.015625 ( 0.013634)
sawa_repeated_perm 1.718750 0.359375 2.078125 ( 2.166724)
sawa_product 1.734375 0.062500 1.796875 ( 1.853555)
В этом последнем тесте, где максимальное значение (1000
) является большим и большим по отношению к размеру выборки (500
), discard_dups
является явным победителем.Здесь размер выборочного пространства равен 1001**2 #=> 1_002_001
, поэтому discard_dups
может столкнуться с относительно небольшим количеством дубликатов при рисовании выборки размером 500
.
sawa_product
работает немного лучше, чем sawa_repeated_perm
, нов других тестах производительность обоих методов одинакова.
1 Включение модуля, содержащего тестируемые методы, упрощает код и упрощает добавление, удаление и переименование тестируемых методов.