1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205
|
#!/usr/bin/env ruby -w
# encoding: UTF-8
#
# = TernarySearchTree.rb -- The TaskJuggler III Project Management Software
#
# Copyright (c) 2006, 2007, 2008, 2009, 2010, 2011, 2012, 2013, 2014
# by Chris Schlaeger <cs@taskjuggler.org>
#
# This program is free software; you can redistribute it and/or modify
# it under the terms of version 2 of the GNU General Public License as
# published by the Free Software Foundation.
#
require 'taskjuggler/UTF8String'
class TaskJuggler
# Classical ternary search tree implementation. It can store any list
# objects who's elements are comparable. These are usually String or Array
# objects. Common elements (by value and index) are only stored once which
# makes it fairly efficient for large lists that have similar start
# sequences. It also provides a fast find method.
class TernarySearchTree
# Create a new TernarySearchTree object. The optional _arg_ can be an
# element to store in the new tree or a list of elements to store.
def initialize(arg = nil)
clear
if arg.nil?
return
elsif arg.is_a?(Array)
sortForBalancedTree(arg).each { |elem| insert(elem) }
else
insert(arg) if arg
end
end
# Stores _str_ in the tree. _index_ is for internal use only.
def insert(str, index = 0)
if str.nil? || str.empty?
raise ArgumentError, "Cannot insert nil or empty lists"
end
if index > (maxIdx = str.length - 1) || index < 0
raise ArgumentError, "index out of range [0..#{maxIdx}]"
end
@value = str[index] unless @value
if str[index] < @value
@smaller = TernarySearchTree.new unless @smaller
@smaller.insert(str, index)
elsif str[index] > @value
@larger = TernarySearchTree.new unless @larger
@larger.insert(str, index)
else
if index == maxIdx
@last = true
else
@equal = TernarySearchTree.new unless @equal
@equal.insert(str, index + 1)
end
end
end
alias << insert
# Insert the elements of _list_ into the tree.
def insertList(list)
list.each { |val| insert(val) }
end
# if _str_ is stored in the tree it returns _str_. If _partialMatch_ is
# true, it returns all items that start with _str_. _index_ is for
# internal use only. If nothing is found it returns either nil or an empty
# list.
def find(str, partialMatch = false, index = 0)
return nil if str.nil? || index > (maxIdx = str.length - 1)
if str[index] < @value
return @smaller.find(str, partialMatch, index) if @smaller
elsif str[index] > @value
return @larger.find(str, partialMatch, index) if @larger
else
if index == maxIdx
# We've reached the end of the search pattern.
if partialMatch
# The strange looking ('' << val) is for Ruby 1.8 compatibility.
return collect { |v| str[0..-2] + ('' << v) }
else
return str if @last
end
end
return @equal.find(str, partialMatch, index + 1) if @equal
end
nil
end
alias [] find
# Returns the number of elements in the tree.
def length
result = 0
result += @smaller.length if @smaller
result += 1 if @last
result += @equal.length if @equal
result += @larger.length if @larger
result
end
# Return the maximum depth of the tree.
def maxDepth(depth = 0)
depth += 1
depths = []
depths << @smaller.maxDepth(depth) if @smaller
depths << @equal.maxDepth(depth) if @equal
depths << @larger.maxDepth(depth) if @larger
depths << depth if @last
depths.max
end
# Invokes _block_ for each element and returns the results as an Array.
def collect(str = nil, &block)
result = []
result += @smaller.collect(str, &block) if @smaller
# The strange looking ('' << val) is for Ruby 1.8 compatibility.
newStr = str.nil? ? ('' << @value) : str + ('' << @value)
result << yield(newStr) if @last
result += @equal.collect(newStr, &block) if @equal
result += @larger.collect(str, &block) if @larger
result
end
# Return an Array with all the elements stored in the tree.
def to_a
collect{ |x| x}
end
# Balance the tree for more effective data retrieval.
def balance!
list = sortForBalancedTree(to_a)
clear
list.each { |x| insert(x) }
end
# Return a balanced version of the tree.
def balanced
TernarySearchTree.new(to_a)
end
def inspect(prefix = ' ', indent = 0)
puts "#{' ' * indent}#{prefix} #{@value} #{@last ? '!' : ''}"
@smaller.inspect('<', indent + 2) if @smaller
@equal.inspect('=', indent + 2) if @equal
@larger.inspect('>', indent + 2) if @larger
end
private
# Reset the node to an empty tree.
def clear
@smaller = @equal = @larger = @value = nil
@last = false
end
# Split the list into the first element and the remaining ones.
def split(str)
# The list may not be nil or empty. This would be a bug.
raise ArgumentError if str.nil? || str.empty?
# The second element of the result may be nil.
[ str[0], str[1..-1] ]
end
# Reorder the list elements so that we get a fully balanced tree when
# inserting the elements from front to back.
def sortForBalancedTree(list)
lists = [ list.sort ]
result = []
while !lists.empty?
newLists = []
lists.each do |l|
# Split the list in half and add the center element to the result
# list.
pivot = l.length / 2
result << l[pivot]
# Add the two remaining sub lists to the newLists Array.
newLists << l[0..pivot - 1] if pivot > 0
newLists << l[pivot + 1..-1] if pivot < l.length - 1
end
lists = newLists
end
result
end
end
end
|