Szukam bardziej eleganckiego sposobu łączenia łańcuchów w Ruby.
Mam następującą linię:
source = "#{ROOT_DIR}/" << project << "/App.config"
Czy jest na to lepszy sposób?
A jeśli o to chodzi, jaka jest różnica między <<
i +
?
Szukam bardziej eleganckiego sposobu łączenia łańcuchów w Ruby.
Mam następującą linię:
source = "#{ROOT_DIR}/" << project << "/App.config"
Czy jest na to lepszy sposób?
A jeśli o to chodzi, jaka jest różnica między <<
i +
?
Odpowiedzi:
Możesz to zrobić na kilka sposobów:
<<
ale to nie jest zwykły sposóbZ interpolacją ciągów
source = "#{ROOT_DIR}/#{project}/App.config"
z +
source = "#{ROOT_DIR}/" + project + "/App.config"
Druga metoda wydaje się być bardziej wydajna pod względem pamięci / prędkości od tego, co widziałem (choć nie zmierzone). Wszystkie trzy metody wygenerują niezainicjowany stały błąd, gdy parametr ROOT_DIR ma wartość zero.
W przypadku nazw ścieżek możesz użyć ich, File.join
aby uniknąć pomieszania z separatorem nazw ścieżek.
Ostatecznie jest to kwestia gustu.
+
Operator jest normalny wybór konkatenacji, a to chyba najszybszy sposób na strunach złączyć.
Różnica między +
i <<
polega na tym, że <<
zmienia obiekt po jego lewej stronie, a +
nie zmienia.
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"
+
i <<
będą o tym samym. Jeśli masz do czynienia z wieloma łańcuchami lub naprawdę dużymi, możesz zauważyć różnicę. Byłem zaskoczony, jak podobne były. gist.github.com/2895311
5.times do ... end
blok) dla każdego tłumacza, uzyskasz dokładniejsze wyniki. Moje testy wykazały, że interpolacja jest najszybszą metodą we wszystkich tłumaczach Ruby. Spodziewałbym <<
się, że będę najszybszy, ale dlatego porównujemy.
z http://greyblake.com/blog/2012/09/02/ruby-perfomance-tricks/
Używanie <<
aka concat
jest znacznie bardziej wydajne niż +=
, ponieważ ten ostatni tworzy obiekt tymczasowy i zastępuje pierwszy obiekt nowym obiektem.
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
wynik:
____________________________________________________________
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)
Ponieważ jest to ścieżka, prawdopodobnie użyłbym tablicy i dołączył:
source = [ROOT_DIR, project, 'App.config'] * '/'
Oto kolejny punkt odniesienia zainspirowany tą istotą . Porównuje konkatenację ( +
), dopisywanie ( <<
) i interpolację ( #{}
) dla ciągów dynamicznych i predefiniowanych.
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
wynik:
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)
Wniosek: interpolacja w MRI jest ciężka.
Wolałbym używać Pathname:
require 'pathname' # pathname is in stdlib
Pathname(ROOT_DIR) + project + 'App.config'
o <<
i +
od ruby docs:
+
: Zwraca nowy ciąg zawierający other_str połączony z str
<<
: Łączy dany obiekt z ul. Jeśli obiektem jest Fixnum od 0 do 255, przed konkatenacją jest on konwertowany na znak.
więc różnica polega na tym, co stanie się z pierwszym operandem ( <<
dokonuje zmian w miejscu, +
zwraca nowy ciąg znaków, więc pamięć jest cięższa) i co będzie, jeśli pierwszym operandem będzie Fixnum ( <<
doda, jakby to był znak z kodem równym tej liczbie, +
podniesie błąd)
Pathname('/home/foo') + '/etc/passwd' # => #<Pathname:/etc/passwd>
. Jest to zgodne z projektem, opartym na przykładzie Rubydoc. Wydaje się, że File.join jest bezpieczniejszy.
(Pathname(ROOT_DIR) + project + 'App.config').to_s
jeśli chcesz zwrócić obiekt typu string.
Pozwól, że pokażę ci całe moje doświadczenie z tym.
Miałem zapytanie, które zwróciło 32k rekordów, dla każdego rekordu wywołałem metodę sformatowania tego rekordu bazy danych w sformatowany ciąg, a następnie powiązania go w ciąg, który pod koniec całego procesu zamieni się w plik na dysku.
Mój problem polegał na tym, że według rekordu, około 24k, proces konkatenacji Sznurka przerodził się w ból.
Robiłem to za pomocą zwykłego operatora „+”.
Kiedy zmieniłem na „<<” było jak magia. Było naprawdę szybko.
Pamiętam więc swoje stare czasy - coś w 1998 roku - kiedy używałem Javy i łączyłem String za pomocą „+” i zmieniłem z String na StringBuffer (a teraz my, deweloper Java, mamy StringBuilder).
Wierzę, że proces + / << w świecie Ruby jest taki sam jak + / StringBuilder.append w świecie Java.
Pierwszy przenosi cały obiekt w pamięci, a drugi wskazuje nowy adres.
Konkatenacja, którą mówisz? A co powiesz na #concat
metodę?
a = 'foo'
a.object_id #=> some number
a.concat 'bar' #=> foobar
a.object_id #=> same as before -- string a remains the same object
Szczerze mówiąc, concat
jest aliasowany jako <<
.
"foo" "bar" 'baz" #=> "foobarabaz"
Oto więcej sposobów na zrobienie tego:
"String1" + "String2"
"#{String1} #{String2}"
String1<<String2
I tak dalej ...
Możesz użyć +
lub <<
operatora, ale w Ruby .concat
jest najbardziej preferowana, ponieważ jest znacznie szybsza niż inne operatory. Możesz go używać jak.
source = "#{ROOT_DIR}/".concat(project.concat("/App.config"))
.
po ostatnim concat
nie?
Sytuacja ma znaczenie, na przykład:
# 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
W pierwszym przykładzie konkatenacja z +
operatorem nie spowoduje aktualizacji output
obiektu, jednak w drugim przykładzie <<
operator zaktualizuje output
obiekt przy każdej iteracji. Tak więc, dla powyższego rodzaju sytuacji, <<
jest lepiej.
W twoim konkretnym przypadku możesz również użyć Array#join
podczas konstruowania typu ścieżki pliku:
string = [ROOT_DIR, project, 'App.config'].join('/')]
Ma to przyjemny efekt uboczny polegający na automatycznej konwersji różnych typów na ciąg znaków:
['foo', :bar, 1].join('/')
=>"foo/bar/1"