AIタイトルアシスト使ったぞ!
sv5からEが落ちる
RubyKaigi2022, 2023で話したポケカの検索エンジンの話。
次の新弾のタイミングでEのカードが落ちるのもあって、Masakiに特定のカードを含まない検索、をできるようにした。
もともとはデッキのcos類似度から似ているデッキを探すエンジンなのだが、デッキの表現を工夫することでいろいろな検索ができる。
カードID(公式サイトのカード番号)を含むデッキを検索する場合には、そのカードだけが入ったデッキと似ているデッキを探す、という具合である。
def search_by_card(card_id, n=5) card = @id_norm[card_id] # カードの正規化 return [] unless card req = [[card, 1]] # それだけが入っているデッキの表現 make_norm1(req) # 単位ベクトルに変換 search_by_(req, n) end
カードが一枚しかないデッキのベクトルは次のようになる。
[ [カード番号, 1] ]
A, Bのカードを含むデッキであればこう。
[ [A, 1], [B, 1] ]
では含まないデッキの表現はどうするか。その次元(カードの番号)の成分を負にすれば良い。 ただし、リジェクトするカードの成分を大きくしておかないと、Aが複数枚あるときに負けて(?)しまうので-15とした。 Aを含みBを含まないデッキは次のようなベクトルになる。
[ [A, 1], [B, -15] ]
最終的には次のようになった。
def search_by_card(card_id_list, n=5) want = Set.new omit = Set.new card_id_list.each {|card_id| card = @id_norm[card_id.abs] return [] unless card if card_id > 0 want << card else omit << card end } return [] if want.intersect?(omit) req = want.map {|x| [x, 1]} + omit.map {|x| [x, -15]} make_norm1(req) search_by_(req, n).find_all {|s, n| c = Set.new(@deck[n].map(&:first)) (! c.intersect?(omit)) && (want.subset?(c)) } end
デッキのベクトル表現だけでは意図しないデッキも見つかってしまう(似ているデッキが少ないと類似度が低いものも見つけてしまうため)。 最後にクエリにあったカードが確かに含まれているか、確かにリジェクトされているかをチェックするようにした。 Set便利だな。
できたもの
カード番号をスペース区切りで並べると、and検索になり、さらにカード番号を負にする(数値の前に-をつける)とそのカードを含まない検索ができます。 このUIはひどいのであとでそれらしいUIを用意しよう。
検索の例
検索窓にこんな感じで入れる。
45134 -44424
サケブシッポのないサーナイトex。
44041 -40657 45134