Skip to main content
edited tags; edited title
Link
200_success
  • 145.7k
  • 22
  • 191
  • 481

Counting words / lines in Ruby - more compact / idiomatic way?

Tweeted twitter.com/#!/StackCodeReview/status/433640244559511553
Source Link

Counting words / lines in Ruby - more compact / idiomatic way?

I solved this problem in Ruby:

Write an utility that takes 3 command-line parameters P1, P2 and P3. P3 is OPTIONAL (see below) P1 is always a file path/name. P2 can take the values:

  • “lines”
  • “words”
  • “find”

Only P2 is “find”, then P3 is relevant/needed, otherwise it is not.

So, the utility does the following:

  • If P2 is “rows” it says how many lines it has
  • If P2 is “words” it says how many words it has (the complete file)
  • If P2 is “find” it prints out the lines where P3 is present

My solution looks like this:

#!/usr/bin/env ruby

def print_usage
  puts "Usage: #{$0} <file> words|lines"
  puts "       #{$0} <file> find <what-to-find>"
end

class LineCounter
  # Initialize instance variables
  def initialize
    @line_count = 0
  end
  def process(line)
    @line_count += 1
  end
  def print_result
    puts "#{@line_count} lines"
  end
end

class WordCounter
  # Initialize instance variables
  def initialize
    @word_count = 0
  end
  def process(line)
    @word_count += line.scan(/\w+/).size
  end
  def print_result
    puts "#{@word_count} words"
  end
end

class WordMatcher
  # Initialize instance variables, using constructor parameter
  def initialize(word_to_find)
    @matches = []
    @word_to_find = word_to_find
  end
  def process(line)
    if line.scan(/#{@word_to_find}/).size > 0   
      @matches << line
    end
  end
  def print_result
    @matches.each { |line|
      puts line
    }
  end   
end

# Main program
if __FILE__ == $PROGRAM_NAME

  processor = nil

  # Try to find a line-processor
  if ARGV.length == 2
    if ARGV[1] == "lines"
      processor = LineCounter.new
    elsif ARGV[1] == "words"
      processor = WordCounter.new
    end
  elsif ARGV.length == 3 && ARGV[1] == "find"
    word_to_find = ARGV[2]
    processor = WordMatcher.new(word_to_find)
  end

  if not processor
    # Print usage and exit if no processor found
    print_usage
    exit 1
  else
    # Process the lines and print result
    File.readlines(ARGV[0]).each { |line|
      processor.process(line)
    }
    processor.print_result
  end

end

My questions are:

  • Is there a more Ruby-esque way of solving it?
  • More compact, but still readable / elegant?

It seems checking for correct command-line parameter combinations takes up a lot of space...

Contrast it to the Scala version found here:

https://gist.github.com/anonymous/93a975cb7aba6dae5a91#file-counting-scala