diff --git a/src/dodb.cr b/src/dodb.cr index 72a91e7..a656200 100644 --- a/src/dodb.cr +++ b/src/dodb.cr @@ -2,4 +2,5 @@ require "file_utils" require "json" require "./fifo.cr" # FIFO class to implement a cache policy. +require "./list.cr" # Double Linked List. require "./dodb/*" # Databases and indexes (basic indexes, partitions, tags, etc.). diff --git a/src/fifo.cr b/src/fifo.cr index 4f3aaf5..5315700 100644 --- a/src/fifo.cr +++ b/src/fifo.cr @@ -43,3 +43,9 @@ class FIFO(V) @data.select! { |x| v != x } end end + +# TODO: this is a draft. +class EfficientFIFO(V) + def initialize(@max_entries : UInt32) + end +end diff --git a/src/list.cr b/src/list.cr new file mode 100644 index 0000000..b5801b9 --- /dev/null +++ b/src/list.cr @@ -0,0 +1,396 @@ +# A linked list is a data structure with each value stored in a small structure called "node". +# Each node points to the next; making the whole structure *Enumerable*. +# The following code represents a *double linked list*, meaning each node points to the next **and the previous**. +# Pointers to other values are required since nodes are not stored in contiguous locations in memory. +# +# ``` +# list = DoubleLinkedList(Int32).new +# list.push(42) +# pp! list.pop # -> 42 +# ``` +class DoubleLinkedList(V) + # Each value of a linked is put in a *node*. + # Nodes are *linked* together thanks to pointers to both the next node + # and the previous one since this is a *double* linked list. + # + # ``` + # node1 = Node.new(42) + # node2 = Node.new(10) + # + # pp! node1.value # -> 42 + # pp! node2.value # -> 10 + # + # node1.next = node2 + # node2.previous = node1 + # ``` + class Node(V) + property next : Node(V)? = nil + property previous : Node(V)? = nil + property value : V + + # Creates a node. + def initialize(@value : V) + end + + def to_s(io) + io << @value + end + end + + class OutOfBounds < ::Exception + end + class BrokenList < ::Exception + end + + @first : Node(V) | Nil + @last : Node(V) | Nil + @size : UInt32 = 0 + + include Enumerable(V | Nil) + + # Creates an empty double linked list. + def initialize + @first = nil + @last = nil + end + + # Creates a linked list with potentially multiple values for the nodes. + def initialize(values : Enumerable(V)) + @first = nil + @last = nil + values.each do |value| + push(value) + end + end + + # Pushes a value at the end of the list. + # + # ``` + # list = DoubleLinkedList(Int32).new + # list.push(1) + # list.push(2) + # list.pop() # => 2 + # list.pop() # => 1 + # ``` + def push(value : V) : Node(V) + # First entry is nil = pushing the first entry. + if @first.nil? + new_node = Node(V).new(value) + @first = new_node + @last = @first + else + new_node = Node(V).new(value) + @last.not_nil!.next = new_node + new_node.previous = @last + @last = new_node + end + @size += 1 + new_node + end + + # Adds a *value* to the linked list at a specified index. + # + # ``` + # list = DoubleLinkedList(Int32).new + # list << 1 # -> [1] + # list << 2 # -> [1] [2] + # list.insert_at(3, 1) # [1] [3] [2] + # ``` + # WARNING: this operation is slow, at worst O(n). + def insert_at(value : V, index : Int32) : Node(V) + # Error case. + raise OutOfBounds.new if index > size + + # Special case: insert the first value. + return unshift(value) if index == 0 + + new_node = Node(V).new(value) + + # Special case: insert the last value. + if size == index + new_node.previous = @last + @last.not_nil!.next = new_node + @last = new_node + @size += 1 + return new_node + end + + # General case. + current = @first.not_nil!.next.not_nil! + i = 1 + + while i < index + if next_entry = current.next + current = next_entry + else + raise BrokenList.new "unlinked list at index #{i}" + end + i += 1 + end + + new_node.next = current.next + current.next = new_node + new_node.previous = current + @size += 1 + new_node + end + + # Adds a value to the end of the linked list. + # Can be chained. + # + # ``` + # list = DoubleLinkedList(Int32).new + # list << 1 + # list << 2 << 3 + # list.pop() # => 3 + # list.pop() # => 2 + # list.pop() # => 1 + # ``` + def <<(value : V) : DoubleLinkedList(V) + push(value) + self + end + + # Adds a list of values to the end of the list. + # + # ``` + # list = DoubleLinkedList(Int32).new + # list.push(1, 2) + # list.pop() # => 2 + # list.pop() # => 1 + # ``` + def push(*values) + values.each do |value| + push(value) + end + end + + # Adds a value to the beginning of the list. + # + # ``` + # list = DoubleLinkedList(Int32).new + # list << 1 + # list.unshift 2 + # list.pop() # => 1 + # list.pop() # => 2 + # ``` + def unshift(value : V) : Node(V) + if first = @first + new_node = Node(V).new(value) + new_node.next = first + first.previous = new_node + @first = new_node + @size += 1 + new_node + else + push(value) + end + end + + # Returns the first node from the list and removes it. + # + # ``` + # list = DoubleLinkedList(Int32).new + # list << 1 + # list << 2 + # list.shift() # -> 1 + # ``` + def shift : Node(V) + if first = @first + @first = first.next + @size -= 1 + first + else + raise OutOfBounds.new "shifting while no value in the list" + end + end + + # Returns the last node of the linked list **without removing it**. + # + # ``` + # list = DoubleLinkedList(Int32).new(1) + # list << 2 + # list.peek() # => 2 + # ``` + def peek : Node(V) + if last = @last + last + else + raise OutOfBounds.new "peek at an empty list" + end + end + + # Returns the last value from the list and removes its node. + # + # ``` + # list = DoubleLinkedList(Int32).new + # list << 1 + # list << 2 + # list.pop() # => 2 + # list.peek() # => 1 + # ``` + def pop : Node(V) + if @size == 0 + raise OutOfBounds.new "pop an empty list" + end + + @size -= 1 + + if @size == 0 # size was just modified above + if current = @first + @first = nil + @last = nil + return current.value + else + raise BrokenList.new "pop a list of 1 element but 'first' is nil" + end + end + + if last = @last + @last = last.previous + last.value + else + raise BrokenList.new "'last' element is nil despite size > 0" + end + end + + # Iterates over all the values in the linked list. + # + # ``` + # values = [1, 2, 3] + # list = DoubleLinkedList(Int32).new(values) + # list.each do |elem| + # puts elem + # end + def each + each_node do |node| + yield node.value + end + self + end + + # Returns a **new** `DoubleLinkedList` with all of the elements from the first list + # followed by all of the elements in the second *list*. + # + # ``` + # list1 = DoubleLinkedList(Int32).new(1, 2) + # list2 = DoubleLinkedList(String).new(3, 4) + # + # list3 = list1 + list2 + # list3.peek # => 4 + # list3.shift # => 1 + # ``` + def +(list : Enumerable(C)) forall C + DoubleLinkedList(V | C).new.tap do |new_list| + each do |value| + new_list.push(value) + end + list.each do |value| + new_list.push(value) + end + end + end + + # Provides a node at a given index in the list. + # + # TODO: either start with the first entry or the last depending on the index. + def [](index : Int32) : Node(V) + raise OutOfBounds.new if index > @size + + return @first if index == 0 + return @last if index == @size - 1 + + i = 0 + each_node do |node| + return node if i == index + i += 1 + end + end + + # Concats two lists. + # + # ``` + # list1 = DoubleLinkedList(Int32).new(1, 2) + # list2 = DoubleLinkedList(Int32).new(3, 4) + # + # list1.concat list2 + # # list1: 1 2 3 4 + # ``` + def concat(list : DoubleLinkedList(V)) : DoubleLinkedList(V) + if @size == 0 + @first = list.first + @last = list.last + elsif last = @last + last.next = list.first + if list_first = list.first + list_first.previous = last + end + end + @size += list.size + self + end + + # Returns true if and only if there are no elements in the list. + # + # ``` + # list = DoubleLinkedList(Int32).new + # list.empty? # => true + # list.push(1) + # list.empty? # => false + # ``` + def empty? + @size == 0 + end + + # Creates a copy of the `DoubleLinkedList` with the order reversed. + # + # ``` + # list = DoubleLinkedList(Int32).new(1, 2, 3) + # reversed_list = list.reverse + # list.pop() # => 1 + # list.pop() # => 2 + # list.pop() # => 3 + # ``` + def reverse + DoubleLinkedList(V).new.tap do |new_list| + each do |value| + new_list.unshift value + end + end + end + + # Iterates over the nodes. + # + # ``` + # values = [1, 2, 3] + # list = DoubleLinkedList(Int32).new(values) + # list.each_node do |elem| + # puts elem.value + # end + # ``` + private def each_node + return if @first.nil? + + current = @first + yield current.not_nil! + + while current = current.not_nil!.next + yield current + end + end + + # Fancy print of the list's content. + def to_s(io) + io << "[ " + + remaining_values = @size + + each do |value| + io << value + remaining_values -= 1 + io << ", " unless remaining_values == 0 + end + + io << " ]" + end +end