Last Updated: November 18, 2020
·
3.568K
· janlelis

Array#sample and Array#shuffle using SecureRandom

Encourages you to use SecureRandom more often. Don't use srand!

USAGE

$ gem install securerandom-array
$ irb
>> require 'securerandom/array'
>> [1,2,3].secure_shuffle # e.g. [1,3,2]
>> [1,2,3].secure_sample # e.g. 2
>> [1,2,3].secure_sample(random: Random.new(42)) # => 2

CODE

require 'securerandom'

class Array
  # Has a similar API like #shuffle, but uses SecureRandom
  # Delegates to original #shuffle if a Hash argument is given
  def secure_shuffle(o={})
    raise ArgumentError, 'wrong number of arguments (1 for 0)' unless o.is_a?(Hash)
    if random = o[:random]
      shuffle(random: random)
    else
       old = dup
      [*1..size].reverse_each.with_object([]){ |i, res|
        res << old.delete_at(SecureRandom.random_number(i))
      }
    end
  end

  # Has a similar API like #sample, but uses SecureRandom
  # Delegates to original #sample if a Hash argument is given
  def secure_sample(n=:single, o={})
    o, n = n, :single if n.is_a?(Hash)
    raise ArgumentError, 'wrong number of arguments (2 for 1)' unless o.is_a?(Hash)
    if random = o[:random]
      args = [{random: random}]
      args.unshift(n) unless n == :single
      sample(*args)
    else
      if n == :single
        self[SecureRandom.random_number(size)]
      else
        raise ArgumentError, 'negative sample number' if n < 0
        res = []
        old = dup
        n = size if n > size
        [*(size-n+1)..size].reverse_each.with_object([]){ |i, res|
          res << old.delete_at(SecureRandom.random_number(i))
        }
      end
    end
  end

end

2 Responses
Add your response

Can you please publish this gem on Github?

over 1 year ago ·

Can't you just do:

[1,2,3].shuffle(random: SecureRandom)
over 1 year ago ·