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