Dusty

Dusty

Which data structure would you use to build a suffix tree in Elixir?

I am interested in implementing suffix trees in Elixir, with the primary goal of building a data structure that will allow fast autocomplete/autosuggest in LiveView. I see that there is an implementation of tries on Hex, but I think it’s important that the user not be required to match the string from the beginning. Given the string “Add New Account,” I would like the user to receive the suggestion for any substring (for example, “new,” or “acc”).

I have been reviewing this excellent article on using Ukkonen’s method to construct a suffix tree. There are some code examples on Rosetta Code, and I also found this article and this book chapter helpful.

At first, I thought it would be as simple as nesting linked lists. I had the idea that the algorithm could simply traverse the top level of the list until the beginning of the entered text was matched, and then move on to traversing the first nested list until further matches, and so on. It even appeared that the usual leaf marker, $, could be eliminated, because we could simply build up the matched string and return it when we arrived at [head | []].

However, further investigation has made me aware that I dramatically underestimated the complexity of constructing such a tree. Even using tuples to provide additional information ({:node, "substring"} or {:leaf, "substring"}), does not address the fact that Ukkonen’s method of construction requires links between nodes of the tree (meaning, jumping between nodes, rather than traversing a list in order).

Perhaps it isn’t possible to use existing Elixir data structures to create such a suffix tree, as the tree itself is a data structure with unique properties. In any event, I am interested in putting some time into this, but I really need to better frame the problem and the approach first.

The ultimate goal would be a Hex package with an API along these lines:

defmodule SuffixTree do
  @doc """
  Takes a list of strings, sorts them, and constructs a suffix tree.
  """
  def build(list, opts \\ []) do
    #...
  end

  @doc """
  Takes a user-entered substring and returns a list of matches from the given suffix tree.
  The number and sorting of matches are determined by opts.
  """
  def match(tree, substring, opts \\ [])
    #...
  end
end

My impression at this stage is that the building of the tree could be greatly simplified if it were done asynchronously and you didn’t care how long it took. The purpose of all the links between nodes in Ukkonen’s method appears to be a reduction in the time it takes to build the tree, from O(m2) to O(m) for a string of length m. Perhaps a full implementation of Ukkonen isn’t needed for smaller sets of strings.

How would you approach this? Am I missing some prior art that has already solved this in Elixir? Should I just hit the ElasticSearch docs and forget about this? Thanks in advance for any feedback.

Most Liked

Dusty

Dusty

Oh gosh, blast from the past.

The short answer is no, I never finished it. There are 2 main reasons:

  1. I migrated to using Rust primarily.
  2. The available implementations in Rust made it clear to me that performance is much better for suffix arrays, and most modern implementations use an optimization like that, rather than copying Ukkonen. For example, Andrew Gallant’s implementation, and specifically the articles referenced in his notes.

You can see the work I had done on the dev branch of this repo. It’s a long way from finished, but I had laid out a strategy (see ukkonen.md and the code comments).

al2o3cr

al2o3cr

The core idea seems like :digraph from the Erlang stdlib could help, but that’s not likely to get comparable performance to the optimized algorithm.

Consider borrowing a technique from :digraph, however: it uses ETS tables to store vertexes / edges / etc mutably with random access. I used a similar-but-vastly-simpler approach for this Advent of Code problem - my solution uses an ETS table containing three-element tuples {marble, previous_marble, next_marble} to simulate a doubly-linked circular list.

mindok

mindok

There’s also a pure elixir digraph implementation - https://github.com/bitwalker/libgraph - performant and doesn’t use ETS

Where Next?

Popular in Questions Top

vonH
In asking this question I am more interested about the expressiveness of the language itself and less concerned about the availability of...
New
sergio_101
I am VERY much an elixir newbie. I have taken one elixir course and one phoenix course on Udemy. During that course, I saw the instructor...
New
pgiesin
This should be a simple problem but I just can’t seem to figure it out. I have a standalone Elixir app that won’t find the database. Dep...
New
_russellb
I want to try my hand at web scraping. What tools/libraries do I need to use. I’m hoping to turn this into something professional so don’...
New
sacepums
Hey guys. I'm new to elixir and im really stocked about it. But I ran into a bit of problem - I need to convert a date sting, for examp...
New
minhajuddin
I have seen a lot of code which picks the first element from a list using Enum.at(0) instead of List.first. Is there a reason why people ...
New
ashish173
I am using Ecto timestamps with postgres, I can see the timestamps() use the :naive_dateime but for my use case I wanted to store the ti...
New
Codball
Mix format works fine if run from the cmd. I’ve followed this to facilitate the implementation into VSC which involves downloading an ext...
New
romenigld
I am trying to run a deploy with docker and I successfully runned with this command: docker build -t romenigld/blog-prod . but when I t...
New
idi527
I’ve been re-reading swift book again and noticed that multiline strings there don’t have a trailing line break, unlike in elixir iex(2)...
New

Other popular topics Top

chrismccord
Phoenix 1.4.0 released Phoenix 1.4 is out! This release ships with exciting new features, most notably with HTTP2 support, improved deve...
688 30048 115
New
shahryarjb
Hello, I get Persian date from my client and convert it to normal calendar like this: def jalali_string_to_miladi_english_number(persi...
New
JakeBecker
TL;DR: I’ve just released an implementation of Microsoft’s IDE-independent Language Server Protocol for Elixir. It adds language support ...
1140 51847 244
New
gshaw
What is the idiomatic way of matching for not nil in Elixir? E.g., First way: defp halt_if_not_signed_in(conn, signed_in_account) when...
New
jerry
Good day to you all. I have been struggling to get a query involving like and ilike to work. Can anyone assist me on this, please? pro...
New
New
vonH
When I run the Plug and I recompile I wind up having to use Ctrl C to quit iex and start again. Witht the help of rlwrap I can use the cu...
New
baxterw3b
Hi guys, i’m new in the Elixir world, and i have to say, that i love it! i’m having some problem to understand anonymous functions with ...
New
beno
I will often find my self writing things similar to: case some_value do nil -> something() "" -> something() _ -> someth...
New
aesmail
Hello guys, I have finally made it. I created an admin interface for a framework. It’s been on my todo list for years and with the curre...
New

We're in Beta

About us Mission Statement