Peter R
Peter R

Reputation: 3516

Binary pattern matching - Can I extract the numbers out of "/word/number/word/number"

I know how to do this with regular String operations, but I'm curious if it's possible to match in one step.

Let's say I have accounts/123123/order/234234234. It's from an external source, so while I know that accounts/ and /order are always fixed length/value, the numbers might not always be a fixed length. Is there any way to do binary pattern matching here? Or anything better than a few String operations?

Upvotes: 3

Views: 537

Answers (4)

Pascal
Pascal

Reputation: 14042

If you are looking for something easy to read, you may convert the binary to list and use the io_lib:format/2 function:

1> Tag = <<"accounts/123123/order/234234234">>.
<<"accounts/123123/order/234234234">>
2> {ok,[ACC,ORD],_} = io_lib:fread("accounts/~d/order/~d",binary_to_list(Tag)).
{ok,[123123,234234234],[]}
3>

Upvotes: 0

7stud
7stud

Reputation: 48599

Other options...

In erlang:

~$ erl
Erlang/OTP 20 [erts-9.3] [source] [64-bit] [smp:4:4] [ds:4:4:10] [async-threads:10] [hipe] [kernel-poll:false]
Eshell V9.3  (abort with ^G)

1> binary:split(<<"accounts/123123/order/234234234">>, <<"/">>, [global]). 
[<<"accounts">>,<<"123123">>,<<"order">>,<<"234234234">>]

In elixir:

~$ iex
Erlang/OTP 20 [erts-9.3] [source] [64-bit] [smp:4:4] [ds:4:4:10] [async-threads:10] [hipe] [kernel-poll:false]
Interactive Elixir (1.8.2) - press Ctrl+C to exit (type h() ENTER for help)

iex(1)> :binary.split("accounts/123123/order/234234234", "/", [:global])
["accounts", "123123", "order", "234234234"]

With elixir regexes:

iex(3)> Regex.scan( ~r{[^/]+}, "accounts/123123/order/234234234") |> List.flatten() 
["accounts", "123123", "order", "234234234"]

Upvotes: 0

Aleksei Matiushkin
Aleksei Matiushkin

Reputation: 121000

There is nothing you cannot do with a bit of metaprogramming :)

defmodule AccOrd do
  @input "accounts/123123/order/234234234"

  for acc <- 1..42 do
    def match(<<"accounts/", acc :: binary-size(unquote(acc)),
                "/order/", ord :: binary>>), do: {acc, ord}
  end

  def test, do: match(@input)
end

AccOrd.test()
#⇒ {"123123", "234234234"}

Here we have basically built 42 clauses of match/1 function. One of them would match your input and voilà.

Upvotes: 1

Brett Beatty
Brett Beatty

Reputation: 5973

if it's possible to match in one step

We can only pattern match when we know the sizes of all the left pieces. As far as I'm aware, this can't be done as one pattern match.

anything better than a few String operations?

What about just one String operation?

iex> tag = "accounts/123123/order/234234234"
iex> ["accounts", account, "order", order] = String.split(tag, "/")
iex> account
"123123"
iex> order
"234234234"

Regex is also a good option

iex> tag = "accounts/123123/order/234234234"
iex> regex = ~R{^accounts/(?<account>\d+)/order/(?<order>\d+)$}
iex> Regex.named_captures(regex, tag)
%{"account" => "123123", "order" => "234234234"}

Upvotes: 7

Related Questions