Решение на Dungeons and Compilers от Димо Чанев

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

Към профила на Димо Чанев

Резултати

  • 15 точки от тестове
  • 0 бонус точки
  • 15 точки общо
  • 11 успешни тест(а)
  • 4 неуспешни тест(а)

Код

use std::collections::HashMap;
use std::io::BufRead;
/// match_prefix("- ", "- Foo") //=> Some("Foo")
/// match_prefix("- ", "Bar") //=> None
///
fn match_prefix<'a, 'b>(prefix: &'a str, input: &'b str) -> Option<&'b str> {
let mut ind: usize = 0;
while ind < prefix.len() && prefix.as_bytes()[ind] == input.as_bytes()[ind] {
ind += 1;
}
if ind == 0 {
return None;
}
Some(&input[ind..])
}
/// Различните грешки, които ще очакваме да върнете като резултат от някои невалидни операции.
/// Повече детайли по-долу.
///
#[derive(Debug)]
pub enum Errors {
DuplicateRoom(String),
UnknownRoom(String),
IoError(std::io::Error),
LineParseError { line_number: usize },
DirectionParseError(String),
}
/// Четирите посоки, в които може една стая да има съседи. Може да добавите още trait
/// имплементации, за да си улесните живота.
///
#[derive(Clone, Copy, Hash, PartialEq, Eq)]
pub enum Direction {
North,
South,
East,
West,
}
/// Една стая в подземията. Дефинира се само с име, макар че в по-интересна имплементация може да
/// държи item-и, противници...
///
#[derive(Clone)]
pub struct Room {
pub name: String,
pub links: HashMap<Direction, String>,
// Каквито други полета ви трябват
}
/// Контейнер за стаите и не само. Ще работим предимно със тази структура.
///
pub struct Dungeon {
rooms: Vec<Room>,
// Каквито полета ви трябват
}
impl Dungeon {
/// Конструиране на празен Dungeon, в който няма никакви стаи.
///
pub fn new() -> Self {
Self { rooms: vec![] }
}
/// Добавяне на стая към Dungeon с име `name`. Връща `Ok(())` при успех. Ако вече има стая с
/// такова име, очакваме да върнете `Errors::DuplicateRoom` с името.
///
pub fn add_room(&mut self, name: &str) -> Result<(), Errors> {
for r in &self.rooms {
if r.name == name {
return Err(Errors::DuplicateRoom(String::from(name)));
}
}
self.rooms.push(Room {
name: String::from(name),
links: HashMap::new(),
});
Ok(())
}
/// Прочитане на дадена стая -- когато извикаме `get_room`, очакваме reference към `Room`
/// структурата с това име.
///
/// Ако няма такава стая, очакваме `Errors::UnknownRoom` с подаденото име.
///
pub fn get_room(&self, room_name: &str) -> Result<&Room, Errors> {
for r in &self.rooms {
if r.name == room_name {
return Ok(r);
}
}
Err(Errors::UnknownRoom(String::from(room_name)))
}
/// Добавяне на съсед на дадена стая. След извикването на функцията, очакваме стаята с име
/// `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> {
match self.get_room(room_name) {
Err(x) => {
return Err(x);
}
_ => {}
}
match self.get_room(other_room_name) {
Err(x) => {
return Err(x);
}
_ => {}
}
for x in &mut self.rooms {
if x.name == room_name {
x.links.insert(direction, String::from(other_room_name));
break;
}
}
for x in &mut self.rooms {
if x.name == other_room_name {
x.links.insert(
match direction {
Direction::North => Direction::South,
Direction::South => Direction::North,
Direction::East => Direction::West,
Direction::West => Direction::East,
},
String::from(room_name),
);
break;
}
}
Ok(())
}

Имаш една проста грешка в тази функция, която ти чупи няколко тестa. Прочети внимателно условието, макар че подозирам че проблема не е в разбирането, колкото че сигурно не си тествал дали кода работи както очакваш.

/// Четене на съседа на стаята с име `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> {
match self.get_room(room_name) {
Err(x) => {
return Err(x);
}
_ => {}
}
for x in &self.rooms {
if x.name == room_name {
if x.links.contains_key(&direction) {
return Ok(Some(
self.get_room(x.links.get(&direction).unwrap()).unwrap(),
));
}
break;
}
}
Ok(None)
}
/// Прочитаме структурата на dungeon от нещо, което имплементира `BufRead`. Това може да е
/// файл, или, ако тестваме, може да е просто колекция от байтове.
///
/// Успешен резултат връща новосъздадения dungeon, пакетиран в `Ok`.
///
/// Вижте по-долу за обяснение на грешките, които очакваме.
///
pub fn from_reader<B: BufRead>(reader: B) -> Result<Self, Errors> {
let mut read_rooms = false;
let mut read_links = false;
let mut num = 0;
let mut res = Dungeon::new();
for line in reader.lines() {
match line {
Ok(x) => {
if num == 0 {
if x.trim() != String::from("## Rooms") {
return Err(Errors::LineParseError { line_number: num });
} else {
read_rooms = true;
}
} else if read_rooms {
if x.trim() == String::from("") {
read_rooms = false;
} else {
match match_prefix("- ", &x) {
Some(x) => match res.add_room(x) {
Err(x) => {
return Err(x);
}
_ => {}
},
None => {
return Err(Errors::LineParseError { line_number: num });
}
}
}
} else if read_links {
match match_prefix("- ", &x) {
Some(x) => {
let split: Vec<&str> = x.split(" -> ").collect();
if split.len() != 3 {
return Err(Errors::LineParseError { line_number: num });
}
let dir = match split[1] {
"East" => Some(Direction::East),
"West" => Some(Direction::West),
"North" => Some(Direction::North),
"South" => Some(Direction::South),
_ => None,
};
if dir == None {
return Err(Errors::DirectionParseError(String::from(
split[1],
)));
}
match res.set_link(split[0], dir.unwrap(), split[2]) {
Err(x) => {
return Err(x);
}
_ => {}
}
}
None => {
return Err(Errors::LineParseError { line_number: num });
}
}
} else {
if x.trim() != String::from("## Links") {
return Err(Errors::LineParseError { line_number: num });
} else {
read_links = true;
}
}
}
Err(x) => {
return Err(Errors::IoError(x));
}
}
num += 1;
}
Ok(res)
}
/// Търси път от `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 stack: Vec<String> = vec![];
let mut parent: HashMap<String, String> = HashMap::new();
stack.push(String::from(start_room_name));
while stack.len() != 0 {
let curr = &stack.pop().unwrap();
if curr == end_room_name {
let end_room;
match self.get_room(end_room_name) {
Ok(x) => end_room = x,
Err(x) => {
return Err(x);
}
}
let mut path: Vec<&Room> = vec![end_room];
while path.last().unwrap().name != start_room_name {
let n;
match self.get_room(parent.get(&path.last().unwrap().name).unwrap()) {
Ok(x) => n = x,
Err(x) => {
return Err(x);
}
}
path.push(n);
}
return Ok(Some(path));
}
let curr_room;
match self.get_room(&curr) {
Ok(x) => curr_room = x,
Err(x) => {
return Err(x);
}
}
for i in curr_room.links.keys() {
let next = curr_room.links.get(i).unwrap().to_string();
match parent.get(&next) {
None => {
parent.insert(String::from(&next), String::from(curr));
stack.push(next);
}
_ => {}
}
}
}
Ok(None)
}
}
#[cfg(test)]
mod tests {
use crate::{Direction, Dungeon};
#[test]
fn test_basic_1() {
let mut dungeon = Dungeon::new();
dungeon.add_room("Entrance").unwrap();
dungeon.add_room("Hallway").unwrap();
dungeon
.set_link("Entrance", Direction::East, "Hallway")
.unwrap();
assert_eq!(dungeon.get_room("Entrance").unwrap().name, "Entrance");
assert_eq!(
dungeon
.get_next_room("Entrance", Direction::East)
.unwrap()
.unwrap()
.name,
"Hallway"
);
}
const TEST_INPUT_1: &str = "
## Rooms
- Entrance
- Hallway
## Links
- Entrance -> East -> Hallway
";
#[test]
fn test_basic_2() {
// .trim() за да премахнем първия и последния ред:
let dungeon = Dungeon::from_reader(TEST_INPUT_1.trim().as_bytes()).unwrap();
assert_eq!(dungeon.get_room("Entrance").unwrap().name, "Entrance");
assert_eq!(dungeon.get_room("Hallway").unwrap().name, "Hallway");
assert_eq!(
dungeon
.get_next_room("Entrance", Direction::East)
.unwrap()
.unwrap()
.name,
"Hallway"
);
}
#[test]
fn test_basic_3() {
let mut dungeon = Dungeon::new();
dungeon.add_room("Entrance").unwrap();
dungeon.add_room("Treasure Room").unwrap();
dungeon
.set_link("Entrance", Direction::West, "Treasure Room")
.unwrap();
let path = dungeon
.find_path("Entrance", "Treasure Room")
.unwrap()
.unwrap();
assert!(path.len() > 0);
}
}

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

Compiling solution v0.1.0 (/tmp/d20220116-3533338-2wbzny/solution)
    Finished test [unoptimized + debuginfo] target(s) in 3.55s
     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 ... 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_finding_a_direct_path stdout ----
thread '<unnamed>' panicked at 'assertion failed: `(left == right)`
  left: `["Treasure Room", "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: `["Treasure Room", "Entrance"]`,
 right: `["Entrance", "Treasure Room"]`', tests/solution_test.rs:306:5

---- solution_test::test_finding_an_indirect_path stdout ----
thread '<unnamed>' panicked at 'assertion failed: `(left == right)`
  left: `"Treasure Room"`,
 right: `"Entrance"`', tests/solution_test.rs:347:9
thread 'main' panicked at 'assertion failed: `(left == right)`
  left: `"Treasure Room"`,
 right: `"Entrance"`', tests/solution_test.rs:320:5

---- solution_test::test_finding_no_path stdout ----
thread '<unnamed>' panicked at 'assertion failed: path.is_err()', tests/solution_test.rs:382:9
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


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

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

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

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

Димо качи първо решение на 29.12.2021 19:57 (преди почти 4 години)

Димо качи решение на 10.01.2022 14:13 (преди над 3 години)

use std::collections::HashMap;
use std::io::BufRead;
/// match_prefix("- ", "- Foo") //=> Some("Foo")
/// match_prefix("- ", "Bar") //=> None
///
fn match_prefix<'a, 'b>(prefix: &'a str, input: &'b str) -> Option<&'b str> {
let mut ind: usize = 0;
while ind < prefix.len() && prefix.as_bytes()[ind] == input.as_bytes()[ind] {
ind += 1;
}
if ind == 0 {
return None;
}
Some(&input[ind..])
}
/// Различните грешки, които ще очакваме да върнете като резултат от някои невалидни операции.
/// Повече детайли по-долу.
///
#[derive(Debug)]
pub enum Errors {
DuplicateRoom(String),
UnknownRoom(String),
IoError(std::io::Error),
LineParseError { line_number: usize },
DirectionParseError(String),
}
/// Четирите посоки, в които може една стая да има съседи. Може да добавите още trait
/// имплементации, за да си улесните живота.
///
#[derive(Clone, Copy, Hash, PartialEq, Eq)]
pub enum Direction {
North,
South,
East,
West,
}
/// Една стая в подземията. Дефинира се само с име, макар че в по-интересна имплементация може да
/// държи item-и, противници...
///
#[derive(Clone)]
pub struct Room {
pub name: String,
pub links: HashMap<Direction, String>,
// Каквито други полета ви трябват
}
/// Контейнер за стаите и не само. Ще работим предимно със тази структура.
///
pub struct Dungeon {
rooms: Vec<Room>,
// Каквито полета ви трябват
}
impl Dungeon {
/// Конструиране на празен Dungeon, в който няма никакви стаи.
///
pub fn new() -> Self {
Self { rooms: vec![] }
}
/// Добавяне на стая към Dungeon с име `name`. Връща `Ok(())` при успех. Ако вече има стая с
/// такова име, очакваме да върнете `Errors::DuplicateRoom` с името.
///
pub fn add_room(&mut self, name: &str) -> Result<(), Errors> {
for r in &self.rooms {
if r.name == name {
return Err(Errors::DuplicateRoom(String::from(name)));
}
}
self.rooms.push(Room {
name: String::from(name),
links: HashMap::new(),
});
Ok(())
}
/// Прочитане на дадена стая -- когато извикаме `get_room`, очакваме reference към `Room`
/// структурата с това име.
///
/// Ако няма такава стая, очакваме `Errors::UnknownRoom` с подаденото име.
///
pub fn get_room(&self, room_name: &str) -> Result<&Room, Errors> {
for r in &self.rooms {
if r.name == room_name {
return Ok(r);
}
}
Err(Errors::UnknownRoom(String::from(room_name)))
}
/// Добавяне на съсед на дадена стая. След извикването на функцията, очакваме стаята с име
/// `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> {
match self.get_room(room_name) {
Err(x) => {
return Err(x);
}
_ => {}
}
match self.get_room(other_room_name) {
Err(x) => {
return Err(x);
}
_ => {}
}
for x in &mut self.rooms {
if x.name == room_name {
x.links.insert(direction, String::from(other_room_name));
break;
}
}
for x in &mut self.rooms {
if x.name == other_room_name {
- x.links.insert(direction, String::from(room_name));
+ x.links.insert(
+ match direction {
+ Direction::North => Direction::South,
+ Direction::South => Direction::North,
+ Direction::East => Direction::West,
+ Direction::West => Direction::East,
+ },
+ String::from(room_name),
+ );
break;
}
}
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> {
match self.get_room(room_name) {
Err(x) => {
return Err(x);
}
_ => {}
}
for x in &self.rooms {
if x.name == room_name {
if x.links.contains_key(&direction) {
return Ok(Some(
self.get_room(x.links.get(&direction).unwrap()).unwrap(),
));
}
break;
}
}
Ok(None)
}
/// Прочитаме структурата на dungeon от нещо, което имплементира `BufRead`. Това може да е
/// файл, или, ако тестваме, може да е просто колекция от байтове.
///
/// Успешен резултат връща новосъздадения dungeon, пакетиран в `Ok`.
///
/// Вижте по-долу за обяснение на грешките, които очакваме.
///
pub fn from_reader<B: BufRead>(reader: B) -> Result<Self, Errors> {
let mut read_rooms = false;
let mut read_links = false;
let mut num = 0;
let mut res = Dungeon::new();
for line in reader.lines() {
match line {
Ok(x) => {
if num == 0 {
if x.trim() != String::from("## Rooms") {
return Err(Errors::LineParseError { line_number: num });
} else {
read_rooms = true;
}
} else if read_rooms {
if x.trim() == String::from("") {
read_rooms = false;
} else {
match match_prefix("- ", &x) {
Some(x) => match res.add_room(x) {
Err(x) => {
return Err(x);
}
_ => {}
},
None => {
return Err(Errors::LineParseError { line_number: num });
}
}
}
} else if read_links {
match match_prefix("- ", &x) {
Some(x) => {
let split: Vec<&str> = x.split(" -> ").collect();
if split.len() != 3 {
return Err(Errors::LineParseError { line_number: num });
}
let dir = match split[1] {
"East" => Some(Direction::East),
"West" => Some(Direction::West),
"North" => Some(Direction::North),
"South" => Some(Direction::South),
_ => None,
};
if dir == None {
return Err(Errors::DirectionParseError(String::from(
split[1],
)));
}
match res.set_link(split[0], dir.unwrap(), split[2]) {
Err(x) => {
return Err(x);
}
_ => {}
}
}
None => {
return Err(Errors::LineParseError { line_number: num });
}
}
} else {
if x.trim() != String::from("## Links") {
return Err(Errors::LineParseError { line_number: num });
} else {
read_links = true;
}
}
}
Err(x) => {
return Err(Errors::IoError(x));
}
}
num += 1;
}
Ok(res)
}
/// Търси път от `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 stack: Vec<String> = vec![];
let mut parent: HashMap<String, String> = HashMap::new();
stack.push(String::from(start_room_name));
while stack.len() != 0 {
let curr = &stack.pop().unwrap();
if curr == end_room_name {
let end_room;
match self.get_room(end_room_name) {
Ok(x) => end_room = x,
Err(x) => {
return Err(x);
}
}
let mut path: Vec<&Room> = vec![end_room];
while path.last().unwrap().name != start_room_name {
let n;
match self.get_room(parent.get(&path.last().unwrap().name).unwrap()) {
Ok(x) => n = x,
Err(x) => {
return Err(x);
}
}
path.push(n);
}
return Ok(Some(path));
}
let curr_room;
match self.get_room(&curr) {
Ok(x) => curr_room = x,
Err(x) => {
return Err(x);
}
}
for i in curr_room.links.keys() {
let next = curr_room.links.get(i).unwrap().to_string();
match parent.get(&next) {
None => {
parent.insert(String::from(&next), String::from(curr));
stack.push(next);
}
_ => {}
}
}
}
Ok(None)
}
}
#[cfg(test)]
mod tests {
use crate::{Direction, Dungeon};
#[test]
fn test_basic_1() {
let mut dungeon = Dungeon::new();
dungeon.add_room("Entrance").unwrap();
dungeon.add_room("Hallway").unwrap();
dungeon
.set_link("Entrance", Direction::East, "Hallway")
.unwrap();
assert_eq!(dungeon.get_room("Entrance").unwrap().name, "Entrance");
assert_eq!(
dungeon
.get_next_room("Entrance", Direction::East)
.unwrap()
.unwrap()
.name,
"Hallway"
);
}
const TEST_INPUT_1: &str = "
## Rooms
- Entrance
- Hallway
## Links
- Entrance -> East -> Hallway
";
#[test]
fn test_basic_2() {
// .trim() за да премахнем първия и последния ред:
let dungeon = Dungeon::from_reader(TEST_INPUT_1.trim().as_bytes()).unwrap();
assert_eq!(dungeon.get_room("Entrance").unwrap().name, "Entrance");
assert_eq!(dungeon.get_room("Hallway").unwrap().name, "Hallway");
assert_eq!(
dungeon
.get_next_room("Entrance", Direction::East)
.unwrap()
.unwrap()
.name,
"Hallway"
);
}
#[test]
fn test_basic_3() {
let mut dungeon = Dungeon::new();
dungeon.add_room("Entrance").unwrap();
dungeon.add_room("Treasure Room").unwrap();
dungeon
.set_link("Entrance", Direction::West, "Treasure Room")
.unwrap();
let path = dungeon
.find_path("Entrance", "Treasure Room")
.unwrap()
.unwrap();
assert!(path.len() > 0);
}
}