My Custom Linked List Class

I implemented a custom LinkedList class below with the help of people on StackOverflow and of ChatGPT to explain exactly how it works. I left comments throughout to explain exactly what I was doing and how it was done

Selection Sort Implementation

Selection Sort ended up working essentially the same as when implemented with an Array. Step-by-step:

  • I defined the current node as the head after determining that there was more than one item in the LinkedList
  • For each item in the LinkedList:
    • I set the minimum to the current node
    • I jump using the Node’s .next to each linked Node, checking if it’s less than the current minimum
      • If less than current minimum, the minimum is replaced
    • If applicable, the recorded minimum is swapped with the “current” node
    • The “current” node is adjusted to its .next, as all previous have already been sorted
  • This is repeated until the LinkedList has been fully sorted
// defining a LinkedList class that can hold elements (T)
// ChatGPT: "T can be any data type that extends Comparable"
public class LinkedList<T extends Comparable<T>> {
    // Node class represents individual list elements
    private static class Node<T> {
        T data; // the element's actual data
        Node<T> next; // referencing (linking to *winky face*) to the next node in the list

        // constructor to create a node with given data
        Node(T data) {
            this.data = data;
            this.next = null; // no next node exists initially
        }
    }

    private Node<T> head; // references the first node in the list
    private int size; // represents the size/length of the list

    // constructor to initialize the LinkedList
    public LinkedList() {
        head = null; // it's empty at first, so there's NOTHING
        size = 0; // the size starts out as 0, as there is NOTHING
    }

    // like .add() for ArrayList, this adds an element to the end of the list
    public void add(T element) {
        if (head == null) {
            head = new Node<>(element); // if the list is empty, this creates the first node
            // when I gave my concept, chatGPT said to add this condition here, credit <-
        } else {
            Node<T> current = head;
            while (current.next != null) {
                current = current.next; // using a while loop to approach the last node
            }
            current.next = new Node<>(element); // adding the new node at the end
        }
        size++; // increasing size of the list
    }

    // HERE'S MY CUSTOM SELECTION SORT
    public void selectionSort() {
        if (head == null || head.next == null) {
            return; // if the list is empty or has only one element, it's already sorted
            // again, this is an inclusion courtesy of ChatGPT and its incredible forethought
        }
        Node<T> current = head;
        while (current != null) {
            Node<T> min = current;
            Node<T> temp = current.next;
            while (temp != null) {
                if (temp.data.compareTo(min.data) < 0) {
                    min = temp; // finding the minimum element
                }
                temp = temp.next;
            }
            T tmpData = current.data; // swapping the current unsorted place with the minimum
            current.data = min.data;
            min.data = tmpData;
            current = current.next; // moving to the next element
        }
    }

    // this method overrides toString
    // it's used for printing the list
    // this was my reference: https://stackoverflow.com/questions/42676579/tostring-method-for-a-linkedlist
    // i shouldn't have needed a reference, but I wanted to make sure there wasn't anything special to do
    @Override
    public String toString() {
        String string = "["; // starting the list format
        Node<T> current = head; // start with head
        while (current != null) {
            string += current.data; // appending the current data to the string
            if (current.next != null) {
                string += ", "; // good punctuation = professionalism
            }
            current = current.next; // moving on
        }
        string += "]"; // capping off the list format
        return string;
    }

    // this is to return a JSON representation of the list
    // i basically did the same thign as I did for the toString() one
    public String toJSON() {
        String json = "{"; // starting format
        Node<T> current = head; // start with head
        while (current != null) {
            json += "\"" + current.data + "\""; // adding current data in quotations for JSON
            if (current.next != null) {
                json += ", "; // good punctuation again, this time very important
            }
            current = current.next; // moving on
        }
        json += "}"; // capping off
        return json;
    }

    // may be implemented? still researching
    public void changeSortKey() {
        // yet to come...
    }

    // main method for testing
    public static void main(String[] args) {
        LinkedList<Integer> list = new LinkedList<>();
        list.add(5);
        list.add(3);
        list.add(8);
        list.add(1);
        System.out.println("Original list: " + list);
        list.selectionSort();
        System.out.println("Sorted list: " + list);
        System.out.println("JSON representation: " + list.toJSON());
    }
}

LinkedList.main(null);
Original list: [5, 3, 8, 1]
Sorted list: [1, 3, 5, 8]
JSON representation: {"1", "3", "5", "8"}

Custom Comparable

I decided to do it with Pokemon, returning to my roots og this year. I figured they’d have a lot of data that could be compared, but I decided to specifically focus on comparing Pokedex numbers. In the franchise, I feel like that would be the most sensible way for a researcher to compare them.

// defining the Collectable interface
interface Collectable<T> extends Comparable<T> {
    // implementing compareTo method to define natural ordering
    @Override
    int compareTo(T other);

    String toString(); // needed for LL printing
}

// defining the Pokemon class implementing Collectable interface
class Pokemon implements Collectable<Pokemon> {
    private String name;
    private String[] types;
    private int dexNumber;

    // basic constructor
    public Pokemon(String name, String[] types, int dexNumber) {
        this.name = name;
        this.types = types;
        this.dexNumber = dexNumber;
    }

    // getters methods
    public String getName() {return name;}
    public String[] getTypes() {return types;}
    public int getDexNumber() {return dexNumber;}

    // implementing the interface compareTo method
    // i definde natural ordering based on pokedex number
    @Override
    public int compareTo(Pokemon other) {
        return Integer.compare(this.dexNumber, other.dexNumber);
    }

    // overriding toString method for better representation of the Pokemon
    @Override
    public String toString() {
        return name + " (#" + dexNumber + ")";
    }
}

Using Custom Comparable With Custom LinkedList

See the commented code cell below:

public class Main {
    public static void main(String[] args) {
        // creating the linkedlist of pokemon
        LinkedList<Pokemon> pokemonList = new LinkedList<>();

        // adding some well-known Pokemon to the list
        pokemonList.add(new Pokemon("Pikachu", new String[]{"Electric"}, 25));
        pokemonList.add(new Pokemon("Walking Wake", new String[]{"Water", "Dragon"}, 1009));
        pokemonList.add(new Pokemon("Charmander", new String[]{"Fire"}, 4));
        pokemonList.add(new Pokemon("Minior", new String[]{"Rock", "Flying"}, 774));
        pokemonList.add(new Pokemon("Bulbasaur", new String[]{"Grass", "Poison"}, 1));
        pokemonList.add(new Pokemon("Ambipom", new String[]{"Normal"}, 424));
        pokemonList.add(new Pokemon("Squirtle", new String[]{"Water"}, 7));

        // I shortened the pokemon toString() for this reason
        System.out.println("Original list:\n\t" + pokemonList);

        // sorting the list based on Pokedex numbers, using SELECTION SORT!!!
        pokemonList.selectionSort();

        // displaying the sorted list (SELECTION SORT!!!)
        System.out.println("Sorted list by Pokedex number:\n\t" + pokemonList);
    }
}

Main.main(null);
Original list:
	[Pikachu (#25), Walking Wake (#1009), Charmander (#4), Minior (#774), Bulbasaur (#1), Ambipom (#424), Squirtle (#7)]
Sorted list by Pokedex number:
	[Bulbasaur (#1), Charmander (#4), Squirtle (#7), Pikachu (#25), Ambipom (#424), Minior (#774), Walking Wake (#1009)]

Working with Sort Keys

I wanted to implement SortKeys in the LinkedList class, specifically because I thought the concept of using enum in Java would be good for me to strengthen. (I’ve used it once in Java, and all other exposure has been in GML, a code language specific to GameMaker Studio.)

Modified Pokemon Collectable

I wanted to make it so that a LinkedList object can look at the class in question, determine its sort key options, and then be able to utilize them. The Pokemon side of this modification is shown below:

// defining the Pokemon class implementing Collectable interface
class Pokemon implements Collectable<Pokemon> {
    private String name;
    private String[] types;
    private int dexNumber;
    private int level;

    // Define the map to assign numerical values to Pokemon types
    public final Map<String, Integer> typeKey = new HashMap<>();
    // initializing typeKey map
    {
        // assigning numerical values to Pokemon types
        typeKey.put("Normal", 0);
        typeKey.put("Fire", 1);
        typeKey.put("Water", 2);
        typeKey.put("Electric", 3);
        typeKey.put("Grass", 4);
        typeKey.put("Ice", 5);
        typeKey.put("Fighting", 6);
        typeKey.put("Poison", 7);
        typeKey.put("Ground", 8);
        typeKey.put("Flying", 9);
        typeKey.put("Psychic", 10);
        typeKey.put("Bug", 11);
        typeKey.put("Rock", 12);
        typeKey.put("Ghost", 13);
        typeKey.put("Dragon", 14);
        typeKey.put("Dark", 15);
        typeKey.put("Steel", 16);
        typeKey.put("Fairy", 17);
    }

    // my attempt at SortKey implementation
    enum SortKey {
        NAME,
        TYPES,
        DEX_NUMBER,
        LEVEL
    }

    private static SortKey currentSortKey = SortKey.DEX_NUMBER; // default sort key is dex number

    // basic constructor
    public Pokemon(String name, String[] types, int dexNumber, int level) {
        this.name = name;
        this.types = types;
        this.dexNumber = dexNumber;
        if (level > 100) { // handler for invalid levels
            this.level = 100;
        } else if (level < 1) {
            this.level = 1;
        } else {
            this.level = level;
        }
    }

    // getters methods
    public String getName() {return name;}
    public String[] getTypes() {return types;}
    public int getDexNumber() {return dexNumber;}

    // special sortkey setter method for static context
    public static void setCurrentSortKey(SortKey sortKey) {
        currentSortKey = sortKey;
    }

    // implementing the interface compareTo method
    // trying to make it founded on the Pokemon class's defined current sort key
    // sorts have been implemented for all types
    @Override
    public int compareTo(Pokemon other) {
        switch (currentSortKey) {
            case NAME: // sorts alphabetically by name
                return this.name.compareTo(other.name);
            case TYPES: // sorts based on values of typeKey, see typeKey above
                return Integer.compare(typeKey.get(this.types[0]), typeKey.get(other.types[0]));
            case DEX_NUMBER: // sorts by increasing pokedex number
                return Integer.compare(this.dexNumber, other.dexNumber);
            case LEVEL: // sorts by increasing level
                return Integer.compare(this.level, other.level);
            default:
                throw new IllegalArgumentException("Invalid sort key");
        }
    }

    // overriding toString method for better representation of the Pokemon
    @Override
    public String toString() {
        return name + " (lvl. " + level + ")";
    }

    public static void main(String[] args) {
        // creating an array of Pokemon
        Pokemon[] pokemonArray = new Pokemon[] {
            new Pokemon("Machop", new String[]{"Fighting"}, 66, 14),
            new Pokemon("Arrokuda", new String[]{"Water"}, 846, 23),
            new Pokemon("Rattata", new String[]{"Normal"}, 19, 4),
            new Pokemon("Cranidos", new String[]{"Rock"}, 408, 25),
            new Pokemon("Scolipede", new String[]{"Poison", "Bug"}, 545, 34),
            new Pokemon("Avalugg", new String[]{"Ice"}, 713, 43),
            new Pokemon("Camerupt", new String[]{"Fire", "Ground"}, 323, 39),
            new Pokemon("Bastiodon", new String[]{"Rock", "Steel"}, 411, 36)
        };
        // creating a LinkedList of Pokemon
        LinkedList<Pokemon> pokemonList = new LinkedList<>();
        // adding Pokemon to the LinkedList
        for (Pokemon pokemon : pokemonArray) {
            pokemonList.add(pokemon);
        }
        // sorting and displaying with all different sort keys
        for (Pokemon.SortKey sortKey : Pokemon.SortKey.values()) {
            // setting the current sort key
            Pokemon.setCurrentSortKey(sortKey);
            // selection sort called for the LinkedList class
            pokemonList.selectionSort();
            // displaying the sorted LinkedList
            System.out.println("Sorted by " + sortKey + ": " + pokemonList);
        }
    }
}

Pokemon.main(null);
Sorted by NAME: [Arrokuda (lvl. 23), Avalugg (lvl. 43), Bastiodon (lvl. 36), Camerupt (lvl. 39), Cranidos (lvl. 25), Machop (lvl. 14), Rattata (lvl. 4), Scolipede (lvl. 34)]
Sorted by TYPES: [Rattata (lvl. 4), Camerupt (lvl. 39), Arrokuda (lvl. 23), Avalugg (lvl. 43), Machop (lvl. 14), Scolipede (lvl. 34), Bastiodon (lvl. 36), Cranidos (lvl. 25)]
Sorted by DEX_NUMBER: [Rattata (lvl. 4), Machop (lvl. 14), Camerupt (lvl. 39), Cranidos (lvl. 25), Bastiodon (lvl. 36), Scolipede (lvl. 34), Avalugg (lvl. 43), Arrokuda (lvl. 23)]
Sorted by LEVEL: [Rattata (lvl. 4), Machop (lvl. 14), Arrokuda (lvl. 23), Cranidos (lvl. 25), Scolipede (lvl. 34), Bastiodon (lvl. 36), Camerupt (lvl. 39), Avalugg (lvl. 43)]

Implementing All Sorts

To meet requirements, after practicing with the sorts checkpoint, I added new sort methods to the LinkedList class.

public class LinkedList<T extends Comparable<T>> {
    private static class Node<T> {
        T data;
        Node<T> next;

        // constructor to create a node
        Node(T data) {
            this.data = data;
            this.next = null;
        }
    }

    private Node<T> head; // first in linkedlist
    private int size;

    // constructor to initialize the LinkedList
    public LinkedList() {
        head = null;
        size = 0;
    }

    // like .add() for ArrayList, this adds an element to the end of the list
    public void add(T element) {
        if (head == null) {
            head = new Node<>(element); // if the list is empty, this creates the first node
        } else {
            Node<T> current = head;
            while (current.next != null) {
                current = current.next; // using a while loop to approach the last node
            }
            current.next = new Node<>(element); // adding the new node at the end
        }
        size++; // increasing size of the list
    }

    // see earlier for citations
    @Override
    public String toString() {
        String string = "["; // starting the list format
        Node<T> current = head; // start with head
        while (current != null) {
            string += current.data; // appending the current data to the string
            if (current.next != null) {
                string += ", "; // good punctuation = professionalism
            }
            current = current.next; // moving on
        }
        string += "]"; // capping off the list format
        return string;
    }

    // this is to return a JSON representation of the list
    public String toJSON() {
        String json = "{"; // starting format
        Node<T> current = head; // start with head
        while (current != null) {
            json += "\"" + current.data + "\""; // adding current data in quotations for JSON
            if (current.next != null) {
                json += ", "; // good punctuation again, this time very important
            }
            current = current.next; // moving on
        }
        json += "}"; // capping off
        return json;
    }

    // new BUBBLE SORT method
    public void bubbleSort() {
        if (head == null || head.next == null) {
            return; // List is empty or has only one node, no need to sort
        }
        boolean swapped;
        Node<T> last = null;
        // this saves some steps, thank you ChatGPT
        // just learned how and why to use do {} while (condition); :O
        do {
            swapped = false;
            Node<T> current = head;
            while (current.next != last) {
                if (current.data.compareTo(current.next.data) > 0) {
                    // swapping data of current and next nodes
                    T temp = current.data;
                    current.data = current.next.data;
                    current.next.data = temp;
                    swapped = true;
                }
                current = current.next;
            }
            last = current;
        } while (swapped);
    }

    // existing selection sort
    public void selectionSort() {
        if (head == null || head.next == null) {
            return; // if the list is empty or has only one element, it's already sorted
            // again, this is an inclusion courtesy of ChatGPT and its incredible forethought
        }
        Node<T> current = head;
        while (current != null) {
            Node<T> min = current;
            Node<T> temp = current.next;
            while (temp != null) {
                if (temp.data.compareTo(min.data) < 0) {
                    min = temp; // finding the minimum element
                }
                temp = temp.next;
            }
            T tmpData = current.data; // swapping the current unsorted place with the minimum
            current.data = min.data;
            min.data = tmpData;
            current = current.next; // moving to the next element
        }
    }

    // new INSERTION SORT method
    public void insertionSort() {
        if (head == null || head.next == null) {
            return; // List is empty or has only one node, no need to sort
        }
        Node<T> sorted = null; // initializing sorted list as empty
        Node<T> current = head;
        while (current != null) {
            Node<T> next = current.next;
            sorted = sortedInsert(sorted, current); // inserting current node into sorted list
            current = next;
        }
        head = sorted; // Update head to point to the sorted list
    }

    // this inserts a node into a sorted list and returns the head of the sorted list
    // thank you for the help, CHat
    private Node<T> sortedInsert(Node<T> sorted, Node<T> newNode) {
        if (sorted == null || sorted.data.compareTo(newNode.data) > 0) {
            newNode.next = sorted;
            return newNode;
        }

        Node<T> current = sorted;
        while (current.next != null && current.next.data.compareTo(newNode.data) < 0) {
            current = current.next;
        }

        newNode.next = current.next;
        current.next = newNode;
        return sorted;
    }

    // new MERGE SORT method
    // chatGpt played a big role in this one
    public void mergeSort() {
        head = mergeSort(head); // call goes straight to this
    }

    // this method will help to call the merge sort recursively
    private Node<T> mergeSort(Node<T> node) {
        if (node == null || node.next == null) {
            return node; // if the list is empty/only has one node, it's sorted
        }

        // splitting the list into two halves
        Node<T> mid = getMiddleFromNode(node); // see this method below
        Node<T> nextOfMid = mid.next;
        mid.next = null;

        // recursively sorting the two halves
        Node<T> left = mergeSort(node); // starts at head, see argumnent above
        Node<T> right = mergeSort(nextOfMid); // starts just past halfway point

        // Merge the sorted halves
        return merge(left, right);
    }

    // Helper method to merge two sorted lists
    private Node<T> merge(Node<T> left, Node<T> right) {
        Node<T> result = null;
        // testing for possible cases with single lists, thanks ChatGPT
        if (left == null) {
            return right;
        }
        if (right == null) {
            return left;
        }
        // choosing the smaller value
        // once chosen, the recursion is called again
        if (left.data.compareTo(right.data) <= 0) {
            result = left;
            result.next = merge(left.next, right);
        } else {
            result = right;
            result.next = merge(left, right.next);
        }
        return result;
    }

    // this finds the middle node of a linked list from a given node
    private Node<T> getMiddleFromNode(Node<T> node) {
        if (node == null) {
            return node;
        }
        Node<T> slow = node;
        Node<T> fast = node.next;
        while (fast != null) {
            fast = fast.next;
            if (fast != null) {
                slow = slow.next;
                fast = fast.next;
            }
        }
        return slow;
    }

    // new QUICK SORT method
    // thanks again, chatGPT
    public void quickSort() {
        Node<T> end = head;
        // getting the end node
        for (int i = 0; i < this.size; i++) {
            end = end.next;
        }
        // calling the other quicksort method
        this.quickSort(head, end);
    }

    // COURTESY OF GEEKS4GEEKS
    // Takes first and last node, 
    // but do not break any links in 
    // the whole linked list 
    private Node<T> partitionLast(Node<T> start, Node<T> end) 
    { 
        if (start == end || start == null || end == null) 
            return start; 
  
        Node<T> pivot_prev = start; 
        Node<T> curr = start; 
        Node<T> pivot = end; 
  
        // iterate till one before the end, 
        // no need to iterate till the end 
        // because end is pivot 
        while (start != end) { 
            if (curr.data.compareTo(pivot.data) < 0) { 
                // keep tracks of last modified item 
                pivot_prev = curr; 
                T temp = curr.data; 
                curr.data = start.data; 
                start.data = temp; 
                curr = curr.next; 
            } 
            start = start.next; 
        } 
  
        // Swap the position of curr i.e. 
        // next suitable index and pivot 
        T temp = curr.data; 
        curr.data = pivot.data; 
        end.data = temp; 
  
        // Return one previous to current 
        // because current is now pointing to pivot 
        return pivot_prev; 
    } 
  
    private void quickSort(Node<T> start, Node<T> end) 
    { 
        if (start == null || start == end 
            || start == end.next) 
            return; 
  
        // Split list and partition recurse 
        Node<T> pivot_prev = partitionLast(start, end); 
        quickSort(start, pivot_prev); 
  
        // If pivot is picked and moved to the start, 
        // that means start and pivot is same 
        // so pick from next of pivot 
        if (pivot_prev != null && pivot_prev == start) 
            quickSort(pivot_prev.next, end); 
  
        // If pivot is in between of the list, 
        // start from next of pivot, 
        // since we have pivot_prev, so we move two nodes 
        else if (pivot_prev != null
                 && pivot_prev.next != null) 
            quickSort(pivot_prev.next.next, end); 
    } 
}
public class Main {
    // new main method for testing all sorts with Pokemon
    public static void main(String[] args) {
        // creating an array of Pokemon
        Pokemon[] pokemonArray = new Pokemon[] {
            new Pokemon("Machop", new String[]{"Fighting"}, 66, 14),
            new Pokemon("Arrokuda", new String[]{"Water"}, 846, 23),
            new Pokemon("Rattata", new String[]{"Normal"}, 19, 4),
            new Pokemon("Cranidos", new String[]{"Rock"}, 408, 25),
            new Pokemon("Scolipede", new String[]{"Poison", "Bug"}, 545, 34),
            new Pokemon("Avalugg", new String[]{"Ice"}, 713, 43),
            new Pokemon("Camerupt", new String[]{"Fire", "Ground"}, 323, 39),
            new Pokemon("Bastiodon", new String[]{"Rock", "Steel"}, 411, 36)
        };
        // creating a LinkedList of Pokemon
        LinkedList<Pokemon> pokemonList = new LinkedList<>();
        // adding Pokemon to the LinkedList
        for (Pokemon pokemon : pokemonArray) {
            pokemonList.add(pokemon);
        }
        // sorting and displaying with all different sort keys
        int i = 0;
        for (Pokemon.SortKey sortKey : Pokemon.SortKey.values()) {
            // incrementing i, which determines sort type
            i++;
            // setting the current sort key
            Pokemon.setCurrentSortKey(sortKey);
            // calling sort for the LinkedList class depending on i
            switch (i) {
                case 1:
                    pokemonList.bubbleSort();
                    break;
                case 2:
                    pokemonList.insertionSort();
                    break;
                case 3:
                    pokemonList.mergeSort();
                    break;
                case 4:
                    pokemonList.quickSort();
                    break;
            }
            // displaying the sorted LinkedList
            System.out.println("Sorted by " + sortKey + ": " + pokemonList);
        }
    }
}

Main.main(null);
Sorted by NAME: [Arrokuda (lvl. 23), Avalugg (lvl. 43), Bastiodon (lvl. 36), Camerupt (lvl. 39), Cranidos (lvl. 25), Machop (lvl. 14), Rattata (lvl. 4), Scolipede (lvl. 34)]
Sorted by TYPES: [Rattata (lvl. 4), Camerupt (lvl. 39), Arrokuda (lvl. 23), Avalugg (lvl. 43), Machop (lvl. 14), Scolipede (lvl. 34), Cranidos (lvl. 25), Bastiodon (lvl. 36)]
Sorted by DEX_NUMBER: [Rattata (lvl. 4), Machop (lvl. 14), Camerupt (lvl. 39), Cranidos (lvl. 25), Bastiodon (lvl. 36), Scolipede (lvl. 34), Avalugg (lvl. 43), Arrokuda (lvl. 23)]
Sorted by LEVEL: [Rattata (lvl. 4), Machop (lvl. 14), Arrokuda (lvl. 23), Cranidos (lvl. 25), Scolipede (lvl. 34), Bastiodon (lvl. 36), Camerupt (lvl. 39), Avalugg (lvl. 43)]

Reflection on Algorhythmic

I was robbed. Mr. Mortensen, I was robbed. In broad daylight, too. Can you believe it? I certainly can’t. I just cannot. It was a blowout—we swept the competition with ease. And yet, what happened? What happened to us, Mr. Mortensen? We had our joy, our lives, our souls TAKEN from us. I will not stand for this.

My contributions to this event were writing the script with Aiden and act act acting! I also helped send out examples for this very review to other group members to help meet requirements. (Don’t worry, nobody copied.)

Anyway, that was a lot of fun. We want to do it again at night at the museum. Raah!