LoginSignup
6
0

More than 5 years have passed since last update.

Ruby でもっと文字列を使い回したい

Posted at

きっかけ

Python3 で FizzBuzz を書くときに、次のようにすると、

def fizzbuzz(n):
    f = 'Fizz' if n % 3 == 0 else ''
    b = 'Buzz' if n % 5 == 0 else ''
    fb = f + b
    return fb or n
fizzbuzz(3) is fizzbuzz(6)        => True
fizzbuzz(5) is fizzbuzz(10)       => True
fizzbuzz(15) is fizzbuzz(30)      => True

となり、'Fizz' も 'Buzz' も 'FizzBuzz' も同じ文字列オブジェクトを使い回しています。Python の文字列が immutable だから、安全に使い回せるんですね。
これを Ruby でもやってみたいということです。
RubyInstaller の 2.5.1 を使います。

つまずき

Ruby では、現状、文字列が使い回せるのは 文字列のリテラル("literal") に、-"literal""literal".freeze したときだとされています。+ メソッドで文字列を結合すると、メソッド呼び出しのたびに新たな文字列オブジェクトが作られるため、Python のようにはいきません。

def fizzbuzz(n)
  f = (n % 3).zero? ? -"Fizz" : -""
  b = (n % 5).zero? ? -"Buzz" : -""
  fb = f + b
  fb.empty? ? n : fb
end

fizzbuzz(3).equal? fizzbuzz(6)           => false
fizzbuzz(5).equal? fizzbuzz(10)          => false
fizzbuzz(15).equal? fizzbuzz(30)         => false

ひらめき

Ruby のマニュアルには、式展開はリテラルだと書かれています。だとすると、式展開と -@ なり freeze なりを組み合わせれば、使い回しのきく文字列が合成できるのではないか?

def fizzbuzz(n)
  f = (n % 3).zero? ? -"Fizz" : -""
  b = (n % 5).zero? ? -"Buzz" : -""
  fb = -"#{f}#{b}"
  fb.empty? ? n : fb
end

fizzbuzz(3).equal? fizzbuzz(6)           => true
fizzbuzz(5).equal? fizzbuzz(10)          => true
fizzbuzz(15).equal? fizzbuzz(30)         => true

大成功です。こんなこと当たり前で、ただ私が不勉強なだけなのかもしれないのですが、まだ知らない人とこの感動を分かち合いたいと思い、勇気を持って投稿しました。

いろいろ実験していて、「ひょっとしてバグ?」と思った点

  • fb = -"#{f}#{b}"fb = "#{f}#{b}".freeze にすると、文字列が使い回されない。

OMG! なぜ、両者で振る舞いを変えるのでしょう?

  • # frozen_string_literal: true にすると、文字列が使い回されない。

ますます、OMG! です。初めから文字列リテラルが frozen なんだから、- が邪魔なのかと思って削ってみたものの変化はありませんでした。
この振る舞いは意図的なものなのでしょうか?

結論的なもの

# frozen_string_literal: false + -"literal" + 式展開 = 最強

6
0
6

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
6
0