Skip to content

Commit 3fbbcd8

Browse files
tnraroclaude
andauthored
Add Rust implementation for hhss program (#47)
- Complete Rust implementation with proper error handling - Includes unit tests for core functionality - Follows project specifications and patterns 🤖 Generated with [Claude Code](https://claude.ai/code) Co-authored-by: Claude <noreply@anthropic.com>
1 parent abe971d commit 3fbbcd8

5 files changed

Lines changed: 352 additions & 0 deletions

File tree

hhss/rust/.gitignore

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
# Rust build artifacts
2+
target/
3+
4+
# Built binary
5+
hhss
6+
7+
# Backup files created by rustfmt
8+
**/*.rs.bk

hhss/rust/Cargo.lock

Lines changed: 133 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

hhss/rust/Cargo.toml

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
[package]
2+
name = "hhss"
3+
version = "0.1.0"
4+
edition = "2021"
5+
6+
[[bin]]
7+
name = "hhss"
8+
path = "hhss.rs"
9+
10+
[dependencies]
11+
rand = "0.8"

hhss/rust/Makefile

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
TARGET = hhss
2+
3+
all: $(TARGET)
4+
5+
$(TARGET): hhss.rs Cargo.toml
6+
cargo build --release
7+
cp target/release/$(TARGET) ./$(TARGET)
8+
9+
clean:
10+
cargo clean
11+
rm -f $(TARGET)
12+
13+
.PHONY: all clean

hhss/rust/hhss.rs

Lines changed: 187 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,187 @@
1+
use std::env;
2+
use std::fs;
3+
use std::io;
4+
use std::process;
5+
use rand::seq::SliceRandom;
6+
use rand::thread_rng;
7+
8+
const MIN_SENTENCE_NUM: usize = 5;
9+
const MIN_USER_NUM: usize = 1;
10+
const SENTENCE_FILE: &str = "hsr.dat";
11+
const USER_FILE: &str = "usr.dat";
12+
const USER_TEMPLATE: &str = "${user}";
13+
14+
fn main() {
15+
let args: Vec<String> = env::args().collect();
16+
17+
if args.len() != 2 {
18+
eprintln!("hhss: argc != 2.");
19+
process::exit(1);
20+
}
21+
22+
let num_sentences = match parse_num_sentences(&args[1]) {
23+
Ok(n) => n,
24+
Err(e) => {
25+
eprintln!("hhss: {}", e);
26+
process::exit(1);
27+
}
28+
};
29+
30+
let sentences = match read_data_file(SENTENCE_FILE) {
31+
Ok(lines) => lines,
32+
Err(e) => {
33+
eprintln!("hhss: failed to open {} as mode \"r\": {}", SENTENCE_FILE, e);
34+
process::exit(1);
35+
}
36+
};
37+
38+
let users = match read_data_file(USER_FILE) {
39+
Ok(lines) => lines,
40+
Err(e) => {
41+
eprintln!("hhss: failed to open {} as mode \"r\": {}", USER_FILE, e);
42+
process::exit(1);
43+
}
44+
};
45+
46+
if sentences.len() < MIN_SENTENCE_NUM {
47+
eprintln!("hhss: it is required for {} to have valid lines more than or equal to {}.",
48+
SENTENCE_FILE, MIN_SENTENCE_NUM);
49+
process::exit(1);
50+
}
51+
52+
if users.len() < MIN_USER_NUM {
53+
eprintln!("hhss: it is required for {} to have valid lines more than or equal to {}.",
54+
USER_FILE, MIN_USER_NUM);
55+
process::exit(1);
56+
}
57+
58+
if num_sentences > sentences.len() {
59+
eprintln!("hhss: argv[1] = {} needs to be <= {}, the number of valid lines in {}.",
60+
num_sentences, sentences.len(), SENTENCE_FILE);
61+
process::exit(1);
62+
}
63+
64+
let selected_sentences = select_random_sentences(&sentences, num_sentences);
65+
66+
for sentence in selected_sentences {
67+
let processed_sentence = replace_templates(&sentence, &users);
68+
println!("{}", processed_sentence);
69+
}
70+
}
71+
72+
fn parse_num_sentences(arg: &str) -> Result<usize, String> {
73+
match arg.parse::<usize>() {
74+
Ok(n) => {
75+
if n < MIN_SENTENCE_NUM {
76+
Err(format!("argv[1] = {} needs to be >= {}.", n, MIN_SENTENCE_NUM))
77+
} else {
78+
Ok(n)
79+
}
80+
}
81+
Err(_) => Err("no conversion can be performed.".to_string())
82+
}
83+
}
84+
85+
fn read_data_file(filename: &str) -> Result<Vec<String>, io::Error> {
86+
let contents = fs::read_to_string(filename)?;
87+
let mut valid_lines = Vec::new();
88+
89+
for line in contents.lines() {
90+
let trimmed = line.trim();
91+
// Skip empty lines and comments
92+
if !trimmed.is_empty() && !trimmed.starts_with('#') {
93+
valid_lines.push(trimmed.to_string());
94+
}
95+
}
96+
97+
Ok(valid_lines)
98+
}
99+
100+
fn select_random_sentences(sentences: &[String], count: usize) -> Vec<String> {
101+
let mut indices: Vec<usize> = (0..sentences.len()).collect();
102+
let mut rng = thread_rng();
103+
104+
// Fisher-Yates shuffle
105+
indices.shuffle(&mut rng);
106+
107+
// Take first 'count' indices and get corresponding sentences
108+
indices.into_iter()
109+
.take(count)
110+
.map(|i| sentences[i].clone())
111+
.collect()
112+
}
113+
114+
fn replace_templates(sentence: &str, users: &[String]) -> String {
115+
let mut result = String::new();
116+
let mut last_user: Option<String> = None;
117+
let mut rng = thread_rng();
118+
119+
let parts: Vec<&str> = sentence.split(USER_TEMPLATE).collect();
120+
121+
for (i, part) in parts.iter().enumerate() {
122+
result.push_str(part);
123+
124+
// If this is not the last part, we need to add a user name
125+
if i < parts.len() - 1 {
126+
let mut available_users = users.to_vec();
127+
128+
// If we have more than one user and we used one before, remove it to avoid repetition
129+
if users.len() > 1 {
130+
if let Some(last) = &last_user {
131+
available_users.retain(|u| u != last);
132+
}
133+
}
134+
135+
let selected_user = available_users.choose(&mut rng).unwrap();
136+
result.push_str(selected_user);
137+
last_user = Some(selected_user.clone());
138+
}
139+
}
140+
141+
result
142+
}
143+
144+
#[cfg(test)]
145+
mod tests {
146+
use super::*;
147+
148+
#[test]
149+
fn test_parse_num_sentences() {
150+
assert_eq!(parse_num_sentences("5").unwrap(), 5);
151+
assert_eq!(parse_num_sentences("10").unwrap(), 10);
152+
assert!(parse_num_sentences("4").is_err());
153+
assert!(parse_num_sentences("abc").is_err());
154+
}
155+
156+
#[test]
157+
fn test_replace_templates() {
158+
let users = vec!["Alice".to_string(), "Bob".to_string()];
159+
let sentence = "Hello ${user}, how are you ${user}?";
160+
let result = replace_templates(sentence, &users);
161+
162+
// Should contain both users and they should be different
163+
assert!(result.contains("Alice") || result.contains("Bob"));
164+
assert!(result.contains("Hello"));
165+
assert!(result.contains("how are you"));
166+
assert!(!result.contains("${user}"));
167+
}
168+
169+
#[test]
170+
fn test_select_random_sentences() {
171+
let sentences = vec![
172+
"First".to_string(),
173+
"Second".to_string(),
174+
"Third".to_string(),
175+
"Fourth".to_string(),
176+
"Fifth".to_string(),
177+
];
178+
179+
let selected = select_random_sentences(&sentences, 3);
180+
assert_eq!(selected.len(), 3);
181+
182+
// All selected sentences should be from the original list
183+
for sentence in &selected {
184+
assert!(sentences.contains(sentence));
185+
}
186+
}
187+
}

0 commit comments

Comments
 (0)