Thanks people for all the responses!
Indeed a lot of way to approach the problem, I will probably use the @amnu3387 response just because is a little bit faster, but as Eric said, is just a matter of taste, and all other options would be completely fine.
Thanks again for the help!
The benchmark results were:
Name ips average deviation median 99th %
mnussbaumer_response 927.74 K 1.08 μs ±2239.93% 0.96 μs 1.98 μs
kokolegorille_response 867.51 K 1.15 μs ±2327.39% 1.00 μs 2.14 μs
gregvaughn_response 766.18 K 1.31 μs ±2168.73% 1.15 μs 2.31 μs
ericgray_response 730.43 K 1.37 μs ±1314.86% 1.26 μs 2.48 μs
chungwong_response 655.58 K 1.53 μs ±1555.15% 1.36 μs 2.78 μs
parse_map_int_fields 567.61 K 1.76 μs ±1491.38% 1.57 μs 3.18 μs
Comparison:
mnussbaumer_response 927.74 K
kokolegorille_response 867.51 K - 1.07x slower +0.0748 μs
gregvaughn_response 766.18 K - 1.21x slower +0.23 μs
ericgray_response 730.43 K - 1.27x slower +0.29 μs
chungwong_response 655.58 K - 1.42x slower +0.45 μs
parse_map_int_fields 567.61 K - 1.63x slower +0.68 μs
And the code used for the benchmark:
defmodule Sanitizer do
def parse_map_int_fields(map) do
ints_map =
map
|> Enum.filter(fn {k, _v} -> Atom.to_string(k) |> String.starts_with?("int_") end)
|> Enum.filter(fn {_k, v} -> !is_integer(v) and !is_nil(v) end)
|> Enum.map(fn {key, val} ->
case Integer.parse(val) do
:error ->
{key, nil}
{parsed, _} ->
{key, parsed}
end
end)
|> Map.new()
Map.merge(map, ints_map)
end
def kokolegorille_response(map) do
Map.new(map, fn {_k, _v} = el ->
if has_to_be_sanitized?(el), do: sanitize(el), else: el
end)
end
defp has_to_be_sanitized?({k, v}) do
Atom.to_string(k) |> String.starts_with?("int_") and !is_integer(v) and !is_nil(v)
end
defp sanitize({k, v}) do
case Integer.parse(v) do
:error ->
{k, nil}
{parsed, _} ->
{k, parsed}
end
end
def chungwong_response(map) do
parse(map)
end
def parse(m) when is_map(m), do: parse(Map.to_list(m), [])
def parse([], acc), do: Enum.into(acc, %{})
def parse([{k, v} | t], acc), do: parse(t, prepend({to_string(k), v}, acc))
def prepend({"int_" <> _ = k, v}, acc) when is_binary(v),
do: [{String.to_atom(k), to_integer(Integer.parse(v))} | acc]
def prepend({k, v}, acc), do: [{String.to_atom(k), v} | acc]
def to_integer(:error), do: nil
def to_integer({parsed, _}), do: parsed
def ericgray_response(map) do
Enum.reduce(map, %{}, &do_parse_map_int_fields/2)
end
# Reconstruct or accumulate key value pairs into a new map
def do_parse_map_int_fields({key, value}, map) do
case starts_with_int?(key) do
true ->
Map.put(map, key, parse_value(value))
false ->
Map.put(map, key, value)
end
end
# Boolean function returns true if field is int
def starts_with_int?(key) do
key
|> Atom.to_string()
|> String.starts_with?("int_")
end
# Parses the value of int field, if not an integer return nil
def parse_value(value) do
case Integer.parse(value) do
{value, _} -> value
:error -> nil
end
end
def mnussbaumer_response(map) do
reshape_map_int_fields(map)
end
@spec reshape_map_int_fields(map) :: map
def reshape_map_int_fields(map) when is_map(map),
do:
Enum.reduce(map, %{}, fn {k, v}, acc ->
Map.put(acc, k, maybe_reshape(k, v))
end)
@spec maybe_reshape(atom, term) :: integer | term
def maybe_reshape(k, v) when is_atom(k) and not is_integer(v) and not is_nil(v) do
case Atom.to_string(k) do
<<"int_", _>> ->
case Integer.parse(v) do
{new_val, _} -> new_val
_ -> nil
end
_ ->
v
end
end
def maybe_reshape(_, v), do: v
def gregvaughn_response(map) do
for {k, v} <- map, match?("int" <> _, Atom.to_string(k)), into: map do
case Integer.parse(v) do
{int, _} -> {k, int}
_other -> {k, nil}
end
end
end
end
test_map = %{
int_a: "1",
int_b: "not_a_int!",
str_a: "This is a string field",
str_b: "Another string field",
fl_a: 0.0
}
Benchee.run(%{
"parse_map_int_fields" => fn -> Sanitizer.parse_map_int_fields(test_map) end,
"kokolegorille_response" => fn -> Sanitizer.kokolegorille_response(test_map) end,
"chungwong_response" => fn -> Sanitizer.chungwong_response(test_map) end,
"ericgray_response" => fn -> Sanitizer.ericgray_response(test_map) end,
"mnussbaumer_response" => fn -> Sanitizer.mnussbaumer_response(test_map) end,
"gregvaughn_response" => fn -> Sanitizer.gregvaughn_response(test_map) end
})






















