巨大数研究 Wiki

ユーザーブログ:Nayuta_Ito/2021HB-p進大好きbotさんの「お料理巨大数投稿用記事」をプログラミング言語に翻訳するにバグがあったので修正し、最新の定義を反映させました。

オリジナルの定義: ユーザーブログ:P進大好きbot/お料理巨大数投稿用記事

# version : 10→10→10

# require 'logger'

# ハンバーガー全体のクラス
class Hamburger
  include Comparable
 
  # arrayは1個以上のパティの列
  def initialize(array)
    @array = array
  end
 
  attr_reader :array
 
  # to_sとinspect「表示」するための文字列を返す)
  def to_s
    str = "("
   
    @array.each do |patty|
      str += patty.to_s + "|"
    end
   
    # chopは文字列の最後の1文字を取り除く
    # 最後が"\r\n"であればその2文字を取り除くが、ここではその仕様は考慮しなくてもよい
    str = str.chop + "]"
    return str
  end
 
  # inspectとはto_sのことである、と定義する
  alias inspect to_s
 
  # 大小関係
  # self > other ならば +1
  # self = other ならば 0
  # self < other ならば -1
  # Rubyの配列の仕様上これで実現できる
  def <=>(other)
    return @array <=> other.array
  end
 
  # 「パランスがよい」を判定する
  # Rubyでは「is○○」の代わりに「○○?」を使う
  def balanced?(y)
    len = y.array.length
    if len == 0 then # 1.
      return false
    else
      w, v = y.odd_material # 2.
      if @array.length == 1 then # 2.1.
        x = @array[0]
        if x.array.length == 0 then # 2.1.1.
          return true
        else # 2.1.2.
          # 先生、スパゲティはハンバーガーの具材に入りますか!?
          z, u = x.odd_material
          cond1 = Hamburger.new([z]).balanced?(y)
          cond2 = y.technical?(u) || Hamburger.new([y]) >= self
          cond3 = u.length == 1
          cond4 = u[0].balanced?(y)
          cond5 = u.length >= 2 && u[0].balanced?(y) && u[1].balanced?(y) # こうしないとu[1]がnilになるのでエラーとなる
         
          return cond1 && cond2 && (cond3 ? cond4 : cond5)
        end
      else # 2.2.
        u = Hamburger.new(@array[0...-1])
        x = @array[-1]
        return u.balanced?(y) && Hamburger.new([x]).balanced?(y)
      end
    end
  end
 
  # 「食べやすい」を判定する
  def easy_to_eat?
    if @array.length == 1 then # 1.
      x = @array[0]
      len = x.array.length
      if len == 0 then # 1.1.
        return true
      else # 1.2.
        # 先生、スパゲティはハンバーガーの具材に入りますか!?
        z, u = x.odd_material
        cond1 = Hamburger.new([z]).easy_to_eat?
        cond2 = u.length == 2 && z.array.length >= 2 && z.array.length % 2 == 0
        cond3 = z.array.length >= 2 && z.array[-2] < u[0] # こうしないとz.array[-2]がnilになるのでエラーとなる
        cond4 = u.length == 1
        cond5 = u[0].easy_to_eat? && u[0].balanced?(x)
        cond6 = u.length >= 2 && u[0].easy_to_eat? && u[1].easy_to_eat? && u[1].balanced?(x) # こうしないとu[1]がnilになるのでエラーとなる
       
        return cond1 && (cond2 ? cond3 : true) && (cond4 ? cond5 : cond6)
      end
    elsif @array.length == 2 then # 2.
      return Hamburger.new([@array[0]]).easy_to_eat? &&
          Hamburger.new([@array[1]]).easy_to_eat? &&
          Hamburger.new([@array[0]]) >= Hamburger.new([@array[1]])
    else # 3.
      return Hamburger.new(@array[0...-1]).easy_to_eat? && Hamburger.new(@array[-2..-1]).easy_to_eat?
    end
  end
 
  # 基本列
  # 指数時間アルゴリズム
  # 後で実装する
  def [](n)
    num_meat = self.to_s.count("\u8089")
   
    # 検索するハンバーガーの候補
    candidates = []
    for i in 1..(num_meat + n) do
      if i == 1 then
        candidates.push(Hamburger.parseHamburger("(\u8089]"))
      else
        # 全探索
        for j in 0...(3 ** (i - 1)) do
          str_num = j.to_s(3)
          while str_num.length < i - 1 do
            str_num = "0" + str_num
          end
         
          str_hamburger = "("
          str_num.each_char do |digit|
            str_hamburger += "\u8089"
            case digit
            when "0" then
              str_hamburger += "("
            when "1" then
              str_hamburger += "|"
            when "2" then
              str_hamburger += "]"
            end
          end
          str_hamburger += "\u8089]"
          hamburger = Hamburger.parseHamburger(str_hamburger)
         
          if !hamburger.nil? then
            candidates.push(hamburger)
          end
        end
      end
    end
    # 便宜上最小のハンバーガーを入れて開始
    max = Hamburger.new([Patty.new([])])
    for t in candidates do
      if t.easy_to_eat? && t < self && t > max then
        max = t
      end
    end
   
    return max
  end
 
  # 急増加関数
  # 記事には「再帰的」と書いてあるが、定義はループであり、
  # 自分を呼び出している部分がどこにもないのである!
  def eat()
    eaten = 0
    current = self
    hamburger_atom = Hamburger.new([Patty.new([])])
    # logger = Logger.new("refrigerator.log")
    while true do
      if current == hamburger_atom then # 1.
        eaten += 1
        break
      else # 2.
        # self.ketchup!
        current = current[eaten + 1]
        eaten += 1
        # logger.info(current)
        # logger.info(eaten)
      end
    end
    return eaten
  end
 
  class << self
    # Λ
    # Hamburger.lambda(10)と呼び出すことを前提とする
    def lambda(n)
      if n == 0 then
        return Hamburger.new([Patty.new([])])
      else
        return Hamburger.new([Patty.new([Hamburger.lambda(n - 1), Hamburger.lambda(n - 1)])])
      end
    end
   
    # 文字列をハンバーガーとしてパースする
    # パースできなければnilを返す
    def parseHamburger(str)
      unless str[0] == "(" && str[-1] == "]" then
        return nil
      else
        # 文字列を|で分割するが、ネストされた部分では分割しない
        tokens = [str[1]]
        layer = 0
        i = 2
        pipe_on_base_layer = false
        while i < str.length - 1 do
          unless layer == 0 && str[i] == "|" then
            if layer == 0 && str[i - 1] == "|" then
              tokens.push(str[i])
            else
              tokens[-1].concat(str[i])
            end
            if str[i] == "(" then
              layer += 1
            elsif str[i] == "]" then
              layer -= 1
            end
          end
          i += 1
        end
        patties = tokens.map{|s| Patty.parsePatty(s)}
       
        if patties.all? then
          return Hamburger.new(patties)
        else
          return nil
        end
      end
    end
  end
end

# パティ全体のクラス
class Patty
  include Comparable
 
  # arrayは0個以上のハンバーガーの列
  def initialize(array)
    if array.nil? then
      @array = []
    else
      @array = array
    end
  end
 
  attr_reader :array
 
  # to_sとinspect(「表示」するための文字列を返す)
  def to_s
    str = "\u8089" # うちのCygwinちゃんがコード中の全角文字を認識しないのでUnicodeで代用
   
    @array.each do |hamburger|
      str += hamburger.to_s + "\u8089"
    end
   
    return str
  end

  # inspectとはto_sのことである、と定義する
  alias inspect to_s
 
  # パティをオッドパティzとパティ素材sを用いてzs肉と表したときの[z, s]を返す
  def odd_material
    if @array.length == 0 then # 肉が入力された場合は便宜上無と無を返す
      return [Patty.new([]), []]
    else
      oddlen = 0 # オッドパティの長さ
      if @array.length % 2 == 0 then
        oddlen = @array.length - 2
      else
        oddlen = @array.length - 1
      end
      z = Patty.new(@array[0...oddlen])
      s = @array[oddlen..-1]
      return [z, s]
    end
  end

  # 大小関係
  # self > other ならば +1
  # self = other ならば 0
  # self < other ならば -1
  def <=>(other)
    if other.array.length == 0 then # 1.
      if @array.length == 0 then
        return 0
      else
        return +1
      end
    elsif @array.length == 0 then # 2.
      return -1
    else # 3.
      s = @array[0...2]
      # @arrayが1項の場合はnilを返すが、initializeでnilチェックしているので問題ない
      u = Patty.new(@array[2..-1])
     
      t = other.array[0...2]
      # other.arrayが1項の場合はnilを返すが、initializeでnilチェックしているので問題ない
      v = Patty.new(other.array[2..-1])
     
      if t.length == 1 then # 3.1.
        if s.length == 1 && s[0] < t[0] then
          return -1
        elsif s.length > 1 || s[0] > t[0] then
          return +1
        else
          return 0
        end
      end
     
      if s.length == 1 && t.length != 1 then # 3.2.
        return -1
      end
     
      # 3.3.
      u0 = s[0]
      u1 = s[1]
      v0 = t[0]
      v1 = t[1]
     
      return [u0, u1, u] <=> [v0, v1, v]
    end
  end
 
  # 技巧的
  # 「uよりselfが技巧的である」を判定し、true/falseを返す
  # uは1個か2個のハンバーガーからなる配列(パティ素材として使用)
  def technical?(u)
    if @array.length == 0 then # 1.
      return false
    else
      w, v = self.odd_material # 2.
      return (u[-1] < v[-1] || w.array.each_slice(2).to_a.index(u))
    end
  end
 
  class << self
    # 文字列をパティとしてパースする
    # パースできなければnilを返す
    def parsePatty(str)
      unless str[0] == "\u8089" && str[-1] == "\u8089" then
        return nil
      else
        # 肉だけのときは例外的にここで返す
        if str == "\u8089" then
          return Patty.new([])
        end
       
        # 文字列を肉で分割するが、ネストされた部分では分割しない
        tokens = [str[1]]
        layer = 1
        i = 2
        meat_on_base_layer = false
        while i < str.length - 1 do
          unless layer == 0 && str[i] == "\u8089" then
            if layer == 0 && str[i - 1] == "\u8089" then
              tokens.push(str[i])
            else
              tokens[-1].concat(str[i])
            end
           
            if str[i] == "(" then
              layer += 1
            elsif str[i] == "]" then
              layer -= 1
            end
          end
          i += 1
        end
       
        hamburgers = tokens.map{|s| Hamburger.parseHamburger(s)}
       
        if hamburgers.all? then
          return Patty.new(hamburgers)
        else
          return nil
        end
      end
    end
  end
end

=begin
# デバッグ用
h = Hamburger.parseHamburger("(\u8089(\u8089]\u8089]")
p h[1]

h = Hamburger.parseHamburger("(\u8089(\u8089]\u8089]")
p h.eat()

require 'benchmark'

nothingPatty = Patty.new([])
nothingBurger = Hamburger.new([nothingPatty])
layer2Patty = Patty.new([nothingBurger, nothingBurger])
layer2Burger = Hamburger.new([layer2Patty])
layer3Patty = Patty.new([layer2Burger])
layer3Burger = Hamburger.new([layer3Patty])

p layer3Burger
for i in 0..5 do
  Benchmark.bm do |x|
    x.report(i.to_s + ":") do
      p layer3Burger[i]
    end
  end
end
# [5]の計算には筆者のコンピュータで37秒を要した
=end

# p Hamburger.new([Patty.new([])])

# n = Hamburger.lambda(10).eat()
# p n