Katkat

Implement a multi-level digital recipe manager

RampSoftware Engineer · Online Assessment

Last updated

Tests skills in data modeling and stateful class implementation, covering CRUD operations, case-insensitive string handling, recipe ID generation, list management, search and sort logic, and basic user/edit permissions.

Implement a RecipeManager class for a digital recipe manager. The task is progressive (Levels 1–4). You can assume a single-process in-memory implementation. Correctness matters; efficiency is not required. Unless otherwise stated, all string comparisons for recipe names and ingredient matching are case-insensitive.

Data model

A recipe has:

  • recipe_id: formatted as "recipe" + N, where N is a sequential integer starting from 1 in creation order.
  • name: unique across all recipes (case-insensitive).
  • ingredients: list of strings (keep original order).
  • steps: list of strings (keep original order).

When returning a recipe, represent it as:

  • [name, ingredients_as_string, steps_as_string]
  • ingredients_as_string is the ingredients joined by "," (comma separator), and similarly for steps_as_string.

If a method returns a list of recipe IDs, they are strings like "recipe1", "recipe2", etc.


Level 1: Basic CRUD (no users)

Implement:

  1. add_recipe(self, name: str, ingredients: list[str], steps: list[str]) -> str | None
  • Add a new recipe and return its recipe_id.
  • If a recipe with the same name already exists (case-insensitive), return None.
  1. get_recipe(self, recipe_id: str) -> list[str]
  • Return [name, ingredients_as_string, steps_as_string] for the recipe.
  • If the recipe does not exist, return an empty array [].
  • Ingredients must be returned in the same order they were added.
  1. update_recipe(self, recipe_id: str, name: str, ingredients: list[str], steps: list[str]) -> bool
  • Update the recipe fields.
  • Return True if successful.
  • Return False if the recipe does not exist, or if the new name conflicts with another existing recipe name (case-insensitive).
  1. delete_recipe(self, recipe_id: str) -> bool
  • Delete the recipe.
  • Return True if it existed and was deleted; otherwise False.

Level 2: Searching and sorting

Implement:

  1. search_recipes_by_ingredient(self, ingredient: str) -> list[str]
  • Return recipe IDs for recipes that contain the specified ingredient (case-insensitive match).
  • Sort results by:
  1. number of ingredients ascending
  2. recipe_id ascending (tie-breaker)
  1. list_recipes(self, sort_by: str) -> list[str]
  • Return all recipe IDs sorted by sort_by, where sort_by is either:
  • "name": lexicographic ascending by recipe name; tie-breaker recipe_id ascending
  • "ingredient_count": ascending by number of ingredients; tie-breaker recipe_id ascending
  • If sort_by is invalid, default to sorting by "name".

Level 3: Users and edits

Introduce users.

  1. add_user(self, user_id: str) -> bool
  • Add a new user.
  • Return True if added; False if the user already exists.
  1. edit_recipe(self, user_id: str, recipe_id: str, new_name: str, new_ingredients: list[str], new_steps: list[str]) -> bool
  • Any existing user may edit any recipe.
  • Enforce recipe-name uniqueness (case-insensitive).
  • Return True on success.
  • Return False if the user does not exist, the recipe does not exist, or the new name conflicts with another recipe name.

Level 4: Version history and rollback

Add version control for recipes.

Definitions:

  • Recipes created by add_recipe have no version history until the first successful edit_recipe or update_recipe.
  • Every successful edit_recipe or update_recipe creates a new version entry.
  • Version entries are numbered starting at 1 and increase by 1 per entry.
  • Each version string format:
  • <version>:<name>:<ingredients_as_string>:<steps_as_string>:<last_edited_by>
  • For edit_recipe, <last_edited_by> is the user_id.
  • For update_recipe, <last_edited_by> is the literal string "system".

Implement:

  1. version_recipe(self, recipe_id: str) -> list[str]
  • Return all versions for the recipe, sorted by version number ascending.
  • Return [] if the recipe does not exist or if it was never edited/updated (i.e., no version history).
  1. rollback_recipe(self, recipe_id: str, version: int) -> bool
  • Roll back the current recipe state (name/ingredients/steps) to match the specified historical version.
  • Rolling back creates a new version entry appended to the history (history is immutable), with editor set to the literal string "rollback".
  • Return True if successful.
  • Return False if the recipe does not exist, the requested version does not exist, or if rolling back would cause a name conflict with another existing recipe (case-insensitive).