Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to quickly join two strings in Ruby

It's common to need to join strings, and in Ruby we have common ways of doing it: appending, concatenating and interpolating one into the other, or using the built-in concat method in String. (We have multiple ways of doing it for flexibility and to ease the transition from other languages to Ruby.)

Starting with:

'a ' << 'z'      # => "a z"
'a '.concat('z') # => "a z"
'a ' + 'z'       # => "a z"
"a #{'z'}"       # => "a z"

Assuming we don't want to change either string and that the strings won't be assigned to variables, what is the fastest way to join them, and does the fastest way change as the size of the "left" string grows?

For those who can't figure out why we'd post questions like this, it's to help educate and show the most efficient way to do a particular task. Newcomers to a language, Ruby in this case, often drag old ways of doing something with them, and inadvertently write code that runs more slowly than necessary. A simple change to their coding style can result in faster code.

like image 671
the Tin Man Avatar asked Mar 14 '17 17:03

the Tin Man


People also ask

How do you join two strings in Ruby?

concat is a String class method in Ruby which is used to Concatenates two objects of String. If the given object is an Integer, then it is considered a codepoint and converted to a character before concatenation. Parameters: This method can take the string object and normal string as the parameters.

How do I combine multiple strings into one?

You concatenate strings by using the + operator. For string literals and string constants, concatenation occurs at compile time; no run-time concatenation occurs. For string variables, concatenation occurs only at run time.

Is concatenation faster than join?

Doing N concatenations requires creating N new strings in the process. join() , on the other hand, only has to create a single string (the final result) and thus works much faster.

How do I combine double strings?

The “+” operator with a String acts as a concatenation operator. Whenever you add a String value to a double using the “+” operator, both values are concatenated resulting a String object. In-fact adding a double value to String is the easiest way to convert a double value to Strings.


1 Answers

Starting with short strings:

z = 'z'
'a ' << z      # => "a z"
'a '.concat(z) # => "a z"
'a ' + z       # => "a z"
"a #{z}"       # => "a z"

require 'fruity'
compare do
  append      { 'a ' << z}
  concat      { 'a '.concat(z)}
  plus        { 'a ' + z}
  interpolate { "a #{z}" }
end

# >> Running each test 65536 times. Test will take about 2 seconds.
# >> interpolate is similar to append
# >> append is similar to plus
# >> plus is faster than concat by 2x ± 0.1

Increasing the "left" string to 11 characters:

require 'fruity'
compare do
  append      { 'abcdefghij ' << z}
  concat      { 'abcdefghij '.concat(z)}
  plus        { 'abcdefghij ' + z}
  interpolate { "abcdefghij #{z}" }
end

# >> Running each test 65536 times. Test will take about 2 seconds.
# >> interpolate is similar to append
# >> append is similar to plus
# >> plus is faster than concat by 2x ± 1.0

51 characters:

compare do
  append      { 'abcdefghijabcdefghijabcdefghijabcdefghijabcdefghij ' << z}
  concat      { 'abcdefghijabcdefghijabcdefghijabcdefghijabcdefghij '.concat(z)}
  plus        { 'abcdefghijabcdefghijabcdefghijabcdefghijabcdefghij ' + z}
  interpolate { "abcdefghijabcdefghijabcdefghijabcdefghijabcdefghij #{z}" }
end

# >> Running each test 32768 times. Test will take about 2 seconds.
# >> plus is faster than append by 2x ± 1.0
# >> append is similar to interpolate
# >> interpolate is similar to concat

101:

compare do
  append      { 'abcdefghijabcdefghijabcdefghijabcdefghijabcdefghijabcdefghijabcdefghijabcdefghijabcdefghijabcdefghij ' << z}
  concat      { 'abcdefghijabcdefghijabcdefghijabcdefghijabcdefghijabcdefghijabcdefghijabcdefghijabcdefghijabcdefghij '.concat(z)}
  plus        { 'abcdefghijabcdefghijabcdefghijabcdefghijabcdefghijabcdefghijabcdefghijabcdefghijabcdefghijabcdefghij ' + z}
  interpolate { "abcdefghijabcdefghijabcdefghijabcdefghijabcdefghijabcdefghijabcdefghijabcdefghijabcdefghijabcdefghij #{z}" }
end

# >> Running each test 32768 times. Test will take about 2 seconds.
# >> plus is faster than interpolate by 2x ± 0.1
# >> interpolate is similar to append
# >> append is similar to concat

501:

compare do
  append      { 'abcdefghijabcdefghijabcdefghijabcdefghijabcdefghijabcdefghijabcdefghijabcdefghijabcdefghijabcdefghijabcdefghijabcdefghijabcdefghijabcdefghijabcdefghijabcdefghijabcdefghijabcdefghijabcdefghijabcdefghijabcdefghijabcdefghijabcdefghijabcdefghijabcdefghijabcdefghijabcdefghijabcdefghijabcdefghijabcdefghijabcdefghijabcdefghijabcdefghijabcdefghijabcdefghijabcdefghijabcdefghijabcdefghijabcdefghijabcdefghijabcdefghijabcdefghijabcdefghijabcdefghijabcdefghijabcdefghijabcdefghijabcdefghijabcdefghijabcdefghij ' << z}
  concat      { 'abcdefghijabcdefghijabcdefghijabcdefghijabcdefghijabcdefghijabcdefghijabcdefghijabcdefghijabcdefghijabcdefghijabcdefghijabcdefghijabcdefghijabcdefghijabcdefghijabcdefghijabcdefghijabcdefghijabcdefghijabcdefghijabcdefghijabcdefghijabcdefghijabcdefghijabcdefghijabcdefghijabcdefghijabcdefghijabcdefghijabcdefghijabcdefghijabcdefghijabcdefghijabcdefghijabcdefghijabcdefghijabcdefghijabcdefghijabcdefghijabcdefghijabcdefghijabcdefghijabcdefghijabcdefghijabcdefghijabcdefghijabcdefghijabcdefghijabcdefghij '.concat(z)}
  plus        { 'abcdefghijabcdefghijabcdefghijabcdefghijabcdefghijabcdefghijabcdefghijabcdefghijabcdefghijabcdefghijabcdefghijabcdefghijabcdefghijabcdefghijabcdefghijabcdefghijabcdefghijabcdefghijabcdefghijabcdefghijabcdefghijabcdefghijabcdefghijabcdefghijabcdefghijabcdefghijabcdefghijabcdefghijabcdefghijabcdefghijabcdefghijabcdefghijabcdefghijabcdefghijabcdefghijabcdefghijabcdefghijabcdefghijabcdefghijabcdefghijabcdefghijabcdefghijabcdefghijabcdefghijabcdefghijabcdefghijabcdefghijabcdefghijabcdefghijabcdefghij ' + z}
  interpolate { "abcdefghijabcdefghijabcdefghijabcdefghijabcdefghijabcdefghijabcdefghijabcdefghijabcdefghijabcdefghijabcdefghijabcdefghijabcdefghijabcdefghijabcdefghijabcdefghijabcdefghijabcdefghijabcdefghijabcdefghijabcdefghijabcdefghijabcdefghijabcdefghijabcdefghijabcdefghijabcdefghijabcdefghijabcdefghijabcdefghijabcdefghijabcdefghijabcdefghijabcdefghijabcdefghijabcdefghijabcdefghijabcdefghijabcdefghijabcdefghijabcdefghijabcdefghijabcdefghijabcdefghijabcdefghijabcdefghijabcdefghijabcdefghijabcdefghijabcdefghij #{z}" }
end

# >> Running each test 16384 times. Test will take about 1 second.
# >> plus is faster than append by 2x ± 0.1
# >> append is similar to interpolate
# >> interpolate is similar to concat

Once the strings got past 50 characters + consistently outperformed the others.

In the comments there are mention of some of these mutating the string on the left. Here's what would happen if it was a variable on the left, not a literal string:

a = 'a'
z = 'z'

a << z # => "az"
a # => "az"

a = 'a'
a.concat(z) # => "az"
a # => "az"

compared to:

a + z # => "az"
a     # => "a"

"#{a} #{z}" # => "a z"
a           # => "a"

Note: The initial version of answer had a bad test using:

"a #{'z'}"

The problem with that is Ruby is smart enough to recognize that 'z' is another literal and converts the string into:

"a z"

with the end result that the test would be unfairly faster than the others.


It's been a while and Ruby's smarter and faster. I added a couple additional tests:

puts "Running Ruby v%s" % RUBY_VERSION

require 'fruity'

z_ = 'z'
compare do
  append       { 'abcdefghij ' << 'z'      }
  concat       { 'abcdefghij '.concat('z') }
  plus         { 'abcdefghij ' + 'z'       }
  interpolate1 { "abcdefghij #{'z'}"       }
  interpolate2 { "abcdefghij #{z_}"        }
  adjacent     { 'abcdefghij' ' z'         }
end

# >> Running Ruby v2.7.0
# >> Running each test 65536 times. Test will take about 3 seconds.
# >> adjacent is similar to interpolate1
# >> interpolate1 is faster than interpolate2 by 2x ± 1.0
# >> interpolate2 is similar to append
# >> append is similar to concat
# >> concat is similar to plus

interpolate1 and adjacent are basically the same as far as the interpreter is concerned and will be concatenated prior to running. interpolate2 forces Ruby to do it at run-time so it's a little slower.

like image 85
5 revs Avatar answered Oct 13 '22 00:10

5 revs