Double Linked Lists!
This commit is contained in:
parent
ceaa9a6af8
commit
e05af4bbf1
@ -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.).
|
||||
|
@ -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
|
||||
|
396
src/list.cr
Normal file
396
src/list.cr
Normal file
@ -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
|
Loading…
Reference in New Issue
Block a user