Rubyでの多次元Hashの作成法 2 : 修正とクラス化

スコープを意識しながらよく見直してみるとなんか無駄にArrayに格納してることが判明。ってかなんでこんな事してたんだ俺w

よりスマートになった感じ。

dimension = 3

node = {} # 末端Hash
(dimension-1).times do
  current = node
  node = Hash.new{ |hash, key| hash[key] = current.dup }
end
tree = node

ついでにクラス化してみた。

ArgumentError出すのにごにょごにょしてるのは、エラー時のstacktraceをHashと全く同じにしたいというだけの本質とは無関係な処理。

class MultidimensionHash < Hash
  attr_reader :dimension
  
  def initialize dimension, ifnone = nil, &default
    raise DimensionError if dimension <= 0
    
    if ifnone
      if default
        traceback = caller
        traceback.unshift traceback.first.gsub(/`new'$/, "`initialize'")
        raise ArgumentError, "wrong number of arguments", traceback
      end
      node = Hash.new(ifnone)
    else
      node = Hash.new(&default)
    end
    
    (dimension-1).times do
      current = node
      node = Hash.new{ |hash, key| hash[key] = current.dup }
    end
    self.replace node
    
    @dimension = dimension
  end
  
  def self.[] dimension
    new dimension
  end
  
  class DimensionError < IndexError; end
end

テスト

シンタックスシュガー的なHash[]*1も上書きしてるけど、MultidimensionHash[]なんて書けても嬉しくなさすw

require "test/unit"
require "multidimension_hash"

class MultidimensionHashTest < Test::Unit::TestCase
  def test_new
    (-16..0).each do |dimension|
      assert_raise(MultidimensionHash::DimensionError){ MultidimensionHash.new(dimension) }
    end 
    (1..16).each do |dimension|
      mdhash = MultidimensionHash.new(dimension)
      assert_kind_of Hash,    mdhash
      assert_equal dimension, mdhash.dimension
      (1..32).each do |d|
        reader = "mdhash"+"[:test]"*d
        if d < dimension
          assert_kind_of Hash, eval(reader)
        elsif d == dimension
          assert_equal nil, eval(reader)
        else
          assert_raise(NoMethodError){ eval(reader) }
        end
      end
    end
  end
  
  def test_new_with_default
    (1..16).each do |dimension|
      mdhash = MultidimensionHash.new(dimension, :default)
      reader = "mdhash"+"[:test]"*dimension
      assert_equal :default, eval(reader), "#{dimension}D"
    end
  end
  
  def test_new_with_default_block
    (1..16).each do |dimension|
      mdhash = MultidimensionHash.new(dimension){ |hash, key| hash[key] = :default_block }
      reader = "mdhash"+"[:test]"*dimension
      assert_equal :default_block, eval(reader), "#{dimension}D"
    end
  end
  
  def test_new_instance_with_default_and_default_block_exception
    errors = []
    2.times do |i|
      begin
        [Hash, MultidimensionHash][i].new(1, 1){}
      rescue => errors[i]
      end
    end
    assert_equal errors[0].class,     errors[1].class
    assert_equal errors[0].message,   errors[1].message
    assert_equal errors[0].backtrace, errors[1].backtrace
  end
  
  def test_new_syntax_sugar
    assert_equal MultidimensionHash.new(2), MultidimensionHash[2]
  end
end

*1:半角だと表示されない……