Page 1 of 1

Strings and Vectors tutorial through examples: Morse Code and more Intro to string operations and applications Rate Topic: ***** 1 Votes

#1 crazyjugglerdrummer  Icon User is offline

  • GAME OVER. NERD WINS.
  • member icon

Reputation: 119
  • View blog
  • Posts: 690
  • Joined: 07-January 09

Posted 24 February 2009 - 07:01 PM

Hey everyone, this is a tutorial through example of how to use vectors and C++ strings. You should have a basic understanding of if, else, switch, loops, functions, simple IO, and a basic understanding of objects. If you do not completely understand any of these, there are plenty of tutorials here on those as well :) Strings are basically a vector of characters, with a few different methods and operations. But keep in mine that the theory is the same. 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.

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;

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);
	
	for(short x=0; x<100; x++) //This is the loop where the string will be
					 //processed in different ways.
	{
		short choice;
		cin >> choice;
		if (choice==0)
			break;
		
		switch(choice)  //the menu for different operations
		{
			case 1:
		 			thestring=repeat(thestring);
				break;
		}
		
		cout << thestring << endl;  //display string after processing
				
	} //end nested loop
	
	cin.ignore();  //clears input for new string
	
}// end first loop
	return 0;
}


We’ll gradually add more cases to the menu switch as we move on.

So our first challenge is to repeat the string 10 times. We will use concentation to add a copy of the string onto its end. All of our function will take string& as an argument and return 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 concentated 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 at returnvalue=returnvalue+s
	}
	
	return returnvalue;
}


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

So know onto something a little harder: reversing a string. Now 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 will use at() just to be safe as it provides bounds checking, and to emphasize that vectors are not arrays. 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 strings 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 passes 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 + concentating 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 :) (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 ar ‘{‘ ‘|’ ‘}’ ‘~’ 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. :D

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 that letter strings, so we're 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 its 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 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 () intializer 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');
			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('.');



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 letter vector global as we’ll use it our last 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 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:
						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("**--**"); //punctuation translations
			morse.push_back("-*-*--");
			morse.push_back("*-*-*-");
			morse.push_back("--**--");



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 concentate the morse code of it to the returnvalue, along with a space. (Also note that there are not 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, its 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, forward the number of chars in the second arg. Modify the test program we had for erase to use substr, and observe the results.


That’s all of the functions 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 or a future tutorial. I'll gladly take any feedback! Thanks for reading!

#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;
}



All of the C++ string member functions can be viewed at http://www.cplusplus...ing/insert.html.

This post has been edited by crazyjugglerdrummer: 28 February 2009 - 04:32 PM


Is This A Good Question/Topic? 0
  • +

Page 1 of 1