Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ are not the only possible solutions to these cases.
7. [Quiz Questions ❔](src/test/java/pl/mperor/interview/tasks/QuizQuestionsTest.java)
8. [Wolf 🐺 & Sheep 🐑](src/test/java/pl/mperor/interview/tasks/exception/WolfFullAndSheepWholeTest.java)
9. [Player Ranking System 🏆](src/test/java/pl/mperor/interview/tasks/ranking/PlayerRankingSystemTest.java)
10. [Simple Linked List 🔗](src/test/java/pl/mperor/interview/tasks/list/SimpleLinkedListTest.java)

## Sources 🔗

Expand Down
132 changes: 132 additions & 0 deletions src/main/java/pl/mperor/interview/tasks/list/SimpleLinkedList.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,132 @@
package pl.mperor.interview.tasks.list;

import java.util.Iterator;
import java.util.NoSuchElementException;
import java.util.Objects;

public class SimpleLinkedList<T> implements Iterable<T> {

private Node<T> head;
private Node<T> tail;
private int size;

public boolean add(T value) {
Node<T> node = new Node<>(value);
if (isEmpty()) {
head = node;
} else {
tail.next = node;
}
tail = node;
size++;
return true;
}

public T get(int index) {
Objects.checkIndex(index, size);
Node<T> current = head;
for (int i = 0; i < index; i++) {
current = current.next;
}
return current.value;
}

public T getFirst() {
if (isEmpty())
throw new NoSuchElementException("List is empty!");
return head.value;
}

public T getLast() {
if (isEmpty())
throw new NoSuchElementException("List is empty!");
return tail.value;
}

public boolean contains(Object object) {
for (T value : this) {
if (Objects.equals(value, object))
return true;
}
return false;
}

public boolean remove(Object object) {
Iterator<T> iterator = iterator();
while (iterator.hasNext()) {
if (Objects.equals(iterator.next(), object)) {
iterator.remove();
return true;
}
}
return false;
}

public int size() {
return size;
}

public boolean isEmpty() {
return size == 0;
}

public void clear() {
head = null;
tail = null;
size = 0;
}

@Override
public Iterator<T> iterator() {
return new Iterator<>() {

private Node<T> previous;
private Node<T> current;
private Node<T> next = head;
private boolean removable;

@Override
public boolean hasNext() {
return next != null;
}

@Override
public T next() {
if (!hasNext())
throw new NoSuchElementException();
previous = current;
current = next;
next = next.next;
removable = true;
return current.value;
}

@Override
public void remove() {
if (!removable)
throw new IllegalStateException();
if (previous == null) {
head = next;
} else {
previous.next = next;
}
if (current == tail) {
tail = previous;
}
current = previous;
removable = false;
size--;
}
};
}

private static class Node<T> {

private final T value;
private Node<T> next;

private Node(T value) {
this.value = value;
}
}
}
164 changes: 164 additions & 0 deletions src/test/java/pl/mperor/interview/tasks/list/SimpleLinkedListTest.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,164 @@
package pl.mperor.interview.tasks.list;

import org.junit.jupiter.api.Test;

import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import java.util.NoSuchElementException;

import static org.junit.jupiter.api.Assertions.*;

/**
* Simple Linked List 🔗
*
* <p>Implement your own singly linked list.
* The implementation can be very simple - it doesn't have to implement all methods of the {@link List} interface.
* It should allow to:
* <ul>
* <li>add elements (including <code>null</code>) to the end of the list,</li>
* <li>get an element by index and get the first/last element,</li>
* <li>check if the list contains an element and remove it,</li>
* <li>check the size of the list, clear it and iterate through its elements.</li>
* </ul>
* Behave like {@link java.util.LinkedList} in edge cases (e.g. accessing elements of an empty list).
*/
public class SimpleLinkedListTest {

@Test
public void testEmptyList() {
var list = new SimpleLinkedList<String>();

assertTrue(list.isEmpty());
assertEquals(0, list.size());
assertFalse(list.iterator().hasNext());
assertThrows(NoSuchElementException.class, list::getFirst);
assertThrows(NoSuchElementException.class, list::getLast);
assertThrows(IndexOutOfBoundsException.class, () -> list.get(0));
}

@Test
public void testAddAndGetElements() {
var list = listOf("First", "Second", "Third");

assertFalse(list.isEmpty());
assertEquals(3, list.size());
assertEquals("First", list.get(0));
assertEquals("Second", list.get(1));
assertEquals("Third", list.get(2));
assertEquals("First", list.getFirst());
assertEquals("Third", list.getLast());
assertThrows(IndexOutOfBoundsException.class, () -> list.get(3));
assertThrows(IndexOutOfBoundsException.class, () -> list.get(-1));
}

@Test
public void testIterateThroughElements() {
var list = listOf("First", "Second", "Third");

assertEquals(List.of("First", "Second", "Third"), toJavaList(list));

Iterator<String> iterator = list.iterator();
iterator.next();
iterator.next();
iterator.next();
assertThrows(NoSuchElementException.class, iterator::next);
}

@Test
public void testNullElements() {
var list = listOf("First", null, "Third");

assertEquals(3, list.size());
assertNull(list.get(1));
assertTrue(list.contains(null));
assertTrue(list.contains("Third"));
assertTrue(list.remove(null));
assertFalse(list.contains(null));
assertEquals(List.of("First", "Third"), toJavaList(list));
}

@Test
public void testContains() {
var list = listOf("First", "Second", "Third");

assertTrue(list.contains("Second"));
assertFalse(list.contains("Fourth"));
assertFalse(list.contains(null));
}

@Test
public void testRemoveElements() {
var list = listOf("First", "Second", "Third", "Fourth");

assertTrue(list.remove("Second"));
assertEquals(List.of("First", "Third", "Fourth"), toJavaList(list));

assertTrue(list.remove("Fourth"));
assertEquals(List.of("First", "Third"), toJavaList(list));
assertEquals("Third", list.getLast());

assertTrue(list.remove("First"));
assertEquals(List.of("Third"), toJavaList(list));
assertEquals("Third", list.getFirst());

assertFalse(list.remove("Fifth"));
assertTrue(list.remove("Third"));
assertTrue(list.isEmpty());

list.add("New");
assertEquals("New", list.getFirst());
assertEquals("New", list.getLast());
}

@Test
public void testRemoveConsecutiveElementsWithIterator() {
var list = listOf("A", "B", "C", "D", "E");

Iterator<String> iterator = list.iterator();
assertThrows(IllegalStateException.class, iterator::remove);
while (iterator.hasNext()) {
String value = iterator.next();
if (!value.equals("A") && !value.equals("E")) {
iterator.remove();
}
}
assertEquals(List.of("A", "E"), toJavaList(list));
assertEquals(2, list.size());

Iterator<String> secondIterator = list.iterator();
secondIterator.next();
secondIterator.remove();
assertThrows(IllegalStateException.class, secondIterator::remove);

assertEquals(List.of("E"), toJavaList(list));
assertEquals(1, list.size());
assertEquals("E", list.getFirst());
assertEquals("E", list.getLast());
}

@Test
public void testClear() {
var list = listOf("First", "Second", "Third");
list.clear();

assertTrue(list.isEmpty());
assertEquals(0, list.size());
assertThrows(NoSuchElementException.class, list::getFirst);
}

@SafeVarargs
private static <T> SimpleLinkedList<T> listOf(T... values) {
var list = new SimpleLinkedList<T>();
for (T value : values) {
assertTrue(list.add(value));
}
return list;
}

private static <T> List<T> toJavaList(SimpleLinkedList<T> list) {
List<T> result = new ArrayList<>();
list.forEach(result::add);
return result;
}
}
Loading