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
// Rust's standard library. Here, we use the mainstream runtime `tokio`.
let rt = tokio::runtime::Builder::new_current_thread()
.build()
.unwrap();
let scores_class_a = &[83, 77, 92];
let scores_class_b = &[84, 88, 96];
let scores_class_c = &[71, 83, 76];
// 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`.
// The macro `tokio::main` wraps the entire main function in a runtime.
#[tokio::main]
async fn main() {
// 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)); let mean_score_a = tokio::spawn(calculate_mean_score("input_files/scores_class_a.txt"));
let bob = rt.spawn(calculate_mean_score(scores_class_b)); let mean_score_b = tokio::spawn(calculate_mean_score("input_files/scores_class_b.txt"));
let catherine = rt.spawn(calculate_mean_score(scores_class_c)); let mean_score_c = tokio::spawn(calculate_mean_score("input_files/scores_class_c.txt"));
// Block the runtime on a task that awaits all three calculations. // TODO: Await the spawned tasks to check their results.
let [mean_score_a, mean_score_b, mean_score_c]: [usize; _] = rt.block_on(async { assert_eq!(mean_score_a, 84); // alice
[ assert_eq!(mean_score_b, 89); // bob
// TODO: "await" all three tasks to fix the compiler error. assert_eq!(mean_score_c, 76); // catherine
alice, bob, catherine,
]
});
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 { // TODO: Fix the compiler errors by making the spawned function async.
let score_sum: usize = score_list.iter().sum(); fn calculate_mean_score(scores_file: &str) -> usize {
score_sum / score_list.len() // Read the file asynchronously
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

@ -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`. // The macro `tokio::main` wraps the entire main function in a runtime.
let rt = tokio::runtime::Builder::new_current_thread() #[tokio::main]
.build() async fn main() {
.unwrap(); 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