Решение на Dungeons and Compilers от Йоан Цвятков

Обратно към всички решения

Към профила на Йоан Цвятков

Резултати

  • 19 точки от тестове
  • 0 бонус точки
  • 19 точки общо
  • 14 успешни тест(а)
  • 1 неуспешни тест(а)

Код

use std::{
collections::{HashMap, VecDeque, HashSet},
io::BufRead,
};
#[derive(Debug)]
pub enum Errors {
DuplicateRoom(String),
UnknownRoom(String),
IoError(std::io::Error),
LineParseError { line_number: usize },
DirectionParseError(String),
}
#[derive(Clone, Copy)]
pub enum Direction {
North,
South,
East,
West,
}
#[derive(Clone)]
pub struct Room {
pub name: String,
pub west_room_name: Option<String>,
pub east_room_name: Option<String>,
pub north_room_name: Option<String>,
pub south_room_name: Option<String>,
}
pub struct Dungeon {
pub rooms: HashMap<String, Room>,
}
impl Room {
fn new(room_name: &str) -> Self {
Room {
name: room_name.to_string(),
south_room_name: None,
north_room_name: None,
west_room_name: None,
east_room_name: None,
}
}
}
impl Dungeon {
pub fn new() -> Self {
Dungeon {
rooms: HashMap::new(),
}
}
pub fn add_room(&mut self, name: &str) -> Result<(), Errors> {
if self.rooms.contains_key(name) == true {
Err(Errors::DuplicateRoom("Name is already present".to_string()))
} else {
self.rooms.insert(name.to_string(), Room::new(name));
Ok(())
}
}
pub fn get_room(&self, room_name: &str) -> Result<&Room, Errors> {
match self.rooms.get(&room_name.to_string()) {
Some(room) => Ok(room),
None => Err(Errors::UnknownRoom(format!("{}", room_name))),
}
}
pub fn set_link(
&mut self,
room_name: &str,
direction: Direction,
other_room_name: &str,
) -> Result<(), Errors> {
let current = match self.rooms.get_mut(room_name) {
Some(room) => room,
None => return Err(Errors::UnknownRoom(format!("{}", room_name))),
};
match direction {
Direction::North => current.north_room_name = Some(other_room_name.to_string()),
Direction::West => current.west_room_name = Some(other_room_name.to_string()),
Direction::East => current.east_room_name = Some(other_room_name.to_string()),
Direction::South => current.south_room_name = Some(other_room_name.to_string()),
};
let current = match self.rooms.get_mut(other_room_name) {
Some(room) => room,
None => return Err(Errors::UnknownRoom(format!("{}", other_room_name))),
};
match direction {
Direction::North => current.south_room_name = Some(room_name.to_string()),
Direction::West => current.east_room_name = Some(room_name.to_string()),
Direction::East => current.west_room_name = Some(room_name.to_string()),
Direction::South => current.north_room_name = Some(room_name.to_string()),
};
Ok(())
}
pub fn get_next_room(
&self,
room_name: &str,
direction: Direction,
) -> Result<Option<&Room>, Errors> {
let room = self.get_room(room_name)?;
match direction {
Direction::North => match &room.north_room_name {
Some(name) => match self.get_room(name) {
Ok(room) => Ok(Some(room)),
Err(_err) => Ok(None),
},
None => Ok(None),
},
Direction::South => match &room.south_room_name {
Some(name) => match self.get_room(name) {
Ok(room) => Ok(Some(room)),
Err(_err) => Ok(None),
},
None => Ok(None),
},
Direction::East => match &room.east_room_name {
Some(name) => match self.get_room(name) {
Ok(room) => Ok(Some(room)),
Err(_err) => Ok(None),
},
None => Ok(None),
},
Direction::West => match &room.west_room_name {
Some(name) => match self.get_room(name) {
Ok(room) => Ok(Some(room)),
Err(_err) => Ok(None),
},
None => Ok(None),
},
}
}
pub fn from_reader<B: BufRead>(reader: B) -> Result<Self, Errors> {
let mut dungeon = Dungeon::new();
let mut line_number = 1;
let mut read_rooms = false;
let mut read_paths = false;
let mut empty_line = false;
for line in reader.lines() {
match line {
Ok(x) => {
if line_number == 1 && x != "## Rooms" {
return Err(Errors::LineParseError { line_number });
}
if x == "## Rooms" {
read_rooms = true;
} else if x == "## Links" {
read_paths = true;
} else if x.starts_with("-") {
if !read_rooms && !read_paths {
return Err(Errors::LineParseError { line_number });
}
if read_rooms {
if let Some(room_name) = match_prefix("- ", x.as_str()) {
dungeon.add_room(room_name)?;
} else {
return Err(Errors::LineParseError { line_number });
}
}
if read_paths {
if let Some(str) = match_prefix("- ", x.as_str()) {
let split_res = str.split(" -> ").collect::<Vec<&str>>();
if split_res.len() != 3 {
return Err(Errors::LineParseError { line_number });
}
let first_room = split_res[0];
let direction = split_res[1];
let second_room = split_res[2];
match direction {
"West" => dungeon.set_link(
first_room,
Direction::West,
second_room,
)?,
"East" => dungeon.set_link(
first_room,
Direction::East,
second_room,
)?,
"Noth" => dungeon.set_link(
first_room,
Direction::North,
second_room,
)?,
"South" => dungeon.set_link(
first_room,
Direction::South,
second_room,
)?,
_ => {
return Err(Errors::DirectionParseError(
"Invalid direction".to_string(),
))
}
}
} else {
return Err(Errors::LineParseError {
line_number: line_number,
});
}
}
} else if x.is_empty() {
if empty_line {
return Err(Errors::LineParseError { line_number });
}
read_rooms = false;
empty_line = true;
} else {
return Err(Errors::LineParseError {
line_number: line_number,
});
}
}
Err(e) => {
return Err(Errors::IoError(e));
}
}
line_number = line_number + 1
}
if line_number == 1 {
return Err(Errors::LineParseError { line_number: 0 });
}
Ok(dungeon)
}
/// Търси път от `start_room_name` до `end_room_name` и го връща във вектор, пакетиран във
/// `Ok(Some(` ако намери.
///
/// Ако няма път между тези две стаи, връща `Ok(None)`.
///
/// Ако четенето на стаи в един момент върне грешка, очакваме да върнете грешката нагоре.
///
pub fn find_path(
&self,
start_room_name: &str,
end_room_name: &str,
) -> Result<Option<Vec<&Room>>, Errors> {
let mut queue: VecDeque<&Room> = VecDeque::new();
let mut parents: HashMap<&String, &Room> = HashMap::new();
let mut visited: HashSet<&String> = HashSet::new();
let mut result: Vec<&Room> = Vec::new();
let first_room = self.get_room(start_room_name)?;
let end_room = self.get_room(end_room_name)?;
queue.push_back(first_room);
while !queue.is_empty() {
let curr_room = queue.pop_front().unwrap();
if visited.contains(&curr_room.name) {
continue;
}
visited.insert(&curr_room.name);
if curr_room.name == end_room_name {
break;
}
if let Some(east_room_name) = &curr_room.east_room_name {
let room = self.get_room(&east_room_name)?;
if !visited.contains(&east_room_name) {
parents.insert(&east_room_name, curr_room);
queue.push_back(room);
}
}
if let Some(west_room_name) = &curr_room.west_room_name {
let room = self.get_room(&west_room_name)?;
if !visited.contains(&west_room_name) {
parents.insert(&west_room_name, curr_room);
queue.push_back(room);
}
}
if let Some(north_room_name) = &curr_room.north_room_name {
let room = self.get_room(&north_room_name)?;
if !visited.contains(&north_room_name) {
parents.insert(&north_room_name, curr_room);
queue.push_back(room);
}
}
if let Some(south_room_name) = &curr_room.south_room_name {
let room = self.get_room(&south_room_name)?;
if !visited.contains(&south_room_name) {
parents.insert(&south_room_name, curr_room);
queue.push_back(room);
}
}
};
let mut curr = end_room;
if visited.contains(&curr.name) {
while parents.contains_key(&curr.name) {
result.push(curr);
curr = parents.get(&curr.name).unwrap();
};
result.push(first_room);
result.reverse();
Ok(Some(result))
} else {
Ok(None)
}
}
}
fn match_prefix<'a, 'b>(prefix: &'a str, input: &'b str) -> Option<&'b str> {
if !input.starts_with(prefix) {
return None;
}
let prefix_size = prefix.chars().count();
let index = prefix.chars().count();
if input.chars().count() == prefix_size {
return None;
}
Some(&input[index..])
}

Лог от изпълнението

Compiling solution v0.1.0 (/tmp/d20220116-3533338-e1jkp0/solution)
    Finished test [unoptimized + debuginfo] target(s) in 3.70s
     Running tests/solution_test.rs (target/debug/deps/solution_test-2e292b23ac75572c)

running 15 tests
test solution_test::test_adding_rooms_1 ... ok
test solution_test::test_adding_rooms_2 ... ok
test solution_test::test_cyrillic_room_names ... ok
test solution_test::test_finding_a_direct_path ... ok
test solution_test::test_finding_a_reflexive_path ... ok
test solution_test::test_finding_an_indirect_path ... ok
test solution_test::test_finding_no_path ... ok
test solution_test::test_invalid_parsing ... FAILED
test solution_test::test_io_error ... ok
test solution_test::test_overwriting_a_room_link ... ok
test solution_test::test_parsing_cyrillic_rooms ... ok
test solution_test::test_parsing_no_rooms_or_links ... ok
test solution_test::test_parsing_rooms ... ok
test solution_test::test_room_errors ... ok
test solution_test::test_room_links ... ok

failures:

---- solution_test::test_invalid_parsing stdout ----
thread 'main' panicked at 'assertion failed: matches!(Dungeon :: from_reader(TEST_INPUT_6.trim().as_bytes()),\n         Err(Errors :: UnknownRoom(_)))', tests/solution_test.rs:279:5
note: run with `RUST_BACKTRACE=1` environment variable to display a backtrace


failures:
    solution_test::test_invalid_parsing

test result: FAILED. 14 passed; 1 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.00s

error: test failed, to rerun pass '--test solution_test'

История (1 версия и 0 коментара)

Йоан качи първо решение на 11.01.2022 02:34 (преди над 3 години)