views:

165

answers:

3

This was a homework assignment for my students (I am a teaching assistant) in c and I am trying to learn Ruby, so I thought I would code it up. The goal is to read integers from a redirected file and print some simple information. The first line in the file is the number of elements, and then each integer resides on its own line.

This code works (although perhaps inefficiently), but how can I make the code more Ruby-like?

#!/usr/bin/ruby -w

# first line is number of inputs (Don't need it)
num_inputs = STDIN.gets.to_i

# read inputs as ints
h = Hash.new
STDIN.each do |n|
  n = n.to_i
  h[n] = 1 unless h[n] and h[n] += 1      
end

# find smallest mode
h.sort.each do |k,v|
  break puts "Mode is: #{k}", "\n" if v == h.values.max
end

# mode unique?
v = h.values.sort
print "Mode is unique: "
puts v.pop == v.pop, "\n"

# print number of singleton odds, 
#       odd elems repeated odd number times in desc order
#       even singletons in desc order
odd_once = 0
odd = Array.new
even = Array.new
h.each_pair do |k, v|
  odd_once += 1 if v == 1 and k.odd?
  odd << k if v.odd?
  even << k if v == 1 and k.even?
end
puts "Number of elements with an odd value that appear only once: #{odd_once}", "\n"
puts "Elements repeated an odd number of times:"
puts odd.sort.reverse, "\n"
puts "Elements with an even value that appear exactly once:"
puts even.sort.reverse, "\n"

# print fib numbers in the hash
class Fixnum
  def is_fib?
    l, h = 0, 1
    while h <= self
      return true if h == self
      l, h = h, l+h
    end
  end
end
puts "Fibonacci numbers:"
h.keys.sort.each do |n|
  puts n if n.is_fib?
end
+1  A: 

Here are some ideas on the first half...

STDIN.gets # just need to get past the first value - not needed, as already noticed

h = Hash.new(0)                    # use the default value argument to Hash#new, 
STDIN.each { |n| h[n.to_i] += 1 }  # so don't have to test for existence

# code seems to be seeking the largest, comment says "smallest"
# Hash#invert switches keys & values, works here if there's only one mode,
# otherwise presents one random key for the modal value
modal_value = h.values.max
mode = h.invert[modal_value]
puts "Mode is: #{mode}"

# OK, so mode may not be unique?
uniqueness = h.select { |k, v| v == modal_value}.size == 1 ? '' : 'NOT'
puts "Mode is #{uniqueness} unique"

#singleton odds ...
singleton_odd_count = h.select { |k,v| v == 1 && k % 2 == 1 }.map { |k, v| k }.size
# ...and evens
singleton_evens = h.select { |k,v| v == 1 && k % 2 == 0 }.map { |k, v| k }
# odd odd counts
odd_odd_count = h.select { |k,v| v % 2 == 1 && k % 2 == 1 }.map { |k, v| k }
Mike Woodhouse
Hey, another Ruby newbie here — I see an assignment to `modal_value`, but then references to `h_modal_value`. Is this just a typo, or is it Ruby magic? And if the latter, what *is* the magic?
Ben Blank
@Ben - no "magic", just a failed attempt to rename a variable in transcription from irb to the answer. Very mundane, really :-)
Mike Woodhouse
+3  A: 
Jörg W Mittag
how about `(1..self).any? { |i| return false if @@fibs[i] > self; @@fibs[i] == self }` ? With `do end` of course, instead of `{}`; typed that way because I don't know how to do line breaks in comment boxes.
Justin L.
Thank you for your thoughtful answer. I am assuming that you were trying to make every action as functional as possible ("Higher-order"?) It will take me some time to digest all this new info.
steadfastbuck
+1  A: 

I think your code is mostly fine. The only thing I can see that's inefficient at a glance is your Fibonacci checking -- you're recomputing the Fibonacci sequence every time, duplicating a lot of work. It'd make more sense to compute the array of Fibonacci numbers once, up to the maximum value in your input set, and then just check each value to see if it's in that array.

Also, you can simplify your hash initialization at the top by passing a value to Hash.new -- this will become the default value for any key it hasn't seen yet. So you could simply do:

h = Hash.new(0)
STDIN.each {|n| h[n.to_i] += 1}

Otherwise, no problems with the individual computations. What strikes me as "un-Rubylike" is simply having one spaghetti script that computes half a dozen different things and spits them back out. It'd be more elegant to wrap each of those different mathematical functions into a method, and put those methods into a module. And then your script can just include the module, call the methods with your hash, and output whatever it needs.

SFEley
Thank you, missed that one. Yes, the assignment was convoluted, but not redundant or important enough to throw everything into methods. :)
steadfastbuck
I thought you were doing it in Ruby for practice. You asked about the "Ruby way" to do this, and I answered. The Ruby way (the agile way in general, actually) is to break the problem down into smaller chunks that can be tested separately and can adapt to changes without a whole stack of Jenga blocks falling over. Outside of classroom exercises, don't ever assume a problem isn't "redundant or important enough" for good structure. The business world is full of crappy throwaway code that never quite dies or gets thrown away.
SFEley