String-Verkettung in Ruby


364

Ich suche nach einer eleganteren Möglichkeit, Zeichenfolgen in Ruby zu verketten.

Ich habe die folgende Zeile:

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

Gibt es eine schönere Möglichkeit, dies zu tun?

Und was ist der Unterschied zwischen <<und +?


3
Diese Frage stackoverflow.com/questions/4684446/… ist eng verwandt.
Auge

<< Dies ist eine effizientere Methode zur Verkettung.
Taimoor Changaiz

Antworten:


574

Sie können dies auf verschiedene Arten tun:

  1. Wie du gezeigt hast <<, ist das aber nicht der übliche Weg
  2. Mit String-Interpolation

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

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

Die zweite Methode scheint in Bezug auf Gedächtnis / Geschwindigkeit effizienter zu sein als das, was ich gesehen habe (allerdings nicht gemessen). Alle drei Methoden werfen einen nicht initialisierten konstanten Fehler aus, wenn ROOT_DIR Null ist.

Wenn Sie mit Pfadnamen arbeiten, möchten Sie möglicherweise verwenden File.join, um zu vermeiden, dass das Pfadnamen-Trennzeichen durcheinander gebracht wird.

Am Ende ist es Geschmackssache.


7
Ich bin nicht sehr erfahren mit Rubin. In Fällen, in denen Sie viele Zeichenfolgen verketten, können Sie jedoch häufig die Leistung steigern, indem Sie die Zeichenfolgen an ein Array anhängen und am Ende die Zeichenfolge atomar zusammenfügen. Dann könnte << nützlich sein?
PEZ

1
Sie müssen ohnehin Speicher hinzufügen und die längere Zeichenfolge kopieren. << ist mehr oder weniger dasselbe wie +, außer dass Sie << mit einem einzelnen Zeichen können.
Keltia

9
Anstatt << für die Elemente eines Arrays zu verwenden, verwenden Sie Array # join, es ist viel schneller.
Grant Hutchins

94

Der +Operator ist die normale Verkettungsoption und wahrscheinlich der schnellste Weg, um Zeichenfolgen zu verketten.

Der Unterschied zwischen +und <<besteht darin, dass <<das Objekt auf der linken Seite geändert wird und +nicht.

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
Der Operator + ist definitiv nicht der schnellste Weg, um Zeichenfolgen zu verketten. Jedes Mal, wenn Sie es verwenden, wird eine Kopie erstellt, während << an Ort und Stelle verkettet und viel leistungsfähiger ist.
Böse Forelle

5
Für die meisten Anwendungen, Interpolation, +und <<werden in etwa gleich sein. Wenn Sie mit vielen oder wirklich großen Saiten zu tun haben, werden Sie möglicherweise einen Unterschied bemerken. Ich war überrascht, wie ähnlich sie auftraten. gist.github.com/2895311
Matt Burke

8
Ihre jruby-Ergebnisse werden durch die frühzeitige JVM-Überlastung gegen die Interpolation verzerrt. Wenn Sie die Testsuite 5.times do ... endfür jeden Interpreter mehrmals ausführen (im selben Prozess - also alles in einen Block einschließen), erhalten Sie genauere Ergebnisse. Meine Tests haben gezeigt, dass Interpolation die schnellste Methode für alle Ruby-Interpreter ist. Ich hätte erwartet <<, am schnellsten zu sein, aber deshalb messen wir uns.
Womble

Da ich mich nicht mit Ruby auskenne, bin ich gespannt, ob die Mutation auf dem Stack oder dem Heap durchgeführt wird. Wenn auf dem Haufen, sogar eine Mutationsoperation, die schneller zu sein scheint, beinhaltet wahrscheinlich irgendeine Form von Malloc. Ohne sie würde ich einen Pufferüberlauf erwarten. Die Verwendung des Stapels könnte ziemlich schnell sein, aber der resultierende Wert wird wahrscheinlich trotzdem auf dem Heap platziert, was eine Malloc-Operation erfordert. Am Ende erwarte ich, dass der Speicherzeiger eine neue Adresse ist, auch wenn die Variablenreferenz ihn wie eine In-Place-Mutation aussehen lässt. Gibt es wirklich einen Unterschied?
Robin Coe

79

Wenn Sie nur Pfade verketten, können Sie Rubys eigene File.join-Methode verwenden.

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

5
Dies scheint der richtige Weg zu sein, da Ruby sich dann darum kümmert, die richtige Zeichenfolge auf dem System mit verschiedenen Pfadtrennzeichen zu erstellen.
PEZ

26

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

Die Verwendung von <<aka concatist weitaus effizienter als +=, da letzteres ein zeitliches Objekt erstellt und das erste Objekt mit dem neuen Objekt überschreibt.

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

Ausgabe:

____________________________________________________________
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

Da dies ein Pfad ist, würde ich wahrscheinlich Array und Join verwenden:

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

9

Hier ist ein weiterer Maßstab, der von diesem Kern inspiriert ist . Es vergleicht Verkettung ( +), Anhängen ( <<) und Interpolation ( #{}) für dynamische und vordefinierte Zeichenfolgen.

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

Ausgabe:

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)

Schlussfolgerung: Die Interpolation in der MRT ist schwer.


Da Saiten jetzt unveränderlich werden, würde ich gerne einen neuen Maßstab dafür sehen.
Bibstha

7

Ich würde Pathname bevorzugen:

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

über <<und +von Ruby Docs:

+: Gibt einen neuen String zurück, der other_str enthält, das mit str verkettet ist

<<: Verkettet das angegebene Objekt mit str. Wenn das Objekt eine Fixnummer zwischen 0 und 255 ist, wird es vor der Verkettung in ein Zeichen konvertiert.

Der Unterschied besteht also darin, was zum ersten Operanden wird ( <<Änderungen an Ort und Stelle vornimmt, +neue Zeichenfolge zurückgibt, damit der Speicher schwerer wird) und was, wenn der erste Operand Fixnum ist ( <<wird hinzugefügt, als wäre es ein Zeichen mit einem Code, der dieser Zahl entspricht, +erhöht) Error)


2
Ich habe gerade festgestellt, dass das Aufrufen von '+' für einen Pfadnamen gefährlich sein kann, da der Empfängerpfad ignoriert wird, wenn das Argument ein absoluter Pfad ist : Pathname('/home/foo') + '/etc/passwd' # => #<Pathname:/etc/passwd>. Dies ist beabsichtigt, basierend auf dem Rubydoc-Beispiel. Scheint, dass File.join sicherer ist.
Kelvin

Außerdem müssen Sie aufrufen, (Pathname(ROOT_DIR) + project + 'App.config').to_swenn Sie ein Zeichenfolgenobjekt zurückgeben möchten.
Lacostenycoder

6

Lassen Sie mich Ihnen all meine Erfahrungen damit zeigen.

Ich hatte eine Abfrage, die 32.000 Datensätze zurückgab. Für jeden Datensatz habe ich eine Methode aufgerufen, um diesen Datenbankdatensatz in eine formatierte Zeichenfolge zu formatieren und diese dann zu einer Zeichenfolge zu verketten, die am Ende dieses Prozesses in eine Datei auf der Festplatte umgewandelt wird.

Mein Problem war, dass nach der Aufzeichnung, ungefähr 24k, der Prozess des Verkettens des Strings einen Schmerz auslöste.

Ich habe das mit dem regulären Operator '+' gemacht.

Als ich zu '<<' wechselte, war das wie Magie. War sehr schnell.

Also erinnerte ich mich an meine alten Zeiten - eine Art von 1998 - als ich Java verwendete und String mit '+' verkettete und von String zu StringBuffer wechselte (und jetzt haben wir, Java-Entwickler, den StringBuilder).

Ich glaube, dass der Prozess von + / << in der Ruby-Welt der gleiche ist wie der von + / StringBuilder.append in der Java-Welt.

Die erste ordnet das gesamte Objekt im Speicher neu zu und die andere zeigt nur auf eine neue Adresse.


5

Verkettung, sagst du? Wie wäre es dann mit #concatMethode?

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

In aller Fairness concatist als voreingenommen <<.


7
Es gibt noch eine Möglichkeit, Saiten zusammenzukleben, die von anderen nicht erwähnt wurden, und zwar durch bloße Gegenüberstellung:"foo" "bar" 'baz" #=> "foobarabaz"
Boris Stitnicky

Hinweis für andere: Das soll kein einfaches Zitat sein, sondern ein doppeltes wie der Rest. Ordentliche Methode!
Joshua Pinter

5

Hier sind weitere Möglichkeiten, dies zu tun:

"String1" + "String2"

"#{String1} #{String2}"

String1<<String2

Und so weiter ...


2

Sie können auch %Folgendes verwenden:

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

Dieser Ansatz funktioniert auch mit '(einfachen) Anführungszeichen.


2

Sie können +oder <<Operator verwenden, aber in Ruby .concatist die Funktion am meisten bevorzugt, da sie viel schneller als andere Operatoren ist. Sie können es wie verwenden.

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

Ich denke du hast ein Extra .nach deinem letzten concatNein?
Lacostenycoder

1

Situation ist wichtig, zum Beispiel:

# 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

Im ersten Beispiel +wird das outputObjekt durch Verketten mit dem Operator nicht aktualisiert. Im zweiten Beispiel <<aktualisiert der Operator das outputObjekt jedoch bei jeder Iteration. Für die oben genannte Art von Situation <<ist es also besser.


1

Sie können die Zeichenfolgendefinition direkt verketten:

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

0

Für Ihren speziellen Fall können Sie Array#joinbeim Erstellen des Dateipfadtyps auch eine Zeichenfolge verwenden:

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

Dies hat einen angenehmen Nebeneffekt, wenn verschiedene Typen automatisch in Zeichenfolgen konvertiert werden:

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

0

Für Marionette:

$username = 'lala'
notify { "Hello ${username.capitalize}":
    withpath => false,
}
Durch die Nutzung unserer Website bestätigen Sie, dass Sie unsere Cookie-Richtlinie und Datenschutzrichtlinie gelesen und verstanden haben.
Licensed under cc by-sa 3.0 with attribution required.