=encoding utf8

=head1 NAME

Sidef::Types::Set::Bag - A multiset (bag) data structure for Sidef

=head1 DESCRIPTION

This class implements a multiset (also known as a bag): a collection that allows duplicate elements and tracks the frequency (count) of each unique element. Unlike a regular L<Sidef::Types::Set::Set>, where each element appears at most once, a Bag keeps a count alongside each distinct value, making it useful for frequency analysis and operations that need to preserve multiplicity. It's a subclass of C<Set>.

Internally, a Bag maps each element's serialized key (see L<Sidef::Types::Set::Set/DESCRIPTION> for how elements are compared/deduplicated) to a small record holding both the original value and its current count.

=head1 SYNOPSIS

    var a = Bag(1, 1, 2, 3)
    var b = Bag(1, 4, 3, 5)

    say(a | b)     #=> Bag(1, 1, 2, 3, 4, 5)
    say(a ^ b)     #=> Bag(1, 2, 4, 5)
    say(a & b)     #=> Bag(1, 3)
    say(a - b)     #=> Bag(1, 2)
    say(a + b)     #=> Bag(1, 1, 1, 2, 3, 3, 4, 5)

    say(a.count(1)) #=> 2
    say(a.elems)    #=> 3
    say(a.len)      #=> 4

    say(a.keys)     #=> [1, 2, 3]
    say(a.freq)     #=> [[1, 2], [2, 1], [3, 1]]

=head1 INHERITS

Inherits methods from L<Sidef::Types::Set::Set>.

=head1 CONSTRUCTION

=head2 new

    Bag(*elements)
    Bag.new(*elements)

Creates a new bag containing C<elements>, tallying a count for each distinct value. With no arguments, creates an empty bag. Note that this constructor always blesses into the literal C<Sidef::Types::Set::Bag> package itself, regardless of the invocant -- it is not polymorphic.

    var bag  = Bag.new
    var bag2 = Bag.new(1, 2, 2, 3)   #=> Bag(1, 2, 2, 3)

Aliases: I<call>

=cut

=head1 SET ALGEBRA

For L</union>, L</intersection>, L</difference>, and L</symmetric_difference>, the right-hand operand doesn't have to already be a C<Bag> -- if it isn't one, it's first converted with C<.to_bag>. L</concat> (the C<+> operator) is the one exception: if C<other> isn't the exact same class, it's added as a single new element (incrementing its own count by 1) rather than merged.

=head2 concat

    self.concat(other)
    self + other

If C<other> is also a C<Bag> (of the exact same class), returns a new bag where every shared element's count is the I<sum> of its counts in both bags (and every element only present in one side keeps its original count). If C<other> is anything else, it is added as a single new element instead.

    say(Bag(1, 2) + Bag(2, 3))     #=> Bag(1, 2, 2, 3)

Aliases: operator C<+>

=cut

=head2 union

    self.union(other)
    self | other

Returns a new bag where each element's count is the I<maximum> of its counts in C<self> and C<other>.

    say(Bag(1, 1, 2) | Bag(1, 3, 3))   #=> Bag(1, 1, 2, 3, 3)

Aliases: I<or>, operator C<|>, operator C<∪>

=cut

=head2 intersection

    self.intersection(other)
    self & other

Returns a new bag containing only the elements present in both C<self> and C<other>, with each count being the I<minimum> of the two.

    say(Bag(1, 1, 2, 3) & Bag(1, 2, 2, 4))   #=> Bag(1, 2)

Aliases: I<and>, operator C<&>, operator C<∩>

=cut

=head2 difference

    self.difference(other)
    self - other

Returns a new bag where each element of C<self> has its count reduced by its count in C<other>; an element is dropped entirely once its count would be zero or negative.

    say(Bag(1, 1, 2, 3) - Bag(1, 3))   #=> Bag(1, 2)

Aliases: I<sub>, I<diff>, operator C<->, operator C<∖>

=cut

=head2 symmetric_difference

    self.symmetric_difference(other)
    self ^ other

Returns a new bag with, for each element, the absolute difference between its counts in C<self> and C<other> (an element present in only one bag keeps its full count there; an element with equal counts on both sides is dropped entirely).

    say(Bag(1, 1, 2) ^ Bag(1, 3))   #=> Bag(1, 2, 3)

Aliases: I<xor>, I<symdiff>, operator C<^>

=cut

=head1 MEMBERSHIP AND COMPARISON

=head2 has

    self.has(obj)
    self ∋ obj

Returns C<true> if C<obj> is an element of the bag (with a count of at least 1), regardless of its actual count, otherwise C<false>.

    say(Bag(1, 2, 3) ∋ 2)   #=> true
    say(Bag(1, 2, 3) ∋ 5)   #=> false

Aliases: I<haskey>, I<has_key>, I<exists>, I<include>, I<includes>, I<contain>, I<contains>, operator C<∋>

=cut

=head2 ∌

    self ∌ obj

Returns C<true> if C<obj> is I<not> an element of the bag -- the logical negation of L</has>.

    say(Bag(1, 2, 3) ∌ 5)   #=> true
    say(Bag(1, 2, 3) ∌ 2)   #=> false

=cut

=head2 contains_all

    self.contains_all(*objects)

Returns C<true> if the bag contains every one of C<objects> with I<at least> as high a count as that object appears among C<objects> itself. In other words, this is multiplicity-aware: asking for C<1> twice requires the bag to contain at least two C<1>s, not just one.

    say(Bag(1, 2, 3, 3).contains_all(1, 3))     #=> true
    say(Bag(1, 2, 3).contains_all(1, 4))        #=> false
    say(Bag(1, 2, 3).contains_all(1, 1))        #=> false  # only one 1 in the bag

=cut

=head2 is_subset

    self.is_subset(other)
    self <= other

Returns C<true> if every element of C<self> also appears in C<other> with a count at least as high (C<other> is converted with C<.to_bag> first if it isn't already a bag).

    say(Bag(1, 2) <= Bag(1, 2, 2, 3))   #=> true

Aliases: operator C<< <= >>, operator C<≤>, operator C<⊆>

=cut

=head2 is_superset

    self.is_superset(other)
    self >= other

Returns C<true> if every element of C<other> also appears in C<self> with a count at least as high (C<other> is converted with C<.to_bag> first if it isn't already a bag).

    say(Bag(1, 2, 2, 3) >= Bag(1, 2))   #=> true

Aliases: operator C<< >= >>, operator C<≥>, operator C<⊇>

=cut

=head2 eq

    self.eq(other)
    self ≡ other

Returns C<true> if C<self> and C<other> contain exactly the same elements with exactly the same counts. Handles circular references safely.

    say(Bag(1, 2, 2) ≡ Bag(2, 1, 2))   #=> true

Aliases: operator C<≡>, operator C<==>

=cut

=head2 ne

    self.ne(other)
    self ≠ other

Returns the logical negation of L</eq>.

    say(Bag(1, 2) ≠ Bag(1, 2, 2))   #=> true

Aliases: operator C<≠>, operator C<!=>

=cut

=head1 ADDING ELEMENTS

=head2 append

    self.append(*objects)
    self << obj

Adds every one of C<objects> to the bag, incrementing each one's count by 1, and returns C<self>.

    var bag = Bag(1, 2)
    bag << 2
    say(bag)             #=> Bag(1, 2, 2)

Aliases: I<add>, I<push>, operator C<< << >>

=cut

=head2 replace_pair

    self.replace_pair(obj, n)

Sets C<obj>'s count to exactly C<n> (overwriting whatever it was before, or creating it if it wasn't present), and returns C<self>. Unlike most other mutating methods here, this does I<not> treat a non-positive C<n> specially -- setting a count to C<0> or less still leaves C<obj> as a present key (so L</has> and L</elems> still count it), just contributing nothing (or a negative amount) if the bag is later expanded or summed.

    var bag = Bag(1, 2, 2)
    bag.replace_pair(2, 5)
    say(bag)             #=> Bag(1, 2, 2, 2, 2, 2)

Aliases: I<set_kv>, I<update_kv>, I<update_pair>

=cut

=head2 replace_pairs

    self.replace_pairs(*pairs)

Calls L</replace_pair> once for each C<(obj, n)> pair given (flattened, e.g. C<obj1, n1, obj2, n2, ...>), and returns C<self>.

    var bag = Bag(1)
    bag.replace_pairs(1, 3, 2, 2)
    say(bag)             #=> Bag(1, 1, 1, 2, 2)

Aliases: I<set_kvs>, I<update_kvs>, I<update_pairs>

=cut

=head2 add_pair

    self.add_pair(obj, n)

Increments C<obj>'s count by C<n> (or sets it to C<n> if C<obj> wasn't already present), and returns C<self>. Like L</replace_pair>, a non-positive C<n> is not treated specially.

    var bag = Bag(1, 2)
    bag.add_pair(2, 3)
    say(bag)             #=> Bag(1, 2, 2, 2, 2)

Aliases: I<add_kv>, I<push_kv>, I<append_kv>, I<push_pair>, I<append_pair>

=cut

=head2 add_pairs

    self.add_pairs(*pairs)

Calls L</add_pair> once for each C<(obj, n)> pair given (flattened), and returns C<self>.

    var bag = Bag(1)
    bag.add_pairs(2, 3, 3, 2)
    say(bag)             #=> Bag(1, 2, 2, 2, 3, 3)

Aliases: I<add_kvs>, I<push_kvs>, I<append_kvs>, I<push_pairs>, I<append_pairs>

=cut

=head1 REMOVING ELEMENTS

=head2 delete

    self.delete(*objects)

Decrements each one of C<objects> by 1 (removing it entirely once its count reaches 0), and returns the list of I<removed values> -- not the bag itself.

    var bag = Bag(1, 1, 2, 3)
    say(bag.delete(1, 2))    #=> (1, 2)
    say(bag)                 #=> Bag(1, 3)

Aliases: I<remove>, I<discard>

=cut

=head2 delete_all

    self.delete_all(*objects)

Removes every one of C<objects> from the bag completely, regardless of count, and returns the removed occurrences (each value repeated according to its former count) -- not the bag itself.

    var bag = Bag(1, 1, 2, 3)
    say(bag.delete_all(1, 2))   #=> (1, 1, 2)
    say(bag)                    #=> Bag(3)

Aliases: I<remove_all>, I<discard_all>

=cut

=head2 delete_key

    self.delete_key(obj)

Removes C<obj> from the bag completely, regardless of count, and returns the value that was removed (or C<nil> if it wasn't present) -- not the bag itself.

    var bag = Bag(1, 1, 2, 3)
    say(bag.delete_key(1))   #=> 1
    say(bag)                 #=> Bag(2, 3)

Aliases: I<remove_key>, I<discard_key>

=cut

=head2 pop

    self.pop

Removes and returns an arbitrary element from the bag, decrementing its count by 1 (removing it entirely if its count was 1). Returns C<nil> if the bag is empty.

    var bag = Bag(1, 2, 2)
    say(bag.pop)         # some element of the bag

=cut

=head2 shift

    self.shift

Removes and returns an arbitrary element from the bag, decrementing its count by 1 (removing it entirely if its count was 1). Returns C<nil> if the bag is empty. As with L<Sidef::Types::Set::Set/shift>, since a bag's keys have no defined order, this behaves identically to L</pop>.

    var bag = Bag(1, 2, 2)
    say(bag.shift)       # some element of the bag

=cut

=head2 delete_if

    self.delete_if(block)

Removes every element (with I<all> of its occurrences) for which C<block> returns a truthy value, and returns C<self>.

    var bag = Bag(1, 2, 2, 3, 4)
    bag.delete_if {|n| n.is_even }
    say(bag)             #=> Bag(1, 3)

=cut

=head2 delete_first_if

    self.delete_first_if(block)

Finds the first element (in the bag's own unordered iteration order) for which C<block> returns a truthy value, and removes just I<one> occurrence of it (decrementing its count by 1, or removing it entirely if its count was 1). Returns C<self>.

    var bag = Bag(1, 2, 3, 4)
    bag.delete_first_if {|n| n > 2 }
    say(bag)             #=> one of 3 or 4 removed, e.g. Bag(1, 2, 4)

=cut

=head1 ITERATION

=head2 each

    self.each(block)

Calls C<block> once for I<every occurrence> of every element (respecting multiplicity), and returns C<self>.

    Bag(1, 2, 2).each {|n| say(n) }
    # Prints: 1, 2, 2 (in some order)

=cut

=head2 each_2d

    self.each_2d(block)

Like L<Sidef::Types::Set::Set/each_2d>: iterates over a bag I<whose elements are themselves array-like>, spreading each element's own contents as separate arguments to C<block>, once per occurrence (respecting multiplicity). Returns C<self>.

    var pairs = Bag([1, "a"], [1, "a"], [2, "b"])
    pairs.each_2d {|k, v| say("#{k}: #{v}") }
    # Prints "1: a" twice and "2: b" once, in some order

=cut

=head2 each_kv

    self.each_kv(block)

Calls C<block(value, count)> once per I<distinct> element (not once per occurrence), passing the element and its total count together. Returns C<self>.

    Bag(1, 2, 2, 3).each_kv {|k, v| say("#{k} appears #{v} times") }

=cut

=head2 iter

    self.iter

Returns an iterator (a C<Block>) over the bag's I<distinct> elements (not its individual occurrences): each call to C<.run> returns a 2-element C<Array> of C<[value, count]>, or C<nil> once exhausted.

    var it = Bag(1, 2, 2).iter
    say(it.run)          #=> [1, 1]  (in some order)
    say(it.run)          #=> [2, 2]

=cut

=head1 TRANSFORMATION AND FILTERING

=head2 map

    self.map(block)

Returns a new bag where each I<distinct> element is replaced by the result(s) of calling C<block> on it once; the resulting element's count is increased by the I<original> element's count (so if C<block> returns more than one value for a given input, each one of them gets the I<full> original count, not a split of it). If C<block> is omitted, elements are kept as-is.

    say(Bag(1, 2, 2).map {|n| n * 2 })   #=> Bag(2, 4, 4)

=cut

=head2 map_2d

    self.map_2d(block)

Like L</map>, but for a bag I<whose elements are themselves array-like> -- each element's own contents are spread as separate arguments to C<block>.

    var pairs = Bag([1, "a"], [2, "b"])
    say(pairs.map_2d {|k, v| [k * 2, v] })

=cut

=head2 map_kv

    self.map_kv(block)

Calls C<block(value, count)> once per distinct element, expecting it to return a flattened list of zero or more C<(new_value, new_count)> pairs. Each returned pair I<sets> (not adds to) C<new_value>'s count in the result -- if two different original elements happen to produce the same C<new_value>, the later one wins rather than the counts being summed. A pair whose C<new_count> is zero or negative is silently dropped.

    say(Bag(1, 2).map_kv {|k, v| [k + 1, v * 2] })   #=> Bag(2, 2, 3, 3)

=cut

=head2 collect

    self.collect(block)

Calls C<block> once per I<distinct> element and repeats each result according to that element's original count, collecting everything into a plain C<Array> -- I<not> a new C<Bag>. Because C<block> only runs once per distinct element, all of that element's repeated copies in the result share the exact same computed value (relevant if C<block> is non-deterministic).

    say(Bag(1, 2, 2).collect {|n| n * 2 })   #=> [2, 4, 4]  (an Array, order may vary)

=cut

=head2 grep

    self.grep(block)

Returns a new bag keeping only the distinct elements for which C<block> returns a truthy value, with their counts unchanged. If C<block> is omitted, elements are kept based on their own truthiness.

    say(Bag(1, 2, 2, 3, 4).grep {|n| n.is_even })   #=> Bag(2, 2, 4)

Aliases: I<select>

=cut

=head2 grep_2d

    self.grep_2d(block)

Like L</grep>, but for a bag I<whose elements are themselves array-like> -- each element's own contents are spread as separate arguments to C<block>.

    var pairs = Bag([10, "a"], [20, "b"], [5, "c"])
    say(pairs.grep_2d {|n, label| n > 8 })   #=> Bag([10, "a"], [20, "b"])

=cut

=head2 grep_kv

    self.grep_kv(block)

Like L</grep>, but calls C<block(value, count)> for each distinct element, passing its count alongside it.

    say(Bag(1, 1, 2, 2, 2, 3).grep_kv {|k, v| v >= k })   #=> Bag(1, 1, 2, 2, 2)

=cut

=head1 AGGREGATION AND PREDICATES

=head2 count

    self.count(obj)
    self.count(block)

If given a plain value, returns C<obj>'s current count (C<0> if not present) -- unlike L<Sidef::Types::Set::Set/count>, this is a true frequency, not just membership. If given a block, delegates entirely to L</count_by>.

    say(Bag(1, 1, 2, 3).count(1))   #=> 2
    say(Bag(1, 1, 2, 3).count(5))   #=> 0

Aliases: I<get>

=cut

=head2 count_by

    self.count_by(block)

Returns the total count (summed across matching I<distinct> elements, respecting each one's own multiplicity) of every element for which C<block> returns a truthy value. This is a single C<Number>, not a grouped tally.

    say(Bag(1, 2, 3, 4).count_by {|n| n.is_even })   #=> 2

=cut

=head2 min

    self.min

Returns the minimum element in the bag, ignoring counts entirely (equivalent to looking only at the distinct keys).

    say(Bag(5, 1, 3, 3).min)   #=> 1

=cut

=head2 min_by

    self.min_by(block)

Returns the distinct element for which C<block> returns the smallest value, ignoring counts.

    say(Bag(1, 2, 3).min_by {|n| -n })   #=> 3

=cut

=head2 max

    self.max

Returns the maximum element in the bag, ignoring counts entirely.

    say(Bag(1, 5, 3, 3).max)   #=> 5

=cut

=head2 max_by

    self.max_by(block)

Returns the distinct element for which C<block> returns the largest value, ignoring counts.

    say(Bag(1, 2, 3).max_by {|n| -n })   #=> 1

=cut

=head1 FREQUENCY INSPECTION

=head2 freq

    self.freq

Returns an C<Array> of plain C<[value, count]> 2-element arrays, one per distinct element. (Compare with L</pairs>, which returns the same information as proper C<Pair> objects instead.)

    say(Bag(1, 1, 2, 3).freq)   #=> [[1, 2], [2, 1], [3, 1]]

=cut

=head2 most_common

    self.most_common(n)

Returns an C<Array> of the C<n> distinct elements with the highest counts, sorted by count in descending order, as C<[value, count]> pairs.

    say(Bag(1, 2, 2, 3, 3, 3).most_common(2))   #=> [[3, 3], [2, 2]]

Aliases: I<top>

=cut

=head2 uniq

    self.uniq

Returns a new bag with every element's count reset to exactly 1 (i.e. behaving like the underlying set, but remaining a C<Bag>).

    say(Bag(1, 1, 2, 3, 3).uniq)   #=> Bag(1, 2, 3)

Aliases: I<unique>

=cut

=head2 elems

    self.elems

Returns the number of I<distinct> elements in the bag (ignoring their counts).

    say(Bag(1, 1, 2, 3).elems)   #=> 3

Aliases: I<keys_len>

=cut

=head2 length

    self.length

Returns the total number of elements in the bag, I<counting multiplicities> (the sum of every element's count).

    say(Bag(1, 1, 2, 3).length)   #=> 4

Aliases: I<len>, I<size>

=cut

=head1 SORTING AND CONVERSION

=head2 sort

    self.sort(block)

Expands the bag (respecting counts, via L</to_a>) and returns the result sorted -- in natural order, or by C<block> if given.

    say(Bag(3, 1, 2, 2).sort)                    #=> [1, 2, 2, 3]
    say(Bag(3, 1, 2).sort {|a, b| b <=> a })      #=> [3, 2, 1]

=cut

=head2 sort_by

    self.sort_by(block)

Expands the bag (respecting counts) and returns the result sorted by the value C<block> returns for each element.

    say(Bag(1, 2, 3, 3).sort_by {|n| -n })   #=> [3, 3, 2, 1]

=cut

=head2 to_a

    self.to_a

Returns an C<Array> with every element of the bag, each one repeated according to its count.

    say(Bag(1, 2, 2, 3).to_a)   #=> [1, 2, 2, 3]

Aliases: I<expand>, I<to_array>

=cut

=head2 to_list

    self...

Returns every element of the bag as a plain list (not wrapped in an C<Array>), each one repeated according to its count -- the list-returning counterpart to L</to_a>.

    var bag = Bag(1, 2, 2)
    var list = bag...
    say([list])          #=> [1, 2, 2]  (order may vary)

Aliases: operator C<...>

=cut

=head2 keys

    self.keys

Returns an C<Array> of the bag's distinct elements, without their counts.

    say(Bag(1, 1, 2, 3).keys)   #=> [1, 2, 3]

=cut

=head2 values

    self.values

Returns an C<Array> of the counts for each distinct element (in the same order as L</keys>, so C<keys[i]> and C<values[i]> correspond to each other).

    say(Bag(1, 1, 2, 3).values)   #=> [2, 1, 1]

=cut

=head2 pairs

    self.pairs

Returns an C<Array> of C<Pair> objects, one per distinct element, pairing each value with its count. (Compare with L</freq>, which returns the same information as plain 2-element arrays instead.)

    say(Bag(1, 1, 2).pairs)   #=> [Pair(1, 2), Pair(2, 1)]

Aliases: I<kv>

=cut

=head2 to_set

    self.to_set

Converts the bag to a plain C<Set>, discarding all count information and keeping only the distinct elements.

    say(Bag(1, 1, 2, 3).to_set)   #=> Set(1, 2, 3)

=cut

=head2 to_bag

    self.to_bag

Returns C<self> unchanged (a C<Bag> is already a bag).

    var bag = Bag(1, 2, 3)
    say(bag.to_bag == bag)   #=> true

=cut

=head2 clone

    self.clone

Returns a shallow copy of the bag: a new bag with the same distinct elements and counts. If an element's own value is itself a reference (e.g. an Array), that nested reference is shared between the original and the clone, not copied.

    var a = Bag(1, 2, 3)
    var b = a.clone
    b << 4
    say(a)   #=> Bag(1, 2, 3)
    say(b)   #=> Bag(1, 2, 3, 4)

=cut

=head2 join

    self.join(sep)

Expands the bag (respecting counts, via L</to_a>) and joins the elements into a C<String>, separated by C<sep>.

    say(Bag(1, 2, 2, 3).join(", "))   #=> "1, 2, 2, 3"

=cut

=head1 INSPECTION

=head2 dump

    self.dump

Returns a C<String> representation of the bag, in the format C<Bag(element, element, ...)>, with each element repeated according to its count. Each element is rendered by calling its own C<dump> method if it has one (falling back to plain stringification, or C<nil>, otherwise), and a circular reference back to the bag itself is shown as an abbreviated placeholder rather than expanded infinitely.

    say(Bag(1, 2, 2).dump)   #=> "Bag(1, 2, 2)"

=cut

=head2 get_value

    self.get_value

Returns the bag's contents as a plain Perl array reference, with every nested Sidef object recursively unwrapped to its own raw underlying value (via its own C<get_value>) and repeated according to its count. Circular references are handled by reusing the same array reference for a bag already seen during the recursion. This is a low-level method, mainly useful for interop rather than everyday Sidef code.

=cut

=head1 EXAMPLES

=head2 Word frequency counter

    var text = "the quick brown fox jumps over the lazy dog the fox runs"
    var bag = Bag(text.split(' ')...)

    say(bag.most_common(2))     #=> [["the", 3], ["fox", 2]]
    say(bag.count("the"))       #=> 3
    say(bag.elems)              #=> 9   # distinct words
    say(bag.len)                #=> 12  # total words

=head2 Building up counts incrementally

    var votes = Bag()
    votes.add_pair("cats", 5)
    votes.add_pair("dogs", 3)
    votes.add_pair("cats", 2)     # increments, doesn't overwrite

    say(votes.freq)              #=> [["cats", 7], ["dogs", 3]]
    say(votes.most_common(1))    #=> [["cats", 7]]

=head2 Multiset algebra for inventory tracking

    var have = Bag("apple", "apple", "banana", "banana", "banana")
    var used = Bag("apple", "banana")

    var remaining = have - used
    say(remaining)                #=> Bag(apple, banana, banana)

    var restocked = remaining | Bag("apple", "apple")
    say(restocked)                #=> Bag(apple, apple, banana, banana)

=head1 SEE ALSO

L<Sidef::Types::Set::Set>

=cut
