Решение на Dungeons and Compilers от Кристиан Николов
Към профила на Кристиан Николов
Резултати
- 12 точки от тестове
- 0 бонус точки
- 12 точки общо
- 9 успешни тест(а)
- 6 неуспешни тест(а)
Код
use std::collections::{HashMap, VecDeque};
use std::fs::File;
use std::io::{BufRead, Error};
use std::io::BufReader;
use std::str::FromStr;
use std::fmt;
use std::borrow::Borrow;
static ROOMS: &str = "## Rooms";
static LINKS: &str = "## Links";
#[derive(Debug)]
pub enum Errors {
DuplicateRoom(String),
UnknownRoom(String),
IoError(std::io::Error),
LineParseError { line_number: usize },
DirectionParseError(String),
}
/// Четирите посоки, в които може една стая да има съседи. Може да добавите още trait
/// имплементации, за да си улесните живота.
///
#[derive(Clone, Copy, Eq, PartialEq, Hash)]
pub enum Direction {
North,
South,
East,
West,
}
impl Direction {
pub fn get_opposite_direction(&self) -> Direction {
match self {
Direction::East => return Direction::West,
Direction::South => return Direction::North,
Direction::North => return Direction::South,
Direction::West => return Direction::East,
}
}
}
impl fmt::Display for Direction {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
match self {
Direction::North => write!(f, "North"),
Direction::South => write!(f, "South"),
Direction::East => write!(f, "East"),
Direction::West => write!(f, "West"),
}
}
}
impl From<std::io::Error> for Errors {
fn from(e: Error) -> Self {
Errors::IoError(e)
}
}
impl std::str::FromStr for Direction {
type Err = Errors;
fn from_str(s: &str) -> Result<Self, Errors> {
match s {
"North" => Ok(Direction::North),
"South" => Ok(Direction::South),
"East" => Ok(Direction::East),
"West" => Ok(Direction::West),
_ => Err(Errors::DirectionParseError("Wrong direction".to_string())),
}
}
}
pub struct Link {
pub first_room: Room,
pub second_room: Room,
pub direction: Direction
}
impl Link {
pub fn new(f_room: String, dir: Direction, s_room: String) -> Link {
Link {
first_room: Room::from(f_room),
direction: dir,
second_room: Room::from(s_room)
}
}
}
#[derive(Clone)]
pub struct Room {
pub name: String,
pub links: HashMap<Direction, String>,
}
impl Room {
pub fn from(room: String) -> Self {
Room {
name: room,
links: HashMap::new(),
}
}
pub fn insert_link(&mut self,
direction: Direction,
other_room: &str) {
self.links.insert(direction, other_room.to_string());
}
}
pub struct Dungeon {
rooms: HashMap<String, Room>
}
impl Dungeon {
pub fn new() -> Self {
Dungeon {
rooms: HashMap::new()
}
}
pub fn add_room(&mut self, name: &str) -> Result<(), Errors> {
let mut name = name.trim();
if self.rooms.contains_key(name) {
return Err(Errors::DuplicateRoom(name.to_string()));
}
self.rooms.insert(name.to_string(), Room::from(name.to_string()));
Ok(())
}
/// Прочитане на дадена стая -- когато извикаме `get_room`, очакваме reference към `Room`
/// структурата с това име.
///
/// Ако няма такава стая, очакваме `Errors::UnknownRoom` с подаденото име.
///
pub fn get_room(&self, room_name: &str) -> Result<&Room, Errors> {
if !self.rooms.contains_key(room_name) {
return Err(Errors::UnknownRoom(room_name.to_string()));
}
Ok(self.rooms.get(room_name).unwrap())
}
/// Добавяне на съсед на дадена стая. След извикването на функцията, очакваме стаята с име
/// `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.rooms.contains_key(room_name) {
return Err(Errors::UnknownRoom(room_name.to_string()));
} else if !self.rooms.contains_key(other_room_name) {
return Err(Errors::UnknownRoom(other_room_name.to_string()));
}
let mut room = self.rooms.get_mut(room_name).unwrap();
room.insert_link(direction, other_room_name);
let mut second_room = self.rooms.get_mut(other_room_name).unwrap();
second_room.insert_link(Direction::get_opposite_direction(&direction), room_name);
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.rooms.contains_key(room_name) {
return Err(Errors::UnknownRoom(room_name.to_string()));
}
let room = self.get_room(room_name)?;
if !room.links.contains_key(&direction) {
return Ok(Option::None);
}
Ok(self.rooms.get(room.links.get(&direction).unwrap()))
}
// Прочитаме структурата на dungeon от нещо, което имплементира `BufRead`. Това може да е
// файл, или, ако тестваме, може да е просто колекция от байтове.
//
// Успешен резултат връща новосъздадения dungeon, пакетиран в `Ok`.
//
// Вижте по-долу за обяснение на грешките, които очакваме.
pub fn from_reader<B: BufRead>(reader: B) -> Result<Self, Errors> {
let mut buffer_reader = BufReader::new(reader);
let mut buffer: String = String::new();
let mut dungeon: Dungeon = Dungeon::new();
buffer_reader.read_line(&mut buffer);
if !Dungeon::check_line_convection(&buffer, ROOMS) {
return Err(Errors::LineParseError{ line_number: 1 });
}
let mut index = 2;
while true {
buffer.clear();
buffer_reader.read_line(&mut buffer)?;
if buffer.eq("\n") {
break;
}
if !buffer.chars().next().unwrap().eq(&'-') {
return Err(Errors::LineParseError { line_number: index })
}
let mut buffer = buffer.trim().to_string();
buffer.remove(0);
dungeon.add_room(buffer.as_str());
index += 1;
}
buffer.clear();
buffer_reader.read_line(&mut buffer)?;
index += 1;
if !Dungeon::check_line_convection(&buffer, LINKS) {
return Err(Errors::LineParseError{ line_number: index });
}
for line in buffer_reader.lines() {
let mut link_data = match line {
Err(e) => return Err(Errors::IoError(e)),
Ok(r) => r
};
let link_result = Dungeon::get_link(link_data, index);
let link = match link_result {
Err(e) => return Err(e),
Ok(r) => r
};
dungeon.set_link(&link.first_room.name, link.direction, &link.second_room.name);
index += 1;
}
Ok(dungeon)
}
pub fn check_line_convection (line: &str, comparable_value: &str) -> bool {
line.trim().eq(comparable_value)
}
pub fn get_link (mut links: String, index: usize) -> Result<Link ,Errors> {
if !links.chars().next().unwrap().eq(&'-') {
return Err(Errors::LineParseError { line_number: index })
}
for i in 0..links.find('-').unwrap() + 1 {
links.remove(0);
}
let number = links.find("->");
if number == None {
return Err(Errors::LineParseError { line_number: index });
}
let first_room: String = links.chars().take(number.unwrap()).collect();
for i in 0..links.find('>').unwrap() + 1 {
links.remove(0);
}
let number = links.find("->");
if number == None {
return Err(Errors::LineParseError { line_number: index });
}
let direction_data: String = links.chars().take(number.unwrap()).collect();
let direction_result = Direction::from_str(&direction_data.trim());
let direction = match direction_result {
Err(e) => return Err(e),
Ok(r) => r
};
for i in 0..links.find('>').unwrap() + 1 {
links.remove(0);
}
let second_room = String::from(links.trim());
Ok(Link::new(first_room.trim().to_string(), direction, second_room.trim().to_string()))
}
pub fn find_path(
&self,
start_room_name: &str,
end_room_name: &str
) -> Result<Option<Vec<&Room>>, Errors> {
let mut queue: VecDeque<&str> = VecDeque::new();
let mut parents: HashMap<&str, &str> = HashMap::new();
let mut visited: HashMap<&str, bool> = HashMap::new();
let mut path: Vec<&Room> = Vec::new();
parents.insert(start_room_name, "");
visited.insert(start_room_name, true);
queue.push_back(start_room_name);
while !queue.is_empty() {
let current = queue.pop_front().unwrap();
if current.eq(end_room_name) {
break;
}
for mut entry in self.rooms.get(current).unwrap().links.iter() {
if *visited.get(entry.1.as_str()).unwrap() {
continue;
}
visited.insert(entry.1.as_str(), true);
queue.push_back(entry.1.as_str());
parents.insert(entry.1.as_str(), current);
}
}
let mut current = end_room_name;
while !current.eq("") {
path.push(self.rooms.get(current).unwrap());
current = parents.get(current).unwrap();
}
path.reverse();
return Ok(Some(path))
}
}
Лог от изпълнението
Compiling solution v0.1.0 (/tmp/d20220116-3533338-1hb4deq/solution) warning: unused import: `std::fs::File` --> src/lib.rs:2:5 | 2 | use std::fs::File; | ^^^^^^^^^^^^^ | = note: `#[warn(unused_imports)]` on by default warning: unused import: `std::borrow::Borrow` --> src/lib.rs:7:5 | 7 | use std::borrow::Borrow; | ^^^^^^^^^^^^^^^^^^^ warning: denote infinite loops with `loop { ... }` --> src/lib.rs:218:9 | 218 | while true { | ^^^^^^^^^^ help: use `loop` | = note: `#[warn(while_true)]` on by default warning: unused variable: `i` --> src/lib.rs:271:13 | 271 | for i in 0..links.find('-').unwrap() + 1 { | ^ help: if this is intentional, prefix it with an underscore: `_i` | = note: `#[warn(unused_variables)]` on by default warning: unused variable: `i` --> src/lib.rs:281:13 | 281 | for i in 0..links.find('>').unwrap() + 1 { | ^ help: if this is intentional, prefix it with an underscore: `_i` warning: unused variable: `i` --> src/lib.rs:299:13 | 299 | for i in 0..links.find('>').unwrap() + 1 { | ^ help: if this is intentional, prefix it with an underscore: `_i` warning: variable does not need to be mutable --> src/lib.rs:126:13 | 126 | let mut name = name.trim(); | ----^^^^ | | | help: remove this `mut` | = note: `#[warn(unused_mut)]` on by default warning: variable does not need to be mutable --> src/lib.rs:170:13 | 170 | let mut room = self.rooms.get_mut(room_name).unwrap(); | ----^^^^ | | | help: remove this `mut` warning: variable does not need to be mutable --> src/lib.rs:173:13 | 173 | let mut second_room = self.rooms.get_mut(other_room_name).unwrap(); | ----^^^^^^^^^^^ | | | help: remove this `mut` warning: variable does not need to be mutable --> src/lib.rs:244:17 | 244 | let mut link_data = match line { | ----^^^^^^^^^ | | | help: remove this `mut` warning: variable does not need to be mutable --> src/lib.rs:327:17 | 327 | for mut entry in self.rooms.get(current).unwrap().links.iter() { | ----^^^^^ | | | help: remove this `mut` warning: unused `Result` that must be used --> src/lib.rs:211:9 | 211 | buffer_reader.read_line(&mut buffer); | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ | = note: `#[warn(unused_must_use)]` on by default = note: this `Result` may be an `Err` variant, which should be handled warning: unused `Result` that must be used --> src/lib.rs:232:13 | 232 | dungeon.add_room(buffer.as_str()); | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ | = note: this `Result` may be an `Err` variant, which should be handled warning: unused `Result` that must be used --> src/lib.rs:254:13 | 254 | dungeon.set_link(&link.first_room.name, link.direction, &link.second_room.name); | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ | = note: this `Result` may be an `Err` variant, which should be handled warning: `solution` (lib) generated 14 warnings Finished test [unoptimized + debuginfo] target(s) in 4.10s 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 ... ok test solution_test::test_finding_an_indirect_path ... FAILED test solution_test::test_finding_no_path ... FAILED 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 ... FAILED 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 'called `Option::unwrap()` on a `None` value', src/lib.rs:328:51 note: run with `RUST_BACKTRACE=1` environment variable to display a backtrace thread 'main' panicked at 'called `Option::unwrap()` on a `None` value', tests/solution_test.rs:306:5 ---- solution_test::test_finding_an_indirect_path stdout ---- thread '<unnamed>' panicked at 'called `Option::unwrap()` on a `None` value', src/lib.rs:328:51 thread 'main' panicked at 'called `Option::unwrap()` on a `None` value', tests/solution_test.rs:320:5 ---- solution_test::test_finding_no_path stdout ---- thread '<unnamed>' panicked at 'called `Option::unwrap()` on a `None` value', src/lib.rs:340:44 thread 'main' panicked at 'called `Option::unwrap()` on a `None` value', tests/solution_test.rs:372:5 ---- solution_test::test_invalid_parsing stdout ---- thread 'main' panicked at 'assertion failed: matches!(Dungeon :: from_reader(\"\".as_bytes()),\n Err(Errors :: LineParseError { line_number : 0 }))', tests/solution_test.rs:276:5 ---- solution_test::test_io_error stdout ---- thread '<unnamed>' panicked at 'assertion failed: matches!(dungeon, Err(Errors :: IoError(_)))', tests/solution_test.rs:196:9 thread 'main' panicked at 'called `Option::unwrap()` on a `None` value', tests/solution_test.rs:194:5 ---- solution_test::test_parsing_cyrillic_rooms stdout ---- thread 'main' panicked at 'called `Result::unwrap()` on an `Err` value: DirectionParseError("Wrong direction")', tests/solution_test.rs:294:72 failures: solution_test::test_finding_a_direct_path solution_test::test_finding_an_indirect_path solution_test::test_finding_no_path solution_test::test_invalid_parsing solution_test::test_io_error solution_test::test_parsing_cyrillic_rooms test result: FAILED. 9 passed; 6 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.00s error: test failed, to rerun pass '--test solution_test'