# Copyright (c) 2023 Yuki Kimoto
# MIT License
class Hash {
interface Cloneable;
use Hash::Entry;
use Fn;
use Array;
use Time;
use Sort;
has bucket_count : int;
has count : ro int;
has entries : Hash::Entry[];
has seed128 : private mutable string;
private enum {
_DEFALUT_HASH_TABLE_LENGTH = 16,
}
private static method _MAX_LOAD_FACTOR : double () { return 0.75; }
precompile static method new : Hash ($key_values = undef : object[]) {
my $self = new Hash;
my $default_capacity = 32;
$self->{bucket_count} = Hash->_DEFALUT_HASH_TABLE_LENGTH();
$self->{entries} = new Hash::Entry[Hash->_DEFALUT_HASH_TABLE_LENGTH()];
$self->{count} = 0;
my $seed_int32 = (int)(Time->time * class_id Hash);
$self->{seed128} = (mutable string)new_string_len 16;
$self->build_seed128($seed_int32, $self->{seed128});
my $length : int;
if ($key_values) {
$length = @$key_values;
}
else {
$length = 0;
}
if ($length % 2 != 0) {
die "The length of the elements in the \$key_values must be an even number";
}
if ($key_values) {
for (my $i = 0; $i < @$key_values; $i += 2) {
my $key = (string)$key_values->[$i];
my $value = $key_values->[$i + 1];
unless ($key isa string) {
die "The key in the \$key_values must be string";
}
$self->set($key => $value);
}
}
return $self;
}
native private method build_seed128 : void ($seed_int32 : int, $seed128 : mutable string);
method set : void ($key : string, $val : object) {
unless ($key) {
die "The \$key must be defined";
}
# Rehash
if (((double)$self->{count} / $self->{bucket_count}) > Hash->_MAX_LOAD_FACTOR()) {
$self->_rehash;
}
my $count = $self->{count};
$self->_set_to_container($self->{entries}, $self->{bucket_count}, \$count, $key, $val);
$self->{count} = $count;
}
precompile method get : object ($key : string) {
my $index = $self->_index_by_key($key, $self->{bucket_count});
my $ref = $self->{entries}->[$index];
unless ($ref) {
return undef;
}
while (1) {
if ($ref->{key} eq $key) {
return $ref->{val};
}
unless ($ref->{next_entry}) {
return undef;
}
$ref = $ref->{next_entry};
}
}
precompile method exists : int ($key : string) {
my $index = $self->_index_by_key($key, $self->{bucket_count});
my $ref = $self->{entries}->[$index];
unless ($ref) {
return 0;
}
while (1) {
if ($ref->{key} eq $key) {
return 1;
}
unless ($ref->{next_entry}) {
return 0;
}
$ref = $ref->{next_entry};
}
}
precompile method delete : object ($key : string) {
my $index = $self->_index_by_key($key, $self->{bucket_count});
my $ref = $self->{entries}->[$index];
my $prev : Hash::Entry = undef;
unless ($ref) {
return undef;
}
while (1) {
if ($ref->{key} eq $key) {
my $ret = $ref->{val};
if ($prev) {
$prev->{next_entry} = $ref->{next_entry};
}
else {
$self->{entries}->[$index] = $ref->{next_entry};
}
$self->{count}--;
return $ret;
}
unless ($ref->{next_entry}) {
return undef;
}
$prev = $ref;
$ref = $ref->{next_entry};
}
}
precompile method keys : string[] () {
my $keys = new string[$self->{count}];
# iterate entries
my $count = 0;
for (my $i = 0; $i < $self->{bucket_count}; ++$i) {
my $ref = $self->{entries}->[$i];
while ($ref) {
$keys->[$count++] = $ref->{key};
$ref = $ref->{next_entry};
}
}
return $keys;
}
precompile method values : object[] () {
my $retval = new object[$self->{count}];
# iterate entries
my $count = 0;
for (my $i = 0; $i < $self->{bucket_count}; ++$i) {
my $ref = $self->{entries}->[$i];
while ($ref) {
$retval->[$count++] = $ref->{val};
$ref = $ref->{next_entry};
}
}
return $retval;
}
precompile method copy : Hash () {
my $ret = Hash->new({});
my $keys = $self->keys;
for (my $i = 0; $i < @$keys; ++$i) {
$ret->set($keys->[$i], $self->get($keys->[$i]));
}
return $ret;
}
precompile method to_array : object[] ($sort = 0 : int) {
my $keys = $self->keys;
my $keys_length = @$keys;
if ($sort) {
Sort->sort_string_asc($keys);
}
my $array = new object[$keys_length * 2];
for (my $i = 0; $i < $keys_length; $i++) {
my $key = $keys->[$i];
$array->[$i * 2] = $key;
my $value = $self->get($key);
$array->[$i * 2 + 1] = $value;
}
return $array;
}
method clone : Hash () {
return $self->copy;
}
native static method _murmur_hash : long ($string : string, $seed : int);
private native static method _siphash13 : long ($string : string, $seed : string);
method set_byte : void ($key : string, $value : int) {
$self->set($key => Byte->new($value));
}
method set_short : void ($key : string, $value : int) {
$self->set($key => Short->new($value));
}
method set_int : void ($key : string, $value : int) {
$self->set($key => Int->new($value));
}
method set_string : void ($key : string, $value : string) {
$self->set($key => $value);
}
method set_long : void ($key : string, $value : long) {
$self->set($key => Long->new($value));
}
method set_float : void ($key : string, $value : float) {
$self->set($key => Float->new($value));
}
method set_double : void ($key : string, $value : double) {
$self->set($key => Double->new($value));
}
method get_byte : int ($key : string) {
return (byte)$self->get($key);
}
method get_short : int ($key : string) {
return (short)$self->get($key);
}
method get_int : int ($key : string) {
return (int)$self->get($key);
}
method get_string : string ($key : string) {
return (string)$self->get($key);
}
method get_long : long ($key : string) {
return (long)$self->get($key);
}
method get_float : float ($key : string) {
return (float)$self->get($key);
}
method get_double : double ($key : string) {
return (double)$self->get($key);
}
method _bucket_count : int () {
return $self->{bucket_count};
}
method _entries : Hash::Entry [] () {
return $self->{entries};
}
method _index_by_key : int ($key : string, $bucket_count : int) {
my $default_seed = $self->{seed128};
my $hash = Hash->_siphash13($key, $default_seed);
my $index_by_key = (int)($hash remul (long)$bucket_count);
return $index_by_key;
}
precompile method _set_to_container : void (
$entries : Hash::Entry[],
$bucket_count : int,
$count_ref : int*,
$key : string,
$val : object)
{
my $index = $self->_index_by_key($key, $bucket_count);
my $ref = $entries->[$index];
unless ($ref) {
$entries->[$index] = new Hash::Entry;
my $new_key : string;
if (is_read_only $key) {
$new_key = $key;
}
else {
$new_key = Fn->copy_string($key);
make_read_only $new_key;
}
$entries->[$index]->{key} = $new_key;
$entries->[$index]->{val} = $val;
$entries->[$index]->{next_entry} = undef;
++$$count_ref;
return;
}
while (1) {
if ($ref->{key} eq $key) {
$ref->{val} = $val;
return;
}
unless ($ref->{next_entry}) {
my $new_key : string;
if (is_read_only $key) {
$new_key = $key;
}
else {
$new_key = Fn->copy_string($key);
make_read_only $new_key;
}
$ref->{next_entry} = new Hash::Entry;
$ref->{next_entry}->{key} = $new_key;
$ref->{next_entry}->{val} = $val;
$ref->{next_entry}->{next_entry} = undef;
++$$count_ref;
return;
}
$ref = $ref->{next_entry};
}
}
precompile method _rehash : void () {
my $new_bucket_count : int;
$new_bucket_count = $self->{bucket_count} * 2;
my $new_entries = new Hash::Entry [$new_bucket_count];
my $new_count = 0;
# iterate entries
for (my $i = 0; $i < $self->{bucket_count}; ++$i) {
my $ref = $self->{entries}->[$i];
while ($ref) {
$self->_set_to_container($new_entries, $new_bucket_count, \$new_count, $ref->{key}, $ref->{val});
$ref = $ref->{next_entry};
}
}
$self->{bucket_count} = $new_bucket_count;
$self->{entries} = $new_entries;
}
method delete_or_default_byte : int ($key : string, $default : int) {
unless ($key) {
die "The \$key must be defined";
}
my $value = 0;
if ($self->exists($key)) {
$value = (byte)$self->get_byte($key);
$self->delete($key);
}
else {
$value = (byte)$default;
}
return $value;
}
method delete_or_default_short : int ($key : string, $default : int) {
unless ($key) {
die "The \$key must be defined";
}
my $value = 0;
if ($self->exists($key)) {
$value = (short)$self->get_short($key);
$self->delete($key);
}
else {
$value = (short)$default;
}
return $value;
}
method delete_or_default_int : int ($key : string, $default : int) {
unless ($key) {
die "The \$key must be defined";
}
my $value = 0;
if ($self->exists($key)) {
$value = $self->get_int($key);
$self->delete($key);
}
else {
$value = $default;
}
return $value;
}
method delete_or_default_long : long ($key : string, $default : long) {
unless ($key) {
die "The \$key must be defined";
}
my $value = 0L;
if ($self->exists($key)) {
$value = (long)$self->get_long($key);
$self->delete($key);
}
else {
$value = $default;
}
return $value;
}
method delete_or_default_float : float ($key : string, $default : float) {
unless ($key) {
die "The \$key must be defined";
}
my $value = 0f;
if ($self->exists($key)) {
$value = $self->get_float($key);
$self->delete($key);
}
else {
$value = $default;
}
return $value;
}
method delete_or_default_double : double ($key : string, $default : double) {
unless ($key) {
die "The \$key must be defined";
}
my $value = 0.0;
if ($self->exists($key)) {
$value = $self->get_double($key);
$self->delete($key);
}
else {
$value = $default;
}
return $value;
}
method delete_or_default_string : string ($key : string, $default : string) {
unless ($key) {
die "The \$key must be defined";
}
my $value = (string)undef;
if ($self->exists($key)) {
$value = $self->get_string($key);
$self->delete($key);
}
else {
$value = $default;
}
return $value;
}
method delete_or_default : object ($key : string, $default : object) {
unless ($key) {
die "The \$key must be defined";
}
my $value = (object)undef;
if ($self->exists($key)) {
$value = $self->get($key);
$self->delete($key);
}
else {
$value = $default;
}
return $value;
}
}