Most elegant way to match on all pattern permutations?

I have to match on data (HTTP headers) that may arrive in any order. What would be the elegant and performant way to do such pattern match?

F.e. say we have to match on these 3 values: <<a>>, <<b>>, <<c>>. Each known beforehand, yet with an indeterminate ordering. Is there any better way than to just write/generate out all the function heads, 6 in this case?

def match(<<a>> <> <<b>> <> <<c>>), do: ...
# 4 matches snipped
def match(<<c>> <> <<b>> <> <<a>>), do: ...

Related thread: Most elegant way to generate all permutations? - #3 by andre1sk

1 Like

If you want to match on an unordered list of values, you can order them before doing the match. Note though, that the kind of binary pattern matching that you gave as an example won’t work in Elixir.

1 Like
def match(complete) do
  complete
  |> Stream.unfold(fn 
    "" -> nil
    rest -> do_match(rest)
  end)
  |> Enum.to_list()
end

defp do_match(<<a, rest::binary>>), do: {:a, rest}
defp do_match(<<b, rest::binary>>), do: {:b, rest}
defp do_match(<<c, rest::binary>>), do: {:c, rest}
7 Likes

Thank you both!

@bartblast, how did you mean that the pattern match in OP “won’t work”? Because it seems to work, in iex, using Elixir 1.13.1:

> defmodule A, do: def a(<<b>> <> <<c>>), do: (IO.inspect {b,c})
> A.a <<1,2>>
{1, 2}

Are there limitations that i’m not aware of, outside of my limited toy example?

1 Like

Nice approach.

1 Like

The function you wrote doesn’t do what you described you wanted.

What you’ve done is match a variable a and a variable b, both a single byte width. What the text you wrote described was to match specific values in any order. So it’d be more like match("foo:" <> foo_value <> "bar:" <> bar_value) but where foo and bar happen in any order. This is not possible to construct in a single match head, but @LostKobrakai’s solution works.

Give it a whirl! Evolve your example from matching on just 2 bytes and do something more like an HTTP header string.

3 Likes

Ah, yes, you’re right. My example was misleading and not what i actually wanted to do:

def match(@header1 <> @header2 <> ...), do: ...
1 Like

@chocolatedonut I meant similar thing to what @benwilson512 wrote - based on your example I thought that you wanted to do something different. Glad you’ve already got some help on your problem!