10
\$\begingroup\$
def getRandomNumbers(count, min, max)
  array = []

    while array.length < count
      tmp = rand(max) + min
      if array.include?(tmp)
        next
      else
        array.push(tmp)
      end
    end

  return array
end


def prettyPrint(someArray)
  string = ""
  someArray.each do |item|
    string += item.to_s + "\t"
  end
  puts string
end

array = getRandomNumbers(6, 1, 49)
prettyPrint(array)

I'm just learning Ruby and wrote this little two functions just to play around and get a feel for Ruby syntax etc. I wanted a function that generates a given amount of random numbers within certain limits. In this example for 6 lottery numbers, therefore each number should only occur once. The second method is pretty self explanatory.

The code runs and does what he is supposed to do. However, what are nice tweaks and tricks to make this code nicer and utilizing the abilities of Ruby?

\$\endgroup\$

4 Answers 4

6
\$\begingroup\$

A min-max pair would be slightly better represented using a Range. In fact, starting with Ruby 1.9, rand can accept a range.

The loop can be written a bit more succinctly.

The pretty-printing function should also be written in one line instead.

lower_case is a more conventional naming convention. An explicit return is not necessary, as the code returns the last value anyway.

def get_random_numbers(range, count=1)
  array = []
  while array.length < count
    n = rand(range)
    array.push(n) unless array.include?(n)
  end
  array
end

def pretty_print(array)
  puts array.join("\t")
end

pretty_print(get_random_numbers(1..49, 6)
\$\endgroup\$
2
  • \$\begingroup\$ Great. Those are the things I was looking for. Wasn't aware of "unless" so far. However thank you, this is a good starting point to dive in more deeply in to the concepts of ruby. \$\endgroup\$
    – Frommel
    Feb 21, 2015 at 15:52
  • \$\begingroup\$ The naming convention is typically called snake_case not lower_case, see style guide \$\endgroup\$ Feb 21, 2015 at 19:00
4
\$\begingroup\$

A simplification

The Array class has the sample method, which does a lot of the work for you: it picks a number of items randomly from the array with no duplicates. It's perfect for your scenario.

def random_numbers(range, count=1)
  range.to_a.sample(count)
end

Notes:

  • I renamed the method using underscores, the preferred convention in Ruby. Also, prefixes like 'get' and 'set' are generally not used.
  • I used the range parameter that @200_success used, I like the way you call it.
  • I converted the range to an array to use the sample method.

I recommend becoming very familiar with all the methods in the Array and Enumerable classes, it will pay huge dividends in your Ruby code!

As for pretty-printing, I wouldn't even bother with a method, as I think this is clear enough:

puts random_numbers(1..49, 6).join "\t"

Alternative: Using a Class

I realize this was a learning exercise, but let's say this lottery picker was something being written professionally. In that case, I'd recommend modeling it with a class. There are many reasons to do this; it nicely encapsulates the logic and actions, it provides an interface for reuse (e.g. other people can use it), and it provides a place to put lottery-related code (in case you wanted to get fancy with your output, for example).

In Ruby, it is very easy to do. Here's a start:

class Lottery
  attr :numbers

  def initialize
    @numbers = (1..49).to_a
  end

  def pick(n=1)
    numbers.sample(n).sort
  end

  def pick_winner
    puts "The winning numbers are:"
    puts pick(6).join "\t"
    puts "And the powerball is: #{pick}"
  end
end

You would use it like this:

game = Lottery.new
game.pick 6 #=> [2, 6, 14, 25, 32, 45]
game.pick 6 #=> [10, 14, 22, 27, 34, 43]
game.pick_winner
The winning numbers are:
4       11      12      15      16      38
And the powerball is: [7]
\$\endgroup\$
10
  • 1
    \$\begingroup\$ Wow I'm stunned. So basically we got down to 2 lines of instructions. I begin to grasp the potential of ruby. :) \$\endgroup\$
    – Frommel
    Feb 21, 2015 at 23:02
  • 1
    \$\begingroup\$ Indeed! You would be surprised the number of things that can be boiled down to 2 lines. Developers coming from other languages are surprised to hear guidelines like methods should be no longer than 5 lines but it is not only possible, it is truly better: that conciseness often increases clarity. \$\endgroup\$ Feb 21, 2015 at 23:33
  • \$\begingroup\$ I added another option, using a class. \$\endgroup\$ Feb 21, 2015 at 23:35
  • \$\begingroup\$ I would expect the object to be stateful. In other words, successive calls to pick on the same object should not result in duplicates. Otherwise, there is no advantage over the simple function. \$\endgroup\$ Feb 21, 2015 at 23:38
  • \$\begingroup\$ Thank you very much. One question though just to be sure: in the method definition i can even declare a standard value for my parameters. Is that right? \$\endgroup\$
    – Frommel
    Feb 21, 2015 at 23:39
2
\$\begingroup\$

You don't actually need a loop because Ruby has built in functions that simplifies all this.

To grab count random UNIQUE numbers in range range, do:

def get_random_numbers(range, count=1)
    range.to_a.shuffle[0..count]
end

Example:

get_random_numbers(0..100,3) # => [34,82,4]
get_random_numbers(0..1000) # => [829]
\$\endgroup\$
3
  • \$\begingroup\$ As others have pointed out, range.to_a.sample(count) is more succinct but I'll keep this here as another viable option \$\endgroup\$ Feb 22, 2015 at 2:02
  • \$\begingroup\$ By the way, you don't need to pass a range into this function. Array has to_a defined as well so you can pass in [0,4,6,7],2 and it'll do pretty much what you expect \$\endgroup\$ Feb 22, 2015 at 2:56
  • 1
    \$\begingroup\$ This solution has one serious drawback: memory consumption. If you want to pick 10 numbers between 1 and 1,000,000 you have to create an Array with a million elements in it. This won't always be a problem, but it's good to keep in mind. \$\endgroup\$
    – britishtea
    Feb 22, 2015 at 19:06
1
\$\begingroup\$

Using Set instead of Array is faster and checks for presence for you:

require "set"

def get_random_numbers count, min, max
  set = Set.new
  set << rand(min..max) until set.size == count
  set.to_a
end
\$\endgroup\$
0

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge that you have read and understand our privacy policy and code of conduct.

Not the answer you're looking for? Browse other questions tagged or ask your own question.