# This is a more complete example of a linked list implementation, but exists # mainly to illustrate how to implement indexing and iteration. # okay, this is the dirtiest hack I have ever done in julia. # Julia is really finicky when it comes to redefining structs. # Usually it isn't a problem if you haven't changed anything, # but because of the recursive inclusion in the Node_ struct it was really # unhappy, so this basically runs this if block the first time # and not any subsequent times. if !isdefined(@__MODULE__, :__first__) __first__ = true # The node struct that actually holds the values mutable struct Node_{T} data::T prev::Union{Node_{T}, Nothing} next::Union{Node_{T}, Nothing} end # An alias for a type union such that we can set a node to the value nothing # when we want to indicate the end of the list. const Node{T} = Union{Node_{T}, Nothing} # Since we made the alias for Node, we make this wrapper for the constructor # of the actual Node_ object. There are probably better ways to do this # but this works fine. function make_node(data::T, prev::Node{T}, next::Node{T}) where T Node_{T}(data, prev, next) end # The wrapper struct for the nodes. Most of the methods will be implemented # on this type. # It is also specified as a subtype of the abstract type AbstractArray. # This signalises that it will mostly act like an array # and makes it possible to pass a LinkedList into a function that is specified # to take in objects of type AbstractArray. This gives a bunch of functions # that needs to be implemented, and some that can be overridden. All of these # are listed under the "Intefaces" section in the julia docs. # Here we will implement the required methods and the methods for iteration. mutable struct LinkedList{T} <: AbstractArray{T, 1} head::Node{T} tail::Node{T} items::Int # Keeping track of how many items for quick length checking # Implementing a default constructor LinkedList{T}() where T = new{T}(nothing, nothing, 0) end end # End of the dirty hack # Constructor to create a list from a collection of elements function LinkedList(elems::AbstractArray{T}) where T l = LinkedList{T}() for e in elems push!(l, e) end l end # Not required for AbstractArray, but makes sence to implement function Base.push!(l::LinkedList{T}, v::T) where T n_node = make_node(v, nothing, nothing) if l.head === nothing l.tail = l.head = n_node else l.tail.next = n_node n_node.prev = l.tail l.tail = n_node # n_node.next = l.head # Uncomment this one if you feel brave ;) end l.items += 1 l end # One of the reqired functions for AbstractArray. size returns a tuple # of all the dimensions, but this is a 1d collection so it only contains # one element. Base.size(l::LinkedList) = (l.items, ) # Implements the iteration function. This makes it possible to for example # write something like "for element in list" to iterate through the list. # The function returns a 2-tuple where the first element is the next iteration # item and the second element is the next state. The state is some variable # that determines the next element and state. When the function returns # nothing, the loop is done. function Base.iterate(l::LinkedList, state=l.head) if state === nothing nothing # If the state is nothing, we return nothing else (state.data, state.next) # Else the next item/state is returned end end # Implements a simple show function for nicer printing. The default show # function is ridiculous when you have circular/recursive containment in # your structs, so it is necessary to implement a nicer version. function Base.show(io::IO, l::LinkedList{T}) where T show(io, l.items) write(io, "-element LinkedList{") show(io, T) write(io, "}:\n ") first = true for elem in l if !first write(io, " → ") end first = false show(io, elem) end end # A utility function for both the getindex and setindex! functions function findnode(l::LinkedList, i::Int) if !(1 <= i <= length(l)) throw(BoundsError(l, i)) # Throwing an error if out of bounds end curnode = l.head for j in 1 : i - 1 curnode = curnode.next end curnode end # Another required function for AbstractArray. # This function makes it possible to read the element at a certain index # with the syntax l[i] Base.getindex(l::LinkedList, i::Int) = findnode(l, i).data # The complimentary function for seting elements at a given index # with corresponding syntax l[i] = v Base.setindex!(l::LinkedList{T}, v::T, i::Int) where T = findnode(l, i).data = v # Practial to override the copy function. In this case it it really # simple to set up because of the way we made the constructor. Base.copy(l::LinkedList) = LinkedList(l) # Implementing the append function as well. Not necessary but practical to have. function Base.append!(l::LinkedList{T}, l2::AbstractArray{T}) where T for elem in l2 push!(l, elem) end end # Implementing insert! for a linked list is quite useful function Base.insert!(l::LinkedList{T}, i::Int, v::T) where T node = findnode(l, i) n_node = make_node(v, node, node.next) if node.next !== nothing node.next.prev = n_node end node.next = n_node l end # Same goes for deleteat! function Base.deleteat!(l::LinkedList, i::Int) node = findnode(l, i) if node.prev !== nothing node.prev.next = node.next end if node.next !== nothing node.next.prev = node.prev end l end # It can be useful to reverse a list function Base.reverse!(l::LinkedList) cur_node = l.head for i = 1 : length(l) cur_node.prev, cur_node.next = cur_node.next, cur_node.prev cur_node = cur_node.prev end l.head, l.tail = l.tail, l.head l end # It can also be useful to make a reversed copy of a list Base.reverse(l::LinkedList) = reverse!(copy(l)) # Some test code: # Creating a linked list l = LinkedList(1 : 5) @show l # showing the third element @show l[3] # setting the third element l[3] = 7 @show l # inserting the value 8 between 2nd and 3rd node insert!(l, 2, 8) @show l # deleting the second node deleteat!(l, 2) @show l # since all required functions for AbstractArray have been implemented # standard functions, such as sort!, should now work. However if performance # is important you should probably implement your own version of functions # so that it is done more efficiently for the collection in question. # for example the sort function here will do a lot of index operations # and since finding a node in a linked list is O(n) complexity and # sort! is O(n log n) list operations, the whole runtime becomes O(n^2 log n). sort!(l) @show l