Created
December 7, 2021 07:22
-
-
Save estum/d07672135ecf801b85dcd8aec4886d8c to your computer and use it in GitHub Desktop.
Map
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
# frozen_string_literal: true | |
require 'concurrent/map' | |
# {include:file:concurrent/map.rb} | |
class Map < Concurrent::Map | |
HashBuilder = proc do |hsh, default = nil, default_proc = nil| | |
hsh = ::Hash[hsh] | |
if !default.nil? | |
hsh.default = default | |
elsif !default_proc.nil? | |
hsh.default_proc = default_proc | |
end | |
hsh | |
end.freeze | |
ProcBuilder = proc { |m, *rest| m.get(*rest) }.curry(2).freeze | |
DefaultValueProc = proc { |h| h.default_value }.freeze | |
MapHash = begin | |
key_itself = proc { |_, k| k }.freeze | |
::Hash.new(&key_itself).freeze | |
end | |
private_constant :MapHash | |
# @!attribute [rw] default_value | |
# @return [Object, nil] | |
attr_accessor :default_value | |
# @!attribute [rw] default_proc | |
# @return [Proc, nil] | |
attr_accessor :default_proc | |
# Creates hash with default proc that returns a missing key | |
# @param hash [::Hash, nil] | |
# @return [::Hash] The new hash from the given with set default proc | |
def self.Hash(hash) | |
MapHash.merge(hash.to_h) | |
end | |
# @overload wrap(map) | |
# bypasses the given {Map} object | |
# @param map [Map] | |
# | |
# @overload wrap(any) | |
# @param hash [::Hash, nil] | |
# | |
# @return [Map] | |
# @see Map.new | |
def self.wrap(map_or_any) | |
self === map_or_any ? map_or_any : self[map_or_any] | |
end | |
# Makes a map from the given hash. If option `*` is given and | |
# it is not a proc, sets it as default value | |
# and {DefaultValueProc} as default proc | |
# | |
# @see Map.new | |
# @param hash [::Hash, nil] | |
# @option hash [Proc, Object, nil] :* Default proc or value | |
# @return [Map] | |
def self.[](hash = nil) | |
return new(hash) unless Hash === hash && !hash[:*].nil? | |
hash = hash.dup | |
star = hash.delete(:*) | |
if Proc === star | |
new(hash, &star) | |
else | |
new(hash, star, &DefaultValueProc) | |
end | |
end | |
# @param hash [::Hash, nil] A source hash | |
# @param default_value [Object, nil] | |
# @yield Default proc | |
# @yieldreturn [Object, nil] the default value | |
# @return [Map] | |
def self.new(hash = nil, default_value = nil, &block) | |
map = allocate | |
map.default_proc = block.to_proc if block_given? | |
map.default_value = default_value if default_value | |
map.marshal_load(hash) | |
map | |
end | |
# Creates empty map with default proc, that | |
# computes value for a missing key using the given proc. | |
# @yield [key] when key is absent | |
# @yieldparam [Any] key | |
# @yieldreturn [Any] value for an absent key to be stored then. | |
# @return [Map] with set {#default_proc} | |
def self.factory | |
new { |map, key| map.compute_if_absent(key) { yield(key) } } | |
end | |
# Populates the map with the given list of keys, computing values by yielding | |
# the block. | |
# | |
# @overload populate(map, *keys, &block) | |
# Uses given map to populate it | |
# @param map [Map] | |
# | |
# @overload populate(hash, *keys, &block) | |
# Creates map from the given hash to populate it. | |
# @param hash [::Hash] | |
# | |
# @overload populate(*keys, &block) | |
# Creates empty map to populate it. | |
# | |
# @param keys [Array<Object>] any number of keys to store into map | |
# @param method_name [Symbol] (see Concurrent::Map) | |
# @param _if [nil, :present, :absent] Method name suffix | |
# @yield computes and stores value for key, can pass old value | |
# @yieldparam [Object] key | |
# @yieldparam [Object, nil] old_value | |
# @yieldreturn [Object, nil] - computed value to store, key will be deteled if returns nil | |
# @return [Map] populated map | |
def self.populate(*keys, method_name: :compute, _if: nil, &block) # :yields: | |
map = case keys[0] | |
when ::Hash, self; wrap(keys.shift) | |
else new(); end | |
method_name = :"#{method_name}_if_#{_if}" if _if | |
keys.each do |key| | |
map.public_send(method_name, key) do |old_value| | |
block.arity > 1 ? block.call(key, old_value) : block.call(key) | |
end | |
end | |
return map | |
end | |
# @see Concurrent::Map#populate_from | |
# @param hash [::Hash] A source hash | |
# @return [self] | |
def marshal_load(hash) | |
initialize(&default_proc) | |
populate_from(hash) if hash | |
self | |
end | |
# Creates vanilla hash from self include default value/proc | |
# @see HashBuilder | |
# @return [::Hash] | |
def to_hash | |
HashBuilder[@backend, default_value, default_proc] | |
end | |
alias_method :to_h, :to_hash | |
# @return [Proc] | |
def to_proc | |
ProcBuilder[self] | |
end | |
# @param (see ::Hash#dig) | |
# @return [Object, nil] | |
def dig(key, *rest) | |
value = get(key) | |
if rest.size > 0 | |
unless value.respond_to?(:dig) | |
raise TypeError, "#{type.class} does not have #dig method" | |
end | |
value.dig(*rest) | |
else | |
value | |
end | |
end | |
# @see Concurrent::Map#freeze | |
# @return [self] | |
def freeze | |
@backend.freeze | |
self | |
end | |
end |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment