File Coverage

blib/lib/Data/Dict.pm
Criterion Covered Total %
statement 72 102 70.5
branch 14 14 100.0
condition 2 3 66.6
subroutine 21 30 70.0
pod 25 25 100.0
total 134 174 77.0


line stmt bran cond sub pod time code
1             package Data::Dict;
2              
3 1     1   53029 use strict;
  1         7  
  1         25  
4 1     1   4 use warnings;
  1         1  
  1         19  
5 1     1   3 use Carp ();
  1         2  
  1         21  
6 1     1   4 use Exporter 'import';
  1         2  
  1         43  
7              
8 1     1   4 use constant HAS_COLLECTION => !!eval { require Mojo::Collection; 1 };
  1         2  
  1         1  
  1         1302  
  0         0  
9              
10             our $VERSION = '0.002';
11              
12             our @EXPORT_OK = 'd';
13              
14 25     25 1 3171 sub d { __PACKAGE__->new(@_) }
15              
16             sub new {
17 50     50 1 71 my $class = shift;
18 50   66     282 return bless {@_}, ref $class || $class;
19             }
20              
21 0     0 1 0 sub TO_JSON { +{%{$_[0]}} }
  0         0  
22              
23             sub delete {
24 9     9 1 22 my $self = shift;
25 9         17 my $deleted = $self->slice(@_);
26 9 100       36 delete @$self{keys %$deleted} if keys %$deleted;
27 9         23 return $deleted;
28             }
29              
30             sub each {
31 3     3 1 451 my ($self, $cb) = @_;
32 3 100       7 return map { [$_, $self->{$_}] } keys %$self unless $cb;
  3         11  
33 2         7 $cb->($_, $self->{$_}) for keys %$self;
34 2         15 return $self;
35             }
36              
37             sub each_c {
38 0     0 1 0 Carp::croak 'Mojo::Collection is required for each_c' unless HAS_COLLECTION;
39 0         0 my $self = shift;
40 0         0 return Mojo::Collection->new(map { Mojo::Collection->new(@$_) } $self->each);
  0         0  
41             }
42              
43             sub each_sorted {
44 3     3 1 449 my ($self, $cb) = @_;
45 3 100       9 return map { [$_, $self->{$_}] } sort keys %$self unless $cb;
  3         11  
46 2         10 $cb->($_, $self->{$_}) for sort keys %$self;
47 2         15 return $self;
48             }
49              
50             sub each_sorted_c {
51 0     0 1 0 Carp::croak 'Mojo::Collection is required for each_sorted_c' unless HAS_COLLECTION;
52 0         0 my $self = shift;
53 0         0 return Mojo::Collection->new(map { Mojo::Collection->new(@$_) } $self->each_sorted);
  0         0  
54             }
55              
56             sub extract {
57 4     4 1 14 my ($self, $cb) = @_;
58 4 100       13 return $self->delete(grep { m/$cb/ } keys %$self) if ref $cb eq 'Regexp';
  9         33  
59 3         7 return $self->delete(grep { $cb->($_, $self->{$_}) } keys %$self);
  7         17  
60             }
61              
62             sub grep {
63 7     7 1 16 my ($self, $cb) = @_;
64 7 100       21 return $self->new(map { ($_, $self->{$_}) } grep { m/$cb/ } keys %$self) if ref $cb eq 'Regexp';
  4         7  
  9         25  
65 6         17 return $self->new(map { ($_, $self->{$_}) } grep { $cb->($_, $self->{$_}) } keys %$self);
  13         29  
  54         128  
66             }
67              
68             sub map {
69 2     2 1 873 my ($self, $cb) = @_;
70 2         5 return map { $cb->($_, $self->{$_}) } keys %$self;
  6         17  
71             }
72              
73             sub map_c {
74 0     0 1 0 Carp::croak 'Mojo::Collection is required for map_c' unless HAS_COLLECTION;
75 0         0 my $self = shift;
76 0         0 return Mojo::Collection->new($self->map(@_));
77             }
78              
79             sub map_sorted {
80 2     2 1 878 my ($self, $cb) = @_;
81 2         7 return map { $cb->($_, $self->{$_}) } sort keys %$self;
  6         17  
82             }
83              
84             sub map_sorted_c {
85 0     0 1 0 Carp::croak 'Mojo::Collection is required for map_sorted_c' unless HAS_COLLECTION;
86 0         0 my $self = shift;
87 0         0 return Mojo::Collection->new($self->map_sorted(@_));
88             }
89              
90 6     6 1 24 sub size { scalar keys %{$_[0]} }
  6         30  
91              
92             sub slice {
93 16     16 1 24 my $self = shift;
94 16         24 return $self->new(map { ($_, $self->{$_}) } @_);
  29         57  
95             }
96              
97             sub tap {
98 1     1 1 2 my ($self, $cb) = (shift, shift);
99 1         4 $_->$cb(@_) for $self;
100 1         4 return $self;
101             }
102              
103             sub to_collection {
104 0     0 1 0 Carp::croak 'Mojo::Collection is required for to_collection' unless HAS_COLLECTION;
105 0         0 return Mojo::Collection->new(%{$_[0]});
  0         0  
106             }
107              
108             sub to_collection_sorted {
109 0     0 1 0 Carp::croak 'Mojo::Collection is required for to_collection_sorted' unless HAS_COLLECTION;
110 0         0 my $self = shift;
111 0         0 return Mojo::Collection->new(map { ($_, $self->{$_}) } sort keys %$self);
  0         0  
112             }
113              
114 24     24 1 30 sub to_hash { +{%{$_[0]}} }
  24         117  
115              
116             sub transform {
117 2     2 1 8 my ($self, $cb) = @_;
118 2         7 return $self->new(map { $cb->($_, $self->{$_}) } keys %$self);
  6         19  
119             }
120              
121             sub values {
122 5     5 1 18 my ($self, $cb) = (shift, shift);
123 5 100       24 return values %$self unless $cb;
124 1         5 $_->$cb(@_) for values %$self;
125 1         7 return $self;
126             }
127              
128             sub values_c {
129 0     0 1 0 Carp::croak 'Mojo::Collection is required for values_c' unless HAS_COLLECTION;
130 0         0 my $self = shift;
131 0         0 return Mojo::Collection->new($self->values);
132             }
133              
134             # define this last because CORE::keys doesn't work before 5.20
135             sub keys {
136 5     5 1 17 my ($self, $cb) = @_;
137 5 100       50 return keys %$self unless $cb;
138 1         5 $cb->($_) for keys %$self;
139 1         7 return $self;
140             }
141              
142             sub keys_c {
143 0     0 1   Carp::croak 'Mojo::Collection is required for keys_c' unless HAS_COLLECTION;
144 0           my $self = shift;
145 0           return Mojo::Collection->new($self->keys);
146             }
147              
148             1;
149              
150             =head1 NAME
151              
152             Data::Dict - Hash-based dictionary object
153              
154             =head1 SYNOPSIS
155              
156             use Data::Dict;
157              
158             # Manipulate dictionary
159             my $dictionary = Data::Dict->new(a => 1, b => 2, c => 3);
160             delete $dictionary->{b};
161             print join "\n", $dictionary->keys;
162              
163             # Chain methods
164             $dictionary->slice(qw(a b))->grep(sub { defined $_[1] })->each(sub {
165             my ($key, $value) = @_;
166             print "$key: $value\n";
167             });
168              
169             # Use the alternative constructor
170             use Data::Dict 'd';
171             use experimental 'signatures';
172             my $num_highest = d(%counts)->transform(sub ($k, $v) { ($k, $v+1) })->grep(sub ($k, $v) { $v > 5 })->size;
173              
174             # Use Mojo::Collection for more chaining
175             d(%hash)->map_sorted_c(sub { join ':', @_ })->shuffle->join("\n")->say;
176              
177             =head1 DESCRIPTION
178              
179             L is a hash-based container for dictionaries, with heavy
180             inspiration from L. Unless otherwise noted, all methods
181             iterate through keys and values in default keys order, which is random but
182             consistent until the hash is modified.
183              
184             # Access hash directly to manipulate dictionary
185             my $dict = Data::Dict->new(a => 1, b => 2, c => 3);
186             $dict->{b} += 100;
187             print "$_\n" for values %$dict;
188              
189             =head1 FUNCTIONS
190              
191             =head2 d
192              
193             my $dict = d(a => 1, b => 2);
194              
195             Construct a new hash-based L object. Exported on demand.
196              
197             =head1 METHODS
198              
199             =head2 new
200              
201             my $dict = Data::Dict->new(a => 1, b => 2);
202              
203             Construct a new hash-based L object.
204              
205             =head2 TO_JSON
206              
207             Alias for L.
208              
209             =head2 delete
210              
211             my $deleted = $dict->delete(@keys);
212              
213             Delete selected keys from the dictionary and return a new dictionary containing
214             the deleted keys and values.
215              
216             =head2 each
217              
218             my @pairs = $dict->each;
219             $dict = $dict->each(sub {...});
220              
221             Evaluate callback for each pair in the dictionary, or return pairs as list of
222             key/value arrayrefs if none has been provided. The callback will receive the
223             key and value as arguments.
224              
225             $dict->each(sub {
226             my ($key, $value) = @_;
227             print "$key: $value\n";
228             });
229              
230             # values can be modified in place
231             $dict->each(sub { $_[1] = $_[0]x2 });
232              
233             =head2 each_c
234              
235             my $collection = $dict->each_c;
236              
237             Create a new collection of key/value pairs as collections. Requires
238             L.
239              
240             # print all keys and values
241             print $dict->each_c->flatten->join(' ');
242              
243             =head2 each_sorted
244              
245             my @pairs = $dict->each_sorted;
246             $dict = $dict->each_sorted(sub {...});
247              
248             As in L, but the pairs are returned or the callback is called in
249             sorted keys order.
250              
251             =head2 each_sorted_c
252              
253             my $collection = $dict->each_sorted_c;
254              
255             As in L, but the pairs are added to the collection in sorted keys
256             order. Requires L.
257              
258             =head2 extract
259              
260             my $extracted = $dict->extract(qr/foo/);
261             my $extracted = $dict->extract(sub {...});
262              
263             Evaluate regular expression on each key, or call callback on each key/value
264             pair in the dictionary, and remove all pairs that matched the regular
265             expression, or for which the callback returned true. Return a new dictionary
266             with the removed keys and values. The callback will receive the key and value
267             as arguments.
268              
269             my $high_numbers = $dict->extract(sub { $_[1] > 100 });
270              
271             =head2 grep
272              
273             my $new = $dict->grep(qr/foo/);
274             my $new = $dict->grep(sub {...});
275              
276             Evaluate regular expression on each key, or call callback on each key/value
277             pair in the dictionary, and return a new dictionary with all pairs that matched
278             the regular expression, or for which the callback returned true. The callback
279             will receive the key and value as arguments.
280              
281             my $banana_dict = $dict->grep(qr/banana/);
282              
283             my $fruits_dict = $dict->grep(sub { $_[1]->isa('Fruit') });
284              
285             =head2 keys
286              
287             my @keys = $dict->keys;
288             $dict = $dict->keys(sub {...});
289              
290             Evaluate callback for each key in the dictionary, or return all keys as a list
291             if none has been provided. The key will be the first argument passed to the
292             callback, and is also available as C<$_>.
293              
294             =head2 keys_c
295              
296             my $collection = $dict->keys_c;
297              
298             Create a new collection from all keys. Requires L.
299              
300             my $first_key = $dict->keys_c->first;
301              
302             =head2 map
303              
304             my @results = $dict->map(sub {...});
305              
306             Evaluate callback for each key/value pair in the dictionary and return the
307             results as a list. The callback will receive the key and value as arguments.
308              
309             my @pairs = $dict->map(sub { [@_] });
310              
311             my @values = $dict->map(sub { $_[1] });
312              
313             =head2 map_c
314              
315             my $collection = $dict->map_c(sub {...});
316              
317             Evaluate callback for each key/value pair in the dictionary and create a new
318             collection from the results. The callback will receive the key and value as
319             arguments. Requires L.
320              
321             my $output = $dict->map_c(sub { "$_[0]: $_[1]" })->join("\n");
322              
323             =head2 map_sorted
324              
325             my @results = $dict->map_sorted(sub {...});
326              
327             As in L, but the callback is evaluated in sorted keys order.
328              
329             =head2 map_sorted_c
330              
331             my $collection = $dict->map_sorted_c(sub {...});
332              
333             As in L, but the callback is evaluated in sorted keys order. Requires
334             L.
335              
336             =head2 size
337              
338             my $size = $dict->size;
339              
340             Number of keys in dictionary.
341              
342             =head2 slice
343              
344             my $new = $dict->slice(@keys);
345              
346             Create a new dictionary with all selected keys.
347              
348             print join ' ', d(a => 1, b => 2, c => 3)->slice('a', 'c')
349             ->map_sorted(sub { join ':', @_ }); # a:1 c:3
350              
351             =head2 tap
352              
353             $dict = $dict->tap(sub {...});
354              
355             Perform callback and return the dictionary object for further chaining, as in
356             L. The dictionary object will be the first argument passed to
357             the callback, and is also available as C<$_>.
358              
359             =head2 to_collection
360              
361             my $collection = $dict->to_collection;
362              
363             Turn dictionary into even-sized collection of keys and values. Requires
364             L.
365              
366             =head2 to_collection_sorted
367              
368             my $collection = $dict->to_collection_sorted;
369              
370             Turn dictionary into even-sized collection of keys and values in sorted keys
371             order. Requires L.
372              
373             =head2 to_hash
374              
375             my $hash = $dict->to_hash;
376              
377             Turn dictionary into hash reference.
378              
379             =head2 transform
380              
381             my $new = $dict->transform(sub {...});
382              
383             Evaluate callback for each key/value pair in the dictionary and create a new
384             dictionary from the returned keys and values (assumed to be an even-sized
385             key/value list). The callback will receive the key and value as arguments.
386              
387             my $reversed = $dict->transform(sub { ($_[1], $_[0]) });
388              
389             my $doubled = $dict->transform(sub {
390             my ($k, $v) = @_;
391             return ($k => $v, ${k}x2 => $v);
392             });
393              
394             =head2 values
395              
396             my @values = $dict->values;
397             $dict = $dict->values(sub {...});
398              
399             Evaluate callback for each value in the dictionary, or return all values as a
400             list if none has been provided. The value will be the first argument passed to
401             the callback, and is also available as C<$_>.
402              
403             # values can be modified in place
404             $dict->values(sub { $_++ });
405              
406             =head2 values_c
407              
408             my $collection = $dict->values_c;
409              
410             Create a new collection from all values. Requires L.
411              
412             my @shuffled_values = $dict->values_c->shuffle->each;
413              
414             =head1 BUGS
415              
416             Report any issues on the public bugtracker.
417              
418             =head1 AUTHOR
419              
420             Dan Book
421              
422             =head1 COPYRIGHT AND LICENSE
423              
424             This software is Copyright (c) 2018 by Dan Book.
425              
426             This is free software, licensed under:
427              
428             The Artistic License 2.0 (GPL Compatible)
429              
430             =head1 SEE ALSO
431              
432             L