diff --git a/src/Collection/LinkedList.php b/src/Collection/LinkedList.php index e69de29..f20d431 100644 --- a/src/Collection/LinkedList.php +++ b/src/Collection/LinkedList.php @@ -0,0 +1,154 @@ + + * @license MIT + * + * @see https://kariricode.org/ + */ +class LinkedList implements Collection +{ + private ?Node $head = null; + private ?Node $tail = null; + private int $size = 0; + + public function add(mixed $element): void + { + $newNode = new Node($element, $this->tail); + if ($this->tail) { + $this->tail->next = $newNode; + } else { + $this->head = $newNode; + } + $this->tail = $newNode; + ++$this->size; + } + + public function addAll(Collection $collection): void + { + foreach ($collection->getItems() as $element) { + $this->add($element); + } + } + + public function remove(mixed $element): bool + { + $current = $this->head; + while (null !== $current) { + if ($current->data === $element) { + if ($current->prev) { + $current->prev->next = $current->next; + } else { + $this->head = $current->next; + } + if ($current->next) { + $current->next->prev = $current->prev; + } else { + $this->tail = $current->prev; + } + --$this->size; + + return true; + } + $current = $current->next; + } + + return false; + } + + public function contains(mixed $element): bool + { + $current = $this->head; + while (null !== $current) { + if ($current->data === $element) { + return true; + } + $current = $current->next; + } + + return false; + } + + public function clear(): void + { + $this->head = null; + $this->tail = null; + $this->size = 0; + } + + public function isEmpty(): bool + { + return 0 === $this->size; + } + + public function getItems(): array + { + $items = []; + $current = $this->head; + while (null !== $current) { + $items[] = $current->data; + $current = $current->next; + } + + return $items; + } + + public function count(): int + { + return $this->size; + } + + public function get(int $index): mixed + { + if ($index < 0 || $index >= $this->size) { + throw new \OutOfRangeException("Index out of range: $index"); + } + $current = $this->head; + for ($i = 0; $i < $index; ++$i) { + $current = $current->next; + } + + return $current->data; + } + + public function set(int $index, mixed $element): void + { + if ($index < 0 || $index >= $this->size) { + throw new \OutOfRangeException("Index out of range: $index"); + } + $current = $this->head; + for ($i = 0; $i < $index; ++$i) { + $current = $current->next; + } + $current->data = $element; + } + + /** + * Clone method to ensure deep copy of nodes + */ + public function __clone() + { + $newList = new LinkedList(); + $current = $this->head; + while ($current !== null) { + $newList->add($current->data); + $current = $current->next; + } + $this->head = $newList->head; + $this->tail = $newList->tail; + } +} diff --git a/src/Node.php b/src/Node.php index e69de29..2974f91 100644 --- a/src/Node.php +++ b/src/Node.php @@ -0,0 +1,15 @@ +add(1); + $this->assertSame([1], $list->getItems()); + } + + // Test adding all elements from another collection + public function testAddAllElementsAddsAllElementsFromAnotherCollection(): void + { + $list1 = new LinkedList(); + $list1->add(1); + $list1->add(2); + + $list2 = new LinkedList(); + $list2->addAll($list1); + + $this->assertSame([1, 2], $list2->getItems()); + } + + // Test removing an element from the list + public function testRemoveElementRemovesElementFromList(): void + { + $list = new LinkedList(); + $list->add(1); + $list->add(2); + + $this->assertTrue($list->remove(1)); + $this->assertFalse($list->remove(3)); + $this->assertSame([2], $list->getItems()); + } + + // Test checking if the list contains an element + public function testContainsElementReturnsTrueIfElementExists(): void + { + $list = new LinkedList(); + $list->add(1); + + $this->assertTrue($list->contains(1)); + $this->assertFalse($list->contains(2)); + } + + // Test clearing all elements from the list + public function testClearElementsRemovesAllElementsFromList(): void + { + $list = new LinkedList(); + $list->add(1); + $list->clear(); + + $this->assertTrue($list->isEmpty()); + $this->assertSame([], $list->getItems()); + } + + // Test checking if the list is empty + public function testIsEmptyReturnsTrueIfListIsEmpty(): void + { + $list = new LinkedList(); + $this->assertTrue($list->isEmpty()); + + $list->add(1); + $this->assertFalse($list->isEmpty()); + } + + // Test getting all items from the list + public function testGetItemsReturnsAllElementsInList(): void + { + $list = new LinkedList(); + $list->add(1); + $list->add(2); + + $this->assertSame([1, 2], $list->getItems()); + } + + // Test counting the elements in the list + public function testCountElementsReturnsNumberOfElementsInList(): void + { + $list = new LinkedList(); + $this->assertSame(0, $list->count()); + + $list->add(1); + $list->add(2); + $this->assertSame(2, $list->count()); + } + + // Test getting an element by its index + public function testGetElementByIndexThrowsExceptionForInvalidIndex(): void + { + $this->expectException(\OutOfRangeException::class); + + $list = new LinkedList(); + $list->add(1); + $list->add(2); + + $this->assertSame(1, $list->get(0)); + $this->assertSame(2, $list->get(1)); + $list->get(2); // This should throw an exception + } + + // Test setting an element by its index + public function testSetElementByIndexThrowsExceptionForInvalidIndex(): void + { + $this->expectException(\OutOfRangeException::class); + + $list = new LinkedList(); + $list->add(1); + $list->add(2); + + $list->set(1, 3); + $this->assertSame(3, $list->get(1)); + $list->set(2, 4); // This should throw an exception + } + + // Test iterating over elements in the list + public function testIterationOverElementsReturnsAllElements(): void + { + $list = new LinkedList(); + $list->add(1); + $list->add(2); + $list->add(3); + + $items = []; + foreach ($list->getItems() as $item) { + $items[] = $item; + } + + $this->assertSame([1, 2, 3], $items); + } + + // Test adding different data types to the list + public function testDifferentDataTypesCanBeAddedToList(): void + { + $list = new LinkedList(); + $list->add('string'); + $list->add(123); + $list->add(45.6); + $list->add(['key' => 'value']); + $list->add(new \stdClass()); + + $this->assertSame('string', $list->get(0)); + $this->assertSame(123, $list->get(1)); + $this->assertSame(45.6, $list->get(2)); + $this->assertSame(['key' => 'value'], $list->get(3)); + $this->assertInstanceOf(\stdClass::class, $list->get(4)); + } + + // Test adding and removing a large number of elements + public function testMassAddAndRemoveHandlesLargeNumberOfElements(): void + { + $list = new LinkedList(); + + for ($i = 0; $i < 1000; ++$i) { + $list->add($i); + } + + $this->assertSame(1000, $list->count()); + + for ($i = 0; $i < 1000; ++$i) { + $list->remove($i); + } + + $this->assertTrue($list->isEmpty()); + } + + // Test index consistency after removing an element + public function testIndexConsistencyAfterRemoval(): void + { + $list = new LinkedList(); + $list->add(1); + $list->add(2); + $list->add(3); + + $list->remove(2); + + $this->assertSame(1, $list->get(0)); + $this->assertSame(3, $list->get(1)); + + $this->expectException(\OutOfRangeException::class); + $list->get(2); + } + + // Test accessing an element by an out-of-bounds index + public function testOutOfBoundsAccessThrowsException(): void + { + $this->expectException(\OutOfRangeException::class); + + $list = new LinkedList(); + $list->add(1); + $list->get(1); // This should throw an exception + } + + // Test setting an element by an out-of-bounds index + public function testOutOfBoundsSetThrowsException(): void + { + $this->expectException(\OutOfRangeException::class); + + $list = new LinkedList(); + $list->add(1); + $list->set(1, 2); // This should throw an exception + } + + // Test removing a non-existent element + public function testOutOfBoundsRemoveReturnsFalse(): void + { + $list = new LinkedList(); + $list->add(1); + + $this->assertFalse($list->remove(2)); // Attempt to remove a non-existent element + } + + // Test handling null values in the list + public function testHandlingNullValuesCorrectly(): void + { + $list = new LinkedList(); + $list->add(null); + + $this->assertTrue($list->contains(null)); + $this->assertTrue($list->remove(null)); + $this->assertFalse($list->contains(null)); + } + + // Test serializing and deserializing the list + public function testSerializationAndDeserialization(): void + { + $list = new LinkedList(); + $list->add(1); + $list->add(2); + + $serialized = serialize($list); + $unserializedList = unserialize($serialized); + + $this->assertEquals($list->getItems(), $unserializedList->getItems()); + } + + // Test cloning the list + public function testCloningCreatesIndependentCopy(): void + { + $list = new LinkedList(); + $list->add(1); + $list->add(2); + + $clonedList = clone $list; + + $this->assertEquals($list->getItems(), $clonedList->getItems()); + + // Modify the cloned list and ensure the original list remains unchanged + $clonedList->add(3); + $this->assertNotEquals($list->getItems(), $clonedList->getItems()); + $this->assertEquals([1, 2], $list->getItems()); + $this->assertEquals([1, 2, 3], $clonedList->getItems()); + } + + // Test list integrity after a sequence of mixed operations + public function testListIntegrityAfterMixedOperations(): void + { + $list = new LinkedList(); + $list->add(1); + $list->add(2); + $list->remove(1); + $list->add(3); + $list->clear(); + $list->add(4); + $list->set(0, 5); + + $this->assertSame([5], $list->getItems()); + } +}