Current section
Files
Jump to
Current section
Files
lib/services/simularity.ex
defmodule ContentIndexer.Services.Similarity do
@moduledoc """
** Summary **
This module accepts a list of tuples which contain the document id and a hash of terms
and and their TF_IDF weights, it also accepts query terms in the form of a hash of terms and
weights, same format as in the tuple above.
[
{ 1, %{ "abc" => 0.001, "term1" => 0.123, "term2" => 0.934, "term3" => 0.945 } },
{ 1, %{ "abc" => 0.001, "term1" => 0.123, "term2" => 0.934, "term3" => 0.945 } }…
]
The module will compute the similarity of all the provided documents to the
query terms. It will then return an ordered set of terms and their corresponding
weights
"""
def compare(document_list, query_terms) do
document_list
|> get_similarity(query_terms)
|> get_filenames()
end
# It will return an list of terms ordered by their cosine similarity
def get_similarity(document_list, query_terms) do
val = document_list
|> Enum.map(fn(doc) ->
{elem(doc, 0), compare_doc(elem(doc, 1), query_terms)}
end)
|> order_docs
Enum.into(val, %{})
end
def get_filenames(similarity_map) do
similarity_map
|> sort_similarity_map()
|> Enum.filter(fn(r) ->
val = elem(r, 1)
val != 0.0
end)
|> Enum.map(fn(r) ->
elem(r, 0)
end)
end
defp sort_similarity_map(similarity_map) do
similarity_map
|> Enum.sort(&(elem(&1, 1) <= elem(&2, 1)))
end
# return a list of documents as well as their cosime similarity to the term
defp compare_doc(document, query) do
d1_weights = get_relevant_weights(document, query)
query_vals = Keyword.values query
dot_prod = dot_product(Enum.zip(d1_weights, query_vals))
d1_magnitude = magnitude(d1_weights)
d2_magnitude = magnitude(query_vals)
if d1_magnitude == 0 || d2_magnitude == 0 do
0.0
else
abs(dot_prod / (d1_magnitude * d2_magnitude))
end
end
defp dot_product(value_array) do
value_array
|> Enum.reduce(0, fn(x, acc) ->
(elem(x, 0) * elem(x, 1)) + acc
end)
end
defp magnitude(values) do
# No math library wtf using erlang instead
:math.sqrt(Enum.reduce(values, 0, fn(x, acc) ->
(x * x) + acc
end))
end
defp get_relevant_weights(document, query) do
# get the query keys corresponding weights from the document
# weight is zero if the key is not in the document
query
|> Enum.map(fn(k) ->
key = elem(k, 0)
weight = document
|> Enum.filter(fn(f) -> elem(f, 0) == key end)
|> List.first
case weight do
nil ->
{key, 0.0}
_ ->
{key, elem(weight, 1)}
end
end)
|> Enum.into(%{})
|> Map.values
end
defp order_docs(x) do
y = length x
if y < 2 do
x
else
halfway = round(Float.floor(y / 2))
front_half = Enum.slice(x, 0, halfway)
back_half = Enum.slice(x, halfway, y)
merge(order_docs(front_half), order_docs(back_half))
end
end
defp merge([], list) do
list
end
defp merge(list, []) do
list
end
defp merge(list1, list2) do
[h1 | t1] = list1
[h2 | t2] = list2
{_, w1} = h1
{_, w2} = h2
if w1 > w2 do
[h1 | merge(t1, list2)]
else
[h2 | merge(list1, t2)]
end
end
end