NAME
autobox::Transform - Autobox methods to transform Arrays and Hashes
SYNOPSIS
# These are equivalent ways to transform arrays and arrayrefs
### map_by
my @genres = map { $_->genre() } @$books;
my @genres = $books->map_by("genre");
my $genres = [ map { $_->genre() } @$books ];
my $genres = $books->map_by("genre");
# With sum from autobox::Core / List::AllUtils
my $book_order_total = sum(
map { $_->price_with_tax($tax_pct) } @{$order->books}
);
my $book_order_total = $order->books
->map_by(price_with_tax => [$tax_pct])->sum;
### grep_by
my $sold_out_books = [ grep { $_->is_sold_out } @$books ];
my $sold_out_books = $books->grep_by("is_sold_out");
my $books_in_library = [ grep { $_->is_in_library($library) } @$books ];
my $books_in_library = $books->grep_by(is_in_library => [$library]);
### group_by
$books->group_by("title"),
# {
# "Leviathan Wakes" => $books->[0],
# "Caliban's War" => $books->[1],
# "The Tree-Body Problem" => $books->[2],
# "The Name of the Wind" => $books->[3],
# },
$authors->group_by(publisher_affiliation => ["with"]),
# {
# 'James A. Corey with Orbit' => $authors->[0],
# 'Cixin Liu with Head of Zeus' => $authors->[1],
# 'Patrick Rothfuss with Gollanz' => $authors->[2],
# },
$books->group_by_count("genre"),
# {
# "Sci-fi" => 3,
# "Fantasy" => 1,
# },
my $genre_books = $books->group_by_array("genre");
# {
# "Sci-fi" => [ $sf_book_1, $sf_book_2, $sf_book_3 ],
# "Fantasy" => [ $fantasy_book_1 ],
# },
#### flat
my $prolific_author_books = [ map { @{$_->books} } @$authors ]
my $prolific_author_books = $authors->map_by("books")->flat
DESCRIPTION
Note: This module supercedes autobox::Array::Transform which was unfortunately named.
High level autobox methods you can call on arrays, arrayrefs, hashes and hashrefs e.g. map_by(), grep_by(), group_by()
Raison d'etre
autobox::Core is awesome, for a variety of reasons.
- It cuts down on dereferencing punctuation clutter.
- It makes map and grep transforms read in the same direction it's executed.
- It makes it easier to write those things in a natural order. No need to move the cursor around a lot just to fix dereferencing, order of operations etc.
autobox::Transform provides a few higher level methods for mapping, greping and sorting common cases which are easier to read and write.
Since they are at a slightly higher semantic level, once you know them they also provide a more specific meaning than just "map" or "grep".
(Compare the difference between seeing a "map" and seeing a "foreach" loop. Just seeing the word "map" hints at what type of thing is going on here: transforming a list into another list).
The methods of autobox::Transform are not suitable for all cases, but when used appropriately they will lead to much more clear, succinct and direct code, especially in conjunction with autobox::Core.
Examples
my $total_order_amount = $order->books
->map_by(price_with_tax => [ $tax_pct ])
->sum;
my $order_authors = $order->books
->map_by("author")
->map_by("name")->uniq->sort->join(", ");
List and Scalar Context
All of the methods below are context sensitive, i.e. they return a list in list context and an arrayref in scalar context, just like autobox::Core.
When in doubt, assume they work like map and grep, and convert the
return value to references where you might have an unobvious list
context. E.g.
$self->my_method(
# Wrong, this is list context and wouldn't return an arrayref
books => $books->grep_by("is_published"),
),
$self->my_method(
# Correct, convert the list to an arrayref
books => [ $books->grep_by("is_published") ],
),
$self->my_method(
# Correct, ensure scalar context i.e. an array ref
books => scalar $books->grep_by("is_published"),
),
AUTOBOX ARRAY METHODS
map_by($method, @$args?) : @array | @$array
Call the $method on each item in the list. Like:
map { $_->$method() }
Examples:
my @ahthor_names = $authors->map_by("name");
my $author_names = @publishers->map_by("authors")->map_by("name");
Optionally pass in @$args in the method call. Like:
map { $_->$method(@$args) }
Examples:
my @prices_including_tax = $books->map_by("price_with_tax", [ $tax_pct ]);
my $prices_including_tax = $books->map_by(price_with_tax => [ $tax_pct ]);
grep_by($method, @$args?) : @array | @$array
Call the $method on each item in the list. Like:
grep { $_->$method() }
Examples:
my @prolific_authors = $authors->grep_by("is_prolific");
Optionally pass in @$args in the method call. Like:
grep { $_->$method(@$args) }
Examples:
my @books_to_charge_for = $books->grep_by("price_with_tax", [ $tax_pct ]);
group_by($method, @$args = [], $value_sub = object) : %key_value | %$key_value
Call ->$method(@$args) on each object in the array (just like ->map_by) and group the return values as keys in a hashref.
The default $value_sub puts the objects in the list as the hash values.
Example:
my $title_book = $books->group_by("title");
# {
# "Leviathan Wakes" => $books->[0],
# "Caliban's War" => $books->[1],
# "The Tree-Body Problem" => $books->[2],
# "The Name of the Wind" => $books->[3],
# },
The $value_sub
This is a bit tricky to use, so the most common thing would probably be to use one of the more specific group_by-methods which do common things (see below). It should be capable enough to achieve what you need though, so here's how it works:
The hash key is whatever is returned from $object->$method(@$args).
The hash value is whatever is returned from
my $new_value = $value_sub->($current_value, $object, $key);
where:
- $current value is the current hash value for this key (or undef if the first one).
- $object is the current item in the list. The current $_ is also set to this.
- $key is the key returned by $object->$method(@$args)
group_by_count($method, @$args = []) : %key_count | %$key_count
Just like group_by, but the hash values are the the number of instances each $method value occurs in the list.
Example:
$books->group_by_count("genre"),
# {
# "Sci-fi" => 3,
# "Fantasy" => 1,
# },
$book->genre() returns the genre string. There are three books counted for the "Sci-fi" key.
group_by_array($method, @$args = []) : %key_objects | %$key_objects
Just like group_by, but the hash values are arrayrefs containing the objects which has each $method value.
Example:
my $genre_books = $books->group_by_array("genre");
# {
# "Sci-fi" => [ $sf_book_1, $sf_book_2, $sf_book_3 ],
# "Fantasy" => [ $fantasy_book_1 ],
# },
$book->genre() returns the genre string. The three Sci-fi book objects are collected under the Sci-fi key.
flat() : @array | @$array
Return a flattened array, assuming the array items themselves are array refs. I.e.
[
[ 1, 2, 3 ],
[ "a", "b" ],
]->flat
returns
[ 1, 2, 3, "a", "b "]
This is useful if e.g. a map_by("some_method") returns arrayrefs of objects which you want to do further method calls on. Example:
# ->books returns an arrayref of Book objects with a ->title
$authors->map_by("books")->flat->map_by("title")
Note: This is different from autobox::Core's ->flatten, which reurns a list rather than an array and therefore can't be used in this way.
DEVELOPMENT
Author
Johan Lindstrom, <johanl [AT] cpan.org>
Source code
https://github.com/jplindstrom/p5-autobox-Transform
Bug reports
Please report any bugs or feature requests on GitHub:
https://github.com/jplindstrom/p5-autobox-Transform/issues.
COPYRIGHT & LICENSE
Copyright 2016- Johan Lindstrom, All Rights Reserved.
This program is free software; you can redistribute it and/or modify it under the same terms as Perl itself.