Ruby中的字符串拼接和插值

84

我刚开始学习Ruby(第一次编程),关于变量和各种写代码的方式,有一个基本语法问题。

Chris Pine的“学习编程”教给了我如下编写基本程序的方法...

num_cars_again= 2
puts 'I own ' + num_cars_again.to_s + ' cars.'

这样做也可以,但是我偶然发现了ruby.learncodethehardway.com上的教程,学习了如何用以下方式编写完全相同的程序...

num_cars= 2
puts "I own #{num_cars} cars."

两种写法输出的结果相同,但很明显,选项2是更简洁的方法。

是否有任何特定的原因让我使用其中一种格式而不是另一种格式?


6
我讨厌初学者书籍经常教授一种不自然的做事方式,却没有至少告诉你存在可替代的方法。对于一个合理的问题没有得到赞同,我给一个+1。 - Andrew Grimm
有更多的选项在https://dev59.com/v3RC5IYBdhLWcg3wMeHf上进行讨论。 - sameers
5个回答

77
无论什么时候有"TIMTOWTDI"(有多种方法可供选择),您都应该寻找利弊。使用"字符串插值"(第二个)而不是"字符串拼接"(第一个):
优点:
  • 键入更少
  • 自动为您调用to_s
  • 在Ruby社区内更符合惯例
  • 运行时执行速度更快
缺点:
  • 自动为您调用to_s (可能您认为你有一个字符串,但to_s 的表示并不是您想要的,并且隐藏了它不是一个字符串的事实)
  • 需要使用"来界定您的字符串,而不是'(也许您习惯使用',或者您之前使用的字符串只需要稍后使用字符串插值)

25
不要忘记“更快”的方面。在这个例子中,字符串连接需要创建3个字符串,而字符串插值只创建一个。 - Dominik Honnef
4
如果你关心基准测试,它也更快:在REPL中尝试我 - leemeichin
2
非常感谢您的回答。我有一个快速问题。为什么Chris Pine的书会教授更长的方法?也许对于初学者来说,学习使用更长的方法会更好?他的书中说大多数时候懒惰=更好,所以我想知道是否因为某种原因(因为我只是在学习),我应该继续按照他的方式做还是采用更好的方法前进。有什么想法吗? - Jeff H.
6
我的猜测是,对于新程序员来说,“使用众所周知的运算符将字符串连接在一起”比“使用自定义语法评估任意代码,调用to_s并将其注入到字符串中间”这个概念更简单。学习任何新事物时,通常存在“易于理解的简单方式”与“专业人士使用的方式”之间的变化。 - Phrogz
7
我知道我来参加这个讨论太晚了,但是我这样做有两个主要原因。第一,就像Phrogz提出的那样:我尽可能地想保持简单,使用他们已经了解的概念。在第一版中我甚至没有涵盖双引号字符串!当学习编程时,最不想要的就是为创建字符串而有六种不同的语法。第二,因为隐式的to_s。对于我们这些了解类型和转换的人来说,这是一个方便。但对于新手程序员来说,理解这些概念真的非常重要。 - Chris Pine
显示剩余5条评论

9

插值和连接各有其优缺点。下面我提供了一个基准测试,清楚地展示了何时使用连接和何时使用插值。

require 'benchmark'

iterations = 1_00_000
firstname = 'soundarapandian'
middlename = 'rathinasamy'
lastname = 'arumugam'

puts 'With dynamic new strings'
puts '===================================================='
5.times do
  Benchmark.bm(10) do |benchmark|
    benchmark.report('concatination') do
      iterations.times do
        'Mr. ' + firstname + middlename + lastname + ' aka soundar'
      end
    end

    benchmark.report('interpolaton') do
      iterations.times do
        "Mr. #{firstname} #{middlename} #{lastname} aka soundar"
      end
    end
  end
  puts '--------------------------------------------------'
end

puts 'With predefined strings'
puts '===================================================='
5.times do
  Benchmark.bm(10) do |benchmark|
    benchmark.report('concatination') do
      iterations.times do
        firstname + middlename + lastname
      end
    end

    benchmark.report('interpolaton') do
      iterations.times do
        "#{firstname} #{middlename} #{lastname}"
      end
    end
  end
  puts '--------------------------------------------------'
end

以下是基准测试结果。
Without predefined strings
====================================================
                 user     system      total        real
concatination  0.170000   0.000000   0.170000 (  0.165821)
interpolaton  0.130000   0.010000   0.140000 (  0.133665)
--------------------------------------------------
                 user     system      total        real
concatination  0.180000   0.000000   0.180000 (  0.180410)
interpolaton  0.120000   0.000000   0.120000 (  0.125051)
--------------------------------------------------
                 user     system      total        real
concatination  0.140000   0.000000   0.140000 (  0.134256)
interpolaton  0.110000   0.000000   0.110000 (  0.111427)
--------------------------------------------------
                 user     system      total        real
concatination  0.130000   0.000000   0.130000 (  0.132047)
interpolaton  0.120000   0.000000   0.120000 (  0.120443)
--------------------------------------------------
                 user     system      total        real
concatination  0.170000   0.000000   0.170000 (  0.170394)
interpolaton  0.150000   0.000000   0.150000 (  0.149601)
--------------------------------------------------
With predefined strings
====================================================
                 user     system      total        real
concatination  0.070000   0.000000   0.070000 (  0.067735)
interpolaton  0.100000   0.000000   0.100000 (  0.099335)
--------------------------------------------------
                 user     system      total        real
concatination  0.060000   0.000000   0.060000 (  0.061955)
interpolaton  0.130000   0.000000   0.130000 (  0.127011)
--------------------------------------------------
                 user     system      total        real
concatination  0.090000   0.000000   0.090000 (  0.092136)
interpolaton  0.110000   0.000000   0.110000 (  0.110224)
--------------------------------------------------
                 user     system      total        real
concatination  0.080000   0.000000   0.080000 (  0.077587)
interpolaton  0.110000   0.000000   0.110000 (  0.112975)
--------------------------------------------------
                 user     system      total        real
concatination  0.090000   0.000000   0.090000 (  0.088154)
interpolaton  0.140000   0.000000   0.140000 (  0.135349)
--------------------------------------------------

结论

如果字符串已经定义且确定它们永远不会是nil,请使用连接符号;否则,请使用插值符号。选择合适的方法可以比易于缩进的方法获得更好的性能。


你使用了哪个 Ruby 版本? - La-comadreja
1
我在 Ruby 2.5.0 中尝试了一下,无论是哪种情况下,插值都比连接更快。我不能在这里粘贴结果,因为评论长度限制,但你可以自己尝试一下。 - Peter T.
1
我不确定这是否完全公平,因为 "#{firstname} #{middlename} #{lastname}" 应该与 firstname + " " + middlename + " " + lastname 进行比较,而不是 firstname + middlename + lastname(5个字符串连接 vs. 3个字符串连接)。 - Mickalot
1
请注意,当我更改您的基准测试以使它们可比较(通过删除“#{firstname} #{middlename} #{lastname}”中的内部空格或在“concatination”情况下添加空格)时,插值始终比连接快得多(至少在Mac OSX上使用Ruby 2.6.3)。 - Mickalot

4

@user1181898 - 我认为这是因为更容易看到正在发生的事情。如@Phrogz所说,字符串插值会自动为您调用to_s方法。作为初学者,您需要看到“引擎盖下”的内容,以便学习概念,而不仅仅是机械地学习。

把它想象成学数学一样。你通过学习“长”方式来理解概念,这样你就可以在真正知道自己在做什么之后采取捷径。我从经验中说话,因为我在Ruby方面还不是很高级,但我犯过足够的错误,能够为人们提供有益建议。希望这有所帮助。


3
如果你正在使用字符串作为缓冲区,我发现使用连接(String#concat)会更快速。
require 'benchmark/ips'

puts "Ruby #{RUBY_VERSION} at #{Time.now}"
puts

firstname = 'soundarapandian'
middlename = 'rathinasamy'
lastname = 'arumugam'

Benchmark.ips do |x|
    x.report("String\#<<") do |i|
        buffer = String.new

        while (i -= 1) > 0
            buffer << 'Mr. ' << firstname << middlename << lastname << ' aka soundar'
        end
    end

    x.report("String interpolate") do |i|
        buffer = String.new

        while (i -= 1) > 0
            buffer << "Mr. #{firstname} #{middlename} #{lastname} aka soundar"
        end
    end

    x.compare!
end

结果:

Ruby 2.3.1 at 2016-11-15 15:03:57 +1300

Warming up --------------------------------------
           String#<<   230.615k i/100ms
  String interpolate   234.274k i/100ms
Calculating -------------------------------------
           String#<<      2.345M (± 7.2%) i/s -     11.761M in   5.041164s
  String interpolate      1.242M (± 5.4%) i/s -      6.325M in   5.108324s

Comparison:
           String#<<:  2344530.4 i/s
  String interpolate:  1241784.9 i/s - 1.89x  slower

猜测插值会生成一个临时字符串,这就是为什么它更慢的原因。

0
这里是一个完整的基准测试,它还比较了Kernel#formatString#+,因为这是我所知道的在Ruby中构建动态字符串的所有方法。
require 'benchmark/ips'

firstname = 'soundarapandian'
middlename = 'rathinasamy'
lastname = 'arumugam'

FORMAT_STR = 'Mr. %<firstname>s %<middlename>s %<lastname>s aka soundar'
Benchmark.ips do |x|
  x.report("String\#<<") do |i|
    str = String.new
    str << 'Mr. ' << firstname << ' ' << middlename << ' ' << lastname << ' aka soundar'
  end

  x.report "String\#+" do
    'Mr. ' + firstname + ' ' + middlename + ' ' + lastname + ' aka soundar'
  end

  x.report "format" do
    format(FORMAT_STR, firstname: firstname, middlename: middlename, lastname: lastname)
  end

  x.report("String interpolate") do |i|
    "Mr. #{firstname} #{middlename} #{lastname} aka soundar"
  end

  x.compare!
end

针对 Ruby 2.6.5 的结果

Warming up --------------------------------------
           String#<<
    94.597k i/100ms
            String#+    75.512k i/100ms
              format    73.269k i/100ms
  String interpolate   164.005k i/100ms
Calculating -------------------------------------
           String#<<     91.385B (±16.9%) i/s -    315.981B
            String#+    905.389k (± 4.2%) i/s -      4.531M in   5.013725s
              format    865.746k (± 4.5%) i/s -      4.323M in   5.004103s
  String interpolate    161.694B (±11.3%) i/s -    503.542B

Comparison:
  String interpolate: 161693621120.0 i/s
           String#<<: 91385051886.2 i/s - 1.77x  slower
            String#+:   905388.7 i/s - 178590.27x  slower
              format:   865745.8 i/s - 186768.00x  slower

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接