File Coverage

blib/lib/autobox/Transform.pm
Criterion Covered Total %
statement 378 384 98.4
branch 112 132 84.8
condition 43 48 89.5
subroutine 89 92 96.7
pod 0 1 0.0
total 622 657 94.6


line stmt bran cond sub pod time code
1             package autobox::Transform;
2              
3 18     18   1884877 use strict;
  18         201  
  18         509  
4 18     18   85 use warnings;
  18         35  
  18         396  
5 18     18   341 use 5.010;
  18         62  
6 18     18   7354 use parent qw/autobox/;
  18         5251  
  18         97  
7              
8             our $VERSION = "1.032";
9              
10             =head1 NAME
11              
12             autobox::Transform - Autobox methods to transform Arrays and Hashes
13              
14             =head1 CONTEXT
15              
16             L provides the ability to call methods on native types,
17             e.g. strings, arrays, and hashes as if they were objects.
18              
19             L provides the basic methods for Perl core functions
20             like C, C, and C.
21              
22             This module, C, provides higher level and more
23             specific methods to transform and manipulate arrays and hashes, in
24             particular when the values are hashrefs or objects.
25              
26              
27              
28             =head1 SYNOPSIS
29              
30             use autobox::Core; # map, uniq, sort, join, sum, etc.
31             use autobox::Transform;
32              
33             =head2 Arrays
34              
35             # use autobox::Core for ->map etc.
36              
37             # filter (like a more versatile grep)
38             $book_locations->filter(); # true values
39             $books->filter(sub { $_->is_in_library($library) });
40             $book_names->filter( qr/lord/i );
41             $book_genres->filter("scifi");
42             $book_genres->filter({ fantasy => 1, scifi => 1 }); # hash key exists
43              
44             # reject: the inverse of filter
45             $book_genres->reject("fantasy");
46              
47             # order (like a more succinct sort)
48             $book_genres->order;
49             $book_genres->order("desc");
50             $book_prices->order([ "num", "desc" ]);
51             $books->order([ sub { $_->{price} }, "desc", "num" ]);
52             $log_lines->order([ num => qr/pid: "(\d+)"/ ]);
53             $books->order(
54             [ sub { $_->{price} }, "desc", "num" ] # first price
55             sub { $_->{name} }, # then name
56             );
57              
58             # group (aggregate) array into hash
59             $book_genres->group; # "Sci-fi" => "Sci-fi"
60             $book_genres->group_count; # "Sci-fi" => 3
61             $book_genres->group_array; # "Sci-fi" => [ "Sci-fi", "Sci-fi", "Sci-fi"]
62              
63             # Flatten arrayrefs-of-arrayrefs
64             $authors->map_by("books") # ->books returns an arrayref
65             # [ [ $book1, $book2 ], [ $book3 ] ]
66             $authors->map_by("books")->flat;
67             # [ $book1, $book2, $book3 ]
68              
69             # Return reference, even in list context, e.g. in a parameter list
70             $book_locations->filter()->to_ref;
71              
72             # Return array, even in scalar context
73             @books->to_array;
74              
75             # Turn paired items into a hash
76             @titles_books->to_hash;
77              
78              
79             =head2 Arrays with hashrefs/objects
80              
81             # $books and $authors below are arrayrefs with either objects or
82             # hashrefs (the call syntax is the same). These have methods/hash
83             # keys like C<$book->genre()>, C<$book->{is_sold_out}>,
84             # C<$book->is_in_library($library)>, etc.
85              
86             $books->map_by("genre");
87             $books->map_by([ price_with_tax => $tax_pct ]);
88              
89             $books->filter_by("is_sold_out");
90             $books->filter_by([ is_in_library => $library ]);
91             $books->filter_by([ price_with_tax => $rate ], sub { $_ > 56.00 });
92             $books->filter_by("price", sub { $_ > 56.00 });
93             $books->filter_by("author", "James A. Corey");
94             $books->filter_by("author", qr/corey/i);
95              
96             # grep_by is an alias for filter_by
97             $books->grep_by("is_sold_out");
98              
99             # reject_by: the inverse of filter_by
100             $books->reject_by("is_sold_out");
101              
102             $books->uniq_by("id");
103              
104             $books->order_by("name");
105             $books->order_by(name => "desc");
106             $books->order_by(price => "num");
107             $books->order_by(price => [ "num", "desc" ]);
108             $books->order_by(name => [ sub { uc($_) }, "desc" ]);
109             $books->order_by([ price_with_tax => $rate ] => "num");
110             $books->order_by(
111             author => "str", # first by author
112             price => [ "num", "desc" ], # then by price, most expensive first
113             );
114             $books->order_by(
115             author => [ "desc", sub { uc($_) } ],
116             [ price_with_tax => $rate ] => [ "num", "desc" ],
117             "name",
118             );
119              
120              
121             $books->group_by("title"),
122             # {
123             # "Leviathan Wakes" => $books->[0],
124             # "Caliban's War" => $books->[1],
125             # "The Tree-Body Problem" => $books->[2],
126             # "The Name of the Wind" => $books->[3],
127             # },
128              
129             $authors->group_by([ publisher_affiliation => "with" ]),
130             # {
131             # 'James A. Corey with Orbit' => $authors->[0],
132             # 'Cixin Liu with Head of Zeus' => $authors->[1],
133             # 'Patrick Rothfuss with Gollanz' => $authors->[2],
134             # },
135              
136             $books->group_by_count("genre"),
137             # {
138             # "Sci-fi" => 3,
139             # "Fantasy" => 1,
140             # },
141              
142             my $genre_books = $books->group_by_array("genre");
143             # {
144             # "Sci-fi" => [ $sf_book_1, $sf_book_2, $sf_book_3 ],
145             # "Fantasy" => [ $fantasy_book_1 ],
146             # },
147              
148              
149             =head2 Hashes
150              
151             # map over each pair
152             # e.g. Upper-case the genre name, and make the count say "n books"
153             # (return a key => value pair)
154             $genre_count->map_each(sub { uc( $_[0] ) => "$_ books" });
155             # {
156             # "FANTASY" => "1 books",
157             # "SCI-FI" => "3 books",
158             # },
159              
160             # map over each value
161             # e.g. Make the count say "n books"
162             # (return the new value)
163             $genre_count->map_each_value(sub { "$_ books" });
164             # {
165             # "Fantasy" => "1 books",
166             # "Sci-fi" => "3 books",
167             # },
168              
169             # map each pair into an array
170             # e.g. Transform each pair to the string "n: genre"
171             # (return list of items)
172             $genre_count->map_each_to_array(sub { "$_: $_[0]" });
173             # [ "1: Fantasy", "3: Sci-fi" ]
174              
175             # filter each pair
176             # Genres with more than five books
177             $genre_count->filter_each(sub { $_ > 5 });
178              
179             # filter out each pair
180             # Genres with no more than five books
181             $genre_count->reject_each(sub { $_ > 5 });
182              
183              
184             # Return reference, even in list context, e.g. in a parameter list
185             %genre_count->to_ref;
186              
187             # Return hash, even in scalar context
188             $author->book_count->to_hash;
189              
190             # Turn key-value pairs into an array
191             %isbn__book->to_array;
192              
193              
194             =head2 Combined examples
195              
196             my $order_authors = $order->books
197             ->filter_by("title", qr/^The/)
198             ->uniq_by("isbn")
199             ->map_by("author")
200             ->uniq_by("name")
201             ->order_by(publisher => "str", name => "str")
202             ->map_by("name")->uniq->join(", ");
203              
204             my $total_order_amount = $order->books
205             ->reject_by("is_sold_out")
206             ->filter_by([ covered_by_vouchers => $vouchers ], sub { ! $_ })
207             ->map_by([ price_with_tax => $tax_pct ])
208             ->sum;
209              
210             =cut
211              
212              
213              
214 18     18   19491 use true;
  18         151672  
  18         100  
215 18     18   18156 use Carp;
  18         37  
  18         11155  
216              
217             sub import {
218 18     18   234 my $self = shift;
219 18         142 $self->SUPER::import( ARRAY => "autobox::Transform::Array" );
220 18         10607 $self->SUPER::import( HASH => "autobox::Transform::Hash" );
221             }
222              
223             sub throw {
224 2     2 0 7 my ($error) = @_;
225             ###JPL: remove lib
226 2         21 $error =~ s/ at [\\\/\w ]*?\bautobox.Transform\.pm line \d+\.\n?$//;
227 2         7 local $Carp::CarpLevel = 1;
228 2         20 croak($error);
229             }
230              
231             # Normalize the two method calling styles for accessor + args:
232             # $acessor, $args_arrayref
233             # or
234             # $acessor_and_args_arrayref
235             sub _normalized_accessor_args_subref {
236 88     88   179 my ($accessor, $args, $subref) = @_;
237              
238             # Note: unfortunately, this won't allow the $subref (modifier) to
239             # become an arrayref later on when we do many types of modifiers
240             # (string eq, qr regex match, sub call, arrayref in) for
241             # filtering.
242             #
243             # That has to happen after the deprecation has expired and the old
244             # syntax is removed.
245 88 100       229 if(ref($args) eq "CODE") {
246 1         3 $subref = $args; # Move down one step
247 1         1 $args = undef;
248             }
249 88 100       177 if(ref($accessor) eq "ARRAY") {
250 14         38 ($accessor, my @args) = @$accessor;
251 14         23 $args = \@args;
252             }
253              
254 88         201 return ($accessor, $args, $subref);
255             }
256              
257             ###JPL: rename subref to predicate
258             # Normalize the two method calling styles for accessor + args:
259             # $acessor, $args_arrayref, $modifier
260             # or
261             # $acessor_and_args_arrayref, $modifier
262             sub _normalized_accessor_args_predicate {
263 25     25   47 my ($accessor, $args, $subref) = @_;
264              
265             # Note: unfortunately, this won't allow the $subref (modifier) to
266             # be an arrayref, or undef for many types of modifiers (string eq,
267             # qr regex match, sub call, arrayref in) for filtering.
268             #
269             # That has to happen after the deprecation has expired and the old
270             # syntax is removed.
271 25 100 100     97 if(defined($args) && ref($args) ne "ARRAY") {
272 10         17 $subref = $args; # Move down one step
273 10         13 $args = undef;
274             }
275 25 100       51 if(ref($accessor) eq "ARRAY") {
276 4         11 ($accessor, my @args) = @$accessor;
277 4         9 $args = \@args;
278             }
279              
280 25         59 return ($accessor, $args, $subref);
281             }
282              
283              
284              
285             sub _predicate {
286 65     65   143 my ($name, $predicate, $default_predicate) = @_;
287              
288             # No predicate, use default is_true
289 65 100       150 defined($predicate) or return $default_predicate;
290              
291             # scalar, do string eq
292 51 100   30   153 my $type = ref($predicate) or return sub { $predicate eq $_ };
  30         163  
293              
294 41 100       102 $type eq "CODE" and return $predicate;
295 18 100   30   62 $type eq "Regexp" and return sub { $_ =~ $predicate };
  30         192  
296 8 100   18   40 $type eq "HASH" and return sub { exists $predicate->{ $_ } };
  18         96  
297              
298             # Invalid predicate
299 2         39 Carp::croak("->$name() \$predicate: ($predicate) is not one of: subref, string, regex");
300             }
301              
302              
303              
304             =head1 DESCRIPTION
305              
306             High level autobox methods you can call on arrays, arrayrefs, hashes
307             and hashrefs.
308              
309              
310             =head2 Transforming lists of objects vs list of hashrefs
311              
312             C, C C etc. (all methods named C<*_by>)
313             work with sets of hashrefs or objects.
314              
315             These methods are called the same way regardless of whether the array
316             contains objects or hashrefs. The items in the list must be either all
317             objects or all hashrefs.
318              
319             If the array contains hashrefs, the hash key is looked up on each
320             item.
321              
322             If the array contains objects, a method is called on each object
323             (possibly with the arguments provided).
324              
325             =head3 Calling accessor methods with arguments
326              
327             For method calls, it's possible to provide arguments to the method.
328              
329             Consider C:
330              
331             $array->map_by($accessor)
332              
333             If the $accessor is a string, it's a simple method call.
334              
335             # method call without args
336             $books->map_by("price")
337             # becomes $_->price() or $_->{price}
338              
339             If the $accessor is an arrayref, the first item is the method name,
340             and the rest of the items are the arguments to the method.
341              
342             # method call with args
343             $books->map_by([ price_with_discount => 5.0 ])
344             # becomes $_->price_with_discount(5.0)
345              
346             =head3 Deprecated syntax
347              
348             There is an older syntax for calling methods with arguments. It was
349             abandoned to open up more powerful ways to use grep/filter type
350             methods. Here it is for reference, in case you run into existing code.
351              
352             $array->filter_by($accessor, $args, $subref)
353             $books->filter_by("price_with_discount", [ 5.0 ], sub { $_ < 15.0 })
354              
355             Call the method $accessor on each object using the arguments in the
356             $args arrayref like so:
357              
358             $object->$accessor(@$args)
359              
360             I, and planned for removal in version 2.000,
361             so if you have code with the old call style, please:
362              
363             =over 4
364              
365             =item
366              
367             Replace your existing code with the new style as soon as possible. The
368             change is trivial and the code easily found by grep/ack.
369              
370             =item
371              
372             If need be, pin your version to < 2.000 in your cpanfile, dist.ini or
373             whatever you use to avoid upgrading modules to incompatible versions.
374              
375             =back
376              
377              
378             =head2 Filter predicates
379              
380             There are several methods that filter items,
381             e.g. C<@array-Efilter> (duh), C<@array-Efilter_by>, and
382             C<%hash-Efilter_each>. These methods take a C<$predicate> argument
383             to determine which items to retain or filter out.
384              
385             The C family of methods do the opposite, and I
386             items that match the predicate, i.e. the opposite of the filter
387             methods.
388              
389             If $predicate is an I, it is compared to each value
390             with C.
391              
392             $books->filter_by("author", "James A. Corey");
393              
394             If $predicate is a I, it is compared to each value with C<=~>.
395              
396             $books->reject_by("author", qr/Corey/);
397              
398             If $predicate is a I, values in @array are retained if the
399             $predicate hash key C (the hash values are irrelevant).
400              
401             $books->filter_by(
402             "author", {
403             "James A. Corey" => undef,
404             "Cixin Liu" => 0,
405             "Patrick Rothfuss" => 1,
406             },
407             );
408              
409             If $predicate is a I, the subref is called for each value to
410             check whether this item should remain in the list.
411              
412             The $predicate subref should return a true value to remain. $_ is set
413             to the current $value.
414              
415             $authors->filter_by(publisher => sub { $_->name =~ /Orbit/ });
416              
417              
418             =head2 Sorting using order and order_by
419              
420             Let's first compare how sorting is done with Perl's C and
421             autobox::Transform's C/C.
422              
423              
424             =head3 Sorting with sort
425              
426             =over 4
427              
428             =item *
429              
430             provide a sub that returns the comparison outcome of two values: $a and $b
431              
432             =item *
433              
434             in case of a tie, provide another comparison of $a and $b
435              
436             =back
437              
438             # If the name is the same, compare age (oldest first)
439             sort {
440             uc( $a->{name} ) cmp uc( $b->{name} ) # first comparison
441             ||
442             int( $b->{age} / 10 ) <=> int( $a->{age} / 10 ) # second comparison
443             } @users
444              
445             (note the opposite order of $a and $b for the age comparison,
446             something that's often difficult to discern at a glance)
447              
448             =head3 Sorting with order, order_by
449              
450             =over 4
451              
452             =item *
453              
454             Provide order options for how one value should be compared with the others:
455              
456             =over 8
457              
458             =item *
459              
460             how to compare (C or C<<=E>)
461              
462             =item *
463              
464             which direction to sort (Cending or Cending)
465              
466             =item *
467              
468             which value to compare, using a regex or subref, e.g. by uc($_)
469              
470             =back
471              
472             =item *
473              
474             In case of a tie, provide another comparison
475              
476             =back
477              
478             # If the name is the same, compare age (oldest first)
479              
480             # ->order
481             @users->order(
482             sub { uc( $_->{name} ) }, # first comparison
483             [ "num", sub { int( $_->{age} / 10 ) }, "desc" ], # second comparison
484             )
485              
486             # ->order_by
487             @users->order_by(
488             name => sub { uc }, # first comparison
489             age => [ num => desc => sub { int( $_ / 10 ) } ], # second comparison
490             )
491              
492             =head3 Comparison Options
493              
494             If there's only one option for a comparison (e.g. C), provide a
495             single option (string/regex/subref) value. If there are many options,
496             provide them in an arrayref in any order.
497              
498             =head3 Comparison operator
499              
500             =over 4
501              
502             =item *
503              
504             C<"str"> (cmp) - default
505              
506             =item *
507              
508             C<"num"> (<=>)
509              
510             =back
511              
512              
513             =head3 Sort order
514              
515             =over 4
516              
517             =item *
518              
519             C<"asc"> (ascending) - default
520              
521             =item *
522              
523             C<"desc"> (descending)
524              
525             =back
526              
527              
528             =head3 The value to compare
529              
530             =over 4
531              
532             =item *
533              
534             A subref - default is: C
535              
536             =over 8
537              
538             =item *
539              
540             The return value is used in the comparison
541              
542             =back
543              
544             =item *
545              
546             A regex, e.g. C
547              
548             =over 8
549              
550             =item *
551              
552             The value of join("", @captured_groups) are used in the comparison (@captured_groups are $1, $2, $3 etc.)
553              
554             =back
555              
556             =back
557              
558             =head3 Examples of a single comparison
559              
560             # order: the first arg is the comparison options (one or an
561             # arrayref with many options)
562             ->order() # Defaults to str, asc, $_, just like sort
563             ->order("num")
564             ->order(sub { uc($_) })
565             # compare captured matches, e.g. "John" and "Doe" as "JohnDoe"
566             ->order( qr/first_name: (\w+), last_name: (\w+)/ )
567             ->order([ num => qr/id: (\d+)/ ])
568             ->order([ sub { int($_) }, "num" ])
569              
570             # order_by: the first arg is the accessor, just like with
571             # map_by. Second arg is the comparison options (one or an arrayref
572             # with many options)
573             ->order_by("id")
574             ->order_by("id", "num")
575             ->order_by("id", [ "num", "desc" ])
576             ->order_by("name", sub { uc($_) })
577             ->order_by(log_line => qr/first_name: (\w+), last_name: (\w+)/ )
578             ->order_by("log_line", [ num => qr/id: (\d+)/ ])
579             ->order_by(age => [ sub { int($_) }, "num" ])
580              
581             # compare int( $a->age_by_interval(10) )
582             ->order_by([ age_by_interval => 10 ] => [ sub { int($_) }, "num" ])
583             # compare uc( $a->name_with_title($title) )
584             ->order_by([ name_with_title => $title ], sub { uc($_) })
585              
586              
587             =head3 Examples of fallback comparisons
588              
589             When the first comparison is a tie, the subsequent ones are used.
590              
591             # order: list of comparison options (one or an arrayref with many
592             # options, per comparison)
593             ->order(
594             [ sub { $_->{price} }, "num" ], # First a numeric comparison of price
595             [ sub { $_->{name} }, "desc" ], # or if same, a reverse comparison of the name
596             )
597             ->order(
598             [ sub { uc($_) }, "desc" ],
599             "str",
600             )
601             ->order(
602             qr/type: (\w+)/,
603             [ num => desc => qr/duration: (\d+)/ ]
604             [ num => sub { /id: (\d+)/ } ],
605             "str",
606             )
607              
608             # order_by: pairs of accessor-comparison options
609             ->order_by(
610             price => "num", # First a numeric comparison of price
611             name => "desc", # or if same, a reverse comparison of the name
612             )
613             ->order_by(
614             price => [ "num", "desc" ],
615             name => "str",
616             )
617             # accessor is a method call with arg: $_->price_with_discount($discount)
618             ->order_by(
619             [ price_with_discount => $discount ] => [ "num", "desc" ],
620             name => [ str => sub { uc($_) } ],
621             "id",
622             )
623              
624              
625              
626             =head2 List and Scalar Context
627              
628             Almost all of the methods are context sensitive, i.e. they return a
629             list in list context and an arrayref in scalar context, just like
630             L.
631              
632             B: I
633              
634             When in doubt, assume they work like C and C (i.e. return a
635             list), and convert the return value to references where you might have
636             an unobvious list context. E.g.
637              
638             =head3 Incorrect
639              
640             $self->my_method(
641             # Wrong, this is list context and wouldn't return an array ref
642             books => $books->filter_by("is_published"),
643             );
644              
645             =head3 Correct
646              
647             $self->my_method(
648             # Correct, put the returned list in an anonymous array ref
649             books => [ $books->filter_by("is_published") ],
650             );
651             $self->my_method(
652             # Correct, ensure scalar context to get an array ref
653             books => scalar $books->filter_by("is_published"),
654             );
655              
656             # Probably the nicest, since ->to_ref goes at the end
657             $self->my_method(
658             # Correct, use ->to_ref to ensure an array ref is returned
659             books => $books->filter_by("is_published")->to_ref,
660             );
661              
662              
663              
664             =head1 METHODS ON ARRAYS
665              
666             =cut
667              
668             package # hide from PAUSE
669             autobox::Transform::Array;
670              
671 18     18   757 use autobox::Core;
  18         11003  
  18         106  
672 18     18   21822 use Sort::Maker ();
  18         78250  
  18         436  
673 18     18   9454 use List::MoreUtils ();
  18         220092  
  18         63312  
674              
675              
676              
677             =head2 @array->filter($predicate = *is_true_subref*) : @array | @$array
678              
679             Similar to Perl's C, return an @array with values for which
680             $predicate yields a true value.
681              
682             $predicate can be a subref, string, undef, regex, or hashref. See
683             L.
684              
685             The default (no $predicate) is a subref which retains true values in
686             the @array.
687              
688             Examples:
689              
690             my @apples = $fruit->filter("apple");
691             my @any_apple = $fruit->filter( qr/apple/i );
692             my @publishers = $authors->filter(
693             sub { $_->publisher->name =~ /Orbit/ },
694             );
695              
696              
697             =head3 filter and grep
698              
699             L's C method takes a subref, just like this
700             method. C also supports the other predicate types, like
701             string, regex, etc.
702              
703              
704             =cut
705              
706             sub filter {
707 9     9   21488 my $array = shift;
708 9         17 my ($predicate) = @_;
709             my $subref = autobox::Transform::_predicate(
710             "filter",
711             $predicate,
712 8     8   16 sub { !! $_ },
713 9         35 );
714              
715 8 50       22 my $result = eval {
716 8         13 [ CORE::grep { $subref->( $_ ) } @$array ]
  29         172  
717             } or autobox::Transform::throw($@);
718              
719 8 100       95 return wantarray ? @$result : $result;
720             }
721              
722             =head2 @array->reject($predicate = *is_false_subref*) : @array | @$array
723              
724             Similar to the Unix command C, return an @array with values
725             for which $predicate yields a I value.
726              
727             $predicate can be a subref, string, undef, regex, or hashref. See
728             L.
729              
730             The default (no $predicate) is a subref which I true
731             values in the @array.
732              
733             Examples:
734              
735             my @apples = $fruit->reject("apple");
736             my @any_apple = $fruit->reject( qr/apple/i );
737             my @publishers = $authors->reject(
738             sub { $_->publisher->name =~ /Orbit/ },
739             );
740              
741             =cut
742              
743             sub reject {
744 9     9   24583 my $array = shift;
745 9         17 my ($predicate) = @_;
746             my $subref = autobox::Transform::_predicate(
747             "reject",
748             $predicate,
749 8     8   18 sub { !! $_ },
750 9         37 );
751              
752 8 50       21 my $result = eval {
753 8         17 [ CORE::grep { ! $subref->( $_ ) } @$array ]
  29         195  
754             } or autobox::Transform::throw($@);
755              
756 8 100       110 return wantarray ? @$result : $result;
757             }
758              
759              
760              
761             my $option__group = {
762             str => "operator",
763             num => "operator",
764             asc => "direction",
765             desc => "direction",
766             };
767             sub _group__value_from_order_options {
768 30     30   43 my ($method_name, $options) = @_;
769 30         45 my $group__value = {};
770 30         49 for my $option (grep { $_ } @$options) {
  38         85  
771 38         40 my $group;
772              
773 38         60 my $ref_option = ref($option);
774 38 100       74 ( $ref_option eq "CODE" ) and $group = "extract";
775 38 100       64 if ( $ref_option eq "Regexp" ) {
776 5         8 my $regex = $option;
777 5     23   17 $option = sub { join("", m/$regex/) };
  23         142  
778 5         9 $group = "extract";
779             }
780              
781 38 100 100     184 $group ||= $option__group->{ $option }
782             or Carp::croak("->$method_name(): Invalid comparison option ($option), did you mean ->order_by('$option')?");
783              
784 36 100       85 exists $group__value->{ $group }
785             and Carp::croak("->$method_name(): Conflicting comparison options: ($group__value->{ $group }) and ($option)");
786              
787 34         82 $group__value->{ $group } = $option;
788             }
789              
790 26         40 return $group__value;
791             }
792              
793             my $transform__sorter = {
794             str => "string",
795             num => "number",
796             asc => "ascending",
797             desc => "descending",
798             };
799             sub _sorter_from_comparisons {
800 28     28   53 my ($method_name, $comparisons) = @_;
801              
802 28         35 my @sorter_keys;
803             my @extracts;
804 28         55 for my $options (@$comparisons) {
805 30 100       98 ref($options) eq "ARRAY" or $options = [ $options ];
806              
807             # Check one comparison
808 30         63 my $group__value = _group__value_from_order_options(
809             $method_name,
810             $options,
811             );
812              
813 26   100     70 my $operator = $group__value->{operator} // "str";
814 26   100     75 my $direction = $group__value->{direction} // "asc";
815 26   100 68   100 my $extract = $group__value->{extract} // sub { $_ };
  68         164  
816              
817 26         48 my $sorter_operator = $transform__sorter->{$operator};
818 26         41 my $sorter_direction = $transform__sorter->{$direction};
819              
820 26         37 push(@extracts, $extract);
821 26         34 my $extract_index = @extracts;
822 26         114 push(
823             @sorter_keys,
824             $sorter_operator => [
825             $sorter_direction,
826             # Sort this one by the extracted value
827             code => "\$_->[ $extract_index ]",
828             ],
829             );
830             }
831              
832 24 50       81 my $sorter = Sort::Maker::make_sorter(
833             "plain", "ref_in", "ref_out",
834             @sorter_keys,
835             ) or Carp::croak(__PACKAGE__ . " internal error: $@");
836              
837 24         11198 return ($sorter, \@extracts);
838             }
839              
840             sub _item_values_array_from_array_item_extracts {
841 12     12   20 my ($array, $extracts) = @_;
842              
843             # Custom Schwartzian Transform where each array item is arrayref of:
844             # 0: $array item; rest 1..n : comparison values
845             # The sorter keys are simply indexed into the nth value
846             return [
847             map { ## no critic
848 12         27 my $item = $_;
  50         66  
849             [
850             $item, # array item to compare
851             map {
852 50         60 my $extract = $_; local $_ = $item;
  55         71  
  55         60  
853 55         65 $extract->();
854             } @$extracts, # comparison values for array item
855             ];
856             }
857             @$array
858             ];
859             }
860              
861             sub _item_values_array_from_map_by_extracts {
862 12     12   22 my ($array, $accessors, $extracts) = @_;
863              
864             # Custom Schwartzian Transform where each array item is arrayref of:
865             # 0: $array item; rest 1..n : comparison values
866             # The sorter keys are simply indexed into the nth value
867             my $accessor_values = $accessors->map(
868 13     13   86 sub { [ map_by($array, $_) ] }
869 12         76 );
870             return [
871             map { ## no critic
872 12         64 my $item = $_;
  49         62  
873 49         53 my $accessor_index = 0;
874             [
875             $item, # array item to compare
876             map {
877 49         57 my $extract = $_;
  54         59  
878 54         50 my $value = shift @{$accessor_values->[ $accessor_index++ ]};
  54         75  
879              
880 54         61 local $_ = $value;
881 54         79 $extract->();
882             } @$extracts, # comparison values for array item
883             ];
884             }
885             @$array
886             ];
887             }
888              
889             =head2 @array->order(@comparisons = ("str")) : @array | @$array
890              
891             Return @array ordered according to the @comparisons. The default
892             comparison is the same as the default sort, e.g. a normal string
893             comparison of the @array values.
894              
895             If the first item in @comparison ends in a tie, the next one is used,
896             etc.
897              
898             Each I consists of a single I
899             options>, e.g. C/C, C/C, or a subref/regex. See
900             L for details about how these work.
901              
902             Examples:
903              
904             @book_genres->order;
905             @book_genres->order("desc");
906             @book_prices->order([ "num", "desc" ]);
907             @books->order([ sub { $_->{price} }, "desc", "num" ]);
908             @log_lines->order([ num => qr/pid: "(\d+)"/ ]);
909             @books->order(
910             [ sub { $_->{price} }, "desc", "num" ] # first price
911             sub { $_->{name} }, # then name
912             );
913              
914             =cut
915              
916             sub order {
917 14     14   11076 my $array = shift;
918 14         29 my (@comparisons) = @_;
919 14 100       34 @comparisons or @comparisons = ("str");
920              
921 14         34 my ($sorter, $extracts) = _sorter_from_comparisons("order", \@comparisons);
922              
923 12         35 my $item_values_array = _item_values_array_from_array_item_extracts(
924             $array,
925             $extracts,
926             );
927 12         281 my $sorted_array = $sorter->($item_values_array);
928 12         502 my $result = [ map { $_->[0] } @$sorted_array ];
  50         70  
929              
930 12 100       152 return wantarray ? @$result : $result;
931             }
932              
933              
934              
935             =head2 @array->group($value_subref = item) : %key_value | %$key_value
936              
937             Group the @array items into a hashref with the items as keys.
938              
939             The default $value_subref puts each item in the list as the hash
940             value. If the key is repeated, the value is overwritten with the last
941             object.
942              
943             Example:
944              
945             my $title_book = $book_titles->group;
946             # {
947             # "Leviathan Wakes" => "Leviathan Wakes",
948             # "Caliban's War" => "Caliban's War",
949             # "The Tree-Body Problem" => "The Tree-Body Problem",
950             # "The Name of the Wind" => "The Name of the Wind",
951             # },
952              
953             =head3 The $value_subref
954              
955             For simple cases of just grouping a single key to a single value, the
956             $value_subref is straightforward to use.
957              
958             The hash key is the array item. The hash value is whatever is returned
959             from
960              
961             my $new_value = $value_sub->($current_value, $object, $key);
962              
963             =over 4
964              
965             =item
966              
967             C<$current> value is the current hash value for this key (or undef if
968             the first one).
969              
970             =item
971              
972             C<$object> is the current item in the list. The current $_ is also set
973             to this.
974              
975             =item
976              
977             C<$key> is the array item.
978              
979             =back
980              
981             See also: C<-Egroup_by>.
982              
983             =cut
984              
985             sub __core_group {
986 5     5   11 my( $name, $array, $value_sub ) = @_;
987 5 0       11 @$array or return wantarray ? () : { };
    50          
988              
989 5         7 my %key_value;
990 5         10 for my $item (@$array) {
991 21         24 my $key = $item;
992              
993 21         29 my $current_value = $key_value{ $key };
994 21         34 local $_ = $item;
995 21         30 my $new_value = $value_sub->($current_value, $item, $key);
996              
997 21         41 $key_value{ $key } = $new_value;
998             }
999              
1000 5 100       57 return wantarray ? %key_value : \%key_value;
1001             }
1002              
1003             sub group {
1004 3     3   1529 my $array = shift;
1005 3         8 my ($value_sub) = _normalized_accessor_args_subref(@_);
1006              
1007 3   100 8   18 $value_sub //= sub { $_ };
  8         12  
1008 3 50       9 ref($value_sub) eq "CODE"
1009             or Carp::croak("group(\$value_sub): \$value_sub ($value_sub) is not a sub ref");
1010              
1011 3         7 return __core_group("group", $array, $value_sub);
1012             }
1013              
1014              
1015              
1016             =head2 @array->group_count : %key_count | %$key_count
1017              
1018             Just like C, but the hash values are the the number of
1019             instances each item occurs in the list.
1020              
1021             Example:
1022              
1023             $book_genres->group_count;
1024             # {
1025             # "Sci-fi" => 3,
1026             # "Fantasy" => 1,
1027             # },
1028              
1029             There are three books counted for the "Sci-fi" key.
1030              
1031             =cut
1032              
1033             sub group_count {
1034 1     1   3549 my $array = shift;
1035              
1036             my $value_sub = sub {
1037 5   100 5   14 my $count = shift // 0;
1038 5         7 return ++$count;
1039 1         4 };
1040              
1041 1         4 return __core_group("group_count", $array, $value_sub);
1042             }
1043              
1044              
1045              
1046              
1047             =head2 @array->group_array : %key_objects | %$key_objects
1048              
1049             Just like C, but the hash values are arrayrefs containing those
1050             same array items.
1051              
1052             Example:
1053              
1054             $book_genres->group_array;
1055             # {
1056             # "Sci-fi" => [ "Sci-fi", "Sci-fi", "Sci-fi" ],
1057             # "Fantasy" => [ "Fantasy" ],
1058             # },
1059              
1060             The three Sci-fi genres are collected under the Sci-fi key.
1061              
1062             =cut
1063              
1064             sub group_array {
1065 1     1   12 my $array = shift;
1066              
1067             my $value_sub = sub {
1068 4   100 4   12 my $value_array = shift // [];
1069 4         7 push( @$value_array, $_ );
1070 4         6 return $value_array;
1071 1         5 };
1072              
1073 1         4 return __core_group("group_array", $array, $value_sub);
1074             }
1075              
1076              
1077              
1078             =head2 @array->flat() : @array | @$array
1079              
1080             Return a (one level) flattened array, assuming the array items
1081             themselves are array refs. I.e.
1082              
1083             [
1084             [ 1, 2, 3 ],
1085             [ "a", "b" ],
1086             [ [ 1, 2 ], { 3 => 4 } ]
1087             ]->flat
1088              
1089             returns
1090              
1091             [ 1, 2, 3, "a", "b ", [ 1, 2 ], { 3 => 4 } ]
1092              
1093             This is useful if e.g. a C<-Emap_by("some_method")> returns
1094             arrayrefs of objects which you want to do further method calls
1095             on. Example:
1096              
1097             # ->books returns an arrayref of Book objects with a ->title
1098             $authors->map_by("books")->flat->map_by("title")
1099              
1100             Note: This is different from autobox::Core's ->flatten, which reurns a
1101             list rather than an array and therefore can't be used in this
1102             way.
1103              
1104             =cut
1105              
1106             sub flat {
1107 2     2   6 my $array = shift;
1108             ###JPL: eval and report error from correct place
1109 2         4 my $result = [ map { @$_ } @$array ];
  3         7  
1110 2 50       12 return wantarray ? @$result : $result;
1111             }
1112              
1113             =head2 @array->to_ref() : $arrayref
1114              
1115             Return the reference to the @array, regardless of context.
1116              
1117             Useful for ensuring the last array method return a reference while in
1118             scalar context. Typically:
1119              
1120             do_stuff(
1121             books => $author->map_by("books")->to_ref,
1122             );
1123              
1124             map_by is called in list context, so without ->to_ref it would have
1125             return an array, not an arrayref.
1126              
1127             =cut
1128              
1129             sub to_ref {
1130 43     43   1227 my $array = shift;
1131 43         203 return $array;
1132             }
1133              
1134             =head2 @array->to_array() : @array
1135              
1136             Return the @array, regardless of context. This is mostly useful if
1137             called on a ArrayRef at the end of a chain of method calls.
1138              
1139             =cut
1140              
1141             sub to_array {
1142 2     2   3307 my $array = shift;
1143 2         10 return @$array;
1144             }
1145              
1146             =head2 @array->to_hash() : %hash | %$hash
1147              
1148             Return the item pairs in the @array as the key-value pairs of a %hash
1149             (context sensitive).
1150              
1151             Useful if you need to continue calling %hash methods on it.
1152              
1153             Die if there aren't an even number of items in @array.
1154              
1155             =cut
1156              
1157             sub to_hash {
1158 2     2   2736 my $array = shift;
1159 2         4 my $count = @$array;
1160              
1161 2 100       21 $count % 2 and Carp::croak(
1162             "\@array->to_hash on an array with an odd number of elements ($count)",
1163             );
1164              
1165 1         4 my %new_hash = @$array;
1166 1 50       5 return wantarray ? %new_hash : \%new_hash;
1167             }
1168              
1169              
1170              
1171             =head1 METHODS ON ARRAYS CONTAINING OBJECTS/HASHES
1172              
1173             =cut
1174              
1175             *_normalized_accessor_args_predicate
1176             = \&autobox::Transform::_normalized_accessor_args_predicate;
1177             *_normalized_accessor_args_subref
1178             = \&autobox::Transform::_normalized_accessor_args_subref;
1179              
1180             sub __invoke_by {
1181 90     90   164 my $invoke = shift;
1182 90         110 my $array = shift;
1183 90         173 my( $accessor, $args, $subref_name, $subref ) = @_;
1184 90 100       180 defined($accessor) or Carp::croak("->${invoke}_by() missing argument: \$accessor");
1185 89 50       172 @$array or return wantarray ? () : [ ];
    100          
1186              
1187 87   100     330 $args //= [];
1188 87 100       245 if ( ref($array->[0] ) eq "HASH" ) {
1189 8 100 66     62 ( defined($args) && (@$args) ) # defined and isn't empty
1190             and Carp::croak("${invoke}_by([ '$accessor', \@args ]): \@args ($args) only supported for method calls, not hash key access");
1191 6         10 $invoke .= "_key";
1192             }
1193              
1194             ###JPL: move up
1195 85 100       207 ref($args) eq "ARRAY"
1196             or Carp::croak("${invoke}_by([ '$accessor', \@args ]): \@args ($args) is not a list");
1197              
1198 84 100       150 if( $subref_name ) {
1199 25 50       45 ref($subref) eq "CODE"
1200             or Carp::croak("${invoke}_by([ '$accessor', \@args ], \$$subref_name): \$$subref_name ($subref) is not an sub ref");
1201             }
1202              
1203 84         117 my %seen;
1204             my $invoke_sub = {
1205 51     51   85 map => sub { [ CORE::map { $_->$accessor( @$args ) } @$array ] },
  184         4293  
1206 6     6   10 map_key => sub { [ CORE::map { $_->{$accessor} } @$array ] },
  24         50  
1207 13     13   24 filter => sub { [ CORE::grep { $subref->( local $_ = $_->$accessor( @$args ) ) } @$array ] },
  39         761  
1208 0     0   0 filter_key => sub { [ CORE::grep { $subref->( local $_ = $_->{$accessor} ) } @$array ] },
  0         0  
1209 12     12   19 reject => sub { [ CORE::grep { ! $subref->( local $_ = $_->$accessor( @$args ) ) } @$array ] },
  36         737  
1210 0     0   0 reject_key => sub { [ CORE::grep { ! $subref->( local $_ = $_->{$accessor} ) } @$array ] },
  0         0  
1211 2   50 2   5 uniq => sub { [ CORE::grep { ! $seen{ $_->$accessor( @$args ) // "" }++ } @$array ] },
  6         178  
1212 0   0 0   0 uniq_key => sub { [ CORE::grep { ! $seen{ $_->{$accessor} // "" }++ } @$array ] },
  0         0  
1213 84         1269 }->{$invoke};
1214              
1215 84 100       1009 my $result = eval { $invoke_sub->() }
  84         155  
1216             or autobox::Transform::throw($@);
1217              
1218 83 100       1661 return wantarray ? @$result : $result;
1219             }
1220              
1221             =head2 @array->map_by($accessor) : @array | @$array
1222              
1223             $accessor is either a string, or an arrayref where the first item is a
1224             string.
1225              
1226             Call the $accessor on each object in @array, or get the hash key value
1227             on each hashref in @array. Like:
1228              
1229             map { $_->$accessor() } @array
1230             # or
1231             map { $_->{$accessor} } @array
1232              
1233             Examples:
1234              
1235             my @author_names = $authors->map_by("name");
1236             my $author_names = @publishers->map_by("authors")->flat->map_by("name");
1237              
1238             Or get the hash key value. Example:
1239              
1240             my @review_scores = $reviews->map_by("score");
1241              
1242             Alternatively for when @array contains objects, the $accessor can be
1243             an arrayref. The first item is the method name, and the rest of the
1244             items are passed as args in the method call. This obviously won't work
1245             when the @array contains hashrefs.
1246              
1247             Examples:
1248              
1249             my @prices_including_tax = $books->map_by([ "price_with_tax", $tax_pct ]);
1250             my $prices_including_tax = $books->map_by([ price_with_tax => $tax_pct ]);
1251              
1252             =cut
1253              
1254             sub map_by {
1255 63     63   79063 my $array = shift;
1256 63         157 my ($accessor, $args) = _normalized_accessor_args_subref(@_);
1257 63         149 return __invoke_by("map", $array, $accessor, $args);
1258             }
1259              
1260              
1261              
1262             =head2 @array->filter_by($accessor, $predicate = *is_true_subref*) : @array | @$array
1263              
1264             $accessor is either a string, or an arrayref where the first item is a
1265             string.
1266              
1267             Call the $accessor on each object in the list, or get the hash key
1268             value on each hashref in the list.
1269              
1270             Example:
1271              
1272             my @prolific_authors = $authors->filter_by("is_prolific");
1273              
1274             Alternatively the $accessor is an arrayref. The first item is the
1275             accessor name, and the rest of the items are passed as args the method
1276             call. This only works when working with objects, not with hashrefs.
1277              
1278             Example:
1279              
1280             my @books_to_charge_for = $books->filter_by([ price_with_tax => $tax_pct ]);
1281              
1282             Use the $predicate to determine whether the value should remain.
1283             $predicate can be a subref, string, undef, regex, or hashref. See
1284             L.
1285              
1286             The default (no $predicate) is a subref which retains true values in
1287             the result @array.
1288              
1289             Examples:
1290              
1291             # Custom predicate subref
1292             my @authors = $authors->filter_by(
1293             "publisher",
1294             sub { $_->name =~ /Orbit/ },
1295             );
1296              
1297             # Call method with args and match a regex
1298             my @authors = $authors->filter_by(
1299             [ publisher_affiliation => "with" ],
1300             qr/Orbit/ },
1301             );
1302              
1303             Note: if you do something complicated with a $predicate subref, it
1304             might be easier and more readable to simply use
1305             C<$array-$filter()>.
1306              
1307              
1308             =head3 Alias
1309              
1310             C is an alias for C. Unlike C vs C,
1311             this one works exaclty the same way.
1312              
1313             =cut
1314              
1315             sub filter_by {
1316 13     13   31415 my $array = shift;
1317 13         33 my ($accessor, $args, $predicate) = _normalized_accessor_args_predicate(@_);
1318             my $subref = autobox::Transform::_predicate(
1319             "filter_by",
1320             $predicate,
1321 15     15   485 sub { !! $_ },
1322 13         52 );
1323             # filter_by $value, if passed the method value must match the value?
1324 13         38 return __invoke_by(
1325             "filter",
1326             $array,
1327             $accessor,
1328             $args,
1329             filter_subref => $subref,
1330             );
1331             }
1332              
1333             *grep_by = \&filter_by;
1334              
1335              
1336              
1337             =head2 @array->reject_by($accessor, $predicate = *is_false_subref*) : @array | @$array
1338              
1339             C is the same as L>, except it I
1340             items that matches the $predicate.
1341              
1342             Example:
1343              
1344             my @unproductive_authors = $authors->reject_by("is_prolific");
1345              
1346             The default (no $predicate) is a subref which I true
1347             values in the result @array.
1348              
1349             =cut
1350              
1351             sub reject_by {
1352 12     12   29659 my $array = shift;
1353 12         31 my ($accessor, $args, $predicate) = _normalized_accessor_args_predicate(@_);
1354             my $subref = autobox::Transform::_predicate(
1355             "reject_by",
1356             $predicate,
1357 12     12   400 sub { !! $_ },
1358 12         50 );
1359             # filter_by $value, if passed the method value must match the value?
1360 12         37 return __invoke_by(
1361             "reject",
1362             $array,
1363             $accessor,
1364             $args,
1365             reject_subref => $subref,
1366             );
1367             }
1368              
1369              
1370              
1371             =head2 @array->uniq_by($accessor) : @array | @$array
1372              
1373             $accessor is either a string, or an arrayref where the first item is a
1374             string.
1375              
1376             Call the $accessor on each object in the list, or get the hash key
1377             value on each hashref in the list. Return list of items wich have a
1378             unique set of return values. The order is preserved. On duplicates,
1379             keep the first occurrence.
1380              
1381             Examples:
1382              
1383             # You have gathered multiple Author objects with duplicate ids
1384             my @authors = $authors->uniq_by("author_id");
1385              
1386             Alternatively the $accessor is an arrayref. The first item is the
1387             accessor name, and the rest of the items are passed as args the method
1388             call. This only works when working with objects, not with hashrefs.
1389              
1390             Examples:
1391              
1392             my @example_book_at_price_point = $books->uniq_by(
1393             [ price_with_tax => $tax_pct ],
1394             );
1395              
1396             =cut
1397              
1398             sub uniq_by {
1399 2     2   7356 my $array = shift;
1400 2         5 my ($accessor, $args) = _normalized_accessor_args_subref(@_);
1401 2         6 return __invoke_by("uniq", $array, $accessor, $args);
1402             }
1403              
1404             =head2 @array->order_by(@accessor_comparison_pairs) : @array | @$array
1405              
1406             Return @array ordered according to the @accessor_comparison_pairs.
1407              
1408             The comparison value comes from an initial
1409             C<@array->map_by($accessor)> for each accessor-comparison pair. It is
1410             important that the $accessor call returns exactly a single scalar that
1411             can be compared with the other values.
1412              
1413             It then works just like with C<-Eorder>.
1414              
1415             $books->order_by("name"); # default order, i.e. "str"
1416             $books->order_by(price => "num");
1417             $books->order_by(price => [ "num", "desc" ]);
1418              
1419             As with C, if the $accessor is used on an object, the method
1420             call can include arguments.
1421              
1422             $books->order_by([ price_wih_tax => $tax_rate ] => "num");
1423              
1424             Just like with C, the value returned by the accessor can be
1425             transformed using a sub, or be matched against a regex.
1426              
1427             $books->order_by(price => [ num => sub { int($_) } ]);
1428              
1429             # Ignore leading "The" in book titles by optionally matching it
1430             # with a non-capturing group and the rest with a capturing group
1431             # paren
1432             $books->order_by( title => qr/^ (?: The \s+ )? (.+) /x );
1433              
1434             If a comparison is missing for the last pair, the default is a normal
1435             C comparison.
1436              
1437             $books->order_by("name"); # default "str"
1438              
1439             If the first comparison ends in a tie, the next pair is used,
1440             etc. Note that in order to provide accessor-comparison pairs, it's
1441             often necessary to provide a default "str" comparison just to make it
1442             a pair.
1443              
1444             $books->order_by(
1445             author => "str",
1446             price => [ "num", "desc" ],
1447             );
1448              
1449             =cut
1450              
1451             sub order_by {
1452 15     15   36588 my $array = shift;
1453 15         32 my (@accessors_and_comparisons) = @_;
1454              
1455 15         20 my $i = 0;
1456             my ($accessors, $comparisons) = List::MoreUtils::part
1457 27     27   75 { $i++ %2 }
1458 15         87 @accessors_and_comparisons;
1459 15   100     64 $accessors ||= [];
1460 15   100     39 $comparisons ||= [];
1461 15 100       45 @$accessors or Carp::croak("->order_by() missing argument: \$accessor");
1462             # Default comparison
1463 14 100       27 @$accessors == @$comparisons or push(@$comparisons, "str");
1464              
1465 14         31 my ($sorter, $extracts) = _sorter_from_comparisons("order_by", $comparisons);
1466              
1467 12         33 my $item_values_array = _item_values_array_from_map_by_extracts(
1468             $array,
1469             $accessors,
1470             $extracts,
1471             );
1472 12         216 my $sorted_array = $sorter->($item_values_array);
1473 12         479 my $result = [ map { $_->[0] } @$sorted_array ];
  49         63  
1474              
1475 12 50       139 return wantarray ? @$result : $result;
1476             }
1477              
1478             =head2 @array->group_by($accessor, $value_subref = object) : %key_value | %$key_value
1479              
1480             $accessor is either a string, or an arrayref where the first item is a
1481             string.
1482              
1483             Call C<-E$accessor> on each object in the array, or get the hash
1484             key for each hashref in the array (just like C<-Emap_by>) and
1485             group the values as keys in a hashref.
1486              
1487             The default $value_subref puts each object in the list as the hash
1488             value. If the key is repeated, the value is overwritten with the last
1489             object.
1490              
1491             Example:
1492              
1493             my $title_book = $books->group_by("title");
1494             # {
1495             # "Leviathan Wakes" => $books->[0],
1496             # "Caliban's War" => $books->[1],
1497             # "The Tree-Body Problem" => $books->[2],
1498             # "The Name of the Wind" => $books->[3],
1499             # },
1500              
1501             =head3 The $value_subref
1502              
1503             For simple cases of just grouping a single key to a single value, the
1504             $value_subref is straightforward to use.
1505              
1506             The hash key is whatever is returned from C<$object-E$accessor>.
1507              
1508             The hash value is whatever is returned from
1509              
1510             my $new_value = $value_sub->($current_value, $object, $key);
1511              
1512             =over 4
1513              
1514             =item
1515              
1516             C<$current> value is the current hash value for this key (or undef if the first one).
1517              
1518             =item
1519              
1520             C<$object> is the current item in the list. The current $_ is also set to this.
1521              
1522             =item
1523              
1524             C<$key> is the key returned by $object->$accessor(@$args)
1525              
1526             =back
1527              
1528             A simple example would be to group by the accessor, but instead of the
1529             object used as the value you want to look up an attribute on each
1530             object:
1531              
1532             my $book_id__author = $books->group_by("id", sub { $_->author });
1533             # keys: book id; values: author
1534              
1535             If you want to create an aggregate value the $value_subref can be a
1536             bit tricky to use, so the most common thing would probably be to use
1537             one of the more specific group_by-methods (see below). It should be
1538             capable enough to achieve what you need though.
1539              
1540             =cut
1541              
1542             sub __core_group_by {
1543 20     20   57 my( $name, $array, $accessor, $args, $value_sub ) = @_;
1544 20 100       58 $accessor or Carp::croak("->$name() missing argument: \$accessor");
1545 19 0       45 @$array or return wantarray ? () : { };
    50          
1546              
1547 19         23 my $invoke = do {
1548             # Hash key
1549 19 100       73 if ( ref($array->[0] ) eq "HASH" ) {
1550 2 100       25 defined($args)
1551             and Carp::croak("$name([ '$accessor', \@args ]): \@args ($args) only supported for method calls, not hash key access.");
1552 1         3 "key";
1553             }
1554             # Method
1555             else {
1556 17   100     64 $args //= [];
1557 17 100       62 ref($args) eq "ARRAY"
1558             or Carp::croak("$name([ '$accessor', \@args ], \$value_sub): \@args ($args) is not a list");
1559 15         24 "method";
1560             }
1561             };
1562              
1563             my $invoke_sub = {
1564 55     55   1017 method => sub { [ shift->$accessor(@$args) ] },
1565 3     3   9 key => sub { [ shift->{$accessor} ] },
1566 16         93 }->{$invoke};
1567              
1568 16         63 my %key_value;
1569 16         31 for my $object (@$array) {
1570 58 100       65 my $key_ref = eval { $invoke_sub->($object) }
  58         78  
1571             or autobox::Transform::throw($@);
1572 57         828 my $key = $key_ref->[0];
1573              
1574 57         73 my $current_value = $key_value{ $key };
1575 57         65 local $_ = $object;
1576 57         87 my $new_value = $value_sub->($current_value, $object, $key);
1577              
1578 57         145 $key_value{ $key } = $new_value;
1579             }
1580              
1581 15 100       158 return wantarray ? %key_value : \%key_value;
1582             }
1583              
1584             sub group_by {
1585 10     10   45144 my $array = shift;
1586 10         26 my ($accessor, $args, $value_sub) = _normalized_accessor_args_subref(@_);
1587              
1588 10   100 15   57 $value_sub //= sub { $_ };
  15         15  
1589 10 50       25 ref($value_sub) eq "CODE"
1590             or Carp::croak("group_by([ '$accessor', \@args ], \$value_sub): \$value_sub ($value_sub) is not a sub ref");
1591              
1592 10         20 return __core_group_by("group_by", $array, $accessor, $args, $value_sub);
1593             }
1594              
1595             =head2 @array->group_by_count($accessor) : %key_count | %$key_count
1596              
1597             $accessor is either a string, or an arrayref where the first item is a
1598             string.
1599              
1600             Just like C, but the hash values are the the number of
1601             instances each $accessor value occurs in the list.
1602              
1603             Example:
1604              
1605             $books->group_by_count("genre"),
1606             # {
1607             # "Sci-fi" => 3,
1608             # "Fantasy" => 1,
1609             # },
1610              
1611             C<$book-Egenre()> returns the genre string. There are three books
1612             counted for the "Sci-fi" key.
1613              
1614             =cut
1615              
1616             sub group_by_count {
1617 8     8   19129 my $array = shift;
1618 8         27 my ($accessor, $args) = _normalized_accessor_args_subref(@_);
1619              
1620             my $value_sub = sub {
1621 26   100 26   84 my $count = shift // 0; return ++$count;
  26         33  
1622 8         32 };
1623              
1624 8         26 return __core_group_by("group_by_count", $array, $accessor, $args, $value_sub);
1625             }
1626              
1627             =head2 @array->group_by_array($accessor) : %key_objects | %$key_objects
1628              
1629             $accessor is either a string, or an arrayref where the first item is a
1630             string.
1631              
1632             Just like C, but the hash values are arrayrefs containing
1633             the objects which has each $accessor value.
1634              
1635             Example:
1636              
1637             my $genre_books = $books->group_by_array("genre");
1638             # {
1639             # "Sci-fi" => [ $sf_book_1, $sf_book_2, $sf_book_3 ],
1640             # "Fantasy" => [ $fantasy_book_1 ],
1641             # },
1642              
1643             $book->genre() returns the genre string. The three Sci-fi book objects
1644             are collected under the Sci-fi key.
1645              
1646             =cut
1647              
1648             sub group_by_array {
1649 2     2   2969 my $array = shift;
1650 2         6 my ($accessor, $args) = _normalized_accessor_args_subref(@_);
1651              
1652             my $value_sub = sub {
1653 8   100 8   21 my $array = shift // [];
1654 8         24 push( @$array, $_ );
1655 8         12 return $array;
1656 2         9 };
1657              
1658 2         6 return __core_group_by("group_by_array", $array, $accessor, $args, $value_sub);
1659             }
1660              
1661              
1662              
1663             =head1 METHODS ON HASHES
1664              
1665             =cut
1666              
1667             package # hide from PAUSE
1668             autobox::Transform::Hash;
1669              
1670 18     18   188 use autobox::Core;
  18         48  
  18         140  
1671              
1672              
1673              
1674             sub key_value {
1675 11     11   1197 my $hash = shift;
1676 11         19 my( $original_key, $new_key ) = @_;
1677 11   66     40 $new_key //= $original_key;
1678 11         24 my %key_value = ( $new_key => $hash->{$original_key} );
1679 11 100       91 return wantarray ? %key_value : \%key_value;
1680             }
1681              
1682             sub __core_key_value_if {
1683 13     13   19 my $hash = shift;
1684 13         17 my( $comparison_sub, $original_key, $new_key ) = @_;
1685 13 50       25 $comparison_sub->($hash, $original_key) or return wantarray ? () : {};
    100          
1686 7         14 return key_value($hash, $original_key, $new_key)
1687             }
1688              
1689             sub key_value_if_exists {
1690 4     4   3034 my $hash = shift;
1691 4         10 my( $original_key, $new_key ) = @_;
1692             return __core_key_value_if(
1693             $hash,
1694 4     4   18 sub { !! exists shift->{ shift() } },
1695 4         17 $original_key,
1696             $new_key
1697             );
1698             }
1699              
1700             sub key_value_if_true {
1701 5     5   2763 my $hash = shift;
1702 5         10 my( $original_key, $new_key ) = @_;
1703             return __core_key_value_if(
1704             $hash,
1705 5     5   38 sub { !! shift->{ shift() } },
1706 5         22 $original_key,
1707             $new_key
1708             );
1709             }
1710              
1711             sub key_value_if_defined {
1712 4     4   2798 my $hash = shift;
1713 4         7 my( $original_key, $new_key ) = @_;
1714             return __core_key_value_if(
1715             $hash,
1716 4     4   24 sub { defined( shift->{ shift() } ) },
1717 4         17 $original_key,
1718             $new_key
1719             );
1720             }
1721              
1722              
1723              
1724             =head2 %hash->map_each($key_value_subref) : %new_hash | %$new_hash
1725              
1726             Map each key-value pair in the hash using the
1727             $key_value_subref. Similar to how to how map transforms a list into
1728             another list, map_each transforms a hash into another hash.
1729              
1730             C<$key_value_subref-E($key, $value)> is called for each pair (with
1731             $_ set to the value).
1732              
1733             The subref should return an even-numbered list with zero or more
1734             key-value pairs which will make up the %new_hash. Typically two items
1735             are returned in the list (the key and the value).
1736              
1737             =head3 Example
1738              
1739             { a => 1, b => 2 }->map_each(sub { "$_[0]$_[0]" => $_ * 2 });
1740             # Returns { aa => 2, bb => 4 }
1741              
1742             =cut
1743              
1744             sub map_each {
1745 7     7   12519 my $hash = shift;
1746 7         13 my ($key_value_subref) = @_;
1747 7   100     21 $key_value_subref //= "";
1748 7 100       45 ref($key_value_subref) eq "CODE"
1749             or Carp::croak("map_each(\$key_value_subref): \$key_value_subref ($key_value_subref) is not a sub ref");
1750             my $new_hash = {
1751             map { ## no critic
1752 5         16 my $key = $_;
  12         15  
1753 12         16 my $value = $hash->{$key};
1754             {
1755 12         19 local $_ = $value;
  12         18  
1756 12         23 my (@new_key_value) = $key_value_subref->($key, $value);
1757 12 100       76 (@new_key_value % 2) and Carp::croak("map_each \$key_value_subref returned odd number of keys/values");
1758 10         33 @new_key_value;
1759             }
1760             }
1761             keys %$hash,
1762             };
1763              
1764 3 100       36 return wantarray ? %$new_hash : $new_hash;
1765             }
1766              
1767             =head2 %hash->map_each_value($value_subref) : %new_hash | %$new_hash
1768              
1769             Map each value in the hash using the $value_subref, but keep the keys
1770             the same.
1771              
1772             C<$value_subref-E($key, $value)> is called for each pair (with $_
1773             set to the value).
1774              
1775             The subref should return a single value for each key which will make
1776             up the %new_hash (with the same keys but with new mapped values).
1777              
1778             =head3 Example
1779              
1780             { a => 1, b => 2 }->map_each_value(sub { $_ * 2 });
1781             # Returns { a => 2, b => 4 }
1782              
1783             =cut
1784              
1785             sub map_each_value {
1786 5     5   11956 my $hash = shift;
1787 5         10 my ($value_subref) = @_;
1788 5   100     15 $value_subref //= "";
1789 5 100       52 ref($value_subref) eq "CODE"
1790             or Carp::croak("map_each_value(\$value_subref): \$value_subref ($value_subref) is not a sub ref");
1791             my $new_hash = {
1792             map { ## no critic
1793 3         11 my $key = $_;
  7         9  
1794 7         9 my $value = $hash->{$key};
1795             {
1796 7         10 local $_ = $value;
  7         8  
1797 7         13 my @new_values = $value_subref->($key, $value);
1798 7 100       44 @new_values > 1 and Carp::croak(
1799             "map_each_value \$value_subref returned multiple values. "
1800             . "You can not assign a list to the value of hash key ($key). "
1801             . "Did you mean to return an arrayref?",
1802             );
1803 6         17 $key => @new_values;
1804             }
1805             }
1806             keys %$hash,
1807             };
1808              
1809 2 100       22 return wantarray ? %$new_hash : $new_hash;
1810             }
1811              
1812             =head2 %hash->map_each_to_array($item_subref) : @new_array | @$new_array
1813              
1814             Map each key-value pair in the hash into a list using the
1815             $item_subref.
1816              
1817             C<$item_subref-E($key, $value)> is called for each pair (with $_
1818             set to the value) in key order.
1819              
1820             The subref should return zero or more list items which will make up
1821             the @new_array. Typically one item is returned.
1822              
1823             =head3 Example
1824              
1825             { a => 1, b => 2 }->map_each_to_array(sub { "$_[0]-$_" });
1826             # Returns [ "a-1", "b-2" ]
1827              
1828             =cut
1829              
1830             sub map_each_to_array {
1831 6     6   13045 my $hash = shift;
1832 6         12 my ($array_item_subref) = @_;
1833 6   100     27 $array_item_subref //= "";
1834 6 100       48 ref($array_item_subref) eq "CODE"
1835             or Carp::croak("map_each_to_array(\$array_item_subref): \$array_item_subref ($array_item_subref) is not a sub ref");
1836             my $new_array = [
1837             map { ## no critic
1838 4         19 my $key = $_;
  13         46  
1839 13         15 my $value = $hash->{$key};
1840             {
1841 13         13 local $_ = $value;
  13         16  
1842 13         24 $array_item_subref->($key, $value);
1843             }
1844             }
1845             sort keys %$hash,
1846             ];
1847              
1848 4 100       43 return wantarray ? @$new_array : $new_array;
1849             }
1850              
1851              
1852             =head2 %hash->filter_each($predicate = *is_true_subref*) : @hash | @$hash
1853              
1854             Return a %hash with values for which $predicate yields a true value.
1855              
1856             $predicate can be a subref, string, undef, regex, or hashref. See
1857             L.
1858              
1859             The default (no $predicate) is a subref which retains true values in
1860             the %hash.
1861              
1862             Examples:
1863              
1864             my @apples = $fruit->filter("apple");
1865             my @any_apple = $fruit->filter( qr/apple/i );
1866             my @publishers = $authors->filter(
1867             sub { $_->publisher->name =~ /Orbit/ },
1868             );
1869              
1870             If the $predicate is a subref, C<$predicate-E($key,
1871             $value)> is called for each pair (with $_ set to the value).
1872              
1873             The subref should return a true value to retain the key-value pair in
1874             the result %hash.
1875              
1876             =head3 Example
1877              
1878             $book_author->filter_each(sub { $_->name =~ /Corey/ });
1879              
1880             =cut
1881              
1882             sub filter_each {
1883 12     12   13047 my $hash = shift;
1884 12         21 my ($predicate) = @_;
1885             my $subref = autobox::Transform::_predicate(
1886             "filter_each",
1887             $predicate,
1888 7     7   23 sub { !! $_ }, # true?
1889 12         42 );
1890              
1891             my $new_hash = {
1892             map { ## no critic
1893 12         49 my $key = $_;
  41         82  
1894 41         49 my $value = $hash->{$key};
1895             {
1896 41         38 local $_ = $value;
  41         41  
1897 41 100       68 $subref->($key, $value)
1898             ? ( $key => $value )
1899             : ();
1900             }
1901             }
1902             keys %$hash,
1903             };
1904              
1905 12 50       106 return wantarray ? %$new_hash : $new_hash;
1906             }
1907             {
1908 18     18   28903 no warnings "once";
  18         45  
  18         2145  
1909             *grep_each = \&filter_each;
1910             }
1911              
1912             sub filter_each_defined {
1913 2     2   2885 my $hash = shift;
1914 2     6   8 return &filter_each($hash, sub { defined($_) });
  6         19  
1915             }
1916             {
1917 18     18   144 no warnings "once";
  18         52  
  18         7556  
1918             *grep_each_defined = \&filter_each_defined;
1919             }
1920              
1921              
1922              
1923             =head2 %hash->reject_each($predicate = *is_false_subref*) : @hash | @$hash
1924              
1925             C is the same as L>, except it I
1926             items that matches the $predicate.
1927              
1928             Examples:
1929              
1930             my @apples = $fruit->reject("apple");
1931             my @any_apple = $fruit->reject( qr/apple/i );
1932             my @publishers = $authors->reject(
1933             sub { $_->publisher->name =~ /Orbit/ },
1934             );
1935              
1936             The default (no $predicate) is a subref which I true
1937             values in the %hash.
1938              
1939             =cut
1940              
1941             sub reject_each {
1942 10     10   11639 my $hash = shift;
1943 10         20 my ($predicate) = @_;
1944             my $subref = autobox::Transform::_predicate(
1945             "reject_each",
1946             $predicate,
1947 4     4   13 sub { !! $_ }, # true?
1948 10         33 );
1949              
1950             my $new_hash = {
1951             map { ## no critic
1952 10         42 my $key = $_;
  35         94  
1953 35         42 my $value = $hash->{$key};
1954             {
1955 35         33 local $_ = $value;
  35         39  
1956 35 100       57 ( ! $subref->($key, $value) )
1957             ? ( $key => $value )
1958             : ();
1959             }
1960             }
1961             keys %$hash,
1962             };
1963              
1964 10 50       94 return wantarray ? %$new_hash : $new_hash;
1965             }
1966              
1967             sub reject_each_defined {
1968 1     1   3294 my $hash = shift;
1969 1     3   5 return &reject_each($hash, sub { defined($_) });
  3         8  
1970             }
1971              
1972              
1973              
1974             =head2 %hash->to_ref() : $hashref
1975              
1976             Return the reference to the %hash, regardless of context.
1977              
1978             Useful for ensuring the last hash method return a reference while in
1979             scalar context. Typically:
1980              
1981             do_stuff(
1982             genre_count => $books->group_by_count("genre")->to_ref,
1983             );
1984              
1985             =cut
1986              
1987             sub to_ref {
1988 5     5   3104 my $hash = shift;
1989 5         23 return $hash;
1990             }
1991              
1992             =head2 %hash->to_hash() : %hash
1993              
1994             Return the %hash, regardless of context. This is mostly useful if
1995             called on a HashRef at the end of a chain of method calls.
1996              
1997             =cut
1998              
1999             sub to_hash {
2000 2     2   2663 my $hash = shift;
2001 2         13 return %$hash;
2002             }
2003              
2004             =head2 %hash->to_array() : @array | @$array
2005              
2006             Return the key-value pairs of the %hash as an @array, ordered by the
2007             keys.
2008              
2009             Useful if you need to continue calling @array methods on it.
2010              
2011             =cut
2012              
2013             sub to_array {
2014 1     1   2878 my $hash = shift;
2015 1     3   5 my @new_array = map_each_to_array($hash, sub { shift() => $_ });
  3         9  
2016 1 50       18 return wantarray ? @new_array : \@new_array;
2017             }
2018              
2019              
2020              
2021             =head1 AUTOBOX AND VANILLA PERL
2022              
2023              
2024             =head2 Raison d'etre
2025              
2026             L is awesome, for a variety of reasons.
2027              
2028             =over 4
2029              
2030             =item
2031              
2032             It cuts down on dereferencing punctuation clutter, both by using
2033             methods on references and by using ->elements to deref arrayrefs.
2034              
2035             =item
2036              
2037             It makes map and grep transforms read in the same direction it's
2038             executed.
2039              
2040             =item
2041              
2042             It makes it easier to write those things in a natural order. No need
2043             to move the cursor around a lot just to fix dereferencing, order of
2044             operations etc.
2045              
2046             =back
2047              
2048             On top of this, L provides a few higher level
2049             methods for mapping, filtering and sorting common cases which are easier
2050             to read and write.
2051              
2052             Since they are at a slightly higher semantic level, once you know them
2053             they also provide a more specific meaning than just "map" or "grep".
2054              
2055             (Compare the difference between seeing a "map" and seeing a "foreach"
2056             loop. Just seeing the word "map" hints at what type of thing is going
2057             on here: transforming a list into another list).
2058              
2059             The methods of autobox::Transform are not suitable for all
2060             cases, but when used appropriately they will lead to much more clear,
2061             succinct and direct code, especially in conjunction with
2062             autobox::Core.
2063              
2064              
2065             =head2 Code Comparison
2066              
2067             These examples are only for when there's a straightforward and simple
2068             Perl equivalent.
2069              
2070             ### map_by - method call: $books are Book objects
2071             my @genres = map { $_->genre() } @$books;
2072             my @genres = $books->map_by("genre");
2073              
2074             my $genres = [ map { $_->genre() } @$books ];
2075             my $genres = $books->map_by("genre");
2076              
2077             # With sum from autobox::Core / List::AllUtils
2078             my $book_order_total = sum(
2079             map { $_->price_with_tax($tax_pct) } @{$order->books}
2080             );
2081             my $book_order_total = $order->books
2082             ->map_by([ price_with_tax => $tax_pct ])->sum;
2083              
2084             ### map_by - hash key: $books are book hashrefs
2085             my @genres = map { $_->{genre} } @$books;
2086             my @genres = $books->map_by("genre");
2087              
2088              
2089              
2090             ### filter_by - method call: $books are Book objects
2091             my $sold_out_books = [ grep { $_->is_sold_out } @$books ];
2092             my $sold_out_books = $books->filter_by("is_sold_out");
2093             my $sold_out_books = $books->grep_by("is_sold_out");
2094              
2095             my $books_in_library = [ grep { $_->is_in_library($library) } @$books ];
2096             my $books_in_library = $books->filter_by([ is_in_library => $library ]);
2097              
2098             ### filter_by - hash key: $books are book hashrefs
2099             my $sold_out_books = [ grep { $_->{is_sold_out} } @$books ];
2100             my $sold_out_books = $books->filter_by("is_sold_out");
2101              
2102              
2103              
2104             ### uniq_by - method call: $books are Book objects
2105             my %seen; my $distinct_books = [ grep { ! %seen{ $_->id // "" }++ } @$books ];
2106             my $distinct_books = $books->uniq_by("id");
2107              
2108             ### uniq_by - hash key: $books are book hashrefs
2109             my %seen; my $distinct_books = [ grep { ! %seen{ $_->{id} // "" }++ } @$books ];
2110             my $distinct_books = $books->uniq_by("id");
2111              
2112              
2113             #### flat - $author->books returns an arrayref of Books
2114             my $author_books = [ map { @{$_->books} } @$authors ]
2115             my $author_books = $authors->map_by("books")->flat
2116              
2117              
2118              
2119             =head1 DEVELOPMENT
2120              
2121             =head2 Author
2122              
2123             Johan Lindstrom, C<< >>
2124              
2125              
2126             =head2 Source code
2127              
2128             L
2129              
2130              
2131             =head2 Bug reports
2132              
2133             Please report any bugs or feature requests on GitHub:
2134              
2135             L.
2136              
2137              
2138              
2139             =head1 COPYRIGHT & LICENSE
2140              
2141             Copyright 2016- Johan Lindstrom, All Rights Reserved.
2142              
2143             This program is free software; you can redistribute it and/or modify it
2144             under the same terms as Perl itself.
2145              
2146             =cut