Решение на Dungeons and Compilers от Димитър Михайлов
Към профила на Димитър Михайлов
Резултати
- 13 точки от тестове
- 0 бонус точки
- 13 точки общо
- 10 успешни тест(а)
- 5 неуспешни тест(а)
Код
use std::{io::BufRead, collections::HashMap, collections::VecDeque};
use std::str::FromStr;
/// Различните грешки, които ще очакваме да върнете като резултат от някои невалидни операции.
/// Повече детайли по-долу.
///
#[derive(Debug)]
pub enum Errors {
DuplicateRoom(String),
UnknownRoom(String),
IoError(std::io::Error),
LineParseError { line_number: usize },
DirectionParseError(String),
}
/// Четирите посоки, в които може една стая да има съседи. Може да добавите още trait
/// имплементации, за да си улесните живота.
///
#[derive(Clone, Copy)]
pub enum Direction {
North,
South,
East,
West,
}
trait Opposite {
fn opposite(&self) -> Direction;
}
impl FromStr for Direction {
type Err = ();
fn from_str(input: &str) -> Result<Direction, Self::Err> {
match input {
"East" => Ok(Direction::East),
"West" => Ok(Direction::West),
"North" => Ok(Direction::North),
"South" => Ok(Direction::South),
_ => Err(()),
}
}
}
impl Opposite for Direction {
fn opposite(&self) -> Direction {
match self {
Direction::East => Direction::West,
Direction::West => Direction::East,
Direction::South => Direction::North,
Direction::North => Direction::South,
}
}
}
/// Една стая в подземията. Дефинира се само с име, макар че в по-интересна имплементация може да
/// държи item-и, противници...
///
pub struct Room {
pub name: String,
pub east: String,
pub west: String,
pub north: String,
pub south: String,
// Каквито други полета ви трябват
}
impl Room {
fn assign_link(&mut self, direction: Direction, room_name: String) {
match direction {
Direction::East => self.east = room_name,
Direction::West => self.west = room_name,
Direction::North => self.north = room_name,
Direction::South => self.south = room_name
}
}
}
impl PartialEq for Room {
fn eq(&self, other: &Self) -> bool {
return self.name.eq(&other.name);
}
}
/// Контейнер за стаите и не само. Ще работим предимно със тази структура.
///
pub struct Dungeon {
pub rooms: HashMap<String, Room>
// Каквито полета ви трябват
}
impl Dungeon {
/// Конструиране на празен Dungeon, в който няма никакви стаи.
///
pub fn new() -> Self {
let result = Dungeon {
rooms: HashMap::new()
};
return result;
}
/// Добавяне на стая към Dungeon с име `name`. Връща `Ok(())` при успех. Ако вече има стая с
/// такова име, очакваме да върнете `Errors::DuplicateRoom` с името.
///
pub fn add_room(&mut self, name: &str) -> Result<(), Errors> {
if self.rooms.contains_key(name) {
return Err(Errors::DuplicateRoom(name.to_string()));
}
self.rooms.insert(name.to_string(), Room {
name: name.to_string(),
east: "".to_string(),
west: "".to_string(),
north: "".to_string(),
south: "".to_string(),
});
return Ok(())
}
/// Прочитане на дадена стая -- когато извикаме `get_room`, очакваме reference към `Room`
/// структурата с това име.
///
/// Ако няма такава стая, очакваме `Errors::UnknownRoom` с подаденото име.
///
pub fn get_room(&self, room_name: &str) -> Result<&Room, Errors> {
match self.rooms.get(room_name) {
Some(room) => Ok(room),
None => Err(Errors::UnknownRoom(room_name.to_string()))
}
}
/// Добавяне на съсед на дадена стая. След извикването на функцията, очакваме стаята с име
/// `room_name` да има връзка в посока `direction` със стаята с име `other_room_name`.
///
/// Също така очакваме `other_room_name` да има връзка с `room_name` в *обратната* посока.
///
/// Успешен резултат е `Ok(())`. В случай, че която от двете стаи не същестува, очакваме грешка
/// `Errors::UnknownRoom` със съответното име на липсваща стая. Ако и двете липсват, спокойно
/// върнете тази, която проверявате първо.
///
pub fn set_link(
&mut self,
room_name: &str,
direction: Direction,
other_room_name: &str,
) -> Result<(), Errors> {
if self.get_room(room_name).is_err() {
return Err(Errors::UnknownRoom(room_name.to_string()));
}
if self.get_room(other_room_name).is_err() {
return Err(Errors::UnknownRoom(other_room_name.to_string()));
}
self.rooms
.get_mut(room_name)
.unwrap()
.assign_link(direction, other_room_name.to_string());
self.rooms
.get_mut(other_room_name)
.unwrap()
.assign_link(Direction::opposite(&direction), room_name.to_string());
return Ok(())
}
/// Четене на съседа на стаята с име `room_name` в посока `direction`. Тук има няколко
/// варианта на изход:
///
/// - Ако подадената стая не съществува, очакваме грешка `Errors::UnknownRoom`
/// - Ако подадената стая няма съсед в тази посока, Ok(None) е смисления резултат
/// - Иначе, чакаме reference към `Room` структурата на въпросния съсед, опакована в `Ok(Some(`.
///
pub fn get_next_room(&self, room_name: &str, direction: Direction) -> Result<Option<&Room>, Errors> {
if self.get_room(room_name).is_err() {
return Err(Errors::UnknownRoom(room_name.to_string()));
}
let current_room = self.rooms.get(room_name).unwrap();
let room_to_get: &String;
match direction {
Direction::East => room_to_get = ¤t_room.east,
Direction::West => room_to_get = ¤t_room.west,
Direction::North => room_to_get = ¤t_room.north,
Direction::South => room_to_get = ¤t_room.south,
}
if room_to_get.len() == 0 {
return Ok(None)
}
match self.get_room(room_to_get) {
Ok(room) => Ok(Some(room)),
Err(err) => Err(err)
}
}
/// Търси път от `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> {
if self.get_room(start_room_name).is_err() {
return Err(Errors::UnknownRoom(start_room_name.to_string()));
}
if self.get_room(end_room_name).is_err() {
return Err(Errors::UnknownRoom(end_room_name.to_string()));
}
let mut queue: VecDeque<&Room> = VecDeque::new();
let mut visited: Vec<&Room> = Vec::new();
let mut room_parent: HashMap<&str, &str> = HashMap::new();
queue.push_front(self.get_room(start_room_name).unwrap());
visited.push(self.get_room(start_room_name).unwrap());
while !queue.is_empty() {
let room = queue.pop_front().unwrap();
if room.name == end_room_name {
break;
}
if room.east.len() > 0 {
let east_room = self.get_room(&room.east).unwrap();
if !visited.contains(&east_room) {
room_parent.insert(&east_room.name, &room.name);
visited.push(east_room);
queue.push_front(east_room);
}
}
if room.west.len() > 0 {
let west_room = self.get_room(&room.west).unwrap();
if !visited.contains(&west_room) {
room_parent.insert(&west_room.name,&room.name);
visited.push(west_room);
queue.push_front(west_room);
}
}
if room.north.len() > 0 {
let north_room = self.get_room(&room.north).unwrap();
if !visited.contains(&north_room) {
room_parent.insert(&north_room.name, &room.name);
visited.push(north_room);
queue.push_front(north_room);
}
}
if room.south.len() > 0 {
let south_room = self.get_room(&room.south).unwrap();
if !visited.contains(&south_room) {
room_parent.insert(&south_room.name, &room.name,);
visited.push(south_room);
queue.push_front(south_room);
}
}
}
if !room_parent.contains_key(end_room_name) {
return Ok(None);
}
let mut result: Vec<&Room> = Vec::new();
result.push(self.get_room(&room_parent[&end_room_name]).unwrap());
while room_parent.contains_key(result.last().unwrap().name.as_str()) {
result.push(self.get_room(&room_parent[result.last().unwrap().name.as_str()]).unwrap())
}
result.reverse();
return Ok(Some(result));
}
/// Прочитаме структурата на dungeon от нещо, което имплементира `BufRead`. Това може да е
/// файл, или, ако тестваме, може да е просто колекция от байтове.
///
/// Успешен резултат връща новосъздадения dungeon, пакетиран в `Ok`.
///
/// Вижте по-долу за обяснение на грешките, които очакваме.
///
pub fn from_reader<B: BufRead>(reader: B) -> Result<Self, Errors> {
let mut line_number = 1;
let mut action: String;
let mut dungeon = Dungeon::new();
let mut iterator = reader.lines();
if iterator.next().unwrap().unwrap() != "## Rooms" {
return Err(Errors::LineParseError {line_number});
} else {
line_number += 1;
action = String::from("room");
}
for line in iterator {
if line.is_err() {
return Err(Errors::IoError(line.unwrap_err()));
}
let mut value = line.unwrap().to_string();
if value.len() == 0 {
line_number += 1;
action = "".to_string();
continue;
}
if value.chars().next().unwrap().eq(&'-') {
value.remove(0);
}
if action == "room" {
if value.len() > 0 {
let room_name = value.trim();
match dungeon.add_room(&room_name) {
Err(err) => return Err(err),
_ => (),
}
} else {
action = String::new();
}
line_number += 1;
} else if action == "link" {
let vec: Vec<& str> = value.split("->").map(|value| value.trim()).collect();
if vec.len() == 3 {
if dungeon.get_room(vec[0]).is_err() {
return Err(Errors::UnknownRoom(vec[0].to_string()));
}
if dungeon.get_room(vec[2]).is_err() {
return Err(Errors::UnknownRoom(vec[2].to_string()));
}
match Direction::from_str(&vec[1]) {
Ok(direction) => {
match dungeon.set_link(&vec[0].to_string(), direction, &vec[2].to_string()) {
Err(err) => return Err(err),
_ => (),
}
},
_ => return Err(Errors::DirectionParseError(vec[1].to_string()))
}
} else {
return Err(Errors::LineParseError {line_number});
}
line_number += 1;
} else if value.contains("## Links") {
action = String::from("link");
line_number += 1;
} else {
return Err(Errors::LineParseError {line_number});
}
}
return Ok(dungeon);
}
}
Лог от изпълнението
Compiling solution v0.1.0 (/tmp/d20220116-3533338-1wwymxx/solution) Finished test [unoptimized + debuginfo] target(s) in 3.72s 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 ... FAILED test solution_test::test_finding_a_reflexive_path ... FAILED test solution_test::test_finding_an_indirect_path ... FAILED test solution_test::test_finding_no_path ... ok test solution_test::test_invalid_parsing ... FAILED test solution_test::test_io_error ... FAILED 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_finding_a_direct_path stdout ---- thread '<unnamed>' panicked at 'assertion failed: `(left == right)` left: `["Entrance"]`, right: `["Entrance", "Treasure Room"]`', tests/solution_test.rs:314:9 note: run with `RUST_BACKTRACE=1` environment variable to display a backtrace thread 'main' panicked at 'assertion failed: `(left == right)` left: `["Entrance"]`, right: `["Entrance", "Treasure Room"]`', tests/solution_test.rs:306:5 ---- solution_test::test_finding_a_reflexive_path stdout ---- thread '<unnamed>' panicked at 'called `Option::unwrap()` on a `None` value', tests/solution_test.rs:360:71 thread 'main' panicked at 'called `Option::unwrap()` on a `None` value', tests/solution_test.rs:354:5 ---- solution_test::test_finding_an_indirect_path stdout ---- thread '<unnamed>' panicked at 'assertion failed: `(left == right)` left: `"Hallway 2"`, right: `"Treasure Room"`', tests/solution_test.rs:348:9 thread 'main' panicked at 'assertion failed: `(left == right)` left: `"Hallway 2"`, right: `"Treasure Room"`', tests/solution_test.rs:320:5 ---- solution_test::test_invalid_parsing stdout ---- thread 'main' panicked at 'called `Option::unwrap()` on a `None` value', /tmp/d20220116-3533338-1wwymxx/solution/src/lib.rs:300:28 ---- solution_test::test_io_error stdout ---- thread '<unnamed>' panicked at 'called `Result::unwrap()` on an `Err` value: Custom { kind: Other, error: "fill_buf error!" }', /tmp/d20220116-3533338-1wwymxx/solution/src/lib.rs:300:37 thread 'main' panicked at 'called `Result::unwrap()` on an `Err` value: Custom { kind: Other, error: "fill_buf error!" }', tests/solution_test.rs:194:5 failures: solution_test::test_finding_a_direct_path solution_test::test_finding_a_reflexive_path solution_test::test_finding_an_indirect_path solution_test::test_invalid_parsing solution_test::test_io_error test result: FAILED. 10 passed; 5 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.00s error: test failed, to rerun pass '--test solution_test'