2
\$\begingroup\$

I have a challenge:

In Sweden, there is a simple child's game similar to Pig Latin called Rövarspråket (Robbers Language).

In the CCC version of Rövarspråket, every consonant is replaced by three letters, in the following order:

  • the consonant itself;
  • the vowel closest to the consonant in the alphabet (e.g. d → e), with the rule that if the consonant falls exactly between two vowels, then the vowel closer to the start of the alphabet will be chosen (e.g. c → a);
  • the next consonant in the alphabet following the original consonant (e.g. d → f) except if the original consonant is z, in which case the next consonant is z as well.

Vowels (a, e, i, o, u) in the word remain the same.

Write a program that translates a word from English (one line of no more than 30 lower-case letters) into Rövarspråket. Example: "joy""jikoyuz".

Here is my working solution:

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

int main()
{
    string message, newmessage="";

    cin>>message;

    for(int i=0; i<message.length(); ++i){
        if(message[i]=='a'||message[i]=='e'||message[i]=='i'||message[i]=='o'||message[i]=='u'){
            newmessage+=message[i];
        }
        else if(message[i]=='b'||message[i]=='c'||message[i]=='d'||message[i]=='f'||message[i]=='g'||
            message[i]=='h'||message[i]=='j'||message[i]=='k'||message[i]=='l'||message[i]=='m'||
            message[i]=='n'||message[i]=='p'||message[i]=='q'||message[i]=='r'||message[i]=='s'||
            message[i]=='t'||message[i]=='u'||message[i]=='v'||message[i]=='w'||message[i]=='x'||
            message[i]=='y'||message[i]=='z'){

            //the letter itself
            newmessage+=message[i];

            //the vowel closest to the consonant
            if(message[i]=='b'){newmessage+="a";}
            else if(message[i]=='c'){newmessage+="a";}
            else if(message[i]=='d'){newmessage+="e";}
            else if(message[i]=='f'){newmessage+="e";}
            else if(message[i]=='g'){newmessage+="e";}
            else if(message[i]=='h'){newmessage+="i";}
            else if(message[i]=='j'){newmessage+="i";}
            else if(message[i]=='k'){newmessage+="i";}
            else if(message[i]=='l'){newmessage+="i";}
            else if(message[i]=='m'){newmessage+="o";}
            else if(message[i]=='n'){newmessage+="o";}
            else if(message[i]=='p'){newmessage+="o";}
            else if(message[i]=='q'){newmessage+="o";}
            else if(message[i]=='r'){newmessage+="o";}
            else if(message[i]=='s'){newmessage+="u";}
            else if(message[i]=='t'){newmessage+="u";}
            else if(message[i]=='v'){newmessage+="u";}
            else if(message[i]=='w'){newmessage+="u";}
            else if(message[i]=='x'){newmessage+="u";}
            else if(message[i]=='y'){newmessage+="u";}
            else if(message[i]=='z'){newmessage+="u";}

            //the next consonant in the alphabet
            if(message[i]=='b'){newmessage+="c";}
            else if(message[i]=='c'){newmessage+="d";}
            else if(message[i]=='d'){newmessage+="f";}
            else if(message[i]=='f'){newmessage+="g";}
            else if(message[i]=='g'){newmessage+="h";}
            else if(message[i]=='h'){newmessage+="j";}
            else if(message[i]=='j'){newmessage+="k";}
            else if(message[i]=='k'){newmessage+="l";}
            else if(message[i]=='l'){newmessage+="m";}
            else if(message[i]=='m'){newmessage+="n";}
            else if(message[i]=='n'){newmessage+="p";}
            else if(message[i]=='p'){newmessage+="q";}
            else if(message[i]=='q'){newmessage+="r";}
            else if(message[i]=='r'){newmessage+="s";}
            else if(message[i]=='s'){newmessage+="t";}
            else if(message[i]=='t'){newmessage+="v";}
            else if(message[i]=='v'){newmessage+="w";}
            else if(message[i]=='w'){newmessage+="x";}
            else if(message[i]=='x'){newmessage+="y";}
            else if(message[i]=='y'){newmessage+="z";}
            else if(message[i]=='z'){newmessage+="z";}
        }
    }

    cout<<newmessage;

    return 0;
}

I would like to avoid hardcoding.

Is there any way to avoid using all those if else statements?

\$\endgroup\$
2
  • \$\begingroup\$ A std::map and a textual format of your choice should be sufficient to make thise code data driven. \$\endgroup\$
    – BlamKiwi
    Feb 19, 2015 at 6:02
  • \$\begingroup\$ There are a great many ways to avoid the hardcoding... one simple one is having search and replacement strings, basic idea: std::string lhs("bcdfghjklmnpqrstvwxyz"), rhs("aeeeiiiiooooouuuuuuu"); auto it = lhs.find(message[i]); if (it != lhs.end()) newmessage += rhs[it - lhs.begin()];. \$\endgroup\$
    – Tony D
    Feb 19, 2015 at 6:08

5 Answers 5

4
\$\begingroup\$

You want to determine whether something is a vowel, and for consonants which is the "closet" vowel and the "next" consonant.

If you implement these as functions,

bool isVowel(char c);
char closestVowel(char c);
char nextConsonant(char c);

your program becomes a straightforward translation of the rules:

for(int i = 0; i < message.length(); ++i){
    char c = message[i];
    if (isVowel(c))
    {
        newmessage += c;
    }
    else
    {
        newmessage += c;
        newmessage += closestVowel(c);
        newmessage += nextConsonant(c);
    }
}

I won't spoil the homework by writing the three functions, but note that isVowel can be useful in the implementation of the other two.

\$\endgroup\$
3
\$\begingroup\$

You could contain all this in an std::map.

map<string, string> letters;
letters["a"] = "a";
letters["b"] = "bac";
letters["c"] = "cad";
letters["d"] = "def";
// and so on...

To access them:

for(int i=0; i<message.length(); ++i){
  newmessage += letters[message[i]]
}
\$\endgroup\$
1
  • \$\begingroup\$ Note: The same effect can be achieved by a switch statement on the input character (which does not require std::map). Also, std::unordered_map<> is more performant than std::map<> in almost all cases, this one included. \$\endgroup\$
    – cmaster
    Feb 19, 2015 at 6:46
2
\$\begingroup\$

Things to consider here are how the inputs change. If we are told that the vowels and the alphabets are absolutely never going to change, then a look-up table would be the way to go - fast and clear.

However, if there is no guarantee about the the vowels and alphabets and we want to be prepared to handle a different set of vowels and alphabets and possibly a different logic, then it might be better to code the logic of the translator.

Another interesting thing that you could do, is make a look-up builder, that will use the logic in the code and construct a look-up table when the program first runs and simply reads from the look-up for every input.

If this is a programming Home-work, it maybe expected to code the logic of the translator. But its always important to think about the trade-offs here and understand the different ways in which a problem can be solved.

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

static const char INVALID_CHAR = ' ';

static const string vowels = "aeiou";
static const string alphabets = "abcdefghijklmnopqrstuvwxyz";

bool IsVowel( char ch )
{
    // find_first_of returns string::npos if character was not found
    if ( vowels.find_first_of( ch ) == string::npos )
    {
        return false;
    }
    return true;
}

char GetNextConsonant( char ch )
{
    int positionInAlphabets = alphabets.find_first_of( ch );

    for ( int i = positionInAlphabets + 1; i < alphabets.length(); i++ )
    {
        char nextCharInAlphabets = alphabets[i];
        if ( !IsVowel( nextCharInAlphabets ) )
        {
            return nextCharInAlphabets;
        }
    }

    return INVALID_CHAR;
}

char GetClosestVowel( char ch )
{
    int positionInAlphabets = alphabets.find_first_of( ch );

    char foundVowelGoingRight = INVALID_CHAR;
    char foundVowelGoingLeft = INVALID_CHAR;

    int distanceGoingRight = 0;
    int distanceGoingLeft = 0;

    for ( int i = positionInAlphabets + 1; i < alphabets.length(); i++ )
    {
        distanceGoingRight++;

        char nextCharInAlphabets = alphabets[i];

        if ( IsVowel( nextCharInAlphabets ) )
        {
            foundVowelGoingRight = nextCharInAlphabets;
            break;
        }

    }

    for ( int i = positionInAlphabets - 1; i >= 0; i-- )
    {
        distanceGoingLeft++;

        char nextCharInAlphabets = alphabets[i];

        if ( IsVowel( nextCharInAlphabets ) )
        {
            foundVowelGoingLeft = nextCharInAlphabets;
            break;
        }
    }

    if ( foundVowelGoingLeft != INVALID_CHAR && foundVowelGoingRight != INVALID_CHAR )
    {
        if ( distanceGoingLeft <= distanceGoingRight )
        {
            return foundVowelGoingLeft;
        }
    }
    else if ( foundVowelGoingLeft != INVALID_CHAR )
    {
        return foundVowelGoingLeft;
    }
    else if ( distanceGoingRight != INVALID_CHAR )
    {
        return foundVowelGoingRight;
    }

    return INVALID_CHAR;
}

int main()
{

    string inputMessage;
    string outputMessage = "";

    cout << "Enter Message : ";
    cin >> inputMessage;

    for ( int i = 0; i < inputMessage.length(); i++ )
    {
        bool isVowel = IsVowel( inputMessage[i] );

        // the consonant itself
        outputMessage += inputMessage[i];

        if ( !isVowel )
        {
            outputMessage += GetClosestVowel( inputMessage[i] );
            outputMessage += GetNextConsonant( inputMessage[i] );
        }

    }

    cout << outputMessage;

    return 0;
}
\$\endgroup\$
0
1
\$\begingroup\$

I think operator OR (||) you can replace by creating a std::set and using it method "find()" to check if specific char is in this set. You can create 2 sets: one for vowel and another for consonant.

UPDATE: And instead of many if-else you can pre-define std::map<char, char> the_map and use newmessage += the_map[message[i]] instead of if (message[i]=='s'){newmessage+="u";}

\$\endgroup\$
1
\$\begingroup\$

Your main() is doing way too much work. It should be short and simple, like this:

int main() {
    std::string in;
    std::cin >> in;
    std::cout << rovarspraket(in) << std::endl;
}

Note that it is customary to print a line terminator at the end of the output.

It's also impossible to see the intention of the code. You need to define some supplementary functions that help you implement the specification. I've used a variety of strategies:

  • strchr()
  • ASCII value comparisons and incrementation
  • lookup table
#include <cstring>
#include <iostream>
#include <string>

bool isVowel(char c) {
    return strchr("aeiou", c);
}

bool isConsonant(char c) {
    return 'a' < c && c <= 'z' && !isVowel(c);
}

char closestVowel(char c) {
    static const char *in  = "abcdefghijklmnopqrstuvwxyz";
    static const char *out = "aaaeeeeiiiiioooooouuuuuuuu";
    char *p = strchr(in, c);
    return p ? out[p - in] : '?';
}

char nextConsonant(char c) {
    if (c == 'z') {
        return 'z';
    }
    do {
        c++;
    } while (isVowel(c));
    return c;
}

std::string rovarspraket(const std::string &in) {
    std::string out;
    for (std::string::const_iterator c = in.begin(); c != in.end(); ++c) {
        out += *c;
        if (isConsonant(*c)) {
            out += closestVowel(*c);
            out += nextConsonant(*c);
        }
    }
    return out;
}
\$\endgroup\$
2
  • \$\begingroup\$ Why can't my main do all the work? \$\endgroup\$ Mar 14, 2015 at 20:37
  • \$\begingroup\$ Computer science is all about how to manage complexity by splitting up the problem into understandable chunks. Each of the functions I have proposed performs a specific transformation on its parameter, which you can easily verify because each contains just a few lines of code with no side effects (i.e., no global variables). You can easily see that main() reads a string and prints the Rövarspråket version of it. And the rovarspraket() function reads very much like the problem specification. In contrast, when main() does everything, you have to analyze all of it at once. \$\endgroup\$ Mar 14, 2015 at 22:31

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.