File Coverage

blib/lib/Algorithm/Toy/HashSC.pm
Criterion Covered Total %
statement 74 78 94.8
branch 18 28 64.2
condition n/a
subroutine 15 15 100.0
pod 8 8 100.0
total 115 129 89.1


line stmt bran cond sub pod time code
1             # -*- Perl -*-
2             #
3             # Toy deterministic separate chain hash implementation, based on code in
4             # "Algorithms (4th Edition)" by Robert Sedgewick and Kevin Wayne. This
5             # code is not for any sort of use where performance is critical, or
6             # where malicious input may cause "Algorithmic Complexity Attacks" (see
7             # perlsec(1)).
8             #
9             # Run perldoc(1) on this file for additional documentation.
10              
11             package Algorithm::Toy::HashSC;
12              
13 2     2   45893 use 5.010;
  2         7  
14 2     2   10 use strict;
  2         4  
  2         43  
15 2     2   8 use warnings;
  2         13  
  2         52  
16              
17 2     2   9 use Carp qw/croak/;
  2         3  
  2         130  
18 2     2   3045720 use Moo;
  2         38695  
  2         12  
19 2     2   4523 use namespace::clean;
  2         26059  
  2         8  
20 2     2   415 use Scalar::Util qw/looks_like_number/;
  2         4  
  2         2131  
21              
22             our $VERSION = '0.01';
23              
24             ##############################################################################
25             #
26             # ATTRIBUTES
27              
28             # Each list should end up with ~N/M key-value pairs, assuming the input
29             # is not malicious, and that the hash function is perfect enough. "M"
30             # here is the modulus, and "N" is the number of key-value pairs added.
31             #
32             # Internally, it's an array of array of arrays, or something like that.
33             has _chain => (
34             is => 'rw',
35             default => sub { [] },
36             );
37              
38             has modulus => (
39             is => 'rw',
40             default => sub { 7 },
41             coerce => sub {
42             die 'modulus must be a positive integer > 1'
43             if !defined $_[0]
44             or !looks_like_number $_[0]
45             or $_[0] < 2;
46             return int $_[0];
47             },
48             trigger => sub {
49             my ($self) = @_;
50             # clobber extant hash (Moo does not provide old value, so cannot do
51             # this only when the modulus changes, oh well)
52             $self->_chain( [] ) unless $self->unsafe;
53             },
54             );
55              
56             # Boolean, disables various sanity checks if set to a true value (in
57             # particular whether the hash is cleared when the modulus is changed).
58             has unsafe => (
59             is => 'rw',
60             default => sub { 0 },
61             coerce => sub { $_[0] ? 1 : 0 },
62             );
63              
64             ##############################################################################
65             #
66             # METHODS
67              
68             sub clear_hash {
69 2     2 1 4 my ($self) = @_;
70 2         8 $self->_chain( [] );
71 2         6 return $self;
72             }
73              
74             sub get {
75 1     1 1 3 my ( $self, $key ) = @_;
76 1 50       4 croak "must provide key" if !defined $key;
77 1         21 my $chain = $self->_chain->[ $self->hash($key) ];
78 1 50       9 if ( defined $chain ) {
79 1         3 for my $kvpair (@$chain) {
80 1 50       6 return $kvpair->[1] if $key eq $kvpair->[0];
81             }
82             }
83 0         0 return;
84             }
85              
86             # Derives the index of the chain a particular key will be added to. The
87             # hashcode function, if available, should return something that ideally
88             # evenly distributes the given keys across the given modulus.
89             #
90             # Alternative: subclass this module and write yer own hash function.
91             sub hash {
92 27     27 1 35 my ( $self, $key ) = @_;
93 27 50       51 croak "must provide key" if !defined $key;
94 27         26 my $code;
95 27 100       126 if ( $key->can('hashcode') ) {
96 6         13 $code = $key->hashcode();
97             } else {
98             # TODO is this adequate?
99 21         67 for my $n ( map ord, split //, $key ) {
100 37         55 $code += $n;
101             }
102             }
103 27         530 return abs( $code % $self->modulus );
104             }
105              
106             sub keys {
107 11     11 1 919 my ($self) = @_;
108 11         15 my @keys;
109 11         12 for my $chain ( @{ $self->_chain } ) {
  11         42  
110 12         24 push @keys, map { $_->[0] } @$chain;
  16         39  
111             }
112 11         53 return @keys;
113             }
114              
115             sub keys_in {
116 2     2 1 4 my ( $self, $index ) = @_;
117 2 50       8 croak "must provide index" if !defined $index;
118 2         48 $index %= $self->modulus; # this will int() any floating-point nums
119 2         11 return map { $_->[0] } @{ $self->_chain->[$index] };
  5         22  
  2         8  
120             }
121              
122             # Keys in the same chain (or bucket) as a given key
123             sub keys_with {
124 5     5 1 14 my ( $self, $key ) = @_;
125 5 50       12 croak "must provide key" if !defined $key;
126 5         8 for my $chain ( @{ $self->_chain } ) {
  5         14  
127 5         8 for my $kvpair (@$chain) {
128 6 100       31 return map $_->[0], @$chain if $key eq $kvpair->[0];
129             }
130             }
131 2         11 return;
132             }
133              
134             sub put {
135 11     11 1 96 my ( $self, $key, $value ) = @_;
136 11 50       22 croak "must provide key" if !defined $key;
137 11         31 my $chain = $self->_chain->[ $self->hash($key) ];
138 11 100       601 if ( defined $chain ) {
139 6         10 for my $kvpair (@$chain) {
140 7 50       28 if ( $key eq $kvpair->[0] ) {
141 0         0 $kvpair->[1] = $value;
142 0         0 return $self;
143             }
144             }
145             }
146 11         12 push @{ $self->_chain->[ $self->hash($key) ] }, [ $key, $value ];
  11         26  
147 11         85 return $self;
148             }
149              
150             # a.k.a. delete but more indicative of the obtaining-a-value aspect
151             sub take {
152 4     4 1 9 my ( $self, $key ) = @_;
153 4 50       12 croak "must provide key" if !defined $key;
154 4         13 my $chain = $self->_chain->[ $self->hash($key) ];
155 4 50       28 if ( defined $chain ) {
156 4         12 for my $i ( 0 .. $#$chain ) {
157 5 100       16 if ( $key eq $chain->[$i][0] ) {
158 4         8 my $kvpair = splice @$chain, $i, 1;
159 4         15 return $kvpair->[1];
160             }
161             }
162             }
163 0           return;
164             }
165              
166             1;
167             __END__