# = 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 and Contributors
#
# * Jan Molic
# * Thomas Sawyer
# Author:: Jan Molic, Thomas Sawyer
# Copyright:: Copyright (c) 2006 Jan Molic, Thomas Sawyer
# 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.
class Dictionary < Hash
class << self
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
hsh[args.shift] = args.shift while args.size > 0
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.key_new
# 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 }
#
# An alias #alpha is also provided.
def key_new( *args, &block )
new(*args, &block).order_by{ |k,v| k }
end
alias_method :alpha, :key_new
# Alternate constructor which sorts by value.
#
# d = Dictionary.value_new
# 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 value_new( *args, &block )
new(*args, &block).order_by{ |k,v| v }
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 )
leet = lambda { |hsh, key| hsh[key] = new( &leet ) }
new(*args, &leet)
end
def key_auto
leet = lambda { |hsh, key| hsh[key] = new( &leet ) }
new(*args, &leet).order_by{ |k,v| k }
end
end
attr_accessor :order
def initialize( *args, &blk )
@order = []
@order_by = nil
super
end
def order
reorder if @order_by
@order
end
def order_by( &block )
@order_by = block
order
self
end
def reorder
if @order_by
assoc = @order.collect{ |k| [k,self[k]] }.sort_by( &@order_by )
@order = assoc.collect{ |k,v| k }
end
@order
end
def store_only( a,b )
store( a,b )
end
alias_method :orig_store, :store
protected :orig_store
def insert( i,k,v )
@order.insert( i,k )
orig_store( k,v )
end
alias_method :unordered_store, :store
private :unordered_store
def store( a,b )
@order.push( a ) unless has_key? a
super( a,b )
end
alias []= store
def ==( hsh2 )
return false if @order != hsh2.order
super hsh2
end
def clear
@order = []
super
end
def delete( key )
@order.delete key
super
end
def each_key
order.each { |k| yield( k ) }
self
end
def each_value
order.each { |k| yield( self[k] ) }
self
end
#alias_method :unordered_each, :each
def each
order.each { |k| yield( k,self[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 self[k] }
ary
end
def keys
order
end
def invert
hsh2 = self.class.new
order.each { |k| hsh2[self[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.keys
super hsh2
end
def shift
key = order.first
key ? [key,delete(key)] : super
end
def unshift( k,v )
unless self.include? k
@order.unshift k
orig_store(k,v)
true
else
false
end
end
def <<(kv)
push *kv
end
def push( k,v )
unless self.include? k
@order.push k
orig_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
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
end
=end