Skip to content

Commit

Permalink
added specs for all SimpleSckiplist#search options except :reverse
Browse files Browse the repository at this point in the history
  • Loading branch information
Oleg Andreev committed Apr 27, 2008
1 parent e17fd6d commit c115223
Show file tree
Hide file tree
Showing 2 changed files with 100 additions and 19 deletions.
38 changes: 24 additions & 14 deletions lib/strokedb/data_structures/simple_skiplist.rb
Expand Up @@ -42,11 +42,9 @@ def empty?
!node_next(@head, 0)
end

# Complicated search algorithm.
# Implementation plan:
# 1. no reverse, no with_keys - just find a start_key and move until end_key or limit.
# 2. with_keys support
# 3. reverse support
# Complicated search algorithm
# TODO: add reverse support
# TODO: add key duplication support
#
def search(start_key, end_key, limit, offset, reverse, with_keys)
offset ||= 0
Expand All @@ -64,10 +62,22 @@ def search(start_key, end_key, limit, offset, reverse, with_keys)
#
def find_by_prefix(start_key, reverse)
# TODO: add reverse support
!start_key and return node_next(node_first, 0)
x = find_nearest_node(start_key)
start_key and node_key(x)[0, start_key.size] != start_key and return nil
x
x = node_first # head [FIXME: change method name]
# if no prefix given, just return a first node
!start_key and return node_next(x, 0)

level = node_level(x)
while level > 0
level -= 1
xnext = node_next(x, level)
while node_compare(xnext, start_key) < 0
x = xnext
xnext = node_next(x, level)
end
end
xnext == @tail and return nil
node_key(xnext)[0, start_key.size] != start_key and return nil
xnext
end

#
Expand All @@ -76,10 +86,10 @@ def skip_nodes(node, offset, reverse)
# TODO: add reverse support
tail = @tail
while offset > 0 && node != tail
node = node_next(x, 0)
node = node_next(node, 0)
offset -= 1
end
offset == 0 ? node : nil
offset <= 0 ? node : nil
end

#
Expand All @@ -90,10 +100,10 @@ def collect_values(x, end_prefix, limit, reverse, with_keys)
meth = method(with_keys ? :node_pair : :node_value)
tail = @tail
limit ||= Float::MAX
end_prefix ||= ""
pfx_size = end_prefix.size
while x != tail
if end_prefix
node_compare(x, end_prefix) > 0 and return values
end
node_key(x)[0, pfx_size] > end_prefix and return values
values.size >= limit and return values
values << meth.call(x).freeze
x = node_next(x, 0)
Expand Down
81 changes: 76 additions & 5 deletions spec/lib/strokedb/data_structures/simple_skiplist_spec.rb
Expand Up @@ -193,10 +193,10 @@
end
end

describe "SimpleSkiplist#search" do
describe "SimpleSkiplist#search [#{lang}]" do
before(:each) do
@list = SimpleSkiplist.new
@keys = %w[ a aa ab b ba bb x xx xy xyz ]
@keys = %w[ a aa ab b ba bb pfx1 pfx2 pfx3 x xx xy xyz ]
@values = @keys.map{|v| v + " value"}
@key_values = @keys.map{|v| [v, v + " value"]}
@key_values.each do |k, v|
Expand All @@ -205,13 +205,84 @@
end

it "should find all items" do
@list.search(nil, nil, nil, nil, nil, nil).should == @values
search_should_yield(@key_values)
end

it "should find all items with keys" do
@list.search(nil, nil, nil, nil, nil, true).should == @key_values
it "should find all items starting with a prefix" do
search_should_yield(@key_values, :start_key => "a")
search_should_yield(@key_values[1..-1], :start_key => "aa")
search_should_yield(@key_values[2..-1], :start_key => "ab")
search_should_yield(@key_values[3..-1], :start_key => "b")
search_should_yield(@key_values[-1..-1], :start_key => "xyz")
search_should_yield(@key_values[6..-1], :start_key => "pfx")
end

it "should not find any items if prefix not matched" do
search_should_yield([], :start_key => "middle")
search_should_yield([], :start_key => "__prefix")
search_should_yield([], :start_key => "zuffix")
search_should_yield([], :start_key => "pfx0")
end

it "should not find all items before the given end_key (inclusive)" do
search_should_yield(@key_values, :end_key => "xyz")
search_should_yield(@key_values, :end_key => "zuffix")
search_should_yield(@key_values[0..-3], :end_key => "xx")
search_should_yield(@key_values[0..2], :end_key => "a")
search_should_yield(@key_values[0..1], :end_key => "aa")
search_should_yield(@key_values[0..5], :end_key => "b")
end

it "should find items in a range" do
search_should_yield(@key_values[1..5], :start_key => "aa", :end_key => "bb")
search_should_yield(@key_values[0..2], :start_key => "a", :end_key => "a")
search_should_yield(@key_values[3..5], :start_key => "b", :end_key => "b")
search_should_yield(@key_values[6..8], :start_key => "pfx", :end_key => "pfx")
end

it "should not find items in an invalid range" do
search_should_yield([], :start_key => "b", :end_key => "a")
search_should_yield([], :start_key => "z", :end_key => "a")
search_should_yield([], :start_key => "_", :end_key => "b")
search_should_yield([], :start_key => "ab1", :end_key => "b")
end


def search_should_yield(results, os = {})
# TODO: added reverse cases
list = @list

os = os.merge(:with_keys => true)
r = search_with_options(list, os)
r.should == results
search_should_use_offsets_and_limits(list, os, r)

os = os.merge(:with_keys => nil)
r = search_with_options(list, os)
r.should == results.map{|k,v| v}
search_should_use_offsets_and_limits(list, os, r)
end

def search_should_use_offsets_and_limits(list, os, r1)

offsets = [-1000, -1, 0, 1, 2, 3, 4, 1000]
limits = [-1000, -1, 0, 1, 2, 3, 4, 1000]

offsets.each do |off|
limits.each do |lim|
r2 = search_with_options(list, os.merge(:offset => off, :limit => lim))
r2.should == (r1[off < 0 ? 0 : off, lim < 0 ? 0 : lim] || [])
end
end
end

def search_with_options(list, os = {})
#puts "OPTIONS: #{os.inspect}"
r = list.search(os[:start_key], os[:end_key], os[:limit], os[:offset], os[:reverse], os[:with_keys])
#puts "R: #{r.inspect}"
r
end

end
end

Expand Down

0 comments on commit c115223

Please sign in to comment.