【Python】マクドナルドのコンパイラを再実装する。

この記事は、[筑波NSミライラボ Advent Calendar 2023](https://qiita.com/advent-calendar/2023/tsukuba-ns-mirailabo)、2日目の記事です。

# はじめに
こんにちは！N高等学校通学プログラミングコースに所属している[@HINOKAGUZUTI](https://qiita.com/HINOKAGUZUTI)です！
普段はゲーム制作（Unity）やC、Pythonでのアルゴリズム制作などを行っています。
今回はマクドナルドコンパイラの制作について記事を書こうと思います。Qiita初投稿なので拙い部分も多いかと思いますが、どうか最後までお付き合いください。
# マクドナルドを知らない方に向けて
アメリカ・カルフォルニア発祥のハンバーガーチェーン。全世界に36000以上の店舗があり、ファーストフードの中でもトップクラスの知名度があるお店です。関西ではマクド、関東ではマックと呼び名が違ったりもするのですが、その最大の特徴は幅広い値段帯と、研究され尽くしたオペレーションによる、提供の速さにあります。
今回はその中でもオペレーションの部分に着目した記事になります。

ちなみに私は現役のマクドナルドクルーで、普段は学校へ行く前の朝0530~0730まで働いてから梅田にあるキャンパスに通っています。

# 制作の経緯〜そもそもマクドナルドコンパイラってなんぞや〜
今回NSミライラボでアドカレを書くことになり、初めは適当に今までの作品を紹介しようかとも考えていたのですが、その話題をプログラミングクラスで話していたところ、私が一年生の頃の三年生が書いていた、[Javaで湯婆婆を実装してみる](https://qiita.com/Nemesis/items/c7192a7c510788d2cba2)と言う記事の話になりまして。

せっかくだから、~~この記事を勝手にパクって…~~ 先輩の記事をリスペクトして記事を書いてみようと言うことになりました。そこで身近にある、アルゴリズムを探し、マクドナルドを題材にしてみました。

### マクドナルドのアルゴリズム
突然ですが、皆さんはマクドナルドで商品を注文してから受け取るまでの流れをご存知ですか？簡単にその流れをフローチャートで書いてみました。
![スクリーンショット 2023-11-28 14.34.57.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3622119/9994f639-065b-3aa7-8c3d-25bc3267c453.png)

見てもらえればわかるかと思いますが、マクドナルドって割とちゃんとアルゴリズムしてるんですよ。オーダーを必要に応じて、店の各所に送ったり、そこから商品を流して、集めたりと役割の最適化が行われています。個人的にはオーダーから、バーガー・サイドとドリンクのデータを抽出してそれぞれのポジションに送り、全てのリストを商品を取り揃えるカウンターに送る部分がとてもアルゴリズム感が満載で、お気に入りです。

ところで、先ほどからマクドナルドのコンパイラと言っている部分が一体どこなのか、そろそろ気になってきたのではないでしょうか？

# マクドのコンパイラ、作っていくで！！
それでは、正解発表のお時間です。
正解はもちろんこの場所、
![スクリーンショット 2023-11-28 14.19.48.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3622119/e6eb670e-19df-ac3c-13af-f2d0007b5312.png)
レジの部分ですね！
流れとしては、まずお客さんから自然言語でオーダーを受け取ります。（主に日本語、英語。流石に中国語で来られると対応できん。）
そして、店員は文章解析を行い、抽出された単語を元に、レジに注文を打ち、マクドナルドのCPUとなる、厨房にわかるようなデータの形にしていきます。
最後にできたデータをCPU(厨房)に実行させるというものです。

ほら！これはもうコンパイラと言って差し支えがないでしょう！？（厳密には少し違うのでは？と考えたそこのあなた。こんな馬鹿馬鹿しいことを真面目に考えたって無駄です。あきらめましょう。）

## 大まかな会話の流れを作ろう！
今回は自然言語が日本語だった場合のみを考えることとします。
マクドナルドのレジの会話といえば大体こんな感じ。

店員「いらっしゃいませ、こんにちは。ご注文をお伺いします。」
客　　オーダーを言う
店員「他に何かご注文はございませんか？」
客　　オーダーを言うor「大丈夫です。」
店員「お会計は。。。続く」
店員「こちらレシートになります。番号でお呼びいたしますので、モニター前でお待ちください。」

ベテランであれば、期間限定の商品の販促でもするのだろうが私にはなかなか難易度が高い。

お会計の部分はコンパイルとはあまり関係がないので今回は省こうと思う。また、レシートの内容と厨房に届く内容はほぼ変わらないので、今回は完成したオーダーのリストをレシートとして、出力しようと思う。
```python:McDonald_Compiler.py
order_list = []#完成したオーダーのリスト
order = input('いらっしゃいませ、こんにちは。ご注文をお伺いします。')
#Todo orderで検出したメニューをorder_listに追加
while True:
  order = input('他に何かご注文はございませんか？')
  """
  Todo
  メニューがあった場合 orderで検出したメニューをorder_listに追加
  大丈夫だった場合 break
  """
input('お会計はこちらになります。。。.(Enterで次に進む)')
print('こちらレシートになります。番号でお呼びいたしますので、モニター前でお待ちください。')
#Todo 完成したオーダーリストを出力
```
一旦大枠を作ってみた。まず最初に注文をinput()で受付。追加注文の確認は大丈夫と言われるまで繰り返す。お会計のくだりをEnterで飛ばした後、セリフを言って出力をすると言う簡単な流れです。

実行するとこんな感じ
![スクリーンショット 2023-11-28 20.33.09.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3622119/7c7fc859-036f-d264-e222-645eef9eef4f.png)

breakの処理ができていないので、他の何かご注文はございませんか？の無限ループが起きます。

## 入力された値から注文を抽出してみる
今回は拡張性を持たせたいので、メニューのリストを作成し、for文で回して、一つ一つin拡張子で判定を行っていく
```
for menu in list:
    if menu in order:
        order_list.append(order)
```
こんなの。実装したのがこんなの。
```diff_python:McDonald_Compiler.py
+#サンドイッチのリスト
+sandwichmenu = ['ハンバーガー','チーズバーガー','ダブルチーズバーガー','テリヤキバーガー','フィレオフィッシュ','ビックマック']
+#サイドメニューのリスト
+sidemenu = ['ポテト','ナゲット','アップルパイ','エダマメコーン','シャカチキ','スマイル']
+#ドリンクのリスト
+drinkmenu = ['コカコーラ','Qoo','ホットコーヒー','アイスコーヒー','アイスティー','ミルク']
+
+all_menus = [sandwichmenu,sidemenu,drinkmenu]

order_list = []#完成したオーダーのリスト
order = input('いらっしゃいませ、こんにちは。ご注文をお伺いします。')
+for menu_list in all_menus:
+  for menu in menu_list:
+    if menu in order:
+        order_list.append(menu)
while True:
-  order = input('他に何かご注文はございませんか？')
+  order = input('他に何かご注文はございませんか？(追加オーダーがない場合はなしと入力してください。)')
+  if order == 'なし':
+    break
+  for menu_list in all_menus:
+    for menu in menu_list:
+      if menu in order:
+        order_list.append(menu)

input('お会計はこちらになります。。。.(Enterで次に進む)')
print('こちらレシートになります。番号でお呼びいたしますので、モニター前でお待ちください。')
#Todo 完成したオーダーリストを出力
```
メニューのリストをいくつか用意して、for文で回していくような実装にしてみました。それぞれのリスト内のメニューをまたforで回して、orderの中にメニューと一致するものがあればメニューをorder_listに追加するというものになっています。

## 出力部分を作る。
とは言ってもfor 変数 in リストの形をまた利用してチャチャっと実装するだけです。pythonは便利ですな。
```diff_python:McDonald_Compiler.py
input('お会計はこちらになります。。。.(Enterで次に進む)')
print('こちらレシートになります。番号でお呼びいたしますので、モニター前でお待ちください。')
+for menu in order_list:
+  print(menu)
```
## テストをしてみる
`python3 McDonald_Compiler.py`でコードを実行する。
![スクリーンショット 2023-11-30 10.57.50.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3622119/aa87f2a0-2edd-fd88-79e3-f0748f6c7dc5.png)

実行するとセリフが出力され、inputを受け付けるようになる。今回はハンバーガー、ポテト、コーラを注文してみた。

![スクリーンショット 2023-11-30 10.58.26.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3622119/d8a49250-21b5-e5e1-8efd-c13f3880195a.png)

Enterキーを押すと追加の注文をせびられる。

![スクリーンショット 2023-11-30 10.58.55.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3622119/e24e352a-63ea-5bc5-ce26-ef4b6cd91306.png)

仕方がないので今回はナゲットを一つ追加した。

![スクリーンショット 2023-11-30 10.59.08.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3622119/49a0c995-1d84-9f5d-c822-e8db5633e069.png)

またせびられたが、お金がないのでキッパリと断っておいた。
お会計をEnterでスキップすると、しっかりとリストが出力された。アルゴリズムはしっかりと動いているようだ。

では、同じ注文を二度繰り返して言うとどうなるだろうか。

![スクリーンショット 2023-11-30 11.33.40.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3622119/cc7ca3dc-41b6-2a9f-4eba-330105a2267b.png)
ハンバーガーのセットを二つ頼んだつもりがハンバーガーが一つしかリストに追加されていない。
## 修正タイム
in 演算子を利用すると最初にメニューを検出した時点で処理が終わってしまうので,今回は`count()`を使って書いていきます。
```diff_python:McDonald_Compiler.py
#サンドイッチのリスト
sandwichmenu = ['ハンバーガー','チーズバーガー','ダブルチーズバーガー','テリヤキバーガー','フィレオフィッシュ','ビックマック']
#サイドメニューのリスト
sidemenu = ['ポテト','ナゲット','アップルパイ','エダマメコーン','シャカチキ','スマイル']
#ドリンクのリスト
drinkmenu = ['コカコーラ','Qoo','ホットコーヒー','アイスコーヒー','アイスティー','ミルク']

all_menus = [sandwichmenu,sidemenu,drinkmenu]

order_list = []#完成したオーダーのリスト
-order = input('いらっしゃいませ、こんにちは。ご注文をお伺いします。')
+order = input('いらっしゃいませ、こんにちは。ご注文をお伺いします。\n')
for menu_list in all_menus:
  for menu in menu_list:
-    if menu in order:
-       order_list.append(menu)
+    quantity = order.count(menu)
+    if quantity > 0:
+      order_list.append(f'{quantity} {menu}')
while True:
-  order = input('他に何かご注文はございませんか？(追加オーダーがない場合はなしと入力してください。)')
+  order = input('他に何かご注文はございませんか？(追加オーダーがない場合は文中に以上と入力してください。)\n')
-  if order == 'なし':
-   break
  for menu_list in all_menus:
    for menu in menu_list:
-      if menu in order:
-       order_list.append(menu)
+      quantity = order.count(menu)
+      if quantity > 0:
+        order_list.append(f'{quantity} {menu}')
+  if '以上' in order:
+    break

input('お会計はこちらになります。。。.(Enterで次に進む)')
print('こちらレシートになります。番号でお呼びいたしますので、モニター前でお待ちください。')
#Todo 完成したオーダーリストを出力
```
`quantity`にorder内で検出したmenuの数を代入し、quantityが1以上であれば、quantityとmenuをorderに追加すると言う実装にした。

ついでに、セリフの後ろオーダーを入力するのが見辛かったので改行を入れた。
また、追加オーダーをしたあと、Enterを押したあとなしと入力しなければならないのがめんどくさかったのと、なしのみを入力するのは冷たい感じがしたため、以上が文中にあればその回で追加オーダーの確認が終わるように実装した。

なしの場合は、氷なしなどで終了してしまう恐れがあったため、完全一致にしていたが、以上の場合はそのような事故はほぼ起こらないかと思う。

![スクリーンショット 2023-11-30 11.47.11.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3622119/a36765e0-be86-c673-f6c7-d0bbf2d47d55.png)
しっかりと個数が記載されるようになった。いつもバイトで見るものとほぼ同じだ。これならクルーもデータを見た途端に動き出すだろう。
改行や以上の検出もうまくいっていそうだ。
ただ、数字の認識ができないため、シャカチキを一度に二つ頼みたい時は『シャカチキとシャカチキをください。』のように注文しなければならず、少し不自然な文章になってしまう。ただし、今回は時間がないので割愛させていただく。

また、今回のメニューにはないが、朝マックのハッシュポテトなどがあった場合、ハッシュポテトとポテトが検出されてしまう問題が発生してしまうことが考えられる。
![スクリーンショット 2023-11-30 12.12.25.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3622119/28fe50f1-f153-8d81-8f61-be2aa48e603c.png)

一応簡単に実装する方法としては、マクドナルドのメニューが基本的にカタカナなのを利用して、検出したメニューの前後の文字がカタカナになっていないかを検出するなどの方法が考えられるが、ポテトとハッシュポテトは同じ時間帯に存在しないため、今回は割愛させていただく。

追記）ダブルチーズバーガーとチーズバーガーに関しては見逃してください、、、完全に忘れてました。。

最終的に出来上がったコードがこちら
```python:McDonald_Compiler.py
#サンドイッチのリスト
sandwichmenu = ['ハンバーガー','チーズバーガー','ダブルチーズバーガー','テリヤキバーガー','フィレオフィッシュ','ビックマック']
#サイドメニューのリスト
sidemenu = ['ポテト','ナゲット','アップルパイ','エダマメコーン','シャカチキ','スマイル']
#ドリンクのリスト
drinkmenu = ['コカコーラ','Qoo','ホットコーヒー','アイスコーヒー','アイスティー','ミルク']

all_menus = [sandwichmenu,sidemenu,drinkmenu]

order_list = []#完成したオーダーのリスト
order = input('いらっしゃいませ、こんにちは。ご注文をお伺いします。\n')
for menu_list in all_menus:
  for menu in menu_list:
    quantity = order.count(menu)
    if quantity > 0:
      order_list.append(f'{quantity} {menu}')
while True:
  order = input('他に何かご注文はございませんか？(追加オーダーがない場合は文中に以上と入力してください。)\n')
  for menu_list in all_menus:
    for menu in menu_list:
      quantity = order.count(menu)
      if quantity > 0:
        order_list.append(f'{quantity} {menu}')
  if '以上' in order:
    break

input('お会計はこちらになります。。。.(Enterで次に進む)')
print('こちらレシートになります。番号でお呼びいたしますので、モニター前でお待ちください。')
for menu in order_list:
  print(menu)
```
私が書いたにしては上出来なコードだろう。
# 作った感想
今回はあまり時間がなかったため、マクドナルドコンパイラの基礎の部分しか実装することができなかったが、もし、さらに完成度を上げるのであれば、セットやコンビの認識、数字の認識や販促などの機能を追加しても良いと思います。
マクドナルドのアルゴリズムを解析したり、実装をするのは割と楽しかったです。皆さんも身近なアルゴリズムを再実装してみるのはいかがでしょうか？

もしかしたら続編を作るかもしれないので、気になる方はあまり期待をせず待っていてください。
# 終わりに
マクドナルドのアルゴリズムはいかがでしたでしょうか？　私は起業に興味があり、もともとは大企業の仕組みや、スタッフの育成・管理などを学びたかったのでマクドナルドでバイトを始めたのですが、プログラミングの目線から見てもマクドナルドは興味深いことがたくさんありました。

この記事を読んでいる皆さんも次にマクドナルドに行く機会があった時には、オーダーが入力され、次々に関数が発火されていく様子を眺めながら、お食事をごゆっくりお楽しみいただければと思います。

ここまで読んでいただきありがとうございました！
明日は[@sh_moc_work](https://qiita.com/sh_moc_work)さんの記事です。
# おまけ
スマイルが注文された際に、ただリストにスマイルを追加するだけなのは寂しいので、実際にスマイルを提供するようにしてみた。
```diff_python:McDonald_Compiler.py
if quantity > 0:
      order_list.append(f'{quantity} {menu}')
+      if menu == 'スマイル':
+        print('(≧∇≦*)')
```
スマイルだった場合のみ笑顔を出力するようにした。

![スクリーンショット 2023-11-30 12.29.50.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3622119/c895564c-2c9f-beb6-2172-b4ec6dc4eac3.png)

本当にとんだ迷惑である。流石にここまでの客はいないし、実際はスマイルの注文も滅多に入らない。私も今までに一度だけスマイルを頼まれたことがあってドキドキしていたのですが、よくオーダーを見てみると、Uber Eatsでの注文でしたw。（仕方がないので商品リストにニコちゃんマークを書きましたw)