C++でレストランの注文一覧表(ディスプレイテーブル)を作成する方法
レストランで顧客が行った注文を表す配列 orders があるとします。各要素は orders[i] = [cust_name_i, table_num_i, food_item_i] の形式で、cust_name_i は顧客名、table_num_i はテーブル番号、food_item_i は顧客が注文した料理を表します。
このとき、レストランの「ディスプレイテーブル」を返す必要があります。ディスプレイテーブルとは、各行に「どのテーブルがどの料理を何個注文したか」を示す表のことです。1列目にはテーブル番号が入り、残りの列には料理名がアルファベット順に並びます。また、1行目はヘッダー行とし、最初のセルは「Table」、続いて料理名を記述します。
入力例と出力例
たとえば、入力が以下のような場合を考えてみましょう。
orders = [["Amal","3","Paratha"],["Bimal","10","Biryni"],["Amal","3","Fried Chicken"],["Raktim","5","Water"],["Raktim","5","Paratha"],["Deepak","3","Paratha"]]
この場合の出力は次のようになります。
[["Table","Biryni","Fried Chicken","Paratha","Water"],["3","0","1","2","0"],["5","0","0","1","1"],["10","1","0","0","0"]]
テーブル3ではパラータが2個、フライドチキンが1個注文されており、テーブル5ではパラータと水がそれぞれ1個ずつ、テーブル10ではビリヤニが1個注文されていることが一目でわかります。
解決のためのアルゴリズム
この問題を解くためには、以下の手順に従います。
マップ
mを定義する(キー:テーブル番号と料理名、値:注文数)セット
namesを定義する(料理名を格納し、自動的にソートされる)セット
tを定義する(テーブル番号を格納する)注文リストの各要素
itに対して以下を処理する:it[1](テーブル番号)をセットtに挿入するフラグ
ok = trueを設定するokが false の場合は、it[2]を空白文字で分割した配列vを作成し、vの各要素xについてm[it[1], x]を1増やし、xをnamesに挿入するそうでない場合は、
m[it[1], it[2]]を1増やし、it[2]をnamesに挿入する
2次元配列
retを定義する配列
tempを作成し、namesの要素をコピーするtempの先頭に「Table」を挿入するtempをretの末尾に追加する(これがヘッダー行になる)セット
tの各要素it(テーブル番号)に対して以下を処理する:配列
teを定義し、先頭にテーブル番号を挿入するnamesの各要素xに対して、m[it, x]の値を文字列としてteの末尾に追加するteをretの末尾に追加する
配列
retをソートする(ヘッダー行を除き、テーブル番号の数値順に並べる)retを返す
C++での実装例
理解を深めるために、以下の実装を見てみましょう。
#include <bits/stdc++.h>
using namespace std;
void print_vector(vector<vector<string> > 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;
}
typedef long long int lli;
class Solution {
public:
vector <string> split(string& s, char delimiter){
vector <string> tokens;
string token;
istringstream tokenStream(s);
while(getline(tokenStream, token, delimiter)){
tokens.push_back(token);
}
return tokens;
}
static bool cmp(vector <string>& a, vector <string>& b){
lli an = stol(a[0]);
lli bn = stol(b[0]);
return an < bn;
}
vector<vector<string>> displayTable(vector<vector<string>>& o) {
map <string, map < string, int> >m;
set <string> names;
set <string> t;
for(auto &it : o){
t.insert(it[1]);
bool ok = true;
if(!ok){
vector <string> v = split(it[2], ' ');
for(auto& x : v){
m[it[1]][x]++;
names.insert(x);
}
}
else{
m[it[1]][it[2]]++;
names.insert(it[2]);
}
}
vector < vector <string> > ret;
vector <string> temp(names.begin(), names.end());
temp.insert(temp.begin(), "Table");
ret.push_back(temp);
for(auto& it : t){
vector <string> te;
te.push_back(it);
for(auto& x : names){
te.push_back(to_string(m[it][x]));
}
ret.push_back(te);
}
sort(ret.begin() + 1, ret.end(), cmp);
return ret;
}
};
main(){
Solution ob;
vector<vector<string>> v = {{"Amal","3","Paratha"},{"Bimal","10","Biryni"},{"Amal","3","Fried Chicken"},{"Raktim","5","Water"},{"Raktim","5","Paratha"},{"Deepak","3","Paratha"}};
print_vector(ob.displayTable(v));
}
入力
{{"Amal","3","Paratha"},{"Bimal","10","Biryni"},{"Amal","3","Fried Chicken"},{"Raktim","5","Water"},{"Raktim","5","Paratha"},{"Deepak","3","Paratha"}}
出力
[[Table, Biryni, Fried Chicken, Paratha, Water],[3, 0, 1, 2, 0],[5, 0, 0, 1, 1],[10, 1, 0, 0, 0]]
実装のポイント
このコードの重要なポイントをいくつか紹介します。
std::map と std::set の活用: C++の
mapとsetは要素を自動的にソートして保持するため、料理名のアルファベット順管理が容易になります。カスタム比較関数: テーブル番号は文字列として保存されているため、そのままソートすると辞書順になってしまいます。
stol()関数で数値に変換してから比較することで、正しく数値順にソートできます。ネストされたマップ構造:
map<string, map<string, int>>という二重マップを使うことで、「テーブル番号 → 料理名 → 注文数」という階層的なデータを効率的に管理できます。
このアルゴリズムの計算量は、注文数を N、テーブル数を T、料理の種類数を F とすると、O(N + T×F) となります。非常に効率的な解法です。
-
C++で文字列をトークン化する方法:stringstreamとgetline()による分割テクニック
この記事では、C++における文字列のトークン化(分割)の方法について解説します。C言語では、文字配列に対してstrtok()関数を使用することで文字列を分割できましたが、C++ではstd::stringクラスを扱うため、少し異なるアプローチが必要です。C++の機能を活用して文字列を分割するには、まずstd::stringをstringstream(文字列ストリーム)に変換します。その後、getline()関数を使うことで、指定した区切り文字(デリミタ)ごとに文字列を切り出すことができます。getline()関数は、以下の3つの引数を受け取ります。入力元となる文字列ストリーム出力結果を格納する文
-
C++で文字列をトークン化(分割)する2つの方法を解説
文字列のトークン化(分割)とは、1つの文字列を区切り文字(スペースやカンマなど)を基準に、複数の部分文字列へ分割する処理のことです。C++では、標準ライブラリだけでもいくつかの方法で実現できます。本記事では、代表的な2つの方法をサンプルコード付きで紹介します。方法1:stringstreamを使って空白で分割する1つ目の方法は、stringstreamを使ってスペースで区切られた単語を順に読み取る方法です。この方法はやや制限がありますが、適切なチェックを加えれば十分に目的を果たすことができます。サンプルコード#include <vector> #include <string