switch to nom

This commit is contained in:
Rüdiger Ludwig 2023-07-30 16:42:17 +02:00
parent a5f19ecae1
commit b00835c25e
7 changed files with 288 additions and 181 deletions

View file

@ -6,9 +6,7 @@ edition = "2021"
[dependencies]
anyhow = "1.0"
const_format = "0.2.31"
itertools = "0.11"
num-traits = "0.2"
once_cell = "1.18.0"
regex = "1.7"
thiserror = "1.0"
nom = "7"

View file

@ -3,5 +3,6 @@ pub mod direction;
pub mod file;
pub mod helper;
pub mod math;
pub mod parser;
pub mod pos;
pub mod turn;

70
src/common/parser.rs Normal file
View file

@ -0,0 +1,70 @@
use nom::{
branch::alt,
bytes::complete::tag,
character::complete::{line_ending, space0, space1},
combinator::{eof, opt, value},
error::ParseError,
multi::many0,
sequence::{delimited, preceded, terminated, tuple},
Err, IResult, Parser,
};
pub fn extract_result<I, F, O, E: ParseError<I>>(
mut parser: F,
) -> impl FnMut(I) -> Result<O, Err<E>>
where
F: FnMut(I) -> IResult<I, O, E>,
{
move |input: I| parser(input).map(|(_, value)| value)
}
pub fn ignore<I, F, O, E: ParseError<I>>(mut parser: F) -> impl FnMut(I) -> Result<I, Err<E>>
where
F: FnMut(I) -> IResult<I, O, E>,
{
move |input: I| parser(input).map(|(i, _)| i)
}
pub fn eol_terminated<'a, F, O, E: ParseError<&'a str>>(
line: F,
) -> impl FnMut(&'a str) -> IResult<&'a str, O, E>
where
F: Parser<&'a str, O, E>,
{
terminated(line, alt((line_ending, eof)))
}
pub fn true_false<'a, E: ParseError<&'a str>>(input: &'a str) -> IResult<&'a str, bool, E> {
alt((value(true, tag("true")), value(false, tag("false"))))(input)
}
pub fn empty_lines<'a, E: ParseError<&'a str>>(input: &'a str) -> Result<&'a str, Err<E>> {
ignore(tuple((many0(line_ending), opt(eof))))(input)
}
pub fn trim_left1<'a, F, O, E: ParseError<&'a str>>(
inner: F,
) -> impl FnMut(&'a str) -> IResult<&'a str, O, E>
where
F: Parser<&'a str, O, E>,
{
preceded(space1, inner)
}
pub fn trim0<'a, F, O, E: ParseError<&'a str>>(
inner: F,
) -> impl FnMut(&'a str) -> IResult<&'a str, O, E>
where
F: Parser<&'a str, O, E>,
{
delimited(space0, inner, space0)
}
pub fn trim1<'a, F, O, E: ParseError<&'a str>>(
inner: F,
) -> impl FnMut(&'a str) -> IResult<&'a str, O, E>
where
F: Parser<&'a str, O, E>,
{
delimited(space1, inner, space1)
}

View file

@ -1,3 +1,5 @@
use std::str::FromStr;
use crate::common::file::split_lines;
use super::template::{DayTrait, ResultType};
@ -14,13 +16,13 @@ impl DayTrait for Day {
}
fn part1(&self, lines: &str) -> anyhow::Result<ResultType> {
let forest = Forest::try_from(lines)?;
let forest: Forest = lines.parse()?;
let result = forest.count_visible();
Ok(ResultType::Integer(result))
}
fn part2(&self, lines: &str) -> anyhow::Result<ResultType> {
let forest = Forest::try_from(lines)?;
let forest: Forest = lines.parse()?;
let result = forest.best_score();
Ok(ResultType::Integer(result))
}
@ -181,10 +183,10 @@ impl Forest {
}
}
impl TryFrom<&str> for Forest {
type Error = ForestError;
impl FromStr for Forest {
type Err = ForestError;
fn try_from(lines: &str) -> Result<Self, Self::Error> {
fn from_str(lines: &str) -> Result<Self, Self::Err> {
split_lines(lines)
.map(|line| {
line.chars()
@ -206,7 +208,7 @@ mod test {
fn test_parse() -> Result<()> {
let day = Day {};
let lines = read_string(day.get_day_number(), "example01.txt")?;
let forest = Forest::try_from(lines.as_str())?;
let forest: Forest = lines.parse()?;
assert_eq!(forest.width, 5);
assert_eq!(forest.height, 5);

View file

@ -1,9 +1,19 @@
use crate::common::file::split_lines;
use crate::common::parser::{
empty_lines, eol_terminated, extract_result, ignore, trim0, trim_left1, true_false,
};
use super::template::{DayTrait, ResultType};
use once_cell::sync::Lazy;
use regex::Regex;
use std::{iter::zip, num::ParseIntError};
use nom::{
branch::alt,
bytes::complete::tag,
character::complete::{char, i64, u32},
combinator::{map, value, verify},
error::Error,
multi::{many1, separated_list1},
sequence::{preceded, tuple},
Err, IResult, Parser,
};
use std::{iter::zip, str::FromStr};
use thiserror::Error;
const DAY_NUMBER: usize = 11;
@ -16,50 +26,50 @@ impl DayTrait for Day {
}
fn part1(&self, lines: &str) -> anyhow::Result<ResultType> {
let troop = Troop::try_from(lines)?;
let troop: Troop = lines.parse()?;
Ok(ResultType::Integer(troop.play(20)))
}
fn part2(&self, lines: &str) -> anyhow::Result<ResultType> {
let troop = Troop::try_from(lines)?;
let troop: Troop = lines.parse()?;
Ok(ResultType::Integer(troop.play_again(10_000)))
}
}
#[derive(Debug, Error)]
enum MonkeyError {
#[error("Not an Integer")]
NotAnInteger(#[from] ParseIntError),
#[error("Can't parse line: {0}")]
UnknownLine(String),
#[error("Did not expect end of Input")]
PrematureEndOfInput,
#[error("Error while parsing: {0}")]
ParsingError(String),
}
#[derive(Debug, PartialEq)]
impl From<Err<Error<&str>>> for MonkeyError {
fn from(value: Err<Error<&str>>) -> Self {
MonkeyError::ParsingError(value.to_string())
}
}
#[derive(Debug, PartialEq, Clone, Copy)]
enum Operation {
Plus(i64),
Times(i64),
Squared,
Times(i64),
Plus(i64),
}
impl Operation {
pub fn parse(line: &str) -> Operation {
match line.split_whitespace().collect::<Vec<_>>()[..] {
["*", "old"] => Operation::Squared,
["*", value] => Operation::Times(value.parse().unwrap()),
["+", value] => Operation::Plus(value.parse().unwrap()),
_ => unreachable!(),
}
fn parse(input: &str) -> IResult<&str, Self> {
let (input, _) = tag("new = old ")(input)?;
alt((
value(Operation::Squared, tag("* old")),
preceded(char('*'), trim0(i64.map(|a| Operation::Times(a)))),
preceded(char('+'), trim0(i64.map(|a| Operation::Plus(a)))),
))(input)
}
pub fn calc(&self, old: i64) -> i64 {
match self {
Operation::Plus(value) => old + *value,
Operation::Times(value) => old * value,
Operation::Squared => old.pow(2),
Operation::Times(value) => old * value,
Operation::Plus(value) => old + value,
}
}
}
@ -74,90 +84,96 @@ struct Monkey {
bad_monkey: usize,
}
static MONKEY: Lazy<Regex> = Lazy::new(|| Regex::new(r"Monkey (\d+)").unwrap());
static STARTING: Lazy<Regex> =
Lazy::new(|| Regex::new(r"Starting items: (\d+(?:, \d+)*)").unwrap());
static OP: Lazy<Regex> =
Lazy::new(|| Regex::new(r"Operation: new = old ([+*] \d+|\* old)").unwrap());
static TEST: Lazy<Regex> = Lazy::new(|| Regex::new(r"Test: divisible by (\d+)").unwrap());
static NEXT: Lazy<Regex> =
Lazy::new(|| Regex::new(r"If (?:true|false): throw to monkey (\d+)").unwrap());
impl Monkey {
fn parse_line<'a>(re: &Regex, line: &'a str) -> Result<&'a str, MonkeyError> {
let caps = re
.captures(line)
.ok_or(MonkeyError::UnknownLine(line.to_owned()))?;
Ok(caps.get(1).unwrap().as_str())
fn number_parse(input: &str) -> IResult<&str, usize> {
let input = ignore(tag("Monkey "))(input)?;
let (input, number) = trim0(u32.map(|val| val as usize))(input)?;
let input = ignore(char(':'))(input)?;
Ok((input, number))
}
pub fn parse_one(
iterator: &mut dyn Iterator<Item = &str>,
) -> Result<Option<Monkey>, MonkeyError> {
if let Some(line) = iterator.next() {
let number: usize = Monkey::parse_line(&MONKEY, line)?.parse()?;
fn starting_items_parse(input: &str) -> IResult<&str, Vec<i64>> {
let input = ignore(tag("Starting items:"))(input)?;
separated_list1(char(','), trim0(i64))(input)
}
let line = iterator.next().ok_or(MonkeyError::PrematureEndOfInput)?;
let start = Monkey::parse_line(&STARTING, line)?;
let items = start
.split(", ")
.map(|item| item.parse::<i64>())
.collect::<Result<Vec<i64>, _>>()?;
fn operation_parse(input: &str) -> IResult<&str, Operation> {
let input = ignore(tag("Operation:"))(input)?;
trim0(Operation::parse)(input)
}
let line = iterator.next().ok_or(MonkeyError::PrematureEndOfInput)?;
let operation = Operation::parse(Monkey::parse_line(&OP, line)?);
fn divisor_parse(input: &str) -> IResult<&str, i64> {
let input = ignore(tag("Test: divisible by"))(input)?;
trim0(i64)(input)
}
let line = iterator.next().ok_or(MonkeyError::PrematureEndOfInput)?;
let divisor: i64 = Monkey::parse_line(&TEST, line)?.parse()?;
fn target_parse(want_good: bool) -> impl FnMut(&str) -> IResult<&str, usize> {
move |input: &str| {
let input = ignore(tuple((
tag("If"),
trim_left1(verify(true_false, |is_good| *is_good == want_good)),
tag(": throw to monkey"),
)))(input)?;
trim0(u32.map(|val| val as usize))(input)
}
}
let line = iterator.next().ok_or(MonkeyError::PrematureEndOfInput)?;
let good_monkey: usize = Monkey::parse_line(&NEXT, line)?.parse()?;
fn parse(input: &str) -> IResult<&str, Monkey> {
let input = empty_lines(input)?;
let (input, number) = eol_terminated(trim0(Monkey::number_parse))(input)?;
let (input, items) = eol_terminated(trim0(Monkey::starting_items_parse))(input)?;
let (input, operation) = eol_terminated(trim0(Monkey::operation_parse))(input)?;
let (input, divisor) = eol_terminated(trim0(Monkey::divisor_parse))(input)?;
let (input, good_monkey) = eol_terminated(trim0(Monkey::target_parse(true)))(input)?;
let (input, bad_monkey) = eol_terminated(trim0(Monkey::target_parse(false)))(input)?;
let line = iterator.next().ok_or(MonkeyError::PrematureEndOfInput)?;
let bad_monkey: usize = Monkey::parse_line(&NEXT, line)?.parse()?;
iterator.next();
Ok(Some(Monkey {
Ok((
input,
Monkey {
number,
items,
operation,
divisor,
good_monkey,
bad_monkey,
}))
} else {
Ok(None)
}
},
))
}
#[inline]
pub fn process(&self, value: i64) -> i64 {
self.operation.calc(value)
}
#[inline]
pub fn check(&self, value: i64) -> bool {
value % self.divisor == 0
}
#[allow(dead_code)]
fn parse_one(lines: &str) -> Result<Monkey, MonkeyError> {
Ok(extract_result(Monkey::parse)(lines)?)
}
}
struct Troop {
monkeys: Vec<Monkey>,
}
impl TryFrom<&str> for Troop {
type Error = MonkeyError;
impl FromStr for Troop {
type Err = MonkeyError;
fn try_from(value: &str) -> Result<Self, Self::Error> {
let mut iter = split_lines(value);
let mut monkeys = Vec::new();
while let Some(monkey) = Monkey::parse_one(&mut iter)? {
monkeys.push(monkey);
}
Ok(Troop { monkeys })
fn from_str(lines: &str) -> Result<Self, Self::Err> {
Ok(extract_result(Troop::parse)(lines)?)
}
}
impl Troop {
fn parse(input: &str) -> IResult<&str, Troop> {
map(many1(Monkey::parse), |monkeys| Troop { monkeys })(input)
}
fn do_play<F>(&self, rounds: usize, alter: F) -> i64
where
F: Fn(i64) -> i64,
@ -204,7 +220,6 @@ mod test {
fn test_parse() -> Result<()> {
let day = Day {};
let lines = read_string(day.get_day_number(), "example01.txt")?;
let mut lines = split_lines(&lines);
let expected = Monkey {
number: 0,
items: vec![79, 98],
@ -213,7 +228,7 @@ mod test {
good_monkey: 2,
bad_monkey: 3,
};
let result = Monkey::parse_one(&mut lines)?.unwrap();
let result = Monkey::parse_one(&lines)?;
assert_eq!(result, expected);
Ok(())
@ -224,7 +239,7 @@ mod test {
let day = Day {};
let lines = read_string(day.get_day_number(), "example01.txt")?;
let expected = 4;
let result = Troop::try_from(lines.as_str())?;
let result: Troop = lines.parse()?;
assert_eq!(result.monkeys.len(), expected);
Ok(())

View file

@ -1,37 +1,44 @@
use super::template::{DayTrait, ResultType};
use crate::common::{file::split_lines, pos::Pos};
use crate::common::{
parser::{eol_terminated, extract_result, ignore, trim0},
pos::Pos,
};
use itertools::Itertools;
use once_cell::sync::Lazy;
use regex::Regex;
use std::{collections::HashSet, num::ParseIntError};
use nom::{
bytes::complete::tag,
character::complete::{char, i64},
error::Error,
multi::many0,
sequence::{separated_pair, tuple},
Err, IResult,
};
use std::collections::HashSet;
use thiserror::Error;
const DAY_NUMBER: usize = 15;
pub struct Day;
fn parse_row(input: &str) -> IResult<&str, i64> {
eol_terminated(i64)(input)
}
impl DayTrait for Day {
fn get_day_number(&self) -> usize {
DAY_NUMBER
}
fn part1(&self, lines: &str) -> anyhow::Result<ResultType> {
let lines = split_lines(lines).collect_vec();
if lines.len() < 2 {
Err(SensorError::ToFewLines)?;
}
let row = lines[0].parse()?;
let (sensors, beacons) = Day::parse_all(&lines[1..])?;
let (lines, row) = parse_row(lines).map_err(|_| SensorError::RowLineNotFound)?;
let (sensors, beacons) = Day::parse_all(lines)?;
let result = Day::count_coverage_at(&sensors, &beacons, row);
Ok(ResultType::Integer(result))
}
fn part2(&self, lines: &str) -> anyhow::Result<ResultType> {
let lines = split_lines(lines).collect_vec();
if lines.len() < 2 {
Err(SensorError::ToFewLines)?;
}
let (sensors, _) = Day::parse_all(&lines[1..])?;
let (lines, _) = parse_row(lines).map_err(|_| SensorError::RowLineNotFound)?;
let (sensors, _) = Day::parse_all(lines)?;
let mid_lines = Day::dividing_lines(&sensors);
let points = mid_lines
.iter()
@ -51,11 +58,12 @@ impl DayTrait for Day {
}
impl Day {
fn parse_all(lines: &[&str]) -> Result<(HashSet<Sensor>, HashSet<Pos<i64>>), SensorError> {
fn parse_all(lines: &str) -> Result<(HashSet<Sensor>, HashSet<Pos<i64>>), SensorError> {
let data = extract_result(many0(eol_terminated(Sensor::parse)))(lines)?;
let mut sensors = HashSet::new();
let mut beacons = HashSet::new();
for line in lines {
let (sensor, beacon) = Sensor::parse(line)?;
for (sensor, beacon) in data {
sensors.insert(sensor);
beacons.insert(beacon);
}
@ -91,17 +99,20 @@ impl Day {
#[derive(Debug, Error)]
enum SensorError {
#[error("Not an Integer")]
NotAnInt(#[from] ParseIntError),
#[error("Unknown line: {0}")]
UnknownLine(String),
#[error("Error Parsing Input: {0}")]
ParsingError(String),
#[error("Did not find exactly one point for the sensor: {0}")]
NotExactlyOneResult(usize),
#[error("Too few lines in input.txt")]
ToFewLines,
#[error("Row line not found at start of Input")]
RowLineNotFound,
}
impl From<Err<Error<&str>>> for SensorError {
fn from(error: Err<Error<&str>>) -> Self {
SensorError::ParsingError(error.to_string())
}
}
#[derive(Debug)]
@ -220,28 +231,30 @@ struct Sensor {
radius: i64,
}
static SENSOR: Lazy<Regex> =
Lazy::new(|| Regex::new(r"x=(-?\d+), y=(-?\d+).*x=(-?\d+), y=(-?\d+)").unwrap());
impl Sensor {
pub fn parse(line: &str) -> Result<(Sensor, Pos<i64>), SensorError> {
let caps = SENSOR
.captures(line)
.ok_or(SensorError::UnknownLine(line.to_owned()))?;
let sensor_x = caps.get(1).unwrap().as_str().parse()?;
let sensor_y = caps.get(2).unwrap().as_str().parse()?;
let beacon_x = caps.get(3).unwrap().as_str().parse()?;
let beacon_y = caps.get(4).unwrap().as_str().parse()?;
let sensor = Pos::new(sensor_x, sensor_y);
let beacon = Pos::new(beacon_x, beacon_y);
let radius = sensor.taxicab(&beacon);
Ok((
Sensor {
pos: sensor,
radius,
},
beacon,
))
fn component<'a>(name: &'a str) -> impl FnMut(&'a str) -> IResult<&'a str, i64> {
move |input: &str| {
let input = ignore(tuple((tag(name), char('='))))(input)?;
trim0(i64)(input)
}
}
fn parse_pos(input: &str) -> IResult<&str, Pos<i64>> {
let (input, (x, y)) = separated_pair(
Sensor::component("x"),
trim0(char(',')),
Sensor::component("y"),
)(input)?;
Ok((input, Pos::new(x, y)))
}
pub fn parse(input: &str) -> IResult<&str, (Sensor, Pos<i64>)> {
let input = ignore(tag("Sensor at"))(input)?;
let (input, pos) = trim0(Sensor::parse_pos)(input)?;
let input = ignore(tag(": closest beacon is at"))(input)?;
let (input, beacon) = trim0(Sensor::parse_pos)(input)?;
let radius = pos.taxicab(&beacon);
Ok((input, (Sensor { pos, radius }, beacon)))
}
pub fn range_at(&self, y: i64) -> Option<Range> {
@ -279,7 +292,7 @@ mod test {
},
Pos::new(-2, 15),
);
let result = Sensor::parse(input)?;
let result = extract_result(Sensor::parse)(input)?;
assert_eq!(result, expected);
Ok(())

View file

@ -1,17 +1,20 @@
use super::template::{DayTrait, ResultType};
use crate::common::parser::{eol_terminated, extract_result, ignore, trim0, trim1, trim_left1};
use itertools::Itertools;
use nom::{
branch::alt,
bytes::complete::tag,
character::complete::{alpha1, i64},
error::Error,
multi::{many1, separated_list1},
Err, IResult,
};
use std::collections::BinaryHeap;
use std::collections::HashMap;
use std::fmt::{Debug, Display};
use std::hash::Hash;
use std::iter::Sum;
use std::ops::{Add, Deref, Mul, Sub};
use std::{collections::BinaryHeap, num::ParseIntError};
use crate::common::file::split_lines;
use super::template::{DayTrait, ResultType};
use const_format::concatcp;
use itertools::Itertools;
use once_cell::sync::Lazy;
use regex::Regex;
use thiserror::Error;
const DAY_NUMBER: usize = 16;
@ -129,11 +132,8 @@ impl Deref for Index {
#[derive(Debug, Error)]
enum ValveError {
#[error("Not an Integer")]
NotAnInt(#[from] ParseIntError),
#[error("Not a valid description: {0}")]
CouldNotParse(String),
ParsingError(String),
#[error("Not a valid valve: {0}")]
ValveNotFound(String),
@ -142,17 +142,11 @@ enum ValveError {
NoOptimumFound,
}
const ID: &str = "[[:alpha:]]+";
const COMMON: &str = concatcp!("^Valve (?<id>", ID, r") has flow rate=(?<rate>\d+); ");
const PLURAL_STR: &str = concatcp!(
COMMON,
"tunnels lead to valves (?<exits>",
ID,
"(?:, ",
ID,
")+)$"
);
const SINGULAR_STR: &str = concatcp!(COMMON, "tunnel leads to valve (?<exits>", ID, ")$");
impl From<Err<Error<&str>>> for ValveError {
fn from(error: Err<Error<&str>>) -> Self {
ValveError::ParsingError(error.to_string())
}
}
#[derive(Debug, PartialEq, Eq, PartialOrd, Ord)]
struct RawValve<'a> {
@ -170,29 +164,45 @@ impl<'a> RawValve<'a> {
}
}
fn from_regex(regex: &Regex, line: &'a str) -> Option<Result<RawValve<'a>, ValveError>> {
regex.captures(line).map(|caps| {
match (caps.name("id"), caps.name("rate"), caps.name("exits")) {
(Some(id), Some(rate), Some(exits)) => {
let rate = rate.as_str().parse()?;
let neighbors = exits.as_str().split(",").map(|s| s.trim_start()).collect();
Ok(RawValve::create(id.as_str(), Flow(rate), neighbors))
fn parse_start(input: &str) -> IResult<&str, (&str, Flow)> {
let input = ignore(tag("Valve"))(input)?;
let (input, valve) = trim1(alpha1)(input)?;
let input = ignore(tag("has flow rate="))(input)?;
let (input, flow) = trim0(i64)(input)?;
let input = ignore(tag(";"))(input)?;
Ok((input, (valve, Flow(flow))))
}
_ => Err(ValveError::CouldNotParse(line.to_string())),
fn parse_singular(input: &str) -> IResult<&str, Vec<&str>> {
let input = ignore(tag("tunnel leads to valve "))(input)?;
let (input, valve) = alpha1(input)?;
Ok((input, vec![valve]))
}
})
fn parse_plural(input: &str) -> IResult<&str, Vec<&str>> {
let input = ignore(tag("tunnels lead to valves "))(input)?;
let (input, valve) = separated_list1(tag(","), trim0(alpha1))(input)?;
Ok((input, valve))
}
fn parse(input: &str) -> IResult<&str, RawValve> {
let (input, (id, flow)) = RawValve::parse_start(input)?;
let (input, neighbors) = trim_left1(eol_terminated(alt((
RawValve::parse_plural,
RawValve::parse_singular,
))))(input)?;
Ok((input, RawValve::create(id, flow, neighbors)))
}
}
impl<'a> TryFrom<&'a str> for RawValve<'a> {
type Error = ValveError;
fn try_from(value: &'a str) -> Result<RawValve<'a>, Self::Error> {
static PLURAL: Lazy<Regex> = Lazy::new(|| Regex::new(PLURAL_STR).unwrap());
static SINGULAR: Lazy<Regex> = Lazy::new(|| Regex::new(SINGULAR_STR).unwrap());
RawValve::from_regex(&PLURAL, value)
.or_else(|| RawValve::from_regex(&SINGULAR, value))
.unwrap_or_else(|| Err(ValveError::CouldNotParse(value.to_string())))
fn try_from(value: &'a str) -> Result<Self, Self::Error> {
Ok(extract_result(RawValve::parse)(value)?)
}
}
@ -267,9 +277,7 @@ struct ValveSystem {
impl ValveSystem {
fn build(lines: &str, start: &str) -> Result<ValveSystem, ValveError> {
let mut raw = split_lines(lines)
.map(|line| RawValve::try_from(line))
.collect::<Result<Vec<_>, _>>()?;
let mut raw = extract_result(many1(RawValve::parse))(lines)?;
raw.sort_unstable_by_key(|valve| valve.id);
let start_idx = raw
@ -931,7 +939,7 @@ mod test {
fn parse_plural() -> Result<()> {
let line = "Valve BB has flow rate=13; tunnels lead to valves CC, AA";
let expected = RawValve::create("BB", Flow(13), vec!["CC", "AA"]);
let result = RawValve::try_from(line)?;
let result = line.try_into()?;
assert_eq!(expected, result);
Ok(())
@ -941,7 +949,7 @@ mod test {
fn parse_singular() -> Result<()> {
let line = "Valve HH has flow rate=22; tunnel leads to valve GG";
let expected = RawValve::create("HH", Flow(22), vec!["GG"]);
let result = RawValve::try_from(line)?;
let result = line.try_into()?;
assert_eq!(expected, result);
Ok(())