# = dictionary.rb # # == Copyright (c) 2005 Jan Molic, Thomas Sawyer # # Ruby License # # This module is free software. You may use, modify, and/or redistribute this # software under the same terms as Ruby. # # This program is distributed in the hope that it will be useful, but WITHOUT # ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS # FOR A PARTICULAR PURPOSE. # # == Special Thanks # # Ported from OrderHash 2.0, Copyright (c) 2005 jan molic # # Thanks to Andrew Johnson for his suggestions and fixes of Hash[], # merge, to_a, inspect and shift. # # == Authors & Contributors # # * Jan Molic # * Thomas Sawyer # Author:: Jan Molic # Copyright:: Copyright (c) 2006 Jan Molic # License:: Ruby License # = Dictionary # # The Dictionary class is a Hash that preserves order. # So it has some array-like extensions also. By defualt # a Dictionary object preserves insertion order, but any # order can be specified including alphabetical key order. # # == Usage # # Just require this file and use Dictionary instead of Hash. # # # You can do simply # hsh = Dictionary.new # hsh['z'] = 1 # hsh['a'] = 2 # hsh['c'] = 3 # p hsh.keys #=> ['z','a','c'] # # # or using Dictionary[] method # hsh = Dictionary['z', 1, 'a', 2, 'c', 3] # p hsh.keys #=> ['z','a','c'] # # # but this don't preserve order # hsh = Dictionary['z'=>1, 'a'=>2, 'c'=>3] # p hsh.keys #=> ['a','c','z'] # # # Dictionary has useful extensions: push, pop and unshift # p hsh.push('to_end', 15) #=> true, key added # p hsh.push('to_end', 30) #=> false, already - nothing happen # p hsh.unshift('to_begin', 50) #=> true, key added # p hsh.unshift('to_begin', 60) #=> false, already - nothing happen # p hsh.keys #=> ["to_begin", "a", "c", "z", "to_end"] # p hsh.pop #=> ["to_end", 15], if nothing remains, return nil # p hsh.keys #=> ["to_begin", "a", "c", "z"] # p hsh.shift #=> ["to_begin", 30], if nothing remains, return nil # # == Usage Notes # # * You can use #order_by to set internal sort order. # * #<< takes a two element [k,v] array and inserts. # * Use ::auto which creates Dictionay sub-entries as needed. # * And ::alpha which creates a new Dictionary sorted by key. module Ramaze class Dictionary class << self #-- # TODO is this needed? Doesn't the super class do this? #++ def []( *args ) hsh = new if Hash === args[0] hsh.replace(args[0]) elsif (args.size % 2) != 0 raise ArgumentError, "odd number of elements for Hash" else while !args.empty? hsh[args.shift] = args.shift end end hsh end # Like #new but the block sets the order. # def new_by( *args, &blk ) new(*args).order_by(&blk) end # Alternate to #new which creates a dictionary sorted by key. # # d = Dictionary.alpha # d["z"] = 1 # d["y"] = 2 # d["x"] = 3 # d #=> {"x"=>3,"y"=>2,"z"=>2} # # This is equivalent to: # # Dictionary.new.order_by { |key,value| key } def alpha( *args, &block ) new( *args, &block ).order_by_key end # Alternate to #new which auto-creates sub-dictionaries as needed. # # d = Dictionary.auto # d["a"]["b"]["c"] = "abc" #=> { "a"=>{"b"=>{"c"=>"abc"}}} # def auto( *args ) #AutoDictionary.new(*args) leet = lambda { |hsh, key| hsh[key] = new( &leet ) } new(*args, &leet) end end def initialize( *args, &blk ) @order = [] @order_by = nil @hash = Hash.new( *args, &blk ) end def order reorder if @order_by @order end # Keep dictionary sorted by a specific sort order. def order_by( &block ) @order_by = block order self end # Keep dictionary sorted by key. # # d = Dictionary.new.order_by_key # d["z"] = 1 # d["y"] = 2 # d["x"] = 3 # d #=> {"x"=>3,"y"=>2,"z"=>2} # # This is equivalent to: # # Dictionary.new.order_by { |key,value| key } # # The initializer Dictionary#alpha also provides this. def order_by_key @order_by = lambda { |k,v| k } order self end # Keep dictionary sorted by value. # # d = Dictionary.new.order_by_value # d["z"] = 1 # d["y"] = 2 # d["x"] = 3 # d #=> {"x"=>3,"y"=>2,"z"=>2} # # This is equivalent to: # # Dictionary.new.order_by { |key,value| value } def order_by_value @order_by = lambda { |k,v| v } order self end # def reorder if @order_by assoc = @order.collect{ |k| [k,@hash[k]] }.sort_by( &@order_by ) @order = assoc.collect{ |k,v| k } end @order end #def ==( hsh2 ) # return false if @order != hsh2.order # super hsh2 #end def ==( hsh2 ) if hsh2.is_a?( Dictionary ) @order == hsh2.order && @hash == hsh2.instance_variable_get("@hash") else false end end def [] k @hash[ k ] end def fetch( k ) @hash.fetch( k ) end # Store operator. # # h[key] = value # # Or with additional index. # # h[key,index] = value def []=(k, i=nil, v=nil) if v insert(i,k,v) else store(k,i) end end def insert( i,k,v ) @order.insert( i,k ) @hash.store( k,v ) end def store( a,b ) @order.push( a ) unless @hash.has_key?( a ) @hash.store( a,b ) end def clear @order = [] @hash.clear end def delete( key ) @order.delete( key ) @hash.delete( key ) end def each_key order.each { |k| yield( k ) } self end def each_value order.each { |k| yield( @hash[k] ) } self end def each order.each { |k| yield( k,@hash[k] ) } self end alias each_pair each def delete_if order.clone.each { |k| delete k if yield } self end def values ary = [] order.each { |k| ary.push @hash[k] } ary end def keys order end def invert hsh2 = self.class.new order.each { |k| hsh2[@hash[k]] = k } hsh2 end def reject( &block ) self.dup.delete_if(&block) end def reject!( &block ) hsh2 = reject(&block) self == hsh2 ? nil : hsh2 end def replace( hsh2 ) @order = hsh2.order @hash = hsh2.hash end def shift key = order.first key ? [key,delete(key)] : super end def unshift( k,v ) unless @hash.include?( k ) @order.unshift( k ) @hash.store( k,v ) true else false end end def <<(kv) push(*kv) end def push( k,v ) unless @hash.include?( k ) @order.push( k ) @hash.store( k,v ) true else false end end def pop key = order.last key ? [key,delete(key)] : nil end def to_a ary = [] each { |k,v| ary << [k,v] } ary end def to_s self.to_a.to_s end def inspect ary = [] each {|k,v| ary << k.inspect + "=>" + v.inspect} '{' + ary.join(", ") + '}' end def dup self.class[*to_a.flatten] end def update( hsh2 ) hsh2.each { |k,v| self[k] = v } reorder self end alias :merge! update def merge( hsh2 ) self.dup.update(hsh2) end def select ary = [] each { |k,v| ary << [k,v] if yield k,v } ary end def find each{|k,v| return k, v if yield(k,v) } return nil end def first @hash[order.first] end def last @hash[order.last] end def length @order.length end alias :size :length def empty? @hash.empty? end end end # _____ _ # |_ _|__ ___| |_ # | |/ _ \/ __| __| # | | __/\__ \ |_ # |_|\___||___/\__| # =begin testing require 'test/unit' class TC_Dictionary < Test::Unit::TestCase def test_create hsh = Dictionary['z', 1, 'a', 2, 'c', 3] assert_equal( ['z','a','c'], hsh.keys ) end def test_op_store hsh = Dictionary.new hsh['z'] = 1 hsh['a'] = 2 hsh['c'] = 3 assert_equal( ['z','a','c'], hsh.keys ) end def test_push hsh = Dictionary['a', 1, 'c', 2, 'z', 3] assert( hsh.push('end', 15) ) assert_equal( 15, hsh['end'] ) assert( ! hsh.push('end', 30) ) assert( hsh.unshift('begin', 50) ) assert_equal( 50, hsh['begin'] ) assert( ! hsh.unshift('begin', 60) ) assert_equal( ["begin", "a", "c", "z", "end"], hsh.keys ) assert_equal( ["end", 15], hsh.pop ) assert_equal( ["begin", "a", "c", "z"], hsh.keys ) assert_equal( ["begin", 50], hsh.shift ) end def test_insert # front h = Dictionary['a', 1, 'b', 2, 'c', 3] r = Dictionary['d', 4, 'a', 1, 'b', 2, 'c', 3] assert_equal( 4, h.insert(0,'d',4) ) assert_equal( r, h ) # back h = Dictionary['a', 1, 'b', 2, 'c', 3] r = Dictionary['a', 1, 'b', 2, 'c', 3, 'd', 4] assert_equal( 4, h.insert(-1,'d',4) ) assert_equal( r, h ) end def test_update # with other orderred hash h1 = Dictionary['a', 1, 'b', 2, 'c', 3] h2 = Dictionary['d', 4] r = Dictionary['a', 1, 'b', 2, 'c', 3, 'd', 4] assert_equal( r, h1.update(h2) ) assert_equal( r, h1 ) # with other hash h1 = Dictionary['a', 1, 'b', 2, 'c', 3] h2 = { 'd' => 4 } r = Dictionary['a', 1, 'b', 2, 'c', 3, 'd', 4] assert_equal( r, h1.update(h2) ) assert_equal( r, h1 ) end def test_merge # with other orderred hash h1 = Dictionary['a', 1, 'b', 2, 'c', 3] h2 = Dictionary['d', 4] r = Dictionary['a', 1, 'b', 2, 'c', 3, 'd', 4] assert_equal( r, h1.merge(h2) ) # with other hash h1 = Dictionary['a', 1, 'b', 2, 'c', 3] h2 = { 'd' => 4 } r = Dictionary['a', 1, 'b', 2, 'c', 3, 'd', 4] assert_equal( r, h1.merge(h2) ) end def test_order_by h1 = Dictionary['a', 3, 'b', 2, 'c', 1] h1.order_by{ |k,v| v } assert_equal( [1,2,3], h1.values ) assert_equal( ['c','b','a'], h1.keys ) end def test_op_store h1 = Dictionary[] h1[:a] = 1 h1[:c] = 3 assert_equal( [1,3], h1.values ) h1[:b,1] = 2 assert_equal( [1,2,3], h1.values ) assert_equal( [:a,:b,:c], h1.keys ) end end =end