feat(curriculum): add bfs workshop (#62517)

Co-authored-by: Ilenia <26656284+ilenia-magoni@users.noreply.github.com>
This commit is contained in:
Dario
2025-10-21 11:59:30 +02:00
committed by GitHub
co-authored by Ilenia
parent b36060bc28
commit 7abb927514
18 changed files with 841 additions and 2 deletions
+4 -2
View File
@@ -4732,8 +4732,10 @@
]
},
"workshop-breadth-first-search": {
"title": "Build a Breadth First Search",
"intro": [""]
"title": "Implement the Breadth-First Search Algorithm",
"intro": [
"In this workshop, you will use the bread-first search algorithm to generate all valid combinations of parentheses."
]
},
"lab-depth-first-search": {
"title": "Implement the Depth-First Search Algorithm",
@@ -0,0 +1,9 @@
---
title: Introduction to the Implement the Breadth-First Search Algorithm
block: workshop-breadth-first-search
superBlock: full-stack-developer
---
## Introduction to the Implement the Breadth-First Search Algorithm
In this workshop, you will use the bread-first search algorithm to generate all valid combinations of parentheses.
@@ -0,0 +1,49 @@
---
id: 68da604b8b6012a33e2344fb
title: Step 1
challengeType: 20
dashedName: step-1
---
# --description--
In this workshop, you'll implement a function that generates all valid combinations of parentheses using a breadth-first search (BFS) approach. For example, the valid combinations of two pairs of parentheses are `(())` and `()()`.
Start by creating a function named `gen_parentheses` with a single parameter `pairs`. For now, return an empty list from the function.
# --hints--
You should define a function named `gen_parentheses`.
```js
({ test: () => runPython(`
assert _Node(_code).has_function("gen_parentheses")
`) })
```
Your `gen_parentheses` function should have a single parameter named `pairs`.
```js
({ test: () => runPython(`
assert _Node(_code).find_function("gen_parentheses").has_args("pairs")
`) })
```
Your function should return an empty list.
```js
({ test: () => runPython(`
foo = _Node(_code).find_function("gen_parentheses")
assert foo.has_return("[]") or foo.has_return("list()")
`) })
```
# --seed--
## --seed-contents--
```py
--fcc-editable-region--
--fcc-editable-region--
```
@@ -0,0 +1,54 @@
---
id: 68da6138d70960c197ec5eec
title: Step 2
challengeType: 20
dashedName: step-2
---
# --description--
Before implementing the core algorithm, you need to validate the input. The `pairs` parameter should be an integer, as it represents the number of parentheses pairs to generate.
Add an `if` statement at the beginning of your function to check if `pairs` is not an integer. Use the `isinstance()` function for that.
If the condition is true, return the string `The number of pairs should be an integer`.
# --hints--
You should have an `if` statement in your `gen_parentheses` function.
```js
({ test: () => runPython(`
assert _Node(_code).find_function("gen_parentheses").find_ifs()[0]
`) })
```
Your `if` statement should check if `pairs` is not an integer using `not isinstance(pairs, int)`.
```js
({ test: () => runPython(`
assert _Node(_code).find_function("gen_parentheses").find_ifs()[0].find_conditions()[0].is_equivalent("not isinstance(pairs, int)")
`) })
```
You should return the string `The number of pairs should be an integer` when the input is not an integer.
```js
({ test: () => runPython(`
msg = 'The number of pairs should be an integer'
args = [1.1, "1", [1, 2], {}]
assert all(gen_parentheses(arg) == msg for arg in args)
`) })
```
# --seed--
## --seed-contents--
```py
--fcc-editable-region--
def gen_parentheses(pairs):
return []
--fcc-editable-region--
```
@@ -0,0 +1,54 @@
---
id: 68da6138d70960c197ec5eed
title: Step 3
challengeType: 20
dashedName: step-3
---
# --description--
Next, you need to validate that the number of pairs is at least one, since you can't generate parentheses combinations with zero or negative pairs.
Add another `if` statement to check if `pairs` is less than `1`. If this condition is true, return the string `The number of pairs should be at least 1`.
# --hints--
You should have a second `if` statement in your `gen_parentheses` function.
```js
({ test: () => runPython(`
assert _Node(_code).find_function("gen_parentheses").find_ifs()[1]
`) })
```
Your second `if` statement should check if `pairs` is less than `1`.
```js
({ test: () => runPython(`
cond = _Node(_code).find_function("gen_parentheses").find_ifs()[1].find_conditions()[0]
assert cond.is_equivalent("pairs < 1") or cond.is_equivalent("1 > pairs")
`) })
```
You should return the string `The number of pairs should be at least 1` when `pairs` is less than `1`.
```js
({ test: () => runPython(`
msg = 'The number of pairs should be at least 1'
assert gen_parentheses(0) == msg and gen_parentheses(-1) == msg
`) })
```
# --seed--
## --seed-contents--
```py
--fcc-editable-region--
def gen_parentheses(pairs):
if not isinstance(pairs, int):
return 'The number of pairs should be an integer'
return []
--fcc-editable-region--
```
@@ -0,0 +1,54 @@
---
id: 68da6138d70960c197ec5eee
title: Step 4
challengeType: 20
dashedName: step-4
---
# --description--
Now you'll set up the data structure to store your results. Create a variable named `result` and initialize it as an empty list. This list will store all the valid parentheses combinations you generate.
Update your return statement to return `result` instead of an empty list.
# --hints--
You should declare a variable named `result` in your `gen_parentheses` function.
```js
({ test: () => runPython(`
assert _Node(_code).find_function("gen_parentheses").has_variable("result")
`) })
```
You should initialize `result` as an empty list.
```js
({ test: () => runPython(`
assert _Node(_code).find_function("gen_parentheses").find_variable("result").is_equivalent("result = []")
`) })
```
You should return `result` at the end of your function.
```js
({ test: () => runPython(`
assert _Node(_code).find_function("gen_parentheses").has_return("result")
`) })
```
# --seed--
## --seed-contents--
```py
--fcc-editable-region--
def gen_parentheses(pairs):
if not isinstance(pairs, int):
return 'The number of pairs should be an integer'
if pairs < 1:
return 'The number of pairs should be at least 1'
return []
--fcc-editable-region--
```
@@ -0,0 +1,51 @@
---
id: 68da6138d70960c197ec5eef
title: Step 5
challengeType: 20
dashedName: step-5
---
# --description--
For the breadth-first search approach, you'll use a queue to track different states as you build the parentheses combinations. Each state will be represented as a tuple containing three elements:
- The current string being built
- The number of opening parentheses used so far
- The number of closing parentheses used so far
Create a variable named `queue` and initialize it with a list containing one tuple: `('', 0, 0)`. This represents the starting state with an empty string and zero parentheses used.
# --hints--
You should declare a variable named `queue` in your `gen_parentheses` function.
```js
({ test: () => runPython(`
assert _Node(_code).find_function("gen_parentheses").has_variable("queue")
`) })
```
You should initialize `queue` with a list containing the tuple `('', 0, 0)`.
```js
({ test: () => runPython(`
assert _Node(_code).find_function("gen_parentheses").find_variable("queue").is_equivalent("queue = [('', 0, 0)]")
`) })
```
# --seed--
## --seed-contents--
```py
--fcc-editable-region--
def gen_parentheses(pairs):
if not isinstance(pairs, int):
return 'The number of pairs should be an integer'
if pairs < 1:
return 'The number of pairs should be at least 1'
result = []
return result
--fcc-editable-region--
```
@@ -0,0 +1,57 @@
---
id: 68da6138d70960c197ec5ef0
title: Step 6
challengeType: 20
dashedName: step-6
---
# --description--
Now you'll implement the main BFS loop. Create a `while` loop that continues as long as the `queue` is not empty (while `queue` evaluates to `True`).
Inside the loop, print `queue`.
# --hints--
You should have a `while` loop in your `gen_parentheses` function.
```js
({ test: () => runPython(`
assert _Node(_code).find_function("gen_parentheses").find_whiles()[0]
`) })
```
Your `while` loop should have the condition `queue`.
```js
({ test: () => runPython(`
assert _Node(_code).find_function("gen_parentheses").find_whiles()[0].find_conditions()[0].is_equivalent("queue")
`) })
```
You should print `queue` inside your `while` loop.
```js
({ test: () => runPython(`
assert _Node(_code).find_function("gen_parentheses").find_whiles()[0].find_bodies()[0].has_call("print(queue)")
`) })
```
# --seed--
## --seed-contents--
```py
--fcc-editable-region--
def gen_parentheses(pairs):
if not isinstance(pairs, int):
return 'The number of pairs should be an integer'
if pairs < 1:
return 'The number of pairs should be at least 1'
queue = [('', 0, 0)]
result = []
return result
--fcc-editable-region--
```
@@ -0,0 +1,62 @@
---
id: 68da6138d70960c197ec5ef1
title: Step 9
challengeType: 20
dashedName: step-9
---
# --description--
Now you need to identify when you've built a complete parentheses combination. A complete combination has a length equal to twice the number of pairs (since each pair contributes one opening and one closing parenthesis).
Inside your `while` loop, add an `if` statement to check if `len(current) == 2 * pairs`. When this condition is true, append `current` to the `result` list.
# --hints--
You should have an `if` statement inside your `while` loop.
```js
({ test: () => runPython(`
assert _Node(_code).find_function("gen_parentheses").find_whiles()[0].find_bodies()[0].find_ifs()[0]
`) })
```
Your `if` statement should check if `len(current) == 2 * pairs`.
```js
({ test: () => runPython(`
assert _Node(_code).find_function("gen_parentheses").find_whiles()[0].find_bodies()[0].find_ifs()[0].find_conditions()[0].is_equivalent("len(current) == 2 * pairs")
`) })
```
You should append `current` to the `result` list inside your new `if` statement.
```js
({ test: () => runPython(`
assert _Node(_code).find_function("gen_parentheses").find_whiles()[0].find_bodies()[0].find_ifs()[0].find_bodies()[0].has_call("result.append(current)")
`) })
```
# --seed--
## --seed-contents--
```py
def gen_parentheses(pairs):
if not isinstance(pairs, int):
return 'The number of pairs should be an integer'
if pairs < 1:
return 'The number of pairs should be at least 1'
queue = [('', 0, 0)]
result = []
--fcc-editable-region--
while queue:
print(queue)
current, opens_used, closes_used = queue.pop(0)
--fcc-editable-region--
return result
print(gen_parentheses(1))
```
@@ -0,0 +1,66 @@
---
id: 68da6138d70960c197ec5ef2
title: Step 10
challengeType: 20
dashedName: step-10
---
# --description--
If the current string isn't complete yet, you need to explore the next possible states. Add an `else` clause to your `if` statement.
Inside the `else` block, you'll handle adding opening parentheses. Add an `if` statement to check if `opens_used < pairs`. This ensures you don't use more opening parentheses than allowed.
If this condition is true, append a new tuple to the `queue`: `(current + '(', opens_used + 1, closes_used)`. This represents the state after adding an opening parenthesis.
# --hints--
You should have an `else` clause for your `if` statement inside the `while` loop.
```js
({ test: () => runPython(`
assert _Node(_code).find_function("gen_parentheses").find_whiles()[0].find_bodies()[0].find_ifs()[0].tree.orelse
`) })
```
You should have an `if` statement inside the `else` block checking if `opens_used < pairs`.
```js
({ test: () => runPython(`
assert _Node(_code).find_function("gen_parentheses").find_whiles()[0].find_bodies()[0].find_ifs()[0].find_conditions()[1].is_equivalent("opens_used < pairs")
`) })
```
You should append `(current + '(', opens_used + 1, closes_used)` to the queue when the condition is true.
```js
({ test: () => runPython(`
assert _Node(_code).find_function("gen_parentheses").find_whiles()[0].find_bodies()[0].find_ifs()[0].find_bodies()[1].has_call("queue.append((current + '(', opens_used + 1, closes_used))")
`) })
```
# --seed--
## --seed-contents--
```py
def gen_parentheses(pairs):
if not isinstance(pairs, int):
return 'The number of pairs should be an integer'
if pairs < 1:
return 'The number of pairs should be at least 1'
queue = [('', 0, 0)]
result = []
--fcc-editable-region--
while queue:
print(queue)
current, opens_used, closes_used = queue.pop(0)
if len(current) == 2 * pairs:
result.append(current)
--fcc-editable-region--
return result
print(gen_parentheses(1))
```
@@ -0,0 +1,72 @@
---
id: 68da6138d70960c197ec5ef3
title: Step 11
challengeType: 20
dashedName: step-11
---
# --description--
Now you need to handle adding closing parentheses. The key rule is that you can only add a closing parenthesis if it maintains balance, meaning there must be more opening parentheses used than closing parentheses.
Still within the `else` block, add second `if` statement to check if `closes_used < opens_used`.
If this condition is true, append another new tuple to the `queue`: `(current + ')', opens_used, closes_used + 1)`. This represents the state after adding a closing parenthesis.
# --hints--
You should have a second `if` statement inside your `else` block.
```js
({ test: () => runPython(`
import ast
assert isinstance(_Node(_code).find_function("gen_parentheses").find_whiles()[0].find_bodies()[0].find_ifs()[0].tree.orelse[1], ast.If)
`) })
```
Your second nested `if` statement should check if `closes_used < opens_used`.
```js
({ test: () => runPython(`
assert _Node(_Node(_code).find_function("gen_parentheses").find_whiles()[0].find_bodies()[0].find_ifs()[0].tree.orelse[1].test).is_equivalent("closes_used < opens_used")
`) })
```
You should append `(current + ')', opens_used, closes_used + 1)` to the queue when the condition is true.
```js
({ test: () => runPython(`
assert _Node(_Node(_code).find_function("gen_parentheses").find_whiles()[0].find_bodies()[0].find_ifs()[0].tree.orelse[1]).has_call("queue.append((current + ')', opens_used, closes_used + 1))")
`) })
```
# --seed--
## --seed-contents--
```py
def gen_parentheses(pairs):
if not isinstance(pairs, int):
return 'The number of pairs should be an integer'
if pairs < 1:
return 'The number of pairs should be at least 1'
queue = [('', 0, 0)]
result = []
while queue:
print(queue)
current, opens_used, closes_used = queue.pop(0)
if len(current) == 2 * pairs:
result.append(current)
--fcc-editable-region--
else:
if opens_used < pairs:
queue.append((current + '(', opens_used + 1, closes_used))
--fcc-editable-region--
return result
print(gen_parentheses(1))
```
@@ -0,0 +1,60 @@
---
id: 68da6138d70960c197ec5ef4
title: Step 12
challengeType: 20
dashedName: step-12
---
# --description--
Your function is now complete. Test it again by printing `gen_parentheses(2)` instead of `gen_parentheses(1)`.
# --hints--
You should print `gen_parentheses(2)`.
```js
({ test: () => runPython(`
assert _Node(_code).has_call("print(gen_parentheses(2))")
`) })
```
You should not have `print(gen_parentheses(1))` in your code
```js
({ test: () => runPython(`
assert not _Node(_code).has_call("print(gen_parentheses(1))")
`) })
```
# --seed--
## --seed-contents--
```py
def gen_parentheses(pairs):
if not isinstance(pairs, int):
return 'The number of pairs should be an integer'
if pairs < 1:
return 'The number of pairs should be at least 1'
queue = [('', 0, 0)]
result = []
while queue:
print(queue)
current, opens_used, closes_used = queue.pop(0)
if len(current) == 2 * pairs:
result.append(current)
else:
if opens_used < pairs:
queue.append((current + '(', opens_used + 1, closes_used))
if closes_used < opens_used:
queue.append((current + ')', opens_used, closes_used + 1))
return result
--fcc-editable-region--
print(gen_parentheses(1))
--fcc-editable-region--
```
@@ -0,0 +1,50 @@
---
id: 68da6138d70960c197ec5ef5
title: Step 13
challengeType: 20
dashedName: step-13
---
# --description--
Now you don't need to print the queue anymore. So remove `print(queue)` from your `while` loop.
# --hints--
You should not have `print(queue)` in your `while` loop.
```js
({ test: () => runPython(`
assert not _Node(_code).find_function("gen_parentheses").find_whiles()[0].find_bodies()[0].has_call("print(queue)")
`) })
```
# --seed--
## --seed-contents--
```py
def gen_parentheses(pairs):
if not isinstance(pairs, int):
return 'The number of pairs should be an integer'
if pairs < 1:
return 'The number of pairs should be at least 1'
queue = [('', 0, 0)]
result = []
--fcc-editable-region--
while queue:
print(queue)
current, opens_used, closes_used = queue.pop(0)
--fcc-editable-region--
if len(current) == 2 * pairs:
result.append(current)
else:
if opens_used < pairs:
queue.append((current + '(', opens_used + 1, closes_used))
if closes_used < opens_used:
queue.append((current + ')', opens_used, closes_used + 1))
return result
print(gen_parentheses(2))
```
@@ -0,0 +1,51 @@
---
id: 68e00c5349cbddfef6dbc053
title: Step 7
challengeType: 20
dashedName: step-7
---
# --description--
Inside your `while` loop, use `queue.pop(0)` to remove and get the first element from the queue. This implements the first-in-first-out (FIFO) behavior characteristic of BFS.
Unpack this tuple into three variables: `current`, `opens_used`, and `closes_used`.
# --hints--
You should use `queue.pop(0)` to get the first element from the queue.
```js
({ test: () => runPython(`
assert "queue.pop(0)" in str(_Node(_code).find_function("gen_parentheses").find_whiles()[0].find_bodies()[0])
`) })
```
You should unpack the result into three variables: `current`, `opens_used`, and `closes_used`.
```js
({ test: () => runPython(`
assert _Node(_code).find_function("gen_parentheses").find_whiles()[0].find_bodies()[0].has_stmt("current, opens_used, closes_used = queue.pop(0)")
`) })
```
# --seed--
## --seed-contents--
```py
def gen_parentheses(pairs):
if not isinstance(pairs, int):
return 'The number of pairs should be an integer'
if pairs < 1:
return 'The number of pairs should be at least 1'
queue = [('', 0, 0)]
result = []
--fcc-editable-region--
while queue:
print(queue)
--fcc-editable-region--
return result
```
@@ -0,0 +1,44 @@
---
id: 68e00f88cf82083df8e0497b
title: Step 8
challengeType: 20
dashedName: step-8
---
# --description--
Before you keep working on the BFS logic, call `gen_parentheses(1)` and print the result to the console.
# --hints--
You should print `gen_parentheses(1)` to the console.
```js
({ test: () => runPython(`
assert _Node(_code).has_call('print(gen_parentheses(1))')
`) })
```
# --seed--
## --seed-contents--
```py
def gen_parentheses(pairs):
if not isinstance(pairs, int):
return 'The number of pairs should be an integer'
if pairs < 1:
return 'The number of pairs should be at least 1'
queue = [('', 0, 0)]
result = []
while queue:
print(queue)
current, opens_used, closes_used = queue.pop(0)
return result
--fcc-editable-region--
--fcc-editable-region--
```
@@ -0,0 +1,77 @@
---
id: 68e03c0b7327c7012f12e333
title: Step 14
challengeType: 20
dashedName: step-14
---
# --description--
Finally, call `gen_parentheses` with `3` as its argument to generate all five valid combinations of three pairs of parentheses and print the result to the console. With that the workshop is complete.
# --hints--
You should print `gen_parentheses(3)`.
```js
({ test: () => runPython(`
assert _Node(_code).has_call("print(gen_parentheses(3))")
`) })
```
# --seed--
## --seed-contents--
```py
def gen_parentheses(pairs):
if not isinstance(pairs, int):
return 'The number of pairs should be an integer'
if pairs < 1:
return 'The number of pairs should be at least 1'
queue = [('', 0, 0)]
result = []
while queue:
current, opens_used, closes_used = queue.pop(0)
if len(current) == 2 * pairs:
result.append(current)
else:
if opens_used < pairs:
queue.append((current + '(', opens_used + 1, closes_used))
if closes_used < opens_used:
queue.append((current + ')', opens_used, closes_used + 1))
return result
--fcc-editable-region--
print(gen_parentheses(2))
--fcc-editable-region--
```
# --solutions--
```py
def gen_parentheses(pairs):
if not isinstance(pairs, int):
return 'The number of pairs should be an integer'
if pairs < 1:
return 'The number of pairs should be at least 1'
queue = [('', 0, 0)]
result = []
while queue:
current, opens_used, closes_used = queue.pop(0)
if len(current) == 2 * pairs:
result.append(current)
else:
if opens_used < pairs:
queue.append((current + '(', opens_used + 1, closes_used))
if closes_used < opens_used:
queue.append((current + ')', opens_used, closes_used + 1))
return result
print(gen_parentheses(2))
print(gen_parentheses(3))
```
@@ -0,0 +1,26 @@
{
"name": "Implement the Breadth-First Search Algorithm",
"isUpcomingChange": true,
"dashedName": "workshop-breadth-first-search",
"helpCategory": "Python",
"blockLayout": "challenge-grid",
"hasEditableBoundaries": true,
"challengeOrder": [
{ "id": "68da604b8b6012a33e2344fb", "title": "Step 1" },
{ "id": "68da6138d70960c197ec5eec", "title": "Step 2" },
{ "id": "68da6138d70960c197ec5eed", "title": "Step 3" },
{ "id": "68da6138d70960c197ec5eee", "title": "Step 4" },
{ "id": "68da6138d70960c197ec5eef", "title": "Step 5" },
{ "id": "68da6138d70960c197ec5ef0", "title": "Step 6" },
{ "id": "68e00c5349cbddfef6dbc053", "title": "Step 7" },
{ "id": "68e00f88cf82083df8e0497b", "title": "Step 8" },
{ "id": "68da6138d70960c197ec5ef1", "title": "Step 9" },
{ "id": "68da6138d70960c197ec5ef2", "title": "Step 10" },
{ "id": "68da6138d70960c197ec5ef3", "title": "Step 11" },
{ "id": "68da6138d70960c197ec5ef4", "title": "Step 12" },
{ "id": "68da6138d70960c197ec5ef5", "title": "Step 13" },
{ "id": "68e03c0b7327c7012f12e333", "title": "Step 14" }
],
"blockType": "workshop",
"usesMultifileEditor": true
}
@@ -773,6 +773,7 @@
"blocks": [
"lecture-understanding-graphs-and-trees",
"lab-adjacency-list-to-matrix-converter",
"workshop-breadth-first-search",
"lab-depth-first-search",
"review-graphs-and-trees",
"quiz-graphs-and-trees"