How do I elegantly return 1.0 if the value returned is 0, in 1 line?

Viewed 90

I have this statement:

> zv.sum(:volume).to_f || 1.0
   (0.5ms)  SELECT SUM("positions"."volume") FROM "positions" WHERE "positions"."volume" = $1  [["volume", 0]]
=> 0.0

What I would like to happen is if zv.sum(:volume) returns 0.0, I want it to return 1.0 instead.

I know I could do a verbose if/unless statement, but I would like to do it elegantly in 1 line above.

How do I do that?

3 Answers

You literally have the solution in your title and in your question (bold emphasis mine):

How do I elegantly return 1.0 if the value returned is 0, in 1 line?

What I would like to happen is if zv.sum(:volume) returns 0.0, I want it to return 1.0 instead.

So, the best way would be to use a conditional expression:

sum = zv.sum(:volume).to_f

if sum == 0.0
  1.0
else
  sum
end

If you want to "optimize" this, you could move the conversion to float to the very edge of the expression:

sum = zv.sum(:volume)

if sum == 0
  1
else
  sum
end.to_f

[Note, this assumes that the value of zv.sum(:volume) will be a number and not something like nil or a string representing a number. If that is guaranteed, this same transformation can also be applied to every single one of the following examples.]

The requirement to do something in 1 line is always trivially achievable in Ruby, since line breaks are always optional. For example:

sum = zv.sum(:volume).to_f; if sum == 0.0 then 1.0 else sum end

Or if you want to avoid using keywords:

sum = zv.sum(:volume).to_f; if sum == 0.0; 1.0 else sum end

There is also the conditional operator, but personally, I see no use for it that cannot also (and better) be served by a conditional expression:

sum = zv.sum(:volume).to_f; sum == 0.0 ? 1.0 : sum

If you want a single expression, you could inline the variable assignment into the conditional:

if (sum = zv.sum(:volume).to_f) == 0.0
  1.0
else
  sum
end

And of course, you can combine the two:

if (sum = zv.sum(:volume).to_f) == 0.0 then 1.0 else sum end

And with avoiding keywords:

if (sum = zv.sum(:volume).to_f) == 0.0; 1.0 else sum end

And with the (still useless) conditional operator:

(sum = zv.sum(:volume).to_f) == 0.0 ? 1.0 : sum

If you don't mind some mildly complex non-local control flow, you could do something like this:

zv.sum(:volume).to_f.tap {|sum| break 1.0 if sum == 0.0 }

Pulling out the float conversion:

zv.sum(:volume).tap {|sum| break 1 if sum == 0 }.to_f

Also, if you know something special about the sum, for example, if you know that the sum will always be either exactly 0 or greater than 1, then you could exploit that extra knowledge and maybe do something like this:

[1.0, zv.sum(:volume).to_f].max

Pulling out the float conversion:

[1, zv.sum(:volume)].max.to_f

However, please note that none of those is as readable and clear as the first version.

Also, please note that the description in your question and the code in your question don't match up, since the code doesn't do what you describe in the question.

You can use nonzero? to treat zero as a falsey value:

zv.sum(:volume).to_f.nonzero? || 1.0

or:

(zv.sum(:volume).nonzero? || 1).to_f

I usually prefer something verbose and well readable, for example :

def some_method
  volume = zv.sum(:volume).to_f
  return 1.0 if volume.zero? 

  volume
end

but if you really want your one-liner, you could do something like that :

zv.sum(:volume).to_f.yield_self { |r| r.zero? ? 1.0 : r }

Related