diff --git a/README.md b/README.md index 2b02ecf..28bbcfc 100644 --- a/README.md +++ b/README.md @@ -2,11 +2,56 @@ [![PHPUnit Tests](https://github.com/cmatosbc/daedalus/actions/workflows/phpunit.yml/badge.svg)](https://github.com/cmatosbc/daedalus/actions/workflows/phpunit.yml) [![PHP Composer](https://github.com/cmatosbc/daedalus/actions/workflows/composer.yml/badge.svg)](https://github.com/cmatosbc/daedalus/actions/workflows/composer.yml) [![PHP Lint](https://github.com/cmatosbc/daedalus/actions/workflows/lint.yml/badge.svg)](https://github.com/cmatosbc/daedalus/actions/workflows/lint.yml) +## Quick Navigation +- [Overview](#overview) +- [Installation](#installation) +- [Features](#features) +- [Data Structures](#data-structures) + - [Dictionary](#dictionary) + - [MultiMap](#multimap) + - [SortedMap](#sortedmap) + - [Set](#set) + - [DisjointSet](#disjointset) + - [HashSet](#hashset) + - [TreeSet](#treeset) + - [Matrix](#matrix) + - [Enhanced Array Object](#enhanced-array-object) +- [Use Cases](#why-use-daedalus) + - [E-commerce Product Catalogs](#1-e-commerce-product-catalogs) + - [User Management Systems](#2-user-management-systems) + - [Financial Applications](#3-financial-applications) + - [Content Management Systems](#4-content-management-systems) + - [API Response Handling](#5-api-response-handling) + - [Configuration Management](#6-configuration-management) +- [Advanced Usage](#advanced-usage) +- [Error Handling](#error-handling) +- [Contributing](#contributing) +- [License](#license) + +## Overview + Daedalus is a powerful PHP library that provides advanced data structures and utilities for modern PHP applications. At its core, it offers an enhanced version of PHP's ArrayObject with additional features like type safety, event handling, immutability options, and a PSR-11 compliant dependency injection container. The library is designed with a focus on type safety, immutability, and event-driven architecture, making it an ideal choice for building robust and maintainable applications. -License: [GNU General Public License v3.0](https://www.gnu.org/licenses/gpl-3.0.en.html) +## Installation + +```bash +composer require cmatosbc/daedalus +``` + +## Features + +- Type safety for array elements +- Event handling for array modifications +- Immutable array support +- Array manipulation methods (map, filter, reduce) +- Deep cloning capability +- Array merging with type checking +- Event listener management +- PSR-11 compliant dependency injection container +- Singleton pattern support +- Automatic dependency resolution ## Why Use Daedalus? @@ -48,499 +93,163 @@ This library is particularly useful in scenarios where you need robust array han - Maintain type-safe option collections - Merge configuration from multiple sources -The library combines the power of PHP's ArrayObject with modern programming practices like immutability, type safety, and event-driven architecture, making it a valuable tool for building robust and maintainable applications. - -## Features - -- Type safety for array elements -- Event handling for array modifications -- Immutable array support -- Array manipulation methods (map, filter, reduce) -- Deep cloning capability -- Array merging with type checking -- Event listener management -- PSR-11 compliant dependency injection container -- Singleton pattern support -- Automatic dependency resolution -- C# like Dictionary class - -## Installation - -```bash -composer require cmatosbc/daedalus -``` - ## Data Structures ### Dictionary +A type-safe key-value collection with: +- Strict type enforcement +- Iteration capabilities +- Serialization support +- Ideal for configurations and mappings -The `Dictionary` class provides a robust key-value collection with type safety and iteration capabilities. It implements `Iterator`, `Countable`, and `Serializable` interfaces, offering a comprehensive solution for managing key-value pairs. - -#### Real-World Use Cases - -1. Configuration Management - - Store application settings with typed values - - Manage environment-specific configurations - - Handle feature flags and toggles - -2. HTTP Headers Management - - Store and manipulate HTTP headers - - Handle custom header mappings - - Normalize header names and values - -3. User Session Data - - Store user preferences - - Manage shopping cart items - - Cache user-specific settings - -4. Language Localization - - Map language keys to translations - - Handle multiple locale configurations - - Store message templates - -5. Cache Implementation - - Store computed results with unique keys - - Implement memory-efficient caching - - Manage cache invalidation - +Example: ```php use Daedalus\Dictionary; -// Create a new dictionary $dict = new Dictionary(); - -// Add key-value pairs $dict->add('name', 'John'); $dict->add('age', 30); -// Get values $name = $dict->get('name'); // Returns 'John' - -// Check if key exists -if ($dict->containsKey('age')) { - // Update value - $dict->update('age', 31); -} - -// Remove a key -$dict->remove('name'); - -// Get all keys or values -$keys = $dict->keys(); // Returns ['age'] -$values = $dict->values(); // Returns [31] - -// Iterate over dictionary -foreach ($dict as $key => $value) { - echo "$key: $value\n"; -} - -// Clear all items -$dict->clear(); ``` -### Set +### MultiMap +A map that allows multiple values per key: +- Store multiple values for a single key +- Maintain insertion order +- Type-safe value collections +- Ideal for tags, categories, and relationships -The `Set` class implements a collection of unique values with standard set operations. It provides methods for union, intersection, difference, and subset operations, making it ideal for mathematical set operations and managing unique collections. +Example: +```php +use Daedalus\MultiMap; -#### Real-World Use Cases +$tags = new MultiMap(); +$tags->add('article1', 'php'); +$tags->add('article1', 'programming'); +$tags->get('article1'); // ['php', 'programming'] -1. User Permissions and Roles - - Store user capabilities - - Manage role assignments - - Calculate effective permissions through set operations - - Check required permission sets +// Bulk operations +$tags->addAll('article2', ['web', 'tutorial']); +$tags->remove('article1', 'php'); +$tags->removeAll('article2'); -2. Social Network Connections - - Manage friend lists - - Find mutual friends (intersection) - - Suggest new connections (difference) - - Track group memberships +// Check contents +$hasTag = $tags->contains('article1', 'programming'); // true +$count = $tags->count(); // Total number of key-value pairs +``` -3. Product Categories and Tags - - Manage product classifications - - Handle multiple category assignments - - Find products with common tags - - Calculate related products +### SortedMap +A map that maintains its keys in sorted order: +- Automatic key sorting +- Customizable sort order +- Type-safe values +- Efficient range operations -4. Event Management - - Track event attendees - - Manage waiting lists - - Find common participants between events - - Handle group registrations +Example: +```php +use Daedalus\SortedMap; + +$scores = new SortedMap(); +$scores['alice'] = 95; +$scores['bob'] = 87; +$scores['carol'] = 92; + +// Keys are automatically sorted +foreach ($scores as $name => $score) { + echo "$name: $score\n"; +} // Outputs in alphabetical order + +// Range operations +$topScores = $scores->subMap('alice', 'bob'); // Get scores from alice to bob +$highScores = $scores->tailMap(90); // Get all scores >= 90 +$lowScores = $scores->headMap(90); // Get all scores < 90 + +// Find adjacent entries +$nextStudent = $scores->higherKey('bob'); // 'carol' +$prevStudent = $scores->lowerKey('carol'); // 'bob' +``` -5. Data Deduplication - - Remove duplicate records - - Track unique visitors - - Manage email subscription lists - - Handle unique identifier collections +### Set +Collections of unique values with: +- Set operations (union, intersection) +- Order maintenance (TreeSet) +- Type safety +- Efficient lookups +Example: ```php use Daedalus\Set; -// Create new sets $set1 = new Set([1, 2, 3]); $set2 = new Set([2, 3, 4]); -// Add and remove items -$set1->add(5); // Returns true (added) -$set1->add(1); // Returns false (already exists) -$set1->remove(1); // Returns true (removed) -$set1->remove(10); // Returns false (didn't exist) - -// Check for existence -$exists = $set1->contains(2); // Returns true - -// Set operations -$union = $set1->union($set2); // {2, 3, 4, 5} +$union = $set1->union($set2); // {1, 2, 3, 4} $intersection = $set1->intersection($set2); // {2, 3} -$difference = $set1->difference($set2); // {5} - -// Check subset relationship -$isSubset = $set1->isSubsetOf($set2); // Returns false - -// Convert to array -$array = $set1->toArray(); // [2, 3, 5] - -// Iterate over set -foreach ($set1 as $item) { - echo $item . "\n"; -} - -// Clear all items -$set1->clear(); ``` -The Set class features: -- Unique value storage -- Standard set operations (union, intersection, difference) -- Subset checking -- Object and scalar value support -- Iterator implementation for foreach loops -- Serialization support - ### DisjointSet - -The `DisjointSet` class extends `Set` to provide an efficient implementation of disjoint sets (union-find data structure) with path compression and union by rank optimizations. It's particularly useful for managing non-overlapping groups and determining connectivity between elements. - -#### Real-World Use Cases - -1. Social Network Analysis - - Track friend groups and communities - - Detect connected components in social graphs - - Analyze information spread patterns - - Identify isolated user clusters - -2. Network Infrastructure - - Monitor network connectivity - - Detect network partitions - - Manage redundant connections - - Track service dependencies - -3. Image Processing - - Connected component labeling - - Region segmentation - - Object detection - - Pixel clustering - -4. Game Development - - Track team/alliance memberships - - Manage territory control - - Handle resource ownership - - Implement faction systems - -5. Distributed Systems - - Partition management - - Cluster state tracking - - Service discovery - - Consensus group management - -```php -use Daedalus\DisjointSet; - -// Create a disjoint set -$ds = new DisjointSet(); - -// Create individual sets -$ds->makeSet("A"); -$ds->makeSet("B"); -$ds->makeSet("C"); -$ds->makeSet("D"); - -// Join sets together -$ds->union("A", "B"); // Now A and B are in the same set -$ds->union("C", "D"); // Now C and D are in the same set -$ds->union("B", "C"); // Now all elements are in the same set - -// Check if elements are connected -$connected = $ds->connected("A", "D"); // Returns true - -// Find the representative element of a set -$rep = $ds->find("B"); // Returns the set's representative - -// Get all elements in the same set -$set = $ds->getSet("A"); // Returns ["A", "B", "C", "D"] - -// Count number of disjoint sets -$count = $ds->countSets(); // Returns 1 - -// Clear all sets -$ds->clear(); -``` - -The DisjointSet class features: -- Efficient union and find operations (O(α(n)), where α is the inverse Ackermann function) +A specialized set implementation for managing non-overlapping groups: +- Efficient union-find operations - Path compression optimization -- Union by rank optimization -- Set connectivity checking -- Set membership queries -- Multiple set management +- Group membership tracking +- Connected component analysis ### HashSet - -The `HashSet` class extends `Set` to provide constant-time performance for basic operations. It uses hash-based storage with no guarantee of iteration order, similar to Java's HashSet. - -#### Real-World Use Cases - -1. Caching Systems - - Store cache keys - - Track recently accessed items - - Manage unique identifiers - - Handle session tokens - -2. Data Validation - - Track processed records - - Validate unique entries - - Filter duplicate submissions - - Check for existing values - -3. Analytics and Tracking - - Track unique visitors - - Monitor unique events - - Store distinct metrics - - Log unique errors - -```php -use Daedalus\HashSet; - -// Create a new HashSet with custom load factor and capacity -$set = new HashSet([], 0.75, 16); - -// Add elements -$set->add("apple"); -$set->add("banana"); -$set->add("apple"); // Returns false (already exists) - -// Bulk operations with another set -$otherSet = new HashSet(["banana", "cherry"]); -$set->addAll($otherSet); // Add all elements from otherSet -$set->removeAll($otherSet); // Remove all elements that exist in otherSet -$set->retainAll($otherSet); // Keep only elements that exist in otherSet - -// Check contents -$exists = $set->contains("apple"); // Returns true -$count = $set->count(); // Get number of elements - -// Convert to array (no guaranteed order) -$array = $set->toArray(); - -// Clear the set -$set->clear(); -``` +An unordered set implementation with constant-time operations: +- O(1) add/remove/contains operations +- No order guarantee +- Memory-efficient storage +- High performance for large datasets ### TreeSet - -The `TreeSet` class implements a self-balancing binary search tree (AVL tree) that maintains elements in sorted order, similar to Java's TreeSet. - -#### Real-World Use Cases - -1. Ranking Systems - - Leaderboard management - - Score tracking - - Priority queues - - Tournament rankings - -2. Time-based Operations - - Event scheduling - - Task prioritization - - Deadline management - - Log entry ordering - -3. Range Queries - - Price range searches - - Date range filtering - - Numeric range queries - - Version management - -```php -use Daedalus\TreeSet; - -// Create a new TreeSet -$set = new TreeSet([3, 1, 4, 1, 5]); // Duplicates are automatically removed - -// Add elements (maintains order) -$set->add(2); -$set->add(6); - -// Access ordered elements -$first = $set->first(); // Get smallest element (1) -$last = $set->last(); // Get largest element (6) - -// Find adjacent elements -$lower = $set->lower(4); // Get largest element < 4 (3) -$higher = $set->higher(4); // Get smallest element > 4 (5) - -// Check contents -$exists = $set->contains(3); // Returns true -$count = $set->count(); // Get number of elements - -// Convert to sorted array -$array = $set->toArray(); // [1, 2, 3, 4, 5, 6] - -// Iterate in order -foreach ($set as $element) { - echo $element . "\n"; -} - -// Clear the set -$set->clear(); -``` - -The TreeSet class features: -- Ordered element storage -- Logarithmic-time operations (O(log n)) -- Efficient range operations -- Natural ordering for scalar types -- Custom ordering via __toString for objects -- AVL tree self-balancing +An ordered set implementation using a self-balancing tree: +- Maintains elements in sorted order +- O(log n) operations +- Range query support +- Ordered iteration ### Matrix +A 2D array implementation with: +- Row and column operations +- Mathematical operations +- Type-safe elements +- Efficient traversal -The `Matrix` class provides a robust implementation for matrix operations in PHP, supporting various mathematical operations and transformations commonly used in linear algebra, computer graphics, and scientific computing. - -#### Features - -- Basic matrix operations (addition, subtraction, multiplication) -- Scalar multiplication -- Matrix transposition -- Determinant calculation -- Identity and zero matrix creation -- Row and column access -- Input validation and error handling - -#### Basic Usage - +Example: ```php use Daedalus\Matrix; -// Create a 2x2 matrix -$matrix1 = new Matrix([ +$matrix = new Matrix([ [1, 2], [3, 4] ]); -// Create another 2x2 matrix -$matrix2 = new Matrix([ - [5, 6], - [7, 8] -]); - -// Matrix addition -$sum = $matrix1->add($matrix2); - -// Matrix multiplication -$product = $matrix1->multiply($matrix2); - -// Scale matrix -$scaled = $matrix1->scale(2); - -// Get determinant -$det = $matrix1->determinant(); - -// Create special matrices -$identity = Matrix::identity(3); // 3x3 identity matrix -$zero = Matrix::zero(2, 3); // 2x3 zero matrix -``` - -#### Advanced Operations - -```php -// Matrix transposition -$matrix = new Matrix([ - [1, 2, 3], - [4, 5, 6] -]); $transposed = $matrix->transpose(); - -// Access and modify elements -$value = $matrix->get(0, 1); // Get element at row 0, column 1 -$matrix->set(1, 2, 10); // Set element at row 1, column 2 - -// Get matrix properties -$rows = $matrix->getRows(); // Number of rows -$cols = $matrix->getCols(); // Number of columns ``` -#### Real-World Use Cases - -1. Scientific Computing - - Solving systems of linear equations - - Statistical calculations - - Data transformations - - Numerical analysis - -2. Computer Graphics - - 2D/3D transformations - - Image processing - - Game development - - Animation calculations - -3. Machine Learning - - Feature transformation - - Covariance matrices - - Principal Component Analysis - - Neural network calculations - -4. Financial Applications - - Portfolio optimization - - Risk analysis - - Asset correlation matrices - - Time series analysis - -5. Engineering Applications - - Structural analysis - - Circuit calculations - - Signal processing - - Control systems - -#### Performance Considerations - -- Efficient implementation for basic operations -- Optimized memory usage -- Input validation for matrix compatibility -- Exception handling for invalid operations -- Type safety for numeric operations - ### Enhanced Array Object +An improved version of PHP's ArrayObject with: +- Type safety for elements +- Event handling for modifications +- Array manipulation (map, filter, reduce) +- Deep cloning and merging -A PHP library that provides an enhanced version of PHP's ArrayObject with additional features like type safety, event handling, immutability options, and a PSR-11 compliant dependency injection container. - +Example: ```php use Daedalus\EnhancedArrayObject; -// Create a basic array object $array = new EnhancedArrayObject([1, 2, 3]); +$array->addEventListener('set', function($key, $value) { + echo "Element set at key $key with value $value\n"; +}); -// Add elements -$array->offsetSet(null, 4); // Appends 4 -$array[5] = 6; // Array access syntax - -// Remove elements -$array->offsetUnset(0); -unset($array[1]); // Array access syntax +$doubled = $array->map(fn($n) => $n * 2); ``` -### Type Safety +## Advanced Usage +### Type Safety ```php use Daedalus\EnhancedArrayObject; @@ -548,220 +257,28 @@ class User { public function __construct(public string $name) {} } -// Create a typed array that only accepts User objects $users = new EnhancedArrayObject([], User::class); - -// This works -$users[] = new User("John"); - -// This throws InvalidArgumentException -$users[] = "Not a user object"; +$users[] = new User("John"); // Works +$users[] = "Not a user"; // Throws InvalidArgumentException ``` ### Event Handling - ```php -use Daedalus\EnhancedArrayObject; - $array = new EnhancedArrayObject([1, 2, 3]); - -// Add event listener for modifications $array->addEventListener('set', function($key, $value) { - echo "Element set at key $key with value $value\n"; + echo "Value changed at $key to $value\n"; }); - -$array[] = 4; // Triggers event: "Element set at key 3 with value 4" - -// Remove event listener -$listener = function($key) { - echo "Element removed at key $key\n"; -}; -$array->addEventListener('unset', $listener); -unset($array[0]); // Triggers event - -// Remove the listener when no longer needed -$array->removeEventListener('unset', $listener); -``` - -### Array Operations - -```php -use Daedalus\EnhancedArrayObject; - -$array = new EnhancedArrayObject([1, 2, 3, 4, 5]); - -// Mapping -$doubled = $array->map(fn($n) => $n * 2); -// Result: [2, 4, 6, 8, 10] - -// Filtering -$evens = $array->filter(fn($n) => $n % 2 === 0); -// Result: [2, 4] - -// Reducing -$sum = $array->reduce(fn($carry, $n) => $carry + $n, 0); -// Result: 15 - -// Merging arrays -$other = new EnhancedArrayObject([6, 7, 8]); -$merged = $array->merge($other); -// Result: [1, 2, 3, 4, 5, 6, 7, 8] - -// Check if empty -if ($array->isEmpty()) { - echo "Array is empty\n"; -} - -// Deep cloning -$clone = $array->cloneDeep(); -``` - -### Immutable Arrays - -```php -use Daedalus\EnhancedArrayObject; -use Daedalus\ImmutableArrayObject; - -// Create a mutable array -$mutable = new EnhancedArrayObject([1, 2, 3]); - -// Convert to immutable -$immutable = $mutable->toImmutable(); - -// These will throw LogicException -$immutable[] = 4; -unset($immutable[0]); -``` - -### Container Array Object - -The library includes a powerful PSR-11 compliant container with singleton management and automatic dependency injection: - -```php -use Daedalus\ContainerArrayObject; - -// Create a container -$container = new ContainerArrayObject(); - -// 1. Basic Value Binding -$container->bind('app.name', 'My Application'); -$container->bind('app.config', [ - 'api_key' => 'secret_key', - 'cache_ttl' => 3600 -]); - -// 2. Class Registration with Dependencies -class Database { - public function __construct( - private string $host, - private string $username - ) {} -} - -class Logger { - private $logFile; - - public function __construct(string $logFile = 'app.log') { - $this->logFile = $logFile; - } -} - -// Register singleton with factory function -$container->singleton('database', function($container) { - return new Database('localhost', 'root'); -}); - -// Register class for auto-instantiation -$container->bind(Logger::class, Logger::class); - -// 3. Automatic Dependency Injection -class UserRepository { - public function __construct( - private Database $database, - private Logger $logger - ) {} -} - -// Container will automatically resolve dependencies -$container->bind(UserRepository::class, UserRepository::class); -$repo = $container->get(UserRepository::class); - -// 4. Interface Binding -interface PaymentGateway { - public function process(float $amount): bool; -} - -class StripeGateway implements PaymentGateway { - public function process(float $amount): bool { - return true; - } -} - -// Bind interface to implementation -$container->bind(PaymentGateway::class, StripeGateway::class); - -// 5. Instance Management -if ($container->has('database')) { - $db = $container->get('database'); -} - -// Clear cached instances -$container->clearInstances(); -``` - -Key Container Features: -- PSR-11 compliance -- Singleton management -- Automatic dependency injection -- Factory function support -- Interface binding -- Value binding -- Instance lifecycle management -- Fluent interface - -## Advanced Usage - -### Combining Features - -```php -use Daedalus\EnhancedArrayObject; - -class Product { - public function __construct( - public string $name, - public float $price - ) {} -} - -// Create a typed array with event handling -$products = new EnhancedArrayObject([], Product::class); - -// Add event listeners -$products->addEventListener('set', function($key, $value) { - echo "Added product: {$value->name} at \${$value->price}\n"; -}); - -// Add products -$products[] = new Product("Widget", 9.99); -$products[] = new Product("Gadget", 19.99); - -// Filter expensive products -$expensive = $products->filter(fn($p) => $p->price > 10); - -// Calculate total value -$total = $products->reduce(fn($sum, $p) => $sum + $p->price, 0); ``` ## Error Handling The library throws the following exceptions: - - `InvalidArgumentException`: When type constraints are violated - `LogicException`: When attempting to modify immutable arrays ## Contributing -Contributions are welcome! Please feel free to submit a Pull Request. +We welcome contributions! Please feel free to submit a Pull Request. ## License diff --git a/src/MultiMap.php b/src/MultiMap.php new file mode 100644 index 0000000..8fcb28a --- /dev/null +++ b/src/MultiMap.php @@ -0,0 +1,172 @@ +allowDuplicates = $allowDuplicates; + } + + /** + * Add a value to the collection of values associated with a key + * + * @param mixed $key The key + * @param mixed $value The value to add + * @return bool True if the value was added, false if it was a duplicate and duplicates are not allowed + */ + public function put($key, $value): bool + { + if (!isset($this->map[$key])) { + $this->map[$key] = []; + } + + if (!$this->allowDuplicates && in_array($value, $this->map[$key], true)) { + return false; + } + + $this->map[$key][] = $value; + return true; + } + + /** + * Get all values associated with a key + * + * @param mixed $key The key to look up + * @return array Array of values associated with the key + */ + public function get($key): array + { + return $this->map[$key] ?? []; + } + + /** + * Remove a specific value from a key's collection + * + * @param mixed $key The key + * @param mixed $value The value to remove + * @return bool True if the value was removed, false if it wasn't found + */ + public function removeValue($key, $value): bool + { + if (!isset($this->map[$key])) { + return false; + } + + $index = array_search($value, $this->map[$key], true); + if ($index === false) { + return false; + } + + array_splice($this->map[$key], $index, 1); + if (empty($this->map[$key])) { + unset($this->map[$key]); + } + return true; + } + + /** + * Remove all values associated with a key + * + * @param mixed $key The key to remove + * @return array The removed values + */ + public function removeAll($key): array + { + $values = $this->get($key); + unset($this->map[$key]); + return $values; + } + + /** + * Get all keys that have at least one value + * + * @return array + */ + public function keys(): array + { + return array_keys($this->map); + } + + /** + * Get all values in the multimap + * + * @return array + */ + public function values(): array + { + return array_merge(...array_values($this->map)); + } + + /** + * Check if the multimap contains a key + * + * @param mixed $key The key to check + * @return bool + */ + public function containsKey($key): bool + { + return isset($this->map[$key]); + } + + /** + * Check if the multimap contains a value for any key + * + * @param mixed $value The value to check + * @return bool + */ + public function containsValue($value): bool + { + foreach ($this->map as $values) { + if (in_array($value, $values, true)) { + return true; + } + } + return false; + } + + /** + * Get the total number of values across all keys + * + * @return int + */ + public function count(): int + { + return array_sum(array_map('count', $this->map)); + } + + /** + * Get the number of keys in the multimap + * + * @return int + */ + public function keyCount(): int + { + return count($this->map); + } + + /** + * Clear all entries from the multimap + * + * @return void + */ + public function clear(): void + { + $this->map = []; + } +} diff --git a/src/SortedMap.php b/src/SortedMap.php new file mode 100644 index 0000000..e9457e5 --- /dev/null +++ b/src/SortedMap.php @@ -0,0 +1,147 @@ +comparator = $comparator ?? fn($a, $b) => $a <=> $b; + } + + /** + * Put a key-value pair into the map + * + * @param mixed $key The key + * @param mixed $value The value + * @return void + */ + public function put($key, $value): void + { + $this->entries[$key] = $value; + $this->sort(); + } + + /** + * Get a value by key + * + * @param mixed $key The key to look up + * @return mixed|null The value, or null if not found + */ + public function get($key) + { + return $this->entries[$key] ?? null; + } + + /** + * Remove a key-value pair from the map + * + * @param mixed $key The key to remove + * @return void + */ + public function remove($key): void + { + unset($this->entries[$key]); + } + + /** + * Get all keys in sorted order + * + * @return array + */ + public function keys(): array + { + return array_keys($this->entries); + } + + /** + * Get all values in order corresponding to sorted keys + * + * @return array + */ + public function values(): array + { + return array_values($this->entries); + } + + /** + * Get the first key in the sorted map + * + * @return mixed|null + */ + public function firstKey() + { + if (empty($this->entries)) { + return null; + } + $keys = $this->keys(); + return reset($keys); + } + + /** + * Get the last key in the sorted map + * + * @return mixed|null + */ + public function lastKey() + { + if (empty($this->entries)) { + return null; + } + $keys = $this->keys(); + return end($keys); + } + + /** + * Check if the map contains a key + * + * @param mixed $key The key to check + * @return bool + */ + public function containsKey($key): bool + { + return array_key_exists($key, $this->entries); + } + + /** + * Get the number of entries in the map + * + * @return int + */ + public function count(): int + { + return count($this->entries); + } + + /** + * Clear all entries from the map + * + * @return void + */ + public function clear(): void + { + $this->entries = []; + } + + /** + * Sort the internal entries array using the comparator + */ + private function sort(): void + { + uksort($this->entries, $this->comparator); + } +} diff --git a/tests/ContainerArrayObjectTest.php b/tests/ContainerArrayObjectTest.php index e87ce66..bbce7a5 100644 --- a/tests/ContainerArrayObjectTest.php +++ b/tests/ContainerArrayObjectTest.php @@ -16,26 +16,54 @@ protected function setUp(): void $this->container = new ContainerArrayObject(); } - public function testPsrCompliance() + /** + * @testdox Implements PSR-11 Container Interface + */ + public function testPsrCompliance(): void { - $this->assertInstanceOf(ContainerInterface::class, $this->container); + $this->assertInstanceOf( + ContainerInterface::class, + $this->container, + 'Should implement PSR-11 ContainerInterface' + ); } - public function testBasicBinding() + /** + * @testdox Can bind and retrieve basic values + */ + public function testBasicBinding(): void { $this->container->bind('key', 'value'); - $this->assertTrue($this->container->has('key')); - $this->assertEquals('value', $this->container->get('key')); + + $this->assertTrue( + $this->container->has('key'), + 'Should detect bound key' + ); + $this->assertEquals( + 'value', + $this->container->get('key'), + 'Should retrieve bound value' + ); } - public function testMissingEntry() + /** + * @testdox Throws exception for missing entries + */ + public function testMissingEntry(): void { - $this->assertFalse($this->container->has('missing')); + $this->assertFalse( + $this->container->has('nonexistent'), + 'Should return false for non-existent key' + ); + $this->expectException(NotFoundExceptionInterface::class); - $this->container->get('missing'); + $this->container->get('nonexistent'); } - public function testSingletonRegistration() + /** + * @testdox Maintains singleton instances + */ + public function testSingletonRegistration(): void { $obj = new \stdClass(); $obj->value = 'test'; @@ -45,10 +73,17 @@ public function testSingletonRegistration() $instance1 = $this->container->get('instance'); $instance2 = $this->container->get('instance'); - $this->assertSame($instance1, $instance2); + $this->assertSame( + $instance1, + $instance2, + 'Should return same instance for singleton' + ); } - public function testFactoryFunction() + /** + * @testdox Supports factory functions for instance creation + */ + public function testFactoryFunction(): void { $count = 0; $this->container->bind('factory', function() use (&$count) { @@ -60,12 +95,19 @@ public function testFactoryFunction() $instance1 = $this->container->get('factory'); $instance2 = $this->container->get('factory'); - $this->assertNotSame($instance1, $instance2); + $this->assertNotSame( + $instance1, + $instance2, + 'Should return new instance each time for factory' + ); $this->assertEquals(1, $instance1->count); $this->assertEquals(2, $instance2->count); } - public function testSingletonFactory() + /** + * @testdox Supports singleton factory with dependencies + */ + public function testSingletonFactory(): void { $count = 0; $this->container->singleton('singleton.factory', function() use (&$count) { @@ -77,12 +119,19 @@ public function testSingletonFactory() $instance1 = $this->container->get('singleton.factory'); $instance2 = $this->container->get('singleton.factory'); - $this->assertSame($instance1, $instance2); + $this->assertSame( + $instance1, + $instance2, + 'Should return same instance for singleton factory' + ); $this->assertEquals(1, $instance1->count); $this->assertEquals(1, $instance2->count); } - public function testAutomaticDependencyInjection() + /** + * @testdox Automatically injects container into factory functions + */ + public function testAutomaticDependencyInjection(): void { // Define test classes in correct order (dependencies first) $this->container->bind(Config::class, Config::class); @@ -92,22 +141,48 @@ public function testAutomaticDependencyInjection() $repo = $this->container->get(UserRepository::class); - $this->assertInstanceOf(UserRepository::class, $repo); - $this->assertInstanceOf(Database::class, $repo->getDatabase()); - $this->assertInstanceOf(Logger::class, $repo->getLogger()); + $this->assertInstanceOf( + UserRepository::class, + $repo, + 'Should resolve all dependencies in chain' + ); + $this->assertInstanceOf( + Database::class, + $repo->getDatabase(), + 'Should resolve all dependencies in chain' + ); + $this->assertInstanceOf( + Logger::class, + $repo->getLogger(), + 'Should resolve all dependencies in chain' + ); } - public function testInterfaceBinding() + /** + * @testdox Can bind interfaces to implementations + */ + public function testInterfaceBinding(): void { $this->container->bind(PaymentGatewayInterface::class, StripeGateway::class); $gateway = $this->container->get(PaymentGatewayInterface::class); - $this->assertInstanceOf(PaymentGatewayInterface::class, $gateway); - $this->assertInstanceOf(StripeGateway::class, $gateway); + $this->assertInstanceOf( + PaymentGatewayInterface::class, + $gateway, + 'Should resolve interface to correct implementation' + ); + $this->assertInstanceOf( + StripeGateway::class, + $gateway, + 'Should resolve interface to correct implementation' + ); } - public function testClearInstances() + /** + * @testdox Can clear cached instances + */ + public function testClearInstances(): void { $obj = new \stdClass(); $obj->value = 'test'; @@ -120,16 +195,34 @@ public function testClearInstances() $instance2 = $this->container->get('singleton'); // Verify that the singleton binding still exists but returns a new instance - $this->assertTrue($this->container->isSingleton('singleton')); - $this->assertNotSame($instance1, $instance2); - $this->assertEquals($instance1->value, $instance2->value); + $this->assertTrue( + $this->container->isSingleton('singleton'), + 'Should still be a singleton after clearing instances' + ); + $this->assertNotSame( + $instance1, + $instance2, + 'Should return new instance after clearing cache' + ); + $this->assertEquals( + $instance1->value, + $instance2->value, + 'Should return new instance after clearing cache' + ); // Verify that subsequent gets return the same instance $instance3 = $this->container->get('singleton'); - $this->assertSame($instance2, $instance3); + $this->assertSame( + $instance2, + $instance3, + 'Should return same instance after clearing cache' + ); } - public function testNestedDependencies() + /** + * @testdox Resolves nested dependencies correctly + */ + public function testNestedDependencies(): void { $this->container->bind(Config::class, Config::class); $this->container->bind(Database::class, Database::class); @@ -138,10 +231,26 @@ public function testNestedDependencies() $service = $this->container->get(UserService::class); - $this->assertInstanceOf(UserService::class, $service); - $this->assertInstanceOf(Database::class, $service->getDatabase()); - $this->assertInstanceOf(Cache::class, $service->getCache()); - $this->assertInstanceOf(Config::class, $service->getDatabase()->getConfig()); + $this->assertInstanceOf( + UserService::class, + $service, + 'Should resolve all dependencies in chain' + ); + $this->assertInstanceOf( + Database::class, + $service->getDatabase(), + 'Should resolve all dependencies in chain' + ); + $this->assertInstanceOf( + Cache::class, + $service->getCache(), + 'Should resolve all dependencies in chain' + ); + $this->assertInstanceOf( + Config::class, + $service->getDatabase()->getConfig(), + 'Should resolve all dependencies in chain' + ); } } diff --git a/tests/HashSetTest.php b/tests/HashSetTest.php index d9f0ebb..b17fe59 100644 --- a/tests/HashSetTest.php +++ b/tests/HashSetTest.php @@ -14,36 +14,48 @@ protected function setUp(): void $this->set = new HashSet(); } + /** + * @testdox Can add elements and check for their existence + */ public function testAddAndContains(): void { - $this->assertTrue($this->set->add("apple")); - $this->assertTrue($this->set->contains("apple")); - $this->assertFalse($this->set->add("apple")); // Adding duplicate + $this->assertTrue($this->set->add("apple"), "Should successfully add new element"); + $this->assertTrue($this->set->contains("apple"), "Should find added element"); + $this->assertFalse($this->set->add("apple"), "Should not add duplicate element"); $this->assertEquals(1, $this->set->count()); } + /** + * @testdox Can remove elements from the set + */ public function testRemove(): void { $this->set->add("apple"); $this->set->add("banana"); - $this->assertTrue($this->set->remove("apple")); - $this->assertFalse($this->set->contains("apple")); + $this->assertTrue($this->set->remove("apple"), "Should successfully remove existing element"); + $this->assertFalse($this->set->contains("apple"), "Should not find removed element"); $this->assertEquals(1, $this->set->count()); - $this->assertFalse($this->set->remove("nonexistent")); + $this->assertFalse($this->set->remove("nonexistent"), "Should return false when removing non-existent element"); } + /** + * @testdox Can clear all elements from the set + */ public function testClear(): void { $this->set->add("apple"); $this->set->add("banana"); $this->set->clear(); - $this->assertEquals(0, $this->set->count()); - $this->assertFalse($this->set->contains("apple")); + $this->assertEquals(0, $this->set->count(), "Should have no elements after clearing"); + $this->assertFalse($this->set->contains("apple"), "Should not find cleared element"); } + /** + * @testdox Can perform bulk operations with arrays + */ public function testBulkOperations(): void { $this->set->add("apple"); @@ -52,22 +64,25 @@ public function testBulkOperations(): void $otherSet = new HashSet(["banana", "cherry"]); // Test addAll - $this->assertTrue($this->set->addAll($otherSet)); - $this->assertEquals(3, $this->set->count()); - $this->assertTrue($this->set->contains("cherry")); + $this->assertTrue($this->set->addAll($otherSet), "Should add all elements from other set"); + $this->assertEquals(3, $this->set->count(), "Should have correct count after adding all"); + $this->assertTrue($this->set->contains("cherry"), "Should contain added element"); // Test removeAll - $this->assertTrue($this->set->removeAll($otherSet)); - $this->assertEquals(1, $this->set->count()); - $this->assertTrue($this->set->contains("apple")); + $this->assertTrue($this->set->removeAll($otherSet), "Should remove all elements from other set"); + $this->assertEquals(1, $this->set->count(), "Should have correct count after removing all"); + $this->assertTrue($this->set->contains("apple"), "Should still contain non-removed element"); // Test retainAll $this->set->add("banana"); - $this->assertTrue($this->set->retainAll($otherSet)); - $this->assertEquals(1, $this->set->count()); - $this->assertTrue($this->set->contains("banana")); + $this->assertTrue($this->set->retainAll($otherSet), "Should retain all elements from other set"); + $this->assertEquals(1, $this->set->count(), "Should have correct count after retaining all"); + $this->assertTrue($this->set->contains("banana"), "Should contain retained element"); } + /** + * @testdox Can customize load factor and initial capacity + */ public function testCustomLoadFactorAndCapacity(): void { $customSet = new HashSet([], 0.75, 32); @@ -76,11 +91,14 @@ public function testCustomLoadFactorAndCapacity(): void $customSet->add("item$i"); } - $this->assertEquals(24, $customSet->count()); - $this->assertTrue($customSet->contains("item0")); - $this->assertTrue($customSet->contains("item23")); + $this->assertEquals(24, $customSet->count(), "Should have correct count with custom load factor and capacity"); + $this->assertTrue($customSet->contains("item0"), "Should contain added element with custom load factor and capacity"); + $this->assertTrue($customSet->contains("item23"), "Should contain added element with custom load factor and capacity"); } + /** + * @testdox Can iterate over set elements + */ public function testIterator(): void { $items = ["apple", "banana", "cherry"]; @@ -94,9 +112,12 @@ public function testIterator(): void } sort($collectedItems); // Sort since HashSet doesn't guarantee order - $this->assertEquals($items, $collectedItems); + $this->assertEquals($items, $collectedItems, "Iterator should return all elements"); } + /** + * @testdox Can convert set to array + */ public function testToArray(): void { $items = ["apple", "banana", "cherry"]; @@ -106,9 +127,12 @@ public function testToArray(): void $array = $this->set->toArray(); sort($array); // Sort since HashSet doesn't guarantee order - $this->assertEquals($items, $array); + $this->assertEquals($items, $array, "Array conversion should contain all elements"); } + /** + * @testdox Can store and retrieve object elements + */ public function testObjectStorage(): void { $obj1 = new \stdClass(); @@ -119,8 +143,8 @@ public function testObjectStorage(): void $this->set->add($obj1); $this->set->add($obj2); - $this->assertEquals(2, $this->set->count()); - $this->assertTrue($this->set->contains($obj1)); - $this->assertTrue($this->set->contains($obj2)); + $this->assertEquals(2, $this->set->count(), "Should maintain correct count with objects"); + $this->assertTrue($this->set->contains($obj1), "Should store and find object references"); + $this->assertTrue($this->set->contains($obj2), "Should store and find object references"); } } diff --git a/tests/MatrixTest.php b/tests/MatrixTest.php index f9897d8..795dcfe 100644 --- a/tests/MatrixTest.php +++ b/tests/MatrixTest.php @@ -8,7 +8,7 @@ class MatrixTest extends TestCase { /** - * @test + * @testdox Can construct a valid matrix with proper dimensions */ public function testMatrixConstruction() { @@ -19,13 +19,13 @@ public function testMatrixConstruction() ]; $matrix = new Matrix($data); - $this->assertEquals(3, $matrix->getRows()); - $this->assertEquals(3, $matrix->getCols()); - $this->assertEquals($data, $matrix->getData()); + $this->assertEquals(3, $matrix->getRows(), 'Matrix should have 3 rows'); + $this->assertEquals(3, $matrix->getCols(), 'Matrix should have 3 columns'); + $this->assertEquals($data, $matrix->getData(), 'Matrix data should match input array'); } /** - * @test + * @testdox Throws exception when constructing matrix with inconsistent dimensions */ public function testInvalidMatrixConstruction() { @@ -37,7 +37,7 @@ public function testInvalidMatrixConstruction() } /** - * @test + * @testdox Can perform matrix addition with compatible matrices */ public function testMatrixAddition() { @@ -49,11 +49,15 @@ public function testMatrixAddition() [6, 8], [10, 12] ]; - $this->assertEquals($expected, $result->getData()); + $this->assertEquals( + $expected, + $result->getData(), + 'Matrix addition should add corresponding elements' + ); } /** - * @test + * @testdox Can perform matrix subtraction with compatible matrices */ public function testMatrixSubtraction() { @@ -65,11 +69,15 @@ public function testMatrixSubtraction() [4, 4], [4, 4] ]; - $this->assertEquals($expected, $result->getData()); + $this->assertEquals( + $expected, + $result->getData(), + 'Matrix subtraction should subtract corresponding elements' + ); } /** - * @test + * @testdox Can perform matrix multiplication with compatible matrices */ public function testMatrixMultiplication() { @@ -81,11 +89,15 @@ public function testMatrixMultiplication() [4, 4], [10, 8] ]; - $this->assertEquals($expected, $result->getData()); + $this->assertEquals( + $expected, + $result->getData(), + 'Matrix multiplication should follow the matrix product rule' + ); } /** - * @test + * @testdox Can scale a matrix by a scalar value */ public function testMatrixScaling() { @@ -96,11 +108,15 @@ public function testMatrixScaling() [2, 4], [6, 8] ]; - $this->assertEquals($expected, $result->getData()); + $this->assertEquals( + $expected, + $result->getData(), + 'Matrix scaling should multiply each element by the scalar' + ); } /** - * @test + * @testdox Can transpose a matrix by switching rows and columns */ public function testMatrixTranspose() { @@ -112,17 +128,25 @@ public function testMatrixTranspose() [2, 5], [3, 6] ]; - $this->assertEquals($expected, $result->getData()); + $this->assertEquals( + $expected, + $result->getData(), + 'Matrix transpose should switch rows and columns' + ); } /** - * @test + * @testdox Can calculate determinant for 2x2 and 3x3 matrices */ public function testDeterminant() { // 2x2 matrix $matrix1 = new Matrix([[4, 3], [2, 1]]); - $this->assertEquals(-2, $matrix1->determinant()); + $this->assertEquals( + -2, + $matrix1->determinant(), + 'Should correctly calculate 2x2 matrix determinant' + ); // 3x3 matrix $matrix2 = new Matrix([ @@ -130,11 +154,15 @@ public function testDeterminant() [4, 5, 6], [7, 8, 9] ]); - $this->assertEquals(0, $matrix2->determinant()); + $this->assertEquals( + 0, + $matrix2->determinant(), + 'Should correctly calculate 3x3 matrix determinant' + ); } /** - * @test + * @testdox Can create identity matrix of specified size */ public function testIdentityMatrix() { @@ -144,11 +172,15 @@ public function testIdentityMatrix() [0, 1, 0], [0, 0, 1] ]; - $this->assertEquals($expected, $identity->getData()); + $this->assertEquals( + $expected, + $identity->getData(), + 'Identity matrix should have 1s on diagonal and 0s elsewhere' + ); } /** - * @test + * @testdox Can create zero matrix of specified dimensions */ public function testZeroMatrix() { @@ -157,27 +189,29 @@ public function testZeroMatrix() [0, 0, 0], [0, 0, 0] ]; - $this->assertEquals($expected, $zero->getData()); + $this->assertEquals( + $expected, + $zero->getData(), + 'Zero matrix should contain all zeros' + ); } /** - * @test + * @testdox Can get and set individual matrix elements */ public function testGetAndSet() { $matrix = new Matrix([[1, 2], [3, 4]]); - // Test get - $this->assertEquals(1, $matrix->get(0, 0)); - $this->assertEquals(4, $matrix->get(1, 1)); + $this->assertEquals(1, $matrix->get(0, 0), 'Should retrieve correct value from first element'); + $this->assertEquals(4, $matrix->get(1, 1), 'Should retrieve correct value from last element'); - // Test set $matrix->set(0, 1, 5); - $this->assertEquals(5, $matrix->get(0, 1)); + $this->assertEquals(5, $matrix->get(0, 1), 'Should update and retrieve modified value'); } /** - * @test + * @testdox Throws exception when accessing invalid row index */ public function testInvalidGetAccess() { @@ -187,7 +221,7 @@ public function testInvalidGetAccess() } /** - * @test + * @testdox Throws exception when setting value at invalid column index */ public function testInvalidSetAccess() { @@ -197,7 +231,7 @@ public function testInvalidSetAccess() } /** - * @test + * @testdox Throws exception when calculating determinant of non-square matrix */ public function testInvalidDeterminant() { diff --git a/tests/MultiMapTest.php b/tests/MultiMapTest.php new file mode 100644 index 0000000..c411fc1 --- /dev/null +++ b/tests/MultiMapTest.php @@ -0,0 +1,158 @@ +map = new MultiMap(); + } + + /** + * @testdox Can store multiple values for a single key + */ + public function testPutAndGet(): void + { + $this->map->put('key1', 'value1'); + $this->map->put('key1', 'value2'); + + $this->assertEquals( + ['value1', 'value2'], + $this->map->get('key1'), + 'Should store and retrieve multiple values for the same key' + ); + } + + /** + * @testdox Can prevent duplicate values for the same key when configured + */ + public function testDuplicateValues(): void + { + $map = new MultiMap(false); // don't allow duplicates + + $this->assertTrue( + $map->put('key1', 'value1'), + 'Should successfully add first occurrence of a value' + ); + $this->assertTrue( + $map->put('key1', 'value2'), + 'Should successfully add different value for same key' + ); + $this->assertFalse( + $map->put('key1', 'value1'), + 'Should prevent adding duplicate value when duplicates are disabled' + ); + + $this->assertEquals( + ['value1', 'value2'], + $map->get('key1'), + 'Should maintain unique values for key when duplicates are disabled' + ); + } + + /** + * @testdox Can remove individual values from a key's collection + */ + public function testRemoveValue(): void + { + $this->map->put('key1', 'value1'); + $this->map->put('key1', 'value2'); + + $this->assertTrue( + $this->map->removeValue('key1', 'value1'), + 'Should successfully remove existing value' + ); + $this->assertEquals( + ['value2'], + $this->map->get('key1'), + 'Should maintain remaining values after removal' + ); + } + + /** + * @testdox Can remove all values associated with a key + */ + public function testRemoveAll(): void + { + $this->map->put('key1', 'value1'); + $this->map->put('key1', 'value2'); + + $removed = $this->map->removeAll('key1'); + $this->assertEquals( + ['value1', 'value2'], + $removed, + 'Should return all removed values' + ); + $this->assertEquals( + [], + $this->map->get('key1'), + 'Should remove all values for the key' + ); + } + + /** + * @testdox Can check if a value exists in any key's collection + */ + public function testContainsValue(): void + { + $this->map->put('key1', 'value1'); + $this->map->put('key2', 'value2'); + + $this->assertTrue( + $this->map->containsValue('value1'), + 'Should find existing value' + ); + $this->assertFalse( + $this->map->containsValue('nonexistent'), + 'Should not find non-existent value' + ); + } + + /** + * @testdox Can count total values and unique keys separately + */ + public function testCount(): void + { + $this->map->put('key1', 'value1'); + $this->map->put('key1', 'value2'); + $this->map->put('key2', 'value3'); + + $this->assertEquals( + 3, + $this->map->count(), + 'Total count should include all values across all keys' + ); + $this->assertEquals( + 2, + $this->map->keyCount(), + 'Key count should only include unique keys' + ); + } + + /** + * @testdox Can clear all entries from the multimap + */ + public function testClear(): void + { + $this->map->put('key1', 'value1'); + $this->map->put('key2', 'value2'); + + $this->map->clear(); + $this->assertEquals( + 0, + $this->map->count(), + 'Cleared map should have no values' + ); + $this->assertEquals( + [], + $this->map->keys(), + 'Cleared map should have no keys' + ); + } +} diff --git a/tests/SortedMapTest.php b/tests/SortedMapTest.php new file mode 100644 index 0000000..719418e --- /dev/null +++ b/tests/SortedMapTest.php @@ -0,0 +1,102 @@ +map = new SortedMap(); + } + + /** + * @testdox Can add and retrieve values maintaining key-value associations + */ + public function testPutAndGet(): void + { + $this->map->put('b', 2); + $this->map->put('a', 1); + $this->map->put('c', 3); + + $this->assertEquals(1, $this->map->get('a'), 'Should retrieve correct value for key "a"'); + $this->assertEquals(2, $this->map->get('b'), 'Should retrieve correct value for key "b"'); + $this->assertEquals(3, $this->map->get('c'), 'Should retrieve correct value for key "c"'); + } + + /** + * @testdox Keys are automatically maintained in natural sorted order + */ + public function testKeyOrder(): void + { + $this->map->put('b', 2); + $this->map->put('a', 1); + $this->map->put('c', 3); + + $this->assertEquals( + ['a', 'b', 'c'], + $this->map->keys(), + 'Keys should be returned in natural sorted order' + ); + } + + /** + * @testdox Supports custom comparison function for key ordering + */ + public function testCustomComparator(): void + { + $map = new SortedMap(fn($a, $b) => -strcmp($a, $b)); // reverse order + $map->put('b', 2); + $map->put('a', 1); + $map->put('c', 3); + + $this->assertEquals( + ['c', 'b', 'a'], + $map->keys(), + 'Keys should be returned in reverse alphabetical order with custom comparator' + ); + } + + /** + * @testdox Can retrieve first and last keys in sorted order + */ + public function testFirstAndLastKey(): void + { + $this->map->put('b', 2); + $this->map->put('a', 1); + $this->map->put('c', 3); + + $this->assertEquals('a', $this->map->firstKey(), 'First key should be the smallest in sorted order'); + $this->assertEquals('c', $this->map->lastKey(), 'Last key should be the largest in sorted order'); + } + + /** + * @testdox Can remove key-value pairs from the map + */ + public function testRemove(): void + { + $this->map->put('a', 1); + $this->map->put('b', 2); + + $this->map->remove('a'); + $this->assertNull($this->map->get('a'), 'Removed key should return null when accessed'); + $this->assertEquals(1, $this->map->count(), 'Map size should decrease after removal'); + } + + /** + * @testdox Can clear all entries from the map + */ + public function testClear(): void + { + $this->map->put('a', 1); + $this->map->put('b', 2); + + $this->map->clear(); + $this->assertEquals(0, $this->map->count(), 'Cleared map should have size 0'); + $this->assertEquals([], $this->map->keys(), 'Cleared map should have no keys'); + } +} diff --git a/tests/TreeSetTest.php b/tests/TreeSetTest.php index 617d181..7f4a163 100644 --- a/tests/TreeSetTest.php +++ b/tests/TreeSetTest.php @@ -14,14 +14,29 @@ protected function setUp(): void $this->set = new TreeSet(); } + /** + * @testdox Can add elements and verify their existence in the set + */ public function testAddAndContains(): void { - $this->assertTrue($this->set->add(5)); - $this->assertTrue($this->set->contains(5)); - $this->assertFalse($this->set->add(5)); // Adding duplicate + $this->assertTrue( + $this->set->add(5), + 'Should successfully add new element' + ); + $this->assertTrue( + $this->set->contains(5), + 'Should find added element' + ); + $this->assertFalse( + $this->set->add(5), + 'Should not add duplicate element' + ); $this->assertEquals(1, $this->set->count()); } + /** + * @testdox Maintains elements in sorted order during insertion + */ public function testOrderedInsertion(): void { $numbers = [5, 3, 7, 1, 9, 2, 8, 4, 6]; @@ -33,6 +48,9 @@ public function testOrderedInsertion(): void $this->assertEquals($expected, $this->set->toArray()); } + /** + * @testdox Can retrieve first and last elements in sorted order + */ public function testFirstAndLast(): void { $numbers = [5, 3, 7, 1, 9]; @@ -44,6 +62,9 @@ public function testFirstAndLast(): void $this->assertEquals(9, $this->set->last()); } + /** + * @testdox Can find elements lower and higher than a given value + */ public function testLowerAndHigher(): void { $numbers = [5, 3, 7, 1, 9]; @@ -57,6 +78,9 @@ public function testLowerAndHigher(): void $this->assertEquals(null, $this->set->higher(9)); } + /** + * @testdox Can remove elements while maintaining order + */ public function testRemove(): void { $numbers = [5, 3, 7]; @@ -69,6 +93,9 @@ public function testRemove(): void $this->assertFalse($this->set->remove(3)); // Already removed } + /** + * @testdox Can clear all elements from the set + */ public function testClear(): void { $numbers = [5, 3, 7]; @@ -82,6 +109,9 @@ public function testClear(): void $this->assertNull($this->set->last()); } + /** + * @testdox Can iterate over elements in sorted order + */ public function testIterator(): void { $numbers = [5, 3, 7, 1, 9]; @@ -97,6 +127,9 @@ public function testIterator(): void $this->assertEquals([1, 3, 5, 7, 9], $result); } + /** + * @testdox Can maintain order with custom object comparisons + */ public function testObjectOrdering(): void { $obj1 = new class { @@ -117,6 +150,9 @@ public function __toString() { return "3"; } $this->assertEquals(["1", "2", "3"], $result); } + /** + * @testdox Handles operations on empty set correctly + */ public function testEmptySetOperations(): void { $this->assertNull($this->set->first()); @@ -127,6 +163,9 @@ public function testEmptySetOperations(): void $this->assertEquals([], $this->set->toArray()); } + /** + * @testdox Maintains balance during insertions and deletions + */ public function testBalancing(): void { // Test AVL tree balancing with sequential insertions