String concatenation vs. interpolation in Ruby
RubyString ConcatenationString InterpolationRuby Problem Overview
I am just starting to learn Ruby (first time programming), and have a basic syntactical question with regards to variables, and various ways of writing code.
Chris Pine's "Learn to Program" taught me to write a basic program like this...
num_cars_again= 2
puts 'I own ' + num_cars_again.to_s + ' cars.'
This is fine, but then I stumbled across the tutorial on ruby.learncodethehardway.com, and was taught to write the same exact program like this...
num_cars= 2
puts "I own #{num_cars} cars."
They both output the same thing, but obviously option 2 is a much shorter way to do it.
Is there any particular reason why I should use one format over the other?
Ruby Solutions
Solution 1 - Ruby
Whenever TIMTOWTDI (there is more than one way to do it), you should look for the pros and cons. Using "string interpolation" (the second) instead of "string concatenation" (the first):
Pros:
- Is less typing
- Automatically calls
to_s
for you - More idiomatic within the Ruby community
- Faster to accomplish during runtime
Cons:
- Automatically calls
to_s
for you (maybe you thought you had a string, and theto_s
representation is not what you wanted, and hides the fact that it wasn't a string) - Requires you to use
"
to delimit your string instead of'
(perhaps you have a habit of using'
, or you previously typed a string using that and only later needed to use string interpolation)
Solution 2 - Ruby
Both interpolation and concatination has its own strength and weakness. Below I gave a benchmark which clearly demonstrates where to use concatination and where to use interpolation.
require 'benchmark'
iterations = 1_00_000
firstname = 'soundarapandian'
middlename = 'rathinasamy'
lastname = 'arumugam'
puts 'With dynamic new strings'
puts '===================================================='
5.times do
Benchmark.bm(10) do |benchmark|
benchmark.report('concatination') do
iterations.times do
'Mr. ' + firstname + middlename + lastname + ' aka soundar'
end
end
benchmark.report('interpolaton') do
iterations.times do
"Mr. #{firstname} #{middlename} #{lastname} aka soundar"
end
end
end
puts '--------------------------------------------------'
end
puts 'With predefined strings'
puts '===================================================='
5.times do
Benchmark.bm(10) do |benchmark|
benchmark.report('concatination') do
iterations.times do
firstname + middlename + lastname
end
end
benchmark.report('interpolaton') do
iterations.times do
"#{firstname} #{middlename} #{lastname}"
end
end
end
puts '--------------------------------------------------'
end
And below is the Benchmark result
Without predefined strings
====================================================
user system total real
concatination 0.170000 0.000000 0.170000 ( 0.165821)
interpolaton 0.130000 0.010000 0.140000 ( 0.133665)
--------------------------------------------------
user system total real
concatination 0.180000 0.000000 0.180000 ( 0.180410)
interpolaton 0.120000 0.000000 0.120000 ( 0.125051)
--------------------------------------------------
user system total real
concatination 0.140000 0.000000 0.140000 ( 0.134256)
interpolaton 0.110000 0.000000 0.110000 ( 0.111427)
--------------------------------------------------
user system total real
concatination 0.130000 0.000000 0.130000 ( 0.132047)
interpolaton 0.120000 0.000000 0.120000 ( 0.120443)
--------------------------------------------------
user system total real
concatination 0.170000 0.000000 0.170000 ( 0.170394)
interpolaton 0.150000 0.000000 0.150000 ( 0.149601)
--------------------------------------------------
With predefined strings
====================================================
user system total real
concatination 0.070000 0.000000 0.070000 ( 0.067735)
interpolaton 0.100000 0.000000 0.100000 ( 0.099335)
--------------------------------------------------
user system total real
concatination 0.060000 0.000000 0.060000 ( 0.061955)
interpolaton 0.130000 0.000000 0.130000 ( 0.127011)
--------------------------------------------------
user system total real
concatination 0.090000 0.000000 0.090000 ( 0.092136)
interpolaton 0.110000 0.000000 0.110000 ( 0.110224)
--------------------------------------------------
user system total real
concatination 0.080000 0.000000 0.080000 ( 0.077587)
interpolaton 0.110000 0.000000 0.110000 ( 0.112975)
--------------------------------------------------
user system total real
concatination 0.090000 0.000000 0.090000 ( 0.088154)
interpolaton 0.140000 0.000000 0.140000 ( 0.135349)
--------------------------------------------------
Conclusion
If strings already defined and sure they will never be nil use concatination else use interpolation.Use appropriate one which will result in better performance than one which is easy to indent.
Solution 3 - Ruby
@user1181898 - IMHO, it's because it's easier to see what's happening. To @Phrogz's point, string interpolation automatically calls the to_s for you. As a beginner, you need to see what's happening "under the hood" so that you learn the concept as opposed to just learning by rote.
Think of it like learning mathematics. You learn the "long" way in order to understand the concepts so that you can take shortcuts once you actually know what you are doing. I speak from experience b/c I'm not that advanced in Ruby yet, but I've made enough mistakes to advise people on what not to do. Hope this helps.
Solution 4 - Ruby
If you are using a string as a buffer, I found that using concatenation (String#concat
) to be faster.
require 'benchmark/ips'
puts "Ruby #{RUBY_VERSION} at #{Time.now}"
puts
firstname = 'soundarapandian'
middlename = 'rathinasamy'
lastname = 'arumugam'
Benchmark.ips do |x|
x.report("String\#<<") do |i|
buffer = String.new
while (i -= 1) > 0
buffer << 'Mr. ' << firstname << middlename << lastname << ' aka soundar'
end
end
x.report("String interpolate") do |i|
buffer = String.new
while (i -= 1) > 0
buffer << "Mr. #{firstname} #{middlename} #{lastname} aka soundar"
end
end
x.compare!
end
Results:
Ruby 2.3.1 at 2016-11-15 15:03:57 +1300
Warming up --------------------------------------
String#<< 230.615k i/100ms
String interpolate 234.274k i/100ms
Calculating -------------------------------------
String#<< 2.345M (± 7.2%) i/s - 11.761M in 5.041164s
String interpolate 1.242M (± 5.4%) i/s - 6.325M in 5.108324s
Comparison:
String#<<: 2344530.4 i/s
String interpolate: 1241784.9 i/s - 1.89x slower
At a guess, I'd say that interpolation generates a temporary string which is why it's slower.
Solution 5 - Ruby
Here is a full benchmark which also compares Kernel#format
and String#+
as it's all methods for construction dynamic string in ruby that I know 樂
require 'benchmark/ips'
firstname = 'soundarapandian'
middlename = 'rathinasamy'
lastname = 'arumugam'
FORMAT_STR = 'Mr. %<firstname>s %<middlename>s %<lastname>s aka soundar'
Benchmark.ips do |x|
x.report("String\#<<") do |i|
str = String.new
str << 'Mr. ' << firstname << ' ' << middlename << ' ' << lastname << ' aka soundar'
end
x.report "String\#+" do
'Mr. ' + firstname + ' ' + middlename + ' ' + lastname + ' aka soundar'
end
x.report "format" do
format(FORMAT_STR, firstname: firstname, middlename: middlename, lastname: lastname)
end
x.report("String interpolate") do |i|
"Mr. #{firstname} #{middlename} #{lastname} aka soundar"
end
x.compare!
end
And results for ruby 2.6.5
Warming up --------------------------------------
String#<<
94.597k i/100ms
String#+ 75.512k i/100ms
format 73.269k i/100ms
String interpolate 164.005k i/100ms
Calculating -------------------------------------
String#<< 91.385B (±16.9%) i/s - 315.981B
String#+ 905.389k (± 4.2%) i/s - 4.531M in 5.013725s
format 865.746k (± 4.5%) i/s - 4.323M in 5.004103s
String interpolate 161.694B (±11.3%) i/s - 503.542B
Comparison:
String interpolate: 161693621120.0 i/s
String#<<: 91385051886.2 i/s - 1.77x slower
String#+: 905388.7 i/s - 178590.27x slower
format: 865745.8 i/s - 186768.00x slower