async1: Read input from files

This commit is contained in:
Remo Senekowitsch 2026-08-04 23:06:38 +02:00
parent 1dd31a8fdd
commit 9e76c96ef8
No known key found for this signature in database
10 changed files with 107 additions and 66 deletions

View File

@ -199,7 +199,7 @@ edition = "2024"
publish = false publish = false
[dependencies] [dependencies]
tokio = { version = "1", features = ["rt"] } tokio = { version = "1", features = ["fs", "macros", "rt", "rt-multi-thread"] }
[profile.release] [profile.release]
panic = "abort" panic = "abort"

View File

@ -4,39 +4,38 @@
// together, they can finish the job much faster. // together, they can finish the job much faster.
// //
// Let's simulate this using asynchronous programming. Each person is // Let's simulate this using asynchronous programming. Each person is
// represented as an asynchronous task, which can be executed concurrently (i.e. // represented as an asynchronous task, which can be executed concurrently.
// they can be doing the calculations at the same time).
fn main() {
// Async tasks need to be executed by a "runtime", which is not provided by // Async tasks need to be executed by a "runtime", which is not provided by
// Rust's standard library. Here, we use the mainstream runtime `tokio`. // Rust's standard library. Here, we use the mainstream runtime `tokio`.
let rt = tokio::runtime::Builder::new_current_thread() // The macro `tokio::main` wraps the entire main function in a runtime.
.build() #[tokio::main]
.unwrap(); async fn main() {
// TODO: Fix the compiler errors by making the spawned function async.
let mean_score_a = tokio::spawn(calculate_mean_score("input_files/scores_class_a.txt"));
let mean_score_b = tokio::spawn(calculate_mean_score("input_files/scores_class_b.txt"));
let mean_score_c = tokio::spawn(calculate_mean_score("input_files/scores_class_c.txt"));
let scores_class_a = &[83, 77, 92]; // TODO: Await the spawned tasks to check their results.
let scores_class_b = &[84, 88, 96]; assert_eq!(mean_score_a, 84); // alice
let scores_class_c = &[71, 83, 76]; assert_eq!(mean_score_b, 89); // bob
assert_eq!(mean_score_c, 76); // catherine
}
// TODO: Fix the compiler errors by making the spawned function async. // TODO: Fix the compiler errors by making the spawned function async.
let alice = rt.spawn(calculate_mean_score(scores_class_a)); fn calculate_mean_score(scores_file: &str) -> usize {
let bob = rt.spawn(calculate_mean_score(scores_class_b)); // Read the file asynchronously
let catherine = rt.spawn(calculate_mean_score(scores_class_c)); let file = tokio::fs::read_to_string(scores_file).await.unwrap();
// Block the runtime on a task that awaits all three calculations. // Initialize the sum and the number of scores
let [mean_score_a, mean_score_b, mean_score_c]: [usize; _] = rt.block_on(async { let mut sum = 0;
[ let mut n = 0;
// TODO: "await" all three tasks to fix the compiler error. for line in file.lines() {
alice, bob, catherine, // Parse every line as a score
] let score = line.parse::<usize>().unwrap();
}); sum += score;
n += 1;
assert_eq!(mean_score_a, 84);
assert_eq!(mean_score_b, 89);
assert_eq!(mean_score_c, 76);
} }
fn calculate_mean_score(score_list: &[usize]) -> usize { sum / n
let score_sum: usize = score_list.iter().sum();
score_sum / score_list.len()
} }

View File

@ -0,0 +1,3 @@
83
77
92

View File

@ -0,0 +1,3 @@
84
88
96

View File

@ -0,0 +1,3 @@
71
83
76

View File

@ -1225,3 +1225,12 @@ the functions "tim", "carl" and "nick".
An async task can wait for another one to complete by "awaiting" it. Add An async task can wait for another one to complete by "awaiting" it. Add
".await" after the three "task_name" variables in the "block_on" call.""" ".await" after the three "task_name" variables in the "block_on" call."""
[[input_files]]
name = "scores_class_a.txt"
[[input_files]]
name = "scores_class_b.txt"
[[input_files]]
name = "scores_class_c.txt"

View File

@ -8,18 +8,23 @@ struct ExerciseInfo<'a> {
dir: &'a str, dir: &'a str,
} }
#[derive(Deserialize)]
struct InputFileInfo<'a> {
name: &'a str,
}
#[derive(Deserialize)] #[derive(Deserialize)]
struct InfoFile<'a> { struct InfoFile<'a> {
#[serde(borrow)] #[serde(borrow)]
exercises: Vec<ExerciseInfo<'a>>, exercises: Vec<ExerciseInfo<'a>>,
input_files: Vec<InputFileInfo<'a>>,
} }
#[proc_macro] #[proc_macro]
pub fn include_files(_: TokenStream) -> TokenStream { pub fn include_files(_: TokenStream) -> TokenStream {
let info_file = include_str!("../info.toml"); let info_file = include_str!("../info.toml");
let exercises = toml::de::from_str::<InfoFile>(info_file) let info = toml::de::from_str::<InfoFile>(info_file).expect("Failed to parse `info.toml`");
.expect("Failed to parse `info.toml`") let exercises = info.exercises;
.exercises;
let exercise_files = exercises let exercise_files = exercises
.iter() .iter()
@ -46,11 +51,18 @@ pub fn include_files(_: TokenStream) -> TokenStream {
.iter() .iter()
.map(|dir| format!("../exercises/{dir}/README.md")); .map(|dir| format!("../exercises/{dir}/README.md"));
let input_file_names = info.input_files.iter().map(|f| f.name);
let input_file_paths = info
.input_files
.iter()
.map(|f| format!("../input_files/{}", f.name));
quote! { quote! {
EmbeddedFiles { EmbeddedFiles {
info_file: #info_file, info_file: #info_file,
exercise_files: &[#(ExerciseFiles { exercise: include_bytes!(#exercise_files), solution: include_bytes!(#solution_files), dir_ind: #dir_inds }),*], exercise_files: &[#(ExerciseFiles { exercise: include_bytes!(#exercise_files), solution: include_bytes!(#solution_files), dir_ind: #dir_inds }),*],
exercise_dirs: &[#(ExerciseDir { name: #dirs, readme: include_bytes!(#readmes) }),*] exercise_dirs: &[#(ExerciseDir { name: #dirs, readme: include_bytes!(#readmes) }),*],
input_files: &[#(InputFile { name: #input_file_names, content: include_str!(#input_file_paths) }),*],
} }
} }
.into() .into()

View File

@ -4,39 +4,35 @@
// together, they can finish the job much faster. // together, they can finish the job much faster.
// //
// Let's simulate this using asynchronous programming. Each person is // Let's simulate this using asynchronous programming. Each person is
// represented as an asynchronous task, which can be executed concurrently (i.e. // represented as an asynchronous task, which can be executed concurrently.
// they can be doing the calculations at the same time).
fn main() {
// Async tasks need to be executed by a "runtime", which is not provided by // Async tasks need to be executed by a "runtime", which is not provided by
// Rust's standard library. Here, we use the mainstream runtime `tokio`. // Rust's standard library. Here, we use the mainstream runtime `tokio`.
let rt = tokio::runtime::Builder::new_current_thread() // The macro `tokio::main` wraps the entire main function in a runtime.
.build() #[tokio::main]
.unwrap(); async fn main() {
let mean_score_a = tokio::spawn(calculate_mean_score("input_files/scores_class_a.txt"));
let mean_score_b = tokio::spawn(calculate_mean_score("input_files/scores_class_b.txt"));
let mean_score_c = tokio::spawn(calculate_mean_score("input_files/scores_class_c.txt"));
let scores_class_a = &[83, 77, 92]; assert_eq!(mean_score_a.await.unwrap(), 84); // alice
let scores_class_b = &[84, 88, 96]; assert_eq!(mean_score_b.await.unwrap(), 89); // bob
let scores_class_c = &[71, 83, 76]; assert_eq!(mean_score_c.await.unwrap(), 76); // catherine
let alice = rt.spawn(calculate_mean_score(scores_class_a));
let bob = rt.spawn(calculate_mean_score(scores_class_b));
let catherine = rt.spawn(calculate_mean_score(scores_class_c));
// Block the runtime on a task that awaits all three calculations.
let [mean_score_a, mean_score_b, mean_score_c]: [usize; _] = rt.block_on(async {
[
alice.await.unwrap(),
bob.await.unwrap(),
catherine.await.unwrap(),
]
});
assert_eq!(mean_score_a, 84);
assert_eq!(mean_score_b, 89);
assert_eq!(mean_score_c, 76);
} }
async fn calculate_mean_score(score_list: &[usize]) -> usize { async fn calculate_mean_score(scores_file: &str) -> usize {
let score_sum: usize = score_list.iter().sum(); // Read the file asynchronously
score_sum / score_list.len() let file = tokio::fs::read_to_string(scores_file).await.unwrap();
// Initialize the sum and the number of scores
let mut sum = 0;
let mut n = 0;
for line in file.lines() {
// Parse every line as a score
let score = line.parse::<usize>().unwrap();
sum += score;
n += 1;
}
sum / n
} }

View File

@ -19,6 +19,12 @@ struct ExerciseFiles {
dir_ind: usize, dir_ind: usize,
} }
// Input files that may be read by exercises.
pub struct InputFile {
pub name: &'static str,
pub content: &'static str,
}
fn create_dir_if_not_exists(path: &str) -> Result<()> { fn create_dir_if_not_exists(path: &str) -> Result<()> {
if let Err(e) = create_dir(path) if let Err(e) = create_dir(path)
&& e.kind() != io::ErrorKind::AlreadyExists && e.kind() != io::ErrorKind::AlreadyExists
@ -58,6 +64,7 @@ pub struct EmbeddedFiles {
pub info_file: &'static str, pub info_file: &'static str,
exercise_files: &'static [ExerciseFiles], exercise_files: &'static [ExerciseFiles],
pub exercise_dirs: &'static [ExerciseDir], pub exercise_dirs: &'static [ExerciseDir],
pub input_files: &'static [InputFile],
} }
impl EmbeddedFiles { impl EmbeddedFiles {

View File

@ -144,6 +144,15 @@ pub fn init() -> Result<()> {
.with_context(|| format!("Failed to create the file {solution_path}"))?; .with_context(|| format!("Failed to create the file {solution_path}"))?;
} }
// init input files
create_dir("input_files").context("Failed to create the directory `input_files`")?;
for input_file in EMBEDDED_FILES.input_files {
fs::write(
format!("input_files/{}", input_file.name),
input_file.content,
)?;
}
let current_cargo_toml = include_str!("../dev-Cargo.toml"); let current_cargo_toml = include_str!("../dev-Cargo.toml");
// Skip the first line (comment). // Skip the first line (comment).
let newline_ind = current_cargo_toml let newline_ind = current_cargo_toml