require "spec" require "spec/helpers/iterate" private alias RecursiveHash = Hash(RecursiveHash, RecursiveHash) private class HashBreaker getter x : Int32 def initialize(@x) end end private class NeverInstantiated end private alias RecursiveType = String | Int32 | Array(RecursiveType) | Hash(String, RecursiveType) private class HashWrapper(K, V) include Enumerable({K, V}) @hash = {} of K => V delegate each, to: @hash end describe "Hash" do describe "empty" do it "size should be zero" do h = {} of Int32 => Int32 h.size.should eq(0) h.empty?.should be_true end end it "sets and gets" do a = {} of Int32 => Int32 a[1] = 2 a[1].should eq(2) end it "gets from literal" do a = {1 => 2} a[1].should eq(2) end it "gets from union" do a = {1 => 2, "foo" => 1.1} a[1].should eq(2) end it "gets nilable" do a = {1 => 2} a[1]?.should eq(2) a[2]?.should be_nil end it "gets array of keys" do a = {} of String => Int32 a.keys.should eq([] of String) a["foo"] = 1 a["bar"] = 2 a.keys.should eq(["foo", "bar"]) end it "gets array of values" do a = {} of String => Int32 a.values.should eq([] of Int32) a["foo"] = 1 a["bar"] = 2 a.values.should eq([1, 2]) end describe "==" do it do a = {1 => 2, 3 => 4} b = {3 => 4, 1 => 2} c = {2 => 3} d = {5 => 6, 7 => 8} a.should eq(a) a.should eq(b) b.should eq(a) a.should_not eq(c) c.should_not eq(a) d.should_not eq(a) end it do a = {1 => nil} b = {3 => 4} a.should_not eq(b) end it "compares hash of nested hash" do a = { {1 => 2} => 3 } b = { {1 => 2} => 3 } a.should eq(b) end end context "subset/superset operators" do h1 = {"a" => 1, "b" => 2} h2 = {"a" => 1, "b" => 2, "c" => 3} h3 = {"c" => 3} h4 = {} of Nil => Nil describe "#proper_subset_of?" do it do h1.proper_subset_of?(h2).should be_true h2.proper_subset_of?(h1).should be_false h1.proper_subset_of?(h1).should be_false h1.proper_subset_of?(h3).should be_false h1.proper_subset_of?(h4).should be_false end it "handles edge case where both values are nil" do {"a" => nil}.proper_subset_of?({"b" => nil, "c" => nil}).should be_false end end describe "#subset_of?" do it do h1.subset_of?(h2).should be_true h2.subset_of?(h1).should be_false h1.subset_of?(h1).should be_true h1.subset_of?(h3).should be_false h1.subset_of?(h4).should be_false end it "handles edge case where both values are nil" do {"a" => nil}.subset_of?({"b" => nil}).should be_false end end describe "#proper_superset_of?" do it do h1.proper_superset_of?(h2).should be_false h2.proper_superset_of?(h1).should be_true h1.proper_superset_of?(h1).should be_false h1.proper_superset_of?(h3).should be_false h1.proper_superset_of?(h4).should be_true end end describe "#superset_of?" do it do h1.superset_of?(h2).should be_false h2.superset_of?(h1).should be_true h1.superset_of?(h1).should be_true h1.superset_of?(h3).should be_false h1.superset_of?(h4).should be_true end end end describe "[]" do it "gets" do a = {1 => 2} a[1].should eq(2) # a[2].should raise_exception a.should eq({1 => 2}) end it "raises on missing key" do h = {1 => 2} expect_raises KeyError, "Missing hash key: 2" do h[2] end end describe "with block" do it "calls and returns if of the same type" do h = Hash(Int32, String).new { "b" } h[0].should eq("b") end it "errors if of the wrong type" do h = Hash(Int32, String).new { "b" } expect_raises KeyError, "Invalid key type: expected Int32, got String" do h["0"] end end end end describe "[]=" do it "overrides value" do a = {1 => 2} a[1] = 3 a[1].should eq(3) end end describe "#put" do it "puts in a small hash" do a = {} of Int32 => Int32 a.put(1, 2) { nil }.should be_nil a.put(1, 3) { nil }.should eq(2) end it "puts in a big hash" do a = {} of Int32 => Int32 100.times do |i| a[i] = i end a.put(100, 2) { nil }.should be_nil a.put(100, 3) { nil }.should eq(2) end it "yields key" do a = {} of Int32 => Int32 a.put(1, 2, &.to_s).should eq("1") end end describe "#put_if_absent" do it "puts if key doesn't exist" do v = [] of String h = {} of Int32 => Array(String) h.put_if_absent(1, v).should be(v) h.should eq({1 => v}) h[1].should be(v) end it "returns existing value if key exists" do v = [] of String h = {1 => v} h.put_if_absent(1, [] of String).should be(v) h.should eq({1 => v}) h[1].should be(v) end it "accepts a block" do v = [] of String h = {1 => v} h.put_if_absent(1) { [] of String }.should be(v) h.put_if_absent(2) { |key| [key.to_s] }.should eq(["2"]) h.should eq({1 => v, 2 => ["2"]}) h[1].should be(v) end it "doesn't put duplicate keys (#14425)" do h = {1 => 2} h.put_if_absent(3) { h[3] = 4 }.should eq(4) h.should eq({1 => 2, 3 => 4}) end end describe "update" do it "updates the value of an existing key with the given block" do h = {"a" => 0, "b" => 1} h.update("b") { |v| v + 41 } h["b"].should eq(42) end it "updates the value of an existing key with the given block (big hash)" do h = {} of Int32 => Int32 100.times do |i| h[i] = i end h.update(2) { |v| x = v * 20 x + 2 } h[2].should eq(42) end it "returns the old value when key exists" do h = {"a" => 0} h.update("a") { |v| v + 1 }.should eq(0) end it "returns the old value when key exists (big hash)" do h = {} of Int32 => Int32 100.times do |i| h[i] = i end h.update(0) { |v| v + 1 }.should eq(0) end it "inserts a new entry using the value returned by the default block as input, if key does not exist" do h = Hash(String, Int32).new { |h, new_key| new_key.size } h.update("new key") { |v| v * 6 } h["new key"].should eq(7 * 6) end it "inserts a new entry using the value returned by the default block as input, if key does not exist (big hash)" do h = Hash(Int32, Int32).new { |h, new_key| new_key } 100.times do |i| h[i] = i end h.update(3000) { |v| v + 42 } h[3000].should eq(3000 + 42) end it "doesn't create a duplicate key, if key does not exist and default block adds the given key (#14416)" do h = Hash(String, Int32).new do |h, new_key| h[new_key] = 1 new_key.size end h.update("new key") { |v| v * 6 } h.size.should eq(1) h["new key"].should eq(7 * 6) end it "inserts a new entry using the default value as input, if key does not exist" do h = Hash(String, Int32).new(2) h.update("new key") { |v| v + 40 } h["new key"].should eq(2 + 40) end it "inserts a new entry using the default value as input, if key does not exist (big hash)" do h = Hash(Int32, Int32).new(2) 100.times do |i| h[i] = i end h.update(3000) { |v| v + 40 } h[3000].should eq(2 + 40) end it "returns the default value when key does not exist" do h = Hash(String, Int32).new(0) h.update("a") { |v| v + 1 }.should eq(0) end it "returns the default value when key does not exist (big hash)" do h = Hash(Int32, Int32).new(0) 100.times do |i| h[i] = i end h.update(3000) { |v| v + 1 }.should eq(0) end it "raises if key does not exist and no default value specified" do h = {} of String => Int32 expect_raises KeyError, %(Missing hash key: "a") do h.update("a") { 42 } end end it "raises if key does not exist and no default value specified (big hash)" do h = {} of Int32 => Int32 100.times do |i| h[i] = i end expect_raises KeyError, %(Missing hash key: 3000) do h.update(3000) { 42 } end end it "can update with a nil value" do h = {"a" => 42} of String => Int32? h.update("a") { nil } h["a"].should be_nil end it "can update a current nil value with a new value" do h = {"a" => nil} of String => Int32? h.has_key?("a").should be_true h.update("a") { 42 }.should be_nil h["a"].should eq(42) end end describe "dig?" do it "gets the value at given path given splat" do ary = [1, 2, 3] h = {"a" => {"b" => {"c" => [10, 20]}}, ary => {"a" => "b"}} h.dig?("a", "b", "c").should eq([10, 20]) h.dig?(ary, "a").should eq("b") end it "returns nil if not found" do ary = [1, 2, 3] h = {"a" => {"b" => {"c" => 300}}, ary => {"a" => "b"}} h.dig?("a", "b", "c", "d", "e").should be_nil h.dig?("z").should be_nil h.dig?("").should be_nil end end describe "dig" do it "gets the value at given path given splat" do ary = [1, 2, 3] h = {"a" => {"b" => {"c" => [10, 20]}}, ary => {"a" => "b", "c" => nil}} h.dig("a", "b", "c").should eq([10, 20]) h.dig(ary, "a").should eq("b") h.dig(ary, "c").should be_nil end it "raises KeyError if not found" do ary = [1, 2, 3] h = {"a" => {"b" => {"c" => 300}}, ary => {"a" => "b"}} expect_raises KeyError, %(Hash value not diggable for key: "c") do h.dig("a", "b", "c", "d", "e") end expect_raises KeyError, %(Missing hash key: "z") do h.dig("z") end expect_raises KeyError, %(Missing hash key: "") do h.dig("") end end end describe "fetch" do it "fetches with default value" do a = {1 => 2} a.fetch(1, 3).should eq(2) a.fetch(2, 3).should eq(3) a.should eq({1 => 2}) end it "fetches with block" do a = {1 => 2} a.fetch(1) { |k| k * 3 }.should eq(2) a.fetch(2) { |k| k * 3 }.should eq(6) a.should eq({1 => 2}) end end describe "values_at" do it "returns the given keys" do {"a" => 1, "b" => 2, "c" => 3, "d" => 4}.values_at("b", "a", "c").should eq({2, 1, 3}) end it "raises when passed an invalid key" do expect_raises KeyError do {"a" => 1}.values_at("b") end end it "works with mixed types" do {1 => "a", "a" => 1, 2.0 => "a", "b" => 1.0}.values_at(1, "a", 2.0, "b").should eq({"a", 1, "a", 1.0}) end end describe "key_for" do it "returns the first key with the given value" do hash = {"foo" => "bar", "baz" => "qux"} hash.key_for("bar").should eq("foo") hash.key_for("qux").should eq("baz") end it "raises when no key pairs with the given value" do expect_raises KeyError do {"foo" => "bar"}.key_for("qux") end end describe "if block is given," do it "returns the first key with the given value" do hash = {"foo" => "bar", "baz" => "bar"} hash.key_for("bar", &.upcase).should eq("foo") end it "yields the argument if no hash key pairs with the value" do hash = {"foo" => "bar"} hash.key_for("qux", &.upcase).should eq("QUX") end end end describe "key_for?" do it "returns the first key with the given value" do hash = {"foo" => "bar", "baz" => "qux"} hash.key_for?("bar").should eq("foo") hash.key_for?("qux").should eq("baz") end it "returns nil if no key pairs with the given value" do hash = {"foo" => "bar", "baz" => "qux"} hash.key_for?("foobar").should be_nil hash.key_for?("bazqux").should be_nil end end describe "has_key?" do it "doesn't have key" do a = {1 => 2} a.has_key?(2).should be_false end it "has key" do a = {1 => 2} a.has_key?(1).should be_true end end describe "has_value?" do it "returns true if contains the value" do a = {1 => 2, 3 => 4, 5 => 6} a.has_value?(4).should be_true end it "returns false if does not contain the value" do a = {1 => 2, 3 => 4, 5 => 6} a.has_value?(3).should be_false end end describe "delete" do it "deletes key in the beginning" do a = {1 => 2, 3 => 4, 5 => 6} a.delete(1).should eq(2) a.has_key?(1).should be_false a.has_key?(3).should be_true a.has_key?(5).should be_true a.size.should eq(2) a.should eq({3 => 4, 5 => 6}) end it "deletes key in the middle" do a = {1 => 2, 3 => 4, 5 => 6} a.delete(3).should eq(4) a.has_key?(1).should be_true a.has_key?(3).should be_false a.has_key?(5).should be_true a.size.should eq(2) a.should eq({1 => 2, 5 => 6}) end it "deletes key in the end" do a = {1 => 2, 3 => 4, 5 => 6} a.delete(5).should eq(6) a.has_key?(1).should be_true a.has_key?(3).should be_true a.has_key?(5).should be_false a.size.should eq(2) a.should eq({1 => 2, 3 => 4}) end it "deletes only remaining entry" do a = {1 => 2} a.delete(1).should eq(2) a.has_key?(1).should be_false a.size.should eq(0) a.should eq({} of Int32 => Int32) end it "deletes not found" do a = {1 => 2} a.delete(2).should be_nil end it "deletes many in the beginning and then will need a resize" do h = {} of Int32 => Int32 8.times do |i| h[i] = i end 5.times do |i| h.delete(i) end (9..12).each do |i| h[i] = i end h.should eq({5 => 5, 6 => 6, 7 => 7, 9 => 9, 10 => 10, 11 => 11, 12 => 12}) end describe "with block" do it "returns the value if a key is found" do a = {1 => 2} a.delete(1) { 5 }.should eq(2) end it "returns the value of the block if key is not found" do a = {1 => 2} a.delete(3) { |key| key }.should eq(3) end it "returns nil if key is found and value is nil" do {3 => nil}.delete(3) { 7 }.should be_nil end end end describe "size" do it "is the same as size" do a = {} of Int32 => Int32 a.size.should eq(a.size) a = {1 => 2} a.size.should eq(a.size) a = {1 => 2, 3 => 4, 5 => 6, 7 => 8} a.size.should eq(a.size) end end it "maps" do hash = {1 => 2, 3 => 4} array = hash.map { |k, v| k + v } array.should eq([3, 7]) end describe "to_s" do it { {1 => 2, 3 => 4}.to_s.should eq("{1 => 2, 3 => 4}") } it do h = {} of RecursiveHash => RecursiveHash h[h] = h h.to_s.should eq("{ {...} => {...} }") end context "when the first key starts with '{'" do it "inserts a space after '{' and before '}' when first key is a Hash, preventing macro interpolation ({{ ... }})" do hash = { {1 => 2} => 3 } hash.to_s.should eq("{ {1 => 2} => 3 }") end it "inserts a space after '{' and before '}' when first key is a Tuple, preventing macro interpolation ({{ ... }})" do hash = { {1, 2, 3} => 4 } hash.to_s.should eq("{ {1, 2, 3} => 4 }") end it "inserts a space after '{' and before '}' when first key is a NamedTuple, preventing macro interpolation ({{ ... }})" do hash = { {a: 1} => 2 } hash.to_s.should eq("{ {a: 1} => 2 }") end end end it "does to_h" do h = {"a" => 1} h.to_h.should be(h) end describe "clone" do it "clones with size = 1" do h1 = {1 => 2} h2 = h1.clone h1.should_not be(h2) h1.should eq(h2) end it "clones empty hash" do h1 = {} of Int32 => Int32 h2 = h1.clone h2.should be_empty end it "clones small hash" do h1 = {} of Int32 => Array(Int32) 4.times do |i| h1[i] = [i] end h2 = h1.clone h1.should_not be(h2) h1.should eq(h2) 4.times do |i| h1[i].should_not be(h2[i]) end h1.delete(0) h2[0].should eq([0]) end it "clones big hash" do h1 = {} of Int32 => Array(Int32) 1_000.times do |i| h1[i] = [i] end h2 = h1.clone h1.should_not be(h2) h1.should eq(h2) 1_000.times do |i| h1[i].should_not be(h2[i]) end h1.delete(0) h2[0].should eq([0]) end it "clones recursive hash" do h = {} of RecursiveHash => RecursiveHash h[h] = h clone = h.clone clone.should be(clone.first[1]) end it "retains default value" do h1 = Hash(Int32, String).new("a") h2 = h1.clone h2[0].should eq("a") h1[1] = "b" h3 = h1.clone h3[0].should eq("a") end it "retains default block" do h1 = Hash(Int32, String).new { "b" } h2 = h1.clone h2[0].should eq("b") end it "retains compare_by_identity" do h = ({} of String => Int32).compare_by_identity h.clone.compare_by_identity?.should be_true end end describe "dup" do it "dups empty hash" do h1 = {} of Int32 => Int32 h2 = h1.dup h2.should be_empty end it "dups small hash" do h1 = {} of Int32 => Array(Int32) 4.times do |i| h1[i] = [i] end h2 = h1.dup h1.should_not be(h2) h1.should eq(h2) 4.times do |i| h1[i].should be(h2[i]) end h1.delete(0) h2[0].should eq([0]) end it "dups big hash" do h1 = {} of Int32 => Array(Int32) 1_000.times do |i| h1[i] = [i] end h2 = h1.dup h1.should_not be(h2) h1.should eq(h2) 1_000.times do |i| h1[i].should be(h2[i]) end h1.delete(0) h2[0].should eq([0]) end it "retains default value" do h1 = Hash(Int32, String).new("a") h2 = h1.dup h2[0].should eq("a") h1[1] = "b" h3 = h1.dup h3[0].should eq("a") end it "retains default block" do h1 = Hash(Int32, String).new { "b" } h2 = h1.dup h2[0].should eq("b") end it "retains compare_by_identity" do h = ({} of String => Int32).compare_by_identity h.dup.compare_by_identity?.should be_true end end it "initializes with block" do h1 = Hash(String, Array(Int32)).new { |h, k| h[k] = [] of Int32 } h1["foo"].should eq([] of Int32) h1["bar"].push 2 h1["bar"].should eq([2]) end it "initializes with default value" do h = Hash(Int32, Int32).new(10) h[0].should eq(10) h.has_key?(0).should be_false h[1] += 2 h[1].should eq(12) h.has_key?(1).should be_true end describe "merge" do it "merges" do h1 = {1 => 2, 3 => 4} h2 = {1 => 5, 2 => 3} h3 = {"1" => "5", "2" => "3"} h4 = h1.merge(h2) h4.should_not be(h1) h4.should eq({1 => 5, 3 => 4, 2 => 3}) h5 = h1.merge(h3) h5.should_not be(h1) h5.should eq({1 => 2, 3 => 4, "1" => "5", "2" => "3"}) end it "merges with block" do h1 = {1 => 5, 2 => 3} h2 = {1 => 5, 3 => 4, 2 => 3} h3 = h2.merge(h1) { |k, v1, v2| k + v1 + v2 } h3.should_not be(h2) h3.should eq({1 => 11, 3 => 4, 2 => 8}) end it "merges recursive type (#1693)" do hash = {"foo" => "bar"} of String => RecursiveType result = hash.merge({"foobar" => "foo"}) result.should eq({"foo" => "bar", "foobar" => "foo"}) end it "merges other type with block" do h1 = {1 => "foo"} h2 = {1 => "bar", "fizz" => "buzz"} h3 = h1.merge(h2) { |k, v1, v2| v1 + v2 } h3.should eq({1 => "foobar", "fizz" => "buzz"}) end it "does not retain default value" do h = Hash(Int32, String).new("a") h.merge({1 => "a"})[0]?.should be_nil h.merge({1 => "a"}) { |k, v1, v2| v1 }[0]?.should be_nil end it "does not retain default block" do h = Hash(Int32, String).new { "b" } h.merge({1 => "a"})[0]?.should be_nil h.merge({1 => "a"}) { |k, v1, v2| v1 }[0]?.should be_nil end it "retains compare_by_identity" do h = ({} of String => Int32).compare_by_identity h.merge({"a" => 1}).compare_by_identity?.should be_true h.merge({"a" => 1}) { |k, v1, v2| v1 + v2 }.compare_by_identity?.should be_true end it "ignores compare_by_identity of an argument" do h = ({} of String => Int32).compare_by_identity {"a" => 1}.merge(h).compare_by_identity?.should be_false {"a" => 1}.merge(h) { |k, v1, v2| v1 + v2 }.compare_by_identity?.should be_false end end it "merges!" do h1 = {1 => 2, 3 => 4} h2 = {1 => 5, 2 => 3} h3 = h1.merge!(h2) h3.should be(h1) h3.should eq({1 => 5, 3 => 4, 2 => 3}) end it "merges! with block" do h1 = {1 => 5, 2 => 3} h2 = {1 => 5, 3 => 4, 2 => 3} h3 = h2.merge!(h1) { |k, v1, v2| k + v1 + v2 } h3.should be(h2) h3.should eq({1 => 11, 3 => 4, 2 => 8}) end it "merges! with block and nilable keys" do h1 = {1 => nil, 2 => 4, 3 => "x"} h2 = {1 => 2, 2 => nil, 3 => "y"} h3 = h1.merge!(h2) { |k, v1, v2| (v1 || v2).to_s } h3.should be(h1) h3.should eq({1 => "2", 2 => "4", 3 => "x"}) end it "selects" do h1 = {"a" => 1, "b" => 2, "c" => 3} h2 = h1.select { |k, v| k == "b" } h2.should eq({"b" => 2}) h2.should_not be(h1) end it "select with non-equality key" do h = {Float64::NAN => true, 0.0 => true} h.select { |k| !k.nan? }.should eq({0.0 => true}) end it "selects!" do h1 = {"a" => 1, "b" => 2, "c" => 3} h2 = h1.select! { |k, v| k == "b" } h2.should be_a(Hash(String, Int32)) h2.should eq({"b" => 2}) h2.should be(h1) end it "select! with non-equality key" do h = {Float64::NAN => true, 0.0 => true} h.select! { |k| !k.nan? } h.should eq({0.0 => true}) end it "rejects" do h1 = {"a" => 1, "b" => 2, "c" => 3} h2 = h1.reject { |k, v| k == "b" } h2.should eq({"a" => 1, "c" => 3}) h2.should_not be(h1) end it "reject with non-equality key" do h = {Float64::NAN => true, 0.0 => true} h.reject(&.nan?).should eq({0.0 => true}) end it "rejects!" do h1 = {"a" => 1, "b" => 2, "c" => 3} h2 = h1.reject! { |k, v| k == "b" } h2.should be_a(Hash(String, Int32)) h2.should eq({"a" => 1, "c" => 3}) h2.should be(h1) end it "reject with non-equality key" do h = {Float64::NAN => true, 0.0 => true} h.reject!(&.nan?) h.should eq({0.0 => true}) end describe "compact" do it "compacts" do h1 = {"a" => 1, "b" => 2, "c" => nil} h2 = h1.compact h2.should be_a(Hash(String, Int32)) h2.should eq({"a" => 1, "b" => 2}) end it "does not retain default value" do h = Hash(Int32, String).new("a") h.compact[0]?.should be_nil end it "does not retain default block" do h = Hash(Int32, String).new { "b" } h.compact[0]?.should be_nil end it "retains compare_by_identity" do h = ({} of String => Int32).compare_by_identity h.compact.compare_by_identity?.should be_true end end it "compacts!" do h1 = {"a" => 1, "b" => 2, "c" => nil} h2 = h1.compact! h2.should be_a(Hash(String, Int32 | Nil)) h2.should eq({"a" => 1, "b" => 2}) h2.should be(h1) end describe "transform_keys" do it "transforms keys" do h1 = {1 => "a", 2 => "b", 3 => "c"} h2 = h1.transform_keys { |x| x + 1 } h2.should eq({2 => "a", 3 => "b", 4 => "c"}) end it "transforms keys with type casting" do h1 = {"a" => 1, "b" => 2, "c" => 3} h2 = h1.transform_keys(&.to_s.upcase) h2.should be_a(Hash(String, Int32)) h2.should eq({"A" => 1, "B" => 2, "C" => 3}) end it "returns empty hash when transforming keys of an empty hash" do h1 = {} of Int32 => String h2 = h1.transform_keys { |x| x + 1 } h2.should be_a(Hash(Int32, String)) h2.should be_empty end it "transforms keys with values included" do h1 = {1 => "a", 2 => "b", 3 => "c"} h2 = h1.transform_keys { |k, v| "#{k}#{v}" } h2.should eq({"1a" => "a", "2b" => "b", "3c" => "c"}) end it "does not retain default value" do h = Hash(Int32, String).new("a") h.transform_keys(&.succ)[0]?.should be_nil end it "does not retain default block" do h = Hash(Int32, String).new { "b" } h.transform_keys(&.succ)[0]?.should be_nil end it "does not retain compare_by_identity" do h = ({} of String => Int32).compare_by_identity h.transform_keys(&.to_s.upcase).compare_by_identity?.should be_false end end describe "transform_keys!" do it "transforms keys in place" do h = {1 => "a", 2 => "b", 3 => "c"} h.transform_keys! { |x| x + 1 }.should be(h) h.should eq({2 => "a", 3 => "b", 4 => "c"}) end it "does nothing when empty hash" do h = {} of Int32 => String h.transform_keys! { |x| x + 1 } h.should be_empty end it "transforms keys with values included" do h = {"1" => "a", "2" => "b", "3" => "c"} h.transform_keys! { |k, v| "#{k}#{v}" } h.should eq({"1a" => "a", "2b" => "b", "3c" => "c"}) end it "allows transformed key type to be a subtype of the original type" do h = {"1" => "foo", 2 => "bar"} # Hash(Int32 | String, String) h.transform_keys!(&.to_i) h.should eq({1 => "foo", 2 => "bar"}) end end describe "transform_values" do it "transforms values" do h1 = {"a" => 1, "b" => 2, "c" => 3} h2 = h1.transform_values { |x| x + 1 } h2.should eq({"a" => 2, "b" => 3, "c" => 4}) end it "transforms values with type casting values" do h1 = {"a" => 1, "b" => 2, "c" => 3} h2 = h1.transform_values(&.to_s) h2.should be_a(Hash(String, String)) h2.should eq({"a" => "1", "b" => "2", "c" => "3"}) end it "returns empty hash when transforming values of an empty hash" do h1 = {} of String => Int32 h2 = h1.transform_values { |x| x + 1 } h2.should be_a(Hash(String, Int32)) h2.should be_empty end it "transforms values with keys included" do h1 = {"a" => 1, "b" => 2, "c" => 3} h2 = h1.transform_values { |v, k| "#{k}#{v}" } h2.should eq({"a" => "a1", "b" => "b2", "c" => "c3"}) end it "does not retain default value" do h = Hash(Int32, String).new("a") h.transform_values(&.succ)[0]?.should be_nil end it "does not retain default block" do h = Hash(Int32, String).new { "b" } h.transform_values(&.succ)[0]?.should be_nil end it "retains compare_by_identity" do h = ({} of String => Int32).compare_by_identity h.transform_values(&.to_s).compare_by_identity?.should be_true end end it "transform values in place" do h = {"a" => 1, "b" => 2, "c" => 3} h.transform_values!(&.+(1)) h.should eq({"a" => 2, "b" => 3, "c" => 4}) end it "transform values in place with keys included" do h = {"a" => "1", "b" => "2", "c" => "3"} h.transform_values! { |v, k| "#{k}#{v}" } h.should eq({"a" => "a1", "b" => "b2", "c" => "c3"}) end it "zips" do ary1 = [1, 2, 3] ary2 = ['a', 'b', 'c'] hash = Hash.zip(ary1, ary2) hash.should eq({1 => 'a', 2 => 'b', 3 => 'c'}) end it "gets first" do h = {1 => 2, 3 => 4} h.first.should eq({1, 2}) end describe "first_key" do it "gets first key" do h = {1 => 2, 3 => 4} h.first_key.should eq(1) end it "raises on first key (nilable key)" do h = {} of Int32? => Int32 expect_raises(Exception, "Can't get first key of empty Hash") do h.first_key end end it "doesn't raise on first key (nilable key)" do h = {nil => 1} of Int32? => Int32 h.first_key.should be_nil end end describe "first_value" do it "gets first value" do h = {1 => 2, 3 => 4} h.first_value.should eq(2) end it "raises on first value (nilable value)" do h = {} of Int32 => Int32? expect_raises(Exception, "Can't get first value of empty Hash") do h.first_value end end it "doesn't raise on first value (nilable value)" do h = {1 => nil} of Int32 => Int32? h.first_value.should be_nil end end describe "last_key" do it "gets last key" do h = {1 => 2, 3 => 4} h.last_key.should eq(3) end it "raises on last key (nilable key)" do h = {} of Int32? => Int32 expect_raises(Exception, "Can't get last key of empty Hash") do h.last_key end end it "doesn't raise on last key (nilable key)" do h = {nil => 1} of Int32? => Int32 h.last_key.should be_nil end end describe "last_value" do it "gets last value" do h = {1 => 2, 3 => 4} h.last_value.should eq(4) end it "raises on last value (nilable value)" do h = {} of Int32 => Int32? expect_raises(Exception, "Can't get last value of empty Hash") do h.last_value end end it "doesn't raise on last value (nilable value)" do h = {1 => nil} of Int32 => Int32? h.last_value.should be_nil end end it "shifts" do h = {1 => 2, 3 => 4} h.shift.should eq({1, 2}) h.should eq({3 => 4}) h.first_key.should eq(3) h.first_value.should eq(4) h[1]?.should be_nil h[3].should eq(4) h.each.to_a.should eq([{3, 4}]) h.each_key.to_a.should eq([3]) h.each_value.to_a.should eq([4]) h.shift.should eq({3, 4}) h.should be_empty expect_raises(IndexError) do h.shift end 20.times do |i| h[i] = i end h.size.should eq(20) 20.times do |i| h.shift.should eq({i, i}) end h.should be_empty end it "shifts: delete elements in the middle position and then in the first position" do h = {1 => 'a', 2 => 'b', 3 => 'c', 4 => 'd'} h.delete(2) h.delete(3) h.delete(1) h.size.should eq(1) h.should eq({4 => 'd'}) h.first.should eq({4, 'd'}) end it "shifts?" do h = {1 => 2} h.shift?.should eq({1, 2}) h.should be_empty h.shift?.should be_nil end it "inserts many" do times = 1000 h = {} of Int32 => Int32 times.times do |i| h[i] = i h.size.should eq(i + 1) end times.times do |i| h[i].should eq(i) end h.first_key.should eq(0) h.first_value.should eq(0) times.times do |i| h.delete(i).should eq(i) h.has_key?(i).should be_false h.size.should eq(times - i - 1) end end it "inserts in one bucket and deletes from the same one" do h = {11 => 1} h.delete(0).should be_nil h.has_key?(11).should be_true h.size.should eq(1) end it "does to_a" do h = {1 => "hello", 2 => "bye"} h.to_a.should eq([{1, "hello"}, {2, "bye"}]) end it "does to_a after shift" do h = {1 => 'a', 2 => 'b', 3 => 'c'} h.shift h.to_a.should eq([{2, 'b'}, {3, 'c'}]) end it "does to_a after delete" do h = {1 => 'a', 2 => 'b', 3 => 'c'} h.delete(2) h.to_a.should eq([{1, 'a'}, {3, 'c'}]) end it "clears" do h = {1 => 2, 3 => 4} h.clear h.should be_empty h.to_a.size.should eq(0) end it "clears after shift" do h = {1 => 2, 3 => 4} h.shift h.clear h.should be_empty h.to_a.size.should eq(0) h[5] = 6 h.should_not be_empty h[5].should eq(6) h.should eq({5 => 6}) end it "computes hash" do h1 = { {1 => 2} => {3 => 4} } h2 = { {1 => 2} => {3 => 4} } h1.hash.should eq(h2.hash) h3 = {1 => 2, 3 => 4} h4 = {3 => 4, 1 => 2} h3.hash.should eq(h4.hash) end it "fetches from empty hash with default value" do x = {} of Int32 => HashBreaker breaker = x.fetch(10) { HashBreaker.new(1) } breaker.x.should eq(1) end it "does to to_s with instance that was never instantiated" do x = {} of Int32 => NeverInstantiated x.to_s.should eq("{}") end describe "invert" do it "inverts" do h1 = {"one" => 1, "two" => 2, "three" => 3} h2 = {"a" => 1, "b" => 2, "c" => 1} h1.invert.should eq({1 => "one", 2 => "two", 3 => "three"}) h3 = h2.invert h3.size.should eq(2) %w(a c).should contain h3[1] end it "does not retain default value" do h = Hash(Int32, String).new("a") h.invert[0]?.should be_nil end it "does not retain default block" do h = Hash(Int32, String).new { "b" } h.invert[0]?.should be_nil end it "does not retain compare_by_identity" do h = ({} of String => Int32).compare_by_identity h.invert.compare_by_identity?.should be_false end end it "does each" do hash = {"foo" => 1, "bar" => 2} ks = [] of String vs = [] of Int32 hash.each do |k, v| ks << k vs << v end.should be_nil ks.should eq(["foo", "bar"]) vs.should eq([1, 2]) end it "does each_key" do hash = {"foo" => 1, "bar" => 2} ks = [] of String hash.each_key do |k| ks << k end.should be_nil ks.should eq(["foo", "bar"]) end it "does each_value" do hash = {"foo" => 1, "bar" => 2} vs = [] of Int32 hash.each_value do |v| vs << v end.should be_nil vs.should eq([1, 2]) end it_iterates "#each", [{"a", 1}, {"b", 2}], {"a" => 1, "b" => 2}.each it_iterates "#each_key", ["a", "b"], {"a" => 1, "b" => 2}.each_key it_iterates "#each_value", [1, 2], {"a" => 1, "b" => 2}.each_value it_iterates "#each_with_index", [{ {"a", 1}, 0 }, { {"b", 2}, 1 }], {"a" => 1, "b" => 2}.each_with_index, tuple: true it_iterates "#each_with_index(offset)", [{ {"a", 1}, 2 }, { {"b", 2}, 3 }], {"a" => 1, "b" => 2}.each_with_index(2), tuple: true describe "#each_with_object" do it_iterates "passes memo, key and value into block", [{ {"a", 1}, "memo" }, { {"b", 2}, "memo" }], {"a" => 1, "b" => 2}.each_with_object("memo"), tuple: true it "reduces the hash to the accumulated value of memo" do hash = {"a" => 'b', "c" => 'd', "e" => 'f'} result = {} of Char => String hash.each_with_object(result) do |(k, v), memo| memo[v] = k end.should be(result) result.should eq({'b' => "a", 'd' => "c", 'f' => "e"}) end end describe "all?" do it "passes key and value into block" do hash = {"a" => 'b'} hash.all? do |k, v| k.should eq("a") v.should eq('b') end end it "returns true if the block evaluates truthy for every kv pair" do hash = {"a" => 'b', "c" => 'd'} result = hash.all? { |k, v| v < 'e' ? "truthy" : nil } result.should be_true hash["d"] = 'e' result = hash.all? { |k, v| v < 'e' ? "truthy" : nil } result.should be_false end it "evaluates the block for only for as many kv pairs as necessary" do hash = {"a" => 'b', "c" => 'd'} hash.all? do |k, v| raise Exception.new("continued iterating") if v == 'd' v == 'a' # this is false for the first kv pair end end end describe "any?" do it "passes key and value into block" do hash = {"a" => 'b'} hash.any? do |k, v| k.should eq("a") v.should eq('b') end end it "returns true if the block evaluates truthy for at least one kv pair" do hash = {"a" => 'b', "c" => 'd'} result = hash.any? { |k, v| v > 'b' ? "truthy" : nil } result.should be_true hash["d"] = 'e' result = hash.any? { |k, v| v > 'e' ? "truthy" : nil } result.should be_false end it "evaluates the block for only for as many kv pairs as necessary" do hash = {"a" => 'b', "c" => 'd'} hash.any? do |k, v| raise Exception.new("continued iterating") if v == 'd' v == 'b' # this is true for the first kv pair end end it "returns true if the hash contains at least one kv pair and no block is given" do hash = {"a" => 'b'} result = hash.any? result.should be_true hash = {} of String => Char result = hash.any? result.should be_false end end describe "reduce" do it "passes memo, key and value into block" do hash = {"a" => 'b'} hash.reduce("") do |memo, (k, v)| memo.should eq("") k.should eq("a") v.should eq('b') end end it "reduces the hash to the accumulated value of memo" do hash = {"a" => 'b', "c" => 'd', "e" => 'f'} result = hash.reduce("") do |memo, (k, v)| memo + v end result.should eq("bdf") end end describe "reject" do it { {"a" => 2, "b" => 3}.reject("b", "d").should eq({"a" => 2}) } it { {"a" => 2, "b" => 3}.reject(Set{"b", "d"}).should eq({"a" => 2}) } it { {"a" => 2, "b" => 3}.reject("b", "a").should eq({} of String => Int32) } it { {"a" => 2, "b" => 3}.reject(["b", "a"]).should eq({} of String => Int32) } it "does not change current hash" do h = {"a" => 3, "b" => 6, "c" => 9} h.reject("b", "c") h.should eq({"a" => 3, "b" => 6, "c" => 9}) end it "does not retain default value" do h = Hash(Int32, String).new("a") h.reject(42)[0]?.should be_nil h.reject([42])[0]?.should be_nil h.reject { false }[0]?.should be_nil end it "does not retain default block" do h = Hash(Int32, String).new { "b" } h.reject(42)[0]?.should be_nil h.reject([42])[0]?.should be_nil h.reject { false }[0]?.should be_nil end it "retains compare_by_identity" do h = ({} of String => Int32).compare_by_identity h.reject("a").compare_by_identity?.should be_true h.reject(["a"]).compare_by_identity?.should be_true h.reject { false }.compare_by_identity?.should be_true end end describe "reject!" do it { {"a" => 2, "b" => 3}.reject!("b", "d").should eq({"a" => 2}) } it { {"a" => 2, "b" => 3}.reject!(Set{"b", "d"}).should eq({"a" => 2}) } it { {"a" => 2, "b" => 3}.reject!("b", "a").should eq({} of String => Int32) } it { {"a" => 2, "b" => 3}.reject!(["b", "a"]).should eq({} of String => Int32) } it "changes current hash" do h = {"a" => 3, "b" => 6, "c" => 9} h.reject!("b", "c") h.should eq({"a" => 3}) end end describe "select" do it { {"a" => 2, "b" => 3}.select("b", "d").should eq({"b" => 3}) } it { {"a" => 2, "b" => 3}.select.should eq({} of String => Int32) } it { {"a" => 2, "b" => 3}.select("b", "a").should eq({"a" => 2, "b" => 3}) } it { {"a" => 2, "b" => 3}.select(["b", "a"]).should eq({"a" => 2, "b" => 3}) } it { {"a" => 2, "b" => 3}.select(Set{"b", "a"}).should eq({"a" => 2, "b" => 3}) } it "does not change current hash" do h = {"a" => 3, "b" => 6, "c" => 9} h.select("b", "c") h.should eq({"a" => 3, "b" => 6, "c" => 9}) end it "does not retain default value" do h = Hash(Int32, String).new("a") h.select(42)[0]?.should be_nil h.select([42])[0]?.should be_nil h.select { true }[0]?.should be_nil end it "does not retain default block" do h = Hash(Int32, String).new { "b" } h.select(42)[0]?.should be_nil h.select([42])[0]?.should be_nil h.select { true }[0]?.should be_nil end it "retains compare_by_identity" do h = ({} of String => Int32).compare_by_identity h.select("a").compare_by_identity?.should be_true h.select(["a"]).compare_by_identity?.should be_true h.select { true }.compare_by_identity?.should be_true end end describe "select!" do it { {"a" => 2, "b" => 3}.select!("b", "d").should eq({"b" => 3}) } it { {"a" => 2, "b" => 3}.select!.should eq({} of String => Int32) } it { {"a" => 2, "b" => 3}.select!("b", "a").should eq({"a" => 2, "b" => 3}) } it { {"a" => 2, "b" => 3}.select!(["b", "a"]).should eq({"a" => 2, "b" => 3}) } it { {"a" => 2, "b" => 3}.select!(Set{"b", "a"}).should eq({"a" => 2, "b" => 3}) } it "does change current hash" do h = {"a" => 3, "b" => 6, "c" => 9} h.select!("b", "c") h.should eq({"b" => 6, "c" => 9}) end it "does not skip elements with an exhaustable enumerable argument (#12736)" do h = {1 => 'a', 2 => 'b', 3 => 'c'}.select!({1, 2, 3}.each) h.should eq({1 => 'a', 2 => 'b', 3 => 'c'}) end end it "doesn't generate a negative index for the bucket index (#2321)" do items = (0..100000).map { rand(100000).to_i16! } items.uniq.size end it "creates with initial capacity" do hash = Hash(Int32, Int32).new(initial_capacity: 1234) hash.@indices_size_pow2.should eq(12) end it "creates with initial capacity and default value" do hash = Hash(Int32, Int32).new(default_value: 3, initial_capacity: 1234) hash[1].should eq(3) hash.@indices_size_pow2.should eq(12) end it "creates with initial capacity and block" do hash = Hash(Int32, Int32).new(initial_capacity: 1234) { |h, k| h[k] = 3 } hash[1].should eq(3) hash.@indices_size_pow2.should eq(12) end describe "#rehash" do it "rehashes" do a = [1] h = {a => 0} (10..100).each do |i| h[[i]] = i end a << 2 h[a]?.should be_nil h.rehash h[a].should eq(0) end it "resets @first (#14602)" do h = {"a" => 1, "b" => 2} h.delete("a") h.rehash # We cannot test direct equivalence here because `Hash#==(Hash)` does not depend on `@first` h.to_s.should eq %({"b" => 2}) end end describe "some edge cases while changing the implementation to open addressing" do it "edge case 1" do h = {1 => 10} h[1]?.should eq(10) h.size.should eq(1) h.delete(1) h[1]?.should be_nil h.size.should eq(0) h[2] = 10 h[2]?.should eq(10) h.size.should eq(1) h[2] = 10 h[2]?.should eq(10) h.size.should eq(1) end it "edge case 2" do hash = Hash(Int32, Int32).new(initial_capacity: 0) hash.@indices_size_pow2.should eq(0) hash[1] = 2 hash[1].should eq(2) end it "edge case 3" do h = {} of Int32 => Int32 (1 << 17).times do |i| h[i] = i h[i].should eq(i) end end end describe "compare_by_identity" do it "small hash" do string = "foo" h = {string => 1} h.compare_by_identity?.should be_false h.compare_by_identity h.compare_by_identity?.should be_true h[string]?.should eq(1) h["fo" + "o"]?.should be_nil end it "big hash" do h = {} of String => Int32 nums = (100..116).to_a strings = nums.map(&.to_s) strings.zip(nums) do |string, num| h[string] = num end h.compare_by_identity nums.each do |num| h[num.to_s]?.should be_nil end strings.zip(nums) do |string, num| h[string]?.should eq(num) end end end it "can be wrapped" do HashWrapper(Int32, Int32).new.to_a.should be_empty end end