diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..e43b0f9 --- /dev/null +++ b/.gitignore @@ -0,0 +1 @@ +.DS_Store diff --git a/README.md b/README.md index 605e027..bd167c8 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,22 @@ Morse Code ========== +Implementation Details +----------- +The morseCodeTree class is implemented using the Trie data structure, which functions identically to a Deterministic Finite Automaton (DFA). The root of the tree is the starting state, and each node in the morseCodeTree (or state, in DFA terms), contains a letter and transition(s) to other nodes. + +When given morse code (e.g.: ..-) to translate, the morseCodeTree will start from its root and recursively traverse through the tree based on the current character (. or -) it is reading while reading each character in the code from left to right. Once the morse code is read, if the morse code is a valid sequence of characters that can be translated to a letter, the current node or state will contain the letter (e.g.: u) that corresponds to the given morse code. Invalid morse codes will lead to a state that contains the null character '\0'. + +The morseCodeTree instance is created at the beginning of program execution, and all valid morse code sequences are added to the tree through a hard-coded array of size 26 that contains the morse code sequences for all 26 letters in the English alphabet. + +Instructions +----------- +1) Clone the repo. +2) Compile .cpp files and create an executable file through the terminal app with command `g++ *.cpp -o morseCode.exe -Wall`. +3) Files should successfully compile without errors or warnings. +4) Run the executable with the input file as a command line argument through the teriminal app with command `./morseCode.exe sampleInput.txt`. +5) Program should translate the morse code from 'sampleInput.txt' file and output the translation on the console of the terminal app. + The Problem ----------- Morse code is a way to encode messages in a series of long and short sounds or visual signals. During transmission, operators use pauses to split letters and words. @@ -25,11 +41,3 @@ Sample Output dog hello world - -The Fine Print --------------- -Please use whatever techniques you feel are applicable to solve the problem. We suggest that you approach this exercise as if this code was part of a larger system. The end result should be representative of your abilities and style. We prefer that you submit your solution in a language targeting the .NET Framework to help us better evaluate your code. - -Please fork this repository. If your solution involves code auto generated by a development tool, please commit it separately from your own work. When you have completed your solution, please issue a pull request to notify us that you are ready. - -Have fun. diff --git a/morseCode.cpp b/morseCode.cpp new file mode 100644 index 0000000..352af83 --- /dev/null +++ b/morseCode.cpp @@ -0,0 +1,75 @@ +// +// morseCode.cpp +// +// +// Created by Paul Lim on 3/23/19. +// + +#include +#include +#include +#include +#include "morseCodeTree.h" + +int main(int argc, char* argv[]) { + if (argc != 2) { + std::cout << "Must only provide 2 arguments." << std::endl; + return 1; + } + + // read morse code file + // syntax of the lines in morseCodeFile are assumed to be "correct", and limited to + // only the characters '.', '-', and "||" as stated in the README.md + std::ifstream morseCodeFile(argv[1]); + if (!morseCodeFile){ + std::cout << "Could not open the morse code file: " << argv[1] << ".\n"; + return 1; + } + // hard code array of all morse codes and all english alphabet letters + // 'morseCodes' and 'letters' correspond so that morseCodes[i] is the morse code for the letter in letters[i] + int const SIZE = 26; + std::string morseCodes[SIZE] = { ".-", "-...", "-.-.", "-..", ".", "..-.", "--.", "....", "..", ".---", "-.-", ".-..", "--", "-.", "---", ".--.", "--.-", ".-.", "...", "-", "..-", "...-", ".--", "-..-", "-.--", "--.." }; + char letters[SIZE] = { 'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o', 'p', 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z' }; + // create MorseCodeTree + // the MorseCodeTree class is essentially a finite state automata where each state is a subsequence of a morse + // code, and all states reached through a valid sequence of a morse code for a letter are "accepting states" + MorseCodeTree tree = MorseCodeTree(); + for (unsigned int i = 0; i < SIZE; i++) { + tree.addMorseCode(morseCodes[i], letters[i]); + } + + // break is b/t chars or letters; prev_break is the start index of the current morse code subsequence for a letter; + // next_break is the end index of the current morse code subsequence (end_index+1), where + // the || begins + size_t prev_break, next_break; + std::string morseCodeSeq, morseCodeSubSeq; + while (morseCodeFile >> morseCodeSeq) { + prev_break = 0; + // while a break exists in the morse code subsequence that I have not translated yet.. + while ((next_break = morseCodeSeq.find("||", prev_break)) != std::string::npos) { + // get the morse code subsequence for a letter + morseCodeSubSeq = morseCodeSeq.substr(prev_break, next_break-prev_break); + // translate that morse code subsequence and print it out + std::cout << tree.getLetterFromCode(morseCodeSubSeq); + // skip over the "||" + prev_break = next_break + 2; + + if (morseCodeSeq[next_break+2] == '|') { + // there are subsequent breaks "||" + // each subsequent break results in a space (eg: |||| -> 2 spaces, + // |||||| -> 3 spaces); assumed that groups of subsequent breaks only + // occur in sizes of even numbers + while (morseCodeSeq[next_break+2] == '|') { + next_break += 2; + std::cout<< " "; + } + prev_break = next_break; + } + } + // last morse code subsequence, which does not precede a break + morseCodeSubSeq = morseCodeSeq.substr(prev_break, morseCodeSeq.size()-prev_break); + std::cout << tree.getLetterFromCode(morseCodeSubSeq) << std::endl; + } + + return 0; +} diff --git a/morseCodeTree.cpp b/morseCodeTree.cpp new file mode 100644 index 0000000..31ed2ee --- /dev/null +++ b/morseCodeTree.cpp @@ -0,0 +1,119 @@ +// +// morseCodeTree.cpp +// +// +// Created by Paul Lim on 3/23/19. +// + +#include +#include +#include "morseCodeTree.h" + +// MorseCodeTree implementation +// public +MorseCodeTree::MorseCodeTree() { + root = new MorseCodeNode(); +} + +MorseCodeTree::~MorseCodeTree() { + deleteTree(root); + delete root; + root = NULL; +} + +// returns NULL char '\0' if morseCode does not exist in the tree; else return the letter +// that matches with the morseCode parameter +char MorseCodeTree::getLetterFromCode(std::string& morseCode) { + if (root == NULL) { + // root should never be null for an object, but check nonetheless + return '\0'; + } + return getLetterFromCode(root, morseCode, 0); +} + +void MorseCodeTree::addMorseCode(std::string& morseCode, char letter) { + if (root == NULL) { + // root should never be null for an object, but check nonetheless + return; + } + addMorseCode(root, morseCode, letter, 0); +} + +// private +char MorseCodeTree::getLetterFromCode(MorseCodeNode* node, std::string& morseCode, unsigned int index) { + if (index == morseCode.size()) { + // 'node' is where the 'letter' should be + // in terms of finite state automata, 'node' should be an accepting state for morseCode + return node->letter; + } + + char subMorseCode = (char) morseCode[index]; + for (std::vector::iterator child = node->children.begin(); + child != node->children.end(); child++) { + // from the currently seen morseCode subsequence "state", check if there is a transition for the + // next morseCode character to the next state + if ((*child)->subMorseCode == subMorseCode) { + return getLetterFromCode(*child, morseCode, index+1); + } + } + return '\0'; +} + +void MorseCodeTree::addMorseCode(MorseCodeNode* node, std::string& morseCode, char letter, unsigned int index) { + if (index == morseCode.size()) { + // 'node' is where the 'letter' should be + // in terms of finite state automata, 'node' should be an accepting state for morseCode + node->setLetter(letter); + return; + } + char subMorseCode = (char) morseCode[index]; + for (std::vector::iterator child = node->children.begin(); + child != node->children.end(); child++) { + // from the currently seen morseCode subsequence "state", check if there is a transition for the + // next morseCode character to the next state + if ((*child)->subMorseCode == subMorseCode) { + addMorseCode(*child, morseCode, letter, index+1); + return; + } + } + // 'node' does not have a transition with 'subMorseCode' as the transition character + // In other words, the 'morseCode' that needs to be added is a new extension of a subsequence of an already + // known morse code, and this extension was never seen before + MorseCodeNode* newChild = new MorseCodeNode(subMorseCode, '\0'); + node->children.push_back(newChild); + // recurse down the new child node + addMorseCode(newChild, morseCode, letter, index+1); +} + +// private recursive delete method called by destructor +void MorseCodeTree::deleteTree(MorseCodeNode* node){ + if (node == NULL) { + return; + } + for (unsigned int i = 0; i < node->children.size(); i++) { + deleteTree(node->children[i]); + delete node->children[i]; + node->children[i] = NULL; + } +} + + +// MorseCodeNode implementation +MorseCodeNode::MorseCodeNode() { + letter = '\0'; + children = std::vector(); +} + +MorseCodeNode::MorseCodeNode(char subMorseCode, char letter) { + this->subMorseCode = subMorseCode; + this->letter = letter; + this->children = std::vector(); +} + +void MorseCodeNode::addChild(MorseCodeNode* child) { + children.push_back(child); +} + +void MorseCodeNode::setLetter(char letter) { + this->letter = letter; +} diff --git a/morseCodeTree.h b/morseCodeTree.h new file mode 100644 index 0000000..1b88c6b --- /dev/null +++ b/morseCodeTree.h @@ -0,0 +1,49 @@ +// +// morseCodeTree.h +// +// +// Created by Paul Lim on 3/23/19. +// +#ifndef morseCodeTree_h +#define morseCodeTree_h + +#include +#include + +class MorseCodeNode; + +class MorseCodeTree { +public: + MorseCodeTree(); + ~MorseCodeTree(); + // Accessors + char getLetterFromCode(std::string& morseCode); + // Modifiers + void addMorseCode(std::string& morseCode, char letter); +private: + char getLetterFromCode(MorseCodeNode* node, std::string& morseCode, unsigned int index); + void addMorseCode(MorseCodeNode* node, std::string& morseCode, char letter, unsigned int index); + void deleteTree(MorseCodeNode* node); + // member fields + MorseCodeNode* root; +}; + + +class MorseCodeNode { +public: + MorseCodeNode(); + MorseCodeNode(char subMorseCode, char letter); + // Accessors + char getLetter(); + // Modifiers + void addChild(MorseCodeNode* child); + void setLetter(char letter); + + friend class MorseCodeTree; +private: + char subMorseCode; + char letter; + std::vector children; +}; + +#endif /* morseCodeTree_h */ diff --git a/sampleInput.txt b/sampleInput.txt new file mode 100644 index 0000000..89ca14b --- /dev/null +++ b/sampleInput.txt @@ -0,0 +1,13 @@ +-..||---||--. + +....||.||.-..||.-..||---||||.--||---||.-.||.-..||-.. + +...||---||..-.||-||.--||.-.||..||-||.||.-.||... + +...||---||..-.||-||||||.--||.-.||..||-||.||.-.||... + +-||....||.-.||.||.||||||||...||.--.||.-||-.-.||.||... + +||||-...||.||--.||..||-.||-.||..||-.||--.||||...||.--.||.-||-.-.||. + +.||-.||-..||||...||.--.||.-||-.-.||.|||| \ No newline at end of file