Skip to content

Commit 47107ed

Browse files
committed
implement fuzzy search
1 parent 6ef434d commit 47107ed

File tree

2 files changed

+61
-35
lines changed

2 files changed

+61
-35
lines changed

lib/cardian/card_registry.ex

Lines changed: 55 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -10,12 +10,14 @@ defmodule Cardian.CardRegistry do
1010
end
1111

1212
def get_card_by_id(id) do
13-
GenServer.call(:card_registry, {:get_card_by_id, id})
13+
:cards
14+
|> :ets.lookup(id)
1415
|> Enum.map(&elem(&1, 1))
1516
end
1617

1718
def get_set_by_id(id) do
18-
GenServer.call(:card_registry, {:get_set_by_id, id})
19+
:sets
20+
|> :ets.lookup(id)
1921
|> Enum.map(&elem(&1, 1))
2022
end
2123

@@ -27,16 +29,27 @@ defmodule Cardian.CardRegistry do
2729
end
2830

2931
def get_cards() do
30-
GenServer.call(:card_registry, :get_cards)
32+
:cards
33+
|> :ets.tab2list()
34+
|> Enum.map(&elem(&1, 1))
3135
end
3236

3337
def search_card(query) when is_binary(query) do
34-
q = normalize_string(query)
38+
query
39+
|> normalize_string()
40+
|> then(&[&1 | tokenize_string(&1)])
41+
|> Stream.flat_map(&:ets.lookup(:index, &1))
42+
|> Stream.flat_map(&elem(&1, 1))
43+
|> Enum.frequencies()
44+
|> Enum.sort_by(&elem(&1, 1), :desc)
45+
|> Enum.flat_map(&get_card_by_id(elem(&1, 0)))
46+
end
3547

36-
get_cards()
37-
|> Stream.filter(&String.contains?(normalize_string(elem(&1, 1).name), q))
38-
|> Stream.map(&elem(&1, 1))
39-
|> Enum.to_list()
48+
defp tokenize_string(string) when is_binary(string) do
49+
string
50+
|> String.graphemes()
51+
|> Stream.chunk_every(3, 1, :discard)
52+
|> Enum.map(&Enum.join(&1))
4053
end
4154

4255
defp normalize_string(string) when is_binary(string) do
@@ -50,48 +63,36 @@ defmodule Cardian.CardRegistry do
5063
@impl true
5164
def init(_) do
5265
Logger.info("Starting Card Registry")
53-
cards = :ets.new(:cards, [:set, :protected, :named_table, read_concurrency: true])
54-
sets = :ets.new(:sets, [:set, :protected, :named_table, read_concurrency: true])
66+
:cards = :ets.new(:cards, [:set, :protected, :named_table, read_concurrency: true])
67+
:sets = :ets.new(:sets, [:set, :protected, :named_table, read_concurrency: true])
68+
:index = :ets.new(:index, [:set, :protected, :named_table, read_concurrency: true])
5569

5670
Process.send(self(), :update_registry, [])
57-
{:ok, {cards, sets}}
71+
{:ok, nil}
5872
end
5973

6074
@impl true
61-
def handle_call({:get_card_by_id, id}, _from, {cards, sets}) do
62-
{:reply, :ets.lookup(cards, id), {cards, sets}}
63-
end
64-
65-
@impl true
66-
def handle_call({:get_set_by_id, id}, _from, {cards, sets}) do
67-
{:reply, :ets.lookup(sets, id), {cards, sets}}
68-
end
75+
def handle_info(:update_registry, _) do
76+
update_cards()
77+
update_sets()
78+
generate_index()
6979

70-
@impl true
71-
def handle_call(:get_cards, _from, {cards, sets}) do
72-
{:reply, :ets.tab2list(cards), {cards, sets}}
73-
end
74-
75-
@impl true
76-
def handle_info(:update_registry, {cards, sets}) do
77-
update_cards(cards)
78-
update_sets(sets)
7980
schedule_update()
80-
{:noreply, {cards, sets}}
81+
{:noreply, nil}
8182
rescue
8283
err ->
8384
Logger.error(Exception.format(:error, err, __STACKTRACE__))
8485
Logger.error("Update failed. Rescheduling...")
8586
schedule_update()
86-
{:noreply, {cards, sets}}
87+
{:noreply, nil}
8788
end
8889

89-
defp update_sets(sets) do
90+
defp update_sets() do
9091
new_sets = Masterduelmeta.get_all_sets()
9192

9293
true =
9394
:ets.insert(
94-
sets,
95+
:sets,
9596
[
9697
{"61fc6622c491eb1813d4c85c",
9798
%Cardian.Model.Set{
@@ -110,12 +111,32 @@ defmodule Cardian.CardRegistry do
110111
Logger.info("Sets updated")
111112
end
112113

113-
defp update_cards(cards) do
114+
defp update_cards() do
114115
new_cards = Masterduelmeta.get_all_cards()
115-
true = :ets.insert(cards, Enum.map(new_cards, &{&1.id, &1}))
116+
# true = :ets.insert(:cards, Enum.map(new_cards, &{&1.id, &1, tokenize_string(&1.name)}))
117+
true = :ets.insert(:cards, Enum.map(new_cards, &{&1.id, &1}))
116118
Logger.info("Cards updated")
117119
end
118120

121+
defp generate_index() do
122+
index =
123+
get_cards()
124+
|> Stream.flat_map(fn card ->
125+
card.name
126+
|> normalize_string()
127+
|> then(&[&1 | tokenize_string(&1)])
128+
|> Stream.map(&%{token: &1, id: card.id})
129+
end)
130+
|> Enum.reduce(%{}, fn elem, acc ->
131+
Map.update(acc, elem.token, MapSet.new([elem.id]), &MapSet.put(&1, elem.id))
132+
end)
133+
|> Map.to_list()
134+
135+
true = :ets.insert(:index, index)
136+
137+
Logger.info("Index generated")
138+
end
139+
119140
defp schedule_update() do
120141
Process.send_after(
121142
:card_registry,

lib/cardian/interactions.ex

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -36,7 +36,12 @@ defmodule Cardian.Interactions do
3636
data: %{name: "card", options: [%{name: "name", value: query, focused: true}]}
3737
} = interaction
3838
) do
39-
cards = CardRegistry.search_card(query)
39+
cards =
40+
if String.length(query) > 0 do
41+
CardRegistry.search_card(query)
42+
else
43+
[]
44+
end
4045

4146
Api.create_interaction_response!(interaction, %{
4247
type: 8,

0 commit comments

Comments
 (0)