Algorithm-Toy-HashSC

 view release on metacpan or  search on metacpan

lib/Algorithm/Toy/HashSC.pm  view on Meta::CPAN

# -*- Perl -*-
#
# Toy deterministic separate chain hash implementation, based on code in
# "Algorithms (4th Edition)" by Robert Sedgewick and Kevin Wayne. This
# code is not for any sort of use where performance is critical, or
# where malicious input may cause "Algorithmic Complexity Attacks" (see
# perlsec(1)).
#
# run perldoc(1) on this file for additional documentation

package Algorithm::Toy::HashSC;

use 5.010;
use strict;
use warnings;

use Carp qw/croak/;
use Moo;
use namespace::clean;
use Scalar::Util qw/looks_like_number/;

our $VERSION = '0.02';

##############################################################################
#
# ATTRIBUTES

# Each list should end up with ~N/M key-value pairs, assuming the input
# is not malicious, and that the hash function is perfect enough. "M"
# here is the modulus, and "N" is the number of key-value pairs added.
#
# Internally, it's an array of array of arrays, or something like that.
has _chain => (
    is      => 'rw',
    default => sub { [] },
);

has modulus => (
    is      => 'rw',
    default => sub { 7 },
    coerce  => sub {
        die 'modulus must be a positive integer > 1'
          if !defined $_[0]
          or !looks_like_number $_[0]
          or $_[0] < 2;
        return int $_[0];
    },
    trigger => sub {
        my ($self) = @_;
        # clobber extant hash (Moo does not provide old value, so cannot do
        # this only when the modulus changes, oh well)
        $self->_chain( [] ) unless $self->unsafe;
    },
);

# Boolean, disables various sanity checks if set to a true value (in
# particular whether the hash is cleared when the modulus is changed).
has unsafe => (
    is      => 'rw',
    default => sub { 0 },
    coerce  => sub { $_[0] ? 1 : 0 },
);

##############################################################################
#
# METHODS

sub clear_hash {
    my ($self) = @_;
    $self->_chain( [] );
    return $self;
}

sub get {
    my ( $self, $key ) = @_;
    croak "must provide key" if !defined $key;
    my $chain = $self->_chain->[ $self->hash($key) ];
    if ( defined $chain ) {
        for my $kvpair (@$chain) {
            return $kvpair->[1] if $key eq $kvpair->[0];
        }
    }
    return;
}

# Derives the index of the chain a particular key will be added to. The
# hashcode function, if available, should return something that ideally
# evenly distributes the given keys across the given modulus.
#
# Alternative: subclass this module and write yer own hash function.
sub hash {
    my ( $self, $key ) = @_;
    croak "must provide key" if !defined $key;
    my $code = 0;
    if ( $key->can('hashcode') ) {
        $code = $key->hashcode();
    } else {
        # this is pretty terrible...
        for my $n ( map ord, split //, $key ) {
            $code += $n;
        }
    }
    return abs( $code % $self->modulus );
}



( run in 0.849 second using v1.01-cache-2.11-cpan-97f6503c9c8 )