Skip to content

Commit

Permalink
Add Hash/Indexable#dig/dig?
Browse files Browse the repository at this point in the history
  • Loading branch information
Sija committed Sep 14, 2018
1 parent 7954c04 commit de7558e
Show file tree
Hide file tree
Showing 4 changed files with 192 additions and 0 deletions.
45 changes: 45 additions & 0 deletions spec/std/hash_spec.cr
Original file line number Diff line number Diff line change
Expand Up @@ -105,6 +105,51 @@ describe "Hash" do
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 eq(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 one argument" do
a = {1 => 2}
Expand Down
48 changes: 48 additions & 0 deletions spec/std/indexable_spec.cr
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,24 @@ private class SafeMixedIndexable
end
end

private class SafeRecursiveIndexable
include Indexable(SafeRecursiveIndexable | Int32)

property size

def initialize(@size : Int32)
end

def unsafe_at(i)
raise IndexError.new unless 0 <= i < size
if (i % 2) == 0
SafeRecursiveIndexable.new(i)
else
i
end
end
end

describe Indexable do
it "does index with big negative offset" do
indexable = SafeIndexable.new(3)
Expand Down Expand Up @@ -166,4 +184,34 @@ describe Indexable do
indexable.join(", ").should eq("0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11")
indexable.join(98).should eq("098198298398498598698798898998109811")
end

describe "dig?" do
it "gets the value at given path given splat" do
indexable = SafeRecursiveIndexable.new(30)
indexable.dig?(20, 10, 4, 3).should eq(3)
end

it "returns nil if not found" do
indexable = SafeRecursiveIndexable.new(30)
indexable.dig?(2, 4).should be_nil
indexable.dig?(3, 7).should be_nil
end
end

describe "dig" do
it "gets the value at given path given splat" do
indexable = SafeRecursiveIndexable.new(30)
indexable.dig(20, 10, 4, 3).should eq(3)
end

it "raises IndexError if not found" do
indexable = SafeRecursiveIndexable.new(30)
expect_raises IndexError, %(Index out of bounds) do
indexable.dig(2, 4)
end
expect_raises IndexError, %(Indexable value not diggable for index: 3) do
indexable.dig(3, 7)
end
end
end
end
59 changes: 59 additions & 0 deletions src/hash.cr
Original file line number Diff line number Diff line change
Expand Up @@ -77,6 +77,65 @@ class Hash(K, V)
fetch(key, nil)
end

# Traverses the depth of a structure and returns the value.
# Returns `nil` if not found.
#
# ```
# h = {"a" => {"b" => [10, 20, 30]}}
# h.dig? "a", "b" # => [10, 20, 30]
# h.dig? "a", "b", "c", "d", "e" # => nil
# ```
def dig?(key : K, *subkeys)
if (value = self[key]?) && value.responds_to?(:dig?)
value.dig?(*subkeys)
end
end

# :nodoc:
def dig?(key : K)
self[key]?
end

# Traverses the depth of a structure and returns the value.
# Returns `nil` if not found.
#
# ```
# h = {"a" => {"b" => [10, 20, 30]}}
# h.dig? "a", "b" # => [10, 20, 30]
# h.dig? "a", "b", "c", "d", "e" # => nil
# ```
def dig?(key : K, *subkeys)
if (value = self[key]?) && value.responds_to?(:dig?)
value.dig?(*subkeys)
end
end

# :nodoc:
def dig?(key : K)
self[key]?
end

# Traverses the depth of a structure and returns the value, otherwise
# raises `KeyError`.
#
# ```
# h = {"a" => {"b" => [10, 20, 30]}}
# h.dig "a", "b" # => [10, 20, 30]
# h.dig "a", "b", "c", "d", "e" # raises KeyError
# ```
def dig(key : K, *subkeys)
value = self[key]
unless value.responds_to?(:dig)
raise KeyError.new "Hash value not diggable for key: #{key.inspect}"
end
value.dig(*subkeys)
end

# :nodoc:
def dig(key : K)
self[key]
end

# Returns `true` when key given by *key* exists, otherwise `false`.
#
# ```
Expand Down
40 changes: 40 additions & 0 deletions src/indexable.cr
Original file line number Diff line number Diff line change
Expand Up @@ -93,6 +93,46 @@ module Indexable(T)
at(index) { nil }
end

# Traverses the depth of a structure and returns the value.
# Returns `nil` if not found.
#
# ```
# ary = [{1, 2, 3, {4, 5, 6}}]
# ary.dig?(0, 3, 2) # => 6
# ary.dig?(0, 3, 3) # => nil
# ```
def dig?(index : Int, *subkeys)
if (value = self[index]?) && value.responds_to?(:dig?)
value.dig?(*subkeys)
end
end

# :nodoc:
def dig?(index : Int)
self[index]?
end

# Traverses the depth of a structure and returns the value, otherwise
# raises `IndexError`.
#
# ```
# ary = [{1, 2, 3, {4, 5, 6}}]
# ary.dig(0, 3, 2) # => 6
# ary.dig(0, 3, 3) # raises IndexError
# ```
def dig(index : Int, *subkeys)
value = self[index]
unless value.responds_to?(:dig)
raise IndexError.new "Indexable value not diggable for index: #{index.inspect}"
end
value.dig(*subkeys)
end

# :nodoc:
def dig(index : Int)
self[index]
end

# By using binary search, returns the first element
# for which the passed block returns `true`.
#
Expand Down

0 comments on commit de7558e

Please sign in to comment.