Swift Algorithm Club: Swift Linked List Data Structure

Learn how to implement a linked list in Swift 3 in this step-by-step tutorial with illustrations and a downloadable example.

Linked List Feature

The Swift Algorithm Club is an open source project on implementing data structures and algorithms in Swift.

Every month, Kelvin Lau and I feature a cool data structure or algorithm from the club in a tutorial on this site. If you want to learn more about algorithms and data structures, follow along with us!

In this tutorial, you’ll learn how to implement a linked list in Swift 3. The linked list implementation was first implemented by Matthijs Hollemans, the founder of the Swift Algorithm Club.

Note: New to the Swift Algorithm Club? Check out our getting started post first.

Getting Started

A linked list is a sequence of data items, where each item is referred to as a node.

There are two main types of linked lists:

Singly linked lists, are linked lists where each node only has a reference to the next node.

Singly linked list

Doubly linked lists, are linked lists where each node has a reference to the previous and next node.

Doubly linked list

You need to keep track of where the list begins and ends. That’s usually done with pointers called head and tail.

Head and tail

Linked List Implementation in Swift 3

In this section, you’ll implement a linked list in Swift 3.

Remember that a linked list is made up of nodes. So to start, let’s create a basic node class. Create a new Swift playground and add the following empty class:

public class Node {
 
}

Value

A node needs a value associated with it. Add the following between the curly braces:

var value: String
 
init(value: String) {
  self.value = value
}

You’ve declared a property named value of type String. In your own apps, this could be any datatype you want to store.

You also declare an initializer, which is required for initializing all non-optional stored properties for your class.

Next

In addition to a value, each node needs a pointer to the next node in the list.

To do this, add the following property to the class:

var next: Node?

You have declared a property named next of type Node. Note that you’ve made next an optional. This is because the last node in the linked list does not point to another node.

Previous

You are implementing a doubly-linked list so we also need a pointer to the previous node in the list.

To do this, add one last property to the class:

weak var previous: Node?

Note: To avoid ownership cycles, we declare the previous pointer to be weak. If you have a node A that is followed by node B in the list, then A points to B but also B points to A. In certain circumstances, this ownership cycle can cause nodes to be kept alive even after you deleted them. We don’t want that, so we make one of the pointers weak to break the cycle.

To learn more about ownership cycles, check out our ARC and Memory Management in Swift tutorial.

Linked List

Now that you have created the Node you also need to keep track of where the list begins and ends.

To do this, add this new LinkedList class to the bottom of the playground:

public class LinkedList {
  fileprivate var head: Node?
  private var tail: Node?

  public var isEmpty: Bool {
    return head == nil
  }

  public var first: Node? {
    return head
  }

  public var last: Node? {
    return tail
  }
}

This class will keep track of where the list begins and ends. It will also provide a number of other helper functions.

Append

To handle appending a new node on your list, you’ll declare a append(value:) method in your LinkedList class. Add the following new method to LinkedList:

public func append(value: String) {
  // 1
  let newNode = Node(value: value)
  // 2
  if let tailNode = tail {
    newNode.previous = tailNode
    tailNode.next = newNode
  } 
  // 3
  else {
    head = newNode
  }
  // 4
  tail = newNode
}

Let’s review this section by section:

  • Create a new Node to contain the value. Remember, the purpose of the Node class is so that each item in the linked list can point to the previous and next node.
  • If tailNode is not nil, that means there is something in the linked list already. If that’s the case, configure the new item to point to the tail of the list as it’s previous item. Similarly, configure the new last item on the list to point to the new node as it’s next item.
  • Finally, set the tail of the list to be the new item in either case.

Printing Your Linked List

Let’s try out your new linked list. Outside the implementation of LinkedList, write the following into your playground:

let dogBreeds = LinkedList()
dogBreeds.append(value: "Labrador")
dogBreeds.append(value: "Bulldog")
dogBreeds.append(value: "Beagle")
dogBreeds.append(value: "Husky")

After defining the list, we will try print the list to the console:

print(dogBreeds)

You can bring up the console by pressing the following keys in combination: Command-Shift-Y. You should see the following printed out to the console:

LinkedList

That isn’t very helpful. To display a more readable output string, you can make LinkedList adopt the CustomStringConvertable protocol. To do this, add the following just below the implementation of your LinkedList class:

// 1
extension LinkedList: CustomStringConvertible {
  // 2
  public var description: String {
    // 3
    var text = "["
    var node = head
    // 4
    while node != nil {
      text += "\(node!.value)"
      node = node!.next
      if node != nil { text += ", " }
    }
    // 5
    return text + "]"
  }
}

Here’s how the code works:

  1. You’ve declared an extension to your LinkedList class, and you’ve adopted the CustomStringConvertible protocol. This protocol expects you to implement a computed property with the name description, with the String type.
  2. You’ve declared the description property. This is a computed property, a read only property that returns a String.
  3. You’ve declared a text variable. This will hold the entire string. For now, it contains an opening brace to represent the start of the list.
  4. You then loop through the list appending the value of each item to the text string.
  5. You add a closing brace to the end of the text variable.

Now, when you call print on your LinkedList classes, you’ll get a nice representation of your list like this:

"[Labrador, Bulldog, Beagle, Husky]"

Accessing Nodes

Even though a linked list works most efficiently when you move through nodes in order via previous and next, sometimes it is handy to access an item by index.

To do this, you will declare a nodeAt(index:) method in your LinkedList class. This will return the Node at the specified index.

Update the implementation of LinkedList to include the following:

public func nodeAt(index: Int) -> Node? {
  // 1
  if index >= 0 {
    var node = head
    var i = index
    // 2
    while node != nil {
      if i == 0 { return node }
      i -= 1
      node = node!.next
    }
  }
  // 3
  return nil
}

Here’s what you’ve done:

  1. Added a check that the specified index is not negative. This prevents an infinite loop if the index is a negative value.
  2. Loop through the nodes until you reach the node at the specified index and return the node.
  3. If the index less than 0 or greater than the number of items in the list, then return nil.

Removing All Nodes

Removing all nodes is simple. We just assign nil to the head and tail:

public func removeAll() {
  head = nil
  tail = nil
}

Removing Individual Nodes

To remove an individual node, you will have to deal with three cases:

  1. Removing the first node. The requires the head and previous pointers to be updated:
    Remove first node
  2. Removing a node in the middle of the list. This requires the previous and next pointers to be updated:
    Remove middle node
  3. Removing the last node in the list. This requires the next and tail pointers to be updated:
    Remove last node

Update the implementation of LinkedList to include:

public func remove(node: Node) -> String {
  let prev = node.previous
  let next = node.next

  if let prev = prev {
    prev.next = next // 1
  } else { 
    head = next // 2
  }
  next?.previous = prev // 3

  if next == nil { 
    tail = prev // 4
  }

  // 5
  node.previous = nil 
  node.next = nil

  // 6
  return node.value
}

Here’s what you’ve done:

  1. Update the next pointer if you are not removing the first node in the list.
  2. Update the head pointer if you are removing the first node in the list.
  3. Update the previous pointer to the previous pointer of the deleted node.
  4. Update the tail if you are removing the last node in the list.
  5. Assign nil to the removed nodes previous and next pointers.
  6. Return the value for the removed node.

Generics

So far you’ve implemented a general-purpose linked list that stores String values. You’ve provided functionality to append, remove and access nodes in your LinkedList class. In this section we will use generics to abstract away the type requirement from our linked list.

Update the implementation of your Node class to the following:

// 1
public class Node<T> {
  // 2
  var value: T
  var next: Node<T>?
  weak var previous: Node<T>?

  // 3
  init(value: T) {
    self.value = value
  }
}

Here’s what you’ve done:

  1. You’ve changed the declaration of the Node class to take a generic type T.
  2. Your goal is to allow the Node class to take in values of any type, so you’ll constrain your value property to be type T rather than a String.
  3. You’ve also updated your initializer to take any type.

Generics: Challenge

Try updating the implementation of LinkedList to use generics.

The solution is provided in the spoiler section down below, but try it yourself first!

[spoiler title=”Solution”]

// 1. Change the declaration of the Node class to take a generic type T
public class LinkedList<T> {
  // 2. Change the head and tail variables to be constrained to type T
  fileprivate var head: Node<T>?
  private var tail: Node<T>?

  public var isEmpty: Bool {
    return head == nil
  }
  
  // 3. Change the return type to be a node constrained to type T
  public var first: Node<T>? {
    return head
  }

  // 4. Change the return type to be a node constrained to type T
  public var last: Node<T>? {
    return tail
  }

  // 5. Update the append function to take in a value of type T
  public func append(value: T) {
    let newNode = Node(value: value)
    if let tailNode = tail {
      newNode.previous = tailNode
      tailNode.next = newNode
    } else {
      head = newNode
    }
    tail = newNode
  }

  // 6. Update the nodeAt function to return a node constrained to type T
  public func nodeAt(index: Int) -> Node<T>? {
    if index >= 0 {
      var node = head
      var i = index
      while node != nil {
        if i == 0 { return node }
        i -= 1
        node = node!.next
      }
    }
    return nil
  }

  public func removeAll() {
    head = nil
    tail = nil
  }

  // 7. Update the parameter of the remove function to take a node of type T. Update the return value to type T.
  public func remove(node: Node<T>) -> T {
    let prev = node.previous
    let next = node.next

    if let prev = prev {
      prev.next = next
    } else {
      head = next
    }
    next?.previous = prev

    if next == nil {
      tail = prev
    }

    node.previous = nil
    node.next = nil
    
    return node.value
  }
}

[/spoiler]

Your code should compile now, so let’s test this out! At the bottom of your playground file, add the following code to verify that your generic linked list is working:

let dogBreeds = LinkedList<String>()
dogBreeds.append(value: "Labrador")
dogBreeds.append(value: "Bulldog")
dogBreeds.append(value: "Beagle")
dogBreeds.append(value: "Husky")

let numbers = LinkedList<Int>()
numbers.append(value: 5)
numbers.append(value: 10)
numbers.append(value: 15)

Where To Go From Here?

I hope you enjoyed this tutorial on making a linked list!

Here is a Swift playground with the above code. You can also find alternative implementations and further discussion in the linked list section of the Swift Algorithm Club repository.

This was just one of the many algorithm clubs focused on the Swift Algorithm Club repository. If you’re interested in more, check out the repo.

If you have any questions on linked lists in Swift, please join the forum discussion below!

Note: The Swift Algorithm Club is always looking for more contributors. If you’ve got an interesting data structure, algorithm, or even an interview question to share, don’t hesitate to contribute! To learn more about the contribution process, check out our Join the Swift Algorithm Club article.

If you enjoyed what you learned in this tutorial, why not check out our Data Structures and Algorithms in Swift book, available on our store?

In Data Structures and Algorithms in Swift, you’ll learn how to implement the most popular and useful data structures and when and why you should use one particular datastructure or algorithm over another. This set of basic data structures and algorithms will serve as an excellent foundation for building more complex and special-purpose constructs.

As well, the high-level expressiveness of Swift makes it an ideal choice for learning these core concepts without sacrificing performance.

  • You’ll start with the fundamental structures of linked lists, queues and stacks, and see how to implement them in a highly Swift-like way.
  • Move on to working with various types of trees, including general purpose trees, binary trees, AVL trees, binary search trees and tries.
  • Go beyond bubble and insertion sort with better-performing algorithms, including mergesort, radix sort, heap sort and quicksort.
  • Learn how to construct directed, non-directed and weighted graphs to represent many real-world models, and traverse graphs and trees efficiently with breadth-first, depth-first, Dijkstra’s and Prim’s algorithms to solve problems such as finding the shortest path or lowest cost in a network.
  • And much, much more!

By the end of this book, you’ll have hands-on experience solving common issues with data structures and algorithms — and you’ll be well on your way to developing your own efficient and useful implementations.

Contributors

Comments