完全栄養マクドナルド食の線型計画による実装～もしマクドナルドだけで生活すると栄養バランスはどうなるのか？～


# 背景と概要

マクドナルドが大好きである。
しかし、ジャンクフード、健康に悪い、
などという話は以前よりよく見かける。

では本当にマクドナルドを食べ続けると、
健康に悪いのだろうか？
マクドナルドだけで生活する場合本当に、
栄養の偏りやカロリー過剰などが発生するのだろうか？

本稿は、マクドナルドだけで
**一日に必要なすべての栄養素を摂取する食事**
をする場合に、どのようなメニューを選ぶべきで、
その結果どのような栄養問題が生じるのか、
PuLPというPythonの線型計画ライブラリを用いて
研究した結果をまとめたものである。

すなわち、**マクドナルドだけで**
**完全栄養食としてのメニューを組み立てるには**
**何をどれだけ食べればいいの？** 
**そしてその時何カロリーになるの？** という
世の中の0.001%くらいの人が一度は疑問に
思ったことがある問題に対して解を与える。

また、栄養食的な代表選手ということで、
**牛乳の栄養素を含んだ上での五角形の形**
になっていることで高名な
コーンフレーク（こちらも大好きである）を
メニューに加えた場合の考察も追加する。

「無人島にマクドナルドだけ持って行っていい」と
言われた場合にとても役立つ研究であることを確信している。

# 実装の基本方針

まず、マクドナルドの全メニューの
栄養情報については、
以下の公式サイトにて公開されている。

https://www.mcdonalds.co.jp/products/nutrition_balance_check/

こちらのサイトでメニューを選び、
自分の性別・年齢・身体活動レベルを
入力することで、一日に必要な栄養素に対して、
どの程度満たすか？を確認することが可能だ。

本研究は、つまりコレを、
**一日分すべての栄養素を１００％満たしつつ、**
**塩分は基準値以下となるように調整し、**
**かつカロリーは出来るだけ少なくする、**
ようなメニュー選択の最適解を目指すことになる。

一見難しそうな問題に見えるものの、
実は単純な**線型計画の問題**であり、
PythonのPuLPライブラリで一発で解決できる。

一番工夫すべき点は、データや変数が多いために、
問題をうまくコードに落とすのが超面倒なこと。
「teriyaki-ba-ga」という変数とか、
「 VitaminC > 100」みたいな式を大量に書いていたら、
夢までも「たらったったったー」で
埋め尽くされるのは間違いない。
月見バーガーなどのメニュー追加にも
対応しにくくなってしまう。

その点に留意しながら、以下のような段取りで研究を進める。

①線型計画問題とはなにか？
②PuLPの基本的な使い方を確認
③マクドナルドの公式サイトからデータを取得し、
　Pythonで加工できるようにする
④一日に必要な栄養素＝完全栄養食問題として、
　コードを実装する。（うまく汎用的に作る）
⑤いくつか条件を変えながら結果を見て遊ぶ
⑥１メニュー１回まで制限にして遊ぶ
⑦コーンフレークをメニューに追加してさらに遊ぶ

# ①線型計画問題とは？

|-|カロリー|栄養１|栄養２|栄養３|
|:--|--:|--:|--:|--:|
|てりやき| 20| 25| 10| 15|
|ポテト|12 |11| 10| 18|
|コーラ|15| 13| 16|25|
| 必要栄養量| ★ここを最小に|300| 200| 100|



■問題：上記のような栄養表があるときに、
栄養素１～３の必要量を満たしつつ、
カロリーを最小にするような、
てりやき、ポテト、コーラの注文の仕方は何か？

⇒答え：
てりやき×８、ポテト×１、コーラ×７
で 298カロリー

このように、何かの最適解を求める問題において、
目的関数（カロリー）や、制約条件（満たすべき栄養素）が
線型（二乗とかが出ない一次元の多項式）の不等式で
表現できる問題のことを、**線型計画問題**と言う。

PythonではPuLPというライブラリが用意されており、
この問題をコードで記載するだけで
一発で最適解を求めることが出来る。

# ②PuLPの基本的な使い方

PuLPの使い方を見てみる。
なお以降のコードも全てColaboratory上で実行を試している。

PuLPのインストール方法

```sh:PuLPのインストール
pip install pulp
```

さきほどの例題を解くコードが以下。
一番基本的な使い方を見てみる。

```py:PuLPの一番基本的な使い方
import pulp
# !pip install pulp
# 参考１：https://www.y-shinno.com/pulp-intro/

# 問題の定義
# 最小化か、最大化か、どちらかを指定する
problem = pulp.LpProblem(name="マック", sense=pulp.LpMinimize)
#problem = pulp.LpProblem(name="マック", sense=pulp.LpMaximize)

# 変数の定義（※変数の指定は、pythonは日本語でもOK）
# 物の個数を表現するため、0以上の整数である、と定義している
てりやき = pulp.LpVariable(name = "てりやき", lowBound = 0, cat="Integer")
ポテト = pulp.LpVariable(name = "ポテト", lowBound = 0, cat="Integer")
コーラ = pulp.LpVariable(name = "コーラ", lowBound = 0, cat="Integer")

# 目的関数（最小or最大にすべき関数）
problem += 20 * てりやき + 12 * ポテト + 15 * コーラ

# 制約条件の定義
# 書き方として、必ず、等号を入れて、<=.==,>= などの書き方にすること！
problem += 25 * てりやき + 11 * ポテト + 13 * コーラ >= 300
problem += 10 * てりやき + 10 * ポテト + 16 * コーラ >= 200
problem += 15 * てりやき + 18 * ポテト + 25 * コーラ >= 100

# 問題を解く
status = problem.solve()
print(pulp.LpStatus[status])
#ステータスの全種類は、下記の通り。
#「Optimal」が、最適解が得られた、の意
# {-3: 'Undefined',
#  -2: 'Unbounded',
#  -1: 'Infeasible',
#  0: 'Not Solved',
#  1: 'Optimal'}

# 結果表示
print("Result")
print("てりやき:", てりやき.value())
print("ポテト:", ポテト.value())
print("コーラ:", コーラ.value())
```


```py:出力結果
Optimal
Result
てりやき: 8.0
ポテト: 1.0
コーラ: 7.0
```

一つ目の工夫のポイントは、
**変数名を「日本語」にしている点**。
**Python3では変数名にアスキーコード以外も利用可能**であり、
最初から日本語にしておくことで、
マクドナルドのメニューの商品名称を
そのまま変数名として扱うことが出来る。
逆にこうしないと、てりやきは「teriyaki」で・・・
みたいに、全メニューに英語名を付与して、
しかもそのマッピングを管理しないといけなくなる。
この時点で日が暮れてしまう。

だが、変数を日本語にしただけでは、各栄養素ごとの式を
全て入力していくのは依然としてものすごく大変である。
他のメニューが追加されたときなどに、全ての
`problem += 10 * てりやき + 10 * ポテト + 16 * コーラ >= 200`
の式を更新していくのも**ヤバイことになる**（語彙力不足）

そこで、以下のように、
メニュー名や栄養素の値をリスト形式で扱えるようにする。
コードはだいぶ異なるが、結果や意味は全く同じである。
**これで月見バーガーの季節になっても全く問題はない**。


```py:PuLPの少し汎用的な書き方
import pulp
# !pip install pulp
# 参考２：http://www.nct9.ne.jp/m_hiroi/light/pulp01.html

# 問題の定義(
# 最小化か、最大化か、どちらかを指定
problem = pulp.LpProblem(name="マック", sense=pulp.LpMinimize)
#problem = pulp.LpProblem(name="マック", sense=pulp.LpMaximize)

# データの定義
target_menu_list =["てりやき","ポテト","コーラ"]
kcal =[20,12,15]
eiyou1 =[25,11,13]
eiyou2 =[10,10,16]
eiyou3 =[15,18,25]

# 変数の定義
# 変数の定義を、リスト内包表記で書く、かつ、変数名を動的に、リスト内のデータて定義
xs = [pulp.LpVariable('{}'.format(x), cat='Integer', lowBound=0) for x in target_menu_list]

# 目的関数や制約条件を、行列の掛け算型で書く
# 目的関数（最小or最大にすべき関数）
problem += pulp.lpDot(kcal, xs)

# 制約条件の定義
problem += pulp.lpDot(eiyou1, xs) >= 300
problem += pulp.lpDot(eiyou2, xs) >= 200
problem += pulp.lpDot(eiyou3, xs) >= 100

# 問題を解く
status = problem.solve()
print(pulp.LpStatus[status])

# 結果表示
print("Result")
print("てりやき:", てりやき.value())
print("ポテト:", ポテト.value())
print("コーラ:", コーラ.value())
```

ポイントは、**行列の掛け算型**で式を定義している点と、
てりやき、などの**変数名を動的に名付けている**点。

つまり、
`target_menu_list =["てりやき","ポテト","コーラ"]`
⇒
`target_menu_list =["てりやき","ポテト","コーラ","月見バーガー"]`
のようにメニュー名のリストを更新してあげれば、
`'{}'.format(x)` の箇所で、変数名として使われて、
`月見バーガー`という変数が動的に生成される、というワケ。

これらの工夫をしておかないと、
数行のデータで遊ぶ分には問題ないが、
マクドナルドの実データ数百行には全く対応出来ない。

PuLPというライブラリがありながらも、
**全人類の夢であった完全栄養マクドナルド食の検討**が
今までなされてこなかった理由は、
**普通に作ったらコードがヤバイことになるから**、
が理由に違いない。きっとそうに違いない（反復法による強調）


# ③マクドナルドのデータ入手＆加工

先に挙げたマクドナルドの公式サイトから、
最新全メニューの栄養価一覧表を見ることが出来る。
バーガー、サイド、ドリンク、バリスタ、の４種類。
スクレイピングなどする必要もなく、
単純に４回コピペして、CSV形式で保存しよう。
　※「-」のデータだけ、置換で「0」にしておく

保存したCSVデータは、以下のようなコードで、
Pythonで辞書形式で読み出すことが出来る。

```py:マクドナルドデータの読み込み
import csv
McDonaldsDict = {}
with open('/content/drive/My Drive/MACD/マクドナルド栄養価一覧20201009_R.csv') as f:
    reader = csv.DictReader(f)
    # OrderedDict([('商品名', 'えびフィレオ'), ('重量g', '174'), ・・・が１行ごとに入っている
    # ※ジュース系などで、栄養価が「-」のものは０を置換済み
    for row in reader:
      # 'えびフィレオ' : OrderedDict([('商品名', 'えびフィレオ'), ('重量g', '174')・・・ の辞書形式に加工
      McDonaldsDict[row["商品名"]] = row

```

後で、対象の商品だけ選んで取り出す、
ということがしやすいように、
「商品名」をキーとした辞書形式にしておく。
（※辞書形式の中がさらに辞書型になっている二重辞書）

また、１日に必要な栄養素についても、
データを参照して以下のように定義しておこう。
対象は、~~完全栄養マクドナルド食に興味がありそうな暇人たち~~
本研究の想定読者層に合わせてみた。

```py:必要な栄養素（例）
#男性：30歳～49歳の１日に必要な栄養量。
#身体活動レベル１＝生活の大部分が座位で、
# 静的な活動が中心の場合、で計算
# ただし、食塩相当量は、必要ではなく「以下」にすべき値
one_da_nutrition_dict ={
    "エネルギーkcal" : 2300.0,
    "たんぱく質g" : 65.0 ,
    "脂質g" : 63.9 ,
    "炭水化物g" : 330.6 ,
    "カルシウムmg" : 750.0 ,
    "鉄mg" : 7.5 ,
    "ビタミンAμg" : 900.0 ,
    "ビタミンB1mg" : 1.4 ,
    "ビタミンB2mg" : 1.6 ,
    "ビタミンCmg" : 100.0 ,
    "食物繊維g" : 21.0 ,
    "食塩相当量g" : 7.5 ,
}
```


# ④いよいよ完全栄養マクドナルド食

ここまでで全部の準備が出来た。

マクドナルドのメニューは多様であるが、
「ミルクも入れて完全な５角形」とか
「サラダや野菜ジュースで健康」というのは
**典型的なマクドナルド感が薄い**ので、
まずは代表選手として主観ながら以下のメンバーを選出した。

 * てりやきマックバーガー
 * ハンバーガー
 * チーズバーガー
 * ダブルチーズバーガー
 * 月見バーガー
 * ビッグマック
 * フィレオフィッシュ
 * チキンマックナゲット 5ピース
 * マックフライポテト(M)
 * マックフライポテト(S)
 * ケチャップ
 * バーベキューソース
 * コカ・コーラ(M)
 * マックシェイク® バニラ(S)
 * ミニッツメイド オレンジ(M)

この選択は大規模な宗教論争なることが予想されるため、
他神の信徒の方もいらっしゃるだろうが、一旦異論は認めない。
他宗派の方はぜひ選抜メンバーを変えてコードを追試してみてほしい。


**これらの組み合わせだけで、**
**一日に必要な栄養素を全て摂取し、**
**かつ塩分は基準値以下とする場合に、**
**最低何カロリーになるのだろうか！？**

さあ以下のコード一発にまとめたので、実行してみよう！！

```py:完全栄養マクドナルド食の線型計画
import pulp

# 問題の定義
# 今回は、カロリーを最小化したいため、最初化で設定
problem = pulp.LpProblem(name="完全栄養マクドナルド食", sense=pulp.LpMinimize)

import csv
McDonaldsDict = {}
with open('/content/drive/My Drive/MACD/マクドナルド栄養価一覧20201009_R.csv') as f:
    reader = csv.DictReader(f)
    # OrderedDict([('商品名', 'えびフィレオ'), ('重量g', '174'), ・・・が１行ごとに入っている
    # ※ジュース系などで、栄養価が「-」のものは０を置換済み
    for row in reader:
      # 'えびフィレオ' : OrderedDict([('商品名', 'えびフィレオ'), ('重量g', '174')・・・ の辞書形式に加工
      McDonaldsDict[row["商品名"]] = row

# 特定の栄養価のリストを取得する
# 対象のtarget_menu_listに入っている順番に、その栄養価の値を取得。
def get_nutrition_val_list(nutrition_dict, target_menu_list, eiyou_name):
  result_list = []
  for menu_name in target_menu_list:
    #栄養価を取得してfloatに置換
    eiyou_val = nutrition_dict[menu_name][eiyou_name]
    result_list.append(float(eiyou_val))
  return result_list


# 品物
# ※カロリーの問題であるため、コカ・コーラ ゼロや爽健美茶など、
# カロリーが完全に０のものは除外しておくこと。
target_menu_list = [
  "てりやきマックバーガー",
  "ハンバーガー",
  "チーズバーガー",
  "ダブルチーズバーガー",
  "月見バーガー",
  "ビッグマック",
  "フィレオフィッシュ",
  "チキンマックナゲット 5ピース",
  "マックフライポテト(M)",
  "マックフライポテト(S)",
  "ケチャップ",
  "バーベキューソース",
#  "スイートコーン",
#  "サイドサラダ",
  "コカ・コーラ(M)",
  "マックシェイク® バニラ(S)",
  "ミニッツメイド オレンジ(M)",
#  "ミルク",
#  "野菜生活100(M)",
]

#男性：30歳～49歳の１日に必要な栄養量。
#身体活動レベル１＝生活の大部分が座位で、
# 静的な活動が中心の場合、で計算
# ただし、食塩相当量は、必要ではなく「以下」にすべき値
one_da_nutrition_dict ={
    "エネルギーkcal" : 2300.0,
    "たんぱく質g" : 65.0 ,
    "脂質g" : 63.9 ,
    "炭水化物g" : 330.6 ,
    "カルシウムmg" : 750.0 ,
    "鉄mg" : 7.5 ,
    "ビタミンAμg" : 900.0 ,
    "ビタミンB1mg" : 1.4 ,
    "ビタミンB2mg" : 1.6 ,
    "ビタミンCmg" : 100.0 ,
    "食物繊維g" : 21.0 ,
    "食塩相当量g" : 7.5 ,
}

# 対象とする栄養素について、対象の商品リストごとの栄養価を、リスト形式で作成する
eiyou_data={}
for  key in one_da_nutrition_dict.keys():
  #keyに入っている栄養の名称（日本語）を、データのdictのkeyにする
  eiyou_data[key] = get_nutrition_val_list(McDonaldsDict, target_menu_list, key)


# 変数の定義（※日本語の文字列をそのまま変数として利用）
xs = [pulp.LpVariable('{}'.format(x), cat='Integer', lowBound=0) for x in target_menu_list]

# 目的関数：エネルギーの最小化
problem += pulp.lpDot(eiyou_data["エネルギーkcal"], xs)

# 制約条件：一日に必要な栄養量をそれぞれ満たすこと。
# 条件カスタマイズ＆ON-OFFしやすいように、あえてループ外で記載。
# 食塩相当については、「以内」としている。解が存在するかどうか？は要注意。
problem += pulp.lpDot(eiyou_data["たんぱく質g"], xs) >= one_da_nutrition_dict["たんぱく質g"]
problem += pulp.lpDot(eiyou_data["脂質g"], xs) >= one_da_nutrition_dict["脂質g"]
problem += pulp.lpDot(eiyou_data["炭水化物g"], xs) >= one_da_nutrition_dict["炭水化物g"]
problem += pulp.lpDot(eiyou_data["カルシウムmg"], xs) >= one_da_nutrition_dict["カルシウムmg"]
problem += pulp.lpDot(eiyou_data["鉄mg"], xs) >= one_da_nutrition_dict["鉄mg"]
problem += pulp.lpDot(eiyou_data["ビタミンAμg"], xs) >= one_da_nutrition_dict["ビタミンAμg"]
problem += pulp.lpDot(eiyou_data["ビタミンB1mg"], xs) >= one_da_nutrition_dict["ビタミンB1mg"]
problem += pulp.lpDot(eiyou_data["ビタミンB2mg"], xs) >= one_da_nutrition_dict["ビタミンB2mg"]
problem += pulp.lpDot(eiyou_data["ビタミンCmg"], xs) >= one_da_nutrition_dict["ビタミンCmg"]
problem += pulp.lpDot(eiyou_data["食物繊維g"], xs) >= one_da_nutrition_dict["食物繊維g"]
problem += pulp.lpDot(eiyou_data["食塩相当量g"], xs) <= one_da_nutrition_dict["食塩相当量g"]

#与えられた問題の内容を表示
print(problem)

status = problem.solve()
print("Status", pulp.LpStatus[status])
# ※「Optimal」であることを確認すること。

# 簡易結果表示
print([x.value() for x in xs])
print(problem.objective.value())

# 変数名ごとに表示
for x in xs:
  print(str(x) + " × "+ str(int(x.value())) )

# それぞれの栄養素がいくらになったのか、計算結果を表示
print("----結果----")
for key in one_da_nutrition_dict.keys():
  print(key + "： " + str(one_da_nutrition_dict[key]) +" に対し " + str(round(pulp.lpDot( eiyou_data[key], xs).value())) )

```

上記を実行すると・・・

**結果：　8035kcal**

```py:出力結果
# 問題定義周りのログ出力は省略して記載
てりやきマックバーガー × 0
ハンバーガー × 0
チーズバーガー × 0
ダブルチーズバーガー × 1
月見バーガー × 2
ビッグマック × 0
フィレオフィッシュ × 0
チキンマックナゲット_5ピース × 0
マックフライポテト(M) × 0
マックフライポテト(S) × 0
ケチャップ × 2
バーベキューソース × 0
コカ・コーラ(M) × 0
マックシェイク®_バニラ(S) × 0
ミニッツメイド_オレンジ(M) × 46
----結果----
エネルギーkcal： 2300.0 に対し 8035
たんぱく質g： 65.0 に対し 175
脂質g： 63.9 に対し 85
炭水化物g： 330.6 に対し 1654
カルシウムmg： 750.0 に対し 1526
鉄mg： 7.5 に対し 20
ビタミンAμg： 900.0 に対し 900
ビタミンB1mg： 1.4 に対し 12
ビタミンB2mg： 1.6 に対し 2
ビタミンCmg： 100.0 に対し 5433
食物繊維g： 21.0 に対し 38
食塩相当量g： 7.5 に対し 8 
#補足；食塩は以下条件であり、roundで8になっている？

```

実に、1日の必要量 2300kcalに対してなんと、
**8035kcal** も摂取することになる。
そして、
ダブルチーズバーガー × 1
月見バーガー × 2　　　を圧倒的に洗い流す、
**46杯というミニッツメイド_オレンジの洪水**。

**典型的なマクドナルド感の代表選手**
のみに限定してしまうと
**さすがにちょっとヤバイ感じ（語彙力）になってしまった**。

だが全国のマクドナルド・ファンの皆様、ご安心召されよ、
そもそもこのように選択肢が少ない状態で、
全栄養素を満たすように、という条件であるため、
8035kcalという結果になってしまっただけである。
より現実的な条件の場合も確認していきたい。

ここまでの結果で終わってしまっては私としても、
夜な夜な、**黄色と赤色のピエロによる闇討ち**を
怖がらなくてはいけなくなってしまう。


# ⑤いくつか条件を変えながら結果を見て遊ぶ

まずは「**野菜もしっかり食べよう**」の勅令に従って、
サラダ、スイートコーン、のコメントアウトを戻す。
また、ミルクや野菜生活もOKとしよう！

**結果：　1994kcal**

```py:野菜もしっかり食べよう
てりやきマックバーガー × 1
ハンバーガー × 0
チーズバーガー × 0
ダブルチーズバーガー × 0
月見バーガー × 0
ビッグマック × 0
フィレオフィッシュ × 0
チキンマックナゲット_5ピース × 0
マックフライポテト(M) × 0
マックフライポテト(S) × 2
ケチャップ × 0
バーベキューソース × 0
スイートコーン × 2
サイドサラダ × 95
コカ・コーラ(M) × 0
マックシェイク®_バニラ(S) × 0
ミニッツメイド_オレンジ(M) × 0
ミルク × 0
野菜生活100(M) × 0
----結果----
エネルギーkcal： 2300.0 に対し 1994
たんぱく質g： 65.0 に対し 72
脂質g： 63.9 に対し 64
炭水化物g： 330.6 に対し 331
カルシウムmg： 750.0 に対し 1308
鉄mg： 7.5 に対し 22
ビタミンAμg： 900.0 に対し 2580
ビタミンB1mg： 1.4 に対し 3
ビタミンB2mg： 1.6 に対し 2
ビタミンCmg： 100.0 に対し 1454
食物繊維g： 21.0 に対し 87
食塩相当量g： 7.5 に対し 4
```

基準値の2300kcalより少ない1994kcal。
しっかり栄養を取りながらダイエットが出来る。
実に健康的な結果が出て、しかも、
てりやきマックバーガー × 1
マックフライポテト(S) × 2
も食べることが出来るなんて！！

と、思いきや、
**サイドサラダ × 95**　（絶句）

こんなに野菜は食べられないですよねー！！
健康ジュースの通販番組のネタになりそうな量である。
**やはりサイドサラダによる影響は大きすぎた。**

そこで、コーンフレーク方式を試す。
**ミルクやジュース等の飲み物による補充はOKとする案**。
サイドサラダ とスイートコーン だけ外そう。

**結果：　2933kcal**

```
てりやきマックバーガー × 0
ハンバーガー × 0
チーズバーガー × 1
ダブルチーズバーガー × 0
月見バーガー × 1
ビッグマック × 0
フィレオフィッシュ × 0
チキンマックナゲット_5ピース × 0
マックフライポテト(M) × 2
マックフライポテト(S) × 2
ケチャップ × 0
バーベキューソース × 0
コカ・コーラ(M) × 0
マックシェイク®_バニラ(S) × 0
ミニッツメイド_オレンジ(M) × 2
ミルク × 3
野菜生活100(M) × 2
----結果----
エネルギーkcal： 2300.0 に対し 2933
たんぱく質g： 65.0 に対し 81
脂質g： 63.9 に対し 125
炭水化物g： 330.6 に対し 371
カルシウムmg： 750.0 に対し 1013
鉄mg： 7.5 に対し 8
ビタミンAμg： 900.0 に対し 2018
ビタミンB1mg： 1.4 に対し 2
ビタミンB2mg： 1.6 に対し 2
ビタミンCmg： 100.0 に対し 311
食物繊維g： 21.0 に対し 21
食塩相当量g： 7.5 に対し 8
```

チーズバーガー × 1
月見バーガー × 1
マックフライポテト(M) × 2
マックフライポテト(S) × 2
に、オレンジジュース、ミルク、野菜ジュースを数本ずつ。
ちょうどお昼と夜にバリューセットを頼む感じで、
2933kcalとちょっと超過しすぎだが、
これならばある程度現実に近い感じではないだろうか！？
（おそらく食物繊維か何かのためにポテトが多すぎるので、
　少し別のメニュー等で食物繊維を摂取すればよい）

これは、**通常のポテト版のバリューセットの**
**飲み物を、オレンジジュース or ミルク or 野菜生活**
**にするだけで、栄養バランス的にはかなり理想的**。
ということを示唆している。（食物繊維/鉄が少し不足）

マクドナルド食は健康に良い！とまでは言えないものの、
そこまで悪い結果でもないだろう。

この示唆に従って、試しに以下の組み合わせで
先述の公式サイトでの栄養表示を試してみた。

* チーズバーガー
* マックフライポテトM
* 野菜生活M
* ミルク
* ミニッツメイドオレンジM

![マクドナルドのサイトより.PNG](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/193512/c14e9359-ff4b-e800-50a9-553cc82474bf.png)

> 画像出典：マクドナルド公式サイト ＞ 私達の責任 ＞ Our Food ＞ 栄養バランスチェック

一食相当分で、およそどの栄養素も１日の40%ラインを超えているため、
**バリューセットにミルクと果物ジュースを追加するだけで**
**こんなに理想的な食事になる！！みたいな宣伝が出来そうである。**

~~脂質が過剰という説はある。~~
~~また、もしかしたらだいたいの食べ物はミルクと野菜ジュースで~~
~~補強すると"コーンフレーク五角形"を作れるのかもしれない。~~


# ⑥１メニュー１回まで制限にして遊ぶ

さらにさらに、より現実的な解として、
**どのメニューも最大１回まで注文できる場合**
（つまり、サラダばかり９５個も頼むのはNGとする）
で最適解を求めてみる。
対象のメニュー範囲も合わせて通常の全種類まで拡大する。

コードを以下のように変えるだけ！
①（バリスタ以外の）全通常メニューを対象とする
②変数の定義時に、「最大値＝１」の条件を追加する

```py:どのメニューも一回だけ注文可能な場合
# バリスタメニューは抜く
target_menu_list = [x for x in McDonaldsDict.keys() if McDonaldsDict[x]["区分"]!="バリスタ"]

# 中略

# 変数の定義　upBound=1　が最大値１の条件
xs = [pulp.LpVariable('{}'.format(x), cat='Integer', lowBound=0, upBound=1) for x in target_menu_list]
```

１回まで注文できる場合で結果を見てみよう！

**結果：　2453kcal**

```
エッグマックマフィン × 1
ストロベリージャム × 1
マックグリドル_ベーコンエッグ × 1
サイドサラダ × 1
シェアポテト × 1
スイートコーン × 1
サントリー黒烏龍茶#濃いめ × 1
プレミアムローストアイスコーヒー(L) × 1
プレミアムローストコーヒー(M) × 1
プレミアムローストコーヒー(S) × 1
ホットティー(ストレート)(M) × 1
マックシェイク®_チョコレート(S) × 1
ミニッツメイド_オレンジ(S) × 1
ミルク × 1
リキッドレモン × 1
野菜生活100(M) × 1
----結果----
エネルギーkcal： 2300.0 に対し 2453
たんぱく質g： 65.0 に対し 67
脂質g： 63.9 に対し 95
炭水化物g： 330.6 に対し 331
カルシウムmg： 750.0 に対し 856
鉄mg： 7.5 に対し 8
ビタミンAμg： 900.0 に対し 1161
ビタミンB1mg： 1.4 に対し 1
ビタミンB2mg： 1.6 に対し 2
ビタミンCmg： 100.0 に対し 254
食物繊維g： 21.0 に対し 21
食塩相当量g： 7.5 に対し 8
```

代表的なバーガー系が出なくなってしまったのが残念だが、
まあまあ現実的に注文できそうな解を得ることが出来た。
サラダ、スイートコーン、野菜生活、
ミルク、オレンジジュース、などの
前回からのエースは相変わらず選ばれている。
**マックシェイク®_チョコレートが入っているのが意外な結果**。

# ⑦コーンフレークをメニューに追加してさらに遊ぶ

最後に、栄養バランス５角形の代表格、
コーンフロスティ（ケロッグ）のデータを追加してみる。
ケロッグの公式サイトから、コーンフロスティのデータを参照し、
以下のように個別にDictデータを作成。
元のデータに追加して実行するだけ。

```py:コーンフロスティのデータ
from collections import OrderedDict
## 牛乳無し版のコーンフロスティデータの追加
# 参考：https://www.kelloggs.jp/ja_JP/products/corn-frosties.html
k_od = OrderedDict()

k_od['商品名'] = "コーンフロスティ"
k_od['重量g'] = 30.0
k_od['エネルギーkcal'] = 114.0
k_od['たんぱく質g'] = 1.7 #1.2～2.2
k_od['脂質g'] = 0.25 #0～0.5
k_od['炭水化物g'] = 26.9
#k_od['ナトリウムmg'] = 
#k_od['カリウムmg'] = 
k_od['カルシウムmg'] = 1.5 #0.5～2.5
#k_od['リンmg'] = 
k_od['鉄mg'] = 1.4
k_od['ビタミンAμg'] = 96 #53～139
k_od['ビタミンB1mg'] = 0.47
k_od['ビタミンB2mg'] = 0.42
#k_od['ナイアシンmg'] = 
k_od['ビタミンCmg'] = 15
#k_od['コレステロールmg'] = 
k_od['食物繊維g'] = 1.2 #0.4～2.0
k_od['食塩相当量g'] = 0.3
#k_od['区分'] = 

#追加コーンフロスティデータの追加
#McDonaldsDict["コーンフロスティ"] = k_od
```

今回は、マクドナルドの代表選手に絞ったメニューで、
サラダ無し、飲み物有り版に、
コーンフロスティを選択肢として加えた版の結果をご紹介しよう。

**結果：　2544kcal**

```
てりやきマックバーガー × 0
ハンバーガー × 2
チーズバーガー × 0
ダブルチーズバーガー × 0
月見バーガー × 0
ビッグマック × 0
フィレオフィッシュ × 0
チキンマックナゲット_5ピース × 0
マックフライポテト(M) × 2
コカ・コーラ(M) × 0
ミニッツメイド_オレンジ(M) × 0
ミルク × 3
野菜生活100(M) × 0
コーンフロスティ × 7
----結果----
エネルギーkcal： 2300.0 に対し 2544
たんぱく質g： 65.0 に対し 68
脂質g： 63.9 に対し 85
炭水化物g： 330.6 に対し 381
カルシウムmg： 750.0 に対し 788
鉄mg： 7.5 に対し 14
ビタミンAμg： 900.0 に対し 940
ビタミンB1mg： 1.4 に対し 4
ビタミンB2mg： 1.6 に対し 4
ビタミンCmg： 100.0 に対し 145
食物繊維g： 21.0 に対し 21
食塩相当量g： 7.5 に対し 7
```

**コーンフロスティ × 7**
**ミルク × 3**
で、コーンフロスティを選択肢に入れない場合には
2933kcalだったのが、2544kcalまで
摂取カロリーを減らすことが出来た。
さすが**あのトラは伊達じゃない。**

が、一方でトラを追加した状態でも、
**ハンバーガー × 2**
**マックフライポテト(M) × 2**
とバリューセット勢が食い込んでいるのも、
**マクドナルド側の優秀さも物語っている**。

すなわち、朝食にコーンフロスティ＋ミルク、（~~×7で結構沢山~~)
昼・夜それぞれバリューセット(ポテト)、という食生活でも、
そこそこの栄養バランスは保たれるようだ。

# 総括と感想

## 完全栄養マクドナルド食について

今回の研究成果によって、
**無人島にマクドナルドだけを持っていける場合に**、
**最適なメニューを注文できるようになった**。

バリューセットは、サイドをポテトにしても
意外といい感じの栄養バランスにはなるものの、
やはりサラダやスイートコーンを選ばない限り、
そこそこカロリーオーバーになってしまう。
**「野菜もしっかり食べよう」**

また、飲み物として、
ミルク、野菜生活、オレンジジュース、
あたりが有用であると分かった。
野菜生活を追加した栄養バランスの５角形を作れば
**黄色のピエロも、腕組みしたトラと戦えるのではないか？**
サイドサラダも加えるとさらに戦力アップ。

一方で、選択肢を超典型的なメニューだけに絞ると、
実に**8000kcal以上** になってしまう。
**好きなものばかり注文していると太る**、
ということが分かった。（あたりまえ）

サイドサラダとスイートコーンをOKにすれば、
一日に必要な栄養素を保ちつつ、
カロリーを基準値以下にすることが可能であったため、
**結局、マクドナルドだけで生活する場合の指針としては、**
**バリューセット(ポテト)を注文しつつも、**
**サラダ、ミルク、野菜生活をより多く注文することで、**
**比較的健康的な栄養バランスの食事を維持可能となる。**

**バランス良く食べようという、一見あたりまえの結論であるものの、**
**１店舗内の選択肢の組み合わせだけでコレが実現出来るのは、**
**結構エライことではないだろうか？**


~~私からマクドナルドに改善を求めるとすれば、~~
~~ハッピーセットのメインの選択肢として、＋１００円くらいで~~
~~てりやきマックバーガーも選べるようにして欲しいこと、くらいである。~~
~~また、ぜひ本コード相当のツールを公式サイトにも組み込んで欲しい。~~
~~その際にはぜひ協力させていただきたい。~~


## 線型計画のコードについて

当初、栄養素に関する計算は、
PuLPや線型計画の代表的な問題であり、
マクドナルド公式サイトにも
データが一覧形式で存在したため、
結構簡単に出来るかなーと思っていた。

しかし、実データに適用しようとすると、
そのまま作るとヤバいコードになってしまう。
日本語名変数、動的な変数付け、行列での式定義、などなど、
PuLPのサンプルから変えないといけない箇所が多数発生し、
予想以上に様々な追加工夫を要した。

最終的には、これらの工夫のおかげて、
様々にデータを入れ替えて ~~遊ぶ~~ 研究を深めることができた。

今回のコードは、CSVさえ作ればブラウザ上だけで、
コピペ１発で実行できる形にまとめているため、
興味がある方はぜひ追試し、
いろいろ条件を変えて考察してみてほしい。
Colaboratoryでできるため窓からでもリンゴからでも実行可能だ。

マクドナルド公式サイトのメニューデータは、
日々更新されているようなので、結果は変わるかもしれない。

---

以上。本研究の結果が、
**マクドナルドの門をくぐるたびに罪悪感を覚えるような、**
**信心の足りないマック信者たちへの救済の一助となれば幸いである。**
~~エンジニア諸氏にはマック信者が多いと誰かが言っていた気がする~~