Je cherche un moyen plus élégant de concaténer des chaînes en Ruby.
J'ai la ligne suivante:
source = "#{ROOT_DIR}/" << project << "/App.config"
Y a-t-il une meilleure façon de faire cela?
Et d'ailleurs, quelle est la différence entre <<
et +
?
Vous pouvez le faire de plusieurs manières:
<<
mais ce n'est pas le habituel wayAvec interpolation de chaîne
source = "#{ROOT_DIR}/#{project}/App.config"
avec +
source = "#{ROOT_DIR}/" + project + "/App.config"
La deuxième méthode semble être plus efficace en termes de mémoire/vitesse que ce que j'ai vu (non mesurée cependant). Les trois méthodes génèrent une erreur constante non initialisée lorsque ROOT_DIR est nul.
Lorsque vous traitez avec des noms de chemin, vous pouvez utiliser File.join
pour éviter de vous embrouiller avec le séparateur de chemin.
En fin de compte, c'est une question de goût.
L'opérateur +
est le choix de concaténation normal et constitue probablement le moyen le plus rapide de concaténer des chaînes.
La différence entre +
et <<
est que <<
modifie l'objet sur son côté gauche et que +
ne le fait 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"
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')
de http://greyblake.com/blog/2012/09/02/Ruby-perfomance-tricks/
Utiliser <<
aka concat
est 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
sortie:
____________________________________________________________
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)
Puisqu'il s'agit d'un chemin, j'utiliserais probablement un tableau et je participerais:
source = [ROOT_DIR, project, 'App.config'] * '/'
Voici un autre repère inspiré par this Gist . Il compare la concaténation (+
), l'ajout (<<
) et l'interpolation (#{}
) des 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
sortie:
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.
Laissez-moi vous montrer toute mon expérience avec cela.
J'avais une requête qui renvoyait 32 000 enregistrements, pour chaque enregistrement, j'ai appelé une méthode permettant de formater cet enregistrement de base de données en une chaîne formatée, puis de la concaténer en une chaîne qui, à la fin de tout ce processus, deviendrait un fichier sur disque.
Mon problème était que, d’après le disque, aux alentours de 24k, le processus de concaténation de la chaîne a tourné mal.
Je faisais cela en utilisant l'opérateur '+' habituel.
Quand je suis passé au '<<, c'était comme par magie. Était vraiment rapide.
Donc, je me souvenais de mon époque - de 1998 - lorsque j’utilisais Java et que je concaténais String avec '+' et que je passais de String à StringBuffer (et maintenant, nous, développeurs Java, avons StringBuilder).
Je crois que le processus de +/<< dans le monde Ruby est identique à celui de +/StringBuilder.append dans le monde Java.
Le premier réaffecte l'objet entier en mémoire et le second pointe vers une nouvelle adresse.
Je préférerais utiliser Pathname:
require 'pathname' # pathname is in stdlib
Pathname(ROOT_DIR) + project + 'App.config'
à propos de <<
et +
de la documentation Ruby:
+
: retourne un new String contenant other_str concaténé à str
<<
: concatène l'objet donné en str. Si l'objet est un Fixnum compris entre 0 et 255, il est converti en caractère avant la concaténation.
la différence est donc dans ce qui devient le premier opérande (<<
fait les changements à la place, +
renvoie la nouvelle chaîne, donc la mémoire est plus lourde) et ce qui sera si le premier opérande est Fixnum (<<
s'ajoutera comme s'il s'agissait d'un caractère avec un code égal à ce nombre , +
provoquera une erreur)
Concaténation dites-vous? Que diriez-vous de la méthode #concat
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 justice, concat
a pour alias <<
.
Voici d'autres moyens de le faire:
"String1" + "String2"
"#{String1} #{String2}"
String1<<String2
Etc ...
Vous pouvez utiliser l'opérateur +
ou <<
, mais en Ruby, la fonction .concat
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"))
Vous pouvez également utiliser %
comme suit:
source = "#{ROOT_DIR}/%s/App.config" % project
Cette approche fonctionne également avec le guillemet '
(unique).
Vous pouvez concaténer directement dans la définition de chaîne:
nombre_apellido = "#{customer['first_name']} #{customer['last_name']} #{order_id}"
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'objet output
, mais dans le deuxième exemple, l'opérateur <<
mettra à jour l'objet output
à chaque itération. Donc, pour le type de situation ci-dessus, <<
est préférable.