Home > Back-end >  How to pass arguments from an Array#sample! method to Array#sample
How to pass arguments from an Array#sample! method to Array#sample

Time:12-09

I am monkey patching the Array class to add an Array#sample! method:

  • Array#sample! should be a destructive version of Array#sample that removes the returned elements from the array.

  • Array#sample! should take the same arguments as Array#sample.

However, I fail at passing the arguments correctly. Here is what I am trying:

class Array
  def sample!(n = nil, **args)
    if n
      self.sample(n, args).map { |e| self.delete(e) }
    else
      self.delete(self.sample(args))
    end
  end
end

a = (1..10).to_a

p a.sample
p a.sample(4)
p a.sample(random: Random.new(1))
p a.sample(4, random: Random.new(1))

p a.sample!
p a.sample!(4)
p a.sample!(random: Random.new(1))
p a.sample!(4, random: Random.new(1))

And it fails with an no implicit conversion of Hash into Integer message :o(

CodePudding user response:

Let's start with the argument passing. You can either do it like this: (the splat (*) effectively omits the positional argument if n is nil)

class Array
  def my_sample(n = nil, **kwargs)
    sample(*n, **kwargs)
  end
end

[1, 2, 3].my_sample #=> 3
[1, 2, 3].my_sample(2) #=> [1, 3]
[1, 2, 3].my_sample(random: Random.new(1)) #=> 2
[1, 2, 3].my_sample(2, random: Random.new(1)) #=> [2, 3]

Or you could use a conditional:

class Array
  def my_sample(n = nil, **kwargs)
    if n
      sample(n, **kwargs)
    else
      sample(**kwargs)
    end
  end
end

Implementing a sample! method that works just like sample but which also removes those element(s) might be a little more complex than your approach.

Since arrays can contain the same element multiple times, you have to take special care to remove the correct ones. (you cant just remove all duplicates, neither an arbitrary one)

Something like this would probably work:

class Array
  def sample!(n = nil, **kwargs)
    indices = (0...size).to_a
    if n
      samples = indices.sample(n, **kwargs)
      remaining = indices - samples
      return_value = values_at(*samples)
    else
      sample = indices.sample(**kwargs)
      remaining = indices - [sample]
      return_value = at(sample)
    end
    replace(values_at(*remaining))
    return_value
  end
end

Here, I'm creating an array of indices (which are guaranteed to be unique) and sample from that array. I then determine the remaining indices (which is simply the difference), set the return value and adjust the array content.

You could unify both code paths but that makes the code a little harder to read.

  •  Tags:  
  • ruby
  • Related