Keeping it Small and Simple

2007.12.04

Parsing a list of numbers in Ruby

Filed under: Ruby Programming — Lorenzo E. Danielsson @ 22:45

I recently needed to be able to parse a comma-separated list of numbers. The numbers were to be put in an array, sorted and duplicates removed. As an additional quirk, the numbers could be ranges instead. All the following are examples of valid input:

4
1, 5, 7, 11
6-10, 1, 3-5

Additionally, the program shouldn’t be too picky about whitespaces, so a line like the following shouldn’t give the program any major problems.

3 , 5 -10 , 4 – 6, 1-9

Since I am not a programming fundamentalist I asked myself “what would be fun to use today?” and came up with “Ruby!” (for the record, I could equally well have said “Python”, “C” or maybe “Haskell”). So off we go.

Let’s start with the simplest possible scenario: the “list” consists of only a single number. In that case, we could possibly do something like the following:


1 #! /usr/bin/ruby -w
2
3 # Parse a number
4
5 if gets.chomp =~ /^\s*(\d+)\s*/
6   puts $1
7 else
8   puts "bad"
9 end

This is obviously not the only way to do it, and not necessarily even the best way to do it, but we shall not bother ourselves with such trivialities here. It works and at the end of the day that must count for something.

Try running the program and enter a single number and it will print it back. Type anything that isn’t a number, on the other hand, and the program will be rude. You started it after all.

That’s very cute, but we did want to parse lists of numbers. It doesn’t take much additional code to do just that.


 1 #! /usr/bin/ruby -w
 2
 3 list = []
 4 gets.chomp.split(/\s*,\s*/).each { |item|
 5   if item =~ /^\s*(\d+)\s*$/
 6     list << item.to_i
 7   else
 8     abort "Error: weird stuff in input: #{item}"
 9   end
10 }
11
12 list.sort.uniq.each { |num| puts num }

Since we are now dealing with a proper list of numbers, we sort and remove duplicates, before we output. Apart from that, it all looks fairly straight-forward. String#split seems ideal for extracting individual elements from the comma-separated list. Notice that the program still works fine even if you just type a single number.

Notice how simple it all still is. Well, at least as long as you have some basic understanding of regular expressions. But then again, if you didn’t you probably wouldn’t be programming in Ruby in the first place.

Now we are just left with one last thing to do, to handle ranges of numbers. It doesn’t require much additional code, and it’s not difficult to understand either.


 1 #! /usr/bin/ruby -w
 2
 3 list = []
 4 puts "Enter a list of numbers: "
 5 gets.chomp.split(/\s*,\s*/).each { |item|
 6   if item =~ /(\d+)\s*-\s*(\d+)/
 7     first, last = $1.to_i, $2.to_i
 8     first, last = last, first if first > last
 9     first.upto(last) { |rangeitem| list << rangeitem }
10   elsif item =~ /^\s*(\d+)\s*$/
11     list << item.to_i
12   else
13     abort "Weird stuff: #{item}"
14   end
15 }
16
17 list.sort.uniq.each { |num| puts num }
18

So there we are. We went from a simple program that parses a single number to a simple program that parses a list of numbers and finally an equally simple program that parses a list of numbers or ranges of numbers. Simple, eh?

If you are new to programming you may find yourself overwhelmed by the complexity of programming problems from time to time. In that case, try to break the task up into smaller parts. I think I am living proof that you don’t need to be exceptionally “brainy” to be a programmer. But you do need to develop certain skills. I can take a complex problem, break it into small, manageable parts and solve each one of those. That’s what I hope I have been able to demonstrate in this post.

Leave a Comment »

No comments yet.

RSS feed for comments on this post. TrackBack URI

Leave a comment

Blog at WordPress.com.