← Back to the index page

Circular linked list

A cicrular linked list is very similar to linked list with two significant differences:

  1. All nodes are linked in a continuous circle.
  2. There is no pointer to nil at the end; last pointer points to the first node.

To understand what is the linear linked list, please proceed to following article.

Figure 01: Circular linked list example

Circular linked list class

Class implementation with OOP principles and annotations.

CircularLinkedList.lua

---TODO check size
---@class Node
---@field value any
---@field next Node | nil
local Node = {}
Node.__index = Node

---@param value any
---@param next? Node | nil
---@return Node
function Node:new(value, next)
    return setmetatable({
        value = value,
        next = next,
    }, self)
end

---@class CircularLinkedList
---@field private _size number
---@field private _tail Node
local CircularLinkedList = {}
CircularLinkedList.__index = CircularLinkedList

---@return CircularLinkedList
function CircularLinkedList:new()
    local t = {
        _size = 0,
        _tail = nil,
    }
    return setmetatable(t, self)
end

---@private
---@param value any
---@return Node
function CircularLinkedList:_addToEmpty(value)
    local node = Node:new(value)
    self._tail = node
    node.next = self._tail
    self._tail = node.next
    return self._tail
end

---@return Node | nil
function CircularLinkedList:tail()
    return self._tail
end

---@return Node | nil
function CircularLinkedList:head()
    return self._tail and self._tail.next
end

---@return number
function CircularLinkedList:size()
    return self._size
end

---@return boolean
function CircularLinkedList:isEmpty()
    return self._size == 0
end

---Inserts new node at the end of the list.
---@param value any
---@return Node
function CircularLinkedList:append(value)
    local node = Node:new(value)
    if self:isEmpty() then
        node = self:_addToEmpty(value)
        self._tail = node.next -- TODO need?
    else
        node.next = self._tail.next
        self._tail.next = node
        self._tail = node
    end
    self._size = self._size + 1
    return node
end

---Inserts new node at the front of the list.
---@param value any
---@return Node
function CircularLinkedList:prepend(value)
    local node = Node:new(value)
    if self:isEmpty() then
        node = self:_addToEmpty(value)
        self._tail = node -- TODO need?
    else
        node.next = self._tail.next
        self._tail.next = node
    end
    self._size = self._size + 1
    return node
end

---Inserts a new node with value after given node. If after node is nil,
---then one node inserted and pointing to itself.
---@param value any
---@param after Node | nil
---@return Node | nil
function CircularLinkedList:insertAfter(value, after)
    local node = Node:new(value)
    if after == nil then
        node = self:_addToEmpty(value)
    else
        node.next = after.next
        after.next = node
    end
    self._size = self._size + 1
    return node
end

---Inserts a new node with value before given node. If before node is nil,
---then one node inserted and pointing to itself.
---@param value any
---@param before Node | nil
---@return Node
function CircularLinkedList:insertBefore(value, before)
    local node = Node:new(value)
    if before == nil then
        node = self:_addToEmpty(value)
    else
        node = Node:new(before.value, before.next)
        before.value = value
        before.next = node
    end
    self._size = self._size + 1
    return node
end

---Removes and returns a node after given node. If given node not found nil is
---returned.
---@return any
function CircularLinkedList:removeNode(node)
    if node and not self:isEmpty() then
        node.value = node.next.value
        node.next = node.next.next
    end
    return node
end

---Removes and returns a node after found node with gven value. If given
---node not found nil is
---returned.
---@return any
function CircularLinkedList:removeByValue(value)
    local node = self:findByValue(value)
    if node then
        self:removeNode(node)
    end
    return node
end

---Checks if the list contins a give value.
---@param value any
---@return boolean
function CircularLinkedList:contains(value)
    return self:findByValue(value) ~= nil
end

---Finds the first occurrence of the value. Returns nil if not found.
---@param value any
---@return Node | nil
function CircularLinkedList:findByValue(value)
    local node = self._tail
    if not node then
        return nil
    end
    repeat
        if node.value == value then
            return node
        end
        node = node.next
    until node == self._tail
    return nil
end

---Traversal of a linked list.
---@param fn fun(node: Node)
function CircularLinkedList:traverse(fn)
    local node = self._tail and self._tail.next
    if not start then
        return
    end
    repeat
        fn(node)
        node = node.next
    until node == self._tail.next
end

---@param sep? string
---@return string
function CircularLinkedList:toString(sep)
    sep = sep or " <=> "
    local t = {}
    self:traverse(function(node)
        t[#t + 1] = tostring(node.value)
    end)
    return table.concat(t, sep)
end

return CircularLinkedList

Usage of CircularLinkedList class

local cll = CircularLinkedList:new()
cll:append("A")
cll:append("B")
cll:append("C")
cll:prepend("D")
cll:prepend("E")

local foundD = cll:findByValue("D")
cll:insertAfter("F", foundD)
local foundB = cll:findByValue("B")
cll:insertBefore("G", foundB)

local foundC = cll:findByValue("C")
cll:removeNode(foundC)
cll:removeByValue("E")
print(cll:toString()) --> "F <=> A <=> G <=> B <=> D""
print("Contains B", cll:contains("B")) --> true
print("Contains X", cll:contains("XX")) --> false

References

Feedback

For feedback, please check the contacts section. Before writing, please specify where you came from and who you are. Sometimes spammers go insane. Thank you in advance for your understanding.

← Back to the index page