Я шукаю більш елегантний спосіб об'єднання струн у 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 дорівнює нулю.
Маючи справу з іменами шляхів, можливо, ви хочете використовувати, 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/
Використання <<
ака 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'] * '/'
Ось ще один орієнтир, натхненний цією суттю . Він порівнює конкатенацію ( +
), додавання ( <<
) та інтерполяцію ( #{}
) для динамічних та заздалегідь заданих рядків.
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
<<
: Об'єднає даний об'єкт з str. Якщо об'єктом є Fixnum між 0 і 255, він перетворюється в символ перед конкатенацією.
тому різниця полягає в тому, що стає першим операндом ( <<
вносить зміни на місце, +
повертає нову рядок, щоб вона була важче пам’яті) і що буде, якщо перший операнд буде Fixnum ( <<
додасть, ніби він був символом з кодом, рівним цьому номеру, +
підніме помилка)
Pathname('/home/foo') + '/etc/passwd' # => #<Pathname:/etc/passwd>
. Це за дизайном, заснованим на прикладі рубідока. Здається, що File.join безпечніший.
(Pathname(ROOT_DIR) + project + 'App.config').to_s
якщо ви хочете повернути рядковий об'єкт.
Дозвольте показати вам увесь мій досвід з цим.
У мене був запит, який повертав 32 к записів, для кожного запису я закликав метод відформатувати цю базу даних у форматовану рядок і потім об'єднати її в String, який в кінці всього цього процесу перетвориться на файл на диску.
Моя проблема полягала в тому, що, за записом, близько 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
І так далі ...
Ви можете використовувати +
або <<
оператора, але .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"