HiveBrain v1.2.0
Get Started
← Back to all entries
patternrubyMinor

Solution to Max Range Sum challenge on CodeEval

Submitted by: @import:stackexchange-codereview··
0
Viewed 0 times
codeevalchallengerangemaxsumsolution

Problem

The challenge in CodeEval is to take a number of days and iterate over the gains and losses and determine which lengths will yield the highest possible profit. I'm wondering if I could write this any better? It seems like sort of clunky code to me.

file = ARGV[0]

File.open(file).each do |line|

    duration, days = line.chomp.split(";")
    days = days.split(" ").map! { |num| num = num.to_i }
    duration = duration.to_i - 1

    profit = 0
    max_profit = 0

    days[0..(days.length - duration)].each_with_index do |day, index|

        days[index..(index + duration)].inject(0) do |change, total|
            profit = change + total
        end

        max_profit = profit if profit > max_profit
    end

    puts max_profit

end

Solution

Just for clarity, I'd split the code into a couple of methods. One to parse input, and one to find the best yield.

To tackle the latter first: Have a look at Enumerable#each_cons, and Enumerable#reduce (aka inject).

Given an array of gains/losses (values) and the length to examine (days), you can find the "best streak" like so:

values.each_cons(days).map { |streak| streak.reduce(&:+) }.max


In English: Take each consecutive "streak" of days values; map each of these streaks to its sum; take the maximum of these sums. As an example:

days = 3
values = [1, 2, -1, 3, 4]

streaks = values.each_cons(days).to_a              # => [ [1, 2, -1], [2, -1, 3], [-1, 3, 4] ]
sums = streaks.map { |streak| streak.reduce(&:+) } # => [2, 4, 6]
max = sums.max                                     # => 6


There are two other things to handle, to solve the task: Return zero if the best gain is actually a loss, or if there are not enough values to satisfy days.

In all, you might do something like:

def best_streak(days, values)
  return 0 if values.count  0 ? gain : 0
end


As for parsing the input, your current code is ok, though I'd still handle it separately.

def parse_line(line)
  days, *values = line.strip.split(/[;\s]/)
  [days.to_i, values.map(&:to_i)]
end


This basically just splits the line in to its parts, treating the first number as the number of days, and the rest as the values.

To put it all together, you can do something like:

File.open(ARGV[0]).each do |line|
  days, values = parse_line(line)
  puts best_streak(days, values)
end


However, it seem that in the CodeEval context, input is passed on STDIN; not in a file. So really it should probably be:

STDIN.each_line do |line|
  days, values = parse_line(line)
  puts best_streak(days, values)
end


There may be a more efficient way to do all this, but this is probably the most readable.

Code Snippets

values.each_cons(days).map { |streak| streak.reduce(&:+) }.max
days = 3
values = [1, 2, -1, 3, 4]

streaks = values.each_cons(days).to_a              # => [ [1, 2, -1], [2, -1, 3], [-1, 3, 4] ]
sums = streaks.map { |streak| streak.reduce(&:+) } # => [2, 4, 6]
max = sums.max                                     # => 6
def best_streak(days, values)
  return 0 if values.count < days
  gain = values.each_cons(days).map { |streak| streak.reduce(&:+) }.max
  gain > 0 ? gain : 0
end
def parse_line(line)
  days, *values = line.strip.split(/[;\s]/)
  [days.to_i, values.map(&:to_i)]
end
File.open(ARGV[0]).each do |line|
  days, values = parse_line(line)
  puts best_streak(days, values)
end

Context

StackExchange Code Review Q#83434, answer score: 3

Revisions (0)

No revisions yet.