String concatenation in Ruby
Asked Answered
I

16

404

I am looking for a more elegant way of concatenating strings in Ruby.

I have the following line:

source = "#{ROOT_DIR}/" << project << "/App.config"

Is there a nicer way of doing this?

And for that matter what is the difference between << and +?

Isola answered 18/12, 2008 at 13:4 Comment(2)
This question #4684946 is highly related.Grouch
<< this is more efficient way to do concatenation.Tyus
C
625

You can do that in several ways:

  1. As you shown with << but that is not the usual way
  2. With string interpolation

    source = "#{ROOT_DIR}/#{project}/App.config"
    
  3. with +

    source = "#{ROOT_DIR}/" + project + "/App.config"
    

The second method seems to be more efficient in term of memory/speed from what I've seen (not measured though). All three methods will throw an uninitialized constant error when ROOT_DIR is nil.

When dealing with pathnames, you may want to use File.join to avoid messing up with pathname separator.

In the end, it is a matter of taste.

Chaldean answered 18/12, 2008 at 13:9 Comment(3)
I'm not very experienced with ruby. But generally in cases where you concatenate lots of strings you often can gain performance by appending the strings to an array and then at the end put the string together atomically. Then << could be useful?Galumph
You'll have to add memory an copy the longer string into it anyway. << is more or less the same as + except that you can << with a single character.Chaldean
Instead of using << on the elements of an array, use Array#join, it's much faster.Expiry
U
108

The + operator is the normal concatenation choice, and is probably the fastest way to concatenate strings.

The difference between + and << is that << changes the object on its left hand side, and + doesn't.

irb(main):001:0> s = 'a'
=> "a"
irb(main):002:0> s + 'b'
=> "ab"
irb(main):003:0> s
=> "a"
irb(main):004:0> s << 'b'
=> "ab"
irb(main):005:0> s
=> "ab"
Unwitting answered 18/12, 2008 at 15:50 Comment(4)
The + operator is definitely not the fastest way to concatenate strings. Every time you use it, it makes a copy, whereas << concatenates in place and is much more performant.Yonah
For most uses, interpolation, + and << are going to be about the same. If you're dealing with a lot of strings, or really big ones, then you might notice a difference. I was surprised by how similar they performed. gist.github.com/2895311Unwitting
Your jruby results are skewed against interpolation by the early-run JVM overload. If you run the test suite several times (in the same process -- so wrap everything in say a 5.times do ... end block) for each interpreter, you'd end up with more accurate results. My testing has shown interpolation is the fastest method, across all Ruby interpreters. I would have expected << to be the quickest, but that's why we benchmark.Malti
Not being too versed on Ruby, I'm curious whether the mutation is performed on the stack or heap? If on heap, even a mutation operation, which seems like it should be quicker, probably involves some form of malloc. Without it, I'd expect a buffer overflow. Using the stack could be pretty fast but the resultant value is probably placed on the heap anyway, requiring a malloc operation. In the end, I expect the memory pointer to be a new address, even if the variable reference makes it look like an in-place mutation. So, really, is there a difference?Capablanca
M
81

If you are just concatenating paths you can use Ruby's own File.join method.

source = File.join(ROOT_DIR, project, 'App.config')
Matrilocal answered 18/12, 2008 at 13:22 Comment(1)
This seems to be the way to go since then ruby will take care of creating the correct string on system with different path separators.Galumph
L
32

from http://greyblake.com/blog/2012/09/02/ruby-perfomance-tricks/

Using << aka concat is far more efficient than +=, as the latter creates a temporal object and overrides the first object with the new object.

require 'benchmark'

N = 1000
BASIC_LENGTH = 10

5.times do |factor|
  length = BASIC_LENGTH * (10 ** factor)
  puts "_" * 60 + "\nLENGTH: #{length}"

  Benchmark.bm(10, '+= VS <<') do |x|
    concat_report = x.report("+=")  do
      str1 = ""
      str2 = "s" * length
      N.times { str1 += str2 }
    end

    modify_report = x.report("<<")  do
      str1 = "s"
      str2 = "s" * length
      N.times { str1 << str2 }
    end

    [concat_report / modify_report]
  end
end

output:

____________________________________________________________
LENGTH: 10
                 user     system      total        real
+=           0.000000   0.000000   0.000000 (  0.004671)
<<           0.000000   0.000000   0.000000 (  0.000176)
+= VS <<          NaN        NaN        NaN ( 26.508796)
____________________________________________________________
LENGTH: 100
                 user     system      total        real
+=           0.020000   0.000000   0.020000 (  0.022995)
<<           0.000000   0.000000   0.000000 (  0.000226)
+= VS <<          Inf        NaN        NaN (101.845829)
____________________________________________________________
LENGTH: 1000
                 user     system      total        real
+=           0.270000   0.120000   0.390000 (  0.390888)
<<           0.000000   0.000000   0.000000 (  0.001730)
+= VS <<          Inf        Inf        NaN (225.920077)
____________________________________________________________
LENGTH: 10000
                 user     system      total        real
+=           3.660000   1.570000   5.230000 (  5.233861)
<<           0.000000   0.010000   0.010000 (  0.015099)
+= VS <<          Inf 157.000000        NaN (346.629692)
____________________________________________________________
LENGTH: 100000
                 user     system      total        real
+=          31.270000  16.990000  48.260000 ( 48.328511)
<<           0.050000   0.050000   0.100000 (  0.105993)
+= VS <<   625.400000 339.800000        NaN (455.961373)
Lyingin answered 19/2, 2015 at 23:22 Comment(0)
C
11

Since this is a path I'd probably use array and join:

source = [ROOT_DIR, project, 'App.config'] * '/'
Celenacelene answered 18/12, 2008 at 13:14 Comment(0)
R
10

Here's another benchmark inspired by this gist. It compares concatenation (+), appending (<<) and interpolation (#{}) for dynamic and predefined strings.

require 'benchmark'

# we will need the CAPTION and FORMAT constants:
include Benchmark

count = 100_000


puts "Dynamic strings"

Benchmark.benchmark(CAPTION, 7, FORMAT) do |bm|
  bm.report("concat") { count.times { 11.to_s +  '/' +  12.to_s } }
  bm.report("append") { count.times { 11.to_s << '/' << 12.to_s } }
  bm.report("interp") { count.times { "#{11}/#{12}" } }
end


puts "\nPredefined strings"

s11 = "11"
s12 = "12"
Benchmark.benchmark(CAPTION, 7, FORMAT) do |bm|
  bm.report("concat") { count.times { s11 +  '/' +  s12 } }
  bm.report("append") { count.times { s11 << '/' << s12 } }
  bm.report("interp") { count.times { "#{s11}/#{s12}"   } }
end

output:

Dynamic strings
              user     system      total        real
concat    0.050000   0.000000   0.050000 (  0.047770)
append    0.040000   0.000000   0.040000 (  0.042724)
interp    0.050000   0.000000   0.050000 (  0.051736)

Predefined strings
              user     system      total        real
concat    0.030000   0.000000   0.030000 (  0.024888)
append    0.020000   0.000000   0.020000 (  0.023373)
interp    3.160000   0.160000   3.320000 (  3.311253)

Conclusion: interpolation in MRI is heavy.

Raffia answered 15/9, 2016 at 9:13 Comment(1)
Since strings are starting to be immutable now, I'd love to see a new benchmark for this.Reckford
M
7

I'd prefer using Pathname:

require 'pathname' # pathname is in stdlib
Pathname(ROOT_DIR) + project + 'App.config'

about << and + from ruby docs:

+: Returns a new String containing other_str concatenated to str

<<: Concatenates the given object to str. If the object is a Fixnum between 0 and 255, it is converted to a character before concatenation.

so difference is in what becomes to first operand (<< makes changes in place, + returns new string so it is memory heavier) and what will be if first operand is Fixnum (<< will add as if it was character with code equal to that number, + will raise error)

Metastasize answered 26/12, 2009 at 6:27 Comment(2)
I just discovered that calling '+' on a Pathname can be dangerous because if the arg is an absolute path, the receiver path is ignored: Pathname('/home/foo') + '/etc/passwd' # => #<Pathname:/etc/passwd>. This is by design, based on the rubydoc example. Seems that File.join is safer.Fimbriation
also you need to call (Pathname(ROOT_DIR) + project + 'App.config').to_s if you want to return a string object.Salvatore
B
6

Let me show to you all my experience with that.

I had an query that returned 32k of records, for each record I called a method to format that database record into a formated string and than concatenate that into a String that at the end of all this process wil turn into a file in disk.

My problem was that by the record goes, around 24k, the process of concatenating the String turned on a pain.

I was doing that using the regular '+' operator.

When I changed to the '<<' was like magic. Was really fast.

So, I remembered my old times - sort of 1998 - when I was using Java and concatenating String using '+' and changed from String to StringBuffer (and now we, Java developer have the StringBuilder).

I believe that the process of + / << in Ruby world is the same as + / StringBuilder.append in the Java world.

The first reallocate the entire object in memory and the other just point to a new address.

Bartholomeo answered 3/8, 2012 at 20:11 Comment(0)
B
5

Here are more ways to do this:

"String1" + "String2"

"#{String1} #{String2}"

String1<<String2

And so on ...

Broek answered 6/3, 2013 at 20:11 Comment(0)
S
5

Concatenation you say? How about #concat method then?

a = 'foo'
a.object_id #=> some number
a.concat 'bar' #=> foobar
a.object_id #=> same as before -- string a remains the same object

In all fairness, concat is aliased as <<.

Swop answered 12/6, 2013 at 3:48 Comment(2)
There is one more way of glueing strings together not mentioned by others, and that is by mere juxtaposition: "foo" "bar" 'baz" #=> "foobarabaz"Swop
Note to others: That's not supposed to be a single quote, but a double one like the rest. Neat method!Tragedy
N
2

You may use + or << operator, but in ruby .concat function is the most preferable one, as it is much faster than other operators. You can use it like.

source = "#{ROOT_DIR}/".concat(project.concat("/App.config"))
Ness answered 9/10, 2018 at 5:26 Comment(1)
I think you have an extra . after your last concat no ?Salvatore
C
2

You can also use % as follows:

source = "#{ROOT_DIR}/%s/App.config" % project

This approach works with ' (single) quotation mark as well.

Conventioneer answered 19/2, 2019 at 13:56 Comment(0)
I
2

You can concatenate in string definition directly:

nombre_apellido = "#{customer['first_name']} #{customer['last_name']} #{order_id}"
Illyria answered 29/3, 2019 at 1:21 Comment(0)
R
1

Situation matters, for example:

# this will not work
output = ''

Users.all.each do |user|
  output + "#{user.email}\n"
end
# the output will be ''
puts output

# this will do the job
output = ''

Users.all.each do |user|
  output << "#{user.email}\n"
end
# will get the desired output
puts output

In the first example, concatenating with + operator will not update the output object,however, in the second example, the << operator will update the output object with each iteration. So, for the above type of situation, << is better.

Resemblance answered 6/2, 2019 at 7:34 Comment(0)
S
0

For your particular case you could also use Array#join when constructing file path type of string:

string = [ROOT_DIR, project, 'App.config'].join('/')]

This has a pleasant side effect of automatically converting different types to string:

['foo', :bar, 1].join('/')
=>"foo/bar/1"
Salvatore answered 27/11, 2019 at 23:28 Comment(0)
A
0

For Puppet:

$username = 'lala'
notify { "Hello ${username.capitalize}":
    withpath => false,
}
Adopted answered 5/3, 2020 at 16:13 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.