Concatenação de strings em Ruby


364

Estou procurando uma maneira mais elegante de concatenar seqüências de caracteres em Ruby.

Eu tenho a seguinte linha:

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

Existe uma maneira melhor de fazer isso?

E, nesse caso, qual é a diferença entre <<e +?


3
Esta pergunta stackoverflow.com/questions/4684446/… está altamente relacionada.
Olho

<< essa é a maneira mais eficiente de concatenar.
Taimoor Changaiz

Respostas:


575

Você pode fazer isso de várias maneiras:

  1. Como você mostrou, <<mas essa não é a maneira usual
  2. Com interpolação de cordas

    source = "#{ROOT_DIR}/#{project}/App.config"
  3. com +

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

O segundo método parece ser mais eficiente em termos de memória / velocidade do que vi (embora não seja medido). Todos os três métodos lançarão um erro constante não inicializado quando ROOT_DIR for nulo.

Ao lidar com nomes de caminho, convém usar File.joinpara evitar bagunçar o separador de nomes de caminho.

No final, é uma questão de gosto.


7
Não tenho muita experiência com rubi. Mas geralmente nos casos em que você concatena muitas seqüências de caracteres, geralmente obtém desempenho anexando-as a uma matriz e, no final, juntando-as atomicamente. Então << poderia ser útil?
PEZ

11
Você precisará adicionar memória e copiar a string mais longa de qualquer maneira. << é mais ou menos o mesmo que +, exceto que você pode << com um único caractere.
Keltia

9
Em vez de usar << nos elementos de uma matriz, use Array # join, é muito mais rápido.
Grant Hutchins

94

O +operador é a opção de concatenação normal e provavelmente é a maneira mais rápida de concatenar seqüências de caracteres.

A diferença entre +e <<é que <<altera o objeto no lado esquerdo e +não.

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
O operador + definitivamente não é a maneira mais rápida de concatenar seqüências de caracteres. Toda vez que você o usa, ele faz uma cópia, enquanto << concatena no lugar e tem muito mais desempenho.
Evil Trout

5
Para a maioria dos usos, interpolação +e <<serão praticamente os mesmos. Se você está lidando com muitas seqüências de caracteres, ou realmente muito grandes, pode notar uma diferença. Fiquei surpreso com o desempenho parecido deles. gist.github.com/2895311
Matt Burke

8
Seus resultados do jruby são inclinados contra a interpolação pela sobrecarga da JVM de execução inicial. Se você executar o conjunto de testes várias vezes (no mesmo processo - envolva tudo em um 5.times do ... endbloco, por exemplo) para cada intérprete, você terá resultados mais precisos. Meus testes mostraram que a interpolação é o método mais rápido em todos os intérpretes Ruby. Eu esperava <<ser o mais rápido, mas é por isso que fazemos benchmark.
Womble

Não sendo muito versado em Ruby, estou curioso para saber se a mutação é realizada na pilha ou pilha? Se no heap, mesmo uma operação de mutação, que parece ser mais rápida, provavelmente envolve alguma forma de malloc. Sem ele, eu esperaria um estouro de buffer. O uso da pilha pode ser bem rápido, mas o valor resultante provavelmente é colocado no heap de qualquer maneira, exigindo uma operação malloc. No final, espero que o ponteiro da memória seja um novo endereço, mesmo que a referência da variável faça com que pareça uma mutação no local. Então, realmente, há alguma diferença?
Robin Coe

79

Se você está apenas concatenando caminhos, pode usar o próprio método File.join do Ruby.

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

5
Este parece ser o caminho a seguir desde então, o ruby ​​cuidará da criação da string correta no sistema com diferentes separadores de caminho.
PEZ

26

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

Usar o <<aka concaté muito mais eficiente do que +=, pois o último cria um objeto temporal e substitui o primeiro pelo novo.

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

resultado:

____________________________________________________________
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

Como esse é um caminho, eu provavelmente usaria o array e ingressaria em:

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

9

Aqui está outra referência inspirada por essa essência . Ele compara concatenação ( +), anexo ( <<) e interpolação ( #{}) para seqüências de caracteres dinâmicas e predefinidas.

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

resultado:

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)

Conclusão: a interpolação na RM é pesada.


Como as strings estão começando a ser imutáveis ​​agora, eu adoraria ver uma nova referência para isso.
bibstha

7

Eu preferiria usar o nome do caminho:

require 'pathname' # pathname is in stdlib
Pathname(ROOT_DIR) + project + 'App.config'

sobre <<e +de ruby ​​docs:

+: Retorna uma nova String contendo other_str concatenada para str

<<: Concatena o objeto especificado para str. Se o objeto for um Fixnum entre 0 e 255, ele será convertido em um caractere antes da concatenação.

então a diferença está no que se torna no primeiro operando ( <<faz alterações no lugar, +retorna uma nova string para que a memória fique mais pesada) e qual será o primeiro operando é Fixnum ( <<será adicionado como se fosse um caractere com código igual a esse número, +aumentará erro)


2
Eu só descobri que chamar '+' em um Pathname pode ser perigoso, porque se o arg é um caminho absoluto, o caminho receptor é ignorado: Pathname('/home/foo') + '/etc/passwd' # => #<Pathname:/etc/passwd>. Isso ocorre por design, com base no exemplo do rubydoc. Parece que File.join é mais seguro.
12117 Kelvin

Também é necessário chamar (Pathname(ROOT_DIR) + project + 'App.config').to_sse você deseja retornar um objeto string.
lacostenycoder

6

Deixe-me mostrar a você toda a minha experiência com isso.

Eu tive uma consulta que retornou 32k de registros, para cada registro eu chamei um método para formatar esse registro de banco de dados em uma string formatada e concatená-la em uma String que ao final de todo esse processo se transformará em um arquivo em disco.

Meu problema era que, segundo os registros, em torno de 24k, o processo de concatenação da String causava dor.

Eu estava fazendo isso usando o operador '+' regular.

Quando mudei para o '<<' era como mágica. Foi muito rápido.

Então, lembrei-me dos meus velhos tempos - tipo 1998 - quando eu estava usando Java e concatenando String usando '+' e mudei de String para StringBuffer (e agora nós, desenvolvedor Java, temos o StringBuilder).

Eu acredito que o processo de + / << no mundo Ruby é o mesmo que + / StringBuilder.append no mundo Java.

O primeiro realoca o objeto inteiro na memória e o outro apenas aponta para um novo endereço.


5

Concatenação você diz? Como sobre o #concatmétodo então?

a = 'foo'
a.object_id #=> some number
a.concat 'bar' #=> foobar
a.object_id #=> same as before -- string a remains the same object

Com toda a justiça, concaté alias como <<.


7
Não é mais uma forma de unir cordas juntos, não mencionado por outros, e que é por mera justaposição:"foo" "bar" 'baz" #=> "foobarabaz"
Boris Stitnicky

Nota para outras pessoas: não é para ser uma citação única, mas uma citação dupla como o resto. Método puro!
Joshua Pinter

5

Aqui estão mais maneiras de fazer isso:

"String1" + "String2"

"#{String1} #{String2}"

String1<<String2

E assim por diante ...


2

Você também pode usar da %seguinte maneira:

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

Essa abordagem também funciona com 'aspas (simples).


2

Você pode usar +ou <<operador, mas na .concatfunção ruby é o mais preferível, pois é muito mais rápido que outros operadores. Você pode usá-lo como.

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

Eu acho que você tem um extra .após o seu último concatnão?
lacostenycoder

1

A situação importa, por exemplo:

# 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

No primeiro exemplo, concatenar com o +operador não atualizará o outputobjeto, no entanto, no segundo exemplo, o <<operador atualizará o outputobjeto a cada iteração. Então, para o tipo de situação acima, <<é melhor.


1

Você pode concatenar diretamente na definição de sequência:

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

0

Para o seu caso em particular, você também pode usar Array#joinao construir o tipo de string do caminho do arquivo:

string = [ROOT_DIR, project, 'App.config'].join('/')]

Isso tem um efeito colateral agradável de converter automaticamente tipos diferentes em string:

['foo', :bar, 1].join('/')
=>"foo/bar/1"

0

Para fantoche:

$username = 'lala'
notify { "Hello ${username.capitalize}":
    withpath => false,
}
Ao utilizar nosso site, você reconhece que leu e compreendeu nossa Política de Cookies e nossa Política de Privacidade.
Licensed under cc by-sa 3.0 with attribution required.