# 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