C++
 Computer >> コンピューター >  >> プログラミング >> C++

C++で実装する検索候補システム:入力文字に応じた商品名サジェスト機能の作り方


問題の概要

商品名の文字列配列 products と、検索語 searchWord が与えられたとします。ここで、searchWord の各文字がタイプされるたびに、products の中から共通の接頭辞(プレフィックス)を持つ商品名を最大3つ提案するモジュールを設計することを目標とします。

候補となる商品が3つより多い場合は、辞書順で最小の3つを返す必要があります。つまり、検索語の各文字が入力されるたびに、その時点での推奨商品リストを求めるのがこの課題です。

入力例と出力例

例えば、入力が次のようになっている場合を考えます。

  • products = ["mobile", "mouse", "moneypot", "monitor", "mousepad"]
  • searchWord = "mouse"

このとき、出力は以下のようになります。

[["mobile","moneypot","monitor"],
 ["mobile","moneypot","monitor"],
 ["mouse","mousepad"],
 ["mouse","mousepad"],
 ["mouse","mousepad"]]

「m」や「mo」の段階では mobile・moneypot・monitor が該当し、「mou」以降は mouse・mousepad だけがマッチするため、このような結果になります。

解法のアプローチ

この問題は、ソート済みの商品リストを接頭辞ごとに事前登録しておくことで、効率よく解くことができます。手順は以下の通りです。

  • キーが文字列型、値が文字列リスト型のマップ m を定義する。

  • 商品配列 p をあらかじめソートしておく。これにより、マップに登録された順序が自動的に辞書順になる。

  • i を 0 から p のサイズ − 1 まで繰り返す。

    • x := 空文字列

    • j を 0 から p[i] の長さ − 1 まで繰り返す。

      • x := x + p[i][j](1文字ずつ接頭辞を伸ばしていく)

      • m[x] の要素数が3未満であれば、p[i]m[x] のリストに追加する。

  • 結果格納用の文字列の二次元配列 res を作成し、temp := 空文字列とする。

  • i を 0 から s のサイズ − 1 まで繰り返す。

    • temp := temp + s[i](検索語を1文字ずつ伸ばす)

    • resm[temp] を追加する。

  • res を返す。

ポイントは、商品配列を先にソートしておくことで、各接頭辞に対して「辞書順で最小の3件」だけを自然に保持できる点です。これにより、検索時にはマップを参照するだけで即座に答えを取得できます。

C++による実装例

それでは、実際のコードを見て理解を深めましょう。

#include <bits/stdc++.h>
using namespace std;
void print_vector(vector<vector<auto> > v){
    cout << "[";
    for(int i = 0; i<v.size(); i++){
       cout << "[";
       for(int j = 0; j <v[i].size(); j++){
          cout << v[i][j] << ", ";
       }
    cout << "],";
    }
    cout << "]"<<endl;
}
class Solution {
    public:
    vector<vector<string>> suggestedProducts(vector<string>& p,
    string s) {
       map <string, vector < string > > m;
       sort(p.begin(), p.end());
       for(int i = 0; i < p.size(); i++){
          string x = "";
          for(int j = 0; j < p[i].size(); j++){
             x += p[i][j];
             if(m[x].size()<3)m[x].push_back(p[i]);
          }
       }
       vector < vector <string> > res;
       string temp = "";
       for(int i = 0; i < s.size(); i++){
          temp += s[i];
          res.push_back(m[temp]);
       }
       return res;
    }
};
main(){
    vector<string> v =
    {"mobile","mouse","moneypot","monitor","mousepad"};
    Solution ob;
    print_vector(ob.suggestedProducts(v, "mouse"));
}

入力

["mobile","mouse","moneypot","monitor","mousepad"]
"mouse"

出力

[[mobile, moneypot, monitor],[mobile, moneypot, monitor],[mouse,
mousepad],[mouse, mousepad],[mouse, mousepad]]

まとめ

この手法では、すべての商品名の全接頭辞をマップに事前登録するため、計算量は商品数 N、平均的な商品名の長さ L として O(N × L) の前処理と、検索語長 M に対して O(M) の参照で済みます。ECサイトの検索窓などでよく見られる「入力補完(オートコンプリート)」機能の基本的な仕組みであり、Trie(トライ木)を使った実装と並んで、サジェスト機能の代表的な解法の一つです。

  1. C++で文字列をトークン化する方法:stringstreamとgetline()による分割テクニック

    この記事では、C++における文字列のトークン化(分割)の方法について解説します。C言語では、文字配列に対してstrtok()関数を使用することで文字列を分割できましたが、C++ではstd::stringクラスを扱うため、少し異なるアプローチが必要です。C++の機能を活用して文字列を分割するには、まずstd::stringをstringstream(文字列ストリーム)に変換します。その後、getline()関数を使うことで、指定した区切り文字(デリミタ)ごとに文字列を切り出すことができます。getline()関数は、以下の3つの引数を受け取ります。入力元となる文字列ストリーム出力結果を格納する文

  2. C++で文字列をトークン化(分割)する2つの方法を解説

    文字列のトークン化(分割)とは、1つの文字列を区切り文字(スペースやカンマなど)を基準に、複数の部分文字列へ分割する処理のことです。C++では、標準ライブラリだけでもいくつかの方法で実現できます。本記事では、代表的な2つの方法をサンプルコード付きで紹介します。方法1:stringstreamを使って空白で分割する1つ目の方法は、stringstreamを使ってスペースで区切られた単語を順に読み取る方法です。この方法はやや制限がありますが、適切なチェックを加えれば十分に目的を果たすことができます。サンプルコード#include <vector> #include <string