@m_seki の

I like ruby tooから引っ越し

カードを含まない検索を可能にしたポケカエンジン

AIタイトルアシスト使ったぞ!

sv5からEが落ちる

RubyKaigi2022, 2023で話したポケカ検索エンジンの話。

rubykaigi.org

masaki.druby.work

次の新弾のタイミングで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便利だな。

できたもの

masaki.druby.work

カード番号をスペース区切りで並べると、and検索になり、さらにカード番号を負にする(数値の前に-をつける)とそのカードを含まない検索ができます。 このUIはひどいのであとでそれらしいUIを用意しよう。

検索の例

検索窓にこんな感じで入れる。

45134 -44424

サケブシッポのないサーナイトex。

44041 -40657 45134

ミラージュステップなキルリアがいないサーナイトex。