Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Paul Seung Mo Lim's completed Morse Code Translator in C++ #22

Open
wants to merge 12 commits into
base: master
Choose a base branch
from
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
.DS_Store
24 changes: 16 additions & 8 deletions README.md
Original file line number Diff line number Diff line change
@@ -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.
Expand All @@ -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.
75 changes: 75 additions & 0 deletions morseCode.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
//
// morseCode.cpp
//
//
// Created by Paul Lim on 3/23/19.
//

#include <stdio.h>
#include <iostream>
#include <fstream>
#include <string>
#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;
}
119 changes: 119 additions & 0 deletions morseCodeTree.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,119 @@
//
// morseCodeTree.cpp
//
//
// Created by Paul Lim on 3/23/19.
//

#include <vector>
#include <string>
#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<MorseCodeNode*>::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<MorseCodeNode*>::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::MorseCodeNode(char subMorseCode, char letter) {
this->subMorseCode = subMorseCode;
this->letter = letter;
this->children = std::vector<MorseCodeNode*>();
}

void MorseCodeNode::addChild(MorseCodeNode* child) {
children.push_back(child);
}

void MorseCodeNode::setLetter(char letter) {
this->letter = letter;
}
49 changes: 49 additions & 0 deletions morseCodeTree.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
//
// morseCodeTree.h
//
//
// Created by Paul Lim on 3/23/19.
//
#ifndef morseCodeTree_h
#define morseCodeTree_h

#include <stdio.h>
#include <vector>

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<MorseCodeNode*> children;
};

#endif /* morseCodeTree_h */
13 changes: 13 additions & 0 deletions sampleInput.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
-..||---||--.

....||.||.-..||.-..||---||||.--||---||.-.||.-..||-..

...||---||..-.||-||.--||.-.||..||-||.||.-.||...

...||---||..-.||-||||||.--||.-.||..||-||.||.-.||...

-||....||.-.||.||.||||||||...||.--.||.-||-.-.||.||...

||||-...||.||--.||..||-.||-.||..||-.||--.||||...||.--.||.-||-.-.||.

.||-.||-..||||...||.--.||.-||-.-.||.||||