Skip to content

Instantly share code, notes, and snippets.

@zr-tex8r
Created May 25, 2019 07:21
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save zr-tex8r/f17160f4f0543363c19e7ed425946016 to your computer and use it in GitHub Desktop.
Save zr-tex8r/f17160f4f0543363c19e7ed425946016 to your computer and use it in GitHub Desktop.
Pandoc:「サイゼリヤで1000円あれば最大何kcal摂れるのか」を解くLuaフィルタ
彩りガーデンサラダ 130 299
小エビのサラダ 115 349
やわらかチキンのサラダ 134 299
わかめサラダ 92 299
イタリアンサラダ 196 299
シーフードサラダ 229 599
半熟卵とポークのサラダ 433 599
コーンクリームスープ 142 149
冷たいパンプキンスープ(季節限定) 105 149
たっぷり野菜のミネストローネ(季節限定) 222 299
削りたてペコリーノチーズ 59 100
ミニフィセル 188 169
ガーリックトースト 252 189
辛味チキン 374 299
アスパラガスのオーブン焼き(季節限定) 221 299
ポップコーンシュリンプ 215 299
エスカルゴのオーブン焼き 256 399
ムール貝のガーリック焼き 164 399
野菜ソースのグリルソーセージ 570 399
チョリソー 393 399
柔らか青豆の温サラダ 213 199
ほうれん草のソテー 138 199
キャベツとアンチョビのソテー 80 199
ポテトのグリル 366 199
セロリのピクルス(季節限定) 52 199
真イカのパプリカソース 138 199
フォッカチオ 214 119
プチフォッカ 214 139
セットプチフォッカ 107 79
フレッシュチーズとトマトのサラダ 203 299
フレッシュチーズとトマトのサラダ(Wサイズ) 406 598
プロシュート 162 399
プロシュート(Wサイズ) 324 798
熟成ミラノサラミ 95 299
熟成ミラノサラミ(Wサイズ) 190 598
マルゲリータピザ 568 399
パンチェッタのピザ 646 399
野菜ときのこのピザ 610 399
やわらかイカのアンチョビのピザ 593 499
バッファローモッツァレラのピザ 575 499
ミラノサラミのピザ 606 499
ほうれん草のグラタン(季節限定) 521 399
シーフードグラタン 537 499
アラビアータ 591 399
ミートソースボロニア風 582 399
半熟卵のミートソースボロニア風 672 468
アーリオ・オーリオ 560 299
キャベツのペペロンチーノ 686 399
タラコソースシシリー風 605 399
スープ入りトマト味ボンゴレ(季節限定) 686 499
パルマ風スパゲッティ 700 399
イカの墨入りスパゲッティ 610 499
カルボナーラ 865 499
アスパラガスとエビのクリームスパゲッティ(季節限定) 711 499
アラビアータ(Wサイズ) 1182 770
ミートソースボロニア風(Wサイズ) 1164 770
アーリオ・オーリオ(Wサイズ) 1120 574
キャベツのペペロンチーノ(Wサイズ) 1372 770
タラコソースシシリー風(Wサイズ) 1210 770
パルマ風スパゲッティ(Wサイズ) 1400 770
イカの墨入りスパゲッティ(Wサイズ) 1220 976
カルボナーラ(Wサイズ) 1730 976
アスパラガスとエビのクリームスパゲッティ(季節限定)(Wサイズ) 1422 976
トッピング半熟卵 90 69
ミラノ風ドリア 542 299
半熟卵のミラノ風ドリア 632 368
セットプチフォッカ付きミラノ風ドリア 649 378
いろどり野菜のミラノ風ドリア 590 399
エビとイカのドリア 624 499
シーフードパエリア 602 599
エビと野菜のトマトクリームリゾット 302 399
ハヤシ&ターメリックライス 638 499
半熟卵のハヤシ&ターメリックライス 728 568
ミックスグリル 823 599
ハンバーグステーキ 514 399
デミグラスソースのハンバーグ 628 499
野菜ソースのハンバーグ(ディアボラ風) 585 499
イタリアンハンバーグ 633 499
焼肉とハンバーグの盛合せ 709 599
若鶏のグリル(ディアボラ風) 541 499
柔らかチキンのチーズ焼き 588 499
パンチェッタと若鶏のグリル 663 599
リブステーキ 621 999
ライス 303 169
ラージライス 454 219
スモールライス 151 119
カプチーノ(アイスケーキ)(季節限定) 114 199
ティラミス(アイスケーキ) 131 199
シナモンフォッカチオ 246 169
プリンとカプチーノの盛合せ 330 399
プリンとティラミスの盛合せ 347 399
ミルクアイスのせシナモンフォッカチオ 346 319
ミルクジェラート 100 199
シチリア産レモンのソルベ 127 199
イタリアンプリン 216 249
チョコレートケーキ 166 299
コーヒーゼリー 162 299
トリフアイスクリーム 164 369
-- saizeriya.lua
-- 「サイゼリヤで1000円あれば最大何kcal摂れるのか」
-- を求めるPandcoフィルタ (えっ)
--
-- @copyright 2019 Takayuki YATO (aka. "ZR")
-- GitHub: https://github.com/zr-tex8r
-- Twitter: @zr_tex8r
-- This program is distributed under the MIT License.
--
local filter_name = 'saizeriya'
---------------------------------------- 準備
--local utils = require 'pandoc.utils'
--local list = require 'pandoc.List'
--for name, fun in pairs(require 'text') do
-- string['uc_'..name] = fun
--end
local insert, unpack = table.insert, table.unpack
---------------------------------------- 設定
--- デバッグログを出すか
local show_log = false
--- 一覧出力用のリンク要素クラス
local class_list = 'saizeriya-list'
--- 計算結果出力用のリンク要素クラス
local class_solve = 'saizeriya-solve'
-- ※異常の2つのリンク要素を"特殊リンク要素"と呼ぶ.
--- 予算値用の属性キー
local attr_badget = 'badget'
--- 予算値の既定値
local attr_badget_default = 1000
---------------------------------------- general helpers
--- デバッグログを出力する.
local function log(fmt, ...)
if not show_log then return end
io.stderr:write(filter_name..': '..fmt:format(...).."\n")
end
-- 列に対して関数をマップする.
local function map(func, list)
local res = {}
for i = 1, #list do
res[i] = func(list[i])
end
return res
end
---------------------------------------- load data file
--- メニューのデータファイルを読み込む.
-- ファイルはCSV形式(ヘッダ無し)でカラム定義は"名称,カロリー,価格".
-- @param fmenu ファイル名
-- @return メニュー(メニューアイテムの列)
local function load_menu_file(fmenu)
local menu = {}
local hm = assert(io.open(fmenu, 'r'))
for line in hm:lines('l') do
-- 各行を解析する
local name, calo, cost = line:match('^(.-),(%d+),(%d+)$')
assert(name, ("syntax error in data file (line %s)"):format(#menu + 1))
insert(menu, {
name = name, calorie = tonumber(calo), cost = tonumber(cost)
})
end
hm:close()
return menu
end
--- サイゼリヤ問題を動的計画法を用いて解く.
-- @param menu メニュー
-- @param badget 予算値
-- @return 選んだアイテムの添字の列
function solve_maximize(menu, badget)
-- mcalo[k]は予算値kに対する最大カロリー値
-- item[k]は最適解で最後に追加したアイテム(添字).
local mcalo, item = {}, {}
-- 初期化
for k = 0, badget do
mcalo[k], item[k] = 0, 0
end
-- アイテムごとのループ
for i = 1, #menu do
local calo, cost = menu[i].calorie, menu[i].cost
for k = badget, cost, -1 do -- 重複禁止の場合
-- for k = cost, badget do -- 重複許容の場合
local nmcalo = mcalo[k - cost] + calo
if nmcalo > mcalo[k] then -- 最適解更新
mcalo[k], item[k] = nmcalo, i
end
end
end
-- 最適解のアイテムのリストを求める
local k, choice = badget, {}
while item[k] > 0 do
insert(choice, item[k]); k = k - menu[item[k]].cost
end
table.sort(choice, function(i, j) -- カロリーの降順でソート
return menu[i].calorie > menu[j].calorie
end)
return choice
end
---------------------------------------- filter helpers
--- 要素が"空白"であるか.
local function is_space(el)
return (el.t == 'Space' or el.t == 'LineBreak' or el.t == 'SoftBreak')
end
--- 単一文字列をPlainブロック列に変換する.
local function plain(str)
return {pandoc.Plain({pandoc.Str(tostring(str))})}
end
--- 単一文字列を重要付でPlainブロック列に変換する.
local function plain_strong(str)
return {pandoc.Plain({pandoc.Strong({pandoc.Str(tostring(str))})})}
end
--- 要素が特殊リンクである場合にその情報を返す.
-- @return 要素クラス名(saizeriya-XXX), 特殊でない場合はnil
-- @return メニューファイルのパス名(文法上はリンク先パス名)
-- @return 計算用の場合は予算値; 一覧用の場合はnil
local function saizeriya_link_info(el)
if el.t ~= "Link" then return nil end
if el.classes:includes(class_list) then -- 一覧出力要素
return class_list, el.target
elseif el.classes:includes(class_solve) then -- 計算結果出力要素
local badget = tonumber(el.attributes['badget']) or attr_badget_default
return class_solve, el.target, badget
end
end
--- 段落を特定の位置で分割する.
-- 段落要素elの子のリストを"idxより前", "idx自体", "idxより後"(idxは添字)
-- の3つに分割し, 各々の部分を段落に変換してそれらの段落の列を返す.
-- ただし空になる部分についてはその段落は結果の列から省く.
-- @return 分割後の段落の列
-- @return "idx自体"の段落の位置(添字, 1または2)
local function split_paragraph(el, idx)
local c, ib, ia = el.content, idx - 1, idx + 1
-- 分割される位置にある"空白"の要素は削除する
while ib > 0 do
if not is_space(c[ib]) then break end
ib = ib - 1
end
while ia <= #c do
if not is_space(c[ia]) then break end
ia = ia + 1
end
local res, ridx = {pandoc.Para({c[idx]})}, 1
if ib > 0 then -- "前"がある
insert(res, 1, pandoc.Para({unpack(c, 1, ib)}))
ridx = 2
end
if ia <= #c then -- "後"がある
insert(res, pandoc.Para({unpack(c, ia, #c)}))
end
return res, ridx
end
---------------------------------------- "precheck"フェーズ
-- 特殊リンク要素の配置には制限がある:
-- - 段落内にしか置けない. (例えば見出しに含めるのはダメ)
-- ※ブロック要素に置き換わるため.
-- - 1つの段落に高々1つしか置けない.
-- その条件が守られている("正当"と呼ぶ)かを検証する.
-- ※このフェーズでは要素の更新は起こらない.
--- 特殊リンク要素の個数 / 正当な要素の個数
local s_link, s_ok_link = 0, 0
--- precheckでのLink要素の処理.
-- 特殊リンクである場合はs_linkを計上する.
local function precheck_Link(el)
local class = saizeriya_link_info(el)
if not class then return end
log("found link '%s'", class)
s_link = s_link + 1
end
--- precheckでのPara要素の処理.
-- その要素の(直接の)子である特殊リンクをs_ok_linkに計上する.
local function precheck_Para(el)
local count = 0 -- この要素内の特殊リンク個数
for _, cel in ipairs(el.content) do -- 各子要素について
local class, target = saizeriya_link_info(cel)
if class then -- 特殊リンクである
log("found valid link '%s' (%s)", class, target)
s_ok_link, count = s_ok_link + 1, count + 1
end
end
if count > 1 then -- 複数配置はダメ
error("Multiple saizeriya links in a paragraph")
end
end
--- precheckでのPandoc要素の処理.
-- 特殊リンク要素が全て正当であるかを検証する.
-- ※特定の処理を1度実行したいからPandoc要素をフックしているのであり,
-- 実際には要素は見ていない.
local function precheck_Pandoc()
log("link count: all=%s, valid=%s", s_link, s_ok_link)
if s_link ~= s_ok_link then -- 非正当なのがあるのでダメ
error("Found saizeriya link outside paragraphs")
end
end
---------------------------------------- "inject"フェーズ
--- 一覧出力のブロックを生成する.
-- メニューの一覧を記した箇条書き.
-- @param target メニューファイルのパス名
-- @param 生成したBulletList要素
local function saizeriya_list_block(target)
local menu = load_menu_file(target)
local items = {}
for _, e in ipairs(menu) do
local s = ("%s:%skcal,%s円"):format(e.name, e.calorie, e.cost)
insert(items, plain(s))
end
return pandoc.BulletList(items)
end
--- 計算結果出力のブロックを生成する.
-- 最適解のアイテムの情報を記した表組.
-- @param target メニューファイルのパス名
-- @param badget 予算値
-- @param 生成したTable要素
local function saizeriya_solve_block(target, badget)
-- 問題を解く
local menu = load_menu_file(target)
local choice = solve_maximize(menu, badget)
-- 合計
local tcalo, tcost = 0, 0
for _, i in ipairs(choice) do
tcalo, tcost = tcalo + menu[i].calorie, tcost + menu[i].cost
end
-- 表を生成する
local align = {pandoc.AlignLeft, pandoc.AlignRight, pandoc.AlignRight}
local header = map(plain, {
"名称", "カロリー/kcal", "価格/円"
})
local body = map(function(i)
local me = menu[i]
return map(plain, {me.name, me.calorie, me.cost})
end, choice)
insert(body, map(plain_strong, {
"合計", tcalo, tcost
}))
return pandoc.Table({}, align, {}, header, body)
end
--- injectでのPara要素の処理.
-- その要素の子要素である特殊リンク要素を所望のブロック要素(一覧用は
-- BulletList、計算用はTable)に変換して置き換える.
-- ※インライン要素をブロック要素に置き換えるため, 事前に段落を分割する.
-- @return 更新後の子要素列(更新しないならnil)
local function inject_Para(el)
-- 子要素の中から特殊リンク要素を探す
local cls, target, badget, idx
for i, cel in ipairs(el.content) do
cls, target, badget = saizeriya_link_info(cel)
if cls then
idx = i; break
end
end
-- 特殊リンクがないなら更新しない
if not idx then return nil end
-- 段落を分割する
local out, oidx = split_paragraph(el, idx)
-- 特殊リンク要素を変換する
if cls == class_list then
out[oidx] = saizeriya_list_block(target)
elseif cls == class_solve then
out[oidx] = saizeriya_solve_block(target, badget)
end
return out
end
---------------------------------------- フィルタ定義
return {
-- precheckフェーズは2回に分ける(順序を保証するため)
{ -- precheck前半
Link = precheck_Link;
Para = precheck_Para;
};
{ -- precheck後半
Pandoc = precheck_Pandoc;
};
{ -- injectフェーズ
Para = inject_Para;
}
}
---------------------------------------- おしまい

問題

予算1000円以内で,サイゼリヤで最大カロリーを摂取するような注文の仕方を求めよ。ただしサイゼリヤの料理のメニューは以下の通りとする。

一覧{.saizeriya-list}

解答

以下の通り。

解答{.saizeriya-solve badget=1000}

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment