Skip to content

Commit 9d05db2

Browse files
feat(datastructures): add SelfOrganizingLinkedList implementation and tests (#7575)
* Add SquareFreeInteger to maths * fix clang-format issues * add newline * Add new test file in test * modified * delete * Add DisariumNumbers with test * feat(datastructures): add SelfOrganizingLinkedList implementation and tests * fix checkstyle formatting errors in SelfOrganizingLinkedList including test file. * Add new Line as per the format * fix(datastructures): prevent null dereference in SelfOrganizingLinkedList * format correction * format * build format * " * format finally * maybe * done * make corrections and add tests * format * build correction * done * pmd done --------- Co-authored-by: Deniz Altunkapan <deniz.altunkapan@outlook.com>
1 parent 65b977e commit 9d05db2

2 files changed

Lines changed: 224 additions & 0 deletions

File tree

Lines changed: 105 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,105 @@
1+
package com.thealgorithms.datastructures.lists;
2+
3+
import java.util.Objects;
4+
5+
/**
6+
* A Self-Organizing Linked List implementation using the Move-To-Front (MTF) strategy.
7+
* When an element is searched, it is automatically moved to the head of the list
8+
* to optimize subsequent lookups.
9+
*
10+
* @param <E> the type of elements held in this list
11+
*/
12+
public class SelfOrganizingLinkedList<E> {
13+
14+
/**
15+
* Node structure for the self-organizing linked list.
16+
*
17+
* @param <E> the type of element held in this node
18+
*/
19+
private static class Node<E> {
20+
E value;
21+
Node<E> next;
22+
23+
Node(E value) {
24+
this.value = value;
25+
this.next = null;
26+
}
27+
}
28+
29+
private Node<E> head;
30+
private int size;
31+
32+
public SelfOrganizingLinkedList() {
33+
this.size = 0;
34+
this.head = null;
35+
}
36+
37+
/**
38+
* Inserts a new value at the end of the list.
39+
*
40+
* @param value the element to add
41+
*/
42+
public void insert(E value) {
43+
Node<E> newNode = new Node<>(value);
44+
if (head == null) {
45+
head = newNode;
46+
} else {
47+
Node<E> temp = head;
48+
while (temp.next != null) {
49+
temp = temp.next;
50+
}
51+
temp.next = newNode;
52+
}
53+
size++;
54+
}
55+
56+
/**
57+
* Searches for a value in the list.
58+
* If found, moves the node to the front (head) of the list.
59+
*
60+
* @param key the value to search for
61+
* @return true if the element is present, false otherwise
62+
*/
63+
public boolean search(E key) {
64+
if (head == null) {
65+
return false;
66+
}
67+
// If the key is already at the head, no pointers need to be rewired
68+
if (Objects.equals(head.value, key)) {
69+
return true;
70+
}
71+
72+
Node<E> prev = head;
73+
Node<E> curr = head.next;
74+
75+
while (curr != null && !Objects.equals(curr.value, key)) {
76+
prev = curr;
77+
curr = curr.next;
78+
}
79+
80+
if (curr == null) {
81+
return false;
82+
}
83+
84+
// Unlink curr from its current position and move it to head
85+
prev.next = curr.next;
86+
curr.next = head;
87+
head = curr;
88+
return true;
89+
}
90+
91+
/** Gets the current head value of the list. */
92+
public E getHeadValue() {
93+
return head != null ? head.value : null;
94+
}
95+
96+
/** Returns the size of the list. */
97+
public int getSize() {
98+
return size;
99+
}
100+
101+
/** Returns true if the list contains no elements. */
102+
public boolean isEmpty() {
103+
return size == 0;
104+
}
105+
}
Lines changed: 119 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,119 @@
1+
package com.thealgorithms.datastructures.lists;
2+
3+
import static org.junit.jupiter.api.Assertions.assertEquals;
4+
import static org.junit.jupiter.api.Assertions.assertFalse;
5+
import static org.junit.jupiter.api.Assertions.assertNull;
6+
import static org.junit.jupiter.api.Assertions.assertTrue;
7+
8+
import org.junit.jupiter.api.BeforeEach;
9+
import org.junit.jupiter.api.Test;
10+
11+
public class SelfOrganizingLinkedListTest {
12+
13+
private SelfOrganizingLinkedList<Integer> list;
14+
15+
@BeforeEach
16+
void setUp() {
17+
list = new SelfOrganizingLinkedList<>();
18+
}
19+
20+
@Test
21+
void testEmptyListAndGetters() {
22+
assertTrue(list.isEmpty());
23+
assertEquals(0, list.getSize());
24+
assertNull(list.getHeadValue());
25+
assertFalse(list.search(10));
26+
}
27+
28+
@Test
29+
void testInsertAndSizeState() {
30+
assertTrue(list.isEmpty());
31+
list.insert(10);
32+
assertFalse(list.isEmpty());
33+
assertEquals(1, list.getSize());
34+
35+
list.insert(20);
36+
assertEquals(2, list.getSize());
37+
}
38+
39+
@Test
40+
void testMoveMiddleElementToFrontPreservesFullListStructure() {
41+
list.insert(10);
42+
list.insert(20);
43+
list.insert(30);
44+
list.insert(40);
45+
46+
// Initial order: [10, 20, 30, 40]
47+
assertTrue(list.search(30));
48+
49+
// Expected order: [30, 10, 20, 40]
50+
assertEquals(4, list.getSize());
51+
assertEquals(30, list.getHeadValue());
52+
53+
// Sequential head tracking to verify middle and tail pointers didn't break
54+
assertTrue(list.search(10)); // [10, 30, 20, 40]
55+
assertEquals(10, list.getHeadValue());
56+
57+
assertTrue(list.search(20)); // [20, 10, 30, 40]
58+
assertEquals(20, list.getHeadValue());
59+
60+
assertTrue(list.search(40)); // [40, 20, 10, 30]
61+
assertEquals(40, list.getHeadValue());
62+
assertEquals(4, list.getSize());
63+
}
64+
65+
@Test
66+
void testMoveLastElementToFrontPreservesFullListStructure() {
67+
list.insert(10);
68+
list.insert(20);
69+
list.insert(30);
70+
71+
// Search tail element '30'
72+
assertTrue(list.search(30)); // Order becomes [30, 10, 20]
73+
74+
assertEquals(30, list.getHeadValue());
75+
assertEquals(3, list.getSize());
76+
77+
// Verify remaining chain order [10, 20]
78+
assertTrue(list.search(20)); // [20, 30, 10]
79+
assertEquals(20, list.getHeadValue());
80+
81+
assertTrue(list.search(10)); // [10, 20, 30]
82+
assertEquals(10, list.getHeadValue());
83+
assertEquals(3, list.getSize());
84+
}
85+
86+
@Test
87+
void testSearchNonExistentElementPreservesStructureAndSize() {
88+
list.insert(10);
89+
list.insert(20);
90+
list.insert(30);
91+
92+
assertFalse(list.search(99));
93+
assertEquals(3, list.getSize());
94+
assertEquals(10, list.getHeadValue());
95+
}
96+
97+
@Test
98+
void testDuplicateValuesMovesFirstMatchedToFront() {
99+
list.insert(10);
100+
list.insert(20);
101+
list.insert(10); // Duplicate '10' at tail
102+
list.insert(30);
103+
104+
// Initial list state: [10, 20, 10, 30]
105+
// Searching '10' hits the head immediately -> no re-linking
106+
assertTrue(list.search(10));
107+
assertEquals(10, list.getHeadValue());
108+
assertEquals(4, list.getSize());
109+
110+
// Searching '20' moves middle element to head: [20, 10, 10, 30]
111+
assertTrue(list.search(20));
112+
assertEquals(20, list.getHeadValue());
113+
114+
// Searching '10' moves the FIRST instance of '10' (index 1) to head: [10, 20, 10, 30]
115+
assertTrue(list.search(10));
116+
assertEquals(10, list.getHeadValue());
117+
assertEquals(4, list.getSize());
118+
}
119+
}

0 commit comments

Comments
 (0)