Implement a Recipe Cart
Company: Decagon
Role: Software Engineer
Category: Coding & Algorithms
Difficulty: medium
Interview Round: Technical Screen
Quick Answer: This question evaluates the ability to design and implement stateful data structures and aggregation logic for tracking ingredient counts, applying tiered bulk discounts, and managing undo/redo history.
Part 1: Recipe Cart Discount Tracker
Constraints
- 0 <= len(recipe_names) == len(recipe_ingredients) <= 10^4
- 0 <= len(discount_ingredients) == len(discount_rules) <= 10^4
- 1 <= len(operations) <= 10^5
- The sum of all recipe ingredient list lengths is at most 2 * 10^5
- The sum of all discount rule counts is at most 2 * 10^5
- recipe_names are unique, discount_ingredients are unique, thresholds are positive integers, and discounts are non-negative integers
Examples
Input: (['Chicken Parm', 'Stir Fry', 'Salad'], [['Garlic', 'Chicken', 'Cheese'], ['Garlic', 'Broccoli'], ['Lettuce']], ['Garlic', 'Chicken'], [[[2, 3], [4, 8]], [[1, 2], [2, 5]]], [['add', 'Chicken Parm'], ['get'], ['add', 'Stir Fry'], ['get'], ['remove', 'Chicken Parm'], ['get']])
Expected Output: [2, 5, 0]
Explanation: Chicken Parm alone gives Chicken quantity 1 for a $2 discount. Adding Stir Fry raises Garlic to quantity 2 for $3 more, so total becomes $5. Removing Chicken Parm drops both Garlic and Chicken below their thresholds.
Input: (['A', 'B'], [['x'], ['x', 'y']], ['x', 'y'], [[[2, 4]], [[1, 1]]], [['add', 'A'], ['add', 'A'], ['get'], ['remove', 'B'], ['get'], ['add', 'B'], ['get']])
Expected Output: [0, 0, 5]
Explanation: Adding A twice is a no-op on the second call. Removing B before it is in the cart is also a no-op. After B is added, x reaches 2 for $4 and y reaches 1 for $1.
Input: ([], [], [], [], [['get']])
Expected Output: [0]
Explanation: Edge case: empty catalog and no discounts. The total discount is always 0.
Input: (['R1', 'R2', 'R3', 'R4'], [['g'], ['g'], ['g'], ['g']], ['g'], [[[2, 3], [4, 8]]], [['add', 'R1'], ['get'], ['add', 'R2'], ['get'], ['add', 'R3'], ['get'], ['add', 'R4'], ['get']])
Expected Output: [0, 3, 3, 8]
Explanation: This checks threshold boundaries: quantity 1 gives $0, 2 gives $3, 3 still gives $3, and 4 gives $8.
Hints
- Recomputing the full discount from scratch after every add or remove is too slow. Track the current quantity of each ingredient and update only the ingredients touched by the recipe being added or removed.
- Sort each ingredient's discount rules by threshold. Then you can use binary search to find the best qualifying rule for a given quantity.
Part 2: Recipe Cart with Undo and Redo
Constraints
- 0 <= len(recipe_names) == len(recipe_ingredients) <= 10^4
- 0 <= len(discount_ingredients) == len(discount_rules) <= 10^4
- 1 <= len(operations) <= 10^5
- The sum of all recipe ingredient list lengths is at most 2 * 10^5
- The sum of all discount rule counts is at most 2 * 10^5
- recipe_names are unique, discount_ingredients are unique, thresholds are positive integers, and discounts are non-negative integers
Examples
Input: (['A', 'B'], [['x'], ['x', 'y']], ['x', 'y'], [[[2, 4]], [[1, 1]]], [['add', 'A'], ['add', 'B'], ['get'], ['undo'], ['get'], ['redo'], ['get']])
Expected Output: [5, 0, 5]
Explanation: After adding both recipes, x qualifies for $4 and y for $1. Undo removes the effect of adding B, and redo restores it.
Input: (['A', 'B', 'C'], [['g'], ['g'], ['z']], ['g', 'z'], [[[2, 3]], [[1, 2]]], [['add', 'A'], ['add', 'B'], ['remove', 'A'], ['get'], ['undo'], ['get'], ['redo'], ['get'], ['undo'], ['add', 'C'], ['redo'], ['get']])
Expected Output: [0, 3, 0, 5]
Explanation: The final redo does nothing because adding C is a new successful cart-changing operation, so it clears the redo history.
Input: (['A'], [['m']], ['m'], [[[1, 7]]], [['undo'], ['redo'], ['remove', 'A'], ['add', 'A'], ['get'], ['add', 'A'], ['undo'], ['get'], ['redo'], ['get']])
Expected Output: [7, 0, 7]
Explanation: Edge case: undo and redo with empty history do nothing. The duplicate add is also a no-op and must not overwrite the real history.
Input: (['R1', 'R2'], [['a'], ['b']], ['a', 'b'], [[[1, 1]], [[1, 2]]], [['add', 'R1'], ['add', 'R2'], ['undo'], ['undo'], ['undo'], ['get'], ['redo'], ['get'], ['redo'], ['get']])
Expected Output: [0, 1, 3]
Explanation: This checks multiple undos past the available history, followed by redoing the undone operations in order.
Hints
- Use one stack for undo history and one stack for redo history. When you undo an add, you perform a remove; when you undo a remove, you perform an add.
- Do not recompute the whole cart after every history action. Reuse the same incremental ingredient-count update logic from add/remove for undo and redo too.