トポロジカルソートで手持ちの材料から作れる料理を全部見つける

料理ごとに要る材料と、手持ちの材料から、作れる料理を全部見つけます。ほかの料理を材料に使う料理もあるので、「要る物が全部そろった料理から作る」を、残りの数を数えながら列で回すトポロジカルソートの練習です。

海外で定番の問題リスト 4 本には入っていません

問題

料理の名前の並び recipes と、料理ごとに要る材料の並び needs を受け取ります。needs[i] は、料理 recipes[i] を作るのに要る材料の名前の並びです。材料には、ほかの料理の名前が入ることもあります。手持ちの材料の並び stock も受け取ります。手持ちの材料は、いくらでも使えます。

作った料理は、ほかの料理の材料として使えます。手持ちの材料と、作れる料理だけを使って作れる料理を全部集め、名前の辞書の順に並べて返す関数を書いてください。1 つも作れなければ、空の並び [] を返します。

  • 名前はどれも英小文字の 1 字以上 10 字以下です。料理の名前は、どれも違います
  • 料理の名前と、手持ちの材料の名前は重なりません
  • どの料理も、材料は 1 つ以上あります。1 つの料理の材料の並びに、同じ名前は 2 回出てきません。料理が自分自身を材料に使うことはありません
  • stock にも、同じ名前は 2 回出てきません
  • recipes の長さは 1 以上 100 以下、stock の長さは 0 以上 100 以下です

入力と出力の例

例 1

roux は flour と butter で作れます。curry は roux と rice で、roux を先に作れば作れます。辞書の順に並べて返します。

recipes = ["curry", "roux"] needs = [["roux", "rice"], ["flour", "butter"]] stock = ["rice", "flour", "butter"]
["curry", "roux"]

例 2

toast は bread で作れますが、bread に要る yeast がありません。bread も toast も作れません。

recipes = ["toast", "bread"] needs = [["bread"], ["flour", "yeast"]] stock = ["flour"]
[]

例 3

soup と stew は、互いを材料に使っています。どちらかを先に作ることができないので、どちらも作れません。salad は手持ちの材料だけで作れます。

recipes = ["soup", "stew", "salad"] needs = [["stew", "salt"], ["soup"], ["leaf"]] stock = ["salt", "leaf"]
["salad"]

面接での聞かれ方

面接では、短く次のように渡されることが多いです。

  • 「レシピと必要な材料、手持ちの材料が渡されます。作れるレシピを全部返してください。レシピはほかのレシピの材料になることがあります」
  • 料理ごとに再帰で調べる書き方を出すと、「料理どうしが互いを材料にしていたら、どうなりますか?」と聞かれます
  • 書き終えると「これは何というアルゴリズムですか?」と、トポロジカルソートと結び付けられるかを見られます

返す順と、手持ちの材料がいくらでも使えるかを最初に確かめておくと、数え方が決まります。

ヒント

まだ開いていません

付箋は 4 枚。下の段ほど答えに近づきます

  1. ヒント 2

  2. ヒント 3

  3. ヒント 4答えに近い

アルゴリズムのチートシートの「グラフ探索と幅優先探索」に、この型の要点を表とコードでまとめています。

グラフ探索は、つながりを近い順か奥からたどります。迷路、最短の手数、講座を受ける順のような問題が同じ形です。

main.py
def possible_recipes(recipes: list[str], needs: list[list[str]], stock: list[str]) -> list[str]:
    """
    Goal: 何を返すか

    idea1: まず思いつく案
    -> runtime: O(?)  space: O(?)

    idea2: もっと速い案
    -> runtime: O(?)  space: O(?)

    edge case: 空、1 つだけ、など
    """
    # ここに書きます
    pass


# 「実行」を押すと、この行の結果が出ます
print(possible_recipes(["curry", "roux"], [["roux", "rice"], ["flour", "butter"]], ["rice", "flour", "butter"]))

できているか「例で確かめる」を押すと、問題の例を全部この関数に渡して、通ったかをここに出します