Решение на Dungeons and Compilers от Теодора Колева
Резултати
- 20 точки от тестове
- 0 бонус точки
- 20 точки общо
- 15 успешни тест(а)
- 0 неуспешни тест(а)
Код
use std::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,
}
impl Direction {
pub fn opposite(&self) -> Direction {
match self {
Direction::North => Direction::South,
Direction::South => Direction::North,
Direction::East => Direction::West,
Direction::West => Direction::East
}
}
pub fn index(&self) -> usize {
match self {
Direction::North => 0,
Direction::South => 1,
Direction::East => 2,
Direction::West => 3
}
}
pub fn parse(str: &str) -> Result<Direction, Errors> {
if str.eq("North") {
Ok(Direction::North)
} else if str.eq("South") {
Ok(Direction::South)
} else if str.eq("East") {
Ok(Direction::East)
} else if str.eq("West") {
Ok(Direction::West)
} else {
Err(Errors::DirectionParseError(str.parse().unwrap()))
}
}
}
pub struct Room {
pub name: String,
}
pub struct Dungeon {
pub rooms: std::collections::HashMap<String, Room>,
pub room_connections: std::collections::HashMap<String, [Option<String>; 4]>,
}
impl Dungeon {
pub fn new() -> Self {
Self {
rooms: std::collections::HashMap::new(),
room_connections: std::collections::HashMap::new(),
}
}
pub fn add_room(&mut self, name: &str) -> Result<(), Errors> {
if self.rooms.contains_key(name) {
Err(Errors::DuplicateRoom(name.parse().unwrap()))
} else {
self.rooms.insert(name.parse().unwrap(), Room { name: name.parse().unwrap() });
self.room_connections.insert(name.parse().unwrap(), [None, None, None, None]);
Ok(())
}
}
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.parse().unwrap()))
}
}
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.parse().unwrap()));
}
if !self.rooms.contains_key(other_room_name) {
return Err(Errors::UnknownRoom(other_room_name.parse().unwrap()));
}
if let Some(room_neighbours) = self.room_connections.get_mut(room_name) {
room_neighbours[direction.index()] = Some(other_room_name.parse().unwrap());
}
if let Some(other_room_neighbours) = self.room_connections.get_mut(other_room_name) {
other_room_neighbours[direction.opposite().index()] = Some(room_name.parse().unwrap());
}
Ok(())
}
pub fn get_next_room(&self, room_name: &str, direction: Direction) -> Result<Option<&Room>, Errors> {
match self.room_connections.get(room_name) {
None => Err(Errors::UnknownRoom(room_name.parse().unwrap())),
Some(room_neighbours) => {
match &room_neighbours[direction.index()] {
None => Ok(None),
Some(next_room_name) => Ok(self.rooms.get(next_room_name))
}
}
}
}
fn match_prefix<'a, 'b>(prefix: &'a str, input: &'b str) -> Option<&'b str> {
input.strip_prefix(prefix)
}
fn check_room_line(&mut self, line: &str, line_counter: usize) -> Result<(), Errors> {
match Dungeon::match_prefix("- ", &line) {
None => Err(Errors::LineParseError { line_number: line_counter }),
Some(room_name) => {
if let Err(e) = self.add_room(room_name) {
return Err(e);
} else {
Ok(())
}
}
}
}
fn check_link_line(&mut self, line: &str, line_counter: usize) -> Result<(), Errors> {
match Dungeon::match_prefix("- ", &line) {
None => Err(Errors::LineParseError { line_number: line_counter }),
Some(sequence) => {
let rooms: Vec<&str> = sequence.split(" -> ").collect();
if rooms.len() != 3 {
return Err(Errors::LineParseError { line_number: line_counter });
}
match Direction::parse(rooms[1]) {
Err(e) => return Err(e),
Ok(dir) => {
if let Err(e) = self.set_link(rooms[0], dir, rooms[2]) {
Err(e)
} else {
Ok(())
}
}
}
}
}
}
pub fn from_reader<B: BufRead>(reader: B) -> Result<Self, Errors> {
let mut lines_iter = reader.lines();
let mut line_counter = 0;
let mut dungeon = Dungeon::new();
let mut rooms = true;
loop {
match lines_iter.next() {
//end of file
None => {
// file is empty
if line_counter == 0 {
return Err(Errors::LineParseError { line_number: line_counter });
} else {
break;
}
}
//io error
Some(Err(e)) => return Err(Errors::IoError(e)),
//line is ok
Some(Ok(line)) => {
line_counter = line_counter + 1;
//empty line => next should be "## Links"
if line.is_empty() {
line_counter = line_counter + 1;
match lines_iter.next() {
None => return Err(Errors::LineParseError { line_number: line_counter }),
Some(Err(e)) => return Err(Errors::IoError(e)),
Some(Ok(line)) => {
match Dungeon::match_prefix("## Links", &line) {
Some(_) => {
rooms = false;
continue;
}
None => return Err(Errors::LineParseError { line_number: line_counter })
}
}
}
}
//in room declarations
if rooms {
//checks that first line is correct
if line_counter == 1 {
match Dungeon::match_prefix("## Rooms", &line) {
None => return Err(Errors::LineParseError { line_number: line_counter }),
Some(_) => continue
}
}
if let Err(e) = dungeon.check_room_line(&line, line_counter) {
return Err(e);
}
}
//in link declarations
else {
if let Err(e) = dungeon.check_link_line(&line, line_counter) {
return Err(e);
}
}
}
}
}
Ok(dungeon)
}
fn backtrack(&self, parents: std::collections::HashMap<&str, &str>, start_room: &str, end_room: &str) -> Vec<&Room> {
let mut reversed_path_names = Vec::new();
reversed_path_names.push(end_room);
while !reversed_path_names.ends_with(&[start_room]) {
let last = reversed_path_names.last().unwrap();
reversed_path_names.push(parents.get(last).unwrap())
}
reversed_path_names.reverse();
let mut path = Vec::new();
for name in reversed_path_names {
path.push(self.get_room(name).unwrap());
}
path
}
pub fn find_path(
&self,
start_room_name: &str,
end_room_name: &str,
) -> Result<Option<Vec<&Room>>, Errors> {
let mut queue = std::collections::VecDeque::new();
let mut parents = std::collections::HashMap::new();
let mut visited = std::collections::HashSet::new();
match self.get_room(start_room_name) {
Ok(_) => {
queue.push_back(start_room_name);
visited.insert(start_room_name);
}
Err(e) => return Err(e)
}
if let Err(e) = self.get_room(end_room_name) {
return Err(e);
}
while !queue.is_empty() {
let current_room = queue.pop_front().unwrap();
if current_room == end_room_name {
return Ok(Some(self.backtrack(parents, start_room_name, end_room_name)));
}
for dir in [Direction::North, Direction::South, Direction::West, Direction::East] {
match self.get_next_room(current_room, dir) {
Err(e) => return Err(e),
Ok(None) => continue,
Ok(Some(room)) => {
if !visited.contains(&*room.name) {
queue.push_back(&room.name);
visited.insert(&room.name);
parents.insert(&room.name, ¤t_room);
}
}
}
}
}
Ok(None)
}
}
Лог от изпълнението
Compiling solution v0.1.0 (/tmp/d20220116-3533338-1h2d6k2/solution) warning: cannot borrow `reversed_path_names` as mutable because it is also borrowed as immutable --> src/lib.rs:236:13 | 235 | let last = reversed_path_names.last().unwrap(); | -------------------------- immutable borrow occurs here 236 | reversed_path_names.push(parents.get(last).unwrap()) | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^----^^^^^^^^^^^ | | | | | immutable borrow later used here | mutable borrow occurs here | = note: `#[warn(mutable_borrow_reservation_conflict)]` on by default = warning: this borrowing pattern was not meant to be accepted, and may become a hard error in the future = note: for more information, see issue #59159 <https://github.com/rust-lang/rust/issues/59159> warning: `solution` (lib) generated 1 warning Finished test [unoptimized + debuginfo] target(s) in 3.89s 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 ... ok 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 test result: ok. 15 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.00s