Hyphenate product codes
A warehouse label printer makes product codes easier to read by inserting a hyphen at every point where a letter is followed by a digit or a digit by a letter: AB12CD3 becomes AB-12-CD-3. Read product codes, one per line, until input ends, and print each code in its hyphenated form on its own line. Codes contain only uppercase letters and digits; a code with no switches is printed unchanged.
Input
One product code per line until end of input; each code has 1-50 characters, all uppercase letters or digits.
Output
One line per input code: the code with a hyphen inserted at every letter/digit boundary.
Example 1
Input
AB12CD3
Output
AB-12-CD-3
There are three boundaries: B|1, 2|C and D|3.
Example 2
Input
ZX90 4400Q Q
Output
ZX-90 4400-Q Q
The first code switches once, the second once, and the single-character code has no boundary at all.
Constraints
- 1 <= number of codes <= 100
- 1 <= length of each code <= 50
- Codes contain only A-Z and 0-9
Hints
Hint 1 of 3
Iterate with code.chars() and build the result in a String with push.
Hint 2 of 3
Remember whether the previous character was a digit (c.is_ascii_digit()); when the current one differs, push a '-' before pushing the character.
Hint 3 of 3
There is no previous character at the start, so keep that memory as an Option<bool> that is None until the first character has been handled.
Solution
Show a reference solution and explanation
use std::io::{self, BufRead};
fn hyphenate(code: &str) -> String {
let mut out = String::new();
let mut prev_is_digit: Option<bool> = None;
for c in code.chars() {
let is_digit = c.is_ascii_digit();
if let Some(prev) = prev_is_digit {
if prev != is_digit {
out.push('-');
}
}
out.push(c);
prev_is_digit = Some(is_digit);
}
out
}
fn main() {
let stdin = io::stdin();
for line in stdin.lock().lines() {
let code = line.unwrap();
let code = code.trim();
if code.is_empty() {
continue;
}
println!("{}", hyphenate(code));
}
}
Why it works
Deciding whether to insert a hyphen needs exactly one piece of memory: was the previous character a digit? Keeping that as an Option<bool> makes the first iteration explicit instead of relying on a fake initial value, and if let Some(prev) skips the comparison exactly once. Building a fresh String with push is the idiomatic way to transform text in Rust, because a String is UTF-8 and cannot be indexed and modified byte by byte. is_ascii_digit is the right test here since the codes are ASCII; is_numeric would also accept digits from other scripts.
Lesson for this exercise: Control Flow in Rust: if, loop, while and for