Create an object/Native demonstration: Difference between revisions
Content added Content deleted
(Added zkl) |
(→{{header|Ruby}}: Replace my three-year-old attempt with a simpler version. Remove confusing default_proc feature. Throw out old tests. Forget Ruby older than 1.9.) |
||
Line 514: | Line 514: | ||
=={{header|Ruby}}== |
=={{header|Ruby}}== |
||
<lang ruby># A FencedHash acts like a Hash, but with a fence around its keys. |
|||
{{in progress|lang=Ruby|day=15|month=February|year=2011}} |
|||
# One may change its values, but not its keys. Any attempt to insert |
|||
# a new key raises KeyError. One may delete a key, but this only |
|||
# restores its original value. |
|||
# |
|||
# FencedHash reimplements these Hash methods: #[] #[]= #clear #delete |
|||
# #delete_if #default #default= #each_key #each_pair #each_value |
|||
# #fetch #has_key? #keep_if #keys #length #values #values_at |
|||
class FencedHash |
|||
# call-seq: |
|||
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. |
|||
# FencedHash.new(hash, obj=nil) -> fh |
|||
# |
|||
# Creates a FencedHash that takes its keys and original values from |
|||
# a source _hash_. The source _hash_ can be any object that |
|||
# responds to each_pair. Sets the default value for missing keys to |
|||
# _obj_, so FencedHash#[] returns _obj_ when a key is not in fence. |
|||
def initialize(hash, obj=nil) |
|||
@default = obj |
|||
@hash = {} |
|||
hash.each_pair do |key, value| |
|||
# @hash[key][0] = current value |
|||
# @hash[key][1] = original value |
|||
@hash[key] = [value, value] |
|||
end |
|||
end |
|||
def initialize_clone(orig) |
|||
<lang ruby># fencedhash.rb |
|||
# Object#clone calls here in Ruby 2.0. If _orig_ was frozen, then |
|||
require 'forwardable' |
|||
# each array of _values_ is frozen, so make frozen clones. |
|||
super |
|||
copy = {} |
|||
@hash.each_pair {|key, values| copy[key] = values.clone } |
|||
@hash = copy |
|||
end |
|||
def initialize_dup(orig) |
|||
# A FencedHash acts like a Hash, but with a fence around its keys. |
|||
# Object#dup calls here in Ruby 2.0. If _orig_ was frozen, then |
|||
# After the creation of a FencedHash, one cannot add nor remove keys. |
|||
# make duplicates that are not frozen. |
|||
# Any attempt to insert a new key will raise KeyError. Any attempt to |
|||
super |
|||
# delete a key-value pair will keep the key but will reset the value to |
|||
copy = {} |
|||
# the default value. |
|||
@hash.each_pair {|key, values| copy[key] = values.dup } |
|||
class FencedHash < Object |
|||
@hash = copy |
|||
extend Forwardable |
|||
end |
|||
include Enumerable |
|||
# Retrieves current value for _key_, like Hash#[]. If _key_ is not |
|||
#-- |
|||
# in fence, returns default object. |
|||
# @hash: our Hash inside the fence |
|||
def [](key) |
|||
# @default_proc: passes self, not @hash |
|||
values = @hash[key] |
|||
#++ |
|||
if values |
|||
def_delegators(:@hash, :[], :assoc, |
|||
values[0] |
|||
:compare_by_identity, :compare_by_identity?, |
|||
else |
|||
:default, :empty?, :fetch, :flatten, |
|||
@default |
|||
: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 |
||
end |
end |
||
# call-seq: |
# call-seq: |
||
# |
# fh[key] = value -> value |
||
# |
# fh.store(key, value) -> value |
||
# |
# |
||
# Sets _value_ for a _key_. Returns _value. If _key_ is not in |
|||
# Creates a FencedHash..... |
|||
# fence, raises KeyError. |
|||
def initialize(*args, &block) |
|||
def []=(key, value) |
|||
n = args.length |
|||
values = @hash[key] |
|||
if |
if values |
||
values[0] = value |
|||
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 |
else |
||
raise |
raise KeyError, "fence prevents adding new key: #{key.inspect}" |
||
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 |
||
end |
end |
||
alias store []= |
|||
# Resets all keys to their original values. Returns self. |
|||
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 |
def clear |
||
@hash = |
@hash.each_value {|values| values[0] = values[1]} |
||
@hash.each_key { |key| delete key } |
|||
self |
self |
||
end |
end |
||
# Resets _key_ to its original value. Returns old value before |
|||
# ..... |
|||
# reset. If _key_ is not in fence, returns +nil+. |
|||
def default=(obj) |
|||
def delete(key) |
|||
@default_proc = nil |
|||
values = @hash[key] |
|||
if values |
|||
old = values[0] |
|||
values[0] = values[1] |
|||
old # return old |
|||
end # else return nil |
|||
end |
end |
||
# |
# call-seq: |
||
# fh.delete_if {|key, value| block } -> fh |
|||
def default_proc=(proc_obj) |
|||
# fh.delete_if -> enumerator |
|||
# 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_. |
|||
# |
# |
||
# |
# Yields each _key_ with current _value_ to _block_. Resets _key_ |
||
# to its original value when block evaluates to true. |
|||
def delete(key) |
|||
def delete_if |
|||
@hash = @hash |
|||
if block_given? |
|||
@hash.each_pair do |key, values| |
|||
begin |
|||
yield(key, values[0]) and values[0] = values[1] |
|||
original_value = @hash.fetch(key) |
|||
rescue IndexError |
|||
# _key_ is not in the fence. |
|||
if block_given? |
|||
yield key |
|||
else |
|||
nil |
|||
end |
end |
||
self |
|||
else |
else |
||
enum_for(:delete_if) { @hash.size } |
|||
# _key_ is in the fence. |
|||
if @default_proc |
|||
@default_proc[self, key] |
|||
else |
|||
@hash[key] = @hash.default |
|||
end |
|||
original_value |
|||
end |
end |
||
end |
end |
||
# The default value for keys not in fence. |
|||
# ..... |
|||
attr_accessor :default |
|||
def delete_if |
|||
return enum_for(:delete_if) unless block_given? |
|||
# call-seq: |
|||
@hash = @hash |
|||
# fh.each_key {|key| block} -> fh |
|||
# fh.each_key -> enumerator |
|||
self |
|||
# |
|||
# Yields each key in fence to the block. |
|||
def each_key(&block) |
|||
# Yields each key-value pair to the block, or returns an enumerator. |
|||
if block |
|||
@hash.each_key(&block) |
|||
def each &block # :yields: key, value |
|||
self |
|||
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 |
else |
||
enum_for(:each_key) { @hash.size } |
|||
c = if defined? KeyError then KeyError else IndexError end |
|||
raise c, "fence prevents new key: #{key}" |
|||
end |
end |
||
end |
end |
||
alias []= store |
|||
# call-seq: |
|||
# Converts _self_ to a regular Hash. Returns a new Hash that has the |
|||
# |
# fh.each_pair {|key, value| block} -> fh |
||
# fh.each_pair -> enumerator |
|||
def to_hash |
|||
# |
|||
@hash.dup |
|||
# Yields each key-value pair to the block, like Hash#each_pair. |
|||
end |
|||
# This yields each [key, value] as an array of 2 elements. |
|||
def each_pair |
|||
# Converts _self_ to a String. |
|||
if block_given? |
|||
def to_s |
|||
@hash.each_pair {|key, values| yield [key, values[0]] } |
|||
"#<#{self.class}: #{@hash.inspect}>" |
|||
self |
|||
end |
|||
else |
|||
alias inspect to_s |
|||
enum_for(:each_pair) { @hash.size } |
|||
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 |
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 |
||
# call-seq |
|||
def test_each |
|||
# fh.each_value {|value| block} -> fh |
|||
count = 0 |
|||
# fh.each_value -> enumerator |
|||
@fh.each do |key, value| |
|||
# |
|||
assert_kind_of Symbol, key |
|||
# Yields current value of each key-value pair to the block. |
|||
assert_kind_of Integer, value |
|||
def each_value |
|||
assert_equal true, (@fh.has_key? key) |
|||
if block_given? |
|||
assert_equal true, (@fh.has_value? value) |
|||
@hash.each_value {|values| yield values[0] } |
|||
count += 1 |
|||
else |
|||
enum_for(:each_value) { @hash.size } |
|||
end |
end |
||
assert_equal 6, count |
|||
end |
end |
||
# call-seq: |
|||
def test_eql? |
|||
# fenhsh.fetch(key [,default]) |
|||
other = FencedHash[:r, 44, :t, 55, :y, 66, |
|||
# fenhsh.fetch(key) {|key| block } |
|||
:q, 11, :w, 22, :e, 33] |
|||
# |
|||
float = FencedHash[:y, 66.0, :t, 55.0, :r, 44.0, |
|||
# Fetches value for _key_. Takes same arguments as Hash#fetch. |
|||
:e, 33.0, :w, 22.0, :q, 11.0] |
|||
def fetch(*argv) |
|||
tt = [true, true] |
|||
argc = argv.length |
|||
unless argc.between?(1, 2) |
|||
raise(ArgumentError, |
|||
if RUBY_VERSION >= "1.9" |
|||
"wrong number of arguments (#{argc} for 1..2)") |
|||
assert_equal tt, [(@fh.eql? other), (other.eql? @fh)] |
|||
end |
|||
assert_equal ff, [(@fh.eql? float), (float.eql? @fh)] |
|||
if argc == 2 and block_given? |
|||
assert_equal ff, [(other.eql? float), (float.eql? other)] |
|||
warn("#{caller[0]}: warning: " + |
|||
"block supersedes default value argument") |
|||
end |
end |
||
key, default = argv |
|||
assert_equal tt, [@fh == other, other == @fh] |
|||
values = @hash[key] |
|||
assert_equal tt, [@fh == float, float == @fh] |
|||
if values |
|||
assert_equal tt, [other == float, float == other] |
|||
values[0] |
|||
elsif block_given? |
|||
yield key |
|||
elsif argc == 2 |
|||
assert_equal ff, [(@fh.eql? h), (h.eql? @fh)] |
|||
default |
|||
else |
|||
raise KeyError, "key not found: #{key.inspect}" |
|||
end |
end |
||
assert_equal ff, [@fh == h, h == @fh] |
|||
end |
end |
||
# Freezes this FencedHash. |
|||
def test_fetch |
|||
def freeze |
|||
assert_equal 11, @fh.fetch(:q) |
|||
@hash.each_value {|values| values.freeze } |
|||
assert_equal 22, @fh.fetch(:w) |
|||
super |
|||
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 |
end |
||
# Returns true if _key_ is in fence. |
|||
def test_freeze |
|||
def has_key?(key) |
|||
assert_equal false, @fh.frozen? |
|||
@ |
@hash.has_key?(key) |
||
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 |
end |
||
alias include? has_key? |
|||
alias member? has_key? |
|||
# call-seq: |
|||
def test_has_key |
|||
# fh.keep_if {|key, value| block } -> fh |
|||
2.times do |t| |
|||
# fh.keep_if -> enumerator |
|||
assert_equal true, (@fh.has_key? :y) |
|||
# |
|||
assert_equal true, (@fh.include? :y) |
|||
# Yields each _key_ with current _value_ to _block_. Resets _key_ |
|||
assert_equal true, (@fh.key? :y) |
|||
# to its original value when block evaluates to false. |
|||
assert_equal true, (@fh.member? :y) |
|||
def keep_if |
|||
if block_given? |
|||
assert_equal false, (@fh.has_key? :u) |
|||
@hash.each_pair do |key, values| |
|||
assert_equal false, (@fh.include? :u) |
|||
yield(key, values[0]) or values[0] = values[1] |
|||
assert_equal false, (@fh.key? :u) |
|||
end |
|||
assert_equal false, (@fh.member? :u) |
|||
self |
|||
else |
|||
# Repeat the tests. |
|||
enum_for(:keep_if) { @hash.size } |
|||
# The fence must prevent any changes to the keys. |
|||
@fh.delete :y |
|||
(@fh[:u] = "value") rescue "ok" |
|||
end |
end |
||
end |
end |
||
# Returns array of keys in fence. |
|||
def test_has_value |
|||
def keys |
|||
assert_equal true, (@fh.has_value? 22) |
|||
@hash.keys |
|||
assert_equal true, (@fh.value? 22) |
|||
assert_equal false, (@fh.has_value? 4444) |
|||
assert_equal false, (@fh.value? 4444) |
|||
end |
end |
||
# Returns number of key-value pairs. |
|||
def test_inject |
|||
def length |
|||
# To get an :inject method, FencedHash should mix in Enumerable. |
|||
@hash.length |
|||
assert_kind_of Enumerable, @fh |
|||
assert_equal 231, @fh.inject(0) { |sum, kv| sum + kv[1] } |
|||
end |
end |
||
alias size length |
|||
# Converts self to a regular Hash. |
|||
def test_keep_if |
|||
def to_h |
|||
a = @fh.keep_if { |key, value| key == :t || value == 66 } |
|||
result = Hash.new(@default) |
|||
assert_same @fh, a |
|||
@hash.each_pair {|key, values| result[key] = values[0]} |
|||
result |
|||
@fh.delete :t |
|||
assert_equal 6, @fh.values.grep(nil).length |
|||
end |
end |
||
# Converts self to a String. |
|||
def test_keys |
|||
def to_s |
|||
assert_equal([:e, :q, :r, :t, :w, :y], |
|||
"#<#{self.class}: #{to_h}>" |
|||
@fh.keys.sort_by { |o| o.to_s }) |
|||
end |
end |
||
alias inspect to_s |
|||
# Returns array of current values. |
|||
def test_length |
|||
def values |
|||
assert_equal 6, @fh.length |
|||
@hash.each_value.map {|values| values[0]} |
|||
assert_equal 6, @fh.size |
|||
end |
end |
||
# Returns array of current values for keys, like Hash#values_at. |
|||
def test_store |
|||
def values_at(*keys) |
|||
assert_raises(KeyEx) { @fh[:a] = 111 } |
|||
keys.map {|key| self[key]} |
|||
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 |
||
end</lang> |
end</lang> |