3
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 3 years have passed since last update.

Ruby で矢印表記を使いたい

Posted at

↑ で冪乗を計算できるようにする

四則演算の演算子は大体のプログラミング言語で共通して + - * / ですが、冪乗(累乗)演算子はプログラミング言語によって様々です。Ruby や Python などでは ** ですが、BASIC では ^ ですし、そもそも C や ES2016 以前の JavaScript など冪乗演算子が無い言語もよくあります。

参考:べき乗演算子 | 数値型 | プログラミング言語の比較 | hydroculのメモ

さて、一部界隈では冪乗を「↑」で表します。つまり $2 \uparrow 4=2^4=16$ です。というわけで Ruby でも「↑」で冪乗を計算できるようにしましょう。

とはいえ、いくら Ruby が自由度の高いプログラミング言語だと言っても、残念ながら自分で新たな演算子を作ることはできません。1 というわけで、「2 ↑ 4」と書きたいところを 2.↑ 4 のようにドットが入る点はご容赦ください。

class Integer
  def (n)
    self ** n
  end
end

puts 2. 4 # => 16 (2 の 4 乗)

Ruby は 非 Ascii 文字もメソッド名に使えるので、こういうことができますね。2

Ruby 3.0 では endless メソッド定義を使って以下のようにも書けます。

  def (n) = self ** n

↑↑ でテトレーション

さて、冪乗とは乗算の繰り返しでした。

2 \uparrow 4 = 2^4 = \underbrace{2 \times 2 \times 2 \times 2}_{4つの2}

そうすると、「冪乗の繰り返し」という演算を考えたくなってきます。一部界隈ではそれを「テトレーション」と呼び、「↑↑」で表します。

2 \uparrow\uparrow 4 = \underbrace{2 \uparrow 2 \uparrow 2 \uparrow 2}_{4つの2} = 2^{2^{2^2}} = 2^{2^4} = 2^{16} = 65536

というわけで 2.↑↑ 4 でテトレーションを計算できるようにしたいと思います。それだけが目的なら、このように書けばいいでしょう。

  def ↑↑(n)
    Array.new(n, self).inject{ |result, item| item. result }
  end

なお、Array.new(n, self).inject(:↑) と書きたいところですが、これではうまく行きません。冪乗は右結合なので $3 \uparrow 3 \uparrow 3 = 3 \uparrow (3 \uparrow 3) = 3 \uparrow 27 = 7625597484987$ となるはずなのですが、Array.new(n, self).inject(:↑) だと計算順序上 $(3 \uparrow 3) \uparrow 3 = 27 \uparrow 3 = 19683$ の意味になってしまいます。

テトレーションのその先へ

さて、冪乗の繰り返しを行うテトレーションという演算を定義しました。そうすると、テトレーションの繰り返しという演算も定義したくなります。それは「ペンテーション」と呼ばれ、「↑↑↑」で表します。つまり、

2 \uparrow\uparrow\uparrow 4 = \underbrace{2 \uparrow\uparrow 2 \uparrow\uparrow 2 \uparrow\uparrow 2}_{4つの2}

ということですね。

ペンテーションをただ行うプログラムを書くなら、上のコードをコピペして少しだけいじって

  def ↑↑↑(n)
    Array.new(n, self).inject{ |result, item| item.↑↑ result }
  end

とすれば良いでしょう。

ですが恐らくご想像のとおり、ペンテーションのさらに先「↑↑↑↑」(ヘキテーション)があります。さらに「↑↑↑↑↑」「↑↑↑↑↑↑」…と無限に矢印を伸ばしていくことができます。

先の方法で一つ上の演算をほぼコピペで定義していくことはできますが、「↑↑…(100個の↑)…↑↑」まで定義するのはさすがに手間です。こうした場合に考えられる方法として、define_method を利用した動的なメソッド定義があります。

class Integer
  2.upto(100) do |i|
    define_method("↑" * i) do |n|
      operator = "↑" * (i - 1)
      Array.new(n, self).inject{ |result, item| item.send(operator, result) }
    end
  end
end

こうすれば簡単に「↑↑」から「↑↑…(100個の↑)…↑↑」まで定義できます。

しかし、こうして「↑↑…(100個の↑)…↑↑」までのメソッドを定義したところで、「↑↑…(101個の↑)…↑↑」が必要な機会があるかもしれません。3 たとえいくつの「↑↑…↑↑」が必要になっても、プログラムを書き換えることなく対応できるようにしたいものです。

そこで、Ruby 黒魔術の王道 method_missing の出番です。4

class Integer
  def (n)
    self ** n
  end

  def method_missing(name, *args)
    return super if name.match?(/[^↑]/)
    unless args.size == 1
      raise ArgumentError,
            "wrong number of arguments (given #{args.size}, expected 1)"
    end

    operator = name[0..-2]
    n = args[0]
    Array.new(n, self).inject do |result, item|
      item.send(operator, result)
    end
  end

  def respond_to_missing?(symbol, include_private)
    symbol.match?(/[^↑]/) ? super : true
  end
end

これでテトレーションだろうがペンテーションだろうがヘキテーションだろうが「↑↑…(1000個の↑)…↑↑」だろうが対応できます!

じゃあ、まずは軽く 3↑↑↑3 でも計算してみますかね!

> puts 3.↑↑↑ 3

bignum too big to convert into `long' (RangeError)

Oh...5

グラハム数を求めるプログラムを書く

さてせっかく矢印表記を定義したので、ギネスに載った巨大数として有名なグラハム数を求めるプログラムを書いてみましょう。もちろん 3↑↑↑3 よりさらに大きな数なので実際に求めることはできませんが、計算手順をプログラムとして表すだけなら可能です。

まず、俗にグラハム関数と呼ばれる関数 $g(n) = 3 \underbrace{\uparrow\uparrow\cdots\uparrow\uparrow}_{n個の\uparrow} 3$ を定義します。

def g(n)
  3.send("↑" * n, 3)
end

method_missing を使って矢印表記を定義したことで、このようにグラハム関数も簡単に表せます。

グラハム数はグラハム関数を使って $G = \underbrace{g(g( \cdots g(g(4)) \cdots ))}_{64個の g( )}$ と表されますが、このような同じ関数の合成を $g^{64}(4)$ と冪乗みたいに書く場合があります。

だとすれば、Ruby においては同じ関数の合成を冪乗演算子 ** を使って method ** 64 みたいに書けるべきではないでしょうか。

class Method
  def **(times)
    Array.new(times, self).inject(:>>)
  end
end

これでグラハム数を求めるプログラムの準備ができました。

g64 = method(:g) ** 64
puts g64.call(4)

実に簡潔にグラハム数を求めるプログラムが書けました!

最後に

Ruby の柔軟さは数学と相性が良いと思うので、数学屋の皆様は Ruby を使ってみてはいかがでしょうか。

  1. できません…よね?やり方があれば是非ともコメントください。

  2. このように組み込みクラスを拡張する場合は Refinement を使うのがマナーですが、このあと使うことになる method_missing は Refinement では使えないので、直接 Integer を拡張してしまいます。

  3. たぶんありません。

  4. 公式ドキュメントにある通り、method_missing を定義する際は respond_to_missing? も定義しましょう。

  5. この数はトリトリと呼ばれ、全宇宙の粒子をメモリとして使ったとしても到底表せないような数です。参考:仮面ライダービルドの数式 第16話 テトレーション

3
1
0

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
3
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?