defmodule RMap.Ruby do @moduledoc """ Summarized all of Ruby's Hash functions. Functions corresponding to the following patterns are not implemented - When a function with the same name already exists in Elixir. - When a method name includes `!`. - <, <=, ==, >, >=, [], []=, default_* """ @spec __using__(any) :: list defmacro __using__(_opts) do RUtils.define_all_functions!(__MODULE__) end import RMap.Support # https://ruby-doc.org/core-3.1.0/Hash.html # [:any?, :assoc, :clear, :compact, :compact!, :compare_by_identity, :compare_by_identity?, :deconstruct_keys, :delete, :delete_if, :dig, :each, :each_key, :each_pair, :each_value, :empty?, :eql?, :except, :fetch, :fetch_values, :filter, :filter!, :flatten, :has_key?, :has_value?, :hash, :include?, :initialize_copy, :inspect, :invert, :keep_if, :key, :key?, :keys, :length, :member?, :merge, :merge!, :rassoc, :rehash, :reject, :reject!, :replace, :select, :select!, :shift, :size, :slice, :store, :to_a, :to_h, :to_hash, :to_proc, :to_s, :transform_keys, :transform_keys!, :transform_values, :transform_values!, :update, :value?, :values, :values_at] # |> RUtils.required_functions([Map, REnum]) # ✔ assoc # ✔ clear # × compare_by_identity # × compare_by_identity? # × deconstruct_keys # ✔ delete_if # ✔ dig # ✔ each_key # ✔ each_pair # ✔ each_value # ✔ eql? # ✔ except # ✔ fetch_values # ✔ flatten # ✔ has_value? # hash TODO: Low priority # × initialize_copy # ✔ inspect # ✔ invert # ✔ keep_if # ✔ key # ✔ key? # ✔ length # ✔ rassoc # × rehash # ✔ shift # ✔ store # ✔ to_hash # × to_proc # ✔ to_s # ✔ transform_keys # ✔ transform_values # ✔ value? # ✔ values_at @doc """ Returns a list whose entries are those for which the function returns a truthy value. ## Examples iex> RMap.filter(%{a: 1, b: 2, c: 3}, fn {_, v} -> v > 1 end) %{b: 2, c: 3} """ @spec filter(map(), function()) :: map() def filter(map, func) do Enum.filter(map, func) |> Map.new() end @doc """ Returns a list whose entries are all those from self for which the function returns false or nil. ## Examples iex> RMap.reject(%{a: 1, b: 2, c: 3}, fn {_, v} -> v > 1 end) %{a: 1} """ @spec reject(map(), function()) :: map() def reject(map, func) do Enum.reject(map, func) |> Map.new() end @doc """ Returns %{}. ## Examples iex> RMap.clear(%{a: 1, b: 2, c: 3}) %{} """ @spec clear(map()) :: %{} def clear(_) do %{} end @doc """ Calls the function with each value; returns :ok. ## Examples iex> RMap.each_value(%{a: 1, b: 2, c: 3}, &IO.inspect(&1)) # 1 # 2 # 3 :ok """ @spec each_value(map(), function()) :: :ok def each_value(map, func) do Enum.each(map, fn {_, value} -> func.(value) end) end @doc """ Calls the function with each key; returns :ok. ## Examples iex> RMap.each_key(%{a: 1, b: 2, c: 3}, &IO.inspect(&1)) # :a # :b # :c :ok """ @spec each_key(map(), function()) :: :ok def each_key(map, func) do Enum.each(map, fn {key, _} -> func.(key) end) end @doc """ Returns true if value is a value in list, otherwise false. ## Examples iex> RMap.value?(%{a: 1, b: 2, c: 3}, 3) true iex> RMap.value?(%{a: 1, b: 2, c: 3}, 4) false """ @spec value?(map(), any) :: boolean() def value?(map, value) do Enum.any?(map, fn {_, v} -> v == value end) end @doc """ Returns a list containing values for the given keys. ## Examples iex> RMap.values_at(%{a: 1, b: 2, c: 3}, [:a, :b, :d]) [1, 2, nil] """ @spec values_at(map(), list()) :: list() def values_at(map, keys) do Enum.map(keys, &Map.get(map, &1)) end @doc """ Returns given map. ## Examples iex> RMap.to_hash(%{a: 1, b: 2, c: 3}) %{a: 1, b: 2, c: 3} """ @spec to_hash(map()) :: map() def to_hash(map) do map end @doc """ Returns the object in nested map that is specified by a given key and additional arguments. ## Examples iex> RMap.dig(%{a: %{b: %{c: 1}}}, [:a, :b, :c]) 1 iex> RMap.dig(%{a: %{b: %{c: 1}}}, [:a, :c, :b]) nil """ def dig(nil, _), do: nil def dig(result, []), do: result @spec dig(map(), list()) :: any() def dig(map, keys) do [key | tail_keys] = keys result = Map.get(map, key) dig(result, tail_keys) end @doc """ Returns a 2-element tuple containing a given key and its value. ## Examples iex> RMap.assoc(%{a: 1, b: 2, c: 3}, :a) {:a, 1} iex> RMap.assoc(%{a: 1, b: 2, c: 3}, :d) nil iex> RMap.assoc(%{a: %{b: %{c: 1}}}, :a) {:a, %{b: %{c: 1}}} """ @spec assoc(map(), any()) :: any() def assoc(map, key) do if(value = Map.get(map, key)) do {key, value} else nil end end @doc """ Returns a 2-element tuple consisting of the key and value of the first-found entry having a given value. ## Examples iex> RMap.rassoc(%{a: 1, b: 2, c: 3}, 1) {:a, 1} iex> RMap.rassoc(%{a: 1, b: 2, c: 3}, 4) nil iex> RMap.rassoc(%{a: %{b: %{c: 1}}}, %{b: %{c: 1}}) {:a, %{b: %{c: 1}}} """ @spec rassoc(map(), any()) :: any() def rassoc(map, value) do Enum.find_value(map, fn {k, v} -> if v == value, do: {k, v} end) end @doc """ Returns a map with modified keys. ## Examples iex> RMap.transform_keys(%{a: 1, b: 2, c: 3}, &to_string(&1)) %{"a" => 1, "b" => 2, "c" => 3} iex> RMap.transform_keys(%{a: %{b: %{c: 1}}}, &to_string(&1)) %{"a" => %{b: %{c: 1}}} """ @spec transform_keys(map(), function()) :: map() def transform_keys(map, func) do Enum.map(map, fn {key, value} -> {func.(key), value} end) |> Map.new() end @doc """ Returns a map with modified values. ## Examples iex> RMap.transform_values(%{a: 1, b: 2, c: 3}, &inspect(&1)) %{a: "1", b: "2", c: "3"} iex> RMap.transform_values(%{a: %{b: %{c: 1}}}, &inspect(&1)) %{a: "%{b: %{c: 1}}"} """ @spec transform_values(map(), function()) :: map() def transform_values(map, func) do Enum.map(map, fn {key, value} -> {key, func.(value)} end) |> Map.new() end @doc """ Returns a map excluding entries for the given keys. ## Examples iex> RMap.except(%{a: 1, b: 2, c: 3}, [:a, :b]) %{c: 3} """ @spec except(map(), list()) :: map() def except(map, keys) do delete_if(map, fn {key, _} -> key in keys end) end @doc """ Returns a list containing the values associated with the given keys. ## Examples iex> RMap.fetch_values(%{ "cat" => "feline", "dog" => "canine", "cow" => "bovine" }, ["cow", "cat"]) ["bovine", "feline"] iex> RMap.fetch_values(%{ "cat" => "feline", "dog" => "canine", "cow" => "bovine" }, ["cow", "bird"]) ** (MapKeyError) key not found: bird """ @spec fetch_values(map(), list()) :: list() def fetch_values(map, keys) do Enum.map(keys, fn key -> if(value = map |> Map.get(key)) do value else raise MapKeyError, "key not found: #{key}" end end) end @doc """ When a function is given, calls the function with each missing key, treating the block's return value as the value for that key. ## Examples iex> RMap.fetch_values(%{ "cat" => "feline", "dog" => "canine", "cow" => "bovine" }, ["cow", "bird"], &(String.upcase(&1))) ["bovine", "BIRD"] """ @spec fetch_values(map(), list(), function()) :: list() def fetch_values(map, keys, func) do Enum.map(keys, fn key -> if(value = map |> Map.get(key)) do value else func.(key) end end) end @doc """ Returns a flatten list. ## Examples iex> RMap.flatten(%{1=> "one", 2 => [2,"two"], 3 => "three"}) [1, "one", 2, 2, "two", 3, "three"] iex> RMap.flatten(%{1 => "one", 2 => %{a: 1, b: %{c: 3}}}) [1, "one", 2, :a, 1, :b, :c, 3] """ @spec flatten(map()) :: list() def flatten(map) do deep_to_list(map) |> List.flatten() end @doc """ Returns a map object with the each key-value pair inverted. ## Examples iex> RMap.invert(%{"a" => 0, "b" => 100, "c" => 200, "d" => 300, "e" => 300}) %{0 => "a", 100 => "b", 200 => "c", 300 => "e"} iex> RMap.invert(%{a: 1, b: 1, c: %{d: 2}}) %{1 => :b, %{d: 2} => :c} """ @spec invert(map()) :: map() def invert(map) do map |> Enum.map(fn {k, v} -> {v, k} end) |> Map.new() end @doc """ Removes the first map entry; returns a 2-element tuple. First element is {key, value}. Second element is a map without first pair. ## Examples iex> RMap.shift(%{a: 1, b: 2, c: 3}) {{:a, 1}, %{b: 2, c: 3}} iex> RMap.shift(%{}) {nil, %{}} """ @spec shift(map()) :: {tuple() | nil, map()} def shift(map) do {result, list} = map |> Enum.split(1) {List.last(result), Map.new(list)} end defdelegate delete_if(map, func), to: __MODULE__, as: :reject defdelegate keep_if(map, func), to: __MODULE__, as: :filter defdelegate select(map, func), to: __MODULE__, as: :filter defdelegate length(map), to: Enum, as: :count defdelegate size(map), to: Enum, as: :count defdelegate to_s(map), to: Kernel, as: :inspect defdelegate inspect(map), to: Kernel, as: :inspect defdelegate each_pair(map, func), to: Enum, as: :each defdelegate key(map, key, default \\ nil), to: Map, as: :get defdelegate key?(map, key), to: Map, as: :has_key? defdelegate has_value?(map, value), to: __MODULE__, as: :value? defdelegate store(map, key, value), to: Map, as: :put defdelegate eql?(map1, map2), to: Map, as: :equal? end defmodule MapKeyError do defexception [:message] end