LMU ☀️ CMSI 2120
DATA STRUCTURES
HOMEWORK #3 Due: 2022-10-20

Learning Objectives

With this assignment you will demonstrate:

Readings and Videos

Please:

Instructions

Work in teams of 1 to 2 students.

Your work will be done in a private GitHub repository called lmu-cmsi-2120 or something similar. Submit to BrightSpace a single text file or pdf that contains the link to this repo. Submit only one submission per team.

Structure your repository, for now, as follows:

  .
  ├── README.md
  ├── .gitignore
  ├── homework1/
  │   └── (existing files from previous assignments)
  ├── homework2/
  │   └── (existing files from previous assignments)
  └── homework3/
      ├── README.md                      (Include ALL students’ names)
      ├── SimpleLinkedList.java          (starter code on course notes)
      ├── SimpleImmutableList.java       (starter code on course notes)
      ├── SimpleLinkedListTest.java      (given to you below)
      └── SimpleImmutableListTest.java   (given to you below)

When grading, I will clone your repository, and run the tests. Since the repo is to be private, please add me as a contributor to your repo (so I can run and comment on your work). My github name is rtoal.

You will be graded on your programming style, so make sure you are set up so that your editor or IDE auto-formats your code. You should also install Sonar Lint because it is amazing. Environment set up was carried out in class earlier, so you should be good already.

Your homework submission will be the state of your repository on the branch master, at 23:59 in the America/Los Angeles time zone on the due date above.

Make sure your README has the names of all the students that have worked on the project.

The Classes You Are To Turn In

For this assignment, you will not be building anything from scratch. Instead, you will start with the classes I built on my notes on Lists, namely the simple (doubly-) linked list and the simple (singly-linked) immutable list.

To the mutable, doubly-linked list implementation, add the following mutating methods. You MUST implement take, drop, and reverse, by manipulating links only. There is to be no creation of new nodes and no copying data between nodes. You may ONLY adjust links in existing nodes.

To the immutable, singly-linked list implementation, add the following methods:

To both list implementations, add:

Here are the testers:

SimpleLinkedListTest.java
import java.util.NoSuchElementException;
import static org.junit.Assert.assertTrue;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertThrows;
import org.junit.jupiter.api.Test;

public class SimpleLinkedListTest {

    // A utility to make many of the tests easier
    private String asString(SimpleLinkedList list) {
        var builder = new StringBuilder();
        list.forEach(builder::append);
        return builder.toString();
    }

    @Test
    public void testListIsEmptyOnConstruction() {
        var list = new SimpleLinkedList();
        assertEquals(0, list.size());
    }

    @Test
    public void testGetAndIndexOf() {
        var list = new SimpleLinkedList();
        list.addFirst(3);
        list.addFirst(2);
        list.addLast(4);
        list.addLast(5);
        list.addFirst(1);
        assertEquals(5, list.size());
        assertEquals(1, list.get(0));
        assertEquals(2, list.get(1));
        assertEquals(3, list.get(2));
        assertEquals(4, list.get(3));
        assertEquals(5, list.get(4));
        assertEquals(0, list.indexOf(1));
        assertEquals(1, list.indexOf(2));
        assertEquals(2, list.indexOf(3));
        assertEquals(3, list.indexOf(4));
        assertEquals(4, list.indexOf(5));
    }

    @Test
    public void testAddAtTheEnds() {
        var list = new SimpleLinkedList();
        list.addFirst(3);
        list.addFirst(2);
        list.addLast(4);
        list.addLast(5);
        list.addFirst(1);
        assertEquals(5, list.size());
        assertEquals("12345", asString(list));
    }

    @Test
    public void testAddAtPosition() {
        var list = new SimpleLinkedList();
        for (var i = 1; i <= 5; i++) {
            list.add(i - 1, i);
        } // 1 2 3 4 5
        list.add(1, 9); // 1 9 2 3 4 5
        list.add(6, 8); // 1 9 2 3 4 5 8
        list.add(3, 7); // 1 9 2 7 3 4 5 8
        assertEquals("19273458", asString(list));
        assertThrows(IndexOutOfBoundsException.class, () -> list.add(-1, "oops"));
        assertThrows(IndexOutOfBoundsException.class, () -> list.add(9, "oops"));
    }

    @Test
    public void testSet() {
        var list = new SimpleLinkedList();
        for (var i = 1; i <= 5; i++) {
            list.add(i - 1, i);
        } // 1 2 3 4 5
        for (var i = 1; i <= 5; i++) {
            list.set(i - 1, i * 10);
        } // 10 20 30 40 50
        assertEquals(5, list.size());
        assertEquals("1020304050", asString(list));
    }

    @Test
    public void testRemoves() {
        var list = new SimpleLinkedList();
        for (var i = 1; i <= 10; i++) {
            list.addLast(i);
        }
        list.remove(7); // 1 2 3 4 5 6 7 9 10
        list.remove(3); // 1 2 3 5 6 7 9 10
        list.remove(7); // 1 2 3 5 6 7 9
        list.remove(0); // 2 3 5 6 7 9
        list.remove(1); // 2 5 6 7 9
        assertEquals(5, list.size());
        assertEquals("25679", asString(list));
    }

    @Test
    public void testForEach() {
        var list = new SimpleLinkedList();
        var builder = new StringBuilder();
        list.forEach(builder::append);
        // First make sure it has no effect if empty
        assertEquals("", builder.toString());
        for (var i = 1; i <= 10; i++) {
            list.addLast(i);
        }
        list.forEach(builder::append);
        assertEquals("12345678910", builder.toString());
    }

    @Test
    public void testDropAndTake() {
        var list = new SimpleLinkedList();
        list.take(0);
        assert (list.size() == 0);
        list.addLast(1);
        list.addLast(2);
        list.addLast(3);
        list.addLast(4);
        assertEquals("1234", asString(list));
        assertThrows(IllegalArgumentException.class, () -> list.take(-1));
        assertThrows(IllegalArgumentException.class, () -> list.take(5));
        list.take(2);
        assert (list.size() == 2);
        assertEquals("12", asString(list));
        list.drop(1);
        assert (list.size() == 1);
        assertEquals("2", asString(list));
        list.drop(1);
        assert (list.size() == 0);
        list.take(0);
        assertThrows(IllegalArgumentException.class, () -> list.take(1));
    }

    @Test
    public void testReverse() {
        var list = new SimpleLinkedList();
        // Reverse the empty list
        list.reverse();
        assertEquals(0, list.size());
        assertEquals("", asString(list));
        // Reverse a one-element list
        list.addLast(1);
        list.reverse();
        assertEquals(1, list.size());
        assertEquals("1", asString(list));
        // Reverse a four-element list
        list.addLast(2);
        list.addLast(3);
        list.addLast(4);
        assertEquals(4, list.size());
        list.reverse();
        assertEquals("4321", asString(list));
    }

    @Test
    public void testAppend() {
        var list = new SimpleLinkedList();
        var other = new SimpleLinkedList();
        // Empty appended with empty
        list.append(other);
        assertEquals(0, list.size());
        other.addLast(2);
        other.addLast(5);
        // Empty appended with nonempty
        list.append(other);
        assertEquals(2, list.size());
        assertEquals("25", asString(list));
        // Do it again to make sure other list did not change
        list.append(other);
        assertEquals(4, list.size());
        assertEquals("2525", asString(list));
        // Append to self
        assertThrows(IllegalArgumentException.class, () -> list.append(list));
        // Append an empty list
        list.append(new SimpleLinkedList());
        assertEquals("2525", asString(list));
    }

    @Test
    public void testMap() {
        var list = new SimpleLinkedList();
        // Map the empty list
        list.map(x -> 2 * (Integer) x);
        assertEquals(0, list.size());
        assertEquals("", asString(list));
        // Map a one-element list
        list.addLast(1);
        list.map(x -> 2 * (Integer) x);
        assertEquals(1, list.size());
        assertEquals("2", asString(list));
        // map a four-element list
        list.addLast(2);
        list.addLast(3);
        list.addLast(4);
        assertEquals(4, list.size());
        list.map(x -> 2 * (Integer) x);
        assertEquals("4468", asString(list));
    }

    @Test
    public void testFilter() {
        var list = new SimpleLinkedList();
        for (var i = 1; i <= 10; i++) {
            list.addLast(i);
        }
        list.filter(x -> (Integer) x % 5 != 0);
        assertEquals(8, list.size());
        assertEquals("12346789", asString(list));
        list.filter(x -> (Integer) x % 3 != 0);
        assertEquals(5, list.size());
        assertEquals("12478", asString(list));
        // Remove the first element through a filter
        list.filter(x -> (Integer) x != 1);
        assertEquals(4, list.size());
        assertEquals("2478", asString(list));
        // Take it down to one element
        list.filter(x -> (Integer) x >= 8);
        assertEquals(1, list.size());
        assertEquals("8", asString(list));
        // Filter down to empty
        list.filter(x -> false);
        assertEquals(0, list.size());
        assertEquals("", asString(list));
        // Filter on empty has no effect
        list.filter(x -> true);
        assertEquals(0, list.size());
        assertEquals("", asString(list));
    }

    @Test
    public void testOf() {
        var list = SimpleLinkedList.of();
        assertEquals(0, list.size());
        assertEquals("", asString(list));
        list = SimpleLinkedList.of(100);
        assertEquals(1, list.size());
        assertEquals("100", asString(list));
        list = SimpleLinkedList.of(13, 8, 21, 13, 55);
        assertEquals(5, list.size());
        assertEquals("138211355", asString(list));
    }

    @Test
    public void testLast() {
        var list = new SimpleLinkedList();
        assertThrows(NoSuchElementException.class, list::last);
        list.addLast(1);
        assertEquals(1, list.last());
        list.addLast(2);
        list.addLast(3);
        assertEquals(3, list.last());
    }

    @Test
    public void testEveryAndSome() {
        var list = new SimpleLinkedList();
        assertTrue(list.every(x -> true));
        assertFalse(list.some(x -> true));
        for (var i = 1; i <= 10; i++) {
            list.addLast(i);
        }
        assertTrue(list.every(x -> (Integer) x > 0));
        assertFalse(list.every(x -> (Integer) x > 5));
        assertTrue(list.some(x -> (Integer) x > 5));
        assertFalse(list.some(x -> (Integer) x > 10));
    }
}
SimpleImmutableListTest.java
import java.util.NoSuchElementException;
import static org.junit.Assert.assertTrue;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertThrows;
import org.junit.jupiter.api.Test;

public class SimpleImmutableListTest {

    // A utility to make many of the tests easier
    private String asString(SimpleImmutableList list) {
        var builder = new StringBuilder();
        list.forEach(builder::append);
        return builder.toString();
    }

    @Test
    public void testListIsEmptyOnConstruction() {
        var list = SimpleImmutableList.of();
        assertEquals(0, list.size());
    }

    @Test
    public void testOf() {
        assertEquals("", asString(SimpleImmutableList.of()));
        assertEquals("5", asString(SimpleImmutableList.of(5)));
        assertEquals("58132", asString(SimpleImmutableList.of(5, 8, 13, 2)));
    }

    @Test
    public void testFrom() {
        var list = SimpleImmutableList.from(5, new EmptyList());
        assertEquals(1, list.size());
        assertEquals("5", asString(list));
        list = SimpleImmutableList.from(8, list);
        list = SimpleImmutableList.from(13, list);
        list = SimpleImmutableList.from(2, list);
        assertEquals(4, list.size());
        assertEquals("21385", asString(list));
    }

    @Test
    public void testHeadAndTail() {
        assertThrows(NoSuchElementException.class, () -> new EmptyList().head());
        assertThrows(NoSuchElementException.class, () -> new EmptyList().tail());
        var list = SimpleImmutableList.of(100);
        assertEquals(100, list.head());
        assertTrue(list.tail() instanceof EmptyList);
        assertEquals(0, list.tail().size());
        list = SimpleImmutableList.of(55, 34, 13, 21);
        assertEquals(55, list.head());
        assertEquals(3, list.tail().size());
        assertEquals("341321", asString(list.tail()));
    }

    @Test
    public void testAt() {
        assertThrows(NoSuchElementException.class, () -> new EmptyList().at(1));
        var list = SimpleImmutableList.of(3, 5, 8, 3, 3, 13, 2);
        assertThrows(NoSuchElementException.class, () -> list.at(-1));
        assertThrows(NoSuchElementException.class, () -> list.at(7));
        assertEquals(3, list.at(0));
        assertEquals(5, list.at(1));
        assertEquals(8, list.at(2));
        assertEquals(3, list.at(3));
        assertEquals(13, list.at(5));
        assertEquals(2, list.at(6));
    }

    @Test
    public void testForEach() {
        var list = SimpleImmutableList.of();
        var builder = new StringBuilder();
        list.forEach(builder::append);
        assertEquals("", builder.toString());
        list = SimpleImmutableList.of(1, 2, 3, 4, 5, 6, 7, 8, 9, 10);
        list.forEach(builder::append);
        assertEquals("12345678910", builder.toString());
    }

    @Test
    public void testDropAndTake() {
        // Take 0 from empty is ok
        assertEquals(0, SimpleImmutableList.of().take(0).size());
        assertEquals(0, SimpleImmutableList.of().drop(0).size());
        // But you cannot take or drop even 1 from empty
        assertThrows(IllegalArgumentException.class, () -> new EmptyList().take(1));
        assertThrows(IllegalArgumentException.class, () -> new EmptyList().drop(1));
        // Do all the takes and drops on non-empties
        var oneThroughFour = SimpleImmutableList.of(1, 2, 3, 4);
        assertThrows(IllegalArgumentException.class, () -> oneThroughFour.take(-1));
        assertEquals("", asString(oneThroughFour.take(0)));
        assertEquals("1", asString(oneThroughFour.take(1)));
        assertEquals("12", asString(oneThroughFour.take(2)));
        assertEquals("123", asString(oneThroughFour.take(3)));
        assertEquals("1234", asString(oneThroughFour.take(4)));
        assertThrows(IllegalArgumentException.class, () -> oneThroughFour.take(5));
        assertThrows(IllegalArgumentException.class, () -> oneThroughFour.drop(-1));
        assertEquals("1234", asString(oneThroughFour.drop(0)));
        assertEquals("234", asString(oneThroughFour.drop(1)));
        assertEquals("34", asString(oneThroughFour.drop(2)));
        assertEquals("4", asString(oneThroughFour.drop(3)));
        assertEquals("", asString(oneThroughFour.drop(4)));
        assertThrows(IllegalArgumentException.class, () -> oneThroughFour.drop(5));
    }

    @Test
    public void testReversed() {
        // Reverse the empty list
        assertEquals("", asString(SimpleImmutableList.of().reversed()));
        // Reverse a one-element list
        assertEquals("1", asString(SimpleImmutableList.of(1).reversed()));
        // Reverse a four-element list
        assertEquals("4321", asString(SimpleImmutableList.of(1, 2, 3, 4).reversed()));
    }

    @Test
    public void testAppend() {
        var empty = SimpleImmutableList.of();
        var twoFive = SimpleImmutableList.of(2, 5);
        var emptyTwoFive = empty.append(twoFive);
        var twoFiveEmpty = twoFive.append(empty);
        var twoFiveTwoFive = twoFive.append(twoFive);
        assertEquals("25", asString(emptyTwoFive));
        assertEquals("25", asString(twoFiveEmpty));
        assertEquals("2525", asString(twoFiveTwoFive));
    }

    @Test
    public void testMap() {
        var list = SimpleImmutableList.of();
        // Map the empty list
        list = list.map(x -> 2 * (Integer) x);
        assertEquals(0, list.size());
        assertEquals("", asString(list));
        // Map a one-element list
        list = SimpleImmutableList.of(1);
        list = list.map(x -> 2 * (Integer) x);
        assertEquals(1, list.size());
        assertEquals("2", asString(list));
        // map a four-element list
        list = SimpleImmutableList.of(2, 2, 3, 4);
        assertEquals(4, list.size());
        list = list.map(x -> 2 * (Integer) x);
        assertEquals("4468", asString(list));
    }

    @Test
    public void testFilter() {
        var list = SimpleImmutableList.of(1, 2, 3, 4, 5, 6, 7, 8, 9, 10);
        var threes = list.filter(x -> (Integer) x % 3 == 0);
        assertEquals(3, threes.size());
        assertEquals("369", asString(threes));
        // Filter without leading elements
        var biggerHalf = list.filter(x -> (Integer) x > 5);
        assertEquals(5, biggerHalf.size());
        assertEquals("678910", asString(biggerHalf));
        // Filter is empty
        var negatives = list.filter(x -> (Integer) x < 0);
        assertEquals(0, negatives.size());
        assertEquals("", asString(negatives));
        // Filter on empty has no effect
        var nothing = new EmptyList().filter(x -> true);
        assertEquals(0, nothing.size());
        assertEquals("", asString(nothing));
    }

    @Test
    public void testLast() {
        var list = SimpleImmutableList.of();
        assertThrows(NoSuchElementException.class, list::last);
        list = SimpleImmutableList.from(1, list);
        assertEquals(1, list.last());
        list = SimpleImmutableList.from(2, list);
        list = SimpleImmutableList.from(3, list);
        assertEquals(1, list.last());
    }

    @Test
    public void testEveryAndSome() {
        var list = SimpleImmutableList.of();
        assertTrue(list.every(x -> true));
        assertFalse(list.some(x -> true));
        list = SimpleImmutableList.of(1, 2, 3, 4, 5, 6, 7, 8, 9, 10);
        assertTrue(list.every(x -> (Integer) x > 0));
        assertFalse(list.every(x -> (Integer) x > 5));
        assertTrue(list.some(x -> (Integer) x > 5));
        assertFalse(list.some(x -> (Integer) x > 10));
    }
}

Grading

As before, the following elements will all contribute to your grade:

In addition, I will be scoring code quality, which is sometimes subjective to be sure, but you deserve feedback.