Concaténation de chaînes en Ruby


364

Je recherche une manière plus élégante de concaténer des cordes en Ruby.

J'ai la ligne suivante:

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

Existe-t-il une meilleure façon de procéder?

Et d'ailleurs, quelle est la différence entre <<et +?


3
Cette question stackoverflow.com/questions/4684446/… est fortement liée.
Eye

<< c'est un moyen plus efficace de faire la concaténation.
Taimoor Changaiz,

Réponses:


575

Vous pouvez le faire de plusieurs manières:

  1. Comme vous l'avez montré avec <<mais ce n'est pas la manière habituelle
  2. Avec interpolation de chaîne

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

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

La deuxième méthode semble être plus efficace en termes de mémoire / vitesse par rapport à ce que j'ai vu (non mesuré cependant). Les trois méthodes génèrent une erreur constante non initialisée lorsque ROOT_DIR est nul.

Lorsque vous traitez des noms de chemin, vous pouvez utiliser File.join pour éviter de gâcher le séparateur de nom de chemin.

Au final, c'est une question de goût.


7
Je ne suis pas très expérimenté avec le rubis. Mais généralement, dans les cas où vous concaténez de nombreuses chaînes, vous pouvez souvent gagner en performances en ajoutant les chaînes à un tableau, puis à la fin, assembler la chaîne de manière atomique. Alors << pourrait être utile?
PEZ

1
Vous devrez de toute façon y ajouter de la mémoire et copier la chaîne la plus longue. << est plus ou moins identique à + sauf que vous pouvez << avec un seul caractère.
Keltia

9
Au lieu d'utiliser << sur les éléments d'un tableau, utilisez Array # join, c'est beaucoup plus rapide.
Grant Hutchins

94

le + opérateur est le choix de concaténation normal et est probablement le moyen le plus rapide de concaténer des chaînes.

La différence entre +et <<est que <<change l'objet sur son côté gauche, et +ne change pas.

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"

32
L'opérateur + n'est certainement pas le moyen le plus rapide de concaténer des chaînes. Chaque fois que vous l'utilisez, il en fait une copie, alors que << concatène en place et est beaucoup plus performant.
Evil Trout

5
Pour la plupart des utilisations, l'interpolation +et <<seront à peu près les mêmes. Si vous avez affaire à beaucoup de chaînes ou à de très grosses chaînes, vous remarquerez peut-être une différence. J'ai été surpris par la similitude de leurs performances. gist.github.com/2895311
Matt Burke

8
Vos résultats jruby sont biaisés contre l'interpolation par la surcharge JVM à exécution précoce. Si vous exécutez la suite de tests plusieurs fois (dans le même processus - alors enveloppez tout dans disons un 5.times do ... endbloc) pour chaque interprète, vous vous retrouveriez avec des résultats plus précis. Mes tests ont montré que l'interpolation est la méthode la plus rapide, sur tous les interprètes Ruby. Je m'attendais <<à être le plus rapide, mais c'est pourquoi nous nous référons.
womble

N'étant pas trop familier avec Ruby, je suis curieux de savoir si la mutation est effectuée sur la pile ou le tas? Si sur le tas, même une opération de mutation, qui semble devoir être plus rapide, implique probablement une forme de malloc. Sans cela, je m'attendrais à un débordement de tampon. L'utilisation de la pile pourrait être assez rapide mais la valeur résultante est probablement placée de toute façon sur le tas, nécessitant une opération malloc. En fin de compte, je m'attends à ce que le pointeur de mémoire soit une nouvelle adresse, même si la référence de variable le fait ressembler à une mutation sur place. Alors, vraiment, y a-t-il une différence?
Robin Coe

79

Si vous ne faites que concaténer des chemins, vous pouvez utiliser la méthode File.join de Ruby.

source = File.join(ROOT_DIR, project, 'App.config')

5
Cela semble être la voie à suivre depuis lors, ruby ​​se chargera de créer la chaîne correcte sur le système avec différents séparateurs de chemin.
PEZ

26

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

Utiliser <<aka concatest beaucoup plus efficace que +=, car ce dernier crée un objet temporel et remplace le premier objet par le nouvel objet.

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

production:

____________________________________________________________
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)

11

Étant donné que c'est un chemin, j'utiliserais probablement un tableau et une jointure:

source = [ROOT_DIR, project, 'App.config'] * '/'

9

Voici une autre référence inspirée de cet essentiel . Il compare la concaténation ( +), l'ajout ( <<) et l'interpolation ( #{}) pour les chaînes dynamiques et prédéfinies.

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

production:

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: l'interpolation en IRM est lourde.


Puisque les cordes commencent à être immuables maintenant, j'aimerais voir une nouvelle référence pour cela.
bibstha

7

Je préfère utiliser Pathname:

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

sur <<et à +partir de ruby ​​docs:

+: Renvoie une nouvelle chaîne contenant other_str concaténée à str

<<: Concatène l'objet donné à str. Si l'objet est un Fixnum compris entre 0 et 255, il est converti en caractère avant la concaténation.

donc la différence est dans ce qui devient le premier opérande ( <<fait des changements sur place, +retourne une nouvelle chaîne donc c'est de la mémoire plus lourde) et ce qui sera si le premier opérande est Fixnum ( <<ajoutera comme s'il s'agissait d'un caractère avec un code égal à ce nombre, +augmentera Erreur)


2
Je viens de découvrir que l' appel « + » sur un chemin peut être dangereux parce que si le arg est un chemin absolu, le chemin du récepteur est ignoré: Pathname('/home/foo') + '/etc/passwd' # => #<Pathname:/etc/passwd>. Ceci est de par leur conception, basé sur l'exemple rubydoc. Semble que File.join est plus sûr.
Kelvin

vous devez également appeler (Pathname(ROOT_DIR) + project + 'App.config').to_ssi vous souhaitez renvoyer un objet chaîne.
lacostenycoder

6

Permettez-moi de vous montrer toute mon expérience avec cela.

J'ai eu une requête qui a retourné 32k d'enregistrements, pour chaque enregistrement, j'ai appelé une méthode pour formater cet enregistrement de base de données dans une chaîne formatée et ensuite la concaténer en une chaîne qui, à la fin de tout ce processus, se transformera en un fichier sur le disque.

Mon problème était que d'après le dossier, vers 24k, le processus de concaténation de la chaîne s'est avéré douloureux.

Je faisais cela en utilisant l'opérateur "+" normal.

Quand je suis passé au «<<», c'était comme par magie. C'était vraiment rapide.

Donc, je me suis souvenu de mes vieux temps - en quelque sorte 1998 - quand j'utilisais Java et concaténais String en utilisant '+' et que je passais de String à StringBuffer (et maintenant nous, développeur Java, avons StringBuilder).

Je crois que le processus de + / << dans le monde Ruby est le même que + / StringBuilder.append dans le monde Java.

Le premier réalloue l'objet entier en mémoire et l'autre pointe simplement vers une nouvelle adresse.


5

Concaténation vous dites? Et la #concatméthode alors?

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

En toute équité, concatest alias comme <<.


7
Il y a une autre façon de coller des cordes ensemble non mentionnée par les autres, et c'est par simple juxtaposition:"foo" "bar" 'baz" #=> "foobarabaz"
Boris Stitnicky

Note aux autres: Ce n'est pas censé être une simple citation, mais une double comme les autres. Méthode soignée!
Joshua Pinter

5

Voici d'autres façons de procéder:

"String1" + "String2"

"#{String1} #{String2}"

String1<<String2

Etc ...


2

Vous pouvez également utiliser %comme suit:

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

Cette approche fonctionne également avec les 'guillemets (simples).


2

Vous pouvez utiliser +ou <<opérateur, mais dans la .concatfonction rubis est la plus préférable, car elle est beaucoup plus rapide que les autres opérateurs. Vous pouvez l'utiliser comme.

source = "#{ROOT_DIR}/".concat(project.concat("/App.config"))

Je pense que vous avez un extra .après votre dernier concatnon?
lacostenycoder

1

La situation est importante, par exemple:

# 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

Dans le premier exemple, la concaténation avec l' +opérateur ne mettra pas à jour l' outputobjet, cependant, dans le deuxième exemple, l' <<opérateur mettra à jour l' outputobjet à chaque itération. Donc, pour le type de situation ci-dessus, <<c'est mieux.


1

Vous pouvez concaténer directement dans la définition de chaîne:

nombre_apellido = "#{customer['first_name']} #{customer['last_name']} #{order_id}"

0

Pour votre cas particulier, vous pouvez également utiliser Array#joinlors de la construction du type de chaîne de chemin de fichier:

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

Cela a un effet secondaire agréable de convertir automatiquement différents types en chaîne:

['foo', :bar, 1].join('/')
=>"foo/bar/1"

0

Pour les marionnettes:

$username = 'lala'
notify { "Hello ${username.capitalize}":
    withpath => false,
}
En utilisant notre site, vous reconnaissez avoir lu et compris notre politique liée aux cookies et notre politique de confidentialité.
Licensed under cc by-sa 3.0 with attribution required.