Packages

n-gram tokenization and distance calculations

Current section

Files

Jump to
ngram lib ngram.ex
Raw

lib/ngram.ex

defmodule Ngram do
@moduledoc """
ngram module provides means to separate a string into n-grams
and calculate n-gram distance between two lists/strings
"""
@ngram_size 2
@doc """
tokenize a string into n-grams
## Examples:
iex> Ngram.tokenize("abcdef")
["ab", "bc", "cd", "de", "ef"]
iex> Ngram.tokenize("abcdef", 3)
["abc", "bcd", "cde", "def"]
"""
def tokenize(str, n \\ @ngram_size)
def tokenize(str, n) when is_binary(str), do:
tokenize(String.codepoints(str), n)
def tokenize(chars, n) when n <= 0 or length(chars) <0, do: :nil
def tokenize(chars, n) do
Stream.chunk(chars, n, 1) |> Enum.map(&(to_string(&1)))
end
@doc """
find duplicate-aware intersection between lists or stings
## Examples:
iex> Ngram.intersect([1,2,3], [2,3,4])
[2, 3]
iex> Ngram.intersect([1,2,2,3], [2,3,4])
[2, 3]
iex> Ngram.intersect([1,2,2,3], [2,2,3,4])
[2, 2, 3]
iex> Ngram.intersect("abcb", "bbcx")
["b", "c", "b"]
iex> Ngram.intersect("abcb", "")
[]
"""
def intersect(a, b) when is_binary(a) and is_binary(b) do
intersect(String.codepoints(a), String.codepoints(b))
end
def intersect(a, b), do: a -- (a -- b)
def calculate(a, b) do
calculate(a, b, @ngram_size)
end
@doc """
calculate n-gram distance between two lists or strings
## Examples:
iex> Ngram.calculate([1,2,3], [2,3])
0.5
iex> Ngram.calculate("lorem ipsum", "lorem dolor", 1)
0.5454545454545454
iex> Ngram.calculate("lorem", "merol")
0.0
iex> Ngram.calculate("lorem", "lorem")
1
iex> Ngram.calculate("lorem", "merol", 1)
1.0
iex> Ngram.calculate("lorem", "xcvbn", 1)
0.0
"""
def calculate(a, b, size) when size == 0 or
byte_size(a) < size or byte_size(b) < size, do: nil
def calculate(a, b, _) when a == b, do: 1
def calculate(a, b, size) do
na = a |> tokenize(size)
nb = b |> tokenize(size)
(intersect(na, nb) |> length) / max(length(na), length(nb))
end
end