Files
containers/sources/map.c
T

675 lines
16 KiB
C
Raw Normal View History

2026-09-12 12:54:20 -05:00
#include <memory.h>
#include "malunal/containers/map.h"
#define MAP_CONTROL_EMPTY ((malunal_uint8_t)0x00)
#define MAP_CONTROL_ERASED ((malunal_uint8_t)0x01)
#define MAP_CONTROL_FILLED ((malunal_uint8_t)0x02)
#define MAP_LOAD_FACTOR 0.75f
#define MAP_NO_SLOT ((malunal_size_t)-1)
#define MAP_HASH_BASIS 0xCBF29CE484222325ULL
#define MAP_HASH_PRIME 0x00000100000001B3ULL
typedef struct {
container_t container;
allocator_mptr_t allocator;
malunal_size_t keystride;
malunal_size_t valstride;
malunal_size_t count;
malunal_size_t capacity;
malunal_mptr_t buffer;
} impl_t;
typedef impl_t* impl_mptr_t;
typedef const impl_t* impl_iptr_t;
_Static_assert(
sizeof(map_container_t) == sizeof(impl_t),
"Map container must be the size of its implementation"
);
static const
container_vtable_t map_container_vtable = {
.allocator = (container_allocator_pfn_t)&map_container_allocator,
.stride = (container_stride_pfn_t)&map_container_stride,
.count = (container_count_pfn_t)&map_container_count,
.capacity = (container_capacity_pfn_t)&map_container_capacity,
.resize = (container_resize_pfn_t)&map_container_resize,
.reserve = (container_reserve_pfn_t)&map_container_reserve,
.append = (container_append_pfn_t)&map_container_append,
.remove = (container_remove_pfn_t)&map_container_remove,
.contains = (container_contains_pfn_t)&map_container_contains,
.clear = (container_clear_pfn_t)&map_container_clear
};
static
malunal_size_t
power_2_ceil(malunal_size_t capacity) {
if (capacity <= 1)
return 1;
capacity--;
malunal_size_t power = 2;
while (capacity >>= 1)
power <<= 1;
return power;
}
static
malunal_size_t
map_container_bytes(
malunal_size_t keystride,
malunal_size_t valstride,
malunal_size_t capacity
) {
return capacity + (keystride + valstride) * capacity;
}
static
malunal_uint8_t*
map_container_control(impl_iptr_t self) {
return (malunal_uint8_t*)self->buffer;
}
static
malunal_uint8_t*
map_container_key(
impl_iptr_t self,
malunal_size_t index
) {
malunal_uint8_t* slots =
map_container_control(self) +
self->capacity;
return slots + (self->keystride + self->valstride) * index;
}
static
malunal_uint8_t*
map_container_value(
impl_iptr_t self,
malunal_size_t index
) {
return
map_container_key(self, index) +
self->keystride;
}
static
malunal_size_t
map_container_hash(
malunal_iptr_t key,
malunal_size_t keystride
) {
malunal_uint8_t* bytes = key;
malunal_uint64_t hash = MAP_HASH_BASIS;
for (malunal_size_t index = 0; index < keystride; index++) {
hash ^= (malunal_uint64_t)bytes[index];
hash *= MAP_HASH_PRIME;
}
return (malunal_size_t)hash;
}
static
malunal_bool_t
map_container_find(
impl_iptr_t self,
malunal_iptr_t key,
malunal_size_t* outidx
) {
*outidx = MAP_NO_SLOT;
if (self->buffer == null || self->capacity == 0)
return false;
malunal_uint8_t* control = map_container_control(self);
malunal_size_t mask = self->capacity - 1;
malunal_size_t index = map_container_hash(key, self->keystride) & mask;
malunal_size_t vacancy = MAP_NO_SLOT;
malunal_size_t probed = 0;
while (probed < self->capacity) {
if (control[index] == MAP_CONTROL_EMPTY) {
*outidx = vacancy != MAP_NO_SLOT ? vacancy : index;
return false;
}
if (control[index] == MAP_CONTROL_ERASED) {
if (vacancy == MAP_NO_SLOT)
vacancy = index;
} else if (memcmp(map_container_key(self, index), key, self->keystride) == 0) {
*outidx = index;
return true;
}
index = (index + 1) & mask;
probed += 1;
}
*outidx = vacancy;
return false;
}
static
malunal_void_t
map_container_place(
impl_mptr_t self,
malunal_size_t index,
malunal_iptr_t key,
malunal_iptr_t value
) {
map_container_control(self)[index] = MAP_CONTROL_FILLED;
memcpy(map_container_key(self, index), key, self->keystride);
if (self->valstride != 0)
memcpy(map_container_value(self, index), value, self->valstride);
}
static
error_t
map_container_realloc(
impl_mptr_t self,
malunal_size_t capacity
) {
if (capacity <= self->capacity)
return NO_ERROR;
malunal_mptr_t buffer;
malunal_size_t newcap = power_2_ceil(capacity);
malunal_size_t bytes = map_container_bytes(
self->keystride,
self->valstride,
newcap
);
error_t result = allocator_acquire(self->allocator, bytes, &buffer);
if (result.domain != null)
return result;
memset(buffer, MAP_CONTROL_EMPTY, newcap);
impl_t rehashed = *self;
rehashed.buffer = buffer;
rehashed.capacity = newcap;
malunal_uint8_t* control = map_container_control(self);
for (malunal_size_t index = 0; index < self->capacity; index++) {
if (control[index] != MAP_CONTROL_FILLED)
continue;
malunal_size_t slot;
malunal_uint8_t* key = map_container_key(self, index);
map_container_find(&rehashed, key, &slot);
map_container_place(&rehashed, slot, key, map_container_value(self, index));
}
if (self->buffer != null) {
bytes = map_container_bytes(
self->keystride,
self->valstride,
self->capacity
);
result = allocator_dispose(self->allocator, self->buffer, bytes);
}
self->buffer = buffer;
self->capacity = newcap;
return result;
}
error_t
map_container_init(
malunal_size_t keystride,
malunal_size_t valstride,
allocator_mptr_t allocator,
map_container_mptr_t map
) {
if (map == null)
return (error_t) {
.domain = &ERROR_DOMAIN_CONTAINER_T,
.code = CONTAINER_ERROR_NULL_CONTAINER
};
impl_mptr_t self = (impl_mptr_t)map;
malunal_size_t capacity = power_2_ceil(MALUNAL_CONTAINERS_MAP_INIT_CAPACITY);
malunal_size_t bytes = map_container_bytes(keystride, valstride, capacity);
error_t result = allocator_acquire(allocator, bytes, &self->buffer);
if (result.domain != null)
return result;
memset(self->buffer, MAP_CONTROL_EMPTY, capacity);
self->container = (container_t){ &map_container_vtable };
self->allocator = allocator;
self->keystride = keystride;
self->valstride = valstride;
self->count = 0;
self->capacity = capacity;
return NO_ERROR;
}
error_t
map_container_free(
map_container_mptr_t map
) {
if (map == null)
return (error_t) {
.domain = &ERROR_DOMAIN_CONTAINER_T,
.code = CONTAINER_ERROR_NULL_CONTAINER
};
impl_mptr_t self = (impl_mptr_t)map;
error_t result = allocator_dispose(
self->allocator,
self->buffer,
map_container_bytes(self->keystride, self->valstride, self->capacity)
);
if (result.domain != null)
return result;
self->keystride = 0;
self->valstride = 0;
self->count = 0;
self->capacity = 0;
self->buffer = null;
return NO_ERROR;
}
error_t
map_container_allocator(
map_container_iptr_t map,
allocator_mptr_t* out
) {
if (map == null)
return (error_t) {
.domain = &ERROR_DOMAIN_CONTAINER_T,
.code = CONTAINER_ERROR_NULL_CONTAINER
};
impl_iptr_t self = (impl_iptr_t)map;
*out = self->allocator;
return NO_ERROR;
}
error_t
map_container_stride(
map_container_iptr_t map,
malunal_size_t* out
) {
if (map == null)
return (error_t) {
.domain = &ERROR_DOMAIN_CONTAINER_T,
.code = CONTAINER_ERROR_NULL_CONTAINER
};
impl_iptr_t self = (impl_iptr_t)map;
*out = self->keystride + self->valstride;
return NO_ERROR;
}
error_t
map_container_key_stride(
map_container_iptr_t map,
malunal_size_t* out
) {
if (map == null)
return (error_t) {
.domain = &ERROR_DOMAIN_CONTAINER_T,
.code = CONTAINER_ERROR_NULL_CONTAINER
};
impl_iptr_t self = (impl_iptr_t)map;
*out = self->keystride;
return NO_ERROR;
}
error_t
map_container_value_stride(
map_container_iptr_t map,
malunal_size_t* out
) {
if (map == null)
return (error_t) {
.domain = &ERROR_DOMAIN_CONTAINER_T,
.code = CONTAINER_ERROR_NULL_CONTAINER
};
impl_iptr_t self = (impl_iptr_t)map;
*out = self->valstride;
return NO_ERROR;
}
error_t
map_container_count(
map_container_iptr_t map,
malunal_size_t* out
) {
if (map == null)
return (error_t) {
.domain = &ERROR_DOMAIN_CONTAINER_T,
.code = CONTAINER_ERROR_NULL_CONTAINER
};
impl_iptr_t self = (impl_iptr_t)map;
*out = self->count;
return NO_ERROR;
}
error_t
map_container_capacity(
map_container_iptr_t map,
malunal_size_t* out
) {
if (map == null)
return (error_t) {
.domain = &ERROR_DOMAIN_CONTAINER_T,
.code = CONTAINER_ERROR_NULL_CONTAINER
};
impl_iptr_t self = (impl_iptr_t)map;
*out = self->capacity;
return NO_ERROR;
}
error_t
map_container_keys(
map_container_iptr_t map,
container_mptr_t container
) {
if (map == null)
return (error_t) {
.domain = &ERROR_DOMAIN_CONTAINER_T,
.code = CONTAINER_ERROR_NULL_CONTAINER
};
if (container == null)
return (error_t) {
.domain = &ERROR_DOMAIN_CONTAINER_T,
.code = CONTAINER_ERROR_NULL_RECEIVER
};
malunal_size_t stride = 0;
impl_iptr_t self = (impl_iptr_t)map;
error_t result = container_stride(container, &stride);
if (result.domain != null)
return result;
if (stride != self->keystride)
return (error_t) {
.domain = &ERROR_DOMAIN_CONTAINER_T,
.code = CONTAINER_ERROR_FAILURE
};
malunal_uint8_t* control = map_container_control(self);
for (malunal_size_t index = 0; index < self->capacity; index++) {
if (control[index] != MAP_CONTROL_FILLED)
continue;
result = container_append(container, map_container_key(self, index));
if (result.domain != null)
return result;
}
return NO_ERROR;
}
error_t
map_container_values(
map_container_iptr_t map,
container_mptr_t container
) {
if (map == null)
return (error_t) {
.domain = &ERROR_DOMAIN_CONTAINER_T,
.code = CONTAINER_ERROR_NULL_CONTAINER
};
if (container == null)
return (error_t) {
.domain = &ERROR_DOMAIN_CONTAINER_T,
.code = CONTAINER_ERROR_NULL_RECEIVER
};
malunal_size_t stride = 0;
impl_iptr_t self = (impl_iptr_t)map;
error_t result = container_stride(container, &stride);
if (result.domain != null)
return result;
if (stride != self->valstride)
return (error_t) {
.domain = &ERROR_DOMAIN_CONTAINER_T,
.code = CONTAINER_ERROR_FAILURE
};
malunal_uint8_t* control = map_container_control(self);
for (malunal_size_t index = 0; index < self->capacity; index++) {
if (control[index] != MAP_CONTROL_FILLED)
continue;
result = container_append(container, map_container_value(self, index));
if (result.domain != null)
return result;
}
return NO_ERROR;
}
error_t
map_container_resize(
map_container_mptr_t map,
malunal_size_t capacity
) {
if (map == null)
return (error_t) {
.domain = &ERROR_DOMAIN_CONTAINER_T,
.code = CONTAINER_ERROR_NULL_CONTAINER
};
impl_mptr_t self = (impl_mptr_t)map;
return map_container_realloc(self, capacity);
}
error_t
map_container_reserve(
map_container_mptr_t map,
malunal_size_t capacity
) {
if (map == null)
return (error_t) {
.domain = &ERROR_DOMAIN_CONTAINER_T,
.code = CONTAINER_ERROR_NULL_CONTAINER
};
impl_mptr_t self = (impl_mptr_t)map;
return map_container_realloc(self, (capacity * 4) / 3 + 1);
}
error_t
map_container_append(
map_container_mptr_t map,
map_pair_iptr_t pair
) {
if (map == null)
return (error_t) {
.domain = &ERROR_DOMAIN_CONTAINER_T,
.code = CONTAINER_ERROR_NULL_CONTAINER
};
if (pair == null)
return (error_t) {
.domain = &ERROR_DOMAIN_CONTAINER_T,
.code = CONTAINER_ERROR_FAILURE
};
return map_container_insert(map, pair->key, pair->value);
}
error_t
map_container_insert(
map_container_mptr_t map,
malunal_iptr_t key,
malunal_iptr_t value
) {
if (map == null)
return (error_t) {
.domain = &ERROR_DOMAIN_CONTAINER_T,
.code = CONTAINER_ERROR_NULL_CONTAINER
};
error_t result = NO_ERROR;
impl_mptr_t self = (impl_mptr_t)map;
if (self->capacity == 0) {
result = map_container_realloc(self, MALUNAL_CONTAINERS_MAP_INIT_CAPACITY);
if (result.domain != null)
return result;
}
malunal_size_t slot;
if (map_container_find(self, key, &slot)) {
if (self->valstride != 0)
memcpy(map_container_value(self, slot), value, self->valstride);
return NO_ERROR;
}
if (slot == MAP_NO_SLOT ||
(float)(self->count + 1) / self->capacity >= MAP_LOAD_FACTOR) {
result = map_container_realloc(self, self->capacity * 2);
if (result.domain != null)
return result;
map_container_find(self, key, &slot);
}
if (slot == MAP_NO_SLOT)
return (error_t) {
.domain = &ERROR_DOMAIN_CONTAINER_T,
.code = CONTAINER_ERROR_FAILURE
};
map_container_place(self, slot, key, value);
self->count++;
return NO_ERROR;
}
error_t
map_container_remove(
map_container_mptr_t map,
malunal_iptr_t key
) {
if (map == null)
return (error_t) {
.domain = &ERROR_DOMAIN_CONTAINER_T,
.code = CONTAINER_ERROR_NULL_CONTAINER
};
malunal_size_t slot;
impl_mptr_t self = (impl_mptr_t)map;
if (!map_container_find(self, key, &slot))
return NO_ERROR;
malunal_uint8_t* control = map_container_control(self);
malunal_size_t mask = self->capacity - 1;
control[slot] = MAP_CONTROL_ERASED;
self->count--;
if (control[(slot + 1) & mask] != MAP_CONTROL_EMPTY)
return NO_ERROR;
malunal_size_t index = slot;
while (control[index] == MAP_CONTROL_ERASED) {
control[index] = MAP_CONTROL_EMPTY;
index = (index - 1) & mask;
}
return NO_ERROR;
}
error_t
map_container_contains(
map_container_iptr_t map,
malunal_iptr_t key
) {
if (map == null)
return (error_t) {
.domain = &ERROR_DOMAIN_CONTAINER_T,
.code = CONTAINER_ERROR_NULL_CONTAINER
};
malunal_size_t slot;
impl_iptr_t self = (impl_iptr_t)map;
return map_container_find(self, key, &slot)
? NO_ERROR
: (error_t) {
.domain = &ERROR_DOMAIN_CONTAINER_T,
.code = CONTAINER_ERROR_FAILURE
};
}
error_t
map_container_clear(
map_container_mptr_t map
) {
if (map == null)
return (error_t) {
.domain = &ERROR_DOMAIN_CONTAINER_T,
.code = CONTAINER_ERROR_NULL_CONTAINER
};
impl_mptr_t self = (impl_mptr_t)map;
if (self->buffer != null)
memset(self->buffer, MAP_CONTROL_EMPTY, self->capacity);
self->count = 0;
return NO_ERROR;
}
error_t
map_container_get(
map_container_iptr_t map,
malunal_iptr_t key,
malunal_mptr_t value
) {
if (map == null)
return (error_t) {
.domain = &ERROR_DOMAIN_CONTAINER_T,
.code = CONTAINER_ERROR_NULL_CONTAINER
};
malunal_size_t slot;
impl_iptr_t self = (impl_iptr_t)map;
if (!map_container_find(self, key, &slot))
return (error_t) {
.domain = &ERROR_DOMAIN_CONTAINER_T,
.code = CONTAINER_ERROR_OUT_OF_BOUNDS
};
if (self->valstride != 0)
memcpy(value, map_container_value(self, slot), self->valstride);
return NO_ERROR;
}
error_t
map_container_set(
map_container_mptr_t map,
malunal_iptr_t key,
malunal_iptr_t value
) {
if (map == null)
return (error_t) {
.domain = &ERROR_DOMAIN_CONTAINER_T,
.code = CONTAINER_ERROR_NULL_CONTAINER
};
malunal_size_t slot;
impl_mptr_t self = (impl_mptr_t)map;
if (!map_container_find(self, key, &slot))
return (error_t) {
.domain = &ERROR_DOMAIN_CONTAINER_T,
.code = CONTAINER_ERROR_OUT_OF_BOUNDS
};
if (self->valstride != 0)
memcpy(map_container_value(self, slot), value, self->valstride);
return NO_ERROR;
}