RosettaCodeData/Task/Abbreviations-simple/Rust/abbreviations-simple.rust

112 lines
4.8 KiB
Plaintext

use std::collections::HashMap;
// The plan here is to build a hashmap of all the commands keyed on the minimum number of
// letters than can be provided in the input to match. For each known command it will appear
// in a list of possible commands for a given string lengths. A command can therefore appear a
// number of times. For example, the command 'recover' has a minimum abbreviation length of 3.
// In the hashmap 'recover' will be stored behind keys for 3, 4, 5, 6 & 7 as any abbreviation of
// 'recover' from 3 until 7 letters inclusive can match. This way, once the length of the input
// string is known a subset of possible matches can be retrieved immediately and then checked.
//
fn main() {
let command_table_string =
"add 1 alter 3 backup 2 bottom 1 Cappend 2 change 1 Schange Cinsert 2 Clast 3
compress 4 copy 2 count 3 Coverlay 3 cursor 3 delete 3 Cdelete 2 down 1 duplicate
3 xEdit 1 expand 3 extract 3 find 1 Nfind 2 Nfindup 6 NfUP 3 Cfind 2 findUP 3 fUP 2
forward 2 get help 1 hexType 4 input_command 1 powerInput 3 join 1 split 2 spltJOIN load
locate 1 Clocate 2 lowerCase 3 upperCase 3 Lprefix 2 macro merge 2 modify 3 move 2
msg next 1 overlay 1 parse preserve 4 purge 3 put putD query 1 quit read recover 3
refresh renum 3 repeat 3 replace 1 Creplace 2 reset 3 restore 4 rgtLEFT right 2 left
2 save set shift 2 si sort sos stack 3 status 4 top transfer 3 type 1 up 1";
// Split up the command table string using the whitespace and set up an iterator
// to run through it. We need the iterator to be peekable so that we can look ahead at
// the next item.
let mut iter = command_table_string.split_whitespace().peekable();
let mut command_table = HashMap::new();
// Attempt to take two items at a time from the command table string. These two items will be
// the command string and the minimum length of the abbreviation. If there is no abbreviation length
// then there is no number provided. As the second item might not be a number, so we need to peek at
// it first. If it is a number we can use it as a key for the hashmap. If it is not a number then
// we use the length of the first item instead because no abbreviations are available for the
// word i.e. the whole word must be used. A while loop is used because we need to control iteration
// and look ahead.
//
while let Some(command_string) = iter.next() {
let command_string_length = command_string.len() as i32;
let min_letter_match = match iter.peek() {
Some(potential_number) => match potential_number.parse::<i32>() {
Ok(number) => {
iter.next();
number
}
Err(_) => command_string_length,
},
None => break,
};
// The word must be stored for every valid abbreviation length.
//
for i in min_letter_match..=command_string_length {
let cmd_list = command_table.entry(i).or_insert_with(Vec::new);
cmd_list.push(command_string.to_uppercase());
}
}
const ERROR_TEXT: &str = "*error*";
let test_input_text = "riG rePEAT copies put mo rest types fup. 6 poweRin";
let mut output_text = String::new();
let mut iter = test_input_text.split_whitespace().peekable();
// Run through each item in the input string, find the length of it
// and then use this to fetch a list of possible matches.
// A while loop is used because we need to look ahead in order to indentify
// the last item and avoid adding an unnecessary space.
//
while let Some(input_command) = iter.next() {
let input_command_length = input_command.len() as i32;
let command_list = match command_table.get(&input_command_length) {
Some(list) => list,
None => {
output_text.push_str(ERROR_TEXT);
continue;
}
};
let input_command_caps = input_command.to_uppercase();
let matched_commands: Vec<&String> = command_list
.iter()
.filter(|command| command.starts_with(&input_command_caps))
.collect();
// Should either be 0 or 1 command found
assert!(
matched_commands.len() < 2,
"Strange.. {:?}",
matched_commands
);
match matched_commands.first() {
Some(cmd) => output_text.push_str(cmd),
None => output_text.push_str(ERROR_TEXT),
}
if iter.peek().is_some() {
output_text.push(' ');
}
}
println!("Input was: {}", test_input_text);
println!("Output is: {}", output_text);
let correct_output = "RIGHT REPEAT *error* PUT MOVE RESTORE *error* *error* *error* POWERINPUT";
assert_eq!(output_text, correct_output)
}