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\$
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)
| improve this answer | |
\$\endgroup\$
  • \$\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 '15 at 15:52
  • \$\begingroup\$ The naming convention is typically called snake_case not lower_case, see style guide \$\endgroup\$ – Devon Parsons Feb 21 '15 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]
| improve this answer | |
\$\endgroup\$
  • 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 '15 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\$ – Mark Thomas Feb 21 '15 at 23:33
  • \$\begingroup\$ I added another option, using a class. \$\endgroup\$ – Mark Thomas Feb 21 '15 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\$ – 200_success Feb 21 '15 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 '15 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]
| improve this answer | |
\$\endgroup\$
  • \$\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\$ – Devon Parsons Feb 22 '15 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\$ – Devon Parsons Feb 22 '15 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 '15 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
| improve this answer | |
\$\endgroup\$

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

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