File Coverage

lib/Hash/MostUtils.pm
Criterion Covered Total %
statement 90 91 98.9
branch 3 4 75.0
condition 3 6 50.0
subroutine 26 26 100.0
pod 10 10 100.0
total 132 137 96.3


line stmt bran cond sub pod time code
1 10     10   595085 use strict;
  10         30  
  10         653  
2 10     10   554 use warnings;
  10         21  
  10         517  
3             package Hash::MostUtils;
4 10     10   151 use base qw(Exporter);
  10         21  
  10         1643  
5              
6 10     10   58 use Carp qw(confess);
  10         29  
  10         1049  
7 10     10   8817 use Hash::MostUtils::leach qw(n_each leach);
  10         28  
  10         8573  
8              
9             our $VERSION = 1.05;
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             n_each
21             n_map
22             n_grep
23             n_apply
24             reindex
25             rekey
26             revalue
27             );
28              
29             # decrementing $| flips it between 0 and 1
30 3     3 1 2483 sub lkeys { local $|; return grep { $|-- == 0 } @_ }
  3         9  
  36         77  
31 3     3 1 28 sub lvalues { local $|; return grep { $|-- == 1 } @_ }
  3         9  
  36         71  
32              
33             # I would put leach() here, but it was imported above
34              
35 4     4   1274 *hashmap = sub(&@) { unshift @_, 2; goto &n_map };
  4         20  
36 3     3   1993 *hashgrep = sub(&@) { unshift @_, 2; goto &n_grep };
  3         16  
37 1     1   20 *hashapply = sub (&@) { unshift @_, 2; goto &n_apply };
  1         7  
38              
39             # I would put n_each() here, but it was imported above
40              
41             sub n_map ($&@) {
42             # Usually I don't mutate @_. Here I deliberately modify @_ for the upcoming non-obvious goto-&NAME.
43 13     13 1 1435 my $n = shift;
44 13     54   46 my $collector = sub { return $_[0]->() };
  54         121  
45 13         30 unshift @_, $collector;
46              
47             # Using a "safe goto" allows n_map() to remove itself from the callstack, which allows _n_collect()
48             # to see the correct caller.
49             #
50             # 'perldoc -f goto' for why this is a safe goto.
51 13         16 goto &{_n_collect($n)};
  13         37  
52             }
53              
54             sub n_grep ($&@) {
55 7     7 1 10689 my $n = shift;
56              
57             # the comments in n_map() apply here as well.
58              
59             my $collector = sub {
60 32     32   50 my ($code, $vals, $aliases) = @_;
61 32 100       66 return $code->() ? @$vals : ();
62 7         41 };
63 7         19 unshift @_, $collector;
64              
65 7         14 goto &{_n_collect($n)};
  7         23  
66             }
67              
68             sub n_apply {
69 3     3 1 13 my $n = shift;
70             my $collector = sub {
71 9     9   14 my ($code, $vals, $aliases) = @_;
72 9         29 $code->();
73 9         42 return map { $$_ } @$aliases;
  22         84  
74 3         16 };
75 3         7 unshift @_, $collector;
76              
77 3         6 goto &{_n_collect($n)};
  3         7  
78             }
79              
80             sub _n_collect($) {
81 23     23   906 my ($n) = @_;
82             return sub(&@) {
83 23     23   57 my $collector = shift;
84 23         39 my $code = shift;
85 23 50       103 if (@_ % $n != 0) {
86 0         0 confess("your input is insane: can't evenly slice " . @_ . " elements into $n-sized chunks\n");
87             }
88              
89             # these'll reserve some namespace back in the callpackage
90 23         479 my @n = ('a' .. 'z');
91              
92             # stash old values back in callpackage *and* in main. If called from main::, this comes down to:
93             # local ${'main::a'}, ${'main::b'}, ${'main::c'}
94             # when $n is 3.
95 23         55 my $caller = caller;
96 10     10   72 no strict 'refs';
  10         20  
  10         18044  
97 23         152 foreach ((@n[ 0 .. $n-1 ])) {
98 71         71 local ${"$caller\::$_"};
  71         317  
99 71         81 local ${"::$_"};
  71         209  
100             }
101              
102 23         46 my @out;
103 23         1994 while (my @chunk = splice @_, 0, $n) { # build up each set...
104 95         549 my @aliases;
105 95         274 foreach (0 .. $#chunk) {
106             # ...assign values from @_ back to localized variables in $caller *and* in 'main::'.
107             # Aliasing in main:: allows you to refer to variables $c and onwards as $::c.
108             # Aliasing in $caller allows you to refer to variables $c and onwards as $whatever::package::c.
109 237         1353 ${"::$n[$_]"} = ${"$caller\::$n[$_]"} = $chunk[$_];
  237         4451  
  237         1565  
110              
111             # Keep a reference to $::a (etc.) and pass them in to the $collector; this allows $code to mutate
112             # $::a (etc) and signal the changed values back to $collector.
113 237         240 push @aliases, \${"::$n[$_]"};
  237         999  
114             }
115 95         253 push @out, $collector->($code, \@chunk, \@aliases); # ...and apply $code.
116             }
117              
118 23         691 return @out;
119 23         225 };
120             }
121              
122             sub hash_slice_of {
123 4     4 1 5072 my ($ref, @keys) = @_;
124 4         12 return map { ($_ => $ref->{$_}) } @keys;
  15         74  
125             }
126              
127             sub hash_slice_by {
128 2     2 1 668 my ($obj, @methods) = @_;
129 2         4 return map { ($_ => scalar($obj->$_)) } @methods;
  4         75  
130             }
131              
132             sub rekey (&@) {
133 3     3 1 1152 my %map = shift()->();
134 3   66 16   42 return n_map 2, sub { $map{$a} || $a => $b }, @_;
  16         130  
135             }
136              
137             sub reindex (&@) {
138 1     1 1 22 my %map = shift()->();
139 1         27 @_[values %map] = delete @_[keys %map];
140 1         7 return @_;
141             }
142              
143             sub revalue (&@) {
144 1     1 1 2989 my %map = shift()->();
145 1   33 2   19 return n_map 2, sub { $a => $map{$b} || $b }, @_;
  2         77  
146             }
147              
148             1;
149              
150             __END__