File Coverage

lib/Hash/MostUtils.pm
Criterion Covered Total %
statement 100 101 99.0
branch 3 4 75.0
condition 3 6 50.0
subroutine 29 29 100.0
pod 11 11 100.0
total 146 151 96.6


line stmt bran cond sub pod time code
1 11     11   209976 use strict;
  11         22  
  11         344  
2 11     11   43 use warnings;
  11         16  
  11         429  
3             package Hash::MostUtils;
4 11     11   43 use base qw(Exporter);
  11         18  
  11         999  
5              
6 11     11   49 use Carp qw(confess);
  11         11  
  11         476  
7 11     11   3508 use Hash::MostUtils::leach qw(n_each leach);
  11         24  
  11         2664  
8              
9             our $VERSION = 1.07;
10              
11             our @EXPORT_OK = qw(
12             lvalues
13             lkeys
14             leach
15             hash_slice_of
16             hash_slice_by
17             hashmap
18             hashgrep
19             hashapply
20             hashsort
21             n_each
22             n_map
23             n_grep
24             n_apply
25             reindex
26             rekey
27             revalue
28             );
29              
30             # decrementing $| flips it between 0 and 1
31 3     3 1 2278 sub lkeys { local $|; return grep { $|-- == 0 } @_ }
  3         7  
  36         65  
32 3     3 1 40 sub lvalues { local $|; return grep { $|-- == 1 } @_ }
  3         6  
  36         51  
33              
34             # I would put leach() here, but it was imported above
35              
36 7     7   885 *hashmap = sub(&@) { unshift @_, 2; goto &n_map };
  7         21  
37 3     3   421 *hashgrep = sub(&@) { unshift @_, 2; goto &n_grep };
  3         9  
38 1     1   14 *hashapply = sub (&@) { unshift @_, 2; goto &n_apply };
  1         4  
39              
40             sub hashsort (&@) {
41 3     3 1 1532 my $sort = shift;
42              
43 3         5 my $caller = caller;
44 11     11   58 no strict 'refs';
  11         15  
  11         4176  
45              
46             return
47 19         40 map { ($_->{key} => $_->{value}) }
  27         30  
48             sort {
49 27         52 local (${"$caller\::a"}, ${"$caller\::b"}) = ($a, $b);
  27         33  
50 27         29 $sort->();
51             }
52 3     19   13 &hashmap(sub { +{key => $a, value => $b} }, @_);
  19         73  
53             }
54              
55             # I would put n_each() here, but it was imported above
56              
57             sub n_map ($&@) {
58             # Usually I don't mutate @_. Here I deliberately modify @_ for the upcoming non-obvious goto-&NAME.
59 16     16 1 984 my $n = shift;
60 16     73   80 my $collector = sub { return $_[0]->() };
  73         107  
61 16         26 unshift @_, $collector;
62              
63             # Using a "safe goto" allows n_map() to remove itself from the callstack, which allows _n_collect()
64             # to see the correct caller.
65             #
66             # 'perldoc -f goto' for why this is a safe goto.
67 16         23 goto &{_n_collect($n)};
  16         31  
68             }
69              
70             sub n_grep ($&@) {
71 7     7 1 1833 my $n = shift;
72              
73             # the comments in n_map() apply here as well.
74              
75             my $collector = sub {
76 32     32   32 my ($code, $vals, $aliases) = @_;
77 32 100       40 return $code->() ? @$vals : ();
78 7         39 };
79 7         13 unshift @_, $collector;
80              
81 7         22 goto &{_n_collect($n)};
  7         14  
82             }
83              
84             sub n_apply {
85 3     3 1 10 my $n = shift;
86             my $collector = sub {
87 9     9   11 my ($code, $vals, $aliases) = @_;
88 9         16 $code->();
89 9         35 return map { $$_ } @$aliases;
  22         50  
90 3         29 };
91 3         6 unshift @_, $collector;
92              
93 3         5 goto &{_n_collect($n)};
  3         9  
94             }
95              
96             sub _n_collect($) {
97 26     26   36 my ($n) = @_;
98             return sub(&@) {
99 26     26   30 my $collector = shift;
100 26         27 my $code = shift;
101 26 50       83 if (@_ % $n != 0) {
102 0         0 confess("your input is insane: can't evenly slice " . @_ . " elements into $n-sized chunks\n");
103             }
104              
105             # these'll reserve some namespace back in the callpackage
106 26         149 my @n = ('a' .. 'z');
107              
108             # stash old values back in callpackage *and* in main. If called from main::, this comes down to:
109             # local ${'main::a'}, ${'main::b'}, ${'main::c'}
110             # when $n is 3.
111 26         45 my $caller = caller;
112 11     11   60 no strict 'refs';
  11         12  
  11         3797  
113 26         78 foreach ((@n[ 0 .. $n-1 ])) {
114 77         61 local ${"::$_"};
  77         219  
115             }
116              
117 26         38 my @out;
118 26         102 while (my @chunk = splice @_, 0, $n) { # build up each set...
119 114         335 my @aliases;
120 114         157 foreach (0 .. $#chunk) {
121             # ...assign values from @_ back to localized variables in $caller *and* in 'main::'.
122             # Aliasing in main:: allows you to refer to variables $c and onwards as $::c.
123             # Aliasing in $caller allows you to refer to variables $c and onwards as $whatever::package::c.
124 275         226 ${"::$n[$_]"} = ${"$caller\::$n[$_]"} = $chunk[$_];
  275         296  
  275         399  
125              
126             # Keep a reference to $::a (etc.) and pass them in to the $collector; this allows $code to mutate
127             # $::a (etc) and signal the changed values back to $collector.
128 275         197 push @aliases, \$ {"::$n[$_]"};
  275         571  
129             }
130 114         187 push @out, $collector->($code, \@chunk, \@aliases); # ...and apply $code.
131             }
132              
133 26         436 return @out;
134 26         154 };
135             }
136              
137             sub hash_slice_of {
138 4     4 1 1029 my ($ref, @keys) = @_;
139 4         7 return map { ($_ => $ref->{$_}) } @keys;
  15         50  
140             }
141              
142             sub hash_slice_by {
143 2     2 1 622 my ($obj, @methods) = @_;
144 2         4 return map { ($_ => scalar($obj->$_)) } @methods;
  4         55  
145             }
146              
147             sub rekey (&@) {
148 3     3 1 401 my %map = shift()->();
149 3   66 16   25 return n_map 2, sub { $map{$a} || $a => $b }, @_;
  16         79  
150             }
151              
152             sub reindex (&@) {
153 1     1 1 12 my %map = shift()->();
154 1         17 @_[values %map] = delete @_[keys %map];
155 1         4 return @_;
156             }
157              
158             sub revalue (&@) {
159 1     1 1 626 my %map = shift()->();
160 1   33 2   13 return n_map 2, sub { $a => $map{$b} || $b }, @_;
  2         17  
161             }
162              
163             1;
164              
165             __END__