feat: add new examples from pocketflow-academy
This commit is contained in:
@@ -0,0 +1,64 @@
|
||||
# PocketFlow Nested BatchFlow Example
|
||||
|
||||
This example demonstrates Nested BatchFlow using a simple school grades calculator.
|
||||
|
||||
## What this Example Does
|
||||
|
||||
Calculates average grades for:
|
||||
1. Each student in a class
|
||||
2. Each class in the school
|
||||
|
||||
## Structure
|
||||
```
|
||||
school/
|
||||
├── class_a/
|
||||
│ ├── student1.txt (grades: 7.5, 8.0, 9.0)
|
||||
│ └── student2.txt (grades: 8.5, 7.0, 9.5)
|
||||
└── class_b/
|
||||
├── student3.txt (grades: 6.5, 8.5, 7.0)
|
||||
└── student4.txt (grades: 9.0, 9.5, 8.0)
|
||||
```
|
||||
|
||||
## How it Works
|
||||
|
||||
1. **Outer BatchFlow (SchoolBatchFlow)**
|
||||
- Processes each class folder
|
||||
- Returns parameters like: `{"class": "class_a"}`
|
||||
|
||||
2. **Inner BatchFlow (ClassBatchFlow)**
|
||||
- Processes each student file in a class
|
||||
- Returns parameters like: `{"student": "student1.txt"}`
|
||||
|
||||
3. **Base Flow**
|
||||
- Loads student grades
|
||||
- Calculates average
|
||||
- Saves result
|
||||
|
||||
## Running the Example
|
||||
|
||||
```bash
|
||||
pip install -r requirements.txt
|
||||
python main.py
|
||||
```
|
||||
|
||||
## Expected Output
|
||||
|
||||
```
|
||||
Processing class_a...
|
||||
- student1: Average = 8.2
|
||||
- student2: Average = 8.3
|
||||
Class A Average: 8.25
|
||||
|
||||
Processing class_b...
|
||||
- student3: Average = 7.3
|
||||
- student4: Average = 8.8
|
||||
Class B Average: 8.05
|
||||
|
||||
School Average: 8.15
|
||||
```
|
||||
|
||||
## Key Concepts
|
||||
|
||||
1. **Nested BatchFlow**: One BatchFlow inside another
|
||||
2. **Parameter Inheritance**: Inner flow gets parameters from outer flow
|
||||
3. **Hierarchical Processing**: Process data in a tree-like structure
|
||||
@@ -0,0 +1,73 @@
|
||||
import os
|
||||
from pocketflow import Flow, BatchFlow
|
||||
from nodes import LoadGrades, CalculateAverage
|
||||
|
||||
def create_base_flow():
|
||||
"""Create base flow for processing one student's grades."""
|
||||
# Create nodes
|
||||
load = LoadGrades()
|
||||
calc = CalculateAverage()
|
||||
|
||||
# Connect nodes
|
||||
load - "calculate" >> calc
|
||||
|
||||
# Create and return flow
|
||||
return Flow(start=load)
|
||||
|
||||
class ClassBatchFlow(BatchFlow):
|
||||
"""BatchFlow for processing all students in a class."""
|
||||
|
||||
def prep(self, shared):
|
||||
"""Generate parameters for each student in the class."""
|
||||
# Get class folder from parameters
|
||||
class_folder = self.params["class"]
|
||||
|
||||
# List all student files
|
||||
class_path = os.path.join("school", class_folder)
|
||||
students = [f for f in os.listdir(class_path) if f.endswith(".txt")]
|
||||
|
||||
# Return parameters for each student
|
||||
return [{"student": student} for student in students]
|
||||
|
||||
def post(self, shared, prep_res, exec_res):
|
||||
"""Calculate and print class average."""
|
||||
class_name = self.params["class"]
|
||||
class_results = shared["results"][class_name]
|
||||
class_average = sum(class_results.values()) / len(class_results)
|
||||
|
||||
print(f"Class {class_name.split('_')[1].upper()} Average: {class_average:.2f}\n")
|
||||
return "default"
|
||||
|
||||
class SchoolBatchFlow(BatchFlow):
|
||||
"""BatchFlow for processing all classes in the school."""
|
||||
|
||||
def prep(self, shared):
|
||||
"""Generate parameters for each class."""
|
||||
# List all class folders
|
||||
classes = [d for d in os.listdir("school") if os.path.isdir(os.path.join("school", d))]
|
||||
|
||||
# Return parameters for each class
|
||||
return [{"class": class_name} for class_name in classes]
|
||||
|
||||
def post(self, shared, prep_res, exec_res):
|
||||
"""Calculate and print school average."""
|
||||
all_grades = []
|
||||
for class_results in shared["results"].values():
|
||||
all_grades.extend(class_results.values())
|
||||
|
||||
school_average = sum(all_grades) / len(all_grades)
|
||||
print(f"School Average: {school_average:.2f}")
|
||||
return "default"
|
||||
|
||||
def create_flow():
|
||||
"""Create the complete nested batch processing flow."""
|
||||
# Create base flow for single student
|
||||
base_flow = create_base_flow()
|
||||
|
||||
# Wrap in ClassBatchFlow for processing all students in a class
|
||||
class_flow = ClassBatchFlow(start=base_flow)
|
||||
|
||||
# Wrap in SchoolBatchFlow for processing all classes
|
||||
school_flow = SchoolBatchFlow(start=class_flow)
|
||||
|
||||
return school_flow
|
||||
@@ -0,0 +1,42 @@
|
||||
import os
|
||||
from flow import create_flow
|
||||
|
||||
def create_sample_data():
|
||||
"""Create sample grade files."""
|
||||
# Create directory structure
|
||||
os.makedirs("school/class_a", exist_ok=True)
|
||||
os.makedirs("school/class_b", exist_ok=True)
|
||||
|
||||
# Sample grades
|
||||
data = {
|
||||
"class_a": {
|
||||
"student1.txt": [7.5, 8.0, 9.0],
|
||||
"student2.txt": [8.5, 7.0, 9.5]
|
||||
},
|
||||
"class_b": {
|
||||
"student3.txt": [6.5, 8.5, 7.0],
|
||||
"student4.txt": [9.0, 9.5, 8.0]
|
||||
}
|
||||
}
|
||||
|
||||
# Create files
|
||||
for class_name, students in data.items():
|
||||
for student, grades in students.items():
|
||||
file_path = os.path.join("school", class_name, student)
|
||||
with open(file_path, 'w') as f:
|
||||
for grade in grades:
|
||||
f.write(f"{grade}\n")
|
||||
|
||||
def main():
|
||||
"""Run the nested batch example."""
|
||||
# Create sample data
|
||||
create_sample_data()
|
||||
|
||||
print("Processing school grades...\n")
|
||||
|
||||
# Create and run flow
|
||||
flow = create_flow()
|
||||
flow.run({})
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
@@ -0,0 +1,52 @@
|
||||
import os
|
||||
from pocketflow import Node
|
||||
|
||||
class LoadGrades(Node):
|
||||
"""Node that loads grades from a student's file."""
|
||||
|
||||
def prep(self, shared):
|
||||
"""Get file path from parameters."""
|
||||
class_name = self.params["class"]
|
||||
student_file = self.params["student"]
|
||||
return os.path.join("school", class_name, student_file)
|
||||
|
||||
def exec(self, file_path):
|
||||
"""Load and parse grades from file."""
|
||||
with open(file_path, 'r') as f:
|
||||
# Each line is a grade
|
||||
grades = [float(line.strip()) for line in f]
|
||||
return grades
|
||||
|
||||
def post(self, shared, prep_res, grades):
|
||||
"""Store grades in shared store."""
|
||||
shared["grades"] = grades
|
||||
return "calculate"
|
||||
|
||||
class CalculateAverage(Node):
|
||||
"""Node that calculates average grade."""
|
||||
|
||||
def prep(self, shared):
|
||||
"""Get grades from shared store."""
|
||||
return shared["grades"]
|
||||
|
||||
def exec(self, grades):
|
||||
"""Calculate average."""
|
||||
return sum(grades) / len(grades)
|
||||
|
||||
def post(self, shared, prep_res, average):
|
||||
"""Store and print result."""
|
||||
# Store in results dictionary
|
||||
if "results" not in shared:
|
||||
shared["results"] = {}
|
||||
|
||||
class_name = self.params["class"]
|
||||
student = self.params["student"]
|
||||
|
||||
if class_name not in shared["results"]:
|
||||
shared["results"][class_name] = {}
|
||||
|
||||
shared["results"][class_name][student] = average
|
||||
|
||||
# Print individual result
|
||||
print(f"- {student}: Average = {average:.1f}")
|
||||
return "default"
|
||||
@@ -0,0 +1 @@
|
||||
pocketflow
|
||||
@@ -0,0 +1,3 @@
|
||||
7.5
|
||||
8.0
|
||||
9.0
|
||||
@@ -0,0 +1,3 @@
|
||||
8.5
|
||||
7.0
|
||||
9.5
|
||||
@@ -0,0 +1,3 @@
|
||||
6.5
|
||||
8.5
|
||||
7.0
|
||||
@@ -0,0 +1,3 @@
|
||||
9.0
|
||||
9.5
|
||||
8.0
|
||||
Reference in New Issue
Block a user