447 lines
8.9 KiB
Crystal
447 lines
8.9 KiB
Crystal
# 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
|
|
|
|
property first : Node(V) | Nil
|
|
property last : Node(V) | Nil
|
|
property 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
|
|
|
|
# Removes an entry.
|
|
#
|
|
# ```
|
|
# list = DoubleLinkedList(Int32).new
|
|
# list << 1 << 2 << 3 << 4 # -> [ 1, 2, 3, 4 ]
|
|
# list.delete Node(Int32).new 2 # -> [ 1, 3, 4 ]
|
|
# ```
|
|
def delete(n : Node(V)) : Node(V)
|
|
if first = @first
|
|
@first = n.next if n.value == first.value
|
|
end
|
|
if last = @last
|
|
@last = n.previous if n.value == last.value
|
|
end
|
|
if prev_node = n.previous
|
|
prev_node.next = n.next
|
|
end
|
|
if next_node = n.next
|
|
next_node.previous = n.previous
|
|
end
|
|
@size -= 1
|
|
n
|
|
end
|
|
|
|
# Removes an entry at an index.
|
|
#
|
|
# ```
|
|
# list = DoubleLinkedList(Int32).new
|
|
# list << 1 << 2 << 3 << 4 # -> [ 1, 2, 3, 4 ]
|
|
# list.delete_at 2 # -> [ 1, 2, 4 ]
|
|
# ```
|
|
def delete_at(index : Int32) : Node(V)
|
|
if index == 0
|
|
shift
|
|
elsif index == @size - 1
|
|
pop
|
|
else
|
|
v = self[index]
|
|
prev_node = v.previous.not_nil!
|
|
next_node = v.next.not_nil!
|
|
|
|
prev_node.next = next_node
|
|
next_node.previous = prev_node
|
|
@size -= 1
|
|
v
|
|
end
|
|
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
|
|
new_node.previous = current.previous
|
|
|
|
current.previous.not_nil!.next = new_node
|
|
current.previous = new_node
|
|
|
|
@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
|
|
else
|
|
raise BrokenList.new "pop a list of 1 element but 'first' is nil"
|
|
end
|
|
end
|
|
|
|
if last = @last
|
|
@last = last.previous
|
|
@last.not_nil!.next = nil
|
|
last
|
|
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.not_nil! if index == 0
|
|
return @last.not_nil! if index == @size - 1
|
|
|
|
i = 0
|
|
each_node do |node|
|
|
return node if i == index
|
|
i += 1
|
|
end
|
|
|
|
raise BrokenList.new "couldn't find the node, smth must be brkn"
|
|
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 << 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)
|
|
io << "[ "
|
|
remaining_values = @size
|
|
each do |value|
|
|
io << value
|
|
remaining_values -= 1
|
|
io << ", " unless remaining_values == 0
|
|
end
|
|
io << " ]"
|
|
end
|
|
end
|