Linked List Implementation in Swift
Swift 5.0, Xcode 10.3
I have written an implementation for a doubly linked list in Swift. As well, I decided to make the node class private and thus hidden to the user so they don't ever need to interact with it. I have written all of the algorithms that I needed to give it MutableCollection
, BidirectionalCollection
, and RandomAccessCollection
conformance.
What I Could Use Help With
- I am pretty sure that my
LinkedList
type properly satisfies all of the time complexity requirements of certain algorithms and operations that come hand in hand with linked lists but am not sure. - I was also wondering if there are any ways I can make my Linked List implementation more efficient.
- In addition, I am not sure if there are and linked list specific methods or computed properties that I have not included that I should implement.
- I have some testing but if you do find any errors/mistakes in my code that would help a lot.
- Any other input is also appreciated!
Here is my code:
public struct LinkedList<Element> {
private var headNode: LinkedListNode<Element>?
private var tailNode: LinkedListNode<Element>?
public private(set) var count: Int = 0
public init() { }
}
//MARK: - LinkedList Node
extension LinkedList {
fileprivate typealias Node<T> = LinkedListNode<T>
fileprivate class LinkedListNode<T> {
public var value: T
public var next: LinkedListNode<T>?
public weak var previous: LinkedListNode<T>?
public init(value: T) {
self.value = value
}
}
}
//MARK: - Initializers
public extension LinkedList {
private init(_ nodeChain: NodeChain<Element>?) {
guard let chain = nodeChain else {
return
}
headNode = chain.head
tailNode = chain.tail
count = chain.count
}
init<S>(_ sequence: S) where S: Sequence, S.Element == Element {
if let linkedList = sequence as? LinkedList<Element> {
self = linkedList
} else {
self = LinkedList(NodeChain(of: sequence))
}
}
}
//MARK: NodeChain
extension LinkedList {
private struct NodeChain<Element> {
let head: Node<Element>!
let tail: Node<Element>!
private(set) var count: Int
// Creates a chain of nodes from a sequence. Returns `nil` if the sequence is empty.
init?<S>(of sequence: S) where S: Sequence, S.Element == Element {
var iterator = sequence.makeIterator()
guard let firstValue = iterator.next() else {
return nil
}
var currentNode = Node(value: firstValue)
head = currentNode
var nodeCount = 1
while true {
if let nextElement = iterator.next() {
let nextNode = Node(value: nextElement)
currentNode.next = nextNode
nextNode.previous = currentNode
currentNode = nextNode
nodeCount += 1
} else {
tail = currentNode
count = nodeCount
return
}
}
return nil
}
}
}
//MARK: - Copy Nodes
extension LinkedList {
private mutating func copyNodes() {
guard let nodeChain = NodeChain(of: self) else {
return
}
headNode = nodeChain.head
tailNode = nodeChain.tail
}
}
//MARK: - Computed Properties
public extension LinkedList {
var head: Element? {
return headNode?.value
}
var tail: Element? {
return tailNode?.value
}
var first: Element? {
return head
}
var last: Element? {
return tail
}
}
//MARK: - Sequence Conformance
extension LinkedList: Sequence {
public typealias Iterator = LinkedListIterator<Element>
public __consuming func makeIterator() -> LinkedList<Element>.Iterator {
return LinkedListIterator(node: headNode)
}
public struct LinkedListIterator<T>: IteratorProtocol {
public typealias Element = T
private var currentNode: LinkedListNode<T>?
fileprivate init(node: LinkedListNode<T>?) {
currentNode = node
}
public mutating func next() -> T? {
guard let node = currentNode else {
return nil
}
currentNode = node.next
return node.value
}
}
}
//MARK: - Collection Conformance
extension LinkedList: Collection {
public typealias Index = LinkedListIndex<Element>
public var startIndex: LinkedList<Element>.Index {
return Index(node: headNode, offset: 0)
}
public var endIndex: LinkedList<Element>.Index {
return Index(node: nil, offset: count)
}
public func index(after i: LinkedList<Element>.Index) -> LinkedList<Element>.LinkedListIndex<Element> {
precondition(i.offset != endIndex.offset, "LinkedList index is out of bounds")
return Index(node: i.node?.next, offset: i.offset + 1)
}
public struct LinkedListIndex<T>: Comparable {
fileprivate weak var node: LinkedList.Node<T>?
fileprivate var offset: Int
fileprivate init(node: LinkedList.Node<T>?, offset: Int) {
self.node = node
self.offset = offset
}
public static func ==<T>(lhs: LinkedListIndex<T>, rhs: LinkedListIndex<T>) -> Bool {
return lhs.offset == rhs.offset
}
public static func < <T>(lhs: LinkedListIndex<T>, rhs: LinkedListIndex<T>) -> Bool {
return lhs.offset < rhs.offset
}
}
}
//MARK: - MutableCollection Conformance
extension LinkedList: MutableCollection {
public subscript(position: LinkedList<Element>.Index) -> Element {
get {
precondition(position.offset != endIndex.offset, "Index out of range")
guard let node = position.node else {
fatalError("LinkedList index is invalid")
}
return node.value
}
set {
precondition(position.offset != endIndex.offset, "Index out of range")
// Copy-on-write semantics for nodes
if !isKnownUniquelyReferenced(&headNode) {
copyNodes()
}
position.node?.value = newValue
}
}
}
//MARK: LinkedList Specific Operations
public extension LinkedList {
mutating func prepend(_ newElement: Element) {
replaceSubrange(startIndex..<startIndex, with: [newElement])
}
mutating func prepend<S>(contentsOf newElements: S) where S: Sequence, S.Element == Element {
replaceSubrange(startIndex..<startIndex, with: newElements)
}
mutating func popFirst() -> Element? {
guard !isEmpty else {
return nil
}
return removeFirst()
}
mutating func popLast() -> Element? {
guard !isEmpty else {
return nil
}
return removeLast()
}
}
//MARK: - BidirectionalCollection Conformance
extension LinkedList: BidirectionalCollection {
public func index(before i: LinkedList<Element>.LinkedListIndex<Element>) -> LinkedList<Element>.LinkedListIndex<Element> {
precondition(i.offset != startIndex.offset, "LinkedList index is out of bounds")
if i.offset == count {
return Index(node: tailNode, offset: i.offset - 1)
}
return Index(node: i.node?.previous, offset: i.offset - 1)
}
}
//MARK: - RangeReplaceableCollection Conformance
extension LinkedList: RangeReplaceableCollection {
public mutating func replaceSubrange<S, R>(_ subrange: R, with newElements: __owned S) where S : Sequence, R : RangeExpression, LinkedList<Element>.Element == S.Element, LinkedList<Element>.Index == R.Bound {
let range = subrange.relative(to: indices)
precondition(range.lowerBound >= startIndex && range.upperBound <= endIndex, "Subrange bounds are out of range")
// If range covers all elements and the new elements are a LinkedList then set references to it
if range.lowerBound == startIndex, range.upperBound == endIndex, let linkedList = newElements as? LinkedList {
self = linkedList
return
}
var newElementsCount = 0
// Update count after replacement
defer {
count = count - (range.upperBound.offset - range.lowerBound.offset) + newElementsCount
}
// There are no new elements, so range indicates deletion
guard let nodeChain = NodeChain(of: newElements) else {
// If there is nothing in the removal range
// This also covers the case that the linked list is empty because this is the only possible range
guard range.lowerBound != range.upperBound else {
return
}
// Deletion range spans all elements
guard !(range.lowerBound == startIndex && range.upperBound == endIndex) else {
headNode = nil
tailNode = nil
return
}
// Copy-on-write semantics for nodes before mutation
if !isKnownUniquelyReferenced(&headNode) {
copyNodes()
}
// Move head up if deletion starts at start index
if range.lowerBound == startIndex {
// Can force unwrap node since the upperBound is not the end index
headNode = range.upperBound.node!
headNode!.previous = nil
// Move tail back if deletion ends at end index
} else if range.upperBound == endIndex {
// Can force unwrap since lowerBound index must have an associated element
tailNode = range.lowerBound.node!.previous
tailNode!.next = nil
// Deletion range is in the middle of the linked list
} else {
// Can force unwrap all bound nodes since they both must have elements
range.upperBound.node!.previous = range.lowerBound.node!.previous
range.lowerBound.node!.previous!.next = range.upperBound.node!
}
return
}
// Obtain the count of the new elements from the node chain composed from them
newElementsCount = nodeChain.count
// Replace entire content of list with new elements
guard !(range.lowerBound == startIndex && range.upperBound == endIndex) else {
headNode = nodeChain.head
tailNode = nodeChain.tail
return
}
// Copy-on-write semantics for nodes before mutation
if !isKnownUniquelyReferenced(&headNode) {
copyNodes()
}
// Prepending new elements
guard range.upperBound != startIndex else {
headNode?.previous = nodeChain.tail
nodeChain.tail.next = headNode
headNode = nodeChain.head
return
}
// Appending new elements
guard range.lowerBound != endIndex else {
tailNode?.next = nodeChain.head
nodeChain.head.previous = tailNode
tailNode = nodeChain.tail
return
}
if range.lowerBound == startIndex {
headNode = nodeChain.head
}
if range.upperBound == endIndex {
tailNode = nodeChain.tail
}
range.lowerBound.node!.previous!.next = nodeChain.head
range.upperBound.node!.previous = nodeChain.tail
}
}
//MARK: - ExpressibleByArrayLiteral Conformance
extension LinkedList: ExpressibleByArrayLiteral {
public typealias ArrayLiteralElement = Element
public init(arrayLiteral elements: LinkedList<Element>.ArrayLiteralElement...) {
self.init(elements)
}
}
//MARK: - CustomStringConvertible Conformance
extension LinkedList: CustomStringConvertible {
public var description: String {
return "[" + lazy.map { "\($0)" }.joined(separator: ", ") + "]"
}
}
Note: My up-to-date LinkedList
implementation can be found here: https://github.com/Wildchild9/LinkedList-Swift.