What is a Circular Linked List?

In singly and doubly linked list, we can determine the last node by looking at its next pointer; a node that points to the nil is the last node. In Circular linked list however, there isn’t a node that points to nil unless the list is empty.

Circular linked list is a list that has no end to it. When you reach the last node, it wraps around back to the head node allowing you to traverse the list from the beginning again.

    head            last                 
[node|value] --> [node|value] --> head --> last --> ....

You can use either singly or doubly linked list to implement a circular linked list. If you need to support backward navigation, you can implement it with doubly linked list as a base. If need forward navigation only, then use singly linked list.

Below implementation used a singly linked list.

class CircularLinkedList 
    attr_accessor :head, :current, :length

    def initialize 
        @head = nil
        @length = 0
        @current = nil
    end

    def insert data 
        return insert_next(nil, data) if @length == 0
        return insert_next(@head, data) if @length == 1

        # find the last node
        @current = @head
        i = 1
        while i < @length
            move_next
            i += 1
        end

        # insert right after the last node
        return insert_next(@current, data)
    end

    def remove data
        return nil unless @length > 0
        last_node = get_last

        if @head.data == data 
            if @length == 1
                @head = nil
            else
                @head = @head.next
                last_node.next = @head
            end
        else
            @current = @head
            while @current.next != @head
                if @current.next.data == data
                    @current.next == @current.next.next
                    break
                end
                move_next
            end
        end

        @length -= 1
    end

    def get_last
        return nil unless @length > 0
        return @head if @length == 1

        i = 1
        @current = @head
        while i < @length
            move_next
            i += 1
        end

        @current
    end

    def clear
        while @length > 0
            remove @head.data
        end
    end

    def move_next
        @current = @current&.next
    end

    def full_scan
        return nil unless block_given?

        @current = @head
        loop do 
            yield @current
            break if move_next == @head
        end
    end

    def find_first &predicate
        return nil unless block_given?
        
        @current = @head
        loop do
            return @current if predicate.call(@current)
            move_next
            return nil if @current == @head
        end
    end

    def print
        if @length == 0
            puts "empty"
        else
            self.full_scan { |item| puts item.data }
        end
    end

    private
    class Node
        attr_accessor :data, :next

        def initialize(item)
            @data = item
            @next = nil
        end
    end

    def insert_next current_node, data
        new_node = Node.new data

        if current_node == nil
            @head = new_node
            @head.next = @head
        else
            new_node.next = current_node.next
            current_node.next = new_node
        end

        @length += 1
    end
end
Backlinks: