Skip to content

2. List

Prerequisite

  • Familiar with classes in Lua
  • Setup code editor with Lua

Overview

A List is a very common data structure that stores values in nodes, where each node contains a value and a pointer to the next node. Unlike arrays (not tables), linked lists can dynamically grow or shrink and do not require contiguous memory. Implement a linked list as a class in Lua to store arbitrary values of any type.

Recall that classes can be created like:

local Box = {}
Box.__index = Box

function Box.new(x) -- static constructor
  local self = setmetatable({}, Box)
  self.x = x
  return self
end

function Box:Get() -- instance method (note ':')
  return self.x
end

return Box

Tasks

Task 1: List class

Create a skeleton List class with the constructor and necessary fields. Add a toString method so that the string representation as shown in the examples below could be returned.

For instance:

local list = List.new(1)  -- create a single List node
print(list:toString())   -- should print "[1]"

Task 2: Append() and Pop()

Add Append() and Pop() methods to manipulate the list. Append(x) should append x in a List to the back. Pop() should pop the last value in the list and return it. Also modify the toString() method to correctly display the values in the list.

For instance:

local list = List.new(1)
list:Append(2)
print(list:toString())     -- should print "[1 2]"
local popped = list:Pop()
print(popped)               -- should print "2"
print(list:toString())     -- should print "[1]"

Task 3: Length()

Add a Length() method to get the length of the instance list.

For instance:

local list = List.new(1)
list:Append(2)
list:Append(3)
print(list:Length())      -- should print "3"

Task 4: Get(i) and Remove(i)

Add a Get(i) method to retrieve the List at index i (1-based). The method should traverse and return the List node if it exists, or nil if the index is out of bounds. Add a Remove(i) method to remove only the List at index i and return it.

Hint: consider using two traversal variables

For instance:

local list = List.new(1)
list:Append(2)
list:Append(3)
print(list:Get(2):toString())       -- should print "[2 3]"
print(list:Remove(2):toString())    -- should print "[2]"
print(list:toString())              -- should print "[1 3]"

Solution

local List = {}
List.__index = List

-- Constructor: Initialize a node with data and a nil tail
function List.new(data)
    local self = setmetatable({}, List)
    self.data = data
    self.tail = nil
    return self
end

-- Add an element with value `x` to the end of the list
function List:append(x)
    local curr = self
    while curr.tail do
        curr = curr.tail
    end
    curr.tail = List.new(x)
end

-- Pop the last List and return its value
function List:pop()
    if not self.tail then
        return self.data
    end

    local curr = self
    while curr.tail and curr.tail.tail do
        curr = curr.tail
    end
    local value = curr.tail.data
    curr.tail = nil
    return value
end

-- Get the List at index `i`
function List:get(i: number)
    local curr = self
    local j = 1

    while j < i and curr.tail do
        curr = curr.tail
        j = j + 1
    end

    return j == i and curr or nil
end

-- Remove the List at index `i`
function List:remove(i: number)
    local prev = nil
    local curr = self
    local j = 1

    while j < i and curr.tail do
        prev = curr
        curr = curr.tail
        j = j + 1
    end

    if j == i then
        -- for non-head, we stitch the prev to the next tail
        -- for head removal edge case (undefined by qn),
        -- i choose to cut off the remaining list and let GC clean it up
        if prev then
            prev.tail = curr.tail
        end
        curr.tail = nil
        return curr
    end
    return nil
end

-- Get the length of the list
function List:length()
    local curr = self.tail
    local i = 1
    while curr do
        i = i + 1
        curr = curr.tail
    end
    return i
end

return List
// This implementation is closer to the CS1101S implementation
public class List {
  private int data;
  private List tail;

  // Constructor
  public List(int data) {
    this.data = data;
    this.tail = null;
  }

  // Add an element with value x to the end
  public void append(int x) {
    List curr = this;
    while (curr.tail != null) {
      curr = curr.tail;
    }
    curr.tail = new List(x);
  }

  // Pop the last node and return the value
  public int pop() {
    if (this.tail == null) {
      return this.data;
    }

    List curr = this;
    while (curr.tail != null && curr.tail.tail != null) {
      curr = curr.tail; 
    }
    int res = curr.tail.data;
    curr.tail = null;
    return res;
  }

  // Get the List at index i
  public List get(int i) {
    List curr = this;
    int j = 0;

    while (j < i && curr.tail != null) {
        curr = curr.tail;
        j++;
    }

    if (j == i) {
        return curr;
    }
    return null;
  }

  // Remove the List at index i
  public List remove(int i) {
    List prev = null;
    List curr = this;
    int j = 0;

    while (j < i && curr.tail != null) {
      prev = curr;
      curr = curr.tail;
      j++;
    }

    if (j == i) {
      // for non-head, we stitch the prev to the next tail
      // for head removal edge case (undefined by qn),
      // i choose to cut off the remaining list and let GC clean it up
      if (prev != null) {
        prev.tail = curr.tail;
      }
      curr.tail = null;
      return curr;
    }
    return null;
  }

  // Get the length of the list
  public int length() {
    int i = 1;
    // you can use for loops like this too, but rest of the code
    // uses while loops for clarity
    for (List curr = this.tail; curr != null; curr = curr.tail, i++);
    return i;
  }

  // Helper function
  public void print() {
    List curr = this;
    while (curr != null) {
      System.out.print(curr.data + " ");
      curr = curr.tail;
    }
    System.out.print("\n");
  }
}

Extra

Consider what else we can do with lists? Is there a point of implementing lists when we already have tables? How could you use tables to write a much terser linked list? How could we use lists in game development?