# AUTHOR # jan molic /mig/at/1984/dot/cz/ # updated by: Nicolas Pouillard # # DESCRIPTION # Override hash to make it creation order preserved. # This will be obsolete - it is better to use special OrderedHash class # than overriding. # Public domain. # # THANKS # Andrew Johnson for his suggestions and fixes of Hash[], # merge, to_a, inspect and shift # # USAGE # just require this file, examples are at the end # you can try to run this file (ruby orderedhash.rb) # # $LastChangedBy: ertai $ # $Id: orderedhash.rb 152 2005-02-13 09:31:49Z ertai $ module OrderedHash_ext def store ( a, b ) @order.push a unless has_key? a super a,b end def []= ( a, b ) @order.push a unless has_key? a super a,b end def == ( hsh2 ) return false if !hsh2.respond_to? :order or @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 def each @order.each { |k| yield(k, self[k]) } self end alias :each_pair :each def delete_if @order.clone.each do |k| delete k if yield(k, self[k]) end self end def values ary = [] @order.each { |k| ary << 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 to_a ary = [] each { |k,v| ary << [k,v] } ary end def to_s to_a.to_s end def update ( hsh2 ) hsh2.each { |k,v| self[k] = v } self end def merge! ( hsh2 ) hsh2.each { |k,v| self[k] = v } self end def merge ( hsh2 ) self.dup.update(hsh2) end def select ary = [] each { |k,v| ary << [k,v] if yield k,v } ary end def ordered? !@order.nil? end end # module OrderedHash_ext class OHash < Hash attr_accessor :order alias :unordered_keys :keys include OrderedHash_ext def initialize ( *a ) @order = unordered_keys super end def self.[] ( *args ) hsh = OHash.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 end # class OHash class Hash def ordered? false end end # class Hash if defined? TEST_MODE or __FILE__ == $0 require 'test/unit' class OrderedHashTest < Test::Unit::TestCase def check_ordered ( h, ref ) h.store(4,3) h.store(2,3) assert_equal(h.to_a, ref) end def test_ordered check_ordered(OHash.new, [[4, 3], [2, 3]]) end def test_unorderd check_ordered(Hash.new, [[2, 3], [4, 3]]) end def test_simply # You can do simply hsh = OHash.new hsh['z'] = 1 hsh['a'] = 2 hsh['c'] = 3 assert_equal(hsh.keys, ['z','a','c']) end def test_preserve # or using OrderedHash[] method hsh = OHash['z', 1, 'a', 2, 'c', 3] hsh2 = OHash['a', 2, 'z', 1, 'c', 3] assert_equal(hsh.keys, ['z','a','c']) assert_not_equal(hsh, hsh2) end def test_dont_preserve # but this don't preserve order hsh = OHash['z'=>1, 'a'=>2, 'c'=>3] hsh2 = OHash['a'=>2, 'z'=>1, 'c'=>3] assert_equal(hsh, hsh2) end def test_unorderd_empty check_ordered({}, [[2, 3], [4, 3]]) end def test_little_ordered a = (0..11).to_a h = OHash[*a] assert_equal(h.to_s, a.to_s) end def test_big_ordered a = (0..501).to_a h = OHash[*a] assert_equal(h.to_s, a.to_s) end def test_eql assert_nothing_raised { OHash.new == {} } assert_not_equal OHash.new, {} assert_equal OHash.new, OHash.new assert_equal OHash[1,2,3,4], OHash[1,2,3,4] assert_not_equal OHash[1,2,3,4], OHash[3,4,1,2] end end # OrderedHashTest end