Reputation: 317
I'm iterating through a list of strings, and I want to return the contents of a string if the beginning of it matches the provided string.
e.g.
strings = [ "GITHUB:github.com", "STACKOVERFLOW:stackoverflow.com" ]
IO.puts fn(strings, "GITHUB") // => "github.com"
This is what I thinking so far:
def get_tag_value([ << tag_name, ": ", tag_value::binary >> | rest ], tag_name), do: tag_value
def get_tag_value([ _ | rest], tag_name), do: get_tag_value(rest, tag_name)
def get_tag_value([], tag_name), do: ""
But I get this:
** (CompileError) lib/file.ex:31: a binary field without size is only allowed at the end of a binary pattern and never allowed in binary generators
Which makes sense, but then I'm not quite sure how to go about doing this. How would I match a substring to a different variable provided as an argument?
Upvotes: 1
Views: 2167
Reputation: 12157
There are many ways to skin this cat.
For example:
def get_tag_value(tag, strings) do
strings
|> Enum.find("", &String.starts_with?(&1, tag <> ":"))
|> String.split(":", parts: 2)
|> Enum.at(1, "")
end
or if you still wanted to explicitly use recursion:
def get_tag_value(_tag, []), do: ""
def get_tag_value(tag, [str | rest]) do
if String.starts_with?(str, tag <> ":") do
String.split(str, ":", parts: 2) |> Enum.at(1, "")
else
get_tag_value(tag, rest)
end
end
Are just two of many possible ways.
However, you won't be able to pattern match the string in the function head without knowing it (or at least the length) beforehand.
Upvotes: 2
Reputation: 222428
Here's how I'd do this making most use of pattern matching and no call to String.starts_with?
or String.split
:
defmodule A do
def find(strings, string) do
size = byte_size(string)
Enum.find_value strings, fn
<<^string::binary-size(size), ":", rest::binary>> -> rest
_ -> nil
end
end
end
strings = ["GITHUB:github.com", "STACKOVERFLOW:stackoverflow.com"]
IO.inspect A.find(strings, "GITHUB")
IO.inspect A.find(strings, "STACKOVERFLOW")
IO.inspect A.find(strings, "GIT")
IO.inspect A.find(strings, "FOO")
Output:
"github.com"
"stackoverflow.com"
nil
nil
Upvotes: 2
Reputation: 1958
This would be my take in Erlang:
get_tag_value(Tag, Strings) ->
L = size(Tag),
[First | _] = [Val || <<Tag:L/binary, $:, Val/binary>> <- Strings]
First.
The same in Elixir (there are probably more idiomatic ways of writing it, tho):
def gtv(tag, strings) do
l = :erlang.size(tag)
[first | _ ] =
for << t :: binary - size(l), ":", value :: binary >> <- strings,
t == tag,
do: value
first
end
Upvotes: 0
Reputation: 2345
You can use a combination of Enum.map and Enum.filter to get the matching pairs you're looking for:
def get_tag_value(tag_name, tags) do
tags
|> Enum.map(&String.split(&1, ":")) # Creates a list of [tag_name, tag_value] elements
|> Enum.filter(fn([tn, tv]) -> tn == tag_name end) # Filters for the tag name you're after
|> List.last # Potentially gets you the pair [tag_name, tag_value] OR empty list
end
And in the end you can either call List.last/1
again to either get an empty list (no match found) or the tag value.
Alternatively you can use a case statement to return a different kind of result, like a :nomatch
atom:
def get_tag_value(tag_name, tags) do
matches = tags
|> Enum.map(&String.split(&1, ":")) # Creates a list of [tag_name, tag_value] elements
|> Enum.filter(fn([tn, tv]) -> tn == tag_name end) # Filters for the tag name you're after
|> List.last # Potentially gets you the pair [tag_name, tag_value] OR empty list
case matches do
[] -> :nomatch
[_, tag_value] -> tag_value
end
end
Upvotes: 0
Reputation: 3320
iex(1)> strings = [ "GITHUB:github.com", "STACKOVERFLOW:stackoverflow.com" ]
iex(2)> Enum.filter(strings, fn(s) -> String.starts_with?(s, "GITHUB") end)
iex(3)> |> Enum.map(fn(s) -> [_, part_2] = String.split(s, ":"); part_2 end)
# => ["github.com"]
In Enum.filter/2 I select all strings they start with "GITHUB" and I get a new List. Enum.map/2 iterates through the new List and splits each string at the colon to return the second part only. Result is a List with all parts after the colon, where the original string starts with "GITHUB".
Be aware, that If there's an item like "GITHUBgithub.com" without colon, you get a MatchError. To avoid this either use String.starts_with?(s, "GITHUB:")
to filter the right strings or avoid the pattern matching like I did in Enum.map/2 or use pattern matching for an empty list like @ryanwinchester did it.
Upvotes: 0