# 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; } }