Create an object/Native demonstration: Difference between revisions
(→{{header|Ruby}}: Implement delete_if, each, each_key, each_value, keep_if. Again change the semantics of default_proc=.) |
(→{{header|Ruby}}: Add methods :to_hash, :to_s, :value?. Fix bugs with :keep_if and :new. Add more tests.) |
||
Line 252: | Line 252: | ||
{{in progress|lang=Ruby|day=15|month=February|year=2011}} |
{{in progress|lang=Ruby|day=15|month=February|year=2011}} |
||
TODO: |
TODO: Write comments for FencedHash::new, FencedHash#delete and related methods. Add more methods (merge, merge!, reject, reject!, select, select!, update). Explain why FencedHash#replace and FencedHash#shift will not exist. |
||
<lang ruby># fencedhash.rb |
<lang ruby># fencedhash.rb |
||
Line 275: | Line 275: | ||
:has_key?, :has_value?, :hash, :include?, |
:has_key?, :has_value?, :hash, :include?, |
||
:key, :key?, :keys, :length, :member?, |
:key, :key?, :keys, :length, :member?, |
||
:rassoc, :size, :to_a, |
:rassoc, :size, :to_a, |
||
:values, :values_at, :value?) |
|||
attr_reader :default_proc |
attr_reader :default_proc |
||
# Acts like Hash::[] but creates a FencedHash. |
# Acts like Hash::[] but creates a FencedHash. |
||
def self.[](*args) |
def self.[](*args) |
||
allocate.instance_eval do |
|||
@hash = Hash[*args] |
@hash = Hash[*args] |
||
self |
self |
||
Line 301: | Line 302: | ||
if n > 0 |
if n > 0 |
||
args[0].each { |key| @hash[key] = nil } |
args[0].each { |key| @hash[key] = nil } |
||
clear |
|||
args[0].each { |key| @hash[key] } |
|||
end |
end |
||
else |
else |
||
Line 321: | Line 322: | ||
# Clears all values. For each key-value pair, this retains the key |
# Clears all values. For each key-value pair, this retains the key |
||
# but resets the value to default. |
# but resets the value to default. |
||
#-- |
|||
# The line "@hash = @hash" checks that _self_ is not frozen, because |
|||
# Object#freeze only freezes _self_ and not @hash. |
|||
#++ |
|||
def clear |
def clear |
||
@hash = @hash |
@hash = @hash |
||
Line 397: | Line 402: | ||
return enum_for(:each_value) unless block |
return enum_for(:each_value) unless block |
||
@hash.each_value &block |
@hash.each_value &block |
||
end |
|||
# Returns true if _other_ is a FencedHash and has the same key-value |
|||
# pairs as _self_. Acts like Hash#eql?. |
|||
#-- |
|||
# Consistent with FencedHash#hash because it delegates to @hash.hash. |
|||
#++ |
|||
def eql?(other) |
|||
FencedHash === other and |
|||
@hash.eql?(other.instance_eval { @hash }) |
|||
end |
|||
# Returns true if _other_ is a FencedHash and if the key-value pairs |
|||
# of _self_ equal those of _other_. Acts like Hash#==. |
|||
def ==(other) |
|||
FencedHash === other and |
|||
@hash == (other.instance_eval { @hash }) |
|||
end |
end |
||
# ..... |
# ..... |
||
def keep_if |
def keep_if |
||
return enum_for(: |
return enum_for(:keep_if) unless block_given? |
||
@hash = @hash |
@hash = @hash |
||
Line 421: | Line 443: | ||
end |
end |
||
alias []= store |
alias []= store |
||
# Converts _self_ to a regular Hash. Returns a new Hash that has the |
|||
# same key-value pairs as _self_. |
|||
def to_hash |
|||
@hash.dup |
|||
end |
|||
# Converts _self_ to a String. |
|||
def to_s |
|||
"#<#{self.class}: #{@hash.inspect}>" |
|||
end |
|||
alias inspect to_s |
|||
end</lang> |
end</lang> |
||
Line 465: | Line 499: | ||
assert_equal 2, @fh.values.grep(nil).length |
assert_equal 2, @fh.values.grep(nil).length |
||
@fh[:y] = "why?" |
@fh[:y] = "why?" |
||
@fh[:t] = " |
@fh[:t] = "tea!" |
||
assert_equal 0, @fh.values.grep(nil).length |
assert_equal 0, @fh.values.grep(nil).length |
||
end |
|||
def test_default |
|||
fruit = FencedHash.new(0, [:apple, :banana, :cranberry]) |
|||
assert_equal [0, 0, 0], fruit.values |
|||
fruit[:apple] += 1 |
|||
fruit[:banana] += 5 |
|||
fruit[:cranberry] *= 5 |
|||
assert_equal 1, fruit[:apple] |
|||
assert_equal 5, fruit[:banana] |
|||
assert_equal 0, fruit[:cranberry] |
|||
assert_equal 0, fruit.default |
|||
end |
|||
def test_default_assign |
|||
assert_nil @fh.default |
|||
@fh.delete :w |
|||
@fh.default = -1 |
|||
assert_equal -1, @fh.default |
|||
@fh.delete :e |
|||
assert_nil @fh[:w] |
|||
assert_equal -1, @fh[:e] |
|||
end |
|||
def test_default_proc |
|||
count = 0 |
|||
fruit = FencedHash.new([:apple, :banana, :cranberry]) do |h, k| |
|||
if h.key? k then h[k] = [] else count += 1 end |
|||
end |
|||
fruit[:apple].push :red |
|||
fruit[:banana].concat [:green, :yellow] |
|||
fruit[:cranberry].push :red |
|||
assert_equal 1, fruit[:orange] |
|||
assert_equal [:red], fruit[:apple] |
|||
assert_equal [:green, :yellow], fruit[:banana] |
|||
assert_equal [:red], fruit.delete(:cranberry) |
|||
assert_equal 2, fruit[:orange] |
|||
assert_equal [], fruit[:cranberry] |
|||
assert_nil fruit.delete(:orange) |
|||
assert_equal 3, fruit[:orange] |
|||
assert_equal [], fruit.default_proc[FencedHash[1 => 2], 1] |
|||
end |
end |
||
Line 479: | Line 556: | ||
end |
end |
||
assert_equal 6, count |
assert_equal 6, count |
||
end |
|||
def test_eql? |
|||
other = FencedHash[:r, 44, :t, 55, :y, 66, |
|||
:q, 11, :w, 22, :e, 33] |
|||
float = FencedHash[:y, 66.0, :t, 55.0, :r, 44.0, |
|||
:e, 33.0, :w, 22.0, :q, 11.0] |
|||
tt = [true, true] |
|||
ff = [false, false] |
|||
if RUBY_VERSION >= "1.9" |
|||
assert_equal tt, [(@fh.eql? other), (other.eql? @fh)] |
|||
assert_equal ff, [(@fh.eql? float), (float.eql? @fh)] |
|||
assert_equal ff, [(other.eql? float), (float.eql? other)] |
|||
end |
|||
assert_equal tt, [@fh == other, other == @fh] |
|||
assert_equal tt, [@fh == float, float == @fh] |
|||
assert_equal tt, [other == float, float == other] |
|||
h = @fh.to_hash |
|||
if RUBY_VERSION >= "1.9" |
|||
assert_equal ff, [(@fh.eql? h), (h.eql? @fh)] |
|||
end |
|||
assert_equal ff, [@fh == h, h == @fh] |
|||
end |
end |
||
Line 499: | Line 601: | ||
assert_raises(FrozenEx) { @fh.clear } |
assert_raises(FrozenEx) { @fh.clear } |
||
assert_raises(FrozenEx) { @fh.delete :q } |
assert_raises(FrozenEx) { @fh.delete :q } |
||
assert_raises(FrozenEx) { @fh.delete_if { true } } |
|||
assert_raises(FrozenEx) { @fh.keep_if { false } } |
|||
assert_raises(FrozenEx) { @fh.store :w, "different" } |
assert_raises(FrozenEx) { @fh.store :w, "different" } |
||
assert_raises(FrozenEx) { @fh[:w] = "different" } |
assert_raises(FrozenEx) { @fh[:w] = "different" } |
||
Line 510: | Line 614: | ||
assert_equal false, @fh.frozen? |
assert_equal false, @fh.frozen? |
||
@fh[:w] = "different" |
@fh[:w] = "different" |
||
assert_equal "different", @fh[:w] |
assert_equal "different", @fh[:w] |
||
end |
end |
||
Line 530: | Line 634: | ||
(@fh[:u] = "value") rescue "ok" |
(@fh[:u] = "value") rescue "ok" |
||
end |
end |
||
end |
|||
def test_has_value |
|||
assert_equal true, (@fh.has_value? 22) |
|||
assert_equal true, (@fh.value? 22) |
|||
assert_equal false, (@fh.has_value? 4444) |
|||
assert_equal false, (@fh.value? 4444) |
|||
end |
end |
||
Line 536: | Line 648: | ||
assert_kind_of Enumerable, @fh |
assert_kind_of Enumerable, @fh |
||
assert_equal 231, @fh.inject(0) { |sum, kv| sum + kv[1] } |
assert_equal 231, @fh.inject(0) { |sum, kv| sum + kv[1] } |
||
end |
|||
def test_keep_if |
|||
a = @fh.keep_if { |key, value| key == :t || value == 66 } |
|||
assert_same @fh, a |
|||
assert_equal 4, @fh.values.grep(nil).length |
|||
@fh.delete :y |
|||
@fh.delete :t |
|||
assert_equal 6, @fh.values.grep(nil).length |
|||
end |
end |
||
Line 565: | Line 686: | ||
assert_same @fh, a |
assert_same @fh, a |
||
assert_equal 4, @fh.values.grep(nil).length |
assert_equal 4, @fh.values.grep(nil).length |
||
end |
|||
def test_keep_if_enum |
|||
a = @fh.keep_if.with_index { |kv, i| i >= 2 } |
|||
assert_same @fh, a |
|||
assert_equal 2, @fh.values.grep(nil).length |
|||
end |
end |
||
end |
end |
||
if RUBY_VERSION >= "1.9" |
if RUBY_VERSION >= "1.9" |
||
def test_class_bracket_operator |
|||
from_pairs = FencedHash[10, "ten", 20, "twenty", 30, "thirty"] |
|||
from_alist = FencedHash[ [ [10, "ten"], [20, "twenty"], [30, "thirty"] ] ] |
|||
from_hash = FencedHash[10 => "ten", 20 => "twenty", 30 => "thirty"] |
|||
from_fhash = FencedHash[from_pairs] |
|||
[from_pairs, from_alist, from_hash, from_fhash, from_pairs |
|||
].each_cons(2) do |a, b| |
|||
assert_equal a, b |
|||
assert_not_same a, b |
|||
end |
|||
end |
|||
def test_default_proc_assign |
|||
assert_nil @fh.default_proc |
|||
p = @fh.default_proc = proc { |h, k| h[k] = :deleted } |
|||
assert_same p, @fh.default_proc |
|||
assert_equal 11, @fh.delete(:q) |
|||
assert_equal :deleted, @fh[:q] |
|||
assert_raises(KeyEx) { @fh[:u] } |
|||
@fh.default = :value |
|||
assert_nil @fh.default_proc |
|||
@fh.default_proc = p |
|||
assert_nil @fh.default |
|||
end |
|||
def test_each_rewind |
def test_each_rewind |
||
class << @fh |
class << @fh |
Revision as of 20:43, 20 February 2011
Task:
Create a Hash/Associative Array/Dictionary-like object, which is initialized with some default key/value pairs. The object should behave like a native Hash/Associative Array/Dictionary of the language, if any, but with the following differences:
- No new item can be added;
- Item cannot be deleted, (but native delete method may used to reset the item's value to default) ;
Objective:
The objective is not just create such object, but to demonstarion the language's native way of object creation. For some language, the task should show how the so-called Magic Methods work.
J
Given a list of keys and an associated list of values, the idiomatic way of expressing this concept in J would be:
<lang j>lookup=: values {~ keys&i.</lang>
For example:
<lang j> lookup=: 10 20 30 40 50 {~ (;:'this is a test')&i.
lookup ;:'a test'
30 40</lang>
Notes:
1) While the result can not be modified or deleted, the name used to refer to it can be made to refer to something else, and once all references are lost it will be garbage collected.
2) In the above example, we have 5 values and 4 keys. The extra value is used when no key is found. If no extra value was provided, the "key not found" case would be an error case.
3) In J, objects are always referenced, but all data is passed by value. This means that objects can never be passed to a function -- only a reference to an object (its name) can be passed. This means that objects exist only in the way things are named, in J. So for the most part, we do not call things "objects" in J, and this task has nothing to do with what are called "objects" in J. However, this does demonstrate how things are created in J -- you write their definition, and can use them and/or assign to names or inspect them or whatever else.
JavaScript
This is a first demonstration of the task, but only implemented the functionality, not any native behavior, eg indexing. JavaScript experts may want to replace this one.
<lang javascript>var keyError = new Error("Invalid Key Error (FixedKeyDict)") ;
function FixedKeyDict(obj) {
var myDefault = new Object() ; var myData = new Object() ; for(k in obj) { myDefault[k] = obj[k] ; myData[k] = obj[k] ; }
var gotKey = function(k) { for(kk in myDefault) { if(kk == k) return true ; } return false ; } ;
this.hasKey = gotKey ;
var checkKey = function(k) { if(!gotKey(k)) throw keyError ; } ; this.getItem = function(k) { checkKey(k) ; return myData[k]; } ; this.setItem = function(k, v) { checkKey(k) ; myData[k] = v ; } ; this.resetItem = function(k) { checkKey(k) ; myData[k] = myDefault[k] ; } ; this.delItem = this.resetItem ; this.clear = function() { for(k in myDefault) myData[k] = myDefault[k] ; } ; this.iterator = function() { for(k in myDefault) yield (k); } ; this.clone = function() { return new FixedKeyDict(myDefault) ; } this.toStr = function() { var s = "" ; for(key in myData) s = s + key + " => " + myData[key] + ", " ; return "FixedKeyDict{" + s + "}" ; } ;
}</lang>
Test run:
<lang javascript>
const BR = "
\n"
var pl = function(s) {
document.write(s + BR) ;
} ;
pl("
") ; var o = { foo:101, bar:102 } ; var h = new FixedKeyDict(o) ; pl("Fixed Key Dict Created") ; pl("toString : " + h.toStr()) ; pl("get an item: " + h.getItem("foo")) ; pl("check a key: " + h.hasKey("boo")) ; pl("ditto : " + h.hasKey("bar")) ; h.setItem("bar", 999) ; pl("set an item: " + h.toStr()) ; pl("Test iterator (or whatever)") ; for(k in h.iterator()) pl(" " + k + " => " + h.getItem(k)) ; var g = h.clone() ; pl("Clone a dict") ; pl(" clone : " + g.toStr()) ; pl(" original : " + h.toStr()) ; h.clear() ; pl("clear or reset the dict") ; pl(" : " + h.toStr()) ; try { h.setItem("NoNewKey", 666 ) ; } catch(e) { pl("error test : " + e.message) ; } </lang> output : <pre> Fixed Key Dict Created toString : FixedKeyDict{foo => 101, bar => 102, } get an item: 101 check a key: false ditto : true set an item: FixedKeyDict{foo => 101, bar => 999, } Test iterator (or whatever) foo => 101 bar => 999 Clone a dict clone : FixedKeyDict{foo => 101, bar => 102, } original : FixedKeyDict{foo => 101, bar => 999, } clear or reset the dict : FixedKeyDict{foo => 101, bar => 102, } error test : Invalid Key Error (FixedKeyDict)
Python
<lang python> from collections import UserDict import copy
class Dict(UserDict):
>>> d = Dict(a=1, b=2) >>> d Dict({'a': 1, 'b': 2}) >>> d['a'] = 55; d['b'] = 66 >>> d Dict({'a': 55, 'b': 66}) >>> d.clear() >>> d Dict({'a': 1, 'b': 2}) >>> d['a'] = 55; d['b'] = 66 >>> d['a'] 55 >>> del d['a'] >>> d Dict({'a': 1, 'b': 66}) def __init__(self, dict=None, **kwargs): self.__init = True super().__init__(dict, **kwargs) self.default = copy.deepcopy(self.data) self.__init = False def __delitem__(self, key): if key in self.default: self.data[key] = self.default[key] else: raise NotImplementedError
def __setitem__(self, key, item): if self.__init: super().__setitem__(key, item) elif key in self.data: self.data[key] = item else: raise KeyError
def __repr__(self): return "%s(%s)" % (type(self).__name__, super().__repr__()) def fromkeys(cls, iterable, value=None): if self.__init: super().fromkeys(cls, iterable, value) else: for key in iterable: if key in self.data: self.data[key] = value else: raise KeyError
def clear(self): self.data.update(copy.deepcopy(self.default))
def pop(self, key, default=None): raise NotImplementedError
def popitem(self): raise NotImplementedError
def update(self, E, **F): if self.__init: super().update(E, **F) else: haskeys = False try: keys = E.keys() haskeys = Ture except AttributeError: pass if haskeys: for key in keys: self[key] = E[key] else: for key, val in E: self[key] = val for key in F: self[key] = F[key]
def setdefault(self, key, default=None): if key not in self.data: raise KeyError else: return super().setdefault(key, default)</lang>
Ruby
TODO: Write comments for FencedHash::new, FencedHash#delete and related methods. Add more methods (merge, merge!, reject, reject!, select, select!, update). Explain why FencedHash#replace and FencedHash#shift will not exist.
<lang ruby># fencedhash.rb require 'forwardable'
- A FencedHash acts like a Hash, but with a fence around its keys.
- After the creation of a FencedHash, one cannot add nor remove keys.
- Any attempt to insert a new key will raise KeyError. Any attempt to
- delete a key-value pair will keep the key but will reset the value to
- the default value.
class FencedHash < Object
extend Forwardable include Enumerable
#-- # @hash: our Hash inside the fence # @default_proc: passes self, not @hash #++ def_delegators(:@hash, :[], :assoc, :compare_by_identity, :compare_by_identity?, :default, :empty?, :fetch, :flatten, :has_key?, :has_value?, :hash, :include?, :key, :key?, :keys, :length, :member?, :rassoc, :size, :to_a, :values, :values_at, :value?) attr_reader :default_proc
# Acts like Hash::[] but creates a FencedHash. def self.[](*args) allocate.instance_eval do @hash = Hash[*args] self end end
# call-seq: # FencedHash.new(obj=nil [,keys]) -> fh # FencedHash.new([keys]) { |fh, key| block } -> fh # # Creates a FencedHash..... def initialize(*args, &block) n = args.length
if block_given? raise ArgumentError, "wrong number of arguments" if n > 1
@default_proc = block @hash = Hash.new { |hash, key| block[self, key] } if n > 0 args[0].each { |key| @hash[key] = nil } clear end else raise ArgumentError, "wrong number of arguments" if n > 2
default = if n > 0 then n[0] else nil end @hash = Hash.new(default) if n > 1 args[1].each { |key| @hash[key] = default } end end end
def initialize_copy(orig) super @hash = @hash.dup end
# Clears all values. For each key-value pair, this retains the key # but resets the value to default. #-- # The line "@hash = @hash" checks that _self_ is not frozen, because # Object#freeze only freezes _self_ and not @hash. #++ def clear @hash = @hash @hash.each_key { |key| delete key } self end
# ..... def default=(obj) @default_proc = nil @hash.default = obj end
# ..... def default_proc=(proc_obj) # Convert _proc_obj_ to a block parameter. proc_obj = proc &proc_obj
@hash.default_proc = proc { |hash, key| proc_obj[self, key] } @default_proc = proc_obj end
# Deletes the value of the key-value pair for _key_. # # If _key_ is in the fence..... def delete(key) @hash = @hash
begin original_value = @hash.fetch(key) rescue IndexError # _key_ is not in the fence. if block_given? yield key else nil end else # _key_ is in the fence. if @default_proc @default_proc[self, key] else @hash[key] = @hash.default end original_value end end
# ..... def delete_if return enum_for(:delete_if) unless block_given?
@hash = @hash @hash.each { |key, value| delete key if yield key, value } self end
# Yields each key-value pair to the block, or returns an enumerator. # Acts like Hash#each. def each &block # :yields: key, value return enum_for(:each) unless block @hash.each &block end alias each_pair each
# Yields each key to the block, or returns an enumerator. # Acts like Hash#each_key. def each_key &block # :yields: key return enum_for(:each_key) unless block @hash.each_key &block end
# Yields each value to the block, or returns an enumerator. # Acts like Hash#each_value. def each_value &block # :yields: value return enum_for(:each_value) unless block @hash.each_value &block end
# Returns true if _other_ is a FencedHash and has the same key-value # pairs as _self_. Acts like Hash#eql?. #-- # Consistent with FencedHash#hash because it delegates to @hash.hash. #++ def eql?(other) FencedHash === other and @hash.eql?(other.instance_eval { @hash }) end
# Returns true if _other_ is a FencedHash and if the key-value pairs # of _self_ equal those of _other_. Acts like Hash#==. def ==(other) FencedHash === other and @hash == (other.instance_eval { @hash }) end
# ..... def keep_if return enum_for(:keep_if) unless block_given?
@hash = @hash @hash.each { |key, value| delete key unless yield key, value } self end
# Stores a _value_ for a _key_. This only works if _key_ is in the # fence; FencedHash prevents the insertion of new keys. If _key_ is # not in the fence, then this method raises KeyError. def store(key, value) @hash = @hash if @hash.has_key? key @hash.store(key, value) else c = if defined? KeyError then KeyError else IndexError end raise c, "fence prevents new key: #{key}" end end alias []= store
# Converts _self_ to a regular Hash. Returns a new Hash that has the # same key-value pairs as _self_. def to_hash @hash.dup end
# Converts _self_ to a String. def to_s "#<#{self.class}: #{@hash.inspect}>" end alias inspect to_s
end</lang>
<lang ruby># fh-test.rb require 'fencedhash' require 'test/unit'
class TestFencedHash < Test::Unit::TestCase
if RUBY_VERSION >= "1.9" KeyEx = KeyError FrozenEx = RuntimeError else KeyEx = IndexError FrozenEx = TypeError end
def setup @fh = FencedHash[:q => 11, :w => 22, :e => 33, :r => 44, :t => 55, :y => 66] end
def test_bracket_operator assert_equal 11, @fh[:q] assert_equal 22, @fh[:w] assert_equal 33, @fh[:e] assert_equal 44, @fh[:r] assert_equal 55, @fh[:t] assert_equal 66, @fh[:y] assert_nil @fh[:u] end
def test_delete assert_equal 44, (@fh.delete :r) assert_nil @fh.fetch(:r) assert_nil @fh.delete(:r) assert_nil @fh.delete(:u) @fh[:r] = "replacement" assert_equal "replacement", (@fh.delete :r) end
def test_delete_if a = @fh.delete_if { |key, value| key == :t || value == 66 } assert_same @fh, a assert_equal 2, @fh.values.grep(nil).length @fh[:y] = "why?" @fh[:t] = "tea!" assert_equal 0, @fh.values.grep(nil).length end
def test_default fruit = FencedHash.new(0, [:apple, :banana, :cranberry]) assert_equal [0, 0, 0], fruit.values fruit[:apple] += 1 fruit[:banana] += 5 fruit[:cranberry] *= 5 assert_equal 1, fruit[:apple] assert_equal 5, fruit[:banana] assert_equal 0, fruit[:cranberry] assert_equal 0, fruit.default end
def test_default_assign assert_nil @fh.default @fh.delete :w
@fh.default = -1 assert_equal -1, @fh.default @fh.delete :e
assert_nil @fh[:w] assert_equal -1, @fh[:e] end
def test_default_proc count = 0 fruit = FencedHash.new([:apple, :banana, :cranberry]) do |h, k| if h.key? k then h[k] = [] else count += 1 end end fruit[:apple].push :red fruit[:banana].concat [:green, :yellow] fruit[:cranberry].push :red assert_equal 1, fruit[:orange] assert_equal [:red], fruit[:apple] assert_equal [:green, :yellow], fruit[:banana] assert_equal [:red], fruit.delete(:cranberry) assert_equal 2, fruit[:orange] assert_equal [], fruit[:cranberry] assert_nil fruit.delete(:orange) assert_equal 3, fruit[:orange] assert_equal [], fruit.default_proc[FencedHash[1 => 2], 1] end
def test_each count = 0 @fh.each do |key, value| assert_kind_of Symbol, key assert_kind_of Integer, value assert_equal true, (@fh.has_key? key) assert_equal true, (@fh.has_value? value) count += 1 end assert_equal 6, count end
def test_eql? other = FencedHash[:r, 44, :t, 55, :y, 66, :q, 11, :w, 22, :e, 33] float = FencedHash[:y, 66.0, :t, 55.0, :r, 44.0, :e, 33.0, :w, 22.0, :q, 11.0] tt = [true, true] ff = [false, false]
if RUBY_VERSION >= "1.9" assert_equal tt, [(@fh.eql? other), (other.eql? @fh)] assert_equal ff, [(@fh.eql? float), (float.eql? @fh)] assert_equal ff, [(other.eql? float), (float.eql? other)] end
assert_equal tt, [@fh == other, other == @fh] assert_equal tt, [@fh == float, float == @fh] assert_equal tt, [other == float, float == other]
h = @fh.to_hash if RUBY_VERSION >= "1.9" assert_equal ff, [(@fh.eql? h), (h.eql? @fh)] end assert_equal ff, [@fh == h, h == @fh] end
def test_fetch assert_equal 11, @fh.fetch(:q) assert_equal 22, @fh.fetch(:w) assert_equal 33, @fh.fetch(:e) assert_equal 44, @fh.fetch(:r) assert_equal 55, @fh.fetch(:t) assert_equal 66, @fh.fetch(:y) assert_raises(KeyEx) { @fh.fetch :u } end
def test_freeze assert_equal false, @fh.frozen? @fh.freeze
2.times do assert_equal true, @fh.frozen? assert_raises(FrozenEx) { @fh.clear } assert_raises(FrozenEx) { @fh.delete :q } assert_raises(FrozenEx) { @fh.delete_if { true } } assert_raises(FrozenEx) { @fh.keep_if { false } } assert_raises(FrozenEx) { @fh.store :w, "different" } assert_raises(FrozenEx) { @fh[:w] = "different" }
# Repeat the tests with a clone. The clone must be frozen. @fh = @fh.clone end
# A duplicate is not frozen. @fh = @fh.dup assert_equal false, @fh.frozen? @fh[:w] = "different" assert_equal "different", @fh[:w] end
def test_has_key 2.times do |t| assert_equal true, (@fh.has_key? :y) assert_equal true, (@fh.include? :y) assert_equal true, (@fh.key? :y) assert_equal true, (@fh.member? :y)
assert_equal false, (@fh.has_key? :u) assert_equal false, (@fh.include? :u) assert_equal false, (@fh.key? :u) assert_equal false, (@fh.member? :u)
# Repeat the tests. # The fence must prevent any changes to the keys. @fh.delete :y (@fh[:u] = "value") rescue "ok" end end
def test_has_value assert_equal true, (@fh.has_value? 22) assert_equal true, (@fh.value? 22)
assert_equal false, (@fh.has_value? 4444) assert_equal false, (@fh.value? 4444) end
def test_inject # To get an :inject method, FencedHash should mix in Enumerable. assert_kind_of Enumerable, @fh assert_equal 231, @fh.inject(0) { |sum, kv| sum + kv[1] } end
def test_keep_if a = @fh.keep_if { |key, value| key == :t || value == 66 } assert_same @fh, a assert_equal 4, @fh.values.grep(nil).length @fh.delete :y @fh.delete :t assert_equal 6, @fh.values.grep(nil).length end
def test_keys assert_equal([:e, :q, :r, :t, :w, :y], @fh.keys.sort_by { |o| o.to_s }) end
def test_length assert_equal 6, @fh.length assert_equal 6, @fh.size end
def test_store assert_raises(KeyEx) { @fh[:a] = 111 } assert_equal 222, (@fh[:e] = 222) assert_equal 222, (@fh.fetch :e) assert_equal 333, @fh.store(:e, 333) assert_equal 333, @fh[:e] end
def test_values assert_equal [11, 22, 33, 44, 55, 66], @fh.values.sort! end
if RUBY_VERSION >= "1.8.7" def test_delete_if_enum a = @fh.delete_if.with_index { |kv, i| i >= 2 } assert_same @fh, a assert_equal 4, @fh.values.grep(nil).length end
def test_keep_if_enum a = @fh.keep_if.with_index { |kv, i| i >= 2 } assert_same @fh, a assert_equal 2, @fh.values.grep(nil).length end end
if RUBY_VERSION >= "1.9" def test_class_bracket_operator from_pairs = FencedHash[10, "ten", 20, "twenty", 30, "thirty"] from_alist = FencedHash[ [ [10, "ten"], [20, "twenty"], [30, "thirty"] ] ] from_hash = FencedHash[10 => "ten", 20 => "twenty", 30 => "thirty"] from_fhash = FencedHash[from_pairs]
[from_pairs, from_alist, from_hash, from_fhash, from_pairs ].each_cons(2) do |a, b| assert_equal a, b assert_not_same a, b end end
def test_default_proc_assign assert_nil @fh.default_proc p = @fh.default_proc = proc { |h, k| h[k] = :deleted } assert_same p, @fh.default_proc
assert_equal 11, @fh.delete(:q) assert_equal :deleted, @fh[:q] assert_raises(KeyEx) { @fh[:u] }
@fh.default = :value assert_nil @fh.default_proc @fh.default_proc = p assert_nil @fh.default end
def test_each_rewind class << @fh attr_reader :test_rewind def rewind @test_rewind = "correct" end end assert_nil @fh.test_rewind
# @fh.each.rewind must call @fh.rewind. If @fh forwards :each # to another object then this test fails. @fh.each.rewind assert_equal "correct", @fh.test_rewind end
def test_insertion_order assert_equal [:q, :w, :e, :r, :t, :y], @fh.keys assert_equal [11, 22, 33, 44, 55, 66], @fh.values end
def test_key assert_equal :q, @fh.key(11) assert_equal :w, @fh.key(22) assert_equal :e, @fh.key(33) assert_equal :r, @fh.key(44) assert_equal :t, @fh.key(55) assert_equal :y, @fh.key(66) assert_nil @fh.key(77) end end
end</lang>