Duc Nguyen
Duc Nguyen

Reputation: 588

Assign string if variable is nil and append otherwise

I'm trying to shorten this code:

if a.nil?
  a = "foo"
else
  a << "foo"
end

a is either nil or a string.

I have already tried a ||= "foo" and a += "foo" but these don't work if a is nil.

Upvotes: 3

Views: 3649

Answers (4)

Andreas
Andreas

Reputation: 998

If you want to combine 2 strings, where any one of them may be nil, you could do:

a.nil? ? a = (b || '') : a << (b || '')

This works even if a, b or both are nil.

This is also by far faster than alternative methods, even though the syntax is somewhat ugly.

Benchmark:

require 'benchmark'

n = 50_000_000
Benchmark.bm do |x|
  x.report("compact.reduce(:+)")   { n.times { a = nil; b = 'foo'; a = [a, b].compact.reduce(:+) } }
  x.report("string interpolation") { n.times { a = nil; b = 'foo'; a = "#{a}#{b}" } }
  x.report("to_s")                 { n.times { a = nil; b = 'foo'; a = a.to_s + b.to_s } }
  x.report("(x || '') +")          { n.times { a = nil; b = 'foo'; a = (a || '') + b } }
  x.report("(x || '') <<")         { n.times { a = nil; b = 'foo'; a = (a || '') << b } } # will throw an exception if used with `frozen_string_literal: true`
  x.report("ternary")              { n.times { a = nil; b = 'foo'; a.nil? ? a = (b || '') : a << (b || '') } }
end

Results:

                      user       system     total       real
compact.reduce(:+)    6.677367   0.000000   6.677367 (  6.677337)
string interpolation  7.983451   0.000000   7.983451 (  7.983440)
to_s                  7.067699   0.000000   7.067699 (  7.067702)
(x || '') +           5.130624   0.000000   5.130624 (  5.130585)
(x || '') <<          4.809965   0.000000   4.809965 (  4.809978)
ternary               3.300871   0.000000   3.300871 (  3.300838)

Upvotes: 0

Stefan
Stefan

Reputation: 114248

It might be irrelevant in your case, but there's a difference between a += and a <<.

+= assigns a new string to a, leaving the old one unchanged:

a = 'abc'
b = a

a += 'foo'

a #=> "abcfoo"   # <- a refers to the new string object
b #=> "foo"      # <- b still refers to the original string

This is because String#+ returns a new string. a += 'foo' is equivalent to a = a + 'foo'.

String#<< on the other hand modifies the existing string, it doesn't create a new one:

a = 'abc'
b = a

a << 'foo'

a #=> "abcfoo"
b #=> "abcfoo"   # <- both changed, because a and b refer to the same object

So, in order to shorten your code without changing its behavior, you could use:

(a ||= '') << 'foo'

a ||= '' assigns an empty string to a if a is nil. Then << 'foo' appends 'foo' to a.

Upvotes: 5

Ursus
Ursus

Reputation: 30071

nil.to_s is equals to '' so you could write

a = a.to_s + 'foo'

or, an alternative

a = "#{a}foo"

Upvotes: 10

Chakreshwar Sharma
Chakreshwar Sharma

Reputation: 2610

write like below:

a = a.nil? ? (a.to_s + 'foo') : 'foo'

Upvotes: 0

Related Questions