From 1d2035c637f748b7bdfb5946c35eac9abed80f52 Mon Sep 17 00:00:00 2001 From: Gibheer Date: Thu, 16 Jun 2011 13:18:43 +0200 Subject: [PATCH] added a binary tree storage --- lib/polecat/storage/binary_storage.rb | 136 +++++++++++++++++++ spec/storage/binary_storage/add_spec.rb | 22 +++ spec/storage/binary_storage/delete_spec.rb | 28 ++++ spec/storage/binary_storage/find_spec.rb | 24 ++++ spec/storage/binary_storage/interval_spec.rb | 28 ++++ spec/storage/binary_storage/new_spec.rb | 10 ++ spec/storage/binary_storage/select_spec.rb | 25 ++++ 7 files changed, 273 insertions(+) create mode 100644 lib/polecat/storage/binary_storage.rb create mode 100644 spec/storage/binary_storage/add_spec.rb create mode 100644 spec/storage/binary_storage/delete_spec.rb create mode 100644 spec/storage/binary_storage/find_spec.rb create mode 100644 spec/storage/binary_storage/interval_spec.rb create mode 100644 spec/storage/binary_storage/new_spec.rb create mode 100644 spec/storage/binary_storage/select_spec.rb diff --git a/lib/polecat/storage/binary_storage.rb b/lib/polecat/storage/binary_storage.rb new file mode 100644 index 0000000..aaa8446 --- /dev/null +++ b/lib/polecat/storage/binary_storage.rb @@ -0,0 +1,136 @@ +module Polecat + module Storage + # This class is a storage engine based on a simple hash. + class BinaryStorage < Storage + def initialize + @root = nil + end + + # add a new key value pair + def add key, value = nil + if key.class == Node + new_node = key + else + check_key key + new_node = Node.new key, value + end + + if @root.nil? + @root = new_node + else + cur_node = @root + while cur_node.lower != new_node && cur_node.upper != new_node + if cur_node.key > new_node.key + if cur_node.lower.nil? + cur_node.lower = new_node + else + cur_node = cur_node.lower + end + elsif cur_node.key <= new_node.key + if cur_node.upper.nil? + cur_node.upper = new_node + else + cur_node = cur_node.upper + end + end + end # end of while + end + end + + def delete key + check_key key + + cur_node = @root + parent = nil + while !cur_node.nil? + if cur_node.key == key + if parent.nil? + @root = nil + else + if parent.lower == cur_node + parent.lower = nil + else + parent.upper = nil + end + end + + add cur_node.lower unless cur_node.lower.nil? + add cur_node.upper unless cur_node.upper.nil? + return cur_node.value + else + if cur_node.key < key + parent = cur_node + cur_node = cur_node.upper + else + parent = cur_node + cur_node = cur_node.lower + end + end + end + nil + end + + def interval lower, upper, node = @root + check_key lower + check_key upper + + select do |key| + lower <= key && key <= upper + end + end + + def find key + check_key key + + cur_node = @root + until cur_node.nil? + if cur_node.key == key + return cur_node.value + elsif + if cur_node.key > key + cur_node = cur_node.lower + else + cur_node = cur_node.upper + end + end + end + nil + end + + def select node = @root, &block + if node.nil? + [] + else + rs = [] + rs << node.value if block.call(node.key) + rs += (select node.lower, &block) + rs += (select node.upper, &block) + end + end + + def count node = @root + if node.nil? + 0 + else + 1 + (count node.lower) + (count node.upper) + end + end + + def check_key key + unless key.respond_to?(:<=>) && key.respond_to?(:<=) + raise ArgumentError, 'key does not support #<=>' + end + end + private :check_key + + class Node + attr_accessor :value, :lower, :upper + attr_reader :key + def initialize key, value + @key = key + @value = value + end + end + end + end +end diff --git a/spec/storage/binary_storage/add_spec.rb b/spec/storage/binary_storage/add_spec.rb new file mode 100644 index 0000000..c7a9803 --- /dev/null +++ b/spec/storage/binary_storage/add_spec.rb @@ -0,0 +1,22 @@ +require File.expand_path(File.dirname(__FILE__) + '/../../spec_helper') +require 'polecat/storage/binary_storage' + +describe "BinaryStorage#add" do + let (:s) { Polecat::Storage::BinaryStorage.new } + + it "raises the element count" do + s.add('foo', 'bar') + s.count.should == 1 + end + + it "raises the element count with every add" do + s.add('foo', 1) + s.add('bar', 2) + s.add('baz', 3) + s.count.should == 3 + end + + it "raises an error when the attribute does not support #<=>" do + lambda { s.add(nil, 'baz') }.should raise_error(ArgumentError) + end +end diff --git a/spec/storage/binary_storage/delete_spec.rb b/spec/storage/binary_storage/delete_spec.rb new file mode 100644 index 0000000..a9e6252 --- /dev/null +++ b/spec/storage/binary_storage/delete_spec.rb @@ -0,0 +1,28 @@ +require File.expand_path(File.dirname(__FILE__) + '/../../spec_helper') +require 'polecat/storage/binary_storage' + +describe "BinaryStorage#delete" do + let (:s) { Polecat::Storage::BinaryStorage.new } + before do + s.add 'foo', 'bar' + s.add 'bar', 'baz' + s.add 'baz', 'foo' + end + + it "decrements the element count" do + s.delete('foo') + s.count.should == 2 + end + + it "returns the value of the deleted key" do + s.delete('foo').should == 'bar' + end + + it "returns nil when the element was not found" do + s.delete('foobar').should be(nil) + end + + it "raises an error when the argument does not support #<=>" do + lambda { s.delete(:foo) }.should raise_error(ArgumentError) + end +end diff --git a/spec/storage/binary_storage/find_spec.rb b/spec/storage/binary_storage/find_spec.rb new file mode 100644 index 0000000..3702a73 --- /dev/null +++ b/spec/storage/binary_storage/find_spec.rb @@ -0,0 +1,24 @@ +require File.expand_path(File.dirname(__FILE__) + '/../../spec_helper') +require 'polecat/storage/binary_storage' + +describe "BinaryStorage#find" do + let (:s) { Polecat::Storage::BinaryStorage.new } + before do + s.add 'foo', 'bar' + s.add 'bar', 'baz' + s.add 'baz', 'foobar' + s.add 'zum', 'zum' + end + + it "returns nil when the element was not found" do + s.find('baba').should be(nil) + end + + it "returns the value of the key" do + s.find('foo').should == 'bar' + end + + it "raises an error when the type does not know #<=>" do + lambda { s.find(:foo) }.should raise_error(ArgumentError) + end +end diff --git a/spec/storage/binary_storage/interval_spec.rb b/spec/storage/binary_storage/interval_spec.rb new file mode 100644 index 0000000..f0ad418 --- /dev/null +++ b/spec/storage/binary_storage/interval_spec.rb @@ -0,0 +1,28 @@ +require File.expand_path(File.dirname(__FILE__) + '/../../spec_helper') +require 'polecat/storage/binary_storage' + +describe "BinaryStorage#interval" do + let (:s) { Polecat::Storage::BinaryStorage.new } + before do + s.add 'foo', 'bar' + s.add 'bar', 'baz' + s.add 'baz', 'foobar' + s.add 'zum', 'zum' + end + + it "returns an empty array when no element was found" do + s.interval('a', 'b').should == [] + end + + it "returns an array of found elements" do + s.interval('ba', 'bb').sort.should == ['baz', 'foobar'] + end + + it "includes the search values" do + s.interval('bar', 'baz').sort.should == ['baz', 'foobar'] + end + + it "raises an error when one of the arguments does not implement #<=>" do + lambda { s.interval(1, :foo) }.should raise_error(ArgumentError) + end +end diff --git a/spec/storage/binary_storage/new_spec.rb b/spec/storage/binary_storage/new_spec.rb new file mode 100644 index 0000000..fed9bb6 --- /dev/null +++ b/spec/storage/binary_storage/new_spec.rb @@ -0,0 +1,10 @@ +require File.expand_path(File.dirname(__FILE__) + '/../../spec_helper') +require 'polecat/storage/binary_storage' + +describe "BinaryStorage#new" do + it "creates a new storage object" do + s = Polecat::Storage::BinaryStorage.new + s.kind_of?(Polecat::Storage::Storage).should == true + s.class.should be(Polecat::Storage::BinaryStorage) + end +end diff --git a/spec/storage/binary_storage/select_spec.rb b/spec/storage/binary_storage/select_spec.rb new file mode 100644 index 0000000..c58552d --- /dev/null +++ b/spec/storage/binary_storage/select_spec.rb @@ -0,0 +1,25 @@ +require File.expand_path(File.dirname(__FILE__) + '/../../spec_helper') +require 'polecat/storage/binary_storage' + +describe "BinaryStorage#select" do + let (:s) { Polecat::Storage::BinaryStorage.new } + before do + s.add 'key', 'value' + s.add 'foo', 'bar' + s.add 'bar', 'baz' + s.add 'baz', 'foobar' + end + + it "returns an empty array, when no element was selected" do + (s.select {|element| false }).should == [] + end + + it "takes a block as an argument" do + (s.select {|element| element.match /ba*/ }).sort.should == ['baz', 'foobar'] + end + + it "does not handle errors" do + lambda { s.select {|element| raise ArgumentError }}.should( + raise_error(ArgumentError)) + end +end