Free tools Windows power users keep installed
One-click scans. No signup required.
Para construir um índice invertido em Elixir, associe cada termo aos documentos em que aparece e guarde a frequência do termo em cada documento. Depois, use essas frequências para gerar candidatos e ordená-los com uma fórmula TF-IDF declarada. A seguir está uma implementação didática em memória, com tratamento de entradas vazias e consultas que não correspondem a nenhum termo.
O que o índice invertido armazena
Uma coleção costuma ser vista como documentos compostos de termos. O índice invertido muda a direção dessa relação: cada termo aponta para os documentos que o contêm. A documentação do Elasticsearch resume: “An inverted index is a data structure that maps each token to the documents that contain it.” (documentação do Elasticsearch).
Para busca booleana, uma lista de IDs pode bastar. Para ordenar resultados por relevância, é útil armazenar também a frequência do termo em cada documento; para busca de frases, podem ser necessárias posições. A documentação do Elasticsearch descreve frequência e posição como metadados possíveis nos postings. Neste exemplo, cada termo aponta para um mapa de ID do documento para frequência:
%{"elixir" => %{1 => 2, 3 => 1}, "busca" => %{2 => 1}}
O mapa externo funciona como dicionário de termos; o mapa interno é a posting list. Como cada ID aparece uma única vez por termo, o número de documentos que contêm o termo é simplesmente o tamanho do mapa interno.
Quick wins for a faster PC:
Fix the driver behind crashes, sound loss and screen glitchesFind Drivers →Repair Windows errors before they cause bigger problemsFix Now →Scan for outdated or missing drivers - takes under a minuteDriver Scan →#1 Best Overall
Prepare os documentos e normalize os termos
Uma função de tokenização precisa ser aplicada tanto aos documentos quanto às consultas. Se a normalização diferir entre indexação e busca, um termo poderá existir no índice e ainda assim não ser encontrado pela consulta. Aqui a regra é deliberadamente simples: converter para minúsculas, separar por qualquer sequência que não seja letra ou número Unicode e descartar partes vazias.
defmodule MiniSearch do
def tokenize(text) when is_binary(text) do
text
|> String.downcase()
|> String.split(~r/[^p{L}p{N}]+/u, trim: true)
end
end
A expressão usa a opção Unicode da regex. A regra preserva letras acentuadas como parte dos tokens e trata hífens e pontuação como separadores. Isso não resolve todas as decisões linguísticas: stemming, palavras vazias, variantes de acentuação, compostos com hífen e outras convenções de Unicode podem exigir regras próprias conforme os dados e a busca esperada.
Construa o índice em memória
Para cada documento, conte primeiro as ocorrências dos tokens. Em seguida, atualize a posting list de cada termo com a frequência daquele documento. A implementação abaixo começa com um mapa vazio e retorna um mapa vazio para uma coleção vazia.
defmodule MiniSearch do
def tokenize(text) when is_binary(text) do
text
|> String.downcase()
|> String.split(~r/[^p{L}p{N}]+/u, trim: true)
end
def index(documents) when is_list(documents) do
Enum.reduce(documents, %{}, fn %{id: id, text: text}, index ->
text
|> tokenize()
|> Enum.frequencies()
|> Enum.reduce(index, fn {term, tf}, acc ->
postings = Map.get(acc, term, %{})
Map.put(acc, term, Map.put(postings, id, tf))
end)
end)
end
end
O formato esperado é uma lista como [%{id: 1, text: "Elixir cria ferramentas"}, %{id: 2, text: "Ferramentas para busca"}]. IDs devem ser únicos: se a entrada repetir um ID, a frequência anterior daquele ID para um termo será substituída pela do documento processado depois, não somada. O índice também não conserva documentos sem tokens; eles não contribuem com postings nem com resultados.
A frequência de termo, ou tf, conta ocorrências dentro de um documento. Já a frequência documental, ou df, conta em quantos documentos do corpus o termo aparece. São medidas diferentes: um termo pode repetir muitas vezes em um documento e ainda ocorrer em poucos documentos.
Recupere candidatos e ordene-os por TF-IDF
Recuperação e ranking são etapas distintas. A recuperação determina quais documentos podem ser candidatos; a pontuação atribui peso aos candidatos. Para uma consulta com vários termos, a implementação abaixo usa OR: basta o documento conter pelo menos um termo consultado para ser candidato. As contribuições dos termos presentes são somadas.
Rank #3
A convenção escolhida é tf(t,d) × log(N / df(t)), em que N é o total de documentos de entrada e df(t) é o número de documentos com o termo. A frequência bruta favorece repetição local; o IDF reduz o peso de termos difundidos pelo corpus. Não há suavização nessa fórmula. Um termo desconhecido não tem posting e, portanto, não gera pontuação; com N maior que zero, qualquer termo indexado tem df maior que zero.
defmodule MiniSearch do
def tokenize(text) when is_binary(text) do
text
|> String.downcase()
|> String.split(~r/[^p{L}p{N}]+/u, trim: true)
end
def index(documents) when is_list(documents) do
Enum.reduce(documents, %{}, fn %{id: id, text: text}, index ->
text
|> tokenize()
|> Enum.frequencies()
|> Enum.reduce(index, fn {term, tf}, acc ->
postings = Map.get(acc, term, %{})
Map.put(acc, term, Map.put(postings, id, tf))
end)
end)
end
def search(documents, index, query) do
n = length(documents)
terms = tokenize(query) |> Enum.uniq()
scores =
Enum.reduce(terms, %{}, fn term, acc ->
case Map.fetch(index, term) do
{:ok, postings} ->
df = map_size(postings)
idf = :math.log(n / df)
Enum.reduce(postings, acc, fn {id, tf}, scores ->
Map.update(scores, id, tf * idf, &(&1 + tf * idf))
end)
:error ->
acc
end
end)
by_id = Map.new(documents, fn %{id: id} = document -> {id, document} end)
scores
|> Enum.map(fn {id, score} -> {Map.fetch!(by_id, id), score} end)
|> Enum.sort_by(fn {_document, score} -> score end, :desc)
end
end
A consulta sem tokens, ou composta apenas por termos desconhecidos, produz uma lista vazia. O mesmo ocorre com uma coleção vazia. Se um termo aparecer em todos os documentos, seu IDF será zero nesta fórmula; ele não altera a ordem por si só. Empates não têm um critério secundário definido aqui. A pontuação não normaliza pelo comprimento do documento.
Do these 3 things before closing this tab:
1Repair Windows errors before they cause bigger problems2Scan for outdated or missing drivers - takes under a minute3Clear out junk files and repair common Windows errorsConfira os resultados à mão
Considere três documentos: ID 1 com “elixir elixir busca”, ID 2 com “elixir busca busca” e ID 3 com “elixir”. Para a consulta “elixir busca”, OR recupera os três. elixir aparece nos três documentos, então df=3 e log(3/3)=0; busca aparece em dois, então df=2 e seu peso IDF é log(3/2). A pontuação do ID 1 é 1 × log(3/2); a do ID 2 é 2 × log(3/2); a do ID 3 é zero. Assim, ID 2 fica à frente de ID 1, e o ID 3 permanece candidato com pontuação zero.
Para “fantasma”, não há posting e a busca retorna lista vazia. Isso é diferente de um termo comum que esteja no índice, mas tenha IDF zero: nesse caso há candidatos, ainda que a contribuição seja zero.
Independent reader supportYour contribution helps us test, update, and keep practical guides available for everyone.O que a fórmula ensina — e o que não promete
TF-IDF é uma etapa pedagógica clara, não uma fórmula única. A variante acima usa frequência bruta, logaritmo natural e nenhum smoothing ou ajuste por comprimento. Outras convenções transformam a frequência, suavizam o IDF e normalizam documentos. A API TFIDFSimilarity do Apache Lucene 7.2.0 documenta, nessa versão, uma variante que inclui TF por raiz quadrada, IDF suavizado baseado em docCount e docFreq, além de fator de normalização de comprimento (API TFIDFSimilarity 7.2.0). Essa versão é um exemplo concreto de escolhas possíveis, não uma declaração sobre a fórmula padrão das versões atuais do Lucene.
O Elasticsearch informa que BM25 é seu modelo de relevância padrão e o descreve como uma variação de TF-IDF (documentação de similaridade do Elasticsearch). Em termos gerais, BM25 satura o ganho de frequência em vez de aumentar linearmente sem limite e incorpora normalização por comprimento; parâmetros como k1 e b controlam esses comportamentos. A configuração e a versão efetivamente usadas podem alterar o comportamento. Para este índice didático, a frequência é linear, não há normalização de comprimento e TF-IDF foi escolhido pela transparência, não por ser automaticamente a escolha de produção.
Best Value
Quando usar Enum e quando considerar Stream
Para a coleção pequena em memória deste exemplo, Enum deixa explícito cada passo e consome imediatamente os enumeráveis. Elixir também oferece Stream para pipelines preguiçosos, úteis quando as etapas podem ser processadas sob demanda em coleções maiores. A documentação da linguagem trata ainda de redução e suspensão de enumeráveis e de pipelines associados a recursos (documentação de Enum e Stream no branch principal do Elixir).
Trocar simplesmente uma chamada por Stream não torna todo o índice limitado em memória: o mapa invertido final continua crescendo conforme os termos e documentos indexados. Se a origem forem arquivos ou outro recurso, prefira APIs que cuidem do encerramento do recurso; e use streaming quando ele evitar materializar etapas intermediárias desnecessárias.
Quick Recap
Product prices and availability are accurate as of the date/time indicated and are subject to change. Any price and availability information displayed on Amazon at the time of purchase will apply.

