Perl x Open Food Facts Hackathon: Paris, France - May 24-25 Learn more

use 5.006;
use strict;
our $AUTHORITY = 'cpan:TOBYINK';
our $VERSION = '0.000_004';
requires qw( target_class to_list );
my $_coerce = sub {
my ( $thing ) = @_;
require Scalar::Util;
if ( Scalar::Util::blessed( $thing ) and $thing->DOES( __PACKAGE__ ) ) {
return $thing;
}
if ( ref( $thing ) eq 'ARRAY' ) {
require LINQ::Array;
return LINQ::Array::->new( $thing );
}
LINQ::Util::Internal::throw(
"CallerError",
message => "Expected a LINQ collection; got '$thing'"
);
};
sub select {
my $self = shift;
my $map = LINQ::Util::Internal::assert_code( @_ );
LINQ::Util::Internal::create_linq(
$self => [ map scalar( $map->( $_ ) ), $self->to_list ],
);
}
sub where {
my $self = shift;
my $filter = LINQ::Util::Internal::assert_code( @_ );
LINQ::Util::Internal::create_linq(
$self => [ grep $filter->( $_ ), $self->to_list ],
);
}
sub select_many {
my $self = shift;
my $map = LINQ::Util::Internal::assert_code( @_ );
LINQ::Util::Internal::create_linq(
$self => [ map $map->( $_ )->$_coerce->to_list, $self->to_list ],
);
}
sub min {
my $self = shift;
return $self->select( @_ )->min if @_;
require List::Util;
&List::Util::min( $self->to_list );
}
sub max {
my $self = shift;
return $self->select( @_ )->max if @_;
require List::Util;
&List::Util::max( $self->to_list );
}
sub sum {
my $self = shift;
return $self->select( @_ )->sum if @_;
require List::Util;
&List::Util::sum( $self->to_list );
}
sub average {
my $self = shift;
$self->sum( @_ ) / $self->count();
}
sub aggregate {
my $self = shift;
my $code = LINQ::Util::Internal::assert_code( shift );
my $wrapper = sub { $code->( $a, $b ) };
require List::Util;
&List::Util::reduce( $wrapper, @_, $self->to_list );
}
my $_prepare_join = sub {
my $x = shift;
my $y = shift;
my $hint = ref( $_[0] ) ? -inner : shift( @_ );
my $x_keys = LINQ::Util::Internal::assert_code( shift );
my $y_keys = LINQ::Util::Internal::assert_code( shift );
my $joiner = LINQ::Util::Internal::assert_code( @_ );
$hint =~ /\A-(inner|left|right|outer)\z/ or LINQ::Util::Internal::throw(
"CallerError",
message => "Expected a recognized join type; got '$hint'"
);
my @x_mapped =
$x->select( sub { [ scalar( $x_keys->( $_[0] ) ), $_[0] ] } )->to_list;
my @y_mapped =
$y->select( sub { [ scalar( $y_keys->( $_[0] ) ), $_[0] ] } )->to_list;
return ( \@x_mapped, \@y_mapped, $hint, $joiner );
};
sub join {
my ( $x_mapped, $y_mapped, $hint, $joiner ) = $_prepare_join->( @_ );
my @joined;
my ( @found_x, @found_y );
for my $Xi ( 0 .. $#$x_mapped ) {
my $X = $x_mapped->[$Xi];
for my $Yi ( 0 .. $#$y_mapped ) {
my $Y = $y_mapped->[$Yi];
if ( $X->[0] eq $Y->[0] ) {
my $a = $X->[1];
my $b = $Y->[1];
$found_x[$Xi]++;
$found_y[$Yi]++;
push @joined, scalar $joiner->( $a, $b );
}
} #/ for my $Yi ( 0 .. $#$y_mapped)
} #/ for my $Xi ( 0 .. $#$x_mapped)
if ( $hint eq -left or $hint eq -outer ) {
for my $Xi ( 0 .. $#$x_mapped ) {
next if $found_x[$Xi];
my $a = $x_mapped->[$Xi][1];
my $b = undef;
push @joined, scalar $joiner->( $a );
}
}
if ( $hint eq -right or $hint eq -outer ) {
for my $Yi ( 0 .. $#$y_mapped ) {
next if $found_y[$Yi];
my $a = undef;
my $b = $y_mapped->[$Yi][1];
push @joined, scalar $joiner->( undef, $b );
}
}
LINQ::Util::Internal::create_linq( $_[0], \@joined );
} #/ sub join
sub group_join {
my ( $x_mapped, $y_mapped, $hint, $joiner ) = $_prepare_join->( @_ );
$hint =~ /\A-(left|inner)\z/ or LINQ::Util::Internal::throw(
"CallerError",
message => "Join type '$hint' not supported for group_join",
);
my @joined;
my ( @found_x, @found_y );
for my $Xi ( 0 .. $#$x_mapped ) {
my $X = $x_mapped->[$Xi];
my @group = map $_->[1], grep $X->[0] eq $_->[0], @$y_mapped;
if ( @group or $hint eq -left ) {
my $a = $X->[1];
my $b = LINQ::Util::Internal::create_linq( $_[0], \@group );
push @joined, scalar $joiner->( $a, $b );
}
} #/ for my $Xi ( 0 .. $#$x_mapped)
LINQ::Util::Internal::create_linq( $_[0], \@joined );
} #/ sub group_join
sub take {
my $self = shift;
my ( $n ) = @_;
$self->take_while( sub { $n-- > 0 } );
}
sub take_while {
my $self = shift;
my $filter = LINQ::Util::Internal::assert_code( @_ );
my $stopped = 0;
my $idx = 0;
require LINQ;
require LINQ::Iterator;
'LINQ::Iterator'->new( sub {
local ( $@, $_ );
my $next;
my $ok = eval { $next = $self->element_at( $idx ); not $stopped };
my $e = $@;
if ( $ok and $filter->( $_ = $next ) ) {
++$idx;
return $next;
}
if ( defined $e and not $ok ) {
require Scalar::Util;
die( $e ) unless Scalar::Util::blessed( $e );
die( $e ) unless $e->isa( 'LINQ::Exception::NotFound' );
}
$stopped = 1;
return LINQ::END();
} );
} #/ sub take_while
sub skip {
my $self = shift;
my ( $n ) = @_;
$self->where( sub { $n-- <= 0 } );
}
sub skip_while {
my $self = shift;
my $filter = LINQ::Util::Internal::assert_code( @_ );
my $skipping = 1;
$self->where(
sub {
$skipping = 0
if $skipping
&& !$filter->( $_ );
not $skipping;
}
);
} #/ sub skip_while
sub concat {
my @collections = @_;
my $idx = 0;
require LINQ;
require LINQ::Iterator;
'LINQ::Iterator'->new( sub {
local ( $@, $_ );
my $next;
FIND_NEXT: {
return LINQ::END() if not @collections;
my $ok = eval { $next = $collections[0]->element_at( $idx ); 1 };
my $e = $@;
if ( $ok ) {
++$idx;
return $next;
}
elsif ( defined $e and not $ok ) {
require Scalar::Util;
die( $e ) unless Scalar::Util::blessed( $e );
die( $e ) unless $e->isa( 'LINQ::Exception::NotFound' );
}
shift @collections;
$idx = 0;
redo FIND_NEXT;
};
} );
}
sub order_by {
my $self = shift;
my $hint = ref( $_[0] ) ? -numeric : shift( @_ );
my $keygen = @_ ? LINQ::Util::Internal::assert_code( @_ ) : undef;
if ( not $keygen ) {
if ( $hint eq -string ) {
return LINQ::Util::Internal::create_linq(
$self => [ sort { $a cmp $b } $self->to_list ]
);
}
elsif ( $hint eq -numeric ) {
return LINQ::Util::Internal::create_linq(
$self => [ sort { $a <=> $b } $self->to_list ]
);
}
}
if ( $hint eq -string ) {
return LINQ::Util::Internal::create_linq(
$self => [
map $_->[1],
sort { $a->[0] cmp $b->[0] }
map [ $keygen->( $_ ), $_ ],
$self->to_list
]
);
} #/ if ( $hint eq -string )
elsif ( $hint eq -numeric ) {
return LINQ::Util::Internal::create_linq(
$self => [
map $_->[1],
sort { $a->[0] <=> $b->[0] }
map [ $keygen->( $_ ), $_ ],
$self->to_list
]
);
} #/ elsif ( $hint eq -numeric)
LINQ::Util::Internal::throw(
"CallerError",
message => "Expected '-numeric' or '-string'; got '$hint'"
);
} #/ sub order_by
sub then_by {
LINQ::Util::Internal::throw( "Unimplemented", method => "then_by" );
}
sub order_by_descending {
my $self = shift;
$self->order_by( @_ )->reverse;
}
sub then_by_descending {
LINQ::Util::Internal::throw( "Unimplemented", method => "then_by_descending" );
}
sub reverse {
my $self = shift;
LINQ::Util::Internal::create_linq(
$self => [ reverse( $self->to_list ) ],
);
}
sub group_by {
my $self = shift;
my $keygen = LINQ::Util::Internal::assert_code( @_ );
my @keys;
my %values;
for ( $self->to_list ) {
my $key = $keygen->( $_ );
unless ( $values{$key} ) {
push @keys, $key;
$values{$key} = [];
}
push @{ $values{$key} }, $_;
}
require LINQ::Grouping;
LINQ::Util::Internal::create_linq(
$self => [
map LINQ::Grouping::->new(
key => $_,
values => LINQ::Util::Internal::create_linq( $self, $values{$_} ),
),
@keys
]
);
} #/ sub group_by
sub distinct {
my $self = shift;
my $compare = @_ ? LINQ::Util::Internal::assert_code( @_ ) : sub { $_[0] == $_[1] };
my @already;
$self->where(
sub {
my $maybe = $_[0];
for my $got ( @already ) {
return !!0 if $compare->( $maybe, $got );
}
push @already, $maybe;
return !!1;
}
);
} #/ sub distinct
sub union {
my $self = shift;
my ( $other, @compare ) = @_;
$self->concat( $other )->distinct( @compare );
}
sub intersect {
my $self = shift;
my $other = shift;
my @compare = @_ ? LINQ::Util::Internal::assert_code( @_ ) : sub { $_[0] == $_[1] };
$self->where( sub { $other->contains( $_, @compare ) } );
}
sub except {
my $self = shift;
my $other = shift;
my @compare = @_ ? LINQ::Util::Internal::assert_code( @_ ) : sub { $_[0] == $_[1] };
$self->where( sub { not $other->contains( $_, @compare ) } );
}
sub sequence_equal {
my $self = shift;
my ( $other, @compare ) = @_;
return !!0 unless $self->count == $other->count;
my @list1 = $self->to_list;
my @list2 = $other->to_list;
return !!0 unless @list1 == @list2;
if ( @compare ) {
my $compare = LINQ::Util::Internal::assert_code( @compare );
for my $i ( 0 .. $#list1 ) {
return !!0 unless $compare->( $list1[$i], $list2[$i] );
}
return !!1;
}
for my $i ( 0 .. $#list1 ) {
return !!0 unless $list1[$i] == $list2[$i];
}
return !!1;
} #/ sub sequence_equal
my $_with_default = sub {
my $self = shift;
my $method = shift;
my @args = @_;
my $default = pop( @args );
my $return;
eval { $return = $self->$method( @args ); 1 } or do {
my $e = $@; # catch
# Rethrow any non-blessed errors.
require Scalar::Util;
die( $e ) unless Scalar::Util::blessed( $e );
# Rethrow any errors of the wrong class.
die( $e )
unless $e->isa( 'LINQ::Exception::NotFound' )
|| $e->isa( 'LINQ::Exception::MultipleFound' );
# Rethrow any errors which resulted from the wrong source.
die( $e ) unless $e->collection == $self;
return $default;
};
return $return;
};
sub first {
my $self = shift;
my $found = $self->where( @_ );
return $found->element_at( 0 ) if $found->count > 0;
LINQ::Util::Internal::throw( 'NotFound', collection => $self );
}
sub first_or_default {
shift->$_with_default( first => @_ );
}
sub last {
my $self = shift;
my $found = $self->where( @_ );
return $found->element_at( -1 ) if $found->count > 0;
LINQ::Util::Internal::throw( 'NotFound', collection => $self );
}
sub last_or_default {
shift->$_with_default( last => @_ );
}
sub single {
my $self = shift;
my $found = $self->where( @_ );
return $found->element_at( 0 ) if $found->count == 1;
$found->count == 0
? LINQ::Util::Internal::throw( 'NotFound', collection => $self )
: LINQ::Util::Internal::throw( 'MultipleFound', collection => $self, found => $found );
}
sub single_or_default {
shift->$_with_default( single => @_ );
}
sub element_at {
my $self = shift;
my ( $i ) = @_;
my @list = $self->to_list;
if ( $i > $#list ) {
LINQ::Util::Internal::throw( 'NotFound', collection => $self );
}
if ( $i < 0 - @list ) {
LINQ::Util::Internal::throw( 'NotFound', collection => $self );
}
$list[$i];
} #/ sub element_at
sub element_at_or_default {
shift->$_with_default( element_at => @_ );
}
sub any {
my $self = shift;
@_
? $self->where( @_ )->any
: ( $self->count > 0 );
}
sub all {
my $self = shift;
$self->where( @_ )->count == $self->count;
}
sub contains {
my $self = shift;
my ( $x, @args ) = @_;
if ( @args ) {
splice( @args, 1, 0, $x );
return $self->any( LINQ::Util::Internal::assert_code( @args ) );
}
$self->any( sub { $_ == $x } );
} #/ sub contains
sub count {
my $self = shift;
return $self->where( @_ )->count if @_;
my @list = $self->to_list;
return scalar( @list );
}
sub to_array {
my $self = shift;
[ $self->to_list ];
}
sub to_dictionary {
my $self = shift;
my ( $keygen ) = LINQ::Util::Internal::assert_code( @_ );
+{ map +( $keygen->( $_ ), $_ ), $self->to_list };
}
sub to_lookup {
my $self = shift;
$self->to_dictionary( @_ );
}
sub to_iterator {
my $self = shift;
my @list = $self->to_list;
sub { @list ? shift( @list ) : () };
}
sub cast {
my $self = shift;
my ( $type ) = @_;
my $cast = $self->of_type( @_ );
return $cast if $self->count == $cast->count;
LINQ::Util::Internal::throw( "Cast", collection => $self, type => $type );
}
sub of_type {
my $self = shift;
my ( $type ) = @_;
require Scalar::Util;
unless ( Scalar::Util::blessed( $type ) and $type->can( 'check' ) ) {
LINQ::Util::Internal::throw(
"CallerError",
message => "Expected type constraint; got '$type'",
);
}
if ( $type->isa( 'Type::Tiny' ) ) {
my $check = $type->compiled_check;
if ( $type->has_coercion ) {
my $coercion = $type->coercion->compiled_coercion;
return $self->select( $coercion )->where( $check );
}
return $self->where( $check );
} #/ if ( $type->isa( 'Type::Tiny'...))
if ( $type->can( 'has_coercion' ) and $type->has_coercion ) {
return $self
->select( sub { $type->coerce( $_ ) } )
->where( sub { $type->check( $_ ) } );
}
return $self->where( sub { $type->check( $_ ) } );
} #/ sub of_type
sub zip {
my $self = shift;
my $other = shift;
my $map = LINQ::Util::Internal::assert_code( @_ );
my @self = $self->to_list;
my @other = $other->to_list;
my @results;
while ( @self and @other ) {
push @results, scalar $map->( shift( @self ), shift( @other ) );
}
LINQ::Util::Internal::create_linq( $self => \@results );
} #/ sub zip
sub default_if_empty {
my $self = shift;
my $item = shift;
if ( $self->count == 0 ) {
return LINQ::Util::Internal::create_linq( $self => [$item] );
}
return $self;
} #/ sub default_if_empty
1;
__END__
=pod
=encoding utf-8
=head1 NAME
LINQ - the interface which all LINQ collections share
=head1 SYNOPSIS
use feature 'say';
use LINQ 'LINQ';
my $double_even_numbers =
LINQ( [1..100] )
->where( sub { $_ % 2 == 0 } )
->select( sub { $_ * 2 } );
if ( not $double_even_numbers->DOES( 'LINQ::Collection' ) ) {
die "What? But you said they all do it!";
}
for my $n ( $double_even_numbers->to_list ) {
say $n;
}
=head1 DESCRIPTION
Objects returned by the C<< LINQ() >>, C<< LINQ::Repeat() >>, and
C<< LINQ::Range() >> functions all provide the LINQ::Collection interface.
Many of the methods in this interface also return new objects which provide
this interface.
=head1 METHODS
Many methods take a parameter "CALLABLE". This means they can accept a
coderef, an object overloading C<< &{} >>, or an arrayref where the first
item is one of the previous two things and the remainder are treated as
arguments to curry to the first argument. A quoted regexp C<< qr/.../ >> can
also be used as a callable.
If using an arrayref, it is generally permissable to flatten it into a
list, unless otherwise noted. An example of this can be seen in the
documentation for C<select>.
=over
=item C<< select( CALLABLE ) >>
LINQ's version of C<map>, except that the code given is always called in
scalar context, being expected to return exactly one result.
Returns a LINQ::Collection of the results.
my $people = LINQ( [
{ name => "Alice", age => 32 },
{ name => "Bob", age => 31 },
{ name => "Carol", age => 34 },
] );
my $names = $people->select( sub {
return $_->{name};
} );
for my $name ( $names->to_list ) {
print "$name\n";
}
Another way of doing the same thing, using currying:
my $people = LINQ( [
{ name => "Alice", age => 32 },
{ name => "Bob", age => 31 },
{ name => "Carol", age => 34 },
] );
my $BY_HASH_KEY = sub {
my ($key) = @_;
return $_->{$key};
};
my $names = $people->select( $BY_HASH_KEY, 'name' );
for my $name ( $names->to_list ) {
print "$name\n";
}
=item C<< select_many( CALLABLE ) >>
If you wanted C<select> to be able to return a list like C<map> does, then
C<select_many> is what you want. However, rather than returning a Perl list,
your callable should return a LINQ::Collection or an arrayref.
=item C<< where( CALLABLE ) >>
LINQ's version of C<grep>. Returns a LINQ::Collection of the filtered results.
my $people = LINQ( [
{ name => "Alice", age => 32 },
{ name => "Bob", age => 31 },
{ name => "Carol", age => 34 },
] );
my $young_people = $people->where( sub {
return $_->{age} < 33;
} );
=item C<< min( CALLABLE? ) >>
Returns the numerically lowest value in the collection.
my $lowest = LINQ( [ 5, 1, 2, 3 ] )->min; # ==> 1
If a callable is provided, then C<select> will be called and the minimum of the
result will be returned.
my $people = LINQ( [
{ name => "Alice", age => 32 },
{ name => "Bob", age => 31 },
{ name => "Carol", age => 34 },
] );
my $lowest_age = $people->min( sub { $_->{age} } ); # ==> 31
If you need more flexible comparison (e.g. non-numeric comparison), use
C<order_by> followed by C<first>.
=item C<< max( CALLABLE? ) >>
Like C<min>, but returns the numerically highest value.
=item C<< sum( CALLABLE? ) >>
Like C<min>, but returns the sum of all values in the collection.
=item C<< average( CALLABLE? ) >>
Takes C<sum>, and divides by the count of items in the collection.
my $people = LINQ( [
{ name => "Alice", age => 32 },
{ name => "Bob", age => 31 },
{ name => "Carol", age => 34 },
] );
my $average_age = $people->average( sub {
return $_->{age};
} ); # ==> 32.33333
=item C<< aggregate( CALLABLE, INITIAL? ) >>
LINQ's version of C<reduce> (from L<List::Util>). We pass C<< $a >> and
C<< $b >> as the last arguments to CALLABLE, rather than using the package
variables like List::Util does.
The CALLABLE must not be a flattened list, but may still be an arrayref.
INITIAL is an initial value.
my $people = LINQ( [
{ name => "Alice", age => 32 },
{ name => "Bob", age => 31 },
{ name => "Carol", age => 34 },
] );
my dotted_names = $people
->select( sub { $_->{name} } )
->aggregate( sub {
my ( $a, $b ) = @_;
return "$a.$b";
} );
print "$dotted_names\n"; # ==> Alice.Bob.Carol
=item C<< join( Y, HINT?, X_KEYS, Y_KEYS, JOINER ) >>
This is akin to an SQL join.
my $people = LINQ( [
{ name => "Alice", dept => 'Marketing' },
{ name => "Bob", dept => 'IT' },
{ name => "Carol", dept => 'IT' },
] );
my $departments = LINQ( [
{ dept_name => 'Accounts', cost_code => 1 },
{ dept_name => 'IT', cost_code => 7 },
{ dept_name => 'Marketing', cost_code => 8 },
] );
my $BY_HASH_KEY = sub {
my ($key) = @_;
return $_->{$key};
};
my $joined = $people->join(
$departments,
-inner, # inner join
[ $BY_HASH_KEY, 'dept' ], # select from $people
[ $BY_HASH_KEY, 'dept_name' ], # select from $departments
sub {
my ( $person, $dept ) = @_;
return {
name => $person->{name},
dept => $person->{dept},
expense_code => $dept->{cost_code},
};
},
);
Hints C<< -inner >>, C<< -left >>, C<< -right >>, and C<< -outer >> are
supported, analagous to the joins with the same names in SQL.
X_KEYS and Y_KEYS are non-list callables which return the values to join the
two collections by.
JOINER is a callable (which may be a flattened list) which is passed items
from each of the two collections and should return a new item. In the case
of left/right/outer joins, one of those items may be undef.
=item C<< group_join( Y, HINT?, X_KEYS, Y_KEYS, JOINER ) >>
Similar to C<group> except that rather than JOINER being called for every
X/Y combination, all the Ys for a particular X are put in a collection, and
the JOINER is called for each X and passed the collection of Ys.
The only hints supported are C<< -inner >> and C<< -left >>.
This is best explained with a full example:
my $departments = LINQ( [
{ dept_name => 'Accounts', cost_code => 1 },
{ dept_name => 'IT', cost_code => 7 },
{ dept_name => 'Marketing', cost_code => 8 },
] );
my $people = LINQ( [
{ name => "Alice", dept => 'Marketing' },
{ name => "Bob", dept => 'IT' },
{ name => "Carol", dept => 'IT' },
] );
my $BY_HASH_KEY = sub {
my ($key) = @_;
return $_->{$key};
};
my $joined = $departments->group_join(
$people,
-left, # left join
[ $BY_HASH_KEY, 'dept_name' ], # select from $departments
[ $BY_HASH_KEY, 'dept' ], # select from $people
sub {
my ( $dept, $people ) = @_; # $people is a LINQ::Collection
my $names = $people->select( $BY_HASH_KEY, 'name' )->to_array;
return {
dept => $dept->{dept_name},
cost_code => $dept->{cost_code},
people => $names,
};
},
);
# [
# {
# 'cost_code' => 1,
# 'dept' => 'Accounts',
# 'people' => []
# },
# {
# 'cost_code' => 7,
# 'dept' => 'IT',
# 'people' => [
# 'Bob',
# 'Carol'
# ]
# },
# {
# 'cost_code' => 8,
# 'dept' => 'Marketing',
# 'people' => [
# 'Alice'
# ]
# }
=item C<< take( N ) >>
Takes just the first N items from a collection, returning a new collection.
=item C<< take_while( CALLABLE ) >>
Takes items from the collection, stopping at the first item where CALLABLE
returns false.
=item C<< skip( N ) >>
Skips the first N items from a collection, and returns the rest as a new
collection.
=item C<< skip_while( CALLABLE ) >>
Skips the first items from a collection while CALLABLE returns true, and
returns the rest as a new collection.
=item C<< concat( COLLECTION ) >>
Returns a new collection by concatenating this collection with another
collection.
my $deck_of_cards = $red_cards->concat( $black_cards );
=item C<< order_by( HINT?, CALLABLE? ) >>
HINT may be C<< -numeric >> (the default) or C<< -string >>.
If CALLABLE is given, it should return a number or string to sort by.
my $sorted = $people->order_by(
-string,
[ $BY_HASH_KEY, 'name' ] # CALLABLE as an arrayref
);
=item C<< then_by( HINT?, CALLABLE ) >>
Not implemented.
=item C<< order_by_descending( HINT?, CALLABLE ) >>
Like C<order_by> but uses reverse order.
=item C<< then_by_descending( HINT?, CALLABLE ) >>
Not implemented.
=item C<< reverse >>
Reverses the order of the collection.
=item C<< group_by( CALLABLE ) >>
Groups the items by the key returned by CALLABLE.
The collection of groups is a LINQ::Collection and each grouping is a
LINQ::Grouping. LINQ::Grouping provides two accessors: C<key> and C<values>,
with C<values> returning a LINQ::Collection of items.
my $people = LINQ( [
{ name => "Alice", dept => 'Marketing' },
{ name => "Bob", dept => 'IT' },
{ name => "Carol", dept => 'IT' },
] );
my $groups = $people->group_by( sub { $_->{dept} } );
for my $group ( $groups->to_list ) {
print "Dept: ", $group->key, "\n";
for my $person ( $group->values->to_list ) {
print " - ", $person->{name};
}
}
=item C<< distinct( CALLABLE? ) >>
Returns a new collection without any duplicates which were in the original.
If CALLABLE is provided, this will be used to determine when two items are
considered identical/equivalent. Otherwise, numeric equality is used.
=item C<< union( COLLECTION, CALLABLE? ) >>
Returns a new collection formed from the union of both collections.
my $first = LINQ( [ 1, 2, 3, 4 ] );
my $second = LINQ( [ 3, 4, 5, 6 ] );
$first->union( $second )->to_array; # ==> [ 1, 2, 3, 4, 5, 6 ]
If CALLABLE is provided, this will be used to determine when two items are
considered identical/equivalent. Otherwise, numeric equality is used.
=item C<< intersect( COLLECTION, CALLABLE? ) >>
Returns a new collection formed from the union of both collections.
my $first = LINQ( [ 1, 2, 3, 4 ] );
my $second = LINQ( [ 3, 4, 5, 6 ] );
$first->intersect( $second )->to_array; # ==> [ 3, 4 ]
If CALLABLE is provided, this will be used to determine when two items are
considered identical/equivalent. Otherwise, numeric equality is used.
=item C<< except( COLLECTION, CALLABLE? ) >>
Returns a new collection formed from the asymmetric difference of both
collections.
my $first = LINQ( [ 1, 2, 3, 4 ] );
my $second = LINQ( [ 3, 4, 5, 6 ] );
$first->except( $second )->to_array; # ==> [ 1, 2 ]
If CALLABLE is provided, this will be used to determine when two items are
considered identical/equivalent. Otherwise, numeric equality is used.
=item C<< sequence_equal( COLLECTION, CALLABLE? ) >>
Returns true if and only if each item in the first collection is
identical/equivalent to its corresponding item in the second collection,
considered in order, according to CALLABLE.
If CALLABLE isn't given, then numeric equality is used.
=item C<< first( CALLABLE? ) >>
Returns the first item in a collection.
If CALLABLE is provided, returns the first item in the collection where
CALLABLE returns true.
If there is no item to return, does not return undef, but throws a
LINQ::Exception::NotFound exception.
=item C<< first_or_default( CALLABLE?, DEFAULT ) >>
Like C<first>, but instead of throwing an exception, will return the DEFAULT.
=item C<< last( CALLABLE? ) >>
Returns the last item in a collection.
If CALLABLE is provided, returns the last item in the collection where
CALLABLE returns true.
If there is no item to return, does not return undef, but throws a
LINQ::Exception::NotFound exception.
=item C<< last_or_default( CALLABLE?, DEFAULT ) >>
Like C<last>, but instead of throwing an exception, will return the DEFAULT.
=item C<< single( CALLABLE? ) >>
Returns the only item in a collection.
If CALLABLE is provided, returns the only item in the collection where
CALLABLE returns true.
If there is no item to return, does not return undef, but throws a
LINQ::Exception::NotFound exception.
If there are multiple items in the collection, or multiple items where
CALLABLE returns true, throws a LINQ::Exception::MultipleFound exception.
=item C<< single_or_default( CALLABLE?, DEFAULT ) >>
Like C<single> but rather than throwing an exception, will return DEFAULT.
=item C<< element_at( N ) >>
Returns element N within the collection. N may be negative to count from the
end of the collection. Collections are indexed from zero.
If N exceeds the length of the collection, throws a LINQ::Exception::NotFound
exception.
=item C<< element_at_or_default( N, DEFAULT ) >>
Like C<element_at> but rather than throwing an exception, will return DEFAULT.
=item C<< any( CALLABLE? ) >>
Returns true if CALLABLE returns true for any item in the collection.
=item C<< all( CALLABLE? ) >>
Returns true if CALLABLE returns true for every item in the collection.
=item C<< contains( ITEM, CALLABLE? ) >>
Returns true if the collection contains ITEM. By default, this is checked
using numeric equality.
If CALLABLE is given, this is passed two items and should return true
if they should be considered identical/equivalent.
my $SAME_NAME = sub {
$_[0]{name} eq $_[1]{name};
};
if ( $people->contains( { name => "Bob" }, $SAME_NAME ) ) {
print "The collection includes Bob.\n";
}
=item C<< count >>
Returns the size of the collection. (Number of items.)
=item C<< to_list >>
Returns the collection as a list.
=item C<< to_array >>
Returns an arrayref for the collection. This may be a tied arrayref and you
should not assume it will be writable.
=item C<< to_dictionary( CALLABLE ) >>
The CALLABLE will be called for each item in the collection and is expected to
return a string key.
The method will return a hashref mapping the keys to each item in the
collection.
=item C<< to_lookup( CALLABLE ) >>
Alias for C<to_dictionary>.
=item C<< to_iterator >>
Returns a coderef which can be used to iterate through the collection.
my $people = LINQ( [
{ name => "Alice", dept => 'Marketing' },
{ name => "Bob", dept => 'IT' },
{ name => "Carol", dept => 'IT' },
] );
my $next_person = $people->to_iterator;
while ( my $person = $next_person->() ) {
print $person->{name}, "\n";
}
=item C<< cast( TYPE ) >>
Given a type constraint (see L<Type::Tiny>) will attempt to coerce every item
in the collection to the type, and will return the collection of coerced
values. If any item cannot be coerced, throws a LINQ::Exception::Cast
exception.
=item C<< of_type( TYPE ) >>
Given a type constraint (see L<Type::Tiny>) will attempt to coerce every item
in the collection to the type, and will return the collection of coerced
values. Any items which cannot be coerced will be skipped.
=item C<< zip( COLLECTION, CALLABLE ) >>
Will loop through both collections in parallel and pass one item from each
collection to CALLABLE as arguments.
If the two collections are of different sizes, will stop after exhausing the
shorter collection.
=item C<< default_if_empty( ITEM ) >>
If this collection contains one or more items, returns itself.
If the collection is empty, returns a new collection containing just a single
item, given as a parameter.
my $collection = $people->default_if_empty( "Bob" );
# Equivalent to:
my $collection = $people->count ? $people : LINQ( [ "Bob" ] );
=item C<< target_class >>
This returns either a class name or a coderef, which will be the usual way
that the methods defined in LINQ::Collection will create new collections
(for example, when they need to return a collection).
Given a collection, you can create a new collection like this:
my $target_class = $old_collection->target_class;
my $new_collection = ref($target_class)
? $target_class->( @items )
: $target_class->new( @items );
=back
=head1 BUGS
Please report any bugs to
=head1 SEE ALSO
L<LINQ>, L<LINQ::Grouping>.
=head1 AUTHOR
Toby Inkster E<lt>tobyink@cpan.orgE<gt>.
=head1 COPYRIGHT AND LICENCE
This software is copyright (c) 2014, 2021 by Toby Inkster.
This is free software; you can redistribute it and/or modify it under
the same terms as the Perl 5 programming language system itself.
=head1 DISCLAIMER OF WARRANTIES
THIS PACKAGE IS PROVIDED "AS IS" AND WITHOUT ANY EXPRESS OR IMPLIED
WARRANTIES, INCLUDING, WITHOUT LIMITATION, THE IMPLIED WARRANTIES OF
MERCHANTIBILITY AND FITNESS FOR A PARTICULAR PURPOSE.