William Ross
William Ross

Reputation: 3920

Elixir searching an ETS table

I have some tuples stored in an Elixir :ets table with the following format,

{id, "first_name", "last_name"}

I'm looking to search the ETS table on both first and last name given a search term. For example if someone's first name was Alice, the search would return this tuple for "Al", "Ali", "Alic", etc.

So far I've tried:

:ets.match_object(table_name, {:_, :_, "last_name"})

which works when it matches exactly on "last_name" but if I have "last" or some shorter permutation in the search string it does not return a result.

I have also looked at search vectors, ets.match, ets.lookup and a few other ways to perform the search. Is there some built in, or other Elixiry way to do this type of string searching?

Upvotes: 1

Views: 897

Answers (2)

Aleksei Matiushkin
Aleksei Matiushkin

Reputation: 121010

One might use a Swiss knife of ets lookups, :ets.foldl/3.

:ets.foldl(fn
  {_, "Al" <> _, _} = y, acc -> [y | acc] # firstname
  {_, _, "Al" <> _} = y, acc -> [y | acc] # lastname
  _, acc -> acc                           # no match
end, [], table)

Please note, this is less efficient, compared to the solution proposed by @MikhailAksenov because the search is done in the calling process and hence all the data has to be transferred there, instead of being filtered “inside” the ets.

Upvotes: 1

Mikhail Aksenov
Mikhail Aksenov

Reputation: 571

Rememeber that you have universal comparison rules in Erlang which means you can compare strings "Alice" and "Al" since they're just lists.

Now to the fun part:

table = :ets.new(:table, [:protected, :set])

:ets.insert(table, {1, "Alice", "Dannings"})
:ets.insert(table, {2, "Ali", "Aaqib"})
:ets.insert(table, {3, "Bob", "Dannings"})
:ets.insert(table, {4, "Amanda", "Clarke"})
:ets.insert(table, {5, "Al", "Clarke"})

# now let's explore elixir comparison rules

"Alice" >= "Al"
"Alice" < "Am"

# based on rules above we can make a selector which will be equivalent to starts_with?

selector = :ets.fun2ms(
  fn({_, x, _} = y) # match all three element tuples
    when # where
       x >= "Al" and x < "Am"   # second element is greater or equal to "Al" and less than "Am" ("m" is the next letter in the alphabet)
     -> y end)

:ets.select(table, selector)

#[{1, "Alice", "Dannings"}, {2, "Ali", "Aaqib"}, {5, "Al", "Clarke"}]

This is hacky, though you can "search" by the beginning of the string.

If you want to use it in runtime, you must either set @compile {:parse_transform, :ms_transform} at the beginning of your module or use library like https://github.com/ericmj/ex2ms to make sure you are generating match specs (selector) properly.

Upvotes: 3

Related Questions