mercurio
Capturing regex matching variables in cond
I have this function for parsing a string into a tuple. "-<number>" should return {:lat, -<number>}, "+<number>" should return {:lat, <number>}, and "<number>" should return {:gridNum, }`, plus some other simpler matches:
defp parseIndex(s) do
cond do
String.match?(s, ~r/^-\d+$/) ->
[_, n] = Regex.run(~r/^-(\d+)$/, s)
{:lat, -1 * String.to_integer(n)}
String.match?(s, ~r/^\+\d+$/) ->
[_, n] = Regex.run(~r/^\+(\d+)$/, s)
{:lat, String.to_integer(n)}
s == "--" ->
{:latFrame, -1}
s == "++" ->
{:latFrame, 1}
s == "-*" ->
{:latEnd, -1}
s == "+*" ->
{:latEnd, 1}
match?({_n, ""}, Integer.parse(s)) ->
{:gridNum, String.to_integer(s)}
true ->
{:gridText, s}
end
end
This works, but is there a better way to do this without running the regex twice, once to match a pattern like -<number> and then again to extract the numerical portion? Same for the second-to-last clause, which ends up parsing the integer twice.
Thanks!
Phil
Most Liked
kip
Noting that:
-
Regex.run/2returnsnilif there is no match and that -
nilisfalsyfor the purposes of boolean evaluation and - you can bind a variable in a match
For an optimisation I would likely put the explicit equality checks first since a cond proceeds in lexical order. Also I think you can collapse the :lat parsing into a single clause. And one last one, you can match and bind on the integer parsing too (the second last clause):
defp parseIndex(s) do
cond do
s == "--" ->
{:latFrame, -1}
s == "++" ->
{:latFrame, 1}
s == "-*" ->
{:latEnd, -1}
s == "+*" ->
{:latEnd, 1}
(match = Regex.run(~r/^([+-]\d+)$/, s)) ->
[_, n] = match
{:lat, String.to_integer(n)}
match?({n, ""}, Integer.parse(s)) ->
{:gridNum, n}
true ->
{:gridText, s}
end
end
edisonywh
What about doing binary matching directly?
defmodule Hello do
def run(string) do
do_run(string)
end
defp do_run("--"), do: {:latFrame, -1}
defp do_run("++"), do: {:latFrame, 1}
defp do_run("-*"), do: {:latEnd, -1}
defp do_run("+*"), do: {:latEnd, 1}
defp do_run(<<"+", number::binary>>), do: {:lon, to_integer(number)}
defp do_run(<<"-", number::binary>>), do: {:lat, -to_integer(number)}
defp do_run(<<"+", number::binary>>), do: {:lon, to_integer(number)}
defp do_run(number), do: {:gridNum, to_integer(number)}
defp to_integer(string), do: String.to_integer(string)
end
mercurio
I like the solution with multiple functions, with a single Regex in the last one after handling all the simple cases. This is the best solution so far, thanks!
hauleth
Here you have it with error handling
defmodule Hello do
def run(string) do
{:ok, do_run(string)}
catch
:throw, error -> {:error, error}
end
defp do_run("--"), do: {:latFrame, -1}
defp do_run("++"), do: {:latFrame, 1}
defp do_run("-*"), do: {:latEnd, -1}
defp do_run("+*"), do: {:latEnd, 1}
defp do_run(<<"+", number::binary>>), do: {:lon, to_integer(number)}
defp do_run(<<"-", number::binary>>), do: {:lat, -to_integer(number)}
defp do_run(<<"+", number::binary>>), do: {:lon, to_integer(number)}
defp do_run(number), do: {:gridNum, to_integer(number)}
defp to_integer(string) do
case Integer.parse(string) do
{num, ""} -> num
_ -> throw({:not_number, string})
end
end
end







