Data Structures¶
Overview¶
These data structures provide basic containers for the kernel and subsystems:
LinkedList<T>– singly-linked list with head/tail and basic operations.HashMap<K, V>– hash table with separate chaining usingLinkedList.Map<K, V>– simple fixed-capacity associative array.Pair<K, V>– minimal key–value struct used by other structures.
They all allocate from the kernel heap via new/delete (backed by MemoryManager).
LinkedList¶
Header: utils/ds/linkedlist.h
Structure¶
template <typename T>
class LinkedList {
public:
struct Node {
T data;
Node* next;
};
Node* head;
Node* tail;
uint32_t count;
// ...
};
- Singly-linked list with:
headpointing to the first node.tailpointing to the last node.counttracking list size.
Construction / Destruction¶
LinkedList() : head(nullptr), tail(nullptr), count(0) {}
~LinkedList() {
Node* temp = head;
while (temp != nullptr) {
Node* next = temp->next;
delete temp;
temp = next;
}
}
- Destructor frees all nodes using
delete, which in turn uses the kernel heap.
Operations¶
- Append – O(1) add to end:
void Append(T val) {
Node* node = new Node;
node->data = val;
node->next = 0;
if (head == 0) {
head = node;
tail = node;
} else {
tail->next = node;
tail = node;
}
count++;
}
- Prepend – O(1) add to front:
void Prepend(T val) {
Node* node = new Node;
node->data = val;
node->next = head;
head = node;
if (tail == 0) {
tail = node;
}
count++;
}
- Insert by index – O(n), insert before a given position:
void Insert(T val, uint32_t position) {
if (position >= count) return;
Node* node = new Node;
node->data = val;
Node* temp = head;
for (int i = 1; i < position - 1 && temp != nullptr; i++)
temp = temp->next;
node->next = temp->next;
temp->next = node;
count++;
}
- Insert before node by value – O(n):
void Insert(T val, T positionNode) {
Node* node = new Node;
Node* temp = head;
while (temp != nullptr && temp->next != positionNode) {
temp = temp->next;
}
Node* beforeNode = temp;
Node* afterNode = temp->next;
beforeNode->next = node;
node->next = afterNode;
count++;
}
-
Assumes
positionNodeis stored asNode*in practice; genericTuse here is fragile and should be used carefully or refactored. -
Remove by value – O(n):
void Remove(T val) {
Node* temp = FindOneBefore(val);
temp->next = val->next;
val->next = nullptr;
delete val;
}
-
Intended to remove the first occurrence; current implementation assumes
valis actually aNode*in some usages. For genericT, this should be revisited. -
Find – O(n), returns first node with matching data:
Node* Find(T val) {
Node* temp = head;
while (temp != nullptr) {
if (temp->data == val) return temp;
temp = temp->next;
}
return 0;
}
- FindOneBefore – O(n):
Node* FindOneBefore(T val) {
Node* temp = head;
while (temp != nullptr) {
if (temp->next->data == val && temp->next != nullptr)
return temp;
temp = temp->next;
}
return 0;
}
- RemoveHead – O(1):
void RemoveHead() {
if (head == 0) return;
Node* temp = head;
head = head->next;
delete temp;
count--;
if (head == 0) tail = 0;
}
- Size and emptiness:
- Printing:
void printList() {
Node* temp = head;
while (temp != nullptr) {
printType(temp->data);
if (temp->next != nullptr) printf(" -> ");
temp = temp->next;
}
printf("\n");
}
void printPairList() {
Node* temp = head;
if (temp == 0) {
printf(RED_COLOR, BLACK_COLOR, "[ERROR]: CANNOT PRINT EMPTY LIST\n");
}
while (temp != 0) {
printf("{");
printType(temp->data.key);
printf(", ");
printType(temp->data.value);
printf("}");
if (temp->next != 0)
printf(" -> ");
temp = temp->next;
}
printf("\n");
}
printTypeis a helper that knows how to print various types.printPairListassumesThas.keyand.value(e.g.,Pair<K, V>).
Notes / Caveats¶
- This list is singly-linked; no direct back-links apart from special logic in users.
- Some methods (notably
RemoveandInsert(T, T)) assumeTbehaves like a node pointer; users should be careful or refactor these methods for strictly value-based semantics. - All memory allocation uses
new/delete(kernel heap).
HashMap¶
Header: utils/ds/hashmap.h
Structure¶
- HashMap()
- ~HashMap()
- uint32_t GetBucketIndex(const K& key);
- void Insert(const K& key, const V& value);
- bool Get(const K& key, V& outValue);
- void Remove(const K& key);
- bool Contains(const K& key);
- void GetKeys(LinkedList
& dest); - void GetValues(LinkedList
& dest); - void GetPairs(LinkedList
>& dest);
template <typename K, typename V, uint32_t MaxSize = 1024>
class HashMap {
private:
struct HashNode {
K key;
V value;
bool operator==(const HashNode& other) const {
return Hasher<K>::isEqual(this->key, other.key);
}
};
LinkedList<HashNode>* buckets[MaxSize];
uint32_t capacity = MaxSize;
uint32_t count;
// ...
};
- Separate chaining hash map:
buckets[i]is a pointer to aLinkedList<HashNode>representing a chain.- Uses a
Hasher<K>specialization (fromutils/hash.h) for hashing and equality.
Construction / Destruction¶
HashMap() {
for (int i = 0; i < capacity; i++)
buckets[i] = 0;
count = 0;
}
~HashMap() {
for (int i = 0; i < capacity; i++)
if (buckets[i] != 0)
delete buckets[i];
}
- Destructor deletes each bucket list, which in turn deletes its nodes.
Bucket index¶
Hasher<K>::Hashmust be provided for the key typeK.
Insert¶
void Insert(const K& key, const V& value) {
uint32_t index = GetBucketIndex(key);
if (buckets[index] == 0)
buckets[index] = (LinkedList<HashNode>*)new LinkedList<HashNode>;
HashNode searchNode;
searchNode.key = *key;
auto* existingNode = buckets[index]->Find(searchNode);
if (existingNode != 0) {
existingNode->data.value = *value; // update existing
return;
}
searchNode.value = value;
buckets[index]->Append(searchNode);
count++;
}
- Behavior:
- If the bucket is empty, a
LinkedList<HashNode>is created. - If key already exists, value is updated.
- Otherwise, a new
HashNodeis appended.
Lookup¶
bool Get(const K& key, V& outValue) {
uint32_t index = GetBucketIndex(key);
if (buckets[index] == 0) return false;
HashNode searchNode;
searchNode.key = key;
auto* existingNode = buckets[index]->Find(searchNode);
if (existingNode != 0) {
outValue = existingNode->data.value;
return true;
}
return false;
}
- Returns
trueand writes intooutValueif key exists,falseotherwise.
Remove¶
void Remove(const K& key) {
uint32_t index = GetBucketIndex(key);
if (buckets[index] == 0) return;
HashNode dummy;
dummy.key = key;
buckets[index]->Remove(dummy);
}
- Relies on
LinkedList<HashNode>::Removematching byHashNode::operator==.
Other utilities¶
- Contains:
bool Contains(const K& key) {
uint32_t index = GetBucketIndex(key);
if (buckets[index] == 0) return false;
HashNode searchNode;
searchNode.key = key;
return buckets[index]->Find(searchNode) != 0;
}
- Size / empty:
- Clear:
void Clear() {
for (uint32_t i = 0; i < capacity; i++) {
if (buckets[i] != 0) {
delete buckets[i];
buckets[i] = 0;
}
}
count = 0;
}
- GetKeys:
void GetKeys(LinkedList<K>& dest) {
for (uint32_t i = 0; i < capacity; i++)
if (buckets[i] != 0) {
auto* temp = buckets[i]->head;
while (temp != 0) {
dest.Append(temp->data.key);
temp = temp->next;
}
}
}
- GetValues:
void GetValues(LinkedList<V>& dest) {
for (uint32_t i = 0; i < capacity; i++)
if (buckets[i] != 0) {
auto* temp = buckets[i]->head;
while (temp != 0) {
dest.Append(temp->data.value);
temp = temp->next;
}
}
}
- GetPairs:
void GetPairs(LinkedList<Pair<K, V>>& dest) {
for (uint32_t i = 0; i < capacity; i++) {
if (buckets[i] != 0) {
auto* temp = buckets[i]->head;
while (temp != 0) {
Pair<K, V> pair;
pair.key = temp->data.key;
pair.value = temp->data.value;
dest.Append(pair);
temp = temp->next;
}
}
}
}
Notes¶
- Capacity (
MaxSize) is fixed at compile time; there is no dynamic resizing. - Complexity:
- Average operations are O(1) with good hashing and low load factor.
- Worst case (all keys in one bucket) degrades to O(n) due to list traversal.
- NOTE: (2/21/2026) changed to value/reference based (K&/V&) instead of pointers (K/V)
Map¶
Header: utils/ds/map.h
Structure¶
template <typename K, typename V, uint32_t MaxSize = 512>
class Map {
private:
struct Pair {
K key;
V value;
bool active;
};
Pair pairs[MaxSize];
uint32_t count;
// ...
};
- Simple associative array backed by a fixed-size array of
Pair { key, value, active }.
Construction / Destruction¶
- Destructor is declared but not defined in the header; implementation can be added if needed (currently no dynamic allocation inside
Mapitself).
put¶
bool put(K key, V value) {
// Update if key exists
for (uint32_t i = 0; i < MaxSize; i++) {
if (pairs[i].active && pairs[i].key == key) {
pairs[i].value = value;
return true;
}
}
if (count >= MaxSize) return false;
// Insert into first inactive slot
for (uint32_t i = 0; i < MaxSize; i++) {
if (!pairs[i].active) {
pairs[i].key = key;
pairs[i].value = value;
pairs[i].active = true;
count++;
return true;
}
}
return false;
}
- Returns:
trueif insertion or update succeeded.falseif container is full and a new key cannot be added.
get¶
V get(K key) {
for (uint32_t i = 0; i < MaxSize; i++) {
if (pairs[i].active && pairs[i].key == key)
return pairs[i].value;
}
return V();
}
- Performs a linear scan.
- Returns default-constructed
Vif not found.
contains¶
bool contains(K key) {
for (uint32_t i = 0; i < MaxSize; i++) {
if (pairs[i].active && pairs[i].key == key)
return true;
}
return false;
}
Accessors¶
Pair* getPair(uint32_t index) {
if (index < 0 || index >= MaxSize) return 0;
return &pairs[index];
}
uint32_t size() { return count; }
uint32_t max_capacity() { return MaxSize; }
getPairreturns a pointer to the internalPairat an index (even if inactive).
Notes¶
Mapis useful when:- Maximum number of key–value pairs is known and small.
- Simpler semantics are desired compared to
HashMap. - All operations are O(MaxSize), which is acceptable for small sizes.
Pair¶
Header: utils/ds/pair.h
- Minimal utility struct used by:
HashMap::GetPairs.LinkedList::printPairList.- Does not have comparison operators or methods; equality and hashing are provided elsewhere when needed.
Usage Examples¶
LinkedList¶
os::utils::ds::LinkedList<const char*> list;
list.Append("foo");
list.Append("bar");
list.Prepend("head");
list.printList(); // head -> foo -> bar
HashMap¶
os::utils::ds::HashMap<const char*, int> map;
const char* key1 = "cpu_count";
int val1 = 4;
map.Insert(&key1, &val1);
int out = 0;
if (map.Get(&key1, &out)) {
// out == 4
}
Map¶
os::utils::ds::Map<int, const char*> smallMap;
smallMap.put(1, "one");
smallMap.put(2, "two");
if (smallMap.contains(2)) {
const char* v = smallMap.get(2); // "two"
}
HashMap keys/values/pairs¶
os::utils::ds::LinkedList<const char*> keys;
map.GetKeys(&keys);
keys.printList();
os::utils::ds::LinkedList<int> values;
map.GetValues(&values);
values.printList();
os::utils::ds::LinkedList<Pair<const char*, int>> pairs;
map.GetPairs(&pairs);
pairs.printPairList();
Invariants and Considerations¶
- All containers use
new/deleteand thus rely on a correctly initializedMemoryManager. - None of the structures are thread-safe; they assume a single‑threaded or externally synchronized context.
HashMap:- Capacity is fixed; there is no dynamic expansion.
- Correctness depends on
Hasher<K>being well-defined. LinkedList:- Some methods assume that
Tbehaves like a pointer or struct with specific fields; future refactors should tighten the API or specialize for particular uses. Map:- Simple and predictable memory usage, but O(MaxSize) operations.
Future improvements to these data structures (e.g., better type safety for LinkedList::Remove, thread-safety, iterators) should be reflected in this document.
```