Click here to Skip to main content
Click here to Skip to main content

Tagged as

String and vector manipulation through example: Morse code and more

, 30 Jul 2009 CPOL
Rate this:
Please Sign up or sign in to vote.
Make a program that can perform various manipulations on a string.

Introduction

This project will take you through some of the basics of working with vectors and strings. We will work to create a program that can perform a variety of operations on a string.

Background

The really cool thing about the program is it can do more than one thing to the same string. You can use a Caesar cipher to shift the characters over 5 chars, remove the spaces, reverse, turn it to Morse code, then send it to your friend to see if they can decode it! See the picture for an example of the program in action. The purpose of this program is to practice manipulating strings with a nice interface for testing them, and to have fun coding a program that is pretty fun to watch at work. Big Grin | :-D

(I know Morse code is completely obsolete and all, but I used it as a learning example. You could adapt the translator (with a little modification) to change each letter to a binary representation, or a numeric one.)

Using the Code

You should have a basic understanding of if, else, switch, loops, functions, simple IO, and a basic understanding of objects before you start this. If you do not completely understand any of these, there are plenty of tutorials here on those as well. Vectors are basically arrays that can grab more memory and therefore grow in size as the programmer wishes. It takes processing time to resize a vector, but the convenience is worth it. They also support some nice features that allow us to use them more easily. A strings is basically a vector of characters, with a few different methods and operations.

We will be making a program that takes a string that the user inputs, then processes it in various ways. First, we will make the basic structure of the menu and string input-output, and later we will add the string processing.

Here’s what our menu will look like:

#include <iostream>
#include <vector>
using namespace std;

int main()
{
    string thestring; //declare variables
    short choice;

    for(short y=0; y<100; y++)
    //This is the loop where the user will enter the
    //string they want to process.
    { 
        cout << "enter the word/sentence: ";

        getline(cin , thestring); //input the string

        for(short x=0; x<100; x++)
        //This is the loop where the string will be
        //processed in different ways.
        {
            cin >> choice; 
            if (choice==0)
                break;
            for (int y=0; y<100; y++) 
            { 
                cout << "enter the word/sentence: ";

                getline(cin, thestring); 

                for(int x=0; x<100; x++) {

                    cin >> choice;

                    switch (choice) {
                        case -1:
                            goto exit;
                            break;
                        case 0:
                            goto outerloop;
                            break;
                         //other cases to be added here
                    }

                    cout << thestring << endl;

                } //end nested loop
                outerloop: cin.ignore(); //clear input buffer
            }// end first loop
            exit: return 0;
        } //end of main

We’ll gradually add more cases to the menu switch as we move on; each case will perform a certain function on our string, and it will get displayed. The loops only run 100 times each to prevent nasty infinite looping possibilities. We use labels to tell the program to break out of nested loops. This is the only thing you should really use labels for. I used them because there is no other choice to break out of nested loops, but you should not get in the habit of using them to structure you program flow when you could have used loops or ifs instead.

So, our first challenge is to repeat the string 10 times. We will use concatenation to add a copy of the string onto its end. All of our functions will take string& as an argument and return a string. We could have modified the string that was passed to the function and returned void, but that would be inconvenient if we wanted to have a copy of the string that was not messed up. Each function will create and return a new string, which is advantageous at times when we continually use the original copy of the string.

Strings are concatenated with the + operator, which simply takes the first string and adds the other string to its end. We will have a loop that adds the string to itself 10 times, resulting in code that looks like this:

string repeat (string& s)
{
    string returnvalue;

    for (int x=0; x<10; x++) //loops 10 times
    {
        returnvalue += s; //same as returnvalue=returnvalue+s
    }

    return returnvalue;
}

This is probably the easiest of our tasks (which is why we did it first).

OK, we have the function done; now, we need to add it to our menu. Just add another switch case to the switch like so:

case -1:
    goto exit;
    break;
case 0:
    goto outerloop;
    break;
case 1:
    thestring = repeat(thestring); 

Now onto something a little harder: reversing a string. We are going to delve into the vector side of strings a bit more, as we need to extract the characters that make up the string. We want to take the chars from our input string and add them to a new string, in reverse order. We can use the at(index) function or the [] operator (like arrays) to access elements in a string/vector. We can also use at() just to be safe as it provides bounds checking. The method size() returns the number of elements in a vector, while length() returns the number of chars in a string.

string reverse(string& s)
{
    string returnvalue;

    for(short x=s.length()-1; x>=0; x--)
    {
        returnvalue.push_back(s.at(x));
        //add char at z to end of returnvalue
    }
    return returnvalue;
}

And, add the call to the function to the menu:

case 2: thestring=reverse(thestring); break;

Our for loop starts at the last character in the inputted string, being the character at the string's length minus one, and goes backwards until we get to -1. It is always important to keep in mind that vectors and arrays start at index 0, not 1. As length() is the string equivalent of size() for a vector, both return the number of elements in the vector. We start with the last character of the string s, and use push_back to add it to the end of the return-string. push_back is a function that works on vectors and strings, and adds the element passed to it to the end of the vector, resizing it if necessary. Remember that you use push_back or at to modify the chars in a string, and the + concatenating operator to add strings to strings.

The next two functions are closely related as they use the same principle of extracting characters. First, we will have a function that takes the initials of a string. We will need to take the first letter of the string, and any char after a space. We have a check to find the spaces in a loop that extracts the characters from the string s.

string initials(string& s)
{
    string returnvalue;

    returnvalue.push_back(s.at(0)); //add first char of s to returnvalue

    for(short x=0; x<s.length()-1; x++)//go forward through string
    {
        if ( s.at(x) ==' ')
        {
            if ( s.at(x+1) != ‘ ‘ )
                returnvalue.push_back( s.at(x+1) );
        }
    }

    return returnvalue;
}

We know that if there is a space at x, the char at x+1 will be the first letter of a word. I threw in a little check that only adds the char after a space if that’s not a space. This would prevent the function from messing up if there were two spaces in a row (yeah, I was bored). I’m trusting you to add the menu call yourself, don’t make me regret it Smile | :) (if you’re worried, check the full code of the program at the end of this tutorial).

We’re going to use the same principle to make a Caesar's key, that shifts letters up or down a certain number of places. We are going to use the method of adding numbers to chars, which increments their letter placement. However, once we go to z, the next four chars are ‘{‘ ‘|’ ‘}’ ‘~’ and further into the depths of the keyboard. When we subtract the same value from the char, we will get the same char we originally had, so that will serve as our decoder. You will notice when you test the program that if we use numbers much larger than 100, we will get a lot of ‘?’s, which don’t decode properly. If we used a higher Unicode encoding, such as 16, we could use greater numbers, but in Unicode-8, we are limited to codes up to about 50 for safe decoding.

string toCode(string& s)
{
    short codechoice;
    char letter;
    string returnvalue;

    cout << "pick a code " << endl;
    cin >> codechoice; //asks for and inputs code number

    for (short x=0; x<s.length(); x++)
    {
        letter=s.at(x);
        letter += codechoice; 
        returnvalue.push_back(letter);
    }

    return returnvalue;
}

We have the user select the code they wish to use, increment each char in the string by that number, then add the chars we made into our return string. If you enter whatever number you used for a code times negative-one, you will get your original message back if you didn’t use too high a code.

The next one is just another example of the same principles, used to remove the spaces from a string. You could do this with any character and ask the user to input what char they wanted to use, but I just stuck to spaces. This function will make more sense when it collaborates with our countLetters function later.

string removeSpaces(string& s)
{
    char letter;

    for (short x=0; x<s.length(); x++)
    {
        letter=s.at(x);
        if (letter==' ')
            s.erase(x,1);
    }
    return s;
}

We could have eliminated the letter variable and just checked the value directly if we wanted to, but this makes the example simpler. We use a new function, erase, to zap the space out of the string. erase takes two arguments, the index to start at and the number of places to erase after that. We just need to erase the char at the index we found with a space, so our length to erase is just 1. This is a vector function, so it works on them as well.

Here’s a brief example of erase in action:

#include <iostream>
using namespace std;

int main()
{
    string s;
    short index,length2erase;
    cout << “enter the string to erase from” << endl;
    getline(cin,s);
    cout << “enter the index to start erasing from” << endl;
    cin >> index;
    cout << “enter the number of chars to erase” << endl;
    cin >> length2erase;
    s.erase(index,length2erase);
    cout << “The erased string is “ << s << endl;

    return 0;
}

Play with it a little, you’ll quickly notice that you have to stay within the size of the string, which is always a concern with strings and vectors. Careful coding and user-restriction can usually prevent this from decimating your code.

Now, we'll try the opposite of the erase method: insert. Our challenge is to add commas to a number string in the appropriate places. We have to find the right indexes to put the commas, then use insert to insert them.

string addCommas(string& s)
{
    bool check=false;

    for (short x=s.length(); x>0; x--)
    {
        if( ( (s.length()-x) +1)%4 ==0 && check) //this is the money line. 
        {
            s.insert(x,",");
        }
        check=true;
    }
    return s;
}

The logic of the "money line" is kind of complicated. The loop goes backwards, so the first part finds the distance from the end of the string to the index. We modulus 4 because when we add the comma, we are adding another character to the string, and we have to account for the new character. The && check part accounts for the fact that we don't need to add a comma at the very end of the string, and since we normally have a comma after every three chars, we need to add one to the first part, which takes the place of the comma that would've been added. Basically, if the index we are at is divisible by four (three chars and the comma before them), we add a comma after those 3 chars.

We could've just moved three chars every time the loop ran, so we wouldn't need to check if we were on a third char, like this:

for (short x=s.length(); x>0; x-=3)
{
    if( ( (s.length()-x) +1) && check)
    //this is the money line. 
    {
        s.insert(x,",");
    }
    check=true;
}

This function is more useful for manipulating number strings than letter strings, so I'm going to leave it out of the full program code at the end, but feel free to add it to the menu!

The stuff before here has all been fairly easy, but now we’re going to delve into some more complicated stuff. Our next task will be to find the number of each letter that our string contains, and the percentage of the total string that that letter makes up. We will have a lot of loops, but bear with me.

We are going to have multiple vectors, one to store the quantity of each letter as a short, a list of all the characters we will store, and one to store the percentages as floats. Vectors are declared as follows:

vector <data_type> name(desired_size);

The data type stored in the vector; be it shorts, strings, objects, or other vectors, goes between the <>. The () constructor is where you tell the vector how large to make itself. This is simple with floats and ints, but with objects, it's more difficult. When you make a vector of 10 cars as such:

vector <car> parkingGarage(10);

Note that vectors store references to the objects they store, not actual copies. The vector tries to make 10 cars with default constructors. This may just require making a default constructor for your class, and storing meaningful objects in the vector later, or you can circumvent the problem by creating a vector without the () initializer and just using push_back to add objects to it. A common technique is to have a car class:

class car
{
    string license;

    car(string newlicense)
    {
        license=newlicense;
    }

};

vector <car> garage;

void makeCar(string s)
{
    car c(s);
    garage.push_back(c);
}

makeCar(“BAD MNKY”);

Car& findCarByLicense(string findLicense)
{
    for (short x=0; x<garage.size(); x++)
    {
        if ( garage.at(x).license == findLicense)
            return garage.at(x);
    }
    return;
}

This technique can be preferred if you just want to add an object to a data-structure, without out needing other access to it. I’ve used this in several programs to make lists of objects, and then to check it something matches any of the objects or other purposes.

But back to our function. First, we need a vector that holds all the characters we will store, so add a mention of vector <char> letters, to the top of the program, and add this code to the beginning of main:

letters.push_back('a');
letters.push_back('b');
letters.push_back('c');
//etc etc, see full code at end of article for copy pasting

Here’s the code for the function.

void findLetters(string& s)
{
    char letter;

    vector <int> numbers(31); 
    vector <float> percentages(31);

    for(short x=0; x<numbers.size(); x++)
    {
        numbers.at(x)=0;
    }

    for (int x=0; x<s.length(); x++)
    {
        letter=s.at(x);

        for (short x=0; x<letters.size(); x++)
        {
            if (letter==letters.at(x) )
            {
                numbers.at(x)++;
                break;
            }
        }
    }
    int total=s.length();

    for (short x=0; x<letters.size(); x++)
    {
        percentages.at(x)= numbers.at(x)*100/total;
        cout << letters.at(x) << " " 
             << numbers.at(x) << " " 
             << percentages.at(x) 
             << "%" << endl;
    }

    cout << "total: " << s.length() << endl;
}

We’ve made the letters vector global as we’ll use it in other functions, and the others we declare at the start of the function as they aren't going to be used anywhere else. First, we initialize all of the numbers vector to 0, then we have a loop that cycles through the characters and counts them. If the letter in the string matches the one in the letters vector, it increments the same position in the numbers vector. After all of the chars have been counted, we find all of the percentages by dividing the quantity of each letter by the total number of letters in the string, and display the letter, number, and percentage. Make sense?

Good, now we can go on to the mother-load, our Morse code translation. I initially did this with a giant switch, of course, but I knew that there was a better way. This way actually works on the same principle, just looks nicer. We have another global vector along with the letters one, called morse. Add a declaration next to that of the letter one: vector <string> morse;. As our Morse code letter equivalents are multiple *’s and -’s, we need to use strings to handle them. Add the code to fill up the vector to the beginning of main with the letters stuff:

Here’s the code for the letters to Morse function:

string toMorse(string& s)
{
    char letter;
    string returnvalue;

    for (short x=0; x<s.length(); x++)
    {
        letter=s.at(x);

        for (short a=0; a<letters.size(); a++)
        {
            if (letter== letters.at(a) )
            {
                returnvalue=returnvalue+morse.at(a);
                break;
            }
        }


        returnvalue.push_back(' ');
    }
    return Morse;
}

It’s a lot less messy than the giant switch we mentioned. We take each of the chars from s, and concatenate the Morse code of it to the return value, along with a space. (Also note that there are no Morse code equivalents for all possible keyboard characters, so this function used in collaboration with our letter shifting function may not result in accurate decoding.)

Going backwards is harder, because we need to find the strings of Morse that make up one letter. That is the major difference in the Morse-to-letter function.

string fromMorse(string& s)
{
    string morseSnippet;
    string returnvalue;
    short idx=0;

    for (short x=0; x<s.length(); x++)
    {
        if (s.at(x) == ' ')
        {
            morseSnipper=s.substr(idx, (x-idx) );
            idx=x+1;

            for (int a=0; a<morse.size(); a++)
            {
                if ( morseSnippet == morse.at(a) )
                {
                    returnvalue.push_back( letters.at(a) );
                    break;
                }
            }

        }//end if

    }//end loop

    return returnvalue;
}

The first thing that should catch your eye is the substr part. Okay, here’s the lowdown: we find a space in the string, take the little sub-string from idx to the space, which is found by taking the idx, and the number of chars to the next space, found by idx-x. We set idx to 0 for the first Morse char. After we extract the substring, we need to set idx to the start of the next Morse piece, being the char directly after the space. After that, it is just a matter of comparing the strings and doing the reverse of what we did in the last function. substr is like erase, taking the string from the first number, forwarding the number of chars in the second arg. Modify the test program we had for erase to use substr, and observe the results. There's a lot of logic involved, so don't be afraid if you don't get it at first. Try changing the code to see what happens.

It is easy to go back and forth between Morse code and letters because our vectors work sort of like a two way dictionary, allowing us to convert back and forth easily. We have to make sure that they line up properly; otherwise, all of our letters will be scrambled! But then again, that's another great way to make a coded message...

That’s all of the functions in the code for the whole program, and a link to a description of the rest of the string methods are at the end of this tutorial.

The real fun of this is that you can do multiple things to the same string, such as reversing it, removing the spaces, shifting all of the letters up 3 values, putting the whole thing into Morse code, and then sending it to a friend to see if they can decode it! Have fun!

So that’s all I have for playing with strings for you today; if you have any other functions that could be done with strings, post them, and I’ll try to include them in this tutorial. Please post any and all comments/questions/concerns. Thanks for reading!

All of the C++ string member functions can be viewed at here.

#include <iostream>
#include <vector>
using namespace std;

vector < string > morse;
vector < char > letters;


string fromMorse(string& s)
{
    string Morse;
    string returnvalue;
    short idx=0;
   
    for (short x=0; x<s.length(); x++)
    {
        if (s.at(x) == ' ')
        {
            Morse=s.substr(idx, (x-idx) );
            idx=x+1;
   
            for (int a=0; a<morse.size(); a++)
            {
                if ( Morse == morse.at(a) )
                {
                    returnvalue.push_back( letters.at(a) );
                    break;
                }
            }
               
        }//end if
   
    }//end loop
   
    return returnvalue;
}

string toMorse(string& s)
{
    char letter;
    string Morse;
   
    for (short x=0; x<s.length(); x++)
    {
        letter=s.at(x);
       
        for (short a=0; a<letters.size(); a++)
        {
            if (letter== letters.at(a) )
            {
                Morse=Morse+morse.at(a);
                break;
            }
        }
       
           
        Morse.push_back(' ');
    }
    return Morse;
}

string reverse(string& s)
{
    string flipped;
   
    for(short x=s.length()-1; x>-1; x--)
    {
        flipped.push_back(s.at(x));
    }
    return flipped;
}

string repeat(string& s)
{
    string returnvalue;
   
        for (int x=0; x<10; x++)
        {
            returnvalue+=s;
        }
   
    return returnvalue;
}


string toCode(string& s)
{
    short codechoice;
    char letter;
    string returnvalue;
   
    cout << "pick a code " << endl;
    cin >> codechoice;
   
    for (short x=0; x<s.length(); x++)
    {
        letter=s.at(x);
        letter += codechoice;
        returnvalue.push_back(letter);
    }
   
    return returnvalue;
}

string initials(string& s)
{
    string returnvalue;
   
    returnvalue.push_back(s.at(0));
   
    for(short x=0; x<s.length()-1; x++)
    {
        if ( s.at(x) ==' ')
        {
            returnvalue.push_back( s.at(x+1) );
        }
    }
   
    return returnvalue;
}
           
string removeSpaces(string& s)
{
    char letter;
       
    for (short x=0; x<s.length(); x++)
    {
        letter=s.at(x);
        if (letter==' ')
            s.erase(x,1);
    }
    return s;
}

void findLetters(string& s)
{
    char letter;
   
    vector <int> numbers(31);
    vector <float> percentages(31);
   
    for(short x=0; x<numbers.size(); x++)
    {
        numbers.at(x)=0;
    }
   
    for (int x=0; x<s.length(); x++)
    {
        letter=s.at(x);
       
        for (short x=0; x<letters.size(); x++)
        {
            if (letter==letters.at(x))
            {
                numbers.at(x)++;
                break;
            }
        }
    }
   
    int total=s.length();
   
    for (short x=0; x<letters.size(); x++)
    {
        percentages.at(x)= numbers.at(x)*100/total;
        cout << letters.at(x) << "  " << numbers.at(x) 
             << "  " << percentages.at(x) 
             << "%" << endl;
    }
   
    cout << "total:  " << s.length() << endl;
}
               

int main (int argc, char * const argv[])
{
    morse.push_back("*-");
    morse.push_back("-***");
    morse.push_back("-*-*");
    morse.push_back("-**");
    morse.push_back("*");
    morse.push_back("**-*");
    morse.push_back("--*");
    morse.push_back("****");
    morse.push_back("**");
    morse.push_back("*---");
    morse.push_back("-*-");
    morse.push_back("*-**");
    morse.push_back("--");
    morse.push_back("-*");
    morse.push_back("---");
    morse.push_back("-**-");
    morse.push_back("--*-");
    morse.push_back("*-*");
    morse.push_back("***");
    morse.push_back("-");
    morse.push_back("**-");
    morse.push_back("***-");
    morse.push_back("*--");
    morse.push_back("-**-");
    morse.push_back("-*--");
    morse.push_back("--**");
    morse.push_back("\n");
    morse.push_back("**--**");
    morse.push_back("-*-*--");
    morse.push_back("*-*-*-");
    morse.push_back("--**--");
           
    letters.push_back('a');
    letters.push_back('b');
    letters.push_back('c');
    letters.push_back('d');
    letters.push_back('e');
    letters.push_back('f');
    letters.push_back('g');
    letters.push_back('h');
    letters.push_back('i');
    letters.push_back('j');
    letters.push_back('k');
    letters.push_back('l');
    letters.push_back('m');
    letters.push_back('n');
    letters.push_back('o');
    letters.push_back('p');
    letters.push_back('q');
    letters.push_back('r');
    letters.push_back('s');
    letters.push_back('t');
    letters.push_back('u');
    letters.push_back('v');
    letters.push_back('w');
    letters.push_back('x');
    letters.push_back('y');
    letters.push_back('z');
    letters.push_back(' ');
    letters.push_back('?');
    letters.push_back('!');
    letters.push_back(',');
    letters.push_back('.');

    cout << "remember: " << endl
        << "0 enter new word" << endl
        << "1 repeat" << endl
        << "2 reverse" << endl
        << "3 translate to morse code" << endl
        << "4 translate from morse code" << endl
        << "5 secret code" << endl
        << "6 find initials" << endl
        << "7 remove spaces" << endl
        << "8 find letters" << endl;

    for(short y=0; y<100; y++)
    {    
        cout << "enter the word/sentence: ";
   
        string thestring;
        getline(cin , thestring);
   
        for(short x=0; x<100; x++)
        {
            short choice;
            cin >> choice;
            if (choice==0)
            {
                break;
            }

            switch(choice)
            {
                case 1:
                    thestring=repeat(thestring);
                    break;
                case 2:
                    thestring=reverse(thestring);
                    break;
                case 3:
                    thestring=toMorse(thestring);
                    break;
                case 4:
                    thestring=fromMorse(thestring);
                    break;
                case 5:
                    thestring=toCode(thestring);
                    break;
                case 6:
                    thestring=initials(thestring);
                    break;
                case 7:
                    thestring=removeSpaces(thestring);
                    break;
                case 8:
                    findLetters(thestring);
            }

            cout << thestring << endl;

        } //end nested loop
   
        cin.ignore();
   
    }// end first loop
    return 0;
}

Points of Interest

For non-beginners: it took me a little while to modify this tutorial. This is actually how it started out, but then I thought I should put the functions in a class, so the vectors of the alphabet and Morse wouldn't be global variables. But then, I wanted to make them static in the class, and that's hard in C++ without static initializers like C# and Java has. See here.

This article is also posted here.

History

  • 7-30-09: Submitted tutorial.

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)

Share

About the Author

CrazyJugglerDrummer

United States United States
I'm currently in the 9th grade taking my first semester of calculus for college credit. I was interested in math, so I quickly grew to programming and loved it. I am working on C++, java, C#, python, html/css/javascript, and php. Hoping to go to MIT and work in a programming field (not sure which to go for yet). Big Grin | :-D

Comments and Discussions

 
Questionrequest PinmemberMember 107190033-Apr-14 14:27 
Questionhello :) PinmemberMember 107190032-Apr-14 5:00 
GeneralNice Job - but ',' Comma (**- - **) is swapped with '.' Period (*-*-*-) Pinmemberphill_mn4-Aug-09 4:32 
GeneralRe: Nice Job - but ',' Comma (**- - **) is swapped with '.' Period (*-*-*-) PinmemberCrazyJugglerDrummer6-Aug-09 14:25 
GeneralRe: Nice Job - but ',' Comma (- - **- -) is swapped with '.' Period (*-*-*-) Pinmemberphill_mn7-Aug-09 3:10 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.

| Advertise | Privacy | Mobile
Web01 | 2.8.141015.1 | Last Updated 30 Jul 2009
Article Copyright 2009 by CrazyJugglerDrummer
Everything else Copyright © CodeProject, 1999-2014
Terms of Service
Layout: fixed | fluid