「サイゼリヤで1000円あれば最大何kcal摂れるのか」を{lpSolve}で解いてみた。
こんな話がある。
後者の2つはシンプルなナップサック問題なのでRでもできそうなので以下を参考にやってみた。
問題としては
メニューのカロリーを
、価格を
とする。予算
の範囲でカロリーの和が最大になるメニューの組み合わせを求めよ。変数
がメニュー
が選択されているならば
、そうでなければ
であるとする。
となり定式化すると
となる。
データはGitHub - marushosummers/Saizeriya_1000yenから取得済み(./dataに保存)として以下のように読み込む。
library(RSQLite) library(dplyr) library(knitr) con = RSQLite::dbConnect(SQLite(), "data/saizeriya.db", synchronous="off") d <- RSQLite::dbGetQuery(con, "select * from menu") RSQLite::dbDisconnect(con) d %>% head() %>% knitr::kable()
id | name | category | type | price | calorie | salt |
---|---|---|---|---|---|---|
1 | 彩りガーデンサラダ | sidedish | salad | 299 | 130 | 1.1 |
2 | 小エビのサラダ | sidedish | salad | 349 | 115 | 1.3 |
3 | やわらかチキンのサラダ | sidedish | salad | 299 | 134 | 1.2 |
4 | わかめサラダ | sidedish | salad | 299 | 92 | 2.1 |
5 | イタリアンサラダ | sidedish | salad | 299 | 196 | 0.7 |
6 | シーフードサラダ | sidedish | salad | 599 | 229 | 2.4 |
前述のリンクを参考にlpSolve::lp()
で定式化した問題を解くと以下のようになる。
ans <- lpSolve::lp(direction = "max", objective.in = d$calorie, const.mat = matrix(d$price, nrow = 1), const.dir = "<=", const.rhs = 1000, all.bin = TRUE) print(ans) Success: the objective function is 1940 d %>% dplyr::mutate(is.choiced = ans$solution) %>% dplyr::filter(is.choiced == 1) %>% knitr::kable()
id | name | category | type | price | calorie | salt | is.choiced |
---|---|---|---|---|---|---|---|
25 | ポテトのグリル | sidedish | appetizer | 199 | 366 | 2.0 | 1 |
73 | アーリオ・オーリオ(Wサイズ) | meal | pasta | 574 | 1120 | 6.4 | 1 |
101 | ラージライス | sidedish | rice | 219 | 454 | 0.0 | 1 |
量子アニーリング計算(Wildqat)、SMTソルバー(Z3)と結果が一致した。
予算を1000円から1500円に増やすと結果は以下のようになる。
ans <- lpSolve::lp(direction = "max", objective.in = d$calorie, const.mat = matrix(d$price, nrow = 1), const.dir = "<=", const.rhs = 1500, all.bin = TRUE) d %>% dplyr::mutate(is.choiced = ans$solution) %>% dplyr::filter(is.choiced == 1) %>% knitr::kable()
id | name | category | type | price | calorie | salt | is.choiced |
---|---|---|---|---|---|---|---|
63 | アーリオ・オーリオ | meal | pasta | 299 | 560 | 3.2 | 1 |
67 | パルマ風スパゲッティ | meal | pasta | 399 | 700 | 4.2 | 1 |
73 | アーリオ・オーリオ(Wサイズ) | meal | pasta | 574 | 1120 | 6.4 | 1 |
101 | ラージライス | sidedish | rice | 219 | 454 | 0.0 | 1 |
炭水化物しかなく栄養バランスが悪すぎるのでmealとdrinkは1つにしたい。制約条件に追加するにはcategoryのダミー変数を作成し、以下のように行う。(ダミー変数の作成はrecipes::step_dummy()
を使用するのが最近のトレンドかもしれないが今回はfastDummies::dummy_cols()
を使用してみた。)
d <- d %>% dplyr::select(id, name, category, price, calorie) %>% fastDummies::dummy_cols(select_columns = "category") d %>% head() %>% knitr::kable()
id | name | category | price | calorie | category_sidedish | category_drink | category_meal |
---|---|---|---|---|---|---|---|
1 | 彩りガーデンサラダ | sidedish | 299 | 130 | 1 | 0 | 0 |
2 | 小エビのサラダ | sidedish | 349 | 115 | 1 | 0 | 0 |
3 | やわらかチキンのサラダ | sidedish | 299 | 134 | 1 | 0 | 0 |
4 | わかめサラダ | sidedish | 299 | 92 | 1 | 0 | 0 |
5 | イタリアンサラダ | sidedish | 299 | 196 | 1 | 0 | 0 |
6 | シーフードサラダ | sidedish | 599 | 229 | 1 | 0 | 0 |
constraints <- rbind(matrix(d$price, nrow = 1), matrix(d$category_sidedish, nrow = 1), matrix(d$category_drink, nrow = 1), matrix(d$category_meal, nrow = 1)) ans <- lp(direction = "max", objective.in = d$calorie, const.mat = constraints, # 合計金額 <= 1000. sidediesh 1つ以上、drink 1つ、meal 1つ const.dir = c("<=", ">=", "==", "=="), # sidedish, drink, mealを1つは選ぶ const.rhs = c(1000, 1, 1, 1), all.bin = TRUE) d %>% dplyr::select(id, name, category, price, calorie) %>% dplyr::mutate(is.choiced = ans$solution) %>% dplyr::filter(is.choiced == 1) %>% knitr::kable()
id | name | category | price | calorie | is.choiced |
---|---|---|---|---|---|
25 | ポテトのグリル | sidedish | 199 | 366 | 1 |
28 | フォッカチオ | sidedish | 119 | 214 | 1 |
31 | グラスワイン | drink | 100 | 0 | 1 |
73 | アーリオ・オーリオ(Wサイズ) | meal | 574 | 1120 | 1 |
categoryではなくてtypeで制約するべきだった。。。(グラスワインのcalorieは間違ってそう)
Rでも簡単にサイゼリヤ1000円問題を解くことができた。時間があれば斉藤努氏が公開されている様々な組合せ最適化についてもRでやってみたい。

今日から使える!組合せ最適化 離散問題ガイドブック (KS理工学専門書)
- 作者: 穴井宏和,斉藤努
- 出版社/メーカー: 講談社
- 発売日: 2015/06/23
- メディア: 単行本(ソフトカバー)
- この商品を含むブログ (1件) を見る