From 37fe1635d08c5e3eb043031a37c4ec77226b3411 Mon Sep 17 00:00:00 2001 From: Lukas Kalbertodt Date: Mon, 21 Nov 2016 22:08:41 +0100 Subject: [PATCH] Add sheet4 solution --- .gitignore | 1 + aufgaben/sheet4/sol1/good.rs | 57 ++++ aufgaben/sheet4/sol2/calculator.rs | 450 +++++++++++++++++++++++++++++ 3 files changed, 508 insertions(+) create mode 100755 .gitignore create mode 100755 aufgaben/sheet4/sol1/good.rs create mode 100755 aufgaben/sheet4/sol2/calculator.rs diff --git a/.gitignore b/.gitignore new file mode 100755 index 0000000..b883f1f --- /dev/null +++ b/.gitignore @@ -0,0 +1 @@ +*.exe diff --git a/aufgaben/sheet4/sol1/good.rs b/aufgaben/sheet4/sol1/good.rs new file mode 100755 index 0000000..57c2d91 --- /dev/null +++ b/aufgaben/sheet4/sol1/good.rs @@ -0,0 +1,57 @@ +/// Prints all happy primes between 1 and 20. +fn main() { + for i in 1..21 { + if is_happy_prime(i) { + println!("{} is a happy prime!", i); + } + } +} + +/// Determines whether the given number is a happy number AND a prime number. +fn is_happy_prime(n: u64) -> bool { + is_happy(n) && is_prime(n) +} + +/// Determines whether the given, positive number is a [happy number][1]. +/// +/// [1]: https://en.wikipedia.org/wiki/Happy_number +fn is_happy(mut number: u64) -> bool { + // Either we end as "1" or in a cycle + while number > 1 { + number = { + // Here we compute the sum of squares of all digits. The trick is that + // the last digit is `number % 10` and that we can remove the last + // digit by dividing number by 10. + let mut sum = 0; + while number > 0 { + let digit = number % 10; + sum += digit * digit; + number /= 10; + } + + sum + }; + + // We ended up in a cycle -> not happy + if number == 4 { + return false; + } + } + + true +} + +/// Determines whether the given, positive, non-zero number is a prime number. +fn is_prime(n: u64) -> bool { + if n == 1 { + return false; + } + + for divisor in 2..n { + if n % divisor == 0 { + return false; + } + } + + true +} diff --git a/aufgaben/sheet4/sol2/calculator.rs b/aufgaben/sheet4/sol2/calculator.rs new file mode 100755 index 0000000..b604886 --- /dev/null +++ b/aufgaben/sheet4/sol2/calculator.rs @@ -0,0 +1,450 @@ +//! A simple command-line calculator +//! + +/// The integer type we use internally to hold the numbers. +type Integer = i64; + +/// Represents an operation our calculator can carry out. +#[derive(Debug, Clone, Copy, PartialEq)] +enum Op { + Add, + Subtract, +} + +impl Op { + /// The operation is applied to the given `lhs` and `rhs`. If an overflow + /// occured during the operation, None is returned. + fn apply(&self, lhs: Integer, rhs: Integer) -> Option { + match *self { + Op::Add => lhs.checked_add(rhs), + Op::Subtract => lhs.checked_sub(rhs), + } + } +} + +/// Represents a (part of a) calculation. +#[derive(Debug, PartialEq)] +enum Expr { + /// Just a literal number (no calculation needed) + Literal(Integer), + + /// An operation with two operands + Op { + /// Kind of operation + operator: Op, + + /// The Operands. + /// + /// The task said the operands should be saved as `Vec` + /// and it would be fine to hand in such a solution, but a vector + /// is actually the wrong type here. We know that we always have + /// exactly two operands, so a type that can hold any number of items + /// is not correct. + /// + /// The correct type is a pair or an array with two elements. But this + /// will lead to a problem: fields are saved inside of the type and + /// therefore it's not possible to have a recursive definition. The + /// only way to do it, is to introduce one layer of indirection. A + /// vector is such a layer, because it saves its contents on the heap. + /// + /// A better choice is `Box`. As a vector, it stores its content + /// on the heap and also owns the content. But unlike the vector, a box + /// always saves one element only. + operands: Box<[Expr; 2]>, + } +} + +/// Stuff that can go wrong during parsing. Of course, it's desirable to have +/// even more information about an error, but this is not in the scope of this +/// task. Apart from that, good error reporting is hard. That's why so many +/// compiler (especially a few years ago) suck at it. +#[derive(Debug, Clone, Copy)] +enum ParseError { + /// Empty input + Empty, + + /// A token that was not expected at that position in the input + UnexpectedToken(Token), + + /// More tokens were expected + UnexpectedEof, + + /// There are unmatched parenthesis + UnmatchedParens, + + /// More tokens in the input found, although none were expected + UnexpectedAdditionalTokens, +} + +impl Expr { + /// This function parses a list of tokens and returns the corresponding + /// expression tree, if the parse was successful. The grammar for our + /// expressions is: + /// + /// ``` + /// expr := ⟨operand⟩ [⟨op⟩ ⟨operand⟩] + /// operand := ⟨num⟩ | "(" ⟨expr⟩ ")" + /// op := "+" | "-" + /// num := "0" | "1" | ... | "9" + /// ``` + /// + /// Parsing is difficult. Parsing mathematical infix notation like this can + /// be done with the Shunting-yard algorithm [1]. But more general parsing + /// algorithms to parse determinstic context-free grammars can be used, + /// too. Recursive descent parsers are probably rather similar to the way + /// humans think about a grammar. + /// + /// This implementation does not implement a concrete algorithm (as far as + /// I know). + /// + /// [1]: https://en.wikipedia.org/wiki/Shunting-yard_algorithm + fn parse(tokens: &[Token]) -> Result { + /// Parses an operand: + /// + /// ``` + /// operand := ⟨num⟩ | "(" ⟨expr⟩ ")" + /// ``` + /// + /// Note that it's usually nicer to build subslices of a slice instead + /// of passing indices manually. But in this case it makes sense to + /// work with "global" indices, because this function also returns an + /// index into the slice. + fn parse_operand(tokens: &[Token], start: usize) + -> Result<(Expr, usize), ParseError> + { + // At this point, we expect an operand, so there need to be some + // tokens left + if start >= tokens.len() { + return Err(ParseError::UnexpectedEof); + } + + match tokens[start] { + // The operand is just a literal number + Token::Number(n) => Ok((Expr::Literal(n), start + 1)), + + // The operand is some operation + Token::ParenOpen => { + match parse_expr(tokens, start + 1) { + Err(e) => Err(e), + Ok((expr, after_expr)) => { + // We expect the closing paren after the parsed + // expression + if tokens.get(after_expr) != Some(&Token::ParenClose) { + return Err(ParseError::UnmatchedParens); + } + Ok((expr, after_expr + 1)) + } + } + } + + // An operand starts either with a number or an opening paren + tok => Err(ParseError::UnexpectedToken(tok)), + } + } + + /// Parses an expression: + /// + /// ``` + /// expr := ⟨operand⟩ [⟨op⟩ ⟨operand⟩] + /// ``` + /// + fn parse_expr(tokens: &[Token], start: usize) + -> Result<(Expr, usize), ParseError> + { + // First: parse the left hand side (lhs). + let (lhs, after_lhs) = match parse_operand(tokens, start) { + Err(e) => return Err(e), + Ok(tuple) => tuple, + }; + + // If the lhs consumed all of our tokens, the LHS is the only + // expression -> return it. Otherwise we expect an operator. + let op = match tokens.get(after_lhs) { + Some(&Token::Plus) => Op::Add, + Some(&Token::Minus) => Op::Subtract, + _ => return Ok((lhs, after_lhs)), + }; + + // Parse the rhs. + let (rhs, after_rhs) = match parse_operand(tokens, after_lhs + 1) { + Err(e) => return Err(e), + Ok(tuple) => tuple, + }; + + Ok(( + Expr::Op { + operator: op, + operands: Box::new([lhs, rhs]), + }, + after_rhs, + )) + } + + + // Check for special case: no tokens. + if tokens.is_empty() { + return Err(ParseError::Empty); + } + + // Start parsing the token stream as ⟨expr⟩ + let (expr, after_expr) = match parse_expr(&tokens, 0){ + Err(e) => return Err(e), + Ok(t) => t, + }; + + // Check if tokens are left (shouldn't happen) and return an error + // if that is the case. + if after_expr < tokens.len() { + return Err(ParseError::UnexpectedAdditionalTokens); + } + + Ok(expr) + } + + /// Evaluates the expression tree to calculate the final result. If an + /// overflow occured, None is returned. + fn evaluate(&self) -> Option { + match *self { + Expr::Literal(value) => Some(value), + Expr::Op { operator, ref operands } => { + let lhs = match operands[0].evaluate() { + None => return None, + Some(lhs) => lhs, + }; + + let rhs = match operands[1].evaluate() { + None => return None, + Some(rhs) => rhs, + }; + + operator.apply(lhs, rhs) + } + } + } +} + +/// A token in the input stream. +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +enum Token { + Plus, + Minus, + ParenOpen, + ParenClose, + Number(Integer), +} + +#[derive(Debug, Clone, Copy)] +enum LexError { + InvalidChar(char), + Overflow, +} + +/// Tokenizes the string and returns a list of tokens, if the input is valid. +fn tokenize(input: &str) -> Result, LexError> { + let mut out = Vec::new(); + let mut current_number = String::new(); + + for c in input.chars() { + // Check if we were reading a number and reached the end of it + if !current_number.is_empty() && !(c >= '0' && c <= '9') { + let num: Integer = match current_number.parse() { + // The string contains valid digits only, so "overflow" is the + // only reason `parse()` would error. + Err(_) => return Err(LexError::Overflow), + Ok(v) => v, + }; + out.push(Token::Number(num)); + current_number.clear(); + } + + let token = match c { + '+' => Token::Plus, + '-' => Token::Minus, + '(' => Token::ParenOpen, + ')' => Token::ParenClose, + c @ '0' ... '9' => { + // We do not yet generate a token, but only push the char on + // the number buffer + current_number.push(c); + continue; + }, + c if c.is_whitespace() => continue, + c => return Err(LexError::InvalidChar(c)), + }; + out.push(token); + } + + // If the last token is a number, we still have to push it + if !current_number.is_empty() { + let num: Integer = match current_number.parse() { + // The string contains valid digits only, so "overflow" is the + // only reason `parse()` would error. + Err(_) => return Err(LexError::Overflow), + Ok(v) => v, + }; + out.push(Token::Number(num)); + } + + Ok(out) +} + +fn main() { + loop { + // Read input from the user and just do nothing when the input is empty + let input = read_string(); + if input.is_empty() { + continue; + } + + // Try to tokenize the input. If it fails, print error message and + // start anew. + let tokens = match tokenize(&input) { + Err(e) => { + println!("{:?}", e); + continue; + } + Ok(tokens) => tokens, + }; + + // Debug output + // println!("{:?}", tokens); + + // Try to parse the tokenstream into an expression. If it fails, + // print the error and start anew. + let expr = match Expr::parse(&tokens) { + Err(e) => { + println!("error: {:?}", e); + continue; + } + Ok(expr) => expr, + }; + + // Debug output + // println!("{:?}", expr); + + // Evaluate the expression and print the result + match expr.evaluate() { + None => println!("Overflow occured!"), + Some(res) => println!("{}", res), + } + } +} + + +/// Reads a string from the user (with a nice prompt). +fn read_string() -> String { + use std::io::Write; + + // Print prompt + print!("calc > "); + std::io::stdout().flush().unwrap(); + + // Read line + let mut buffer = String::new(); + std::io::stdin() + .read_line(&mut buffer) + .expect("something went horribly wrong..."); + + // Discard trailing newline + let new_len = buffer.trim_right().len(); + buffer.truncate(new_len); + + buffer +} + + +#[test] +fn parser_valid() { + use Token::*; + + // "3" + assert_eq!( + Expr::parse(&[Number(3)]).unwrap(), + Expr::Literal(3) + ); + + // "(3)" + assert_eq!( + Expr::parse(&[ParenOpen, Number(3), ParenClose]).unwrap(), + Expr::Literal(3) + ); + + // "3 + 4" + assert_eq!( + Expr::parse(&[Number(3), Plus, Number(4)]).unwrap(), + Expr::Op { + operator: Op::Add, + operands: Box::new([ + Expr::Literal(3), + Expr::Literal(4), + ]), + } + ); + + // "(3 + 4)" + assert_eq!( + Expr::parse(&[ParenOpen, Number(3), Plus, Number(4), ParenClose]).unwrap(), + Expr::Op { + operator: Op::Add, + operands: Box::new([ + Expr::Literal(3), + Expr::Literal(4), + ]), + } + ); + + // "(3) + 4" + assert_eq!( + Expr::parse(&[ParenOpen, Number(3), ParenClose, Plus, Number(4)]).unwrap(), + Expr::Op { + operator: Op::Add, + operands: Box::new([ + Expr::Literal(3), + Expr::Literal(4), + ]), + } + ); + + // "(3 - 7) + 4" + assert_eq!( + Expr::parse(&[ + ParenOpen, Number(3), Minus, Number(7), ParenClose, Plus, Number(4) + ]).unwrap(), + Expr::Op { + operator: Op::Add, + operands: Box::new([ + Expr::Op { + operator: Op::Subtract, + operands: Box::new([ + Expr::Literal(3), + Expr::Literal(7), + ]), + }, + Expr::Literal(4), + ]), + } + ); +} + +#[test] +fn parser_invalid() { + use Token::*; + + // "" + assert!(Expr::parse(&[]).is_err()); + + // "-" + assert!(Expr::parse(&[Minus]).is_err()); + + // "1 +" + assert!(Expr::parse(&[Number(1), Plus]).is_err()); + + // "1 + 2 + 3" + assert!(Expr::parse(&[Number(1), Plus, Number(2), Plus, Number(3)]).is_err()); + + // "(1" + assert!(Expr::parse(&[ParenOpen, Number(1)]).is_err()); + + // "1)" + assert!(Expr::parse(&[Number(1), ParenClose]).is_err()); +}