例如,假设我有以下范围
5031..5032 6248..6249
如何在这两个范围内生成随机数?
答案 0 :(得分:5)
简单的方法:
(r1.to_a + r2.to_a).choice
更快,更节省内存的通用解决方案将涉及计算总范围大小,生成随机数,然后将数字标准化到它所属的范围。
更新:好的,明白了。此解决方案适用于任意数量的范围,并且不生成巨型数组或迭代通过范围本身。 (它在 Ranges 数组上迭代两次,而不是Range元素数组。)
def rangerand *r
r.inject(rand(
r.inject(0) do |accum, rng|
accum + rng.last - rng.first + 1
end
)) do |accum, rng|
rngsize = rng.last - rng.first + 1
return rng.first + accum if accum < rngsize
accum - rngsize
end
# raise 'this "cannot happen"'
end
puts rangerand 1..3, 999..1001, 10_200..10_205
嘿,我之前从未在同一个表达中使用过两个#inject
。
答案 1 :(得分:4)
对于任意数量的范围
total
。rnd
和0
之间生成随机数total - 1
。rnd
减去可能的范围(直到rnd
为非负数)。rnd
- 当前范围内的数字。答案 2 :(得分:3)
我编写了这段代码,现在看看Nikita的答案,我认为这或多或少是他的想法(我使用r.end
/ r.begin
,因为Ruby范围没有预先计算的长度属性)
ranges = [1..10, 21..40, 81..120]
counts = ranges.map { |r| r.end - r.begin + (r.exclude_end? ? 0 : 1) }
random = rand(counts.inject(&:+))
idx, remainder = counts.inject([0, random]) do |(idx, remainder), count|
if remainder < count
[idx, remainder] # break here if you care more for efficiency than for functional purity
else
[idx+1, remainder - count]
end
end
p ranges[idx].begin + remainder
当然,如果我的合理长度的范围,我只是使用已经讨论过的其他解决方案。
答案 3 :(得分:1)
将范围添加到数组并将其展平以获得一系列数字,然后调用rand
以查找此数组的特定元素。
range_1 = 1..10
range_2 = 2..30
range_array = [range_1.to_a, range_2.to_a].flatten
range_array[rand(range_array.length)]
答案 4 :(得分:1)
这就是我提出的:
require 'benchmark'
class RangeRandom
def initialize(ranges=[])
# sanity checks need to go here for things like:
# backward/inverted ranges: (0..-1)
# range overlaps: (0..2 & 1..3)
# could combine ranges, or raise an exception
#
# Restrict to ranges that pass ".inclusive?"?
# the end value is informative only
@prepped_ranges = ranges.map{ |r| [ r.begin, r.end - r.begin, r.end ] }
end
def rand
range = @prepped_ranges[ Kernel::rand( @prepped_ranges.size ) ]
range[0] + Kernel::rand( range[1] )
end
end
一些测试:
range_random = RangeRandom.new([0..10, 90..100])
5.times do
puts range_random.rand
end
puts
# >> 94
# >> 97
# >> 92
# >> 92
# >> 8
n = 500_000
Benchmark.bm(7) do |x|
x.report('rand1:') { n.times do ; r = RangeRandom.new([ 0..1 ]); r.rand; end }
x.report('rand2:') { n.times do ; r = RangeRandom.new([ 0..1_000_000 ]); r.rand; end }
x.report('rand3:') { n.times do ; r = RangeRandom.new([ 0..1_000_000_000 ]); r.rand; end }
x.report('rand4:') { n.times do ; r = RangeRandom.new([ 0..1 ]); r.rand; end }
x.report('rand5:') { n.times do ; r = RangeRandom.new([ 0..1, 2..3, 4..5, 6..7 ]); r.rand; end }
x.report('rand6:') { r = RangeRandom.new([ 0..1 ]) ; n.times do ; r.rand; end }
x.report('rand7:') { r = RangeRandom.new([ 0..1_000_000_000 ]) ; n.times do ; r.rand; end }
end
500,000次迭代的总运行时间:
# >> user system total real
# >> rand1: 2.220000 0.000000 2.220000 ( 2.224894)
# >> rand2: 2.250000 0.010000 2.260000 ( 2.254730)
# >> rand3: 2.250000 0.000000 2.250000 ( 2.247406)
初始化500,000次迭代的多个范围的时间:
# >> rand4: 2.220000 0.000000 2.220000 ( 2.222983)
# >> rand5: 4.340000 0.000000 4.340000 ( 4.337312)
单次初始化的时间,然后是500,000次迭代的小范围与大范围:
# >> rand6: 0.560000 0.000000 0.560000 ( 0.559673)
# >> rand7: 0.580000 0.000000 0.580000 ( 0.584331)
使用大范围时,时间会略微增加,但可能会因系统活动而出现变化。初始化数组中的多个范围会产生更大的影响,这正是我所期望的。
答案 5 :(得分:0)
对于宽范围 range.to_a 不是一个很好的解决方案。您可以创建不必要的许多大数组。最快的解决方案可能如下所示:
ruby-1.9.2-head > from, to = 1, 50000000
=> [1, 50000000]
ruby-1.9.2-head > from + rand(to-from+1)
=> 20698596
ruby-1.9.2-head > from + rand(to-from+1)
=> 15143263
ruby-1.9.2-head > from + rand(to-from+1)
=> 18469491
Ranges的类似解决方案几乎与 range.to_a 一样慢:
ruby-1.9.2-head > range1 = 1..5000000
=> 50..50000
ruby-1.9.2-head > range1.begin + rand(range1.count) # TAKES ABOUT 1 SECOND!
=> 1788170