Double Linked Lists!

This commit is contained in:
Philippe PITTOLI 2024-05-25 21:51:10 +02:00
parent ceaa9a6af8
commit e05af4bbf1
3 changed files with 403 additions and 0 deletions

View File

@ -2,4 +2,5 @@ require "file_utils"
require "json" require "json"
require "./fifo.cr" # FIFO class to implement a cache policy. 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.). require "./dodb/*" # Databases and indexes (basic indexes, partitions, tags, etc.).

View File

@ -43,3 +43,9 @@ class FIFO(V)
@data.select! { |x| v != x } @data.select! { |x| v != x }
end end
end end
# TODO: this is a draft.
class EfficientFIFO(V)
def initialize(@max_entries : UInt32)
end
end

396
src/list.cr Normal file
View 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