-
Notifications
You must be signed in to change notification settings - Fork 21.3k
feat(datastructures): add SelfOrganizingLinkedList implementation and tests #7575
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: master
Are you sure you want to change the base?
Changes from all commits
0c85933
488fa21
c83cd4f
2156b67
3e5e1ab
d3c1360
28bfe86
90c0e87
259db0d
4809f82
88d9ba2
6e39000
beb01b2
5b513e6
2905459
7d05132
c71cec2
3db080e
3cab177
67df287
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| 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> { | ||
| 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; | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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()); | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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()); | ||
| } | ||
| } | ||
There was a problem hiding this comment.
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.