RPN (Reverse Polish Notation) or Postfix Notation, is a form of notation in which we write mathematical expressions beginning with the operands or numbers of the expression, ending with their operators.
For example, we would write 2+2
, which is in inline notation, as 2 2 +
, in
Postfix Notation.
For more complex expressions, we don't use parentheses to denote order of operations as in with normal inline notation. To compute postfix expressions, we follow along a specific algorithms using a stack.
Inline Expressions are converted into RPN using the Stunting Yard Algorithm.
A stack is a type of ordered collection, where values are put in a piling order, ie, following FILO principle. Imagine a pile of books on a desk. The first book at the bottom would be the last you would get to, going top down to inspect every book.
Stacks across programming languages have some common operatoins.
You can pop, ie remove, the top level of a stack. In some languages, pop would also read out the value to you. In the context of C++, these are two distinct operations.
You can push, ie insert values onto the top level succesively.
In C++, you can call a stack Data Type from the Standard Library.
Take the case of an RPN expressoin, 1 2 3 + -
To compute this expressions, you read from left to right.
Imagine you have an empty stack, where you can put in the values. Reading from left to write, if you see an integral or numerical value, you put the numbers in the stack. So, from the bottom up (remember, the values at the bottom level is accessed last), you stack would read;
|------|---|
|top | 3 |
|......| 2 |
|bottom| 1 |
------------
At which point, we come to the operators. When we come across the first operator, which is the + sign, we expel the top two operands- the numbers to be operated on, from the stack. So, we would take out first 3, then 2, subsequently performing the operatoin 3+2. The result is 5, and we put it back in the stack. We repeat this with the - operator. We get the top two values in the stack, being 5 and 1 repectively. 1-5 computed is -4. This is our final result.
At the end of postfix notation, there are no more operators left, and we have one value, the result, left on stack.
The code is separated into 5 files. The script running the calculator is in the main.cpp file.
With a C++ compiler, such as GCC or Clang, compile the individual files *.cpp in the path ./src/
in terminal. For this, I assume to be using a GCC compiler already in PATH on a Mac Computer.
Explained step by step,
- Go to the path with the files in terminal in root directory
RPN Stack in C++
- Compile files into object files
2a)) Run
g++ -c src/rpn_caculator.cpp
, assuming you're in the root.
directory 2b)) Rung++ --std=c++17 c src/main.cpp
Note: In general, best use -std=c++17 if any versioning errors pop up in build
This generates a main.o
and rpn_calculator.o
file in your root directory
- Link the two files into an executable in the build/bin/ directory;
g++ -o build/bin/calc main.o rpn_calculator.o
This makes a binary executable named calc in the specified path. This name can be anything.
- Run the executable with
build/bin/calc
and follow through the instructions in program
At any time,
- type in
exit
to quit the program from the terminal, - type in
help
for further instructions on how to operate the program. - type in
in
to change mode to Inline Expression Mode - type in
post
orrpn
to change mode to RPN Expression Mode
Here's an excerpt from the help files on running the program.
Welcome to the RPN Stack Calculator Project!
This is an example implementation of a Calculator with Postfix Notation,
also known as RPN implemented with a Stack in C++
Enter a valid expression. Current Mode: RPN
Type exit or e to to quit. Type help or h to get additonal instructions.
>>h
Some Instructions to help you along the tool
Type in Mathematical Expressions using:
-> '+' for Addition,
-> '-' for Subtraction,
-> '/' for Division,
-> '*' for Multplication,
-> '^' for Exponent
The program runs either in RPN or Inline Mode
Type in Inline to switch to Inline mode,
or type in RPN to switch to RPN mode at any time
Expressions have to be in RPN or Postfix Notation when in RPN Mode;
You would write an inline expression 5+3 in RPN Notation as 5 3 +
Expressions have to be in Inline Notation when in Inline Mode;
You would write an inline expression >> 5 + 3
Inline expressions are converted into RPN before evaluation
Note that the expression tokens have to be separated by a space
Enter a valid expression. Current Mode: RPN
Type exit or e to to quit. Type help or h to get additonal instructions.
>>
Acceptable responses are space separated in postfix notation.
Examples are:
- 5 4 +,
- 5 9 1 + *,
- ...
Inline notation allows space separated entries with usage of only '(' adn ')' as brackets.
Normally, I would have already compiled the calc binary exectuable. So, steps 1 through 4 are done for you. And you would only need to do the 4th step to hop into the executable.
The src
directory also has a test file with which we can validate the efficacy of the program in the RPNStackCalculator
Class. Here's how you can compile and run it yourself to test it.
In the src/test_rpn_calculatpr.cpp
file, insert in your own expressions and values to test in expressions
and expectResults
array, defined in Line 9 and 19 respectively. By default, you can also work with the values there.
The steps are similar to compiling and linking the main.cpp
file.
Explained step by step,
- Go to the path with the files in terminal in root directory
RPN Stack in C++
- Compile files into object files
2a)) Run
g++ -c src/rpn_caculator.cpp
, assuming you're in the root.
directory. This may already be done from 2.1.1. 2b)) Rung++ -std=c++17 -c src/test_rpn_calculator.cpp
. This change with the-std
is important, because at least in my computer, thestd::size
in Line 21 wouldn't compile otherwise.
This generates a test_rpn_calculator.o
and rpn_calculator.o
file in your root directory
- Link the two files into an executable in the build/bin/ directory;
g++ -o build/bin/test_calc test_rpn_calculator.o rpn_calculator.o
This makes a binary executable named test_calc in the specified path. This name can be anything of your choice.
- Run the executable with
build/bin/test_calc
and the assertion results print to terminal. In case of errors, you would see something going wrong.
Normally, I would have already compiled the test_calc binary exectuable. So, steps 1 through 4 are done for you. And you would only need to do the 4th step to hop into the executable.
As of implementing the Inline Functionality in current branch, the test cases are yet to be updated different format to test for edge cases. Most tests were done off repo in a prototyping file.
Again, I am assuming this to be done on a Darwin Mac Computer. I expect it to be slightly modified on a Windows computer. Linux/Ubuntu should be the same.
- Go to an appropriate directory in build. I usually build in
build/Debug
. If the directory isn't there, you can make a new named folder. - If there are already CMake files in there, delete the cache to make a new one if that is necessary, or jump to step X.
- From your path,
build/Debug
,build/Release
orbuild/Test
; runcmake ../..
- Basically, we're calling to the
CMakeLists.txt
file in the root directory, which contains build instructions.
- Once the build is done, and there are CMake files in your current directory, there would also be a
Makefile
file created. - Run
make
from that path, and the executablesrpn_calculator
andtest_calculator
will be created in the same directory - Run
./rpn_calculator
or./test_calculator
to run the executables
I should have already built them on push to the repositary (under build/Release
in circumstances).
Either run the Commands from Step 6 there, or run build/Release/rpn_calculator
or test with build/Release/test_calculator
.
The program is built with five files in src:
- main.cpp
- rpn_calculator.cpp
- rpn_calculator.h
- func_operator.h
- helpSheet.h
- infixToPostfix.h
- validateInput.h
- test_rpn_calculator.cpp
-
in 2 and 3, we add a RPNStackCalculator Class to take on the repetitive operations into three method calls.A Stack Container is called in privately by the class whenever we declare an instance.
-
in 4, we define an array of functions externally to check whether something is an operator, paranthesis, and also assign operator importance.
-
in 5, we add in terminal output to help users in the terminal upon usage
-
in 6, we add in the functionality of changing notations from infix
-
in 7, we define a function that checks for illegal characters in the expression with a high degree of modularity
-
in 1, the whole porgram is expose as an endpoint CLI
-
in 8, we define some simple assertions tests to inspect edge cases and bugs
We parse our input string and call on the methods to run the Calculator.
Basic CLI Functionality is added in an infintie while loop with a bool switch.
The program is kept bare bones on purpose, as anything other than this would be needless overengineering. This has become quite the extensive and large build as is.
The task is simple to begin with. I've nevertheless packed everything into its own modular files to allow future work on this to be easy and error averse. As such, it can be expanded endlessly by, for example, adding more methods in the rpn_calculator header and .cpp file, or adding in more operations in the rpn_calculator.cpp and func_operator.h file, etc.
Optimally I would make this file with Cmake to allow for better distribution, which I will still try to add in. I opted to do this manually, which is possible but difficult, as VSCode isn't being cooperative(There are some glitched with lldb and GDB Debuggers and config files).
I've written the Readme in context of a Mac Darwin Development Environment.
Add any point, the commands help, quit helps the user get more bang for the buck out of this program.
Users can also change between Inline and RPN Notation. The expressions is evaluated in Postfix. Any Infix is converted to Postfix, when run on Inline mode.
There is an extensive use of guard clauses to rule out edge cases and validate inputs, for example.
All in all, I think this project demonstrated good practice in code writing, linting, formatting, project management, unit testing, modular expansion, proper use of functional and OOP approaches. I consider this a success.
Die RPN (Reverse Polish Notation) oder Postfix-Notation ist eine Form der Notation, bei der mathematische Ausdrücke beginnend mit den Operanden oder Zahlen des Ausdrucks geschrieben werden und mit ihren Operatoren enden.
Zum Beispiel würden wir 2+2
, was in der Inline-Notation ist, als 2 2 +
in der Postfix-Notation schreiben.
Bei komplexeren Ausdrücken verwenden wir keine Klammern, um die Reihenfolge der Operationen anzugeben, wie es bei der normalen Inline-Notation der Fall ist. Um Postfix-Ausdrücke zu berechnen, folgen wir einem spezifischen Algorithmus unter Verwendung eines Stapels.
Inline-Ausdrücke werden mithilfe des Shunting Yard-Algorithmus in RPN umgewandelt.
Ein Stapel ist eine Art geordneter Sammlung, bei der Werte in einer stapelähnlichen Reihenfolge abgelegt werden, dh nach dem Prinzip "FILO" (First In, Last Out). Stellen Sie sich einen Stapel Bücher auf einem Schreibtisch vor. Das erste Buch unten wäre das letzte, das Sie erreichen würden, während Sie jedes Buch von oben nach unten inspizieren.
Stapel haben in verschiedenen Programmiersprachen gemeinsame Operationen.
Sie können den obersten Wert eines Stapels entfernen (auch als "pop" bezeichnet). In einigen Sprachen würde "pop" auch den Wert für Sie auslesen. Im Kontext von C++ handelt es sich um zwei verschiedene Operationen.
Sie können Werte nacheinander auf den Stapel legen (auch als "push" bezeichnet).
In C++ können Sie einen Stack-Datentyp aus der Standardbibliothek verwenden.
Nehmen Sie den Fall eines RPN-Ausdrucks: 1 2 3 + -
Um diesen Ausdruck zu berechnen, lesen Sie von links nach rechts.
Stellen Sie sich vor, Sie haben einen leeren Stapel, in den Sie die Werte legen können. Beim Lesen von links nach rechts, wenn Sie einen ganzzahligen oder numerischen Wert sehen, legen Sie die Zahlen auf den Stapel. Wenn Sie also von unten nach oben lesen (denken Sie daran, dass die Werte auf der untersten Ebene zuletzt abgerufen werden), sieht Ihr Stapel so aus:
|------|---|
|top | 3 |
|......| 2 |
|bottom| 1 |
------------
An diesem Punkt gelangen wir zu den Operatoren. Wenn wir auf den ersten Operator stoßen, der das + Zeichen ist, entfernen wir die obersten beiden Operanden - die Zahlen, auf denen die Operation ausgeführt werden soll - aus dem Stapel. Wir nehmen zuerst 3 und dann 2 heraus und führen anschließend die Operation 3+2 aus. Das Ergebnis ist 5, und wir legen es wieder auf den Stapel. Dies wiederholen wir mit dem - Operator. Wir nehmen die beiden obersten Werte im Stapel heraus, nämlich 5 und 1. 1-5 ergibt -4. Dies ist unser Endergebnis.
Am Ende der Postfix-Notation gibt es keine weiteren Operatoren mehr, und wir haben einen Wert, das Ergebnis, das im Stapel verbleibt.
Der Code ist in 5 Dateien unterteilt. Das Skript, das den Rechner ausführt, befindet sich in der Datei main.cpp. Mit einem C++-Compiler wie GCC oder Clang können Sie die einzelnen Dateien *.cpp im Pfad ./src/
im Terminal kompilieren. In diesem Beispiel gehe ich davon aus, dass Sie auf einem Mac-Computer bereits einen GCC-Compiler im PATH verwenden.
Schritt für Schritt erklärt:
- Navigieren Sie im Terminal zum Pfad mit den Dateien im Stammverzeichnis
RPN Stack in C++
. - Kompilieren Sie die Dateien in Objektdateien.
2a) Führen Sie
g++ -c src/rpn_calculator.cpp
aus, vorausgesetzt, Sie befinden sich im Stammverzeichnis.
. 2b) Führen Sieg++ --std=c++17 -c src/main.cpp
aus.
Hinweis: Verwenden Sie im Allgemeinen -std=c++17
, wenn bei der Kompilierung Fehler aufgrund von Versionsinkompatibilitäten auftreten.
Dadurch werden eine main.o
- und eine rpn_calculator.o
-Datei im Stammverzeichnis erstellt.
- Verknüpfen Sie die beiden Dateien zu einer ausführbaren Datei im Verzeichnis
build/bin/
mit dem Befehlg++ -o build/bin/calc main.o rpn_calculator.o
.
Dadurch wird eine ausführbare Binärdatei mit dem Namen "calc" im angegebenen Pfad erstellt. Dieser Name kann beliebig sein.
- Führen Sie die ausführbare Datei mit
build/bin/calc
aus und folgen Sie den Anweisungen im Programm.
Zu jeder Zeit:
- Geben Sie
exit
ein, um das Programm über die Befehlszeile zu beenden. - Geben Sie
help
ein, um weitere Anweisungen zur Bedienung des Programms zu erhalten. - Geben Sie
in
ein, um in den Inline-Ausdrucksmodus zu wechseln. - Geben Sie
post
oderrpn
ein, um in den RPN-Ausdrucksmodus zu wechseln.
Hier ist ein Auszug aus den Hilfedateien zur Ausführung des Programms:
Welcome to the RPN Stack Calculator Project!
This is an example implementation of a Calculator with Postfix Notation,
also known as RPN implemented with a Stack in C++
Enter a valid expression. Current Mode: RPN
Type exit or e to to quit. Type help or h to get additonal instructions.
>>h
Some Instructions to help you along the tool
Type in Mathematical Expressions using:
-> '+' for Addition,
-> '-' for Subtraction,
-> '/' for Division,
-> '*' for Multplication,
-> '^' for Exponent
The program runs either in RPN or Inline Mode
Type in Inline to switch to Inline mode,
or type in RPN to switch to RPN mode at any time
Expressions have to be in RPN or Postfix Notation when in RPN Mode;
You would write an inline expression 5+3 in RPN Notation as 5 3 +
Expressions have to be in Inline Notation when in Inline Mode;
You would write an inline expression >> 5 + 3
Inline expressions are converted into RPN before evaluation
Note that the expression tokens have to be separated by a space
Enter a valid expression. Current Mode: RPN
Type exit or e to to quit. Type help or h to get additonal instructions.
>>
Akzeptable Antworten sind durch Leerzeichen getrennte Werte in Postfix-Notation.
Beispiele sind:
- 5 4 +,
- 5 9 1 + *,
- ...
Die Inline-Notation erlaubt Einträge mit Leerzeichen und die Verwendung von (
und )
als Klammern.
Normalerweise hätte ich die calc
-Binärdatei bereits kompiliert. Die Schritte 1 bis 4 sind bereits für Sie erledigt. Sie müssen nur den 4. Schritt ausführen, um zur ausführbaren Datei zu gelangen.
Das src
-Verzeichnis enthält auch eine Testdatei, mit der Sie die Effektivität des Programms in der RPNStackCalculator
-Klasse validieren können. Hier erfahren Sie, wie Sie sie selbst kompilieren und ausführen können, um das Programm zu testen.
In der Datei src/test_rpn_calculator.cpp
fügen Sie Ihre eigenen Ausdrücke und Werte ein, um sie in den Arrays expressions
und expectResults
zu testen, die in Zeile 9 bzw. 19 definiert sind. Standardmäßig können Sie auch mit den Werten arbeiten, die dort angegeben sind.
Die Schritte sind ähnlich wie beim Kompilieren und Verknüpfen der Datei main.cpp
.
Schritt für Schritt erklärt:
- Navigieren Sie im Terminal zum Pfad mit den Dateien im Stammverzeichnis
RPN Stack in C++
. - Kompilieren Sie die Dateien in Objektdateien.
2a) Führen Sie
g++ -c src/rpn_calculator.cpp
aus, vorausgesetzt, Sie befinden sich im Stammverzeichnis.
. Dies könnte bereits in Schritt 2.1.1 erledigt worden sein. 2b) Führen Sieg++ -std=c++17 -c src/test_rpn_calculator.cpp
aus. Diese Änderung mit-std
ist wichtig, da zumindest auf meinem Computerstd::size
in Zeile 21 andernfalls nicht kompiliert werden würde.
Dadurch werden eine test_rpn_calculator.o
- und eine rpn_calculator.o
-Datei im Stammverzeichnis erstellt.
- Verknüpfen Sie die beiden Dateien zu einer ausführbaren Datei im Verzeichnis
build/bin/
mit dem Befehlg++ -o build/bin/test_calc test_rpn_calculator.o rpn_calculator.o
.
Dadurch wird eine ausführbare Binärdatei mit dem Namen "test_calc" im angegebenen Pfad erstellt. Dieser Name kann beliebig sein.
- Führen Sie die ausführbare Datei mit
build/bin/test_calc
aus, und die Ergebnisse der Überprüfungen werden in der Befehlszeile angezeigt. Im Falle von Fehlern sehen Sie, wenn etwas schief geht.
Normalerweise hätte ich die test_calc
-Binärdatei bereits kompiliert. Die Schritte 1 bis 4 sind bereits für Sie erledigt. Sie müssen nur den 4. Schritt ausführen, um zur ausführbaren Datei zu gelangen.
Was die Implementierung der Inline-Funktionalität im aktuellen Branch betrifft, müssen die Testfälle noch auf ein anderes Format aktualisiert werden, um Randfälle zu testen. Die meisten Tests wurden außerhalb des Repositorys in einer Prototyping-Datei durchgeführt.
Nochmals, ich gehe davon aus, dass dies auf einem Mac-Darwin-Entwicklungsumfeld durchgeführt wird. Ich erwarte, dass es auf einem Windows-Computer leicht modifiziert wird. Für Linux/Ubuntu sollte es gleich sein.
- Gehen Sie zu einem geeigneten Verzeichnis im Verzeichnis "build". Normalerweise erstelle ich in
build/Debug
,build/Release
oderbuild/Test
. Wenn das Verzeichnis dort nicht existiert, können Sie einen neuen Ordner mit dem gewünschten Namen erstellen. - Wenn bereits CMake-Dateien vorhanden sind, löschen Sie den Cache, um bei Bedarf neue Dateien zu erstellen, oder fahren Sie mit Schritt X fort.
- Von Ihrem Pfad
build/Debug
,build/Release
oderbuild/Test
aus führen Siecmake ../..
aus.
- Im Wesentlichen rufen wir die Datei "CMakeLists.txt" im Stammverzeichnis auf, die Build-Anweisungen enthält.
- Sobald der Build abgeschlossen ist und CMake-Dateien in Ihrem aktuellen Verzeichnis vorhanden sind, wird auch eine Datei "Makefile" erstellt.
- Führen Sie
make
von diesem Pfad aus aus, und die ausführbaren Dateien "rpn_calculator" und "test_calculator" werden im selben Verzeichnis erstellt. - Führen Sie
./rpn_calculator
oder./test_calculator
aus, um die ausführbaren Dateien auszuführen.
Normalerweise hätte ich die Binärdateien bereits im Verzeichnis "build/Release" erstellt. Führen Sie entweder die Befehle aus Schritt 6 dort aus oder führen Sie build/Release/rpn_calculator
aus oder testen Sie mit build/Release/test_calculator
.
Das Programm besteht aus fünf Dateien im Verzeichnis "src":
- main.cpp
- rpn_calculator.cpp
- rpn_calculator.h
- func_operator.h
- helpSheet.h
- infixToPostfix.h
- validateInput.h
- test_rpn_calculator.cpp
-
In 2 und 3 fügen wir eine Klasse RPNStackCalculator hinzu, um die wiederholten Operationen in drei Methodenaufrufe zu übernehmen. Ein Stapelcontainer wird von der Klasse privat verwendet, wann immer wir eine Instanz deklarieren.
-
In 4 definieren wir extern ein Array von Funktionen, um zu überprüfen, ob etwas ein Operator oder eine Klammer ist, und weisen auch die Wichtigkeit von Operatoren zu.
-
In 5 fügen wir die Ausgabe im Terminal hinzu, um Benutzern bei der Verwendung im Terminal zu helfen.
-
In 6 fügen wir die Funktionalität hinzu, von der Infix-Notation in Postfix umzuwandeln.
-
In 7 definieren wir eine Funktion, die unzulässige Zeichen im Ausdruck mit einem hohen Maß an Modularität überprüft.
-
In 1 wird das gesamte Programm als CLI-Endpunkt bereitgestellt.
-
In 8 definieren wir einige einfache Assertions-Tests, um Randfälle und Fehler zu überprüfen.
Wir analysieren unseren Eingabezeichenfolge und rufen die Methoden auf, um den Rechner auszuführen.
Die grundlegende CLI-Funktionalität ist in einer unendlichen Schleife mit einem boolschen Schalter enthalten.
Das Programm ist absichtlich minimalistisch gehalten, da alles andere überflüssige Überkonstruktionen wären. Es ist bereits ziemlich umfangreich und groß.
Die Aufgabe ist zu Beginn einfach. Dennoch habe ich alles in separate modulare Dateien verpackt, um zukünftige Arbeiten daran einfach und fehlerfrei zu gestalten. So kann es endlos erweitert werden, indem beispielsweise weitere Methoden in der Header-Datei rpn_calculator und .cpp-Datei hinzugefügt werden oder mehr Operationen in der rpn_calculator.cpp- und func_operator.h-Datei hinzugefügt werden usw.
Optimalerweise würde ich diese Datei mit CMake erstellen, um eine bessere Verteilung zu ermöglichen, was ich immer noch versuchen werde hinzuzufügen. Ich habe mich dafür entschieden, dies manuell zu tun, was möglich, aber schwierig ist, da VSCode nicht kooperiert (es gibt einige Probleme mit lldb- und GDB-Debuggern und Konfigurationsdateien).
Ich habe die Readme im Kontext einer Mac Darwin-Entwicklungsumgebung geschrieben.
Fügen Sie an jeder Stelle die Befehle "help", "quit" hinzu, um dem Benutzer in der Befehlszeile mehr Nutzen aus diesem Programm zu bieten.
Benutzer können auch zwischen Inline- und RPN-Notation wechseln. Der Ausdruck wird in Postfix ausgewertet. Jegliche Infix-Notation wird bei der Ausführung im Inline-Modus in Postfix umgewandelt.
Es gibt eine umfangreiche Verwendung von Guard-Klauseln, um Randfälle auszuschließen und Eingaben zu validieren, zum Beispiel.
Insgesamt denke ich, dass dieses Projekt eine gute Praxis im Schreiben von Code, Linting, Formatierung, Projektmanagement, Unittesting, modularer Erweiterung, ordnungsgemäßer Verwendung funktionaler und OOP-Ansätze demonstriert. Ich finde es ist gelungen.