added files
authorNilsForssen <forssennils@gmail.com>
Mon, 2 Oct 2023 06:53:22 +0000 (08:53 +0200)
committerNilsForssen <forssennils@gmail.com>
Mon, 2 Oct 2023 06:53:22 +0000 (08:53 +0200)
expression_node_test.cc [new file with mode: 0644]
notes.md
uppgift12/postfix.cc [new file with mode: 0644]
uppgift12/postfix_test.cc [new file with mode: 0644]
uppgift12/postifx.h [new file with mode: 0644]
uppgift12/token.cc [new file with mode: 0644]
uppgift12/token.h [new file with mode: 0644]
uppgift12/token_test.cc [new file with mode: 0644]
uppgift13.cc [new file with mode: 0644]

diff --git a/expression_node_test.cc b/expression_node_test.cc
new file mode 100644 (file)
index 0000000..ae64b55
--- /dev/null
@@ -0,0 +1,80 @@
+#include <limits>
+#include <cmath> // abs
+#include <memory>
+
+#include "catch.hpp"
+
+TEST_CASE("operand real")
+{
+  Operand* pi{ new Real{3.14} };
+
+  CHECK( pi->evaluate() == 3.14 );
+  CHECK( pi->postfix() == "3.140" );
+}
+
+#if 0 // Flytta ned denna rad för att aktivera nästa TEST_CASE
+
+TEST_CASE("addition")
+{
+  Node* pi = new Real{3.14};
+  Node* e = new Real{2.72};
+  Node* pluslr = new Addition{pi, e};
+  Node* plusrl = new Addition{e, pi};
+
+  CHECK( pluslr->evaluate() == 5.86 );
+  CHECK( plusrl->evaluate() == 5.86 );
+  CHECK( pluslr->postfix() == "3.140 2.720 +" );
+  CHECK( plusrl->postfix() == "2.720 3.140 +" );
+}
+
+// If catch report 0.42 != 0.42 you are likely to have a small
+// rounding error in some invisible decimal place. In such case you
+// can compare if the difference is small enough to consider two
+// doubles equal anyway.
+bool compare_equal(double a, double b)
+{
+    return std::abs(a - b) <= std::numeric_limits<double>::epsilon();
+}
+
+TEST_CASE("test case ignoring rounding errors")
+{
+  Node* a = new Real{0.01};
+  Node* b = new Real{0.09};
+  Node* plus = new Addition{a, b};
+
+  CHECK( abs(-0.1) == 0.1 ); // make sure we use correct "abs"
+  // CHECK( plus->evaluate() == 0.1 ); // Rounding error!!
+  CHECK( compare_equal(plus->evaluate(), 0.1) );
+}
+
+
+TEST_CASE("operand integer")
+{
+  Operand* i = new Integer{7};
+
+  CHECK( i->evaluate() == 7 );
+  CHECK( i->postfix() == "7" );
+}
+
+TEST_CASE("conversion to string")
+{
+    Node* a = new Addition{ new Subtraction{ new Real{1.0},
+                                             new Integer{1} },
+                            new Multiplication{ new Integer{2},
+                                                new Integer{5} }
+    };
+    SECTION("prefix")
+    {
+        CHECK( a->prefix() == "+ - 1.000 1 * 2 5" );
+    }
+    SECTION("infix")
+    {
+        CHECK( a->infix() == "( ( 1.000 - 1 ) + ( 2 * 5 ) )" );
+    }
+    SECTION("postfix")
+    {
+        CHECK( a->postfix() == "1.000 1 - 2 5 * +" );
+    }
+}
+
+#endif
index a09751fd3029f15ea22ecc3d3d97e39abd4b4c3e..7eb1931daf2a666010e830159589728f4ebf1e65 100644 (file)
--- a/notes.md
+++ b/notes.md
  7. Behöver inte veta någonting mer än att noden har en get_value funktion. 
 
 
+# Uppgift 2
+
+Diagram finns. https://app.smartdraw.com/editor.aspx?credID=-54147836&depoId=50100329&flags=128#depoId=50103329&credID=-54147836, forssennils2@gmail.com
+fråga om resultatnod /lövnoder/lövklasser. Behövs alla 9?
+
+
+
 class Equation {
     string result_var 
     expression (operator eller operand nod)
@@ -30,4 +37,4 @@ struct/class variabel {
     operand-nod värde;
 }
 
-sin(4)
\ No newline at end of file
+sin(4)
diff --git a/uppgift12/postfix.cc b/uppgift12/postfix.cc
new file mode 100644 (file)
index 0000000..a51f184
--- /dev/null
@@ -0,0 +1,129 @@
+#include "postfix.h"
+#include "token.h"
+
+#include <string>
+#include <vector>
+#include <stack>
+#include <map>
+#include <istream>
+
+#include <sstream>
+#include <algorithm>
+#include <iterator>
+
+// PUBLIC
+
+Postfix::Postfix(std::string const& infix_string) : expr{}
+{
+  std::istringstream is{infix_string};
+  expr = make_postfix(is);
+}
+    
+std::string Postfix::to_string() const
+{
+  std::ostringstream os;
+  std::copy( std::begin(expr), std::end(expr),
+             std::ostream_iterator<std::string>{os, " "});
+  return os.str();
+}
+
+Postfix::operator std::string() const
+{
+  return to_string();
+}
+
+bool Postfix::operator==(std::string const& rhs) const
+{
+  return to_string() == rhs;
+}
+
+
+// PRIVATE
+
+// right associative operators have input priority > stack priority  
+// left associative operators have input priority < stack priority
+const Postfix::priority_table Postfix::operator_table {
+  // {symbol, input prio, stack prio}
+  {"^", {8, 7}},
+  {"*", {5, 6}},
+  {"/", {5, 6}},
+  {"%", {5, 6}},
+  {"+", {3, 4}},
+  {"-", {3, 4}},
+  {"=", {2, 1}}
+};
+
+bool Postfix::is_operator(const std::string& token)
+{
+  // C++20: return operator_table.contains( token );
+  return ( operator_table.count( token ) > 0 );
+}
+
+Postfix::expression Postfix::make_postfix(std::istream& is, bool match_parenthesis) const
+{
+  using namespace std::literals; // for string literals ""s
+  
+  op_stack operator_stack;
+  expression postfix;
+  Token token;
+  int unused_operands{0};
+
+  while (is >> token && token != ")"s )
+  {
+    if ( is_operator(token) )
+    {
+      while ( ! operator_stack.empty() && 
+              operator_table.at(token).input <=
+              operator_table.at(operator_stack.top()).stack )
+      {
+        postfix.push_back( operator_stack.top() );
+        unused_operands -= 1; // uses 2 but creates 1
+        operator_stack.pop();
+      }
+      operator_stack.push(token);
+    }
+    else if (token == "("s)
+    {
+      expression parentesis{ make_postfix(is, true) };
+      std::copy( std::begin(parentesis), std::end(parentesis), std::back_inserter(postfix) );
+      unused_operands += 1; // entire postix is 1 operand
+    }
+    else
+    {
+      postfix.push_back( token );
+      unused_operands += 1;
+    }
+  }
+
+  if ( postfix.empty() && match_parenthesis && token == ")"s )
+  {
+    throw Infix_Error{"Empty parenthesis"};
+  }
+
+  if ( match_parenthesis && token != ")"s )
+  {
+    throw Infix_Error{"Missing ending parenthesis"};
+  }
+    
+  if ( ! match_parenthesis && token == ")"s )
+  {
+    throw Infix_Error{"Missing starting parenthesis"};
+  }
+    
+  while ( ! operator_stack.empty() )
+  {
+    postfix.push_back( operator_stack.top() );
+    unused_operands -= 1; // uses 2 but creates 1
+    operator_stack.pop();
+  }
+
+  if ( unused_operands != 1 ) // the last one is the answer
+  {
+    if ( unused_operands > 1 )
+      throw Infix_Error{"Missing operator"};
+    else
+      throw Infix_Error{"Missing operand"};
+  }
+    
+  return postfix;
+}
diff --git a/uppgift12/postfix_test.cc b/uppgift12/postfix_test.cc
new file mode 100644 (file)
index 0000000..660aab1
--- /dev/null
@@ -0,0 +1,67 @@
+#define CATCH_CONFIG_RUNNER
+#include "catch.hpp"
+
+#include <iostream>
+#include <sstream>
+
+#include "postfix.h"
+
+using namespace std;
+
+TEST_CASE("infix to postfix conversion")
+{
+  CHECK( Postfix{"1"} == "1 ");
+
+  CHECK( Postfix{"1 + 2"} == "1 2 + ");
+  
+  CHECK( Postfix{"1 + 2 + 3"} == "1 2 + 3 + ");
+  CHECK( Postfix{"1 + 2 * 3"} == "1 2 3 * + ");
+  CHECK( Postfix{"1 * 2 + 3"} == "1 2 * 3 + ");
+  CHECK( Postfix{"1 ^ 2 ^ 3"} == "1 2 3 ^ ^ ");
+  CHECK( Postfix{"1 - 2 - 3"} == "1 2 - 3 - ");
+  CHECK( Postfix{"1 - 2 + 3"} == "1 2 - 3 + ");
+
+  CHECK( Postfix{"1 / 2 - 3 * 4"} == "1 2 / 3 4 * - ");
+  
+  CHECK( Postfix{"( ( 0 ) )"} == "0 ");
+  
+  CHECK( Postfix{"( 1 ^ 2 ) ^ 3"} == "1 2 ^ 3 ^ ");
+  CHECK( Postfix{"1 ^ ( 2 ^ 3 )"} == "1 2 3 ^ ^ ");
+  CHECK( Postfix{"( 1 ^ ( 2 ^ 3 ) )"} == "1 2 3 ^ ^ ");
+  CHECK( Postfix{"( ( 1 ^ ( 2 ) ^ 3 ) )"} == "1 2 3 ^ ^ ");
+
+  CHECK( Postfix{"1 + 2 * 3 - 4 * 5 + 6 / 7 - 8"} == "1 2 3 * + 4 5 * - 6 7 / + 8 - ");
+  CHECK( Postfix{"( 1 + 2 ) * 3 - ( 4 * 5 + 6 ) / 7 - 8"} == "1 2 + 3 * 4 5 * 6 + 7 / - 8 - ");
+
+//  CHECK( Postfix{"(1+2)*3-(4*5+6)/7-8"} == "1 2 + 3 * 4 5 * 6 + 7 / - 8 - ");
+}
+
+TEST_CASE("infix to postfix conversion problems")
+{
+  // Missing operands (empty infix)
+  CHECK_THROWS( Postfix("") );
+  
+  // Missing operands (empty paranthesis)
+  CHECK_THROWS( Postfix("( )") );
+  
+  // Missing operands
+  CHECK_THROWS( Postfix("1 +") );
+  CHECK_THROWS( Postfix("+ 2") );
+
+  // Missing operator
+  CHECK_THROWS( Postfix("1 1 - 1") );
+  
+  // Missing parenthesis
+  CHECK_THROWS( Postfix("( ) + 2") );
+  CHECK_THROWS( Postfix("( + 2") );
+  CHECK_THROWS( Postfix("1 + 2 )") );
+}
+
+int main()
+{
+  Catch::Session session;
+
+  session.run();
+  
+  return 0;
+}
diff --git a/uppgift12/postifx.h b/uppgift12/postifx.h
new file mode 100644 (file)
index 0000000..3d3a69c
--- /dev/null
@@ -0,0 +1,48 @@
+#ifndef POSTFIX_H
+#define POSTFIX_H
+
+#include <string>
+#include <vector>
+#include <stack>
+#include <map>
+#include <istream>
+#include <stdexcept>
+
+class Infix_Error : public std::logic_error
+{
+    using std::logic_error::logic_error;
+};
+
+// Creates a Postfix from a given infix string. All operands and
+// operators are required to be separated by at least one space.
+class Postfix
+{
+public:
+  Postfix(std::string const& infix_string);
+    
+  std::string to_string() const;
+  operator std::string() const;
+  bool operator==(std::string const& rhs) const;
+  
+private:
+  using op_stack = std::stack<std::string>;
+  using expression = std::vector<std::string>;
+  
+  expression expr;
+  
+  struct priority
+  {
+    int input;
+    int stack;
+  };
+  
+  using priority_table = std::map<std::string, Postfix::priority>;
+  static const priority_table operator_table;
+
+  static bool is_operator(const std::string& token);
+  
+  expression make_postfix(std::istream& is, bool match_parenthesis = false) const;
+};
+
+#endif
diff --git a/uppgift12/token.cc b/uppgift12/token.cc
new file mode 100644 (file)
index 0000000..65db2cd
--- /dev/null
@@ -0,0 +1,163 @@
+#include <iostream>
+#include <iomanip>
+#include <string>
+#include <set>
+#include <algorithm>
+#include <cctype>
+
+#include "token.h"
+
+using namespace std;
+
+// Public
+
+set<string> const Token::default_operators{
+  "+", "-", "*", "/", "%", "^", "=", "(", ")", "?", "£", "$"
+    };
+
+string const Token::default_separators{" \t\n\r"};
+
+Token::Token(set<string> const& ops, string const& sep)
+  : operators{ops}, separators{sep}, token{}
+{
+}
+
+istream& operator>>(istream& iss, Token& t)
+{
+  return t.next(iss);
+}
+
+ostream& operator<<(ostream& oss, Token const& t)
+{
+  return oss << t.token;
+}
+
+bool Token::is_operator() const
+{
+  return operators.count( token ) == 1;
+}
+
+bool Token::is_integer() const
+{
+  return all_of(token.begin(), token.end(), ::isdigit);
+}
+
+bool Token::is_decimal() const
+{
+  bool valid_chars = all_of(token.begin(), token.end(), [](char c)->bool
+                            {
+                              return c == '.' || isdigit(c);
+                            });
+  bool one_dot = count(token.begin(), token.end(), '.') == 1;
+  return valid_chars && one_dot;
+}
+
+bool Token::is_identifier() const
+{
+  return all_of(token.begin(), token.end(), [](char c)->bool
+                {
+                  return c == '_' || isalnum(c);
+                });
+}
+
+// Private
+
+bool Token::is_separator(int c) const
+{
+  return ( separators.find(c) != string::npos );
+}
+
+bool Token::is_delimeter(int c) const
+{
+  if ( c == -1 ) // Traits::eof()
+    return true;
+  
+  if ( is_separator( c ) )
+    return true;
+  
+  auto b{ operators.begin() };
+  auto e{ operators.end() };
+  
+  for ( ; b != e; ++b )
+  {
+    if ( b->at(0) == c )
+      return true;
+  }
+  return false;
+}
+
+bool Token::is_candidate() const
+{
+  auto b{ operators.begin() };
+  auto e{ operators.end() };
+
+  for ( ; b != e; ++b )
+  {
+    if ( b->find( token ) == 0 && *b != token )
+      return true;
+  }
+  return false;
+}
+
+void Token::append(int c)
+{
+  token.push_back( static_cast<char>(c) );
+}
+
+void Token::ignore_separators(istream& iss)
+{
+  auto c{ iss.peek() };
+
+  while ( c != -1 ) // Traits::eof()
+  {
+    if ( ! is_separator( c ) )
+      return;
+    
+    iss.get();
+    c = iss.peek();
+  }
+}
+
+istream& Token::next(istream& iss)
+{
+  token.clear();
+
+  ignore_separators( iss );
+  
+  auto c{ iss.peek() };
+
+  bool prev_is_op{false};
+  
+  while ( c != -1 ) // Traits::eof()
+  {
+    append( c );
+
+    bool is_op{ is_operator() };
+    bool is_can{ is_candidate() };
+    bool is_del{ is_delimeter(c) };
+    
+    if ( is_op && ! is_can )
+    {
+      iss.get();
+      return iss;
+    }
+
+    if ( prev_is_op && ! is_op && ! is_can )
+    {
+      token.pop_back();
+      return iss;
+    }
+    
+    if ( is_del && ! is_can )
+    {
+      token.pop_back();
+      return iss;
+    }
+
+    prev_is_op = is_op;
+    iss.get();
+    c = iss.peek();
+  }
+  
+  return iss;
+}
diff --git a/uppgift12/token.h b/uppgift12/token.h
new file mode 100644 (file)
index 0000000..35bfd0e
--- /dev/null
@@ -0,0 +1,78 @@
+#ifndef TOKEN_H
+#define TOKEN_H
+
+#include <iostream>
+#include <string>
+#include <set>
+
+// A class to read strings from an istream just like std::string,
+// but tokens are separated not only by space, but also by operators.
+//
+// You should be able to just change from "std::string" to "Token" in
+// your program unless you do something special with your strings.
+//
+// Tokens can be classified as integers, deciamls, identifiers or operators.
+//
+class Token
+{
+public:
+
+  // interoperability with standard library (STL)
+  using token_t = std::string;
+  
+  using value_type = token_t::value_type;
+  using size_type = token_t::size_type;
+  using pointer = token_t::pointer;
+  using reference = token_t::reference;
+  using iterator = token_t::const_iterator;
+
+  iterator begin() const { return token.begin(); }
+  iterator end() const { return token.end(); }
+  size_type size() const { return token.size(); }
+  size_type length() const { return token.length(); }
+  value_type at(int i) const { return token.at(i); }
+
+  // static constants for default values
+  static std::set<std::string> const default_operators;
+  static std::string const default_separators;
+
+  // public members
+  Token(std::set<std::string> const& operators = default_operators,
+        std::string const& separators = default_separators);
+
+  friend std::istream& operator>>(std::istream& iss, Token& t);
+  
+  friend std::ostream& operator<<(std::ostream& oss, Token const& t);
+
+  operator std::string() const { return token; }
+
+  bool operator==(std::string const& rhs) { return token == rhs; }
+  bool operator!=(std::string const& rhs) { return token != rhs; }
+  
+  bool is_operator() const;
+  bool is_integer() const;
+  bool is_decimal() const;
+  bool is_identifier() const;
+  
+private:
+
+  // help functions
+  bool is_delimeter(int c) const;
+  bool is_separator(int c) const;
+
+  bool is_candidate() const;
+
+  void append(int c);
+  void ignore_separators(std::istream& iss);
+  
+  std::istream& next(std::istream& iss);
+
+  // data members
+  std::set<std::string> const& operators;
+  std::string const& separators;
+  std::string token;
+
+  friend class Token_Private_Tester;
+};
+
+#endif
diff --git a/uppgift12/token_test.cc b/uppgift12/token_test.cc
new file mode 100644 (file)
index 0000000..39cc98e
--- /dev/null
@@ -0,0 +1,146 @@
+#define CATCH_CONFIG_RUNNER
+#include "catch.hpp"
+
+#include <iostream>
+#include <sstream>
+
+#include "token.h"
+
+using namespace std;
+
+const std::set<std::string> my_operators{
+  "+", "+-+", "-", "*", "/", "%", "^", "=", "==", "(", ")", "?", "£", "$", "####"
+    };
+
+class Token_Private_Tester
+{
+public:
+  Token_Private_Tester(Token& t) : t{t} {}
+  
+  bool is_delimeter(int c) const { return t.is_delimeter(c); }
+  bool is_separator(int c) const { return t.is_separator(c); }
+
+  bool is_candidate() const { return t.is_candidate(); }
+
+  bool check_candidate(string const& s) { t.token = s; return t.is_candidate(); }
+
+  Token& set(string const& s) { t.token = s; return t; }
+  
+private:
+  Token& t;
+};
+
+TEST_CASE("help functions")
+{
+  Token tok{my_operators};
+  Token_Private_Tester t{tok};
+
+  CHECK( t.is_delimeter('+') );
+  CHECK( t.is_delimeter('#') );
+  CHECK( t.is_delimeter(' ') );
+  CHECK_FALSE( t.is_delimeter('.') );
+  
+  CHECK( t.is_separator(' ') );
+  CHECK_FALSE( t.is_separator('0') );
+  
+  CHECK(       t.check_candidate("+") );
+  CHECK(       t.check_candidate("+-") );
+  CHECK_FALSE( t.check_candidate("+-+") );
+  CHECK_FALSE( t.check_candidate("+-++") );
+
+}
+
+TEST_CASE("classifier functions")
+{
+  Token tok{my_operators};
+  Token_Private_Tester t{tok};
+  
+  CHECK( t.set("+").is_operator() );
+  CHECK_FALSE( t.set("+-").is_operator() );
+  CHECK( t.set("+-+").is_operator() );
+  CHECK_FALSE( t.set("+-++").is_operator() );
+  
+  CHECK( t.set("123").is_integer() );
+  CHECK( t.set("3.14").is_decimal() );
+  CHECK( t.set("x_34").is_identifier() );
+
+  CHECK_FALSE( t.set("123a").is_integer() );
+  CHECK_FALSE( t.set("3.14.").is_decimal() );
+  CHECK_FALSE( t.set("=x_34").is_identifier() );
+}
+
+TEST_CASE("normal input output")
+{
+  Token t{my_operators};
+  istringstream iss{"1+2*34-sfd"};
+  ostringstream oss{};
+  
+  while ( iss >> t )
+  {
+    oss << t << " ";
+  }
+  CHECK( oss.str() == "1 + 2 * 34 - sfd ");
+}
+
+TEST_CASE("normal input output, default operators")
+{
+  Token t;
+  istringstream iss{"1+2*34-sfd"};
+  ostringstream oss{};
+  
+  while ( iss >> t )
+  {
+    oss << t << " ";
+  }
+  CHECK( oss.str() == "1 + 2 * 34 - sfd ");
+}
+
+TEST_CASE("multichar operator input output")
+{
+  Token t{my_operators};
+  istringstream iss{"1+-++2===as#####fgh"};
+  ostringstream oss{};
+  
+  while ( iss >> t )
+  {
+    oss << t << " ";
+  }
+  CHECK( oss.str() == "1 +-+ + 2 == = as #### #fgh ");
+}
+
+int main(int argc, char* argv[] __attribute__((unused)))
+{
+  Catch::Session session;
+
+  session.run();
+
+   
+  Token t{my_operators};
+  string s;
+
+  if ( argc <= 1 )
+    return 0;
+  
+  cout << "Manual test mode, press Ctrl-D to exit. "
+       << "Please enter lines to split: " << endl;
+  while ( getline(cin, s) )
+  {
+    istringstream iss{s};
+    
+    while ( iss >> t )
+    {
+      if ( t.is_operator() )
+        cout << "op: '" << t << "', ";
+      else if ( t.is_integer() )
+        cout << "int: '" << t << "', ";
+      else if ( t.is_decimal() )
+        cout << "double: '" << t << "', ";
+      else if ( t.is_identifier() )
+        cout << "var: '" << t << "', ";
+      else
+        cout << "token: '" << t << "', ";
+    }
+    cout << endl;
+  }
+  return 0;
+}
diff --git a/uppgift13.cc b/uppgift13.cc
new file mode 100644 (file)
index 0000000..fb82a08
--- /dev/null
@@ -0,0 +1,15 @@
+#include <iostream>
+
+using namespace std;
+
+int main()
+{
+  string line;
+  while ( getline(cin, line) )
+  {
+    Expression e{line};
+    cout << e.to_string() << endl
+         << e.evaluate() << endl;
+  }
+  return 0;
+}