=begin pod

=TITLE role Baggy

=SUBTITLE Collection of distinct weighted objects

    role Baggy does QuantHash { }

A role for collections of weighted objects.  See L<Bag>, L<BagHash>, and
L<Mixy>.

=head1 Methods

=head2 method grab

Defined as:

    multi method grab(Baggy:D:) returns Any
    multi method grab(Baggy:D: $count) returns Array:D

Like L<pick|#method pick>, a C<grab> returns a random selection of elements, weighted
by the values corresponding to each key. Unlike C<pick>, it works only on mutable
structures, e.g. L<BagHash|/type/BagHash>. Use of C<grab> on an immutable structure results in an
C<X::Immutable> exception. If C<*> is passed as C<$count>, or C<$count> is greater than
or equal to the L<total|#method total> of the invocant, then C<total> elements from the
invocant are returned in a random sequence.

Grabbing decrements the grabbed key's weight by one (deleting the key
when it reaches 0). By definition, the C<total> of the invocant also decreases by one, so the
probabilities stay consistent through subsequent C<grab> operations.

    my $cars = ('Ford' => 2, 'Rover' => 3).BagHash;
    say $cars.grab;                                   # Ford
    say $cars.grab(2);                                # [Rover Rover]
    say $cars.grab(*);                                # [Rover Ford]

    my $breakfast = ('eggs' => 2, 'bacon' => 3).Bag;
    say $breakfast.grab;
    CATCH { default { put .^name, ': ', .Str } };
    # OUTPUT: «X::Immutable: Cannot call 'grab' on an immutable 'Bag'␤»

=head2 method grabpairs

Defined as:

    multi method grabpairs(Baggy:D:) returns Any
    multi method grabpairs(Baggy:D: $count) returns List:D

Returns a C<Pair> or a C<List> of C<Pair>s depending on the version of the method
being invoked. Each C<Pair> returned has an element of the invocant as its key and the
elements weight as its value. Unlike L<pickpairs>, it works only on mutable structures,
e.g. L<BagHash|/type/BagHash>. Use of C<grabpairs> on 'an immutable structure results in
an C<X::Immutable> exception. If C<*> is passed as C<$count>, or C<$count> is greater
than or equal to the number of L<elements|#method elems> of the invocant, then all
element/weight C<Pair>s from the invocant are returned in a random sequence.

What makes C<grabpairs> different from L<pickpairs|#method pickpairs> is that the
'grabbed' elements are in fact removed from the invocant.

    my $breakfast = (eggs => 2, bacon => 3).BagHash;
    say $breakfast.grabpairs;                         # bacon => 3
    say $breakfast;                                   # BagHash.new(eggs(2))
    say $breakfast.grabpairs(1);                      # [eggs => 2]
    say $breakfast.grabpairs(*);                      # []

    my $diet = ('eggs' => 2, 'bacon' => 3).Bag;
    say $diet.grabpairs;
    CATCH { default { put .^name, ': ', .Str } };
    # OUTPUT: «X::Immutable: Cannot call 'grabpairs' on an immutable 'Bag'␤»

=head2 method pick

Defined as:

    multi method pick(Baggy:D:) returns Any
    multi method pick(Baggy:D: $count) returns Seq:D

Like an ordinary list L<pick|/type/List#routine_pick>, but returns keys
of the invocant weighted by their values, as if the keys were replicated
the number of times indicated by the corresponding value and then list
pick used. The underlying metaphor for picking is that you're pulling
colored marbles out a bag. (For "picking with replacement" see
L<roll|#method roll> instead). If C<*> is passed as C<$count>, or C<$count> is
greater than or equal to the L<total|#method total> of the invocant, then
C<total> elements from the invocant are returned in a random sequence.

Note that each C<pick> invocation maintains its own private state and has
no effect on subsequent C<pick> invocations.

    my $breakfast = bag <eggs bacon bacon bacon>;
    say $breakfast.pick;                              # eggs
    say $breakfast.pick(2);                           # (eggs bacon)

    say $breakfast.total;                             # 4
    say $breakfast.pick(*);                           # (bacon bacon bacon eggs)

=head2 method pickpairs

Defined as:

    multi method pickpairs(Baggy:D:) returns Pair:D
    multi method pickpairs(Baggy:D: $count) returns List:D

Returns a C<Pair> or a C<List> of C<Pair>s depending on the version of the method
being invoked. Each C<Pair> returned has an element of the invocant as its key and the
elements weight as its value. The elements are 'picked' without replacement. If C<*>
is passed as C<$count>, or C<$count> is greater than or equal to the number of
L<elements|#method elems> of the invocant, then all element/weight C<Pair>s from
the invocant are returned in a random sequence.

Note that each C<pickpairs> invocation maintains its own private state and has
no effect on subsequent C<pickpairs> invocations.

    my $breakfast = bag <eggs bacon bacon bacon>;
    say $breakfast.pickpairs;                         # eggs => 1
    say $breakfast.pickpairs(1);                      # (bacon => 3)
    say $breakfast.pickpairs(*);                      # (eggs => 1 bacon => 3)

=head2 method roll

Defined as:

    multi method roll(Baggy:D:) returns Any:D
    multi method roll(Baggy:D: $count) returns Seq:D

Like an ordinary list L<roll|/type/List#routine_roll>, but returns keys of the invocant weighted
by their values, as if the keys were replicated the number of times indicated
by the corresponding value and then list roll used. The underlying
metaphor for rolling is that you're throwing C<$count> dice that are
independent of each other, which (in bag terms) is equivalent to picking
a colored marble out your bag and then putting it back, and doing this
C<$count> times. In dice terms, the number of marbles corresponds to the
number of sides, and the number of marbles of the same color corresponds
to the number of sides with the same color. (For "picking without replacement"
see L<pick|#method pick> instead).

If C<*> is passed to C<$count>, returns a lazy, infinite sequence of randomly
chosen elements from the invocant.

    my $breakfast = bag <eggs bacon bacon bacon>;
    say $breakfast.roll;                                  # bacon
    say $breakfast.roll(3);                               # (bacon eggs bacon)

    my $random_dishes := $breakfast.roll(*);
    say $random_dishes[^5];                               # (bacon eggs bacon bacon bacon)

=head2 method pairs

Defined as:

    method pairs(Baggy:D:) returns Seq:D

Returns all elements and their respective weights as a L<Seq|/type/Seq> of C<Pair>s
where the key is the element itself and the value is the weight of that element.

    my $breakfast = bag <bacon eggs bacon>;
    my $seq = $breakfast.pairs;
    say $seq.sort;                                    # (bacon => 2 eggs => 1)

=head2 method antipairs

Defined as:

    method antipairs(Baggy:D:) returns Seq:D

Returns all elements and their respective weights as a L<Seq|/type/Seq> of L<Pairs|/type/Pair>,
where the element itself is the value and the weight of that element is the key, i.e.
the opposite of method L<pairs|#method pairs>.

    my $breakfast = bag <bacon eggs bacon>;
    my $seq = $breakfast.antipairs;
    say $seq.sort;                                    # (1 => eggs 2 => bacon)

=head2 method invert

Defined as:

    method invert(Baggy:D:) returns Seq:D

Returns all elements and their respective weights as a L<Seq|/type/Seq> of L<Pairs|/type/Pair>,
where the element itself is the value and the weight of that element is the key, i.e.
the opposite of method L<pairs|#method pairs>. Except for some esoteric cases C<invert> on a
Baggy type returns the same result as L<antipairs|#method_antipairs>.

    my $breakfast = bag <bacon eggs bacon>;
    my $seq = $breakfast.invert;
    say $seq.sort;                                    # (1 => eggs 2 => bacon)

=head2 method classify-list

Defined as:

    multi method classify-list(&test, *@list) returns Baggy:D

Transforms a list of values into a classification of those values according to
C<&test> and stores the results in the invocant which is then returned. Each
key represents the classification for one or more of the incoming list values,
and the corresponding value contains the number of list values classified by
C<&test> into the category of the associated key.

As an example, suppose that we have a list of C<Int>s which we would like to
classify into two categories, namely even and odd numbers. Here's one way to solve
this problem:

    my $b = BagHash.new();
    dd $b.classify-list( { $_ %% 2 ?? 'even' !! 'odd' }, (1, 7, 6, 3, 2) );
    # returns: ("even"=>2,"odd"=>3).BagHash

The printed result shows us that of the numbers in the list two were classified
as even and three as odd. Note that the result doesn't show us B<which> numbers were
classified as being even or odd. If that's what you want, use the
L<classify|/type/List#routine_classify> routine in L<List|/type/List> instead.

=head2 method categorize-list

Defined as:

    multi method categorize-list(&test:(Any --> Bool), *@list) returns Baggy:D

Transforms a list of values into categorizations of those values according to
C<&test> and stores the results in the invocant which is then returned.
Each key represents one possible categorization for one or more of the
incoming list values, and the corresponding value contains the number of list
values categorized by C<&test> into the category of the associated key.

The signature for C<&test> contains one parameter of type C<Any> which simply
means that C<&test> must be able to handle arguments of the same type as those
in C<*@list>.

As an example, suppose that we have a list of C<Int>s each of which is either
even or odd and, at the same time, prime or non-prime. We want to get hold of
an aggregated result which shows us how many numbers were even, odd, prime and
non-prime.

    my $b = BagHash.new();
    dd $b.categorize-list( { $_ %% 2 ?? 'even' !! 'odd',
                             $_.is-prime ?? 'prime' !! 'not prime'
                           }, (1, 7, 6, 3, 2) );
    # returns: ("non-prime"=>2,"even"=>2,"prime"=>3,"odd"=>3).BagHash

The result shows us that of the numbers in the list two were categorized as
even and three as odd. In addition the result also shows that three of the numbers
were also categorized as prime and two as non-prime. Note that the result doesn't
show us B<which> numbers were categorized as being even, odd, prime or non-prime.
If that is what you want, use the L<categorize|/type/List#routine_categorize>
routine in L<List|/type/List> instead.

It should also be noted that, unlike L<classify-list|/type/Baggy#method_classify-list>,
which assumes that the return value of C<&test> is a single value, C<categorize-list>
always assumes that the return value of C<&test> is a list of categories that are
appropriate to the current value.

=head2 method keys

Defined as:

    method keys(Baggy:D:) returns List:D

Returns a list of all keys in the C<Baggy> object without taking
their individual weights into account as opposed to L<kxxv|#method kxxv>.

    my $breakfast = bag <eggs spam spam spam>;
    say $breakfast.keys.sort;                        # (eggs spam)

    my $n = ("a" => 5, "b" => 2).BagHash;
    say $n.keys.sort;                                # (a b)

=head2 method values

Defined as:

    method values(Baggy:D:) returns List:D

Returns a list of all values, i.e. weights, in the C<Baggy> object.

    my $breakfast = bag <eggs spam spam spam>;
    say $breakfast.values.sort;                      # (1 3)

    my $n = ("a" => 5, "b" => 2, "a" => 1).BagHash;
    say $n.values.sort;                              # (2 6)

=head2 method kv

Defined as:

    method kv(Baggy:D:) returns List:D

Returns a list of keys and values interleaved.

    my $breakfast = bag <eggs spam spam spam>;
    say $breakfast.kv;                                # (spam 3 eggs 1)

    my $n = ("a" => 5, "b" => 2, "a" => 1).BagHash;
    say $n.kv;                                        # (a 6 b 2)

=head2 method kxxv

Defined as:

    method kxxv(Baggy:D:) returns List:D

Returns a list of the keys of the invocant, with each key multiplied by its
weight. Note that C<kxxv> only works for C<Baggy> types which have integer
weights, i.e. L<Bag> and L<BagHash>.

    my $breakfast = bag <spam eggs spam spam bacon>;
    say $breakfast.kxxv.sort;                         # (bacon eggs spam spam spam)

    my $n = ("a" => 0, "b" => 1, "b" => 2).BagHash;
    say $n.kxxv;                                      # (b b b)

=head2 method elems

Defined as:

    method elems(Baggy:D:) returns Int:D

Returns the number of elements in the C<Baggy> object without
taking the individual elements weight into account.

    my $breakfast = bag <eggs spam spam spam>;
    say $breakfast.elems;                             # 2

    my $n = ("b" => 9.4, "b" => 2).MixHash;
    say $n.elems;                                     # 1

=head2 method total

Defined as:

    method total(Baggy:D:)

Returns the sum of weights for all elements in the C<Baggy>
object.

    my $breakfast = bag <eggs spam spam bacon>;
    say $breakfast.total;                             # 4

    my $n = ("a" => 5, "b" => 1, "b" => 2).BagHash;
    say $n.total;                                     # 8

=head2 method default

Defined as:

    method default(Baggy:D:) returns Int:D

Returns zero.

    my $breakfast = bag <eggs bacon>;
    say $breakfast.default;                           # 0

=head2 method hash

Defined as:

    method hash(Baggy:D:) returns Hash:D

Returns a L<Hash|/type/Hash> where the elements of the invocant
are the keys and their respective weights the values;

    my $breakfast = bag <eggs bacon bacon>;
    my $h = $breakfast.hash;
    say $h.WHAT;                                      # (Hash)
    say $h;                                           # {bacon => 2, eggs => 1}

=head2 method Bool

Defined as:

    method Bool(Baggy:D:) returns Bool:D

Returns C<True> if the invocant contains at least one element.

    my $breakfast = ('eggs' => 1).BagHash;
    say $breakfast.Bool;                              # True   (since we have one element)
    $breakfast<eggs> = 0;                             # weight == 0 will lead to element removal
    say $breakfast.Bool;                              # False

=head2 method Set

Defined as:

    method Set() returns Set:D

Returns a L<Set|/type/Set> whose elements are the L<keys|#method keys> of the invocant.

    my $breakfast = (eggs => 2, bacon => 3).BagHash;
    say $breakfast.Set;                               # set(bacon, eggs)

=head2 method SetHash

Defined as:

    method SetHash() returns SetHash:D

Returns a L<SetHash|/type/SetHash> whose elements are the L<keys|#method keys> of the invocant.

    my $breakfast = (eggs => 2, bacon => 3).BagHash;
    my $sh = $breakfast.SetHash;
    say $sh.WHAT;                                     # (SetHash)
    say $sh.elems;                                    # 2

=head2 method ACCEPTS

Defined as:

    method ACCEPTS($other) returns Bool:D

Used in smart-matching if the right-hand side is a C<Baggy>.

If the right hand side is the type object, i.e. C<Baggy>, the method
returns C<True> if C<$other> L<does|/routine/does#class_Mu> C<Baggy>
otherwise C<False> is returned.

If the right hand side is a C<Baggy> object, C<True> is returned only if
C<$other> has the same elements, with the same weights, as the invocant.

    my $breakfast = bag <eggs bacon>;
    say $breakfast ~~ Baggy;                            # True
    say $breakfast.does(Baggy);                         # True

    my $second-breakfast = (eggs => 1, bacon => 1).Mix;
    say $breakfast ~~ $second-breakfast;                # True

    my $third-breakfast = (eggs => 1, bacon => 2).Bag;
    say $second-breakfast ~~ $third-breakfast;          # False

=head1 See Also

L<Sets, Bags, and Mixes|/language/setbagmix>

=end pod
