=encoding utf8

=head1 NAME

Sidef::Types::Hash::Hash - Associative hash table mapping keys to values with convenient collection methods.

=head1 DESCRIPTION

This class implements a hash (associative array) data type, providing key-value storage and various methods for hash manipulation, iteration, transformation, and set operations.

A Hash is an unordered collection of key-value pairs where each unique key maps to exactly one value. Keys are compared using structural equality.

=head1 SYNOPSIS

    # Creating hashes
    var hash = Hash(a => 1, b => 2, c => 3)
    var h = Hash(:foo, :bar)  # Keys with nil values

    # Accessing values
    say hash{:a}        #=> 1
    say hash.item(:b)   #=> 2

    # Setting values
    hash{:d} = 4
    hash.append(e => 5, f => 6)

    # Basic operations
    say hash.keys       #=> [:a, :b, :c, :d, :e, :f]
    say hash.values     #=> [1, 2, 3, 4, 5, 6]
    say hash.len        #=> 6
    say hash.has(:a)    #=> true

    # Iteration
    hash.each { |key, value|
        say "#{key} => #{value}"
    }

    # Transformation
    var doubled  = hash.map_v { _1 * 2 }
    var filtered = hash.grep { |k, v| v > 2 }

    # Set operations
    var h1 = Hash(a => 1, b => 2)
    var h2 = Hash(b => 3, c => 4)

    say (h1 | h2)   #=> Hash(a => 1, b => 2, c => 4)  # Union
    say (h1 & h2)   #=> Hash(b => 2)                  # Intersection
    say (h1 - h2)   #=> Hash(a => 1)                  # Difference
    say (h1 ^ h2)   #=> Hash(a => 1, c => 4)          # Symmetric difference

=head1 INHERITS

Inherits methods from L<Sidef::Object::Object>.

=head1 CONSTRUCTION

=head2 new

    Hash.new
    Hash.new(pairs...)

Creates and returns a new Hash object, optionally initialized with the given key-value pairs.

    var h1 = Hash.new
    var h2 = Hash.new(a => 1, b => 2)
    var h3 = Hash(a => 1, b => 2)  # Shorthand

Aliases: I<call>

=cut

=head1 ACCESSING VALUES

=head2 item

    hash.item(key)

Returns the value associated with the given key, or nil if the key does not exist.

    var h = Hash(a => 1, b => 2)

    say h.item(:a)  #=> 1
    say h.item(:z)  #=> nil

This is equivalent to subscript notation: C<hash{key}>.

=cut

=head2 items

    hash.items(keys...)

Returns an Array of values for the specified keys. Returns nil for keys that do not exist.

    var h = Hash(a => 1, b => 2, c => 3)
    say h.items(:a, :c, :z)  #=> [1, 3, nil]

=cut

=head2 fetch

    hash.fetch(key, default)

Returns the value for the given key if it exists; otherwise returns the default value.

    var h = Hash(a => 1, b => 2)

    say h.fetch(:a, 0)    #=> 1
    say h.fetch(:z, 0)    #=> 0
    say h.fetch(:z, nil)  #=> nil

Unlike direct subscript access (C<hash{key}>), C<fetch> allows you to distinguish between a missing key and a key whose value is nil.

=cut

=head2 dig

    hash.dig(key, keys...)

Recursively retrieves a nested value by traversing a chain of keys. Returns the value at the nested key path, or nil if any key in the path does not exist.

    var h = Hash(
        a => Hash(
            b => Hash(
                c => 42
            )
        )
    )

    say h.dig(:a, :b, :c)  #=> 42
    say h.dig(:a, :x)      #=> nil

This is safer than chaining subscript operations when intermediate keys may be absent.

=cut

=head1 TESTING AND COUNTING

=head2 has

    hash.has(key)

Returns true if the given key exists in the hash, false otherwise.

    var h = Hash(a => 1, b => nil)

    say h.has(:a)  #=> true
    say h.has(:b)  #=> true  (key exists even with nil value)
    say h.has(:z)  #=> false

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

=cut

=head2 is_empty

    hash.is_empty

Returns true if the hash contains no key-value pairs, false otherwise.

    var h1 = Hash()
    var h2 = Hash(a => 1)

    say h1.is_empty  #=> true
    say h2.is_empty  #=> false

=cut

=head2 same_keys

    hash.same_keys(other)

Returns true if the hash has exactly the same set of keys as C<other> (another hash), false otherwise. Values are not compared.

    var h1 = Hash(a => 1, b => 2)
    var h2 = Hash(a => 10, b => 20)
    var h3 = Hash(a => 1, c => 3)

    say h1.same_keys(h2)  #=> true
    say h1.same_keys(h3)  #=> false

=cut

=head2 len

    hash.len

Returns the number of key-value pairs in the hash.

    var h = Hash(a => 1, b => 2, c => 3)
    say h.len  #=> 3

Aliases: I<size>, I<length>

=cut

=head2 count

    hash.count(key)
    hash.count { |key, value| ... }

When called with a key, returns 1 if the key exists in the hash, 0 otherwise.

When called with a block, returns the number of key-value pairs for which the block returns true.

    var h = Hash(a => 1, b => 2, c => 3, d => 4)

    say h.count(:b)                #=> 1
    say h.count(:z)                #=> 0
    say h.count { |k, v| v > 2 }  #=> 2

=cut

=head2 count_by

    hash.count_by { |key, value| ... }

Returns the number of key-value pairs for which the block returns a true value. The block receives (key, value) as arguments.

    var h = Hash(a => 1, b => 2, c => 3, d => 4)
    say h.count_by { |k, v| v.is_even }  #=> 2

=cut

=head1 KEYS, VALUES, AND PAIRS

=head2 keys

    hash.keys

Returns an Array containing all keys in the hash.

    var h = Hash(a => 1, b => 2, c => 3)
    say h.keys  #=> [:a, :b, :c]

Note: Hash keys are unordered, so the order of keys in the returned array is not guaranteed.

=cut

=head2 values

    hash.values

Returns an Array containing all values in the hash.

    var h = Hash(a => 1, b => 2, c => 3)
    say h.values  #=> [1, 2, 3]

Note: Since hash keys are unordered, the order of values in the returned array is not guaranteed.

=cut

=head2 kv

    hash.kv

Returns an Array of Pair objects, where each Pair contains a key and its associated value.

    var h = Hash(a => 1, b => 2)
    say h.kv  #=> [Pair(:a, 1), Pair(:b, 2)]

    # Useful for iteration with destructuring
    h.kv.each { |pair|
        say "#{pair.key} => #{pair.value}"
    }

Aliases: I<to_a>, I<pairs>, I<to_array>

=cut

=head2 get_pair

    hash.get_pair(key)

Returns a Pair object containing the given key and its associated value.

    var h = Hash(a => 1, b => 2)
    var p = h.get_pair(:a)
    say p        #=> Pair(:a, 1)
    say p.key    #=> :a
    say p.value  #=> 1

Returns a Pair with a nil value if the key does not exist.

=cut

=head2 get_pairs

    hash.get_pairs(keys...)

Returns an Array of Pair objects for the specified keys.

    var h = Hash(a => 1, b => 2, c => 3)
    var pairs = h.get_pairs(:a, :c)
    say pairs  #=> [Pair(:a, 1), Pair(:c, 3)]

Missing keys produce Pair objects with nil values.

=cut

=head2 slice

    hash.slice(keys...)

Returns a new hash containing only the specified keys. Keys that exist in the original hash keep their values, and keys that do not exist are included in the result with nil values.

    var h = Hash(a => 1, b => 2, c => 3, d => 4)
    var subset = h.slice(:a, :c, :z)
    say subset  #=> Hash(a => 1, c => 3, z => nil)

Note: Missing keys are included with nil values. See C<lsel> for the Array/to_list variant of this operation.

=cut

=head2 lsel

    hash.lsel(keys_array)

Performs a linear selection: returns a new hash containing the keys specified in C<keys_array>, including missing keys with nil values. The main difference from C<slice> is the argument shape: C<lsel> takes an Array (or any object responding to C<to_list>), while C<slice> takes variadic keys.

    var h = Hash(a => 1, b => 2)
    var selected = h.lsel([:a, :c])
    say selected  #=> Hash(a => 1, c => nil)

The argument must be an Array (or any object responding to C<to_list>).

Aliases: I<linsel>, I<linear_selection>

=cut

=head1 MODIFYING THE HASH

=head2 append

    hash.append(pairs...)

Appends the given key-value pairs to the hash in-place and returns the modified hash. If a key already exists, its value is updated.

    var h = Hash(a => 1)
    h.append(b => 2, c => 3)
    say h  #=> Hash(a => 1, b => 2, c => 3)

=cut

=head2 clear

    hash.clear

Removes all key-value pairs from the hash in-place and returns the now-empty hash.

    var h = Hash(a => 1, b => 2)
    h.clear
    say h      #=> Hash()
    say h.len  #=> 0

=cut

=head2 delete

    hash.delete(keys...)

Removes the specified keys from the hash in-place and returns the deleted values as an Array (or a single value if only one key was given).

    var h = Hash(a => 1, b => 2, c => 3)
    say h.delete(:b)      #=> 2
    say h.delete(:a, :c)  #=> [1, 3]
    say h                 #=> Hash()

Aliases: I<remove>

=cut

=head2 delete_if

    hash.delete_if { |key, value| ... }

Deletes all key-value pairs for which the block returns true. The block receives (key, value) as arguments. Returns the modified hash.

    var h = Hash(a => 1, b => 2, c => 3, d => 4)
    h.delete_if { |k, v| v.is_even }
    say h  #=> Hash(a => 1, c => 3)

This is the in-place counterpart to C<grep>, which returns a new filtered hash instead of modifying the original.

=cut

=head2 set_keys

    hash.set_keys(keys...)

Sets the specified keys to nil in the hash in-place and returns the modified hash.

    var h = Hash(a => 1)
    h.set_keys(:b, :c)
    say h  #=> Hash(a => 1, b => nil, c => nil)

This is useful for pre-initializing keys before assigning their actual values.

=cut

=head2 merge_values

    hash.merge_values(other_hash)

Updates the values in the hash for keys that also exist in C<other_hash>. Only keys already present in the original hash are affected; no new keys are added. Modifies the hash in-place and returns it.

    var h1 = Hash(a => 1, b => 2, c => 3)
    var h2 = Hash(b => 20, c => 30, d => 40)

    h1.merge_values(h2)
    say h1  #=> Hash(a => 1, b => 20, c => 30)

Note: Key C<:d> from C<h2> was not added because it did not already exist in C<h1>.

=cut

=head1 ITERATION

=head2 each

    hash.each { |key, value| ... }
    hash.each

Iterates over each key-value pair in the hash, calling the block with (key, value). When called without a block, acts as a stateful iterator, returning one key-value pair per call (or nil when the iteration is exhausted).

    var h = Hash(a => 1, b => 2, c => 3)

    h.each { |k, v|
        say "#{k} => #{v}"
    }

    # Stateful iterator usage
    loop {
        var (k, v) = h.each \\ break
        say "#{k} => #{v}"
    }

Aliases: I<each_kv>, I<each_pair>

=cut

=head2 each_k

    hash.each_k { |key| ... }

Iterates over each key in the hash, calling the block with the key. Returns the hash.

    var h = Hash(a => 1, b => 2, c => 3)
    h.each_k { |k|
        say k
    }

Aliases: I<each_key>

=cut

=head2 each_v

    hash.each_v { |value| ... }

Iterates over each value in the hash, calling the block with the value. Returns the hash.

    var h = Hash(a => 1, b => 2, c => 3)
    h.each_v { |v|
        say v * 2
    }

Aliases: I<each_value>

=cut

=head1 TRANSFORMATION AND FILTERING

=head2 map

    hash.map { |key, value| ... }

Transforms the hash by applying the block to each key-value pair. The block receives (key, value) and should return a new key-value pair as a flat two-element list. Returns a new hash built from the collected pairs.

    var h = Hash(a => 1, b => 2)
    var mapped = h.map { |k, v|
        (k.uc, v * 10)
    }
    say mapped  #=> Hash(A => 10, B => 20)

Aliases: I<map_kv>

=cut

=head2 map_v

    hash.map_v { |key, value| ... }

Returns a new hash with the same keys but with values transformed by the block. The block receives (key, value) and should return the new value.

    var h = Hash(a => 1, b => 2, c => 3)
    var doubled = h.map_v { |k, v| v * 2 }
    say doubled  #=> Hash(a => 2, b => 4, c => 6)

    # The block may also take just the value
    var squared = h.map_v { _1 ** 2 }
    say squared  #=> Hash(a => 1, b => 4, c => 9)

Aliases: I<map_val>

=cut

=head2 grep

    hash.grep { |key, value| ... }

Returns a new hash containing only the key-value pairs for which the block returns true. The block receives (key, value) as arguments.

    var h = Hash(a => 1, b => 2, c => 3, d => 4)
    var evens = h.grep { |k, v| v.is_even }
    say evens  #=> Hash(b => 2, d => 4)

This is the non-destructive counterpart to C<delete_if>.

Aliases: I<grep_kv>, I<select>

=cut

=head2 grep_v

    hash.grep_v { |value| ... }

Returns a new hash containing only the key-value pairs for which the block (called with the value only) returns true.

    var h = Hash(a => 1, b => 2, c => 3, d => 4)
    var evens = h.grep_v { .is_even }
    say evens  #=> Hash(b => 2, d => 4)

Aliases: I<grep_val>

=cut

=head2 collect

    hash.collect { |key, value| ... }

Iterates over each key-value pair, calling the block with (key, value), and collects all return values into a new Array.

    var h = Hash(a => 1, b => 2, c => 3)
    var result = h.collect { |k, v|
        "#{k}=#{v}"
    }
    say result  #=> ["a=1", "b=2", "c=3"]

Aliases: I<collect_kv>

=cut

=head2 flip

    hash.flip

Returns a new hash with keys and values swapped. The original values become the new keys, and the original keys become the new values.

    var h = Hash(a => 1, b => 2, c => 3)
    say h.flip  #=> Hash(1 => :a, 2 => :b, 3 => :c)

Note: If multiple keys share the same value, only one key-value pair will survive in the flipped hash (the last one encountered wins).

Aliases: I<invert>, I<reverse>

=cut

=head1 SORTING

=head2 sort

    hash.sort
    hash.sort { |a, b| ... }

Returns an Array of Pairs sorted by key. When no block is given, keys are sorted in lexicographic order. When a block is provided, it is used as a comparator for the keys.

    var h = Hash(c => 3, a => 1, b => 2)
    say h.sort  #=> [Pair(:a, 1), Pair(:b, 2), Pair(:c, 3)]

    # Custom comparator (reverse order)
    var rev = h.sort { |a, b| b <=> a }
    say rev  #=> [Pair(:c, 3), Pair(:b, 2), Pair(:a, 1)]

=cut

=head2 sort_by

    hash.sort_by { |key, value| ... }

Returns an Array of Pairs sorted by the values returned by the block. The block receives (key, value) as arguments.

    var h = Hash(a => 3, b => 1, c => 2)
    var sorted = h.sort_by { |k, v| v }
    say sorted  #=> [Pair(:b, 1), Pair(:c, 2), Pair(:a, 3)]

    # Sort by key length
    var by_len = h.sort_by { |k, v| k.len }

=cut

=head2 min_by

    hash.min_by { |key, value| ... }

Returns the key-value Pair for which the block returns the minimum value. The block receives (key, value) as arguments.

    var h = Hash(a => 3, b => 1, c => 5, d => 2)
    var min_pair = h.min_by { |k, v| v }
    say min_pair  #=> Pair(:b, 1)

=cut

=head2 max_by

    hash.max_by { |key, value| ... }

Returns the key-value Pair for which the block returns the maximum value. The block receives (key, value) as arguments.

    var h = Hash(a => 3, b => 1, c => 5, d => 2)
    var max_pair = h.max_by { |k, v| v }
    say max_pair  #=> Pair(:c, 5)

=cut

=head1 OPERATORS

=head2 ==

    hash1 == hash2

Returns true if both hashes have the same keys with equal values (structural equality).

    var h1 = Hash(a => 1, b => 2)
    var h2 = Hash(b => 2, a => 1)
    say (h1 == h2)  #=> true

Aliases: I<eq>

=cut

=head2 ≠

    hash1 ≠ hash2

Returns true if the two hashes are not structurally equal (different keys or different values).

    var h1 = Hash(a => 1)
    var h2 = Hash(a => 2)
    say (h1 ≠ h2)  #=> true

Aliases: I<!=>, I<ne>

=cut

=head2 +

    hash1 + hash2

Returns a new hash containing all key-value pairs from both hashes. If a key exists in both, the value from the second hash takes precedence (right-hand side wins).

    var h1 = Hash(a => 1, b => 2)
    var h2 = Hash(b => 20, c => 30)
    say (h1 + h2)  #=> Hash(a => 1, b => 20, c => 30)

Aliases: I<merge>, I<concat>

=cut

=head2 -

    hash1 - hash2

Returns the set difference of two hashes. The result contains only keys that exist in the first hash but not in the second.

    var h1 = Hash(a => 1, b => 2, c => 3)
    var h2 = Hash(b => 20, d => 40)
    say (h1 - h2)  #=> Hash(a => 1, c => 3)

Aliases: I<sub>, I<diff>, I<difference>

=cut

=head2 &

    hash1 & hash2

Returns the intersection of two hashes. The result contains only keys that exist in both hashes, with values taken from the first hash.

    var h1 = Hash(a => 1, b => 2, c => 3)
    var h2 = Hash(b => 20, c => 30, d => 40)
    say (h1 & h2)  #=> Hash(b => 2, c => 3)

Aliases: I<and>, I<intersection>

=cut

=head2 |

    hash1 | hash2

Returns the union of two hashes. The result contains all keys from both hashes; for keys present in both, the value from the first hash takes precedence (left-hand side wins).

    var h1 = Hash(a => 1, b => 2)
    var h2 = Hash(b => 20, c => 30)
    say (h1 | h2)  #=> Hash(a => 1, b => 2, c => 30)

Note the difference from C<+> (merge): union keeps the left-hand value for common keys, while merge keeps the right-hand value.

Aliases: I<or>, I<union>

=cut

=head2 ^

    hash1 ^ hash2

Returns the symmetric difference of two hashes. The result contains keys that exist in either hash but not in both.

    var h1 = Hash(a => 1, b => 2)
    var h2 = Hash(b => 20, c => 30)
    say (h1 ^ h2)  #=> Hash(a => 1, c => 30)

Aliases: I<xor>, I<symdiff>, I<symmetric_difference>

=cut

=head2 ...

    hash...

Returns the hash as a flat list of alternating keys and values. This is useful for unpacking a hash into an argument list or for converting it to other data structures.

    var h = Hash(a => 1, b => 2)
    say h...  #=> [:a, 1, :b, 2]

Aliases: I<to_list>

=cut

=head1 CONVERSION

=head2 dump

    hash.dump

Returns a string representation of the hash.

    var h = Hash(a => 1, b => 2)
    say h.dump  #=> "Hash(a => 1, b => 2)"

Aliases: I<to_s>, I<to_str>

=cut

=head2 to_set

    hash.to_set

Converts the hash's keys to a Set object. The hash values are ignored.

    var h = Hash(a => 1, b => 2, c => 3)
    var set = h.to_set
    say set  #=> Set(:a, :b, :c)

=cut

=head2 to_bag

    hash.to_bag

Converts the hash's keys to a Bag object. The hash values are ignored.

    var h = Hash(a => 1, b => 2, c => 3)
    var bag = h.to_bag
    say bag  #=> Bag(:a, :b, :c)

=cut

=head2 as_tree

    hash.as_tree(root)

Returns the hash as a tree structure represented as nested Pairs, using the given C<root> as the tree's root label. Nested hashes are recursively converted to sub-trees. Non-hash leaf values are represented as Pairs with an empty array C<[]>; only the key hierarchy is preserved, not the leaf values themselves.

    var h = Hash(
        a => Hash(b => 1),
        c => 2
    )
    say h.as_tree(:root)
    #=> Pair(:root, [Pair(:a, [Pair(:b, [])]), Pair(:c, [])])

This is useful for visualizing or processing hashes as hierarchical data structures.

=cut

=head1 EXAMPLES

=head2 Frequency Counting

    var text = "hello world"
    var freq = Hash()

    text.each_char { |char|
        freq{char} := 0
        freq{char}++
    }

    say freq  #=> Hash(h => 1, e => 1, l => 3, ...)

=head2 Grouping Data

    var people = [
        Hash(name => "Alice", age => 30),
        Hash(name => "Bob",   age => 25),
        Hash(name => "Carol", age => 30)
    ]

    var by_age = Hash()
    people.each { |person|
        var age = person{:age}
        by_age{age} := []
        by_age{age} << person
    }

=head2 Transforming Data

    var prices = Hash(apple => 1.50, banana => 0.75, orange => 2.00)

    # Apply a discount
    var discounted = prices.map_v { _1 * 0.9 }

    # Filter expensive items
    var expensive = prices.grep { |k, v| v > 1.00 }

=head2 Merging with Custom Logic

    var defaults = Hash(color => :red, size => :medium, count => 1)
    var options  = Hash(size => :large)

    # Keep defaults for missing keys
    var config = defaults + options
    say config  #=> Hash(color => :red, size => :large, count => 1)

=head1 SEE ALSO

L<Sidef::Types::Array::Array>, L<Sidef::Types::Set::Set>, L<Sidef::Types::Bag::Bag>

=cut
