Skip to content

Commit

Permalink
Version bump to add a couple of methods to search keys for approximat…
Browse files Browse the repository at this point in the history
…e matches, and to cleanup style issues.
  • Loading branch information
wyhaines committed Aug 5, 2024
1 parent 668a4d9 commit 741dab6
Show file tree
Hide file tree
Showing 5 changed files with 107 additions and 30 deletions.
2 changes: 1 addition & 1 deletion VERSION
Original file line number Diff line number Diff line change
@@ -1 +1 @@
0.2.2
0.3.0
2 changes: 1 addition & 1 deletion shard.lock
Original file line number Diff line number Diff line change
Expand Up @@ -2,5 +2,5 @@ version: 2.0
shards:
ameba:
git: https://github.com/crystal-ameba/ameba.git
version: 1.4.3
version: 1.6.1

4 changes: 2 additions & 2 deletions shard.yml
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
name: splay_tree_map
version: 0.2.2
version: 0.3.0

authors:
- Kirk Haines <[email protected]>
Expand All @@ -11,4 +11,4 @@ license: "Apache 2.0"
development_dependencies:
ameba:
github: crystal-ameba/ameba
version: ~> 1.4
version: ~> 1.6.1
59 changes: 42 additions & 17 deletions spec/splay_tree_spec.cr
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ describe SplayTreeMap do

it "can create trees with complex keys" do
st = SplayTreeMap({String, String}, String).new
10.times { |n| st[{n.to_s, n.to_s}] = n.to_s }
10.times { |num| st[{num.to_s, num.to_s}] = num.to_s }

st.size.should eq 10
st[{"5", "5"}].should eq "5"
Expand All @@ -26,7 +26,7 @@ describe SplayTreeMap do
end

it "can create a tree with a block to initialize missing values" do
st = SplayTreeMap(String, Array(Int32)).new { |t, k| t[k] = [] of Int32 }
st = SplayTreeMap(String, Array(Int32)).new { |tmp, key| tmp[key] = [] of Int32 }
st["a"] << 1
st["a"] << 2
st["a"] << 3
Expand Down Expand Up @@ -117,9 +117,9 @@ describe SplayTreeMap do
intermediate_100.each { |x| intermediate_heights << (st.height(x) || 0) }
regular_100.each { |x| regular_heights << (st.height(x) || 0) }

sum_top_100 = top_heights.reduce(0) { |a, v| a + v }
sum_intermediate_100 = intermediate_heights.reduce(0) { |a, v| a + v }
sum_regular_100 = regular_heights.reduce(0) { |a, v| a + v }
sum_top_100 = top_heights.reduce(0) { |acc, val| acc + val }
sum_intermediate_100 = intermediate_heights.reduce(0) { |acc, val| acc + val }
sum_regular_100 = regular_heights.reduce(0) { |acc, val| acc + val }

Log.debug { "average height -- top :: intermediate :: other == #{sum_top_100 / 100} :: #{sum_intermediate_100 / 100} :: #{sum_regular_100 / 100}" }
sum_top_100.should be < sum_intermediate_100
Expand Down Expand Up @@ -221,9 +221,9 @@ describe SplayTreeMap do
log.size.should eq 10

n = 0
stm.each do |k, _v|
stm.each do |key, _val|
n += 1
log.delete(k)
log.delete(key)
end

n.should eq 10
Expand Down Expand Up @@ -336,8 +336,8 @@ describe SplayTreeMap do
other1 = SplayTreeMap.new({"b" => 254, "c" => 300})
stm2 = SplayTreeMap.new({1 => 1, 2 => 2})
other2 = SplayTreeMap.new({2 => 4, 3 => 9})
stm2.merge!(other2) { |_k, v1, v2| v1 + v2 }
stm1.merge!(other1) { |_k, v1, v2| v1 + v2 }
stm2.merge!(other2) { |_key, val1, val2| val1 + val2 }
stm1.merge!(other1) { |_key, val1, val2| val1 + val2 }
stm1["a"].should eq 100
stm1["b"].should eq 454
stm1["c"].should eq 300
Expand Down Expand Up @@ -421,20 +421,45 @@ describe SplayTreeMap do
st.last.should eq({9, 9})
end

it "max; can find the max key" do
st = SplayTreeMap(Int32, Int32).new
10.times { |x| st[x] = x }

st.max.should eq 9
end

it "max(limit); can find the largest key less than or equal to the limit" do
st = SplayTreeMap(Int32, Int32).new
10.times { |x| st[x * 2] = x * 2 }

st.max(6).should eq 6
st.max(7).should eq 6
st.max(8).should eq 8
end

it "min; can find the min key" do
st = SplayTreeMap(Int32, Int32).new
10.times { |x| st[x] = x }

st.min.should eq 0
end

it "min(limit); can find the smallest key greater than or equal to the limit" do
st = SplayTreeMap(Int32, Int32).new
10.times { |x| st[x * 2] = x * 2 }

st.min(6).should eq 6
st.min(7).should eq 8
st.min(8).should eq 8
end

it "reject; can create a new tree with select keys removed" do
stm = SplayTreeMap.new({"a" => 100, "b" => 200, "c" => 300})
res = stm.reject { |k, _v| k > "a" }
res = stm.reject { |key, _val| key > "a" }
res.size.should eq 1
res["a"].should eq 100
res.has_key?("b").should be_false
res = stm.reject { |_k, v| v < 200 }
res = stm.reject { |_key, val| val < 200 }
res.size.should eq 2
res.has_key?("a").should be_false
res["c"].should eq 300
Expand All @@ -454,12 +479,12 @@ describe SplayTreeMap do

it "reject!; can remove a set of keys from the current tree" do
stm = SplayTreeMap.new({"a" => 100, "b" => 200, "c" => 300})
stm.reject! { |k, _v| k > "a" }
stm.reject! { |key, _val| key > "a" }
stm.size.should eq 1
stm["a"].should eq 100
stm.has_key?("b").should be_false
stm = SplayTreeMap.new({"a" => 100, "b" => 200, "c" => 300})
stm.reject! { |_k, v| v < 200 }
stm.reject! { |_key, val| val < 200 }
stm.size.should eq 2
stm.has_key?("a").should be_false
stm["c"].should eq 300
Expand All @@ -481,11 +506,11 @@ describe SplayTreeMap do
end

it "select; can create a new tree that includes only specific keys" do
stm = SplayTreeMap.new({"a" => 100, "b" => 200, "c" => 300}).select { |k, _v| k > "a" }
stm = SplayTreeMap.new({"a" => 100, "b" => 200, "c" => 300}).select { |key, _val| key > "a" }
stm.size.should eq 2
stm["b"].should eq 200
stm["c"].should eq 300
stm = SplayTreeMap.new({"a" => 100, "b" => 200, "c" => 300}).select { |_k, v| v < 200 }
stm = SplayTreeMap.new({"a" => 100, "b" => 200, "c" => 300}).select { |_key, val| val < 200 }
stm.size.should eq 1
stm["a"].should eq 100
stm = SplayTreeMap.new({"a" => 1, "b" => 2, "c" => 3, "d" => 4}).select({"a", "c"})
Expand All @@ -504,12 +529,12 @@ describe SplayTreeMap do

it "select!; can remove all keys from the current tree except for a small set" do
stm = SplayTreeMap.new({"a" => 100, "b" => 200, "c" => 300})
stm.select! { |k, _v| k > "a" }
stm.select! { |key, _val| key > "a" }
stm.size.should eq 2
stm["b"].should eq 200
stm["c"].should eq 300
stm = SplayTreeMap.new({"a" => 100, "b" => 200, "c" => 300})
stm.select! { |_k, v| v < 200 }
stm.select! { |_key, val| val < 200 }
stm.size.should eq 1
stm["a"].should eq 100
stm = SplayTreeMap.new({"a" => 1, "b" => 2, "c" => 3, "d" => 4})
Expand Down
70 changes: 61 additions & 9 deletions src/splay_tree_map.cr
Original file line number Diff line number Diff line change
Expand Up @@ -271,7 +271,7 @@ class SplayTreeMap(K, V)

# :nodoc:
def push(key, value)
# TODO: This is surprisingly slow. I assume it is due to the overhead
# This is surprisingly slow. I assume it is due to the overhead
# of declaring nodes on the heap. Is there a way to make them work as
# structs instead of classes?
@lock.synchronize do
Expand Down Expand Up @@ -678,8 +678,8 @@ class SplayTreeMap(K, V)
# ```
#
def has_value?(value) : Bool
self.each do |_k, v|
return true if v == value
self.each do |_key, val|
return true if val == value
end
false
end
Expand Down Expand Up @@ -782,7 +782,7 @@ class SplayTreeMap(K, V)
def keys : Array(K)
@lock.synchronize do
a = [] of K
each { |k, _v| a << k }
each { |key, _value| a << key }
a
end
end
Expand All @@ -799,6 +799,32 @@ class SplayTreeMap(K, V)
n.try &.key
end

# Returns the largest key equal to or less than the provided limit argument.
# Returns nil if the SplayTreeMap is empty.
def max(limit)
return nil unless @root

n = @root
while n
cmp = limit <=> n.key
if cmp == 0
return n.key
elsif cmp == -1
n = n.left
else
if right = n.right
if limit < right.key
return n.key
else
n = n.right
end
else
return n.key
end
end
end
end

# Adds the contents of *other* to this `SplayTreeMap`.
#
# For Array-like structures, which return a single value to the block passed
Expand Down Expand Up @@ -833,7 +859,7 @@ class SplayTreeMap(K, V)
# stm[7] # => 343
#
def merge!(other : T) forall T
self.merge!(other) { |_k, _v1, v2| v2 }
self.merge!(other) { |_key, _value1, value2| value2 }
end

# Adds the contents of *other* to this `SplayTreeMap`.
Expand Down Expand Up @@ -942,12 +968,38 @@ class SplayTreeMap(K, V)
n.try &.key
end

# Returns the smallest key equal to or greater than the provided limit argument.
# Returns nil if the SplayTreeMap is empty.

def min(limit)
return nil unless @root

n = @root
while n
cmp = limit <=> n.key
if cmp == 0
return n.key
elsif cmp == -1
if left = n.left
if limit > left.key
return n.key
else
n = n.left
end
else
return n.key
end
else
n = n.right
end
end
end

# This will remove all of the leaves at the end of the tree branches.
# That is, every node that does not have any children. This will tend
# to remove the least used elements from the tree.
# This function is expensive, as implemented, as it must walk every
# node in the tree.
# TODO: Come up with a more efficient way of getting this same effect.
def prune
@was_pruned = false
return if @root.nil?
Expand All @@ -957,7 +1009,7 @@ class SplayTreeMap(K, V)
height_limit = height / 2

@lock.synchronize do
# TODO: Better to do an if root = @root.... and then raise an exception if root is nil?
# Is it better to do an if root = @root.... and then raise an exception if root is nil?
# ameba:disable Lint/NotNil
descend_from(@root.not_nil!, height_limit)
# ameba:disable Lint/NotNil
Expand Down Expand Up @@ -1101,7 +1153,7 @@ class SplayTreeMap(K, V)
# h1 # => {"a" => 1, "c" => 3}
# ```
def select!(keys : Array | Tuple)
each { |k, _v| delete(k) unless keys.includes?(k) }
each { |key, _value| delete(key) unless keys.includes?(key) }
self
end

Expand Down Expand Up @@ -1218,7 +1270,7 @@ class SplayTreeMap(K, V)
#
def values : Array(V)
a = [] of V
each { |_k, v| a << v }
each { |_key, value| a << value }
a
end

Expand Down

0 comments on commit 741dab6

Please sign in to comment.