Łączenie łańcuchów w Ruby


364

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 +?


3
To pytanie stackoverflow.com/questions/4684446/... jest ściśle powiązane.
Oko

<< jest to bardziej wydajny sposób na konkatenację.
Taimoor Changaiz,

Odpowiedzi:


574

Możesz to zrobić na kilka sposobów:

  1. Jak pokazałeś, <<ale to nie jest zwykły sposób
  2. Z interpolacją ciągów

    source = "#{ROOT_DIR}/#{project}/App.config"
  3. 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.joinaby uniknąć pomieszania z separatorem nazw ścieżek.

Ostatecznie jest to kwestia gustu.


7
Nie mam zbyt dużego doświadczenia z rubinem. Ale ogólnie w przypadkach, w których łączy się wiele ciągów, często można zwiększyć wydajność, dołączając ciągi do tablicy, a następnie na końcu układając ciąg atomowo. W takim razie << może być przydatne?
PEZ,

1
W każdym razie musisz dodać pamięć i skopiować do niej dłuższy ciąg. << jest mniej więcej takie samo jak +, tyle że można << za pomocą jednego znaku.
Keltia,

9
Zamiast używać << na elementach tablicy, użyj Array # join, jest to znacznie szybsze.
Grant Hutchins,

94

+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"

32
Operator + nie jest zdecydowanie najszybszym sposobem łączenia łańcuchów. Za każdym razem, gdy go używasz, tworzy kopię, podczas gdy << łączy się w miejscu i jest znacznie wydajniejszy.
Evil Trout

5
Dla większości zastosowań, interpolacji +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
Matt Burke,

8
Twoje niepewne wyniki są zniekształcone w stosunku do interpolacji przez wczesne przeciążenie JVM. Jeśli uruchomisz pakiet testowy kilka razy (w tym samym procesie - więc zawiń wszystko, powiedzmy 5.times do ... endblok) 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.
womble

Nie jestem zbyt obeznany z Ruby, jestem ciekawy, czy mutacja jest wykonywana na stosie czy na stosie? Na stosie nawet operacja mutacji, która wydaje się szybsza, prawdopodobnie wiąże się z pewną formą malloc. Bez tego oczekiwałbym przepełnienia bufora. Korzystanie ze stosu może być dość szybkie, ale wynikowa wartość i tak prawdopodobnie jest umieszczana na stercie, co wymaga operacji malloc. W końcu spodziewam się, że wskaźnik pamięci będzie nowym adresem, nawet jeśli odwołanie do zmiennej sprawia, że ​​wygląda jak mutacja lokalna. Czy naprawdę jest różnica?
Robin Coe

79

Jeśli tylko łączysz ścieżki, możesz użyć własnej metody Ruby File.join.

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

5
Wydaje się, że jest to właściwa droga, ponieważ Ruby zajmie się tworzeniem poprawnego ciągu w systemie z różnymi separatorami ścieżek.
PEZ,

26

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

Używanie <<aka concatjest 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)

11

Ponieważ jest to ścieżka, prawdopodobnie użyłbym tablicy i dołączył:

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

9

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.


Ponieważ ciągi zaczynają być niezmienne, chciałbym zobaczyć nowy punkt odniesienia w tym zakresie.
bibstha

7

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)


2
Właśnie odkryłem, że nazywając „+” na nazwę ścieżki może być niebezpieczne, ponieważ jeśli arg jest ścieżka bezwzględna, ścieżka odbiornik jest ignorowany: 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.
Kelvin

musisz także zadzwonić, (Pathname(ROOT_DIR) + project + 'App.config').to_sjeśli chcesz zwrócić obiekt typu string.
lacostenycoder

6

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.


5

Konkatenacja, którą mówisz? A co powiesz na #concatmetodę?

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, concatjest aliasowany jako <<.


7
Jest jeszcze jeden sposób sklejania sznurków razem, o których nie wspominają inni, a mianowicie przez zwykłe zestawienie:"foo" "bar" 'baz" #=> "foobarabaz"
Boris Stitnicky

Uwaga dla innych: To nie powinien być pojedynczy cytat, ale podwójny jak reszta. Zgrabna metoda!
Joshua Pinter

5

Oto więcej sposobów na zrobienie tego:

"String1" + "String2"

"#{String1} #{String2}"

String1<<String2

I tak dalej ...


2

Możesz również użyć %w następujący sposób:

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

To podejście działa również z '(pojedynczym) znakiem cudzysłowu.


2

Możesz użyć +lub <<operatora, ale w Ruby .concatjest najbardziej preferowana, ponieważ jest znacznie szybsza niż inne operatory. Możesz go używać jak.

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

Myślę, że masz dodatkowe .po ostatnim concatnie?
lacostenycoder

1

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 outputobiektu, jednak w drugim przykładzie <<operator zaktualizuje outputobiekt przy każdej iteracji. Tak więc, dla powyższego rodzaju sytuacji, <<jest lepiej.


1

Możesz połączyć konkatenację w definicję ciągu bezpośrednio:

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

0

W twoim konkretnym przypadku możesz również użyć Array#joinpodczas 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"

0

Dla Puppet:

$username = 'lala'
notify { "Hello ${username.capitalize}":
    withpath => false,
}
Korzystając z naszej strony potwierdzasz, że przeczytałeś(-aś) i rozumiesz nasze zasady używania plików cookie i zasady ochrony prywatności.
Licensed under cc by-sa 3.0 with attribution required.