Я ищу более элегантный способ объединения строк в Ruby.
У меня есть следующая строка:
source = "#{ROOT_DIR}/" << project << "/App.config"
Есть ли лучший способ сделать это?
И в этом отношении, в чем разница между <<
и +
?
Я ищу более элегантный способ объединения строк в Ruby.
У меня есть следующая строка:
source = "#{ROOT_DIR}/" << project << "/App.config"
Есть ли лучший способ сделать это?
И в этом отношении, в чем разница между <<
и +
?
Ответы:
Вы можете сделать это несколькими способами:
<<
но это не обычный способСо строковой интерполяцией
source = "#{ROOT_DIR}/#{project}/App.config"
с +
source = "#{ROOT_DIR}/" + project + "/App.config"
Второй метод кажется более эффективным с точки зрения памяти / скорости из того, что я видел (хотя и не измерялось). Все три метода выдают неинициализированную постоянную ошибку, когда ROOT_DIR равен nil.
При работе с путевыми именами вы можете использовать их, File.join
чтобы избежать путаницы с разделителем путевых имен.
В конце концов, это вопрос вкуса.
+
Оператор нормальный выбор конкатенации, и, вероятно , самый быстрый способ конкатенации строк.
Разница между +
и <<
заключается в том, <<
что объект изменяется с левой стороны, а +
не изменяется .
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"
+
и <<
будет примерно одинаковой. Если вы имеете дело со многими или очень большими строками, то вы можете заметить разницу. Я был удивлен тем, насколько похожи они выступили. gist.github.com/2895311
5.times do ... end
блок) для каждого интерпретатора, вы получите более точные результаты. Мое тестирование показало, что интерполяция - самый быстрый метод среди всех интерпретаторов Ruby. Я ожидал <<
бы быть самым быстрым, но именно поэтому мы тестируем.
с http://greyblake.com/blog/2012/09/02/ruby-perfomance-tricks/
Использование <<
aka concat
намного эффективнее +=
, поскольку последний создает временный объект и переопределяет первый объект новым объектом.
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
вывод:
____________________________________________________________
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)
Поскольку это путь, я бы, вероятно, использовал массив и соединение:
source = [ROOT_DIR, project, 'App.config'] * '/'
Вот еще один ориентир, вдохновленный этой сущностью . Он сравнивает concatenation ( +
), appending ( <<
) и interpolation ( #{}
) для динамических и предопределенных строк.
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
вывод:
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)
Вывод: интерполяция в МРТ тяжелая.
Я бы предпочел использовать Pathname:
require 'pathname' # pathname is in stdlib
Pathname(ROOT_DIR) + project + 'App.config'
о <<
и +
из рубиновых документов:
+
: Возвращает новую строку, содержащую other_str, соединенную с str
<<
: Объединяет данный объект на ул. Если объект является Fixnum между 0 и 255, он преобразуется в символ перед конкатенацией.
так что разница в том, что становится первым операндом ( <<
вносит изменения на месте, +
возвращает новую строку, так что она становится тяжелее в памяти), и то, что будет, если первый операнд будет Fixnum ( <<
будет добавлено, как если бы это был символ с кодом, равным этому числу, +
повысит ошибка)
Pathname('/home/foo') + '/etc/passwd' # => #<Pathname:/etc/passwd>
. Это разработано на основе примера Rubydoc. Кажется, что File.join безопаснее.
(Pathname(ROOT_DIR) + project + 'App.config').to_s
если вы хотите вернуть строковый объект.
Позвольте мне показать вам весь мой опыт с этим.
У меня был запрос, который возвращал 32 тыс. Записей, для каждой записи я вызывал метод для форматирования этой записи базы данных в форматированную строку и затем объединял ее в строку, которая в конце всего этого процесса превращается в файл на диске.
Моя проблема заключалась в том, что, согласно записи, около 24 тыс., Процесс конкатенации строки включал боль.
Я делал это, используя обычный оператор «+».
Когда я перешел на «<<», это было похоже на магию. Было действительно быстро.
Итак, я вспомнил свои старые времена, вроде 1998 года, когда я использовал Java и конкатенировал String с помощью '+' и изменил с String на StringBuffer (и теперь у нас, разработчика Java, есть StringBuilder).
Я считаю, что процесс + / << в мире Ruby такой же, как + / StringBuilder.append в мире Java.
Первые перераспределяют весь объект в памяти, а другие просто указывают на новый адрес.
Сцепление говорите? Как насчет #concat
метода тогда?
a = 'foo'
a.object_id #=> some number
a.concat 'bar' #=> foobar
a.object_id #=> same as before -- string a remains the same object
Справедливости ради, concat
псевдоним как <<
.
"foo" "bar" 'baz" #=> "foobarabaz"
Вот еще несколько способов сделать это:
"String1" + "String2"
"#{String1} #{String2}"
String1<<String2
И так далее ...
Вы можете использовать оператор +
или <<
оператор, но в Ruby .concat
функция является наиболее предпочтительной, поскольку она намного быстрее, чем другие операторы. Вы можете использовать это как.
source = "#{ROOT_DIR}/".concat(project.concat("/App.config"))
.
после вашего последнего concat
нет?
Ситуация имеет значение, например:
# 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
В первом примере объединение с +
оператором не будет обновлять output
объект, однако во втором примере <<
оператор будет обновлять output
объект с каждой итерацией. Таким образом, для ситуации вышеупомянутого типа, <<
лучше.
Для вашего конкретного случая вы также можете использовать Array#join
при построении пути к файлу тип строки:
string = [ROOT_DIR, project, 'App.config'].join('/')]
Это имеет приятный побочный эффект автоматического преобразования различных типов в строку:
['foo', :bar, 1].join('/')
=>"foo/bar/1"