はじめに
ある日、ふと「料理のレシピを機械的に表現したい」という思いに駆られました。料理において、例えば「じゃがいもを切る」という工程は、入力として「じゃがいも」を渡すと、出力として「切られたじゃがいも」が出てくる関数のようなものなわけで、その関数群を一定のルールで表記できれば、レシピ全体を表現できそうです。
考えたことを記録として残してみようと思います。
表記ルール
取り扱いやすさを考え、JSONで表現することにしました。AWSのCloudFormationやStep Functionsなどを参考に組み立てています。
概要
レシピは、大きく分けて以下の3つの要素から構成される、と考え、各要素を列挙することにより全体を表現します。
要素 | 説明 |
---|---|
素材(material) | 「玉ねぎ」「砂糖」「牛肉」など、料理の材料となるもの |
容器(container) | 「鍋」「どんぶり」「ボウル」など、素材を入れるもの |
操作(action) | 「切る」「煮る」など、素材の状態を変えるもの |
全体像は以下のような感じです。
{
"title": "名前",
"description": "説明",
"materials": { /* 素材を列挙 */ },
"containers": { /* 容器を列挙 */ },
"actions": { /* 操作を列挙 */ }
}
素材(material)
素材は、名前の通り料理の材料となるものです。記載方法の例は以下です。
{
…
"materials": {
"onion1": {
"title": "玉ねぎ",
"type": "onion",
"description": "新鮮な玉ねぎ"
"quantity": [
{
"amount": 0.5,
"unit": "pieces"
},
{
"amount": 100,
"unit": "g"
}
]
},
…
}
}
要素 | 説明 | |
---|---|---|
title | 素材の表示名。省略した場合はtypeにより自動で決まる。typeとしてcustomを指定した場合は必須。 | |
type | 素材の種別。onion(玉ねぎ)、sugar(砂糖)などを事前に定義しておく。任意の素材としてcustomを指定することも可。 | |
description | 素材の説明。省略可。 | |
quantity | 分量。「玉ねぎ半個(100g)」のように、1つの素材に複数の分量がある場合があるので配列にする。 | |
amount | 素材の量 | |
unit | 素材の量の単位。g(グラム)、pieces(個)、tbsp(大さじ)など |
後述しますが、今回は「牛丼」を題材として考えたため、typeやunitは牛丼に必要なものしか定義していないです。レシピを増やしながら定義を拡充していきたいところです。
容器(container)
容器は、複数の素材をグループ化するときに利用します。例えば「煮る」という操作は複数の素材に対して同時に行われます。そのような場合は、容器を使って素材をひとまとめにし、その容器に対して「煮る」操作を適用する、と考えます。容器の記載例は以下です。
{
…
"containers": {
"pot1": {
"title": "お鍋",
"type": "pot",
"description": "大き目の鍋。蓋も必要。"
},
…
}
}
要素 | 説明 |
---|---|
title | 容器の表示名。省略した場合はtypeにより自動で決まる。typeとしてcustomを指定した場合は必須。 |
type | 容器の種別。pot(鍋)、riceBowl(どんぶり)などを事前に定義しておく。任意の容器としてcustomを指定することも可。 |
description | 容器の説明。省略可。 |
実際に容器を使って素材をグループ化するには、後述する「add」アクションを使います。
操作(action)
操作は、素材や容器に対して適用され、何等かの形で状態を変化させるものです。例は以下です。
{
…
"actions": {
"cutOnion": {
"type": "cut",
"source": "onion1",
"description": "繊維と平行に、幅1センチ程度になるようにスライスする。"
},
"addOnionToPot": {
"type": "add",
"source": "onion1",
"target": "pot1",
"description": "玉ねぎを鍋に投入する。",
"depend": "cutOnion"
},
…
}
}
要素 | 説明 | |
---|---|---|
title | 操作の表示名。省略した場合はtypeにより自動で決まる。typeとしてcustomを指定した場合は必須。 | |
type | 操作の種別。cut(切る)、add(加える)、stew(煮る)などを事前に定義しておく。任意の操作としてcustomを指定することも可。 | |
source | 操作の入力元となる素材、又は容器。typeによっては配列で複数指定も可。 | |
target | 操作の出力先となる素材、又は容器。 | |
description | 操作の説明。省略可。 | |
depend | 依存関係。その操作を行う前に終わらせておくべき操作を指定する。配列で複数指定も可。 | |
until | アクションに終了条件(…分経過するまで、柔らかくなるまで、など)がある場合は指定。 | |
type | 終了条件のタイプ。今のところtime(…分経過するまで)のみ | |
value | untilの値 |
上の例だと、玉ねぎを鍋に入れる前には玉ねぎを切っておく必要があるので、addOnionToPot(玉ねぎを鍋に入れる)のdepend属性としてcutOnion(玉ねぎを切る)を指定しています。実行順序ではなく依存関係で考えるのがポイントで、例えば「人参を切る」と「玉ねぎを切る」はどちらを先に実行してもいいので依存関係はありませんが、「玉ねぎの皮をむく」と「玉ねぎを切る」は、皮むきのほうが先のはずなので依存関係を持たせるべきです。
また、addOnionToPotは、容器を使って素材をグループ化している例です。addアクションを使って容器「pot1(鍋)」に「onion1(玉ねぎ)」を追加しています。素材をグループ化するためには実際に手を動かす(鍋に入れる、ボウルに入れる、など)必要があるので、その都度アクションを割り当てる、という発想になります。
全体像
ここまでを踏まえて作った牛丼のレシピが以下となります。
クリックで展開
{
"title": "牛丼",
"description": "薄く切った牛肉とタマネギなどを醤油などで甘辛く煮込み、丼に盛った飯の上に載せた料理",
"containers": {
"pot1": {
"type": "pot"
},
"riceBowl1": {
"type": "riceBowl",
"description": "一般的などんぶり"
}
},
"materials": {
"rice1": {
"type": "rice",
"quantity": [
{
"amount": 1,
"unit": "go"
}
]
},
"water1": {
"type": "water",
"quantity": [
{
"amount": 150,
"unit": "ml"
}
]
},
"sugar1": {
"type": "sugar",
"quantity": [
{
"amount": 1,
"unit": "tbsp"
}
]
},
"soySauce1": {
"type": "soySauce",
"description": "一般的なこいくち醤油",
"quantity": [
{
"amount": 3,
"unit": "tbsp"
}
]
},
"mirin1": {
"type": "mirin",
"quantity": [
{
"amount": 3,
"unit": "tbsp"
}
]
},
"ginger_tube1": {
"type": "ginger_tube",
"quantity": [
{
"amount": 1,
"unit": "cm"
}
]
},
"beefRib": {
"title": "牛バラ肉",
"type": "beef",
"description": "薄く細切りの牛バラ肉",
"quantity": [
{
"amount": 200,
"unit": "g"
}
]
},
"onion1": {
"type": "onion",
"quantity": [
{
"amount": 0.5,
"unit": "pieces"
},
{
"amount": 100,
"unit": "g"
}
]
}
},
"actions": {
"cookRice": {
"type": "cookRice",
"source": "rice1",
"description": "炊飯器で米を炊く。"
},
"makeBroth": {
"title": "煮汁を作る",
"type": "add",
"source": [
"water1",
"sugar1",
"soySauce1",
"mirin1",
"ginger_tube1"
],
"target": "pot1",
"description": "各調味料を計量し、鍋に入れる。"
},
"cutBeefRib": {
"type": "cut",
"source": "beefRib",
"description": "長辺5センチ程度になるようにカットする。大きさを揃えておくと、味の染み込み具合や食感を均一にできる。"
},
"peelOnion": {
"type": "peel",
"source": "onion1",
"description": "2分割して固いところを切り落とした後、素手で皮をむく。"
},
"cutOnion": {
"type": "cut",
"source": "onion1",
"description": "繊維と平行に、幅1センチ程度になるようにスライスする。",
"depend": "peelOnion"
},
"boil": {
"type": "bringToABoil",
"source": "pot1",
"description": "調味料が入った鍋を強火で沸騰させる。",
"depend": "makeBroth"
},
"addBeefRibToPot": {
"type": "add",
"source": "beefRib",
"target": "pot1",
"description": "中火に変更し、牛肉を加えて解きほぐす。",
"depend": [
"cutBeefRib",
"boil"
]
},
"stew1": {
"type": "stew",
"source": "pot1",
"until": {
"type": "time",
"value": 5
},
"description": "中火のまま5分間煮込む。灰汁が気になる場合は取り除いておく。",
"depend": "addBeefRibToPot"
},
"addOnionToPot": {
"type": "add",
"source": "onion1",
"target": "pot1",
"description": "玉ねぎを鍋に投入する。",
"depend": [
"cutOnion",
"stew1"
]
},
"serve": {
"type": "serve",
"source": [
"rice1",
"pot1"
],
"target": "riceBowl1",
"description": "どんぶりにご飯と具材を盛り付ける。",
"depend": [
"cookRice"
]
}
}
}
視覚化
JSONでの表現ができたところで、せっかくなのでビューアを作って視覚化してみました。
素材や容器で指定されたtypeに応じて、わかりやすいように画像を表示させています。依存関係をきちんと定義すれば、ワークフローのようにレシピを表現できるはずです。
さいごに
まだ牛丼のことしか考えていないので、表現力が足りていないところがあるかもしれません。他にもレシピをJSON化しながら足りないところを足していきたいです。
また、レシピをこのように構造的に表現できれば、クリティカルパスの計算や、それに基づいた作業順序の推奨、進捗の管理などなど、できることの想像が広がります。色々試してみようと思います。
参考文献
レシピ部分で以下を参考にさせて頂きました。
・チューブ生姜適量ではなくて1cmがいい人の理系の料理
Comments