Skip to content
Open
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
Original file line number Diff line number Diff line change
@@ -0,0 +1,98 @@
package com.thealgorithms.datastructures.lists;

import java.util.Objects;

/**
* Node structure for the generic linked list.
*
* @param <E> the type of element held in this node
*/
class LinkedList<E> {

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Calling the node class LinkedList is confusing, it's a node, not a list, and it clashes with java.util.LinkedList. The repo already has a convention for this (SinglyLinkedListNode). Could you rename it to Node and make it a static nested class inside SelfOrganizingLinkedList? That also keeps it to one top-level class per file, which is what the other list files do.

E value;
LinkedList<E> next;

LinkedList(E value) {
this.value = value;
this.next = null;
}
}

/**
* A Self-Organizing Linked List implementation using the Move-To-Front (MTF) strategy.
* When an element is searched, it is automatically moved to the head of the list
* to optimize subsequent lookups.
*
* @param <E> the type of elements held in this list
*/
public class SelfOrganizingLinkedList<E> {
private LinkedList<E> head;
private int size;

public SelfOrganizingLinkedList() {
this.size = 0;
this.head = null;
}

/** Inserts a new value at the end of the list. */
public void insert(E value) {
LinkedList<E> newNode = new LinkedList<>(value);
if (head == null) {
head = newNode;
} else {
LinkedList<E> temp = head;
while (temp.next != null) {
temp = temp.next;
}
temp.next = newNode;
}
size++;
}

/**
* Searches for a value in the list.
* If found, moves the node to the front (head) of the list.
*
* @param key the value to search for
* @return true if the element is present, false otherwise
*/
public boolean search(E key) {
if (head == null) {
return false;
}
if (Objects.equals(head.value, key)) {
return true;
}

LinkedList<E> prev = head;
LinkedList<E> curr = head.next;

while (curr != null && !Objects.equals(curr.value, key)) {
prev = curr;
curr = curr.next;
}

if (curr == null) {
return false;
}

prev.next = curr.next;
curr.next = head;
head = curr;
return true;
}

/** Gets the current head of the list. */
public E getHeadValue() {
return head != null ? head.value : null;
}

/** Returns the size of the list. */
public int getSize() {
return size;
}

/** Returns true if the list contains no elements. */
public boolean isEmpty() {
return head == null;

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Codecov flags two uncovered lines, these getters (getSize / isEmpty) look like the culprits. Worth a small test so the patch is fully covered.

}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,85 @@
package com.thealgorithms.datastructures.lists;

import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertFalse;
import static org.junit.jupiter.api.Assertions.assertNull;
import static org.junit.jupiter.api.Assertions.assertTrue;

import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;

public class SelfOrganizingLinkedListTest {
private SelfOrganizingLinkedList<Integer> list;

@BeforeEach
void setUp() {
list = new SelfOrganizingLinkedList<>();
}

@Test
void testSearchOnEmptyList() {
assertFalse(list.search(10));
assertNull(list.getHeadValue());
}

@Test
void testSearchElementAtHeadValueDoesNotChangeStructure() {
list.insert(10);
list.insert(20);
list.insert(30);

assertTrue(list.search(10));
assertEquals(10, list.getHeadValue());
}

@Test
void testMoveMiddleElementToFront() {
list.insert(10);
list.insert(20);
list.insert(30);
list.insert(40);

// Search middle element '30'
assertTrue(list.search(30));

// '30' should now be the new head
assertEquals(30, list.getHeadValue());

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The tests only check getHeadValue, so they confirm the searched element ends up at the front but never that the rest of the list survives. A broken move-to-front (lost node, cycle, wrong order) would still pass all of these. Could you add a test that walks the whole list after a search and asserts the full order plus unchanged size? A duplicate-value case would be good too.

}

@Test
void testMoveLastElementToFront() {
list.insert(10);
list.insert(20);
list.insert(30);

// Search last element '30'
assertTrue(list.search(30));

assertEquals(30, list.getHeadValue());
}

@Test
void testSearchNonExistentElement() {
list.insert(10);
list.insert(20);

assertFalse(list.search(99));
assertEquals(10, list.getHeadValue()); // Head remains unchanged
}

@Test
void testMultipleSearchesSequentialMoveToFront() {
list.insert(1);
list.insert(2);
list.insert(3);

list.search(2); // Head becomes 2
assertEquals(2, list.getHeadValue());

list.search(3); // Head becomes 3
assertEquals(3, list.getHeadValue());

list.search(1); // Head becomes 1
assertEquals(1, list.getHeadValue());
}
}
Loading