Question

So I'm writing some code that computes the cost of various public transport ticket options over a multi-segment journey. This boils down to computing the cost of each segment (map) and summing them together (reduce aka inject), which is simple enough:

total = journey.map {|segment| compute segment}.reduce(:+)

Problem is, sometimes compute returns nil because the segment is not possible for a given ticket, and then the sum operation blows up. In this case I want to return that nil instead, because a single bad segment means the whole journey is invalid. Currently I'm doing this:

fares = journey.map {|segment| compute segment}
total = if fares.include? nil
  nil
else
  fares.reduce(:+)
end

Which I can also write as the terse but (IMHO) less readable:

fares = journey.map {|segment| compute segment}
total = fares.reduce{|sum, n| (sum.nil? or n.nil?) ? nil : sum + n}

But there has to be a nicer way, right...?

Was it helpful?

Solution

The first "nicer" thing I can think of is:

journey.map { |s| compute(s) }.reduce(0) do |sum, fee| 
  break nil unless fee
  sum + fee
end

Update: This returns nil as soon as compute returns nil:

journey.reduce(0) do |sum, segment|
  fee = compute(segment)
  break nil unless fee
  sum + fee
end

OTHER TIPS

Do as below :

result = []
journey.take_while { |segment| !(result << compute(segment)).include?(nil) }
total = (result.size == journey.size)  ? result.inject(:+) : nil

You may use Array#take_while method :

Passes elements to the block until the block returns nil or false, then stops iterating and returns an array of all prior elements.

Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top