642 lines
12 KiB
Ruby
642 lines
12 KiB
Ruby
module Hashery
|
|
|
|
# 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.
|
|
#
|
|
# Using a Dictionary is almost the same as using a 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
|
|
#
|
|
# == 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.
|
|
#
|
|
# == Acknowledgments
|
|
#
|
|
# Dictionary is a port of OrderHash 2.0 Copyright (c) 2005 Jan Molic.
|
|
#
|
|
# People who have contributed to this class since then include:
|
|
#
|
|
# * Andrew Johnson (merge, to_a, inspect, shift and Hash[])
|
|
# * Jeff Sharpe (reverse and reverse!)
|
|
# * Thomas Leitner (has_key? and key?)
|
|
#
|
|
# OrderedHash is public domain.
|
|
#
|
|
class Dictionary
|
|
|
|
include Enumerable
|
|
|
|
class << self
|
|
|
|
#
|
|
# Create a new Dictionary storing argument pairs as an initial mapping.
|
|
#
|
|
# TODO: Is this needed? Doesn't the super class do this?
|
|
#
|
|
# Returns Dictionary instance.
|
|
#
|
|
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 instead of the default.
|
|
#
|
|
# Dictionary.new_by{ |k,v| k }
|
|
#
|
|
def new_by(*args, &blk)
|
|
new(*args).order_by(&blk)
|
|
end
|
|
|
|
#
|
|
# Alternate to #new which creates a dictionary sorted by the key as a string.
|
|
#
|
|
# d = Dictionary.alphabetic
|
|
# 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.to_s }
|
|
#
|
|
def alphabetic(*args, &block)
|
|
new(*args, &block).order_by { |key,value| key.to_s }
|
|
end
|
|
|
|
# DEPRECATED: Use #alphabetic instead.
|
|
alias :alpha :alphabetic
|
|
|
|
#
|
|
# Alternate to #new which auto-creates sub-dictionaries as needed.
|
|
#
|
|
# Examples
|
|
#
|
|
# 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
|
|
|
|
#
|
|
# New Dictiionary.
|
|
#
|
|
def initialize(*args, &blk)
|
|
@order = []
|
|
@order_by = nil
|
|
if blk
|
|
dict = self # This ensures automatic key entry effect the
|
|
oblk = lambda{ |hsh, key| blk[dict,key] } # dictionary rather then just the interal hash.
|
|
@hash = Hash.new(*args, &oblk)
|
|
else
|
|
@hash = Hash.new(*args)
|
|
end
|
|
end
|
|
|
|
#
|
|
# Order of keys.
|
|
#
|
|
# Returns [Array].
|
|
#
|
|
def order
|
|
reorder if @order_by
|
|
@order
|
|
end
|
|
|
|
#
|
|
# Keep dictionary sorted by a specific sort order.
|
|
#
|
|
# block - Ordering procedure.
|
|
#
|
|
# Returns +self+.
|
|
#
|
|
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.
|
|
#
|
|
# Returns +self+.
|
|
#
|
|
def order_by_key
|
|
if block_given?
|
|
@order_by = Proc.new{ |k,v| yield(k) }
|
|
else
|
|
@order_by = Proc.new{ |k,v| k }
|
|
end
|
|
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
|
|
if block_given?
|
|
@order_by = Proc.new{ |k,v| yield(v) }
|
|
else
|
|
@order_by = Proc.new{ |k,v| v }
|
|
end
|
|
order
|
|
self
|
|
end
|
|
|
|
#
|
|
# Re-apply the sorting procedure.
|
|
#
|
|
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
|
|
|
|
#
|
|
# Is the dictionary instance equivalent to another?
|
|
#
|
|
def ==(hsh2)
|
|
if hsh2.is_a?( Dictionary )
|
|
@order == hsh2.order &&
|
|
@hash == hsh2.instance_variable_get("@hash")
|
|
else
|
|
false
|
|
end
|
|
end
|
|
|
|
#
|
|
# Lookup entry with key.
|
|
#
|
|
def [] key
|
|
@hash[ key ]
|
|
end
|
|
|
|
#
|
|
# Featch entry given +key+.
|
|
#
|
|
def fetch(key, *a, &b)
|
|
@hash.fetch(key, *a, &b)
|
|
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
|
|
|
|
#
|
|
# Insert entry into dictionary at specific index position.
|
|
#
|
|
# index - [Integer] Position of order placement.
|
|
# key - [Object] Key to associate with value.
|
|
# value - [Object] Value to associate with key.
|
|
#
|
|
# Returns `value` stored.
|
|
#
|
|
def insert(index, key, value)
|
|
@order.insert(index, key)
|
|
@hash.store(key, value)
|
|
end
|
|
|
|
#
|
|
# Add entry into dictionary.
|
|
#
|
|
# Returns `value`.
|
|
#
|
|
def store(key, value)
|
|
@order.push(key) unless @hash.has_key?(key)
|
|
@hash.store(key, value)
|
|
end
|
|
|
|
#
|
|
# Clear dictionary of all entries.
|
|
#
|
|
def clear
|
|
@order = []
|
|
@hash.clear
|
|
end
|
|
|
|
#
|
|
# Delete the entry with given +key+.
|
|
#
|
|
def delete(key)
|
|
@order.delete(key)
|
|
@hash.delete(key)
|
|
end
|
|
|
|
#
|
|
# Iterate over each key.
|
|
#
|
|
def each_key
|
|
order.each { |k| yield( k ) }
|
|
self
|
|
end
|
|
|
|
#
|
|
# Iterate over each value.
|
|
#
|
|
def each_value
|
|
order.each { |k| yield( @hash[k] ) }
|
|
self
|
|
end
|
|
|
|
#
|
|
# Iterate over each key-value pair.
|
|
#
|
|
def each
|
|
order.each { |k| yield( k,@hash[k] ) }
|
|
self
|
|
end
|
|
|
|
alias each_pair each
|
|
|
|
#
|
|
# Delete entry if it fits conditional block.
|
|
#
|
|
def delete_if
|
|
order.clone.each { |k| delete k if yield(k,@hash[k]) }
|
|
self
|
|
end
|
|
|
|
#
|
|
# List of all dictionary values.
|
|
#
|
|
# Returns [Array].
|
|
#
|
|
def values
|
|
ary = []
|
|
order.each { |k| ary.push @hash[k] }
|
|
ary
|
|
end
|
|
|
|
#
|
|
# List of all dictionary keys.
|
|
#
|
|
# Returns [Array].
|
|
#
|
|
def keys
|
|
order
|
|
end
|
|
|
|
#
|
|
# Invert the dictionary.
|
|
#
|
|
# Returns [Dictionary] New dictionary that is inverse of the original.
|
|
#
|
|
def invert
|
|
hsh2 = self.class.new
|
|
order.each { |k| hsh2[@hash[k]] = k }
|
|
hsh2
|
|
end
|
|
|
|
#
|
|
# Reject entries based on give condition block and return
|
|
# new dictionary.
|
|
#
|
|
# Returns [Dictionary].
|
|
#
|
|
def reject(&block)
|
|
self.dup.delete_if(&block)
|
|
end
|
|
|
|
#
|
|
# Reject entries based on give condition block.
|
|
#
|
|
# Returns [Hash] of rejected entries.
|
|
#
|
|
# FIXME: This looks like it is implemented wrong!!!
|
|
#
|
|
def reject!( &block )
|
|
hsh2 = reject(&block)
|
|
self == hsh2 ? nil : hsh2
|
|
end
|
|
|
|
#
|
|
# Replace dictionary entries with new table.
|
|
#
|
|
def replace(hsh2)
|
|
case hsh2
|
|
when Dictionary
|
|
@order = hsh2.order
|
|
@hash = hsh2.to_h
|
|
when Hash
|
|
@hash = hsh2
|
|
@order = @hash.keys
|
|
else
|
|
@hash = hsh2.to_h
|
|
@order = @hash.keys
|
|
end
|
|
reorder
|
|
end
|
|
|
|
#
|
|
# Remove entry from the to top of dictionary.
|
|
#
|
|
def shift
|
|
key = order.first
|
|
key ? [key,delete(key)] : super
|
|
end
|
|
|
|
#
|
|
# Push entry on to the top of dictionary.
|
|
#
|
|
def unshift( k,v )
|
|
unless @hash.include?( k )
|
|
@order.unshift( k )
|
|
@hash.store( k,v )
|
|
true
|
|
else
|
|
false
|
|
end
|
|
end
|
|
|
|
#
|
|
# Same as #push.
|
|
#
|
|
def <<(kv)
|
|
push(*kv)
|
|
end
|
|
|
|
#
|
|
# Push entry on to bottom of the dictionary.
|
|
#
|
|
def push(k,v)
|
|
unless @hash.include?( k )
|
|
@order.push( k )
|
|
@hash.store( k,v )
|
|
true
|
|
else
|
|
false
|
|
end
|
|
end
|
|
|
|
#
|
|
# Pop entry off the bottom of dictionary.
|
|
#
|
|
def pop
|
|
key = order.last
|
|
key ? [key,delete(key)] : nil
|
|
end
|
|
|
|
#
|
|
# Inspection string for Dictionary.
|
|
#
|
|
# Returns [String].
|
|
#
|
|
def inspect
|
|
ary = []
|
|
each {|k,v| ary << k.inspect + "=>" + v.inspect}
|
|
'{' + ary.join(", ") + '}'
|
|
end
|
|
|
|
#
|
|
# Duplicate dictionary.
|
|
#
|
|
# Returns [Dictionary].
|
|
#
|
|
def dup
|
|
a = []
|
|
each{ |k,v| a << k; a << v }
|
|
self.class[*a]
|
|
end
|
|
|
|
#
|
|
# Update dictionary with other hash.
|
|
#
|
|
# Returns self.
|
|
#
|
|
def update( hsh2 )
|
|
hsh2.each { |k,v| self[k] = v }
|
|
reorder
|
|
self
|
|
end
|
|
|
|
alias :merge! update
|
|
|
|
#
|
|
# Merge other hash creating new dictionary.
|
|
#
|
|
# Returns [Dictionary].
|
|
#
|
|
def merge(hsh2)
|
|
self.dup.update(hsh2)
|
|
end
|
|
|
|
#
|
|
# Select items from dictiornary.
|
|
#
|
|
# Returns [Array] of two-element arrays.
|
|
#
|
|
def select
|
|
ary = []
|
|
each { |k,v| ary << [k,v] if yield k,v }
|
|
ary
|
|
end
|
|
|
|
#
|
|
# Reverse the order of the dictionary.
|
|
#
|
|
# Returns self.
|
|
#
|
|
def reverse!
|
|
@order.reverse!
|
|
self
|
|
end
|
|
|
|
#
|
|
# Reverse the order of duplicte dictionary.
|
|
#
|
|
# Returns [Dictionary].
|
|
#
|
|
def reverse
|
|
dup.reverse!
|
|
end
|
|
|
|
#
|
|
# Get/set initial entry value.
|
|
#
|
|
def first(x=nil)
|
|
return @hash[order.first] unless x
|
|
order.first(x).collect { |k| @hash[k] }
|
|
end
|
|
|
|
#
|
|
# Get/set last entry value.
|
|
#
|
|
def last(x=nil)
|
|
return @hash[order.last] unless x
|
|
order.last(x).collect { |k| @hash[k] }
|
|
end
|
|
|
|
#
|
|
# Number of items in the dictionary.
|
|
#
|
|
def length
|
|
@order.length
|
|
end
|
|
|
|
alias :size :length
|
|
|
|
#
|
|
# Is the dictionary empty?
|
|
#
|
|
# Returns `true` or `false`.
|
|
#
|
|
def empty?
|
|
@hash.empty?
|
|
end
|
|
|
|
#
|
|
# Does the dictionary have a given +key+.
|
|
#
|
|
# Returns `true` or `false`.
|
|
#
|
|
def has_key?(key)
|
|
@hash.has_key?(key)
|
|
end
|
|
|
|
#
|
|
# Does the dictionary have a given +key+.
|
|
#
|
|
# Returns `true` or `false`.
|
|
#
|
|
def key?(key)
|
|
@hash.key?(key)
|
|
end
|
|
|
|
#
|
|
# Convert to array.
|
|
#
|
|
# Returns [Array] of two-element arrays.
|
|
#
|
|
def to_a
|
|
ary = []
|
|
each { |k,v| ary << [k,v] }
|
|
ary
|
|
end
|
|
|
|
#
|
|
# Convert to array then to string.
|
|
#
|
|
# Returns [String].
|
|
#
|
|
def to_s
|
|
self.to_a.to_s
|
|
end
|
|
|
|
#
|
|
# Get a duplicate of the underlying hash table.
|
|
#
|
|
# Returns [Hash].
|
|
#
|
|
def to_hash
|
|
@hash.dup
|
|
end
|
|
|
|
#
|
|
# Get a duplicate of the underlying hash table.
|
|
#
|
|
# Returns [Hash].
|
|
#
|
|
def to_h
|
|
@hash.dup
|
|
end
|
|
|
|
protected
|
|
|
|
#
|
|
# Underlying hash table.
|
|
#
|
|
def hash_table
|
|
@hash
|
|
end
|
|
|
|
end
|
|
|
|
end
|