Recently, a company has asked me to make an implementation of a doubly linked list with unity tests to test my skills. In the task description they specified that the solution should be as academic as possible. After seeing the test, they said it was not passed, so, since they have not specified what could be improved or what was directly wrong, I was hoping to find the answer here.
Here is my code
package org.raider.list;
/**
* Lista doblemente enlazada con métodos básicos de inserción, consulta y
* borrado
*
* @author Raider
* @param <E>
*
*/
public class CustomDoubleLinkedList<E> {
transient CustomNode<E> first;
transient CustomNode<E> last;
transient int length;
/**
* Constructor básico
*/
public CustomDoubleLinkedList() {
this.first = null;
this.last = null;
this.length = 0;
}
/**
* Método para comprobar si la lista está vacia
*
* @return
*/
public boolean isEmpty() {
return this.length == 0 && this.first == null && this.last == null;
}
/**
* Devuelve el tamaño de la lista
*
* @return número de elementos de la lista
*/
public int size() {
return this.length;
}
/**
* Agrega un elemento al final de la lista
*
* @param element
*/
public void add(E element) {
final CustomNode<E> lastElm = this.last;
final CustomNode<E> newCustomNode = new CustomNode<>(lastElm, element, null);
this.last = newCustomNode;
if (lastElm == null) {
this.first = newCustomNode;
} else {
lastElm.nextItem = newCustomNode;
}
this.length++;
}
/**
* Agrega un elemento al principio de la lista
*
* @param element
*/
public void addAtBegin(E element) {
final CustomNode<E> firstElm = this.first;
final CustomNode<E> newCustomNode = new CustomNode<>(null, element, firstElm);
this.first = newCustomNode;
if (firstElm == null) {
this.last = newCustomNode;
} else {
firstElm.prevItem = newCustomNode;
}
this.length++;
}
/**
* Agrega un elemento en una determinada índice (comienza en 0)
*
* @param elem elemento a agregar
* @param index índice
*/
public void addAt(E elem, int index) {
if (index == 0) {
addAtBegin(elem);
return;
} else {
CustomNode<E> existingNode = searchNode(index);
CustomNode<E> newElem = new CustomNode<>(null, elem, null);
CustomNode<E> tmp = existingNode.prevItem;
newElem.nextItem = existingNode;
tmp.nextItem = newElem;
newElem.prevItem = tmp;
existingNode.prevItem = newElem;
}
this.length++;
}
/**
* Agregamos todos los elementos de una lista en una determinada posición de
* otra
*
* @param index lugar a partir del que añadiremos la otra lista
* @param list lista a añadir
*/
public void addAll(int index, CustomDoubleLinkedList list) {
int originalSize = list.size();
if (originalSize > 0) {
CustomNode<E> prev;
CustomNode<E> next;
if (index == originalSize) {
next = null;
prev = this.last;
} else {
next = searchNode(index);
prev = next.prevItem;
}
for (int i = 0; i < originalSize; i++) {
E e = (E) list.get(i);
CustomNode<E> newNode = new CustomNode<>(prev, e, null);
if (prev == null) {
this.first = newNode;
} else {
prev.nextItem = newNode;
}
prev = newNode;
}
if (next == null) {
this.last = prev;
} else {
prev.nextItem = next;
next.prevItem = prev;
}
this.length += originalSize;
}
}
/**
* Elimina todos los elementos de la lista
*/
public void clear() {
/*
* Realmente podríamos eliminar el primero, el último y poner el tamaño a 0,
* pero de esta forma nos aseguramos liberar memoria
*/
CustomNode<E> cn = this.first;
for (int i = 0; i < this.length; i++) {
CustomNode<E> next = cn.nextItem;
cn.item = null;
cn.nextItem = null;
cn.prevItem = null;
cn = next;
}
this.first = null;
this.last = null;
this.length = 0;
}
/**
* Elimina todos los elementos que coincidan
*
* @param element
*/
public void remove(Object element) {
for (int i = 0; i < this.length; i++) {
CustomNode<E> node = searchNode(i);
if (element == null) {
if (node.item == null) {
deleteNode(node);
}
} else {
if (element.equals(node.item)) {
deleteNode(node);
}
}
}
}
/**
* Elimina un elemento en determinado índice
*
* @param index
*/
public void removeIndex(int index) {
CustomNode<E> node = searchNode(index);
deleteNode(node);
}
/**
* Obtiene un elemento por su índice
*
* @param index índice
* @return elemento
* @throws NullPointerException si el objeto es null
*/
public E get(int index) {
CustomNode<E> node = searchNode(index);
return node.item;
}
/**
* Devuelve el primer elemento
*
* @return elemento
*/
public E getFirst() {
return this.first.item;
}
/**
* Devuelve el ultimo elemento
*
* @return elemento
*/
public E getLast() {
return this.last.item;
}
/**
* Devuelve el índice de un objeto, si no existe, devuelve -1
*
* @param element elemento a buscar
* @return índice del objeto o -1
*/
public int indexOf(E element) {
int index = -1;
for (int i = 0; i < this.length; i++) {
// for (CustomNode<E> node = first; node != null; node = node.nextItem) {
CustomNode<E> node = searchNode(i);
if (element == null) {
if (node.item == null) {
index = i;
break;
}
} else {
if (element.equals(node.item)) {
index = i;
break;
}
}
}
return index;
}
/**
* Elimina un nodo
*
* @param node
*/
private void deleteNode(CustomNode<E> node) {
final E element = node.item;
final CustomNode<E> next = node.nextItem;
final CustomNode<E> prev = node.prevItem;
if (prev == null) {
this.first = next;
} else {
prev.nextItem = next;
node.prevItem = null;
}
if (next == null) {
this.last = prev;
} else {
next.prevItem = prev;
node.nextItem = null;
}
node.item = null;
this.length--;
}
/**
* Devuelve un nodo de una determinada posición
*
* @param index indice
* @return nodo
*/
private CustomNode<E> searchNode(int index) {
/*
* Si index < 0 cogeremos el primer elemento y si es mayor que la longitud
* cogeremos el último
*/
if (index < 0) {
index = 0;
}
if (index < this.length) {
CustomNode<E> aux = this.first;
for (int i = 0; i < index; i++) {
aux = aux.nextItem;
}
return aux;
} else {
CustomNode<E> aux = last;
return aux;
}
}
/**
* Clase CustomNode (nombrada así debido a que ya existe una similar en java)
* que usaremos para obtener los valores anterior y posterior de un determinado
* elemento de la lista
*
* @author Raider
*
* @param <E> Parámetro genéico para que nuestra lista pueda aceptar todos los
* tipos de parámetros
*/
private static class CustomNode<E> {
E item;
CustomNode<E> nextItem;
CustomNode<E> prevItem;
/**
* Constructor de CustomNode
*
* @param prev elemento anterior
* @param element elemento referenciado
* @param next elemento siguiente
*/
CustomNode(CustomNode<E> prev, E element, CustomNode<E> next) {
this.item = element;
this.nextItem = next;
this.prevItem = prev;
}
}
}
And here JUnit tests (surely the worst part)
package org.raider.junit.tests;
import static org.junit.Assert.assertEquals;
import static org.junit.jupiter.api.Assertions.assertThrows;
import org.aam.list.CustomDoubleLinkedList;
import org.junit.jupiter.api.Test;
class CustomDoubleLinkedListTest {
CustomDoubleLinkedList<String> strList;
@Test
public void addAndGetTest() {
strList = new CustomDoubleLinkedList();
strList.add("pos 0");
strList.add("pos 1");
strList.add("pos 2");
strList.add("pos 3");
assertEquals("pos 0", strList.get(0));
assertEquals("pos 1", strList.get(1));
assertEquals("pos 2", strList.get(2));
assertEquals("pos 3", strList.get(3));
}
@Test
public void outOfBounds() {
strList = new CustomDoubleLinkedList();
strList.add("pos 0");
strList.add("pos 1");
strList.add("pos 2");
strList.add("pos 3");
assertEquals("pos 3", strList.get(288));
assertEquals("pos 0", strList.get(-100));
}
@Test
public void isEmpty() {
strList = new CustomDoubleLinkedList();
assertEquals(true, strList.isEmpty());
strList.add("pos 0");
assertEquals(false, strList.isEmpty());
}
@Test
public void size() {
strList = new CustomDoubleLinkedList();
assertEquals(0, strList.size());
strList.add("pos 0");
strList.add("pos 1");
strList.add("pos 2");
strList.add("pos 3");
assertEquals(4, strList.size());
}
@Test
public void addAtBegin() {
strList = new CustomDoubleLinkedList();
strList.add("pos 0");
strList.add("pos 1");
strList.add("pos 2");
strList.add("pos 3");
assertEquals("pos 0", strList.get(0));
strList.addAtBegin("pos -1");
assertEquals("pos -1", strList.get(0));
assertEquals("pos 0", strList.get(1));
}
@Test
public void addAt() {
strList = new CustomDoubleLinkedList();
strList.add("pos 0");
strList.add("pos 1");
strList.add("pos 2");
strList.add("pos 3");
assertEquals("pos 0", strList.get(0));
assertEquals("pos 1", strList.get(1));
assertEquals("pos 2", strList.get(2));
assertEquals("pos 3", strList.get(3));
strList.addAt("pos x", 2);
assertEquals("pos 0", strList.get(0));
assertEquals("pos 1", strList.get(1));
assertEquals("pos x", strList.get(2));
assertEquals("pos 2", strList.get(3));
assertEquals("pos 3", strList.get(4));
assertEquals(5, strList.size());
strList.addAt("pos y", 0);
assertEquals("pos y", strList.get(0));
}
@Test
public void addAll() {
strList = new CustomDoubleLinkedList();
CustomDoubleLinkedList<String> listToAdd = new CustomDoubleLinkedList();
strList.add("pos 0");
strList.add("pos 1");
strList.add("pos 2");
strList.add("pos 3");
assertEquals("pos 0", strList.get(0));
assertEquals("pos 1", strList.get(1));
assertEquals("pos 2", strList.get(2));
assertEquals("pos 3", strList.get(3));
listToAdd.add("pos x");
listToAdd.add("pos y");
listToAdd.add("pos z");
assertEquals("pos x", listToAdd.get(0));
assertEquals("pos y", listToAdd.get(1));
assertEquals("pos z", listToAdd.get(2));
strList.addAll(2, listToAdd);
assertEquals("pos 0", strList.get(0));
assertEquals("pos 1", strList.get(1));
assertEquals("pos x", strList.get(2));
assertEquals("pos y", strList.get(3));
assertEquals("pos z", strList.get(4));
assertEquals("pos 2", strList.get(5));
assertEquals("pos 3", strList.get(6));
}
@Test
public void clear() {
strList = new CustomDoubleLinkedList();
strList.add("pos 0");
strList.add("pos 1");
strList.add("pos 2");
strList.add("pos 3");
assertEquals("pos 0", strList.get(0));
assertEquals("pos 1", strList.get(1));
assertEquals("pos 2", strList.get(2));
assertEquals("pos 3", strList.get(3));
assertEquals(4, strList.size());
strList.clear();
assertThrows(NullPointerException.class, () -> strList.get(0));
assertThrows(NullPointerException.class, () -> strList.get(1));
assertThrows(NullPointerException.class, () -> strList.get(2));
assertThrows(NullPointerException.class, () -> strList.get(3));
assertEquals(0, strList.size());
}
@Test
public void remove() {
strList = new CustomDoubleLinkedList();
strList.add("pos 0");
strList.add("pos 1");
strList.add("pos 2");
strList.add("pos 3");
assertEquals("pos 0", strList.get(0));
assertEquals("pos 1", strList.get(1));
assertEquals("pos 2", strList.get(2));
assertEquals("pos 3", strList.get(3));
assertEquals(4, strList.size());
strList.remove("pos 2");
assertEquals("pos 0", strList.get(0));
assertEquals("pos 1", strList.get(1));
assertEquals("pos 3", strList.get(2));
assertEquals(3, strList.size());
strList.add("pos 1");
strList.add("pos 4");
assertEquals("pos 0", strList.get(0));
assertEquals("pos 1", strList.get(1));
assertEquals("pos 3", strList.get(2));
assertEquals("pos 1", strList.get(3));
assertEquals("pos 4", strList.get(4));
assertEquals(5, strList.size());
strList.remove("pos 1");
assertEquals("pos 0", strList.get(0));
assertEquals("pos 3", strList.get(1));
assertEquals("pos 4", strList.get(2));
assertEquals(3, strList.size());
}
@Test
public void removeIndex() {
strList = new CustomDoubleLinkedList();
strList.add("pos 0");
strList.add("pos 1");
strList.add("pos 2");
strList.add("pos 3");
assertEquals("pos 0", strList.get(0));
assertEquals("pos 1", strList.get(1));
assertEquals("pos 2", strList.get(2));
assertEquals("pos 3", strList.get(3));
assertEquals(4, strList.size());
strList.removeIndex(2);
assertEquals("pos 0", strList.get(0));
assertEquals("pos 1", strList.get(1));
assertEquals("pos 3", strList.get(2));
assertEquals(3, strList.size());
}
@Test
public void getFirstAndLast() {
strList = new CustomDoubleLinkedList();
strList.add("pos 0");
strList.add("pos 1");
strList.add("pos 2");
strList.add("pos 3");
assertEquals("pos 0", strList.getFirst());
assertEquals("pos 3", strList.getLast());
}
@Test
public void indexOf() {
strList = new CustomDoubleLinkedList();
strList.add("pos 0");
strList.add("pos 1");
strList.add("pos 2");
strList.add("pos 3");
assertEquals(2, strList.indexOf("pos 2"));
}
}
Could someone try to explain me where I can improve this code, what I forgot or what is the most academic way to do this?
EDIT: I didn't implement or extend from Collection
or another interfaces/clasess because in the test it was specified that it would not be used