class BagHash
ErrorsCollection

class BagHash

Mutable collection of distinct objects with integer weights

class BagHash does Baggy { }

A BagHash is a mutable bag/multiset, meaning a collection of distinct items in no particular order that each have an integer weight assigned to them signifying how many copies of that element are considered "in the bag". If you do not need the mutability that a BagHash provides, consider using the immutable Bag type instead.

An item may be a definite object of any type – not just a Str. For example, you can store Sub's in a BagHash, and you will store the actual Sub rather than a string with the same name as the Sub. Within a BagHash, items that would compare positively with the === operator are considered the same element, with the number of how many there were as its weight. Alternatively, you can use the kxxv method to easily get back the expanded list of items (without the order):

my $breakfast = <spam eggs spam spam bacon spam>.BagHash;
 
say $breakfast.elems;      # OUTPUT: «3␤» 
say $breakfast.keys.sort;  # OUTPUT: «bacon eggs spam␤» 
 
say $breakfast.total;      # OUTPUT: «6␤» 
say $breakfast.kxxv.sort;  # OUTPUT: «bacon eggs spam spam spam spam␤» 

BagHashes can be treated as object hashes using the { } postcircumfix operator, or the < > postcircumfix operator for literal string keys, which returns the corresponding integer weight for keys that are elements of the bag, and 0 for keys that aren't. These operators can also be used to modify weights (see Updating BagHash Objects, below).

my $breakfast = <spam eggs spam spam bacon spam>.BagHash;
say $breakfast<bacon>;     # OUTPUT: «1␤» 
say $breakfast<spam>;      # OUTPUT: «4␤» 
say $breakfast<sausage>;   # OUTPUT: «0␤» 
 
$breakfast<sausage> = 2;
$breakfast<bacon>--;
say $breakfast.kxxv.sort;  # OUTPUT: «eggs sausage sausage spam spam spam spam␤» 

Creating BagHash objects

BagHashes can be composed using BagHash.new. Any positional parameters, regardless of their type, become elements of the bag:

my $n = BagHash.new: "a""b""c""c";
say $n.raku;             # OUTPUT: «("b"=>1,"a"=>1,"c"=>2).BagHash␤» 
say $n.keys.raku;        # OUTPUT: «("b", "a", "c").Seq␤» 
say $n.values.raku;      # OUTPUT: «(1, 1, 2).Seq␤»

Besides, BagHash.new-from-pairs can create a BagHash with items and their occurrences.

my $n = BagHash.new-from-pairs: "a" => 0"b" => 1"c" => 2"c" => 2;
say $n.raku;             # OUTPUT: «("b"=>1,"c"=>4).BagHash␤» 
say $n.keys.raku;        # OUTPUT: «("b", "c").Seq␤» 
say $n.values.raku;      # OUTPUT: «(1, 4).Seq␤»

Alternatively, the .BagHash coercer (or its functional form, BagHash()) can be called on an existing object to coerce it to a BagHash. Its semantics depend on the type and contents of the object. In general it evaluates the object in list context and creates a bag with the resulting items as elements, although for Hash-like objects or Pair items, only the keys become elements of the bag, and the (cumulative) values become the associated integer weights:

my $m = ("a""b""c""c").BagHash;
my $n = ("a" => 0"b" => 1"c" => 2"c" => 2).BagHash;
say $m.raku;             # OUTPUT: «("b"=>1,"a"=>1,"c"=>2).BagHash␤» 
say $n.raku;             # OUTPUT: «("b"=>1,"c"=>4).BagHash␤»

You can also create BagHash masquerading as a hash by using the is trait:

my %bh is BagHash = <a b b c c c>;
say %bh<b>;  # 2 
say %bh<d>;  # 0

Since 6.d (2019.03 and later) it is also possible to specify the type of values you would like to allow in a BagHash. This can either be done when calling .new:

# only allow strings 
my $n = BagHash[Str].new: <a b b c c c>;

or using the masquerading syntax:

# only allow strings 
my %bh is BagHash[Str= <a b b c c c>;
say %bh<b>;  # 2 
say %bh<d>;  # 0 
 
# only allow whole numbers 
my %bh is BagHash[Int= <a b b c c c>;
# Type check failed in binding; expected Int but got Str ("a")

Updating BagHash Objects

Once you have created a BagHash, you can update its values in two ways. First, you can use the add and remove methods:

my $n = BagHash.new: "a""b""c""c";
say $n.raku;             # OUTPUT: «("b"=>1,"a"=>1,"c"=>2).BagHash␤» 
$n.add('c');
say $n.raku;             # OUTPUT: «("b"=>1,"c"=>3,"a"=>1).BagHash␤» 
$n.remove(('b''a'),);
say $n.raku;             # OUTPUT: «("c"=>3).BagHash␤» 
$n.remove('c');
say $n.raku;             # OUTPUT: «("c"=>2).BagHash␤»

Note that, as shown in the final example, the remove method removes a single value from the BagHash; it doesn't entirely remove the key from the BagHash.

Alternatively, you can use assignment (including with autoincrement operators such as ++ and --) to modify the BagHash's contents.

my $n = BagHash.new: "a""b""c""c";
say $n.raku;             # OUTPUT: «("b"=>1,"a"=>1,"c"=>2).BagHash␤» 
$n<c>++;
say $n.raku;             # OUTPUT: «("b"=>1,"c"=>3,"a"=>1).BagHash␤» 
$n<b> -= 1;
say $n.raku;             # OUTPUT: «("a"=>1,"c"=>3).BagHash␤» 
$n{'a'} = 0;
say $n.raku;             # OUTPUT: «("c"=>3).BagHash␤»

Using either syntax, if you set the value of an item to zero or less than zero, the item will be removed from the BagHash.

Operators

See Operators with set semantics for a complete list of "set operators" applicable to, among other types, BagHash.

Examples:

my ($a$b= BagHash.new(224), BagHash.new(2334);
 
say $a (<) $b;   # OUTPUT: «False␤» 
say $a (<=) $b;  # OUTPUT: «False␤» 
say $a (^) $b;   # OUTPUT: «BagHash(3(2) 2)␤» 
say $a (+) $b;   # OUTPUT: «BagHash(2(3) 4(2) 3(2))␤» 
 
# Unicode versions: 
say $a  $b;  # OUTPUT: «False␤» 
say $a  $b;  # OUTPUT: «False␤» 
say $a  $b;  # OUTPUT: «BagHash(3(2) 2)␤» 
say $a  $b;  # OUTPUT: «BagHash(2(3) 4(2) 3(2))␤» 

Note on reverse and ordering.

BagHash inherits reverse from Any, however, Bags do not have an inherent order and you should not trust it returning a consistent output.

If you sort a BagHash, the result is a list of pairs, at which point reverse makes perfect sense:

my $a = BagHash.new(221834);
say $a;  # OUTPUT: «BagHash(18 2(2) 3 4)␤» 
 
say $a.sort;  # OUTPUT: «(2 => 2 3 => 1 4 => 1 18 => 1)␤» 
say $a.sort.reverse;  # OUTPUT: «(18 => 1 4 => 1 3 => 1 2 => 2)␤» 

method add

method add(BagHash: \to-add*%_ --> Nil)

When to-add is a single item, add inserts it into the BagHash or, if it was already present, increases its weight by 1. When to-add is a List, Array, Seq, or any other type that does the Iterator Role, add inserts each element of the Iterator into the SetHash or increments the weight of each element by 1.

Note: Added in version 2020.02.

method remove

method remove(BagHash: \to-remove*%_ --> Nil)

When to-remove is a single item, remove reduces the weight of that item by one. If this results in the item having a weight of 0, this removes the item from the BagHash. If the item is not present in the BagHash, remove has no effect. When to-remove is a List, Array, Seq, or any other type that does the Iterator Role, remove reduces the weight of each element by 1 and removes any items with the resulting weight of 0.

Note: Added in version 2020.02.

See Also

Sets, Bags, and Mixes