autobo...nsform(3pm)

✨ NAME ✨

autobox::Transform β€” Autobox methods to transform Arrays and Hashes πŸ¦‹

(yes bestie, that's autobox::Transform, section 3pm, straight out of the User Contributed Perl Documentation β€” she's an icon, she's a legend, and she's about to make your arrays and hashes do tricks πŸ’…)


🌸 CONTEXT 🌸

Okay so, quick lore drop πŸ“–:

  • autobox gives you the power to call methods on native types β€” like strings, arrays, and hashes β€” as if they were objects. Literally the glow-up of the century.
  • autobox::Core is the one that brings you the basic methods for Perl core functions like uc, map, and grep. The essentials, the daily drivers πŸŽ€.
  • And autobox::Transform β€” that's this module, hi, hello πŸ‘‹ β€” is the high-level, more specific queen. She hands you methods to transform and manipulate arrays and hashes, and she especially shines when your values are hashrefs or objects. That's her whole moment. That's her main character energy πŸ‘‘.

πŸŽ€ SYNOPSIS πŸŽ€

use autobox::Core;  # map, uniq, sort, join, sum, etc.
use autobox::Transform;

πŸ“ Arrays

# use autobox::Core for ->map etc.

# filter (like a more versatile grep)
$book_locations->filter(); # true values
$books->filter(sub { $_->is_in_library($library) });
$book_names->filter( qr/lord/i );
$book_genres->filter("scifi");
$book_genres->filter({ fantasy => 1, scifi => 1 }); # hash key exists

# reject: the inverse of filter
$book_genres->reject("fantasy");

# order (like a more succinct sort)
$book_genres->order;
$book_genres->order("desc");
$book_prices->order([ "num", "desc" ]);
$books->order([ sub { $_->{price} }, "desc", "num" ]);
$log_lines->order([ num => qr/pid: "(\d+)"/ ]);
$books->order(
    [ sub { $_->{price} }, "desc", "num" ] # first price
    sub { $_->{name} },                    # then name
);

# group (aggregate) array into hash
$book_genres->group;       # "Sci-fi" => "Sci-fi"
$book_genres->group_count; # "Sci-fi" => 3
$book_genres->group_array; # "Sci-fi" => [ "Sci-fi", "Sci-fi", "Sci-fi"]

# Flatten arrayrefs-of-arrayrefs
  $authors->map_by("books") # ->books returns an arrayref
  # [ [ $book1, $book2 ], [ $book3 ] ]
  $authors->map_by("books")->flat;
  # [ $book1, $book2, $book3 ]

# Return reference, even in list context, e.g. in a parameter list
$book_locations->filter()->to_ref;

# Return array, even in scalar context
@books->to_array;

# Turn paired items into a hash
@titles_books->to_hash;

πŸ¦‹ Arrays where the items are hashrefs/objects

# $books and $authors below are arrayrefs with either objects or
# hashrefs (the call syntax is the same). These have methods/hash
# keys like C<$book->genre()>, C<$book->{is_in_stock}>,
# C<$book->is_in_library($library)>, etc.

$books->map_by("genre");
$books->map_by([ price_with_tax => $tax_pct ]);

$books->filter_by("is_in_stock");
$books->filter_by([ is_in_library => $library ]);
$books->filter_by([ price_with_tax => $rate ], sub { $_ > 56.00 });
$books->filter_by("price", sub { $_ > 56.00 });
$books->filter_by("author", "James A. Corey");
$books->filter_by("author", qr/corey/i);

# grep_by is an alias for filter_by
$books->grep_by("is_in_stock");

# reject_by: the inverse of filter_by
$books->reject_by("is_sold_out");

$books->uniq_by("id");

$books->order_by("name");
$books->order_by(name => "desc");
$books->order_by(price => "num");
$books->order_by(price => [ "num", "desc" ]);
$books->order_by(name => [ sub { uc($_) }, "desc" ]);
$books->order_by([ price_with_tax => $rate ] => "num");
$books->order_by(
    author => "str",             # first by author
    price  => [ "num", "desc" ], # then by price, most expensive first
);
$books->order_by(
    author                      => [ "desc", sub { uc($_) } ],
    [ price_with_tax => $rate ] => [ "num", "desc" ],
    "name",
);

$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 ],
# },

πŸ’– Hashes

# map over each pair
# e.g. Upper-case the genre name, and make the count say "n books"
#     (return a key => value pair)
$genre_count->map_each(sub { uc( $_[0] ) => "$_ books" });
# {
#     "FANTASY" => "1 books",
#     "SCI-FI"  => "3 books",
# },

# map over each value
# e.g. Make the count say "n books"
#     (return the new value)
$genre_count->map_each_value(sub { "$_ books" });
# {
#     "Fantasy" => "1 books",
#     "Sci-fi"  => "3 books",
# },

# map each pair into an array
# e.g. Transform each pair to the string "n: genre"
#     (return list of items)
$genre_count->map_each_to_array(sub { "$_: $_[0]" });
# [ "1: Fantasy", "3: Sci-fi" ]

# filter each pair
# Genres with more than five books
$genre_count->filter_each(sub { $_ > 5 });

# filter out each pair
# Genres with more than five books
$genre_count->reject_each(sub { $_ <= 5 });

# Return reference, even in list context, e.g. in a parameter list
%genre_count->to_ref;

# Return hash, even in scalar context
$author->book_count->to_hash;

# Turn key-value pairs into an array
%isbn__book->to_array;

🌈 Combined examples

my $order_authors = $order->books
    ->filter_by("title", qr/^The/)
    ->uniq_by("isbn")
    ->map_by("author")
    ->uniq_by("name")
    ->order_by(publisher => "str", name => "str")
    ->map_by("name")->uniq->join(", ");

my $total_order_amount = $order->books
    ->reject_by("is_sold_out")
    ->filter_by([ covered_by_vouchers => $vouchers ], sub { ! $_ })
    ->map_by([ price_with_tax => $tax_pct ])
    ->sum;

look at her go 😭 chaining like that, absolutely unbothered, absolutely ate 🫢

✨ DESCRIPTION ✨

"autobox::Transform" is literally your bestie for calling high level autobox methods on arrays, arrayrefs, hashes and hashrefs. It's giving ✨main character energy✨ for your data structures.


πŸ’– Transforming lists of objects vs list of hashrefs

map_by, filter_by, order_by etc. (all the methods named *_by) work with sets of hashrefs or objects.

You call them the same way whether the array holds objects or hashrefs, bestie. The only rule: the items in the list have to be either all objects or all hashrefs. No mixing, periodt. πŸ’…

  • If the array contains hashrefs, the hash key gets looked up on each item.
  • If the array contains objects, a method gets called on each object (possibly with the arguments you provided).

πŸŽ€ Calling accessor methods with arguments

For method calls, you can totally provide arguments to the method. The way that works is:

Consider map_by:

$array->map_by($accessor)

If the $accessor is a string, it's a simple method call, no thoughts needed:

# method call without args
$books->map_by("price")
# becomes $_->price() or $_->{price}

If the $accessor is an arrayref, the first item is the method name, and the rest of the items are the arguments to the method. Slay.

# method call with args
$books->map_by([ price_with_discount => 5.0 ])
# becomes $_->price_with_discount(5.0)

πŸ¦‹ Filter predicates

There are several methods that filter items, e.g. @array->filter (duh), @array->filter_by, and %hash->filter_each. These methods take a $predicate argument to determine which items to keep and which to yeet out. πŸ—‘οΈ

The reject family of methods do the opposite β€” they filter out the items that match the predicate, i.e. the opposite of the filter methods.

If $predicate is an unblessed scalar, it gets compared to each value with string eq:

$books->filter_by("author", "James A. Corey");

If $predicate is a regex, it gets compared to each value with =~:

$books->reject_by("author", qr/Corey/);

If $predicate is a hashref, values in @array are retained if the $predicate hash key exists (the hash values are literally irrelevant, they don't matter at all, bestie):

$books->filter_by(
    "author", {
        "James A. Corey"   => undef,
        "Cixin Liu"        => 0,
        "Patrick Rothfuss" => 1,
    },
);

If $predicate is a subref, the subref gets called for each value to check whether this item should remain in the list.

The $predicate subref should return a true value to remain. $_ is set to the current $value. 🫢

$authors->filter_by(publisher => sub { $_->name =~ /Orbit/ });

🌈 Sorting using order and order_by

Okay so first let's compare how sorting is done with Perl's sort versus autobox::Transform's order/order_by. Sit down, this one's iconic.

⭐ Sorting with sort

  • provide a sub that returns the comparison outcome of two values: $a and $b
  • in case of a tie, provide another comparison of $a and $b
# If the name is the same, compare age (oldest first)
sort {
    uc( $a->{name} ) cmp uc( $b->{name} )           # first comparison
    ||
    int( $b->{age} / 10 ) <=> int( $a->{age} / 10 ) # second comparison
} @users

(note the opposite order of $a and $b for the age comparison β€” something that's often difficult to discern at a glance, and honestly? rude. 😭)

🌸 Sorting with order, order_by

  • Provide order options for how one value should be compared with the others:
    • how to compare (cmp or <=>)
    • which direction to sort (ascending or descending)
    • which value to compare, using a regex or subref, e.g. by uc($_)
  • In case of a tie, provide another comparison.
# If the name is the same, compare age (oldest first)

# ->order
@users->order(
    sub { uc( $_->{name} ) },                         # first comparison
    [ "num", sub { int( $_->{age} / 10 ) }, "desc" ], # second comparison
)

# ->order_by
@users->order_by(
    name => sub { uc },                                # first comparison
    age  => [ num => desc => sub { int( $_ / 10 ) } ], # second comparison
)

🧚 Comparison Options

If there's only one option for a comparison (e.g. num), provide a single option (string/regex/subref) value. If there are many options, provide them in an arrayref in any order. Do whatever, bestie, it's flexible. ✨

πŸ’œ Comparison operator

  • str (cmp) β€” default
  • num (<=>)

πŸ’œ Sort order

  • asc (ascending) β€” default
  • desc (descending)

πŸ’œ The value to compare

  • A subref β€” default is: sub { $_ }
    • The return value is used in the comparison
  • A regex, e.g. qr/id: (\d+)/
    • The value of join("", @captured_groups) are used in the comparison (@captured_groups are $1, $2, $3 etc.)

πŸŽ€ Examples of a single comparison

# order: the first arg is the comparison options (one or an
# arrayref with many options)
->order()  # Defaults to str, asc, $_, just like sort
->order("num")
->order(sub { uc($_) })
# compare captured matches, e.g. "John" and "Doe" as "JohnDoe"
->order( qr/first_name: (\w+), last_name: (\w+)/ )
->order([ num => qr/id: (\d+)/ ])
->order([ sub { int($_) }, "num" ])

# order_by: the first arg is the accessor, just like with
# map_by. Second arg is the comparison options (one or an arrayref
# with many options)
->order_by("id")
->order_by("id", "num")
->order_by("id", [ "num", "desc" ])
->order_by("name", sub { uc($_) })
->order_by(log_line => qr/first_name: (\w+), last_name: (\w+)/ )
->order_by("log_line", [ num => qr/id: (\d+)/ ])
->order_by(age => [ sub { int($_) }, "num" ])

# compare int( $a->age_by_interval(10) )
->order_by([ age_by_interval => 10 ] => [ sub { int($_) }, "num" ])
# compare uc( $a->name_with_title($title) )
->order_by([ name_with_title => $title ], sub { uc($_) })

πŸ”₯ Examples of fallback comparisons

When the first comparison is a tie, the subsequent ones are used. Chain them, queen. πŸ‘‘

# order: list of comparison options (one or an arrayref with many
# options, per comparison)
->order(
    [ sub { $_->{price} }, "num" ], # First a numeric comparison of price
    [ sub { $_->{name} }, "desc" ], # or if same, a reverse comparison of the name
)
->order(
    [ sub { uc($_) }, "desc" ],
    "str",
)
->order(
    qr/type: (\w+)/,
    [ num => desc => qr/duration: (\d+)/ ]
    [ num => sub { /id: (\d+)/ } ],
    "str",
)

# order_by: pairs of accessor-comparison options
->order_by(
    price => "num", # First a numeric comparison of price
    name => "desc", # or if same, a reverse comparison of the name
)
->order_by(
    price => [ "num", "desc" ],
    name  => "str",
)
# accessor is a method call with arg: $_->price_with_discount($discount)
->order_by(
    [ price_with_discount => $discount ] => [ "num", "desc" ],
    name                                 => [ str => sub { uc($_) } ],
    "id",
)

🫢 List and Scalar Context

Almost all of the methods are context sensitive β€” i.e. they return a list in list context and an arrayref in scalar context, just like autobox::Core. It's giving consistency. πŸ’…

⚠️ Beware: you might be in list context when you actually need an arrayref. It happens to the best of us, no cap.

When in doubt, assume they work like map and grep (i.e. return a list), and convert the return value to references wherever you might have a non-obvious list context. E.g.

Incorrect πŸ™ˆ

$self->my_method(
    # Wrong, this is list context and wouldn't return an array ref
    books => $books->filter_by("is_published"),
);

Correct ✨

$self->my_method(
    # Correct, put the returned list in an anonymous array ref
    books => [ $books->filter_by("is_published") ],
);
$self->my_method(
    # Correct, ensure scalar context to get an array ref
    books => scalar $books->filter_by("is_published"),
);

# Probably the nicest, since ->to_ref goes at the end
$self->my_method(
    # Correct, use ->to_ref to ensure an array ref is returned
    books => $books->filter_by("is_published")->to_ref,
);

✨ METHODS ON ARRAYS ✨

Okay bestie, this is the array section and it's giving main character energy. Every method below can hang off an @array or an @$array (arrayref) depending on the signature, so read those lil signatures like the group chat rules. πŸ’…

@array->filter($predicate = *is_true_subref*) : @array | @$array

Lowkey exactly like Perl's grep, except it returns an @array with the values where $predicate gives you a true value. Slay.

$predicate can be a subref, string, undef, regex, or hashref. Peep "Filter predicates" for the whole vibe.

If you don't pass $predicate, the default is a subref that keeps the true values in the @array. No cap.

Examples:

my @apples     = $fruit->filter("apple");
my @any_apple  = $fruit->filter( qr/apple/i );
my @publishers = $authors->filter(
    sub { $_->publisher->name =~ /Orbit/ },
);

filter and grep

autobox::Core's grep method takes a subref, just like this method. filter also supports the other predicate types, like string, regex, etc. It's giving versatile.

@array->reject($predicate = *is_false_subref*) : @array | @$array

Similar to the Unix command grep -v, return an @array with values for which $predicate yields a false value.

$predicate can be a subref, string, undef, regex, or hashref. See "Filter predicates".

The default (no $predicate) is a subref which filters out true values in the @array.

Examples:

my @apples     = $fruit->reject("apple");
my @no_apples  = $fruit->reject( qr/apple/i );
my @publishers = $authors->reject(
    sub { $_->publisher->name =~ /Orbit/ },
);

@array->order(@comparisons = ("str")) : @array | @$array

Return @array ordered according to the @comparisons. The default comparison is the same as the default sort, e.g. a normal string comparison of the @array values.

If the first item in @comparison ends in a tie, the next one is used, etc.

Each comparison consists of a single option or an arrayref of options, e.g. "str"/"num", "asc"/"desc", or a subref/regex. See "Sorting using order and order_by" for details about how these work.

Examples:

@book_genres->order;
@book_genres->order("desc");
@book_prices->order([ "num", "desc" ]);
@books->order([ sub { $_->{price} }, "desc", "num" ]);
@log_lines->order([ num => qr/pid: "(\d+)"/ ]);
@books->order(
    [ sub { $_->{price} }, "desc", "num" ] # first price
    sub { $_->{name} },                    # then name
);

@array->group($value_subref = item) : %key_value | %$key_value

Group the @array items into a hashref with the items as keys.

The default $value_subref puts each item in the list as the hash value. If the key is repeated, the value is overwritten with the last object. Oop.

Example:

my $title_book = $book_titles->group;
# {
#     "Leviathan Wakes"       => "Leviathan Wakes",
#     "Caliban's War"         => "Caliban's War",
#     "The Tree-Body Problem" => "The Tree-Body Problem",
#     "The Name of the Wind"  => "The Name of the Wind",
# },

The $value_subref

For simple cases of just grouping a single key to a single value, the $value_subref is straightforward to use.

The hash key is the array item. The hash value is whatever is returned from

my $new_value = $value_sub->($current_value, $object, $key);
  • $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 array item.

See also: ->group_by.

@array->group_count : %key_count | %$key_count

Just like group, but the hash values are the number of instances each item occurs in the list.

Example:

$book_genres->group_count;
# {
#     "Sci-fi"  => 3,
#     "Fantasy" => 1,
# },

There are three books counted for the "Sci-fi" key. Periodt.

@array->group_array : %key_objects | %$key_objects

Just like group, but the hash values are arrayrefs containing those same array items.

Example:

$book_genres->group_array;
# {
#     "Sci-fi"  => [ "Sci-fi", "Sci-fi", "Sci-fi" ],
#     "Fantasy" => [ "Fantasy" ],
# },

The three Sci-fi genres are collected under the Sci-fi key. Obsessed.

@array->flat() : @array | @$array

Return a (one level) flattened array, assuming the array items themselves are array refs. I.e.

[
    [ 1, 2, 3 ],
    [ "a", "b" ],
    [ [ 1, 2 ], { 3 => 4 } ]
]->flat

returns

[ 1, 2, 3, "a", "b ", [ 1, 2 ], { 3 => 4 } ]

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 returns a list rather than an array and therefore can't be used in this way.

@array->to_ref() : $arrayref

Return the reference to the @array, regardless of context.

Useful for ensuring the last array method return a reference while in scalar context. Typically:

do_stuff(
    books => $author->map_by("books")->to_ref,
);

map_by is called in list context, so without ->to_ref it would have returned an array, not an arrayref. The way that context switch can sneak up on you? Iconic.

@array->to_array() : @array

Return the @array, regardless of context. This is mostly useful if called on a ArrayRef at the end of a chain of method calls.

@array->to_hash() : %hash | %$hash

Return the item pairs in the @array as the key-value pairs of a %hash (context sensitive).

Useful if you need to continue calling %hash methods on it.

Die if there aren't an even number of items in @array.

πŸ¦‹ METHODS ON ARRAYS CONTAINING OBJECTS/HASHES πŸ¦‹

okay bestie this is the good stuff β€” when your array is full of objects (or hashrefs) you don't have to hand-roll a map/grep every single time. every one of these methods also works on @$array, so arrayrefs are welcome too πŸ’–


✨ @array->map_by($accessor) : @array | @$array

$accessor is either a string, or an arrayref where the first item is a string.

Call the $accessor on each object in @array, or get the hash key value on each hashref in @array. Like:

map { $_->$accessor() } @array
# or
map { $_->{$accessor} } @array

Examples:

my @author_names = $authors->map_by("name");
my $author_names = @publishers->map_by("authors")->flat->map_by("name");

Or get the hash key value. Example:

my @review_scores = $reviews->map_by("score");

Alternatively for when @array contains objects, the $accessor can be an arrayref. The first item is the method name, and the rest of the items are passed as args in the method call. This obviously won't work when the @array contains hashrefs.

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 ]);

🌸 @array->filter_by($accessor, $predicate = *is_true_subref*) : @array | @$array

$accessor is either a string, or an arrayref where the first item is a string.

Call the $accessor on each object in the list, or get the hash key value on each hashref in the list.

Example:

my @prolific_authors = $authors->filter_by("is_prolific");

Alternatively the $accessor is an arrayref. The first item is the accessor name, and the rest of the items are passed as args the method call. This only works when working with objects, not with hashrefs.

Example:

my @books_to_charge_for = $books->filter_by([ price_with_tax => $tax_pct ]);

Use the $predicate to determine whether the value should remain. $predicate can be a subref, string, undef, regex, or hashref. See "Filter predicates".

The default (no $predicate) is a subref which retains true values in the result @array.

Examples:

# Custom predicate subref
my @authors = $authors->filter_by(
    "publisher",
    sub { $_->name =~ /Orbit/ },
);

# Call method with args and match a regex
my @authors = $authors->filter_by(
    [ publisher_affiliation => "with" ],
    qr/Orbit/ },
);
ngl if you start doing something complicated with a $predicate subref, it might just be easier and more readable to simply use $array->filter(). no shame in it πŸ’…

Alias

grep_by is an alias for filter_by. Unlike grep vs filter, this one works exaclty the same way.


πŸŽ€ @array->reject_by($accessor, $predicate = *is_false_subref*) : @array | @$array

reject_by is the same as filter_by, except it filters out items that matches the $predicate.

Example:

my @unproductive_authors = $authors->reject_by("is_prolific");

The default (no $predicate) is a subref which filters out true values in the result @array.


πŸ¦‹ @array->uniq_by($accessor) : @array | @$array

$accessor is either a string, or an arrayref where the first item is a string.

Call the $accessor on each object in the list, or get the hash key value on each hashref in the list. Return list of items which have a unique set of return values. The order is preserved. On duplicates, keep the first occurrence.

Examples:

# You have gathered multiple Author objects with duplicate ids
my @authors = $authors->uniq_by("author_id");

Alternatively the $accessor is an arrayref. The first item is the accessor name, and the rest of the items are passed as args the method call. This only works when working with objects, not with hashrefs.

Examples:

my @example_book_at_price_point = $books->uniq_by(
    [ price_with_tax => $tax_pct ],
);

⭐ @array->order_by(@accessor_comparison_pairs) : @array | @$array

Return @array ordered according to the @accessor_comparison_pairs.

The comparison value comes from an initial @array->map_by($accessor) for each accessor-comparison pair. It is important that the $accessor call returns exactly a single scalar that can be compared with the other values.

It then works just like with ->order.

$books->order_by("name"); # default order, i.e. "str"
$books->order_by(price => "num");
$books->order_by(price => [ "num", "desc" ]);

As with map_by, if the $accessor is used on an object, the method call can include arguments.

$books->order_by([ price_wih_tax => $tax_rate ] => "num");

Just like with order, the value returned by the accessor can be transformed using a sub, or be matched against a regex.

$books->order_by(price => [ num => sub { int($_) } ]);

# Ignore leading "The" in book titles by optionally matching it
# with a non-capturing group and the rest with a capturing group
# paren
$books->order_by( title => qr/^ (?: The \s+ )? (.+) /x );

If a comparison is missing for the last pair, the default is a normal "str" comparison.

$books->order_by("name"); # default "str"

If the first comparison ends in a tie, the next pair is used, etc. Note that in order to provide accessor-comparison pairs, it's often necessary to provide a default "str" comparison just to make it a pair.

$books->order_by(
    author => "str",
    price  => [ "num", "desc" ],
);

πŸ’œ @array->group_by($accessor, $value_subref = object) : %key_value | %$key_value

$accessor is either a string, or an arrayref where the first item is a string.

Call ->$accessor on each object in the array, or get the hash key for each hashref in the array (just like ->map_by) and group the values as keys in a hashref.

The default $value_subref puts each object in the list as the hash value. If the key is repeated, the value is overwritten with the last object.

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_subref

For simple cases of just grouping a single key to a single value, the $value_subref is straightforward to use.

The hash key is whatever is returned from $object->$accessor.

The hash value is whatever is returned from

my $new_value = $value_sub->($current_value, $object, $key);
  • $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->$accessor(@$args)

A simple example would be to group by the accessor, but instead of the object used as the value you want to look up an attribute on each object:

my $book_id__author = $books->group_by("id", sub { $_->author });
# keys: book id; values: author

If you want to create an aggregate value the $value_subref can be a bit tricky to use, so the most common thing would probably be to use one of the more specific group_by-methods (see below). It should be capable enough to achieve what you need though. 🫢


πŸ“ @array->group_by_count($accessor) : %key_count | %$key_count

$accessor is either a string, or an arrayref where the first item is a string.

Just like group_by, but the hash values are the number of instances each $accessor 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.


🌈 @array->group_by_array($accessor) : %key_objects | %$key_objects

$accessor is either a string, or an arrayref where the first item is a string.

Just like group_by, but the hash values are arrayrefs containing the objects which has each $accessor 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. πŸ’–

✨ METHODS ON HASHES ✨

  • %hash->map_each($key_value_subref) : %new_hash | %$new_hash β€” maps every single key-value pair in the hash through $key_value_subref. Think of it like how map turns one list into another list, except this turns a hash into another hash. iconic behavior fr.

"$key_value_subref->($key, $value)" gets called for each pair (and $_ is set to the value, bestie πŸ’…).

The subref should hand back an even-numbered list with zero or more key-value pairs, and those become your %new_hash. Usually you return two things: the key and the value.

Example:

{ a => 1, b => 2 }->map_each(sub { "$_[0]$_[0]" => $_ * 2 });
# Returns { aa => 2, bb => 4 }
  • %hash->map_each_value($value_subref) : %new_hash | %$new_hash β€” maps every value in the hash through $value_subref but keeps the keys exactly the same. keys stay, values get a glow-up ✨

"$value_subref->($key, $value)" gets called for each pair (with $_ set to the value).

The subref should return one single value per key, and that makes up the %new_hash (same keys, new mapped values).

Example:

{ a => 1, b => 2 }->map_each_value(sub { $_ * 2 });
# Returns { a => 2, b => 4 }
  • %hash->map_each_to_array($item_subref) : @new_array | @$new_array β€” maps every key-value pair in the hash into a list using $item_subref. hash in, list out, no cap πŸ¦‹

"$item_subref->($key, $value)" gets called for each pair (with $_ set to the value), and it happens in key order.

The subref should return zero or more list items which become the @new_array. Typically you return one item.

Example:

{ a => 1, b => 2 }->map_each_to_array(sub { "$_[0]-$_" });
# Returns [ "a-1", "b-2" ]
  • %hash->filter_each($predicate = *is_true_subref*) : @hash | @$hash β€” returns a %hash with the values for which $predicate yields a true value. basically a vibe check for your hash πŸ’–

$predicate can be a subref, string, undef, regex, or hashref. Go look at "Filter predicates" for the tea.

The default (when you pass no $predicate) is a subref which retains true values in the %hash.

If $predicate is a subref, "$predicate->($key, $value)" gets called for each pair (with $_ set to the value).

The subref should return a true value to keep the key-value pair in the result %hash.

Examples:

{ a => 1, b => 2 }->filter_each(sub { $_ == 2 });
# Returns { b => 2 }

$book_author->filter_each(sub { $_->name =~ /Corey/ });
  • %hash->reject_each($predicate = *is_false_subref*) : @hash | @$hash β€” reject_each is literally the same as filter_each, except it filters out the items that match the $predicate. it's the "not you, bestie" method πŸ™ˆ

Examples:

{ a => 1, b => 2 }->reject_each(sub { $_ == 2 });
# Returns { a => 1 }

The default (no $predicate) is a subref which filters out true values in the %hash.

  • %hash->to_ref() : $hashref β€” returns the reference to the %hash, no matter what context you're in.

Super useful for making sure the last hash method returns a reference while you're in scalar context. Typically like this:

do_stuff(
    genre_count => $books->group_by_count("genre")->to_ref,
);
  • %hash->to_hash() : %hash β€” returns the %hash, regardless of context. mostly the move when you're calling it on a HashRef at the end of a chain of method calls. 🫢
  • %hash->to_array() : @array | @$array β€” returns the key-value pairs of the %hash as an @array, ordered by the keys.

Handy if you need to keep calling @array methods on it. slay πŸ”₯


🌈 AUTOBOX AND VANILLA PERL 🌈

Raison d'etre

autobox::Core is awesome, for a whole variety of reasons. periodt.

  • It cuts down on dereferencing punctuation clutter, both by using methods on references and by using ->elements to deref arrayrefs.
  • 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.

On top of all that, autobox::Transform provides a few higher level methods for mapping, filtering 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". πŸ‘‘

Code Comparison

These examples are only for when there's a straightforward and simple Perl equivalent.

### map_by - method call: $books are Book objects
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;

### map_by - hash key: $books are book hashrefs
my @genres = map { $_->{genre} } @$books;
my @genres = $books->map_by("genre");

### filter_by - method call: $books are Book objects
my $sold_out_books = [ grep { $_->is_in_stock } @$books ];
my $sold_out_books = $books->filter_by("is_in_stock");
my $sold_out_books = $books->grep_by("is_in_stock");

my $books_in_library = [ grep { $_->is_in_library($library) } @$books ];
my $books_in_library = $books->filter_by([ is_in_library => $library ]);

### reject_by - hash key: $books are book hashrefs
my $sold_out_books = [ grep { ! $_->{is_in_stock} } @$books ];
my $sold_out_books = $books->reject_by("is_in_stock");

### uniq_by - method call: $books are Book objects
my %seen; my $distinct_books = [ grep { ! %seen{ $_->id // "" }++ } @$books ];
my $distinct_books = $books->uniq_by("id");

### uniq_by - hash key: $books are book hashrefs
my %seen; my $distinct_books = [ grep { ! %seen{ $_->{id} // "" }++ } @$books ];
my $distinct_books = $books->uniq_by("id");

#### flat - $author->books returns an arrayref of Books
my $author_books = [ map { @{$_->books} } @$authors ];
my $author_books = $authors->map_by("books")->flat;

πŸ’œ DEVELOPMENT πŸ’œ

Author

Johan Lindstrom, "<johanl [AT] cpan.org>" β€” shoutout to the author, main character energy fr πŸŽ€

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 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.


and that's the whole page bestie, you ate it up πŸ’…βœ¨ this was perl v5.30.3, dated 2020-07-30, and it's the autobo...nsform(3pm) manpage β€” go forth and transform, ily πŸ«ΆπŸ¦‹

πŸ“¦ where this page comes from