Double Linked Lists!
This commit is contained in:
parent
ceaa9a6af8
commit
e05af4bbf1
@ -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.).
|
||||||
|
@ -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
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