diff --git a/.run/spice.run.xml b/.run/spice.run.xml index f5034be42..ee21d71b0 100644 --- a/.run/spice.run.xml +++ b/.run/spice.run.xml @@ -1,5 +1,5 @@ - + diff --git a/media/test-project/test.spice b/media/test-project/test.spice index 5a5c7e1b1..ea2f4ed30 100644 --- a/media/test-project/test.spice +++ b/media/test-project/test.spice @@ -1,77 +1,45 @@ -type Speak interface { - p sayHello(string); -} - -type MakeSound interface { - p makeSound(); -} - -type Human struct : MakeSound, Speak { - string firstName - string lastName - unsigned int age -} - -p Human.ctor(string firstName, string lastName, unsigned int age) { - this.firstName = firstName; - this.lastName = lastName; - this.age = age; -} - -p Human.makeSound() { - printf("Sigh...\n"); -} - -p Human.sayHello(string name) { - printf("Hi, %s!\n", name); -} - -type Car struct : MakeSound { - string brand - string model - unsigned int seats -} - -p Car.ctor(string brand, string model, unsigned int seats) { - this.brand = brand; - this.model = model; - this.seats = seats; -} - -p Car.makeSound() { - printf("Wroom, wroom!\n"); -} - -type Parrot struct : MakeSound, Speak { - string name - unsigned int age -} - -p Parrot.ctor(string name, unsigned int age) { - this.name = name; - this.age = age; -} - -p Parrot.makeSound() { - printf("Sqawk!\n"); -} - -p Parrot.sayHello(string name) { - printf("Hello %s, squawk!\n", name); -} +import "std/data/unordered-map"; +import "std/time/timer"; f main() { - Human human = Human("John", "Doe", 25); - Car car = Car("Toyota", "Corolla", 5); - Parrot parrot = Parrot("Polly", 3); - - human.makeSound(); - car.makeSound(); - parrot.makeSound(); - - human.sayHello("Jane"); - parrot.sayHello("Jane"); - return 0; + Timer timer = Timer(); + timer.start(); + + UnorderedMap map = UnorderedMap(3l); + assert map.getSize() == 0; + assert map.isEmpty(); + map.upsert(1, "one"); + map.upsert(2, "two"); + map.upsert(3, "three"); + map.upsert(4, "four"); + assert map.getSize() == 4; + assert !map.isEmpty(); + assert map.contains(1); + assert map.contains(2); + assert map.contains(3); + assert map.contains(4); + assert !map.contains(5); + assert map.get(1) == "one"; + assert map.get(2) == "two"; + assert map.get(3) == "three"; + assert map.get(4) == "four"; + const Result item5 = map.getSafe(5); + assert item5.isErr(); + map.remove(2); + assert !map.contains(2); + assert map.getSize() == 3; + map.clear(); + assert map.getSize() == 0; + assert map.isEmpty(); + map.upsert(1, "one"); + map.upsert(1, "one new"); + assert map.getSize() == 1; + assert map.get(1) == "one new"; + map.remove(1); + assert map.isEmpty(); + + timer.stop(); + printf("All assertions passed in %d microseconds!\n", timer.getDurationInMicros()); } /*import "bootstrap/util/block-allocator"; diff --git a/std/data/hash-table.spice b/std/data/hash-table.spice index dab32bdc6..823cb9b57 100644 --- a/std/data/hash-table.spice +++ b/std/data/hash-table.spice @@ -1,6 +1,7 @@ import "std/data/vector"; import "std/data/linked-list"; -import "std/data/optional"; +import "std/type/result"; +import "std/type/error"; import "std/math/hash"; // Generic types for key and value @@ -13,50 +14,136 @@ type HashEntry struct { } public type HashTable struct { - Vector>> table + Vector>> buckets } public p HashTable.ctor(unsigned long bucketCount = 100l) { - this.table = Vector>>(bucketCount); + this.buckets = Vector>>(bucketCount); for unsigned long i = 0l; i < bucketCount; i++ { - this.table.pushBack(LinkedList>()); + this.buckets.pushBack(LinkedList>()); } } +/** + * Insert a key-value pair into the hash table. + * If the key already exists, the value is updated. + * + * @param key The key to insert + * @param value The value to insert + */ public p HashTable.upsert(const K& key, const V& value) { const unsigned long index = this.hash(key); - const LinkedList>& list = this.table.get(index); - foreach const HashEntry& entry : list { + const LinkedList>& bucket = this.buckets.get(index); + foreach const HashEntry& entry : bucket { if (entry.key == key) { entry.value = value; return; } } + bucket.pushBack(HashEntry{key, value}); } -public f> HashTable.get(const K& key) { +/** + * Retrieve the value associated with the given key. + * If the key is not found, panic. + * + * @param key The key to look up + * @return The value associated with the key + */ +public f HashTable.get(const K& key) { const unsigned long index = this.hash(key); - const LinkedList>& list = this.table.get(index); - foreach const HashEntry& entry : list { + const LinkedList>& bucket = this.buckets.get(index); + foreach const HashEntry& entry : bucket { if (entry.key == key) { - return Optional(entry.value); + return entry.value; } } - return Optional(); + panic(Error("The provided key was not found")); } +/** + * Retrieve the value associated with the given key as Optional. + * If the key is not found, return an empty optional. + * + * @param key The key to look up + * @return Optional, containing the value associated with the key or empty if the key is not found + */ +public f> HashTable.getSafe(const K& key) { + const unsigned long index = this.hash(key); + const LinkedList>& bucket = this.buckets.get(index); + foreach const HashEntry& entry : bucket { + if (entry.key == key) { + return ok(entry.value); + } + } + return err(Error("The provided key was not found")); +} + +/** + * Remove the key-value pair associated with the given key. + * If the key is not found, do nothing. + * + * @param key The key to remove + */ public p HashTable.remove(const K& key) { const unsigned long index = this.hash(key); - LinkedList>& list = this.table.get(index); - for (unsigned long i = 0l; i < list.getSize(); i++) { - if (list.get(i).key == key) { - list.remove(i); + LinkedList>& bucket = this.buckets.get(index); + for unsigned long i = 0l; i < bucket.getSize(); i++ { + if (bucket.get(i).key == key) { + bucket.removeAt(i); return; } } } +/** + * Check if the hash table contains the given key. + * + * @param key The key to check for + * @return True if the key is found, false otherwise + */ +public f HashTable.contains(const K& key) { + const unsigned long index = this.hash(key); + const LinkedList>& bucket = this.buckets.get(index); + foreach const HashEntry& entry : bucket { + if (entry.key == key) { + return true; + } + } + return false; +} + +/** + * Get the size of the hash table. + * + * @return The number of key-value pairs in the hash table + */ +public inline f HashTable.getSize() { + result = 0l; + foreach LinkedList>& bucket : this.buckets { + result += bucket.getSize(); + } +} + +/** + * Checks if the hash table is empty. + * + * @return True if empty, false otherwise. + */ +public inline f HashTable.isEmpty() { + return this.getSize() == 0l; +} + +/** + * Clear the hash table, removing all key-value pairs. + */ +public inline p HashTable.clear() { + foreach LinkedList>& bucket : this.buckets { + bucket.clear(); + } +} + inline f HashTable.hash(const K& key) { - K keyCopy = key; - return hash(keyCopy) % this.table.getSize(); + const K keyCopy = key; + return hash(keyCopy) % this.buckets.getSize(); } \ No newline at end of file diff --git a/std/data/linked-list.spice b/std/data/linked-list.spice index 757991d51..6d57b6119 100644 --- a/std/data/linked-list.spice +++ b/std/data/linked-list.spice @@ -43,6 +43,11 @@ public type LinkedList struct : IIterable { public p LinkedList.ctor() {} +/** + * Pushes a new item to the back of the list + * + * @param value Value to push + */ public p LinkedList.pushBack(const T& value) { // Create new node heap Node* newNode = this.createNode(value); @@ -57,6 +62,11 @@ public p LinkedList.pushBack(const T& value) { this.size++; } +/** + * Pushes a new item to the front of the list + * + * @param value Value to push + */ public p LinkedList.pushFront(const T& value) { // Create new node heap Node* newNode = this.createNode(value); @@ -71,6 +81,12 @@ public p LinkedList.pushFront(const T& value) { this.size++; } +/** + * Inserts a new item at the given index + * + * @param idx Index to insert the new item + * @param value Value to insert + */ public p LinkedList.insertAt(unsigned long idx, const T& value) { // Abort if the index is out of bounds if idx < 0l || idx >= this.size { return; } @@ -94,10 +110,21 @@ public p LinkedList.insertAt(unsigned long idx, const T& value) { this.size++; } -public p LinkedList.insertAt(unsigned int idx, const T& value) { +/** + * Inserts a new item at the given index + * + * @param idx Index to insert the new item + * @param value Value to insert + */ +public inline p LinkedList.insertAt(unsigned int idx, const T& value) { this.insertAt((unsigned long) idx, value); } +/** + * Removes the first occurrence of the given value + * + * @param valueToRemove Value to remove + */ public p LinkedList.remove(const T& valueToRemove) { // Abort if the list is already empty if this.isEmpty() { return; } @@ -127,6 +154,11 @@ public p LinkedList.remove(const T& valueToRemove) { this.size--; } +/** + * Removes the first occurrence of the given value + * + * @param valueToRemove Value to remove + */ public p LinkedList.removeAt(unsigned long idx) { // Abort if the index is out of bounds if idx < 0l || idx >= this.size { return; } @@ -159,26 +191,53 @@ public p LinkedList.removeAt(unsigned long idx) { this.size--; } -public p LinkedList.removeAt(unsigned int index) { - this.removeAt((unsigned long) index); +/** + * Removes the first occurrence of the given value + * + * @param idx Index to remove + */ +public inline p LinkedList.removeAt(unsigned int idx) { + this.removeAt((unsigned long) idx); } -public p LinkedList.removeFront() { +/** + * Removes the first item of the list + */ +public inline p LinkedList.removeFront() { this.removeAt(0l); } -public p LinkedList.removeBack() { +/** + * Removes the last item of the list + */ +public inline p LinkedList.removeBack() { this.removeAt(this.size - 1l); } +/** + * Returns the size of the list + * + * @return Size of the list + */ public inline f LinkedList.getSize() { return this.size; } +/** + * Returns if the list is empty + * + * @return true if the list is empty, false otherwise + */ public inline f LinkedList.isEmpty() { return this.size == 0l; } +/** + * Returns the item at the given index + * + * @param idx Index to access + * @return Reference to the item + */ public f LinkedList.get(unsigned long idx) { // Abort if the index is out of bounds if idx < 0 || idx >= this.size { panic(Error("Access index out of bound")); } @@ -190,20 +249,45 @@ public f LinkedList.get(unsigned long idx) { return curr.value; } -public f LinkedList.get(unsigned int idx) { +/** + * Returns the item at the given index + * + * @param idx Index to access + * @return Reference to the item + */ +public inline f LinkedList.get(unsigned int idx) { return this.get((unsigned long) idx); } +/** + * Returns the first item of the list + * + * @return Reference to the first item + */ public inline f LinkedList.getFront() { if this.isEmpty() { panic(Error("Access index out of bounds")); } return this.tail.value; } +/** + * Returns the last item of the list + * + * @return Reference to the last item + */ public inline f LinkedList.getBack() { if this.isEmpty() { panic(Error("Access index out of bounds")); } return this.head.value; } +/** + * Clears the list + */ +public inline p LinkedList.clear() { + while !this.isEmpty() { + this.removeFront(); + } +} + f*> LinkedList.createNode(const T& value) { heap Node* newNode; unsafe { diff --git a/std/data/red-black-tree.spice b/std/data/red-black-tree.spice index 75cca1f8b..8d218c661 100644 --- a/std/data/red-black-tree.spice +++ b/std/data/red-black-tree.spice @@ -158,7 +158,11 @@ public p RedBlackTree.remove(const K& key) { * @return The value for the given key */ public f RedBlackTree.find(const K& key) { - return this.search(key).value; + heap Node* node = this.search(key); + if node == nil*> { + panic(Error("The provided key was not found")); + } + return node.value; } /** diff --git a/std/data/unordered-map.spice b/std/data/unordered-map.spice index d43cb25b9..b76ffef9b 100644 --- a/std/data/unordered-map.spice +++ b/std/data/unordered-map.spice @@ -1,3 +1,6 @@ +import "std/data/hash-table"; +import "std/type/result"; + // Add generic type definitions type K dyn; type V dyn; @@ -11,9 +14,87 @@ type V dyn; * Lookup: O(1) (average case), O(n) (worst case) */ public type UnorderedMap struct { + HashTable hashTable +} + +public p UnorderedMap.ctor(unsigned long bucketCount = 100l) { + this.hashTable = HashTable(bucketCount); +} + +/** + * Insert a key-value pair into the map + * If the key already exists, the value is updated. + * + * @param key The key to insert + * @param value The value to insert + */ +public p UnorderedMap.upsert(const K& key, const V& value) { + this.hashTable.upsert(key, value); +} + +/** + * Retrieve the value associated with the given key. + * If the key is not found, panic. + * + * @param key The key to look up + * @return The value associated with the key + */ +public f UnorderedMap.get(const K& key) { + return this.hashTable.get(key); +} + +/** + * Retrieve the value associated with the given key as Optional. + * If the key is not found, return an empty optional. + * + * @param key The key to look up + * @return Optional, containing the value associated with the key or empty if the key is not found + */ +public f> UnorderedMap.getSafe(const K& key) { + return this.hashTable.getSafe(key); +} +/** + * Check if the map contains the given key. + * + * @param key The key to check for + * @return True if the key is found, false otherwise + */ +public p UnorderedMap.remove(const K& key) { + this.hashTable.remove(key); } -public p UnorderedMap.ctor() { +/** + * Check if the map contains the given key. + * + * @param key The key to check for + * @return True if the key is found, false otherwise + */ +public f UnorderedMap.contains(const K& key) { + return this.hashTable.contains(key); +} +/** + * Get the size of the unordered map. + * + * @return The number of key-value pairs in the map + */ +public f UnorderedMap.getSize() { + return this.hashTable.getSize(); +} + +/** + * Check if the unordered map is empty. + * + * @return True if empty, false otherwise + */ +public f UnorderedMap.isEmpty() { + return this.hashTable.isEmpty(); +} + +/** + * Clear the unordered map, removing all key-value pairs. + */ +public p UnorderedMap.clear() { + this.hashTable.clear(); } \ No newline at end of file diff --git a/test/test-files/std/data/unordered-map-normal-usecase/cout.out b/test/test-files/std/data/unordered-map-normal-usecase/cout.out new file mode 100644 index 000000000..d8347e3cc --- /dev/null +++ b/test/test-files/std/data/unordered-map-normal-usecase/cout.out @@ -0,0 +1 @@ +All assertions passed! diff --git a/test/test-files/std/data/unordered-map-normal-usecase/source.spice b/test/test-files/std/data/unordered-map-normal-usecase/source.spice new file mode 100644 index 000000000..716382ea1 --- /dev/null +++ b/test/test-files/std/data/unordered-map-normal-usecase/source.spice @@ -0,0 +1,38 @@ +import "std/data/unordered-map"; + +f main() { + UnorderedMap map = UnorderedMap(3l); + assert map.getSize() == 0; + assert map.isEmpty(); + map.upsert(1, "one"); + map.upsert(2, "two"); + map.upsert(3, "three"); + map.upsert(4, "four"); + assert map.getSize() == 4; + assert !map.isEmpty(); + assert map.contains(1); + assert map.contains(2); + assert map.contains(3); + assert map.contains(4); + assert !map.contains(5); + assert map.get(1) == "one"; + assert map.get(2) == "two"; + assert map.get(3) == "three"; + assert map.get(4) == "four"; + const Result item5 = map.getSafe(5); + assert item5.isErr(); + map.remove(2); + assert !map.contains(2); + assert map.getSize() == 3; + map.clear(); + assert map.getSize() == 0; + assert map.isEmpty(); + map.upsert(1, "one"); + map.upsert(1, "one new"); + assert map.getSize() == 1; + assert map.get(1) == "one new"; + map.remove(1); + assert map.isEmpty(); + + printf("All assertions passed!\n"); +} \ No newline at end of file