Tuesday 18 November 2014

Weekend code puzzle: my answer (Ruby version)

G'day:
I'll move on from this topic soon, rest assured. Having come up with a CFML answer for my code puzzle question ("Weekend code puzzle: my answer (CFML version)"), I decided to stretch myself (albeit slightly) and work out how to do it in Ruby too.

For the sake of completeness, here it is.


# getSubseries.rb

def getSubseries(series, threshold)
    working = []
    series.inject [] do |reduction, current|
        working.push current
        working.shift while working.inject(0, &:+) > threshold

        workingIsBetterForLength    = working.length > reduction.length
        workingIsBetterForTotal        = working.length == reduction.length && working.inject(0, &:+) > reduction.inject(0, &:+)
        
        workingIsBetterForLength || workingIsBetterForTotal ? working.clone : reduction
    end
end

This is the same logic as the CFML one. Observations:


  • the reduction / folding operation on Ruby is called inject for some reason.
  • this indecipherable mess - working.inject(0, &:+) - is shorthand for this:

    array.inject{|sum,x| sum + x }
    

    IE: the Ruby equivalent of .sum(). Ruby has no inbuilt method for summing arrays.

    I have no idea what it means (and, lazily, I have not checked yet), or how that works.
  • Of my own code, I like this version the best. It's clearer code IMO.
  • I like how Ruby method calls don't need parentheses, too.
I don't have a testing framework installed for Ruby, so I just did the tests for this by hand. These are the same tests as done for PHP and CFML:

# getSubseriesTest.rb

load "getSubseries.rb"

result = getSubseries [], 0
puts "Should return an array\n" unless result.is_a? Array

result = getSubseries [100], 100
puts "Should return elements if there are any within the threshold\n" unless result == [100]

result = getSubseries [100,100], 500
puts "Should return a multi-element subseries\n" unless result == [100,100]

result = getSubseries [100,100,100,100,100,100], 500
puts "Total of elements should not be greater than threshold\n" unless result.inject(0){|sum,x| sum + x } <= 500

result = getSubseries [600,100,100,100,600,100,100,100,100,600], 500
puts "Should return a subsequent larger subseries\n" unless result == [100,100,100,100]

result = getSubseries [600,100,100,100,600,200,100,100,100,100,100,600], 500
puts "Should return a longer adjacent subseries\n" unless result == [100,100,100,100,100]

result = getSubseries [600,700,800,900], 500
puts "Should work when threshold is less than all items\n" unless result == []

result = getSubseries [600,700,800,900], 1000
puts "Should work when threshold is greater than all items\n" unless result == [900]

result = getSubseries [600,700,800,900], 5000
puts "Should work when threshold is greater than sum of all items\n" unless result == [600,700,800,900]

result = getSubseries [100,50,50,50,50,50,500,100,60,60,60,60,60,500], 500
unless result == [100,60,60,60,60,60] then
    puts "Should return subseries with highest tally when there are two equal-length subseries (second subseries is higher)\n" 
end

result = getSubseries [100,60,60,60,60,60,500,100,50,50,50,50,50,500], 500
unless result == [100,60,60,60,60,60]
    puts "Should return subseries with highest tally when there are two equal-length subseries (first subseries is higher)\n"
end

That's quite nice-looking code too.

Anyway, that's all I have to say on that. I had better crack on with some work.

If you know Ruby and see how I've messed things up, or could have done better: please let me know! I'm definitely a newbie when it comes to Ruby.

--
Adam