Current section
Files
Jump to
Current section
Files
lib/tf_idf.ex
defmodule TfIdf do
alias :math, as: Math
defmodule Doc do
defstruct tf: %{}, max_tf: 1, count: 0
end
defmodule Model do
defstruct docs: [], count: 0, idf: %{}
end
def build_model(docs) do
docs
|> calculate_docs_tf()
|> calculate_idf()
end
def calculate_tf_idf(model, query) do
model.docs
|> Enum.reduce([], fn {doc_name, %{tf: tf, count: total_count}}, acc ->
score =
query
|> String.downcase()
|> String.split(~r/[^a-zA-Z]/)
|> Enum.map(fn token ->
idf = Map.get(model.idf, token, 0)
token_count = Map.get(tf, token, 0)
tf = token_count / total_count
tf * idf
end)
|> Enum.sum()
if score == 0 do
acc
else
[{doc_name, score} | acc]
end
end)
|> Enum.sort_by(fn {_, score} -> score end, :desc)
end
def calculate_log_norm_idf(model, query) do
model.docs
|> Enum.reduce([], fn {doc_name, %{tf: tf, count: total_count}}, acc ->
score =
query
|> String.downcase()
|> String.split(~r/[^a-zA-Z]/)
|> Enum.map(fn token ->
idf = Map.get(model.idf, token, 0)
token_count = Map.get(tf, token, 0)
tf = token_count / total_count
if tf == 0 do
0
else
(1 + Math.log10(tf)) * idf
end
end)
|> Enum.sum()
if score == 0 do
acc
else
[{doc_name, score} | acc]
end
end)
|> Enum.sort_by(fn {_, score} -> score end, :desc)
end
def calculate_dn_idf(model, query) do
model.docs
|> Enum.reduce([], fn {doc_name, %{tf: tf, max_tf: max_tf}}, acc ->
score =
query
|> String.downcase()
|> String.split(~r/[^a-zA-Z]/)
|> Enum.map(fn token ->
idf = Map.get(model.idf, token, 0)
token_count = Map.get(tf, token, 0)
tf = 0.5 + 0.5 * (token_count / max_tf)
tf * idf
end)
|> Enum.sum()
if score == 0 do
acc
else
[{doc_name, score} | acc]
end
end)
|> Enum.sort_by(fn {_, score} -> score end, :desc)
end
def calculate_token_value(model, token) do
idf = Map.get(model.idf, token, 0)
model.docs
|> Enum.map(fn {doc_name, %{tf: tf, count: total_count}} ->
token_count = Map.get(tf, token, 0)
tf = token_count / total_count
{doc_name, tf * idf}
end)
end
defp calculate_docs_tf(docs) do
Enum.map(docs, &calculate_doc_tf/1)
end
defp calculate_doc_tf({name, doc}) when is_binary(name) and is_binary(doc) do
{_, tf} = calculate_doc_tf(doc)
{name, tf}
end
defp calculate_doc_tf(doc) when is_binary(doc) do
words =
doc
|> String.downcase()
|> String.split(~r/[^a-zA-Z]/)
n = length(words)
tf = Enum.frequencies(words)
max_tf = Map.values(tf) |> Enum.max()
{doc, %TfIdf.Doc{tf: tf, max_tf: max_tf, count: n}}
end
defp calculate_idf(docs) do
n = length(docs)
idf =
docs
|> Enum.flat_map(&Map.keys(elem(&1, 1).tf))
|> Enum.frequencies()
|> Map.new(fn {t, c} -> {t, Math.log10((n + 1) / (c + 1))} end)
%TfIdf.Model{docs: docs, count: n, idf: idf}
end
end