File Coverage

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


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   228392 use 5.010;
  2         12  
14 2     2   10 use strict;
  2         4  
  2         38  
15 2     2   9 use warnings;
  2         4  
  2         71  
16              
17 2     2   11 use Carp qw/croak/;
  2         3  
  2         91  
18 2     2   1151 use Moo;
  2         23411  
  2         9  
19 2     2   4063 use namespace::clean;
  2         23265  
  2         13  
20 2     2   631 use Scalar::Util qw/looks_like_number/;
  2         4  
  2         2299  
21              
22             our $VERSION = '0.02';
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 6 my ($self) = @_;
70 2         11 $self->_chain( [] );
71 2         6 return $self;
72             }
73              
74             sub get {
75 4     4 1 28 my ( $self, $key ) = @_;
76 4 100       201 croak "must provide key" if !defined $key;
77 3         14 my $chain = $self->_chain->[ $self->hash($key) ];
78 3 100       29 if ( defined $chain ) {
79 2         6 for my $kvpair (@$chain) {
80 2 100       16 return $kvpair->[1] if $key eq $kvpair->[0];
81             }
82             }
83 2         7 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 32     32 1 606 my ( $self, $key ) = @_;
93 32 100       147 croak "must provide key" if !defined $key;
94 31         45 my $code = 0;
95 31 100       132 if ( $key->can('hashcode') ) {
96 6         12 $code = $key->hashcode();
97             } else {
98             # this is pretty terrible...
99 25         127 for my $n ( map ord, split //, $key ) {
100 49         85 $code += $n;
101             }
102             }
103 31         619 return abs( $code % $self->modulus );
104             }
105              
106             sub keys {
107 11     11 1 6149 my ($self) = @_;
108 11         19 my @keys;
109 11         27 for my $chain ( @{ $self->_chain } ) {
  11         35  
110 12         30 push @keys, map { $_->[0] } @$chain;
  16         39  
111             }
112 11         65 return @keys;
113             }
114              
115             sub keys_in {
116 3     3 1 455 my ( $self, $index ) = @_;
117 3 100       91 croak "must provide index" if !defined $index;
118 2         48 $index %= $self->modulus; # this will int() any floating-point nums
119 2         13 return map { $_->[0] } @{ $self->_chain->[$index] };
  5         19  
  2         8  
120             }
121              
122             # Keys in the same chain (or bucket) as a given key
123             sub keys_with {
124 6     6 1 494 my ( $self, $key ) = @_;
125 6 100       104 croak "must provide key" if !defined $key;
126 5         7 for my $chain ( @{ $self->_chain } ) {
  5         15  
127 5         33 for my $kvpair (@$chain) {
128 6 100       33 return map $_->[0], @$chain if $key eq $kvpair->[0];
129             }
130             }
131 2         11 return;
132             }
133              
134             sub put {
135 13     13 1 1425 my ( $self, $key, $value ) = @_;
136 13 100       114 croak "must provide key" if !defined $key;
137 12         37 my $chain = $self->_chain->[ $self->hash($key) ];
138 12 100       86 if ( defined $chain ) {
139 7         22 for my $kvpair (@$chain) {
140 8 100       27 if ( $key eq $kvpair->[0] ) {
141 1         2 $kvpair->[1] = $value;
142 1         3 return $self;
143             }
144             }
145             }
146 11         17 push @{ $self->_chain->[ $self->hash($key) ] }, [ $key, $value ];
  11         29  
147 11         89 return $self;
148             }
149              
150             # a.k.a. delete but more indicative of the obtaining-a-value aspect
151             sub take {
152 6     6 1 471 my ( $self, $key ) = @_;
153 6 100       93 croak "must provide key" if !defined $key;
154 5         19 my $chain = $self->_chain->[ $self->hash($key) ];
155 5 100       45 if ( defined $chain ) {
156 4         14 for my $i ( 0 .. $#$chain ) {
157 5 100       13 if ( $key eq $chain->[$i][0] ) {
158 4         9 my $kvpair = splice @$chain, $i, 1;
159 4         16 return $kvpair->[1];
160             }
161             }
162             }
163 1         5 return;
164             }
165              
166             1;
167             __END__