Skip to content

Add concept exercise: bakery-order-system #373

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 5 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
13 changes: 12 additions & 1 deletion config.json
Original file line number Diff line number Diff line change
Expand Up @@ -92,14 +92,25 @@
"name": "RPN Calculator",
"uuid": "536d9f09-5910-4a26-93fd-2242667b0b87",
"concepts": [
"control-flow",
"enums"
],
"prerequisites": [
"arrays"
],
"status": "wip"
},
{
"slug": "bakery-order-system",
"name": "Bakery Order System",
"uuid": "a979b9a0-7fd9-46f7-bacf-f0044c3c9bb0",
"concepts": [
"control-flow"
],
"prerequisites": [
"match-basics"
],
"status": "beta"
},
{
"slug": "welcome-to-tech-palace",
"name": "Welcome To Tech Palace!",
Expand Down
28 changes: 28 additions & 0 deletions exercises/concept/bakery-order-system/.docs/hints.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
# Hints

## General

- [The Cairo Book - Control Flow][tcb-control-flow]
- [The Cairo Book - Arrays][tcb-arrays]

## 1. Calculate Order Total

- Use `match` or `if`/`else if` to handle different pastry types
- Multiply the price per pastry by the quantity
- Each pastry type has a fixed price in coins

## 2. Apply Volume Discounts

- Use `if`/`else if` to check discount tiers from highest to lowest
- Remember that discounts should be applied as integer division (rounded down)
- 10% discount means multiply by 90 and divide by 100

## 3. Create Baking Schedule

- Use a `loop` to build the schedule hour by hour
- Each hour, bake up to 5 orders (or whatever remains)
- Continue until all orders are scheduled
- Use `break` when no more orders remain

[tcb-control-flow]: https://www.starknet.io/cairo-book/ch02-05-control-flow.html
[tcb-arrays]: https://www.starknet.io/cairo-book/ch03-01-arrays.html
43 changes: 43 additions & 0 deletions exercises/concept/bakery-order-system/.docs/instructions.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
# Instructions

In this exercise you'll help manage a small bakery's daily operations using Cairo's control flow constructs.

## 1. Calculate Order Total

Implement `calculate_total` that calculates the cost of an order based on pastry type and quantity:

- Croissant: 3 coins each
- Muffin: 2 coins each
- Cookie: 1 coin each

```rust
calculate_total(Pastry::Croissant, 4) // Returns: 12 (4 * 3)
calculate_total(Pastry::Cookie, 10) // Returns: 10 (10 * 1)
```

## 2. Apply Volume Discounts

Implement `apply_discount` that applies discounts based on order total:

- Orders ≥ 20 coins: 10% discount
- Orders ≥ 10 coins: 5% discount
- Orders < 10 coins: no discount

```rust
apply_discount(25) // Returns: 22 (25 - 10% = 22.5, rounded down)
apply_discount(15) // Returns: 14 (15 - 5% = 14.25, rounded down)
apply_discount(8) // Returns: 8 (no discount)
```

## 3. Create Baking Schedule

Implement `baking_schedule` that creates a daily baking plan.
The bakery bakes in batches of 5 orders per hour:

```rust
baking_schedule(18) // Returns: [5, 5, 5, 3] (3 full batches + 3 remaining)
baking_schedule(10) // Returns: [5, 5] (2 full batches)
baking_schedule(3) // Returns: [3] (1 partial batch)
```

Use appropriate control flow constructs (`if`/`else`, `loop`) to solve each problem efficiently.
66 changes: 66 additions & 0 deletions exercises/concept/bakery-order-system/.docs/introduction.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
# Introduction

Control flow in Cairo allows you to control the execution path of your programs based on conditions and repetition.

## Conditional Execution

Use `if` expressions to execute code based on conditions:

```rust
let number = 42;
if number > 0 {
println!("Positive number");
} else if number < 0 {
println!("Negative number");
} else {
println!("Zero");
}
```

`if` is an expression, so it can return values:

```rust
let result = if condition { 5 } else { 10 };
```

## Loops

Use `loop` to repeat code until a condition is met:

```rust
let mut counter = 0;
loop {
if counter == 5 {
break;
}
println!("Counter: {}", counter);
counter += 1;
}
```

Loops can return values using `break`:

```rust
let result = loop {
counter += 1;
if counter == 10 {
break counter * 2; // Returns 20
}
};
```

Use `continue` to skip to the next iteration:

```rust
let mut i = 0;
loop {
i += 1;
if i % 2 == 0 {
continue; // Skip even numbers
}
if i > 10 {
break;
}
println!("{}", i); // Only prints odd numbers
}
```
20 changes: 20 additions & 0 deletions exercises/concept/bakery-order-system/.meta/config.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
{
"authors": [
"0xNeshi"
],
"files": {
"solution": [
"src/lib.cairo"
],
"test": [
"tests/bakery_order_system.cairo"
],
"exemplar": [
".meta/exemplar.cairo"
],
"invalidator": [
"Scarb.toml"
]
},
"blurb": "Learn Cairo's control flow constructs while managing a bakery's daily operations."
}
19 changes: 19 additions & 0 deletions exercises/concept/bakery-order-system/.meta/design.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
# Design

## Learning Objectives

- Know how to use control flow constructs.

## Concepts

- Control flow

## Prerequisites

- Match basics

## Resources to Refer To

- [Cairo Book - Control Flow][cf]

[cf]: https://www.starknet.io/cairo-book/ch02-05-control-flow.html
50 changes: 50 additions & 0 deletions exercises/concept/bakery-order-system/.meta/exemplar.cairo
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
#[derive(Drop, PartialEq)]
pub enum Pastry {
Croissant,
Muffin,
Cookie,
}

pub fn calculate_total(pastry: Pastry, quantity: u32) -> u32 {
let price_per_item = match pastry {
Pastry::Croissant => 3,
Pastry::Muffin => 2,
Pastry::Cookie => 1,
};

price_per_item * quantity
}

pub fn apply_discount(total: u32) -> u32 {
if total >= 20 {
// 10% discount: multiply by 90, divide by 100
(total * 90) / 100
} else if total >= 10 {
// 5% discount: multiply by 95, divide by 100
(total * 95) / 100
} else {
// No discount
total
}
}

pub fn baking_schedule(total_orders: u32) -> Array<u32> {
let mut schedule = ArrayTrait::new();
let mut remaining_orders = total_orders;

loop {
if remaining_orders == 0 {
break;
}

if remaining_orders >= 5 {
schedule.append(5);
remaining_orders -= 5;
} else {
schedule.append(remaining_orders);
remaining_orders = 0;
}
};

schedule
}
7 changes: 7 additions & 0 deletions exercises/concept/bakery-order-system/Scarb.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
[package]
name = "bakery_order_system"
version = "0.1.0"
edition = "2024_07"

[dev-dependencies]
cairo_test = "2.9.2"
30 changes: 30 additions & 0 deletions exercises/concept/bakery-order-system/src/lib.cairo
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
/// Types of pastries available in the bakery
#[derive(Drop, PartialEq)]
pub enum Pastry {
Croissant,
Muffin,
Cookie,
}

/// Calculate the total cost of an order
pub fn calculate_total(pastry: Pastry, quantity: u32) -> u32 {
// Croissant: 3 coins, Muffin: 2 coins, Cookie: 1 coin
// Return total cost for the given quantity
panic!("implement `calculate_total`")
}

/// Apply discount based on order size
pub fn apply_discount(total: u32) -> u32 {
// Orders >= 20 coins: 10% discount
// Orders >= 10 coins: 5% discount
// Orders < 10 coins: no discount
panic!("implement `apply_discount`")
}

/// Generate the daily baking schedule
pub fn baking_schedule(total_orders: u32) -> Array<u32> {
// Bake in batches of 5 orders each
// Return an array showing how many orders are baked each hour
// Continue until all orders are completed
panic!("implement `baking_schedule`")
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,81 @@
use bakery_order_system::{Pastry, calculate_total, apply_discount, baking_schedule};

#[test]
fn test_calculate_croissant_total() {
assert_eq!(calculate_total(Pastry::Croissant, 4), 12);
assert_eq!(calculate_total(Pastry::Croissant, 1), 3);
}

#[test]
#[ignore]
fn test_calculate_muffin_total() {
assert_eq!(calculate_total(Pastry::Muffin, 5), 10);
assert_eq!(calculate_total(Pastry::Muffin, 3), 6);
}

#[test]
#[ignore]
fn test_calculate_cookie_total() {
assert_eq!(calculate_total(Pastry::Cookie, 10), 10);
assert_eq!(calculate_total(Pastry::Cookie, 7), 7);
}

#[test]
#[ignore]
fn test_no_discount() {
assert_eq!(apply_discount(5), 5);
assert_eq!(apply_discount(9), 9);
}

#[test]
#[ignore]
fn test_five_percent_discount() {
assert_eq!(apply_discount(10), 9); // 10 * 0.95 = 9.5, rounded down to 9
assert_eq!(apply_discount(15), 14); // 15 * 0.95 = 14.25, rounded down to 14
assert_eq!(apply_discount(19), 18); // 19 * 0.95 = 18.05, rounded down to 18
}

#[test]
#[ignore]
fn test_ten_percent_discount() {
assert_eq!(apply_discount(20), 18); // 20 * 0.9 = 18
assert_eq!(apply_discount(25), 22); // 25 * 0.9 = 22.5, rounded down to 22
assert_eq!(apply_discount(30), 27); // 30 * 0.9 = 27
}

#[test]
#[ignore]
fn test_baking_schedule_multiple_batches() {
let result = baking_schedule(18);
let expected = [5, 5, 5, 3].span();

assert_eq!(result.len(), 4);
assert_eq!(result.span(), expected);
}

#[test]
#[ignore]
fn test_baking_schedule_exact_batches() {
let result = baking_schedule(10);
let expected = [5, 5].span();

assert_eq!(result.len(), 2);
assert_eq!(result.span(), expected);
}

#[test]
#[ignore]
fn test_baking_schedule_single_partial_batch() {
let result = baking_schedule(3);
let expected = [3].span();

assert_eq!(result.len(), 1);
assert_eq!(result.span(), expected);
}

#[test]
#[ignore]
fn test_baking_schedule_zero_orders() {
let result = baking_schedule(0);
assert_eq!(result.len(), 0);
}
Loading