Final: Telephone Keypad - Converting numbers to letters

So I am going to keep the explanation of my assignment simple, by posting what we are to do for the assignment below then explaining what I am having trouble with.

Assignment:

Write a program that prompts the user for a seven-digit number and writes to a file every possible seven-letter word corresponding to that number. User should be prompted to choose the appropriate filename. There are 2187 (3 to the seventh power) such words. Avoid phone numbers with the digits 0 and 1. Submit your file along with the code.
2 = a b c
3 = d e f
4 = g h i
5 = j k l
6 = m n o
7 = p q r s
8 = t u v
9 = w x y z


Issues:

So I have been trying to find other forums on this but I can only find threads that convert the letters on the keypad to the numbers, not the other way around. Just as well they are not storing them into a file. Now I did find the ASCII table on the internet. Should I be using that rather than trying to use a switch statement? Second, I understand how to send basic information to a .txt file, but I am having a hard time understanding how to write to it so that it corresponds with every possible seven-letter word that corresponds to the number that the user inputs and how to make it so that the program prompts the user to choose the filename they want. Could someone shed some light on this for me and possibly show me how the coding for it works? Thanks to anyone in advance!
Last edited on
Do you have any code so far?
This is what I have so far but I'm unsure if it will work as well as unsure of what to do next if it does.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
#include<iostream>
#include<string>
#include<fstream>

using namespace std;
// main function
int main()
{
	// opening statement for user
	cout << "Converting numbers to letters on a phone keypad\n" << "Formatted as (###.###.####)\n" << "Please enter phone number\n";
	// setting variables
	string letter;
	getline(cin, letter);
	cout << endl;

	char phrase[12];
	int phoneNum;
	int MAX = 12;

	for (unsigned int x = 0; x < 12; ++x)
		phrase[x] = letter[x];
	//checking numbers entered for letters using ASCII Table
	//http://www.asciitable.com/
	for (int x = 0; x < MAX; ++x)
	{
		if (isalnum(phrase[x]))
		{
			if (phoneNum = 2)
				phrase[x] = 97 || 98 || 99;
			if (phoneNum = 3)
				phrase[x] = 100 || 101 || 102;
			if (phoneNum = 4)
				phrase[x] = 103 || 104 || 105;
			if (phoneNum = 5)
				phrase[x] = 106 || 107 || 108;
			if (phoneNum = 6)
				phrase[x] = 109 || 110 || 111;
			if (phoneNum = 7)
				phrase[x] = 112 || 113 || 114 || 115;
			if (phoneNum = 8)
				phrase[x] = 116 || 117 || 118;
			if (phoneNum = 9)
				phrase[x] = 119 || 120 || 121 || 122;
		}
	}
	return 0;
}

phrase[x] = 97 || 98 || 99;

What are you trying to do here? The expression 97 || 98 || 99 will evaluate to a boolean, that will always be true, so phrase[x] will always have the value 1.

It looks as though you're trying to store some indication that phrase[x] can be one of the values 97, 98 or 99, but you can't do that. However, phrase[x] is an int char, so, unsurprisingly, all it can store is a single integer value.

If you want to store all the possible values for each digit, you'll have to make phrase an array of arrays, i.e. a 2-dimensional array. Although I'm not sure that's the best algorithm for what you're doing anyway. You might be better off just looping over the array of digits, and converting to the possible characters on the fly.

Also, you need to remember that = is for assignment, not for doing an equality comparison. The equality comparison operator is ==.

(EDIT: Corrected a mistake in the type of phrase[x])
Last edited on
Store your integer to possible characters mapping as, e.g., an array of strings; say:
vector<string> keyboard = { "", "", "abc", "def", "ghi", "jkl", "mno", "pqrs", "tuv", "wxyz" };

Keep an expansible collection of possible words, say:
vector<string> wordlist;

Get a sequence of digits from the user, say:
vector<int> digits(7);
Eventually, write a function to get these; whilst developing, just hard-code them and use a small number, say 2 or 3, rather than going straight on to 7.


For the first digit, push_back() its individual characters into wordlist[];
e.g. if digit is 5, then wordlist[] will contain "j", "k", "l".

For the second digit, then FOR EACH OF the strings in wordlist[] and FOR EACH OF the characters associated with that digit, push_back() their combination (string+char) into a temporary string array, say:
vector<string> temp;
At the end of this, set wordlist = temp and clear temp ready to deal with the next digit.
e.g. if second digit is 2, then, continuing the example above, wordlist[] will contain
"ja", "jb", "jc", "ka", "kb", "kc", "la", "lb", "lc"


Repeat for the third digit and so on.


When I tried it I ended up with code with three nested loops, here in pseudo-code:
for ( ints in digits[] )
{
   for ( strings in wordlist[] )
   {
      for ( chars associated with that digit )
      {
          push_back the string + char into temp
      }
   }
   reassign wordlist[] and clear temp[]
}

The order of the two inner loops can be reversed.


Finally, dump wordlist[] to file.


Since some digits have 4 characters associated with them, you will actually end up with between 37 and 47 entries.
Last edited on
I need to make it so that when the user inputs a phone number it takes the last 7 numbers of the phone number because the area code is not included, and make it equal 3 or 4 possible letters depending on each individual number from the user phone number. Then I need to store the numbers in a file where it will then output all the possible words that can be created with the numbers. Then the user picks which word or phrase they want. The phrase[x] = 97 || 98 || 99; , in my head, would be saying, when the individual number = 2 the letter could be a, b, or c. And it would do that for all the numbers. (I know I said the last 7 but I was just trying to find a way for it to work for all of them and then go back and edit it to check just the last 7 later) I was under the impression that because the phrase[x] variable was a character it would see the phrase[x] = 97 || 98 || 99; as phrase[x] = a || b || c;. Should I not be using the ASCII table for this? Honestly, I really don't know what the correct way to do this is, I was trying what I thought could be a way to get what I needed but I hit a wall, which tells me I am missing something or I am just wrong with my method.

By looping over the array, do you mean storing the values in an array and then sending them to the external file in the loop, and then overwriting the array with new data and sending that until the loop is finished?
The phrase[x] = 97 || 98 || 99; , in my head, would be saying, when the individual number = 2 the letter could be a, b, or c. And it would do that for all the numbers. (I know I said the last 7 but I was just trying to find a way for it to work for all of them and then go back and edit it to check just the last 7 later) I was under the impression that because the phrase[x] variable was a character it would see the phrase[x] = 97 || 98 || 99; as phrase[x] = a || b || c;.

So, in other words, my guess was right.

Should I not be using the ASCII table for this?

It has nothing to do with whether or not you use the ASCII table (although I don't see what you achieve by using the numerical codes rather than the actual characters). It's to do with the fact that you can't store three different characters in a single char.

What your code is doing is storing a single number in phrase[x], and that number is always 1. Do you understand why?
Last edited on
Yeah, I see it now because it is testing whether it is true or false and it will always be one because it is true. I think I am just overthinking what the assignment is asking. I didn't see lastchance's post till just now and it is making more sense to me know. I am going to work on it and see what I get. Thanks for the help so far guys
@lastchance You said to use a function to get the digits from the user. Should I use a vector function or a regular function? I have minimal experience with vectors since our professor never went into depth on how they are used.
Ultimately, I suggest that you write a function getNumber() which will take the input from the user and return an array (vector) of integer digits. In main() you would then just write
vector<int> digits = getNumber();

However, this is NOT the principal element of the assignment and I suggest that you first of all hard-code the setting of digits by writing
vector<int> digits = { 2, 3, 4 };
in main() and get other things working. Note that I have reduced it to 3 digits here to keep output to a minimum during testing and development; if you get everything working with for() loops then changing 3 digits to 7 takes minimal effort.

Similarly, develop your code by writing output to screen. Switching to output to file can be done later, once everything else is working.

Make sure that you do code development with pen and paper handy to follow calculations sequentially. (The classic rubber-duck debugging tends to come in useful too.)


Incidentally, your assignment just says a "7-digit number": it doesn't say it is formatted in any fancy way. That just over-complicates things.
Last edited on
closed account (48T7M4Gy)
This is all a bit too hard.

Why not work backwards from a word list, selecting all 7 letter words that don't have apostrophes etc from it, and generate the numerical phone numbers? I haven't tested this with my array of wordlists which amount to about 40,000 words but my guess is there are more than 2187 7 letter words.
I have been working on it while I was waiting to hear back and that is basically what I have been doing lastchance. I have been trying to hard code it and trying to get other things working until I figure out the rest.

Working backward wouldn't be a bad idea but I've already got this going and really don't want to start over again. Especially since I think I am starting to understand what is going on with what we have been talking about.
admittedly hackish, please make this a bit more elegant:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
#include <iostream>
#include <map>
#include <vector>
#include <algorithm>
#include <string>
#include <cctype>


using Keypad = std::map<char, std::vector<char>>;

const Keypad keys{{'2', {'a','b','c'}}, {'3', {'d','e','f'}}, {'4', {'g','h','i'}}, {'5',{'j','k','l'}},
        {'6', {'m','n','o'}}, {'7', {'p','q','r','s'}}, {'8',{'t','u','v'}}, {'9',{'w','x','y','z'}}};

int main()
{
    std::cout << "Enter telephone number: \n";
    std::string telNumber{};
    getline(std::cin, telNumber);
    if((telNumber.size() != 7) || !(std::all_of(telNumber.begin(), telNumber.end(), ::isdigit)))
    {
        std::cout << "invalid number \n";
    }
    std::vector<std::vector<char>> telNumberKeys{};
    for (const auto& elem : telNumber)
    {
        telNumberKeys.push_back(keys.find(elem)->second);
    }
    for (const auto& elem0 : telNumberKeys[0])
    {
        for (const auto& elem1 : telNumberKeys[1])
        {
            for (const auto& elem2 : telNumberKeys[2])
            {
               for (const auto& elem3 : telNumberKeys[3])
               {
                   for (const auto& elem4 : telNumberKeys[4])
                   {
                       for (const auto& elem5 : telNumberKeys [5])
                       {
                           for (const auto& elem6 : telNumberKeys [6])
                           {
                               std::string myString  = std::string(1, elem0) + std::string(1, elem1) + std::string(1, elem2)
                            + std::string(1, elem3) + std::string(1, elem4) + std::string(1, elem5) + std::string(1, elem6);

                                std::cout << myString << "\n";
                           }
                       }
                   }
               }
            }
        }
    }
}



using variadic templates, operator overloading and std::accumulate with std::multiplies<>() to clean up the above program:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
#include <iostream>
#include <map>
#include <vector>
#include <algorithm>
#include <string>
#include <cctype>

template<typename T>
const std::vector<T>& multiplication (const std::vector<T>& v) {
  return v;
}

template<typename T>
std::vector<T> operator * (const std::vector<T>& lhs, const std::vector<T>& rhs)
{
    std::vector<T> multVec{};
    for (const auto& elemLHS : lhs)
    {
        for (const auto& elemRHS : rhs)
        {
            multVec.push_back(std::move(elemLHS+elemRHS));
        }
    }
    return multVec;
}
template <typename T, typename... Args>
std::vector<T> operator * (const std::vector<T>& lhs, Args... args)
{
    return lhs * multiplication(args...);
}

using Keypad = std::map<char, std::vector<std::string>>;

const Keypad keys{{'2', {"a","b","c"}}, {'3', {"d","e","f"}}, {'4', {"g","h","i"}}, {'5',{"j","k","l"}},
        {'6', {"m","n","o"}}, {'7', {"p","q","r","s"}}, {'8',{"t","u","v"}}, {'9',{"w","x","y","z"}}};

int main ()
{
    std::cout << "Enter telephone number: \n";
    std::string telNumber{};
    getline(std::cin, telNumber);
    if((telNumber.size() != 7) || !(std::all_of(telNumber.begin(), telNumber.end(), ::isdigit)))
    //also check no 1 or 9
    {
        std::cout << "invalid number \n";
    }
    std::vector<std::vector<std::string>> telNumberKeys{};
    for (const auto& elem : telNumber)
    {
        telNumberKeys.push_back(keys.find(elem)->second);
    }
    auto result = std::accumulate(telNumberKeys.begin()+1, telNumberKeys.end(), telNumberKeys[0], std::multiplies<>());

   for (const auto& elem : result)std::cout << elem << "\n";
}
@gunnerfunner
Now it's my turn to point out compiler errors ;)

I wrote a SSCCE: http://coliru.stacked-crooked.com/a/b37cb217d8a36261

GCC accepts incorrect code in this case, but this shouldn't compile and indeed Clang chokes on the program.
std::multiplies is defined before std::vector<T> operator * (const std::vector<T>& lhs, Args... args) and therefore the operator* isn't visible at that point. In other words, unqualified name lookup happens at the name, not at the point of instantiation, and operator* isn't found.

You can solve the problem in general by
- relying on Koenig lookup by putting the missing function in the namespace of one of its arguments; or
- declaring the function before its point of use.

Of course you cannot do the first option, since adding names to the standard namespace is cause for undefined behavior.

You can either specialize std::multiplies to perform the multiplication directly, or you could place a declaration of your operator* before you include any standard library headers. You can also inherit from std::vector and use the derived class instead, but the usual trick of adding an extra default function argument doesn't work here because operator* may not have 3 arguments.

Here's one way to avoid the issue:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
# include <vector>     
# include <numeric>    // for std::accumulate
# include <functional> // for std::multiplies
# include <string>

namespace foo { 
    // Rely on Koenig lookup to find the definition of operator* in namespace foo:  
    // We cannot define operator* in the standard namespace.
    template <typename...Args>
    class vector final: public std::vector<Args...> {};
    template<typename T>
    vector<T> operator*(const vector<T>& lhs, const vector<T>&) {
        return lhs; // simplified for space
    }
}

int main () {
    using foo::vector; 
    using T = vector<std::string>; 
    
    vector<T> v{};
    std::accumulate(v.begin(), v.end(), T{}, std::multiplies<>{});
}

http://coliru.stacked-crooked.com/a/2ad1e47c835a7862

Last edited on
max: as ever, an excellent piece of forensic work, I commend thee and thanks.
in addition to the methods you describe, overloading the *= operator:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
#include <iostream>
#include <map>
#include <vector>
#include <algorithm>
#include <string>
#include <cctype>

template<typename T>
const std::vector<T>& multiplication (const std::vector<T>& v) {
  return v;
}
template<typename T>
std::vector<T> operator * (const std::vector<T>& lhs, const std::vector<T>& rhs)
{
    std::vector<T> multVec{};
    for (const auto& elemLHS : lhs)
    {
        for (const auto& elemRHS : rhs)
        {
            multVec.push_back(std::move(elemLHS+elemRHS));
        }
    }
    return multVec;
}
template <typename T, typename... Args>
std::vector<T> operator * (const std::vector<T>& lhs, Args... args)
{
    return lhs * multiplication(args...);
}
template <typename T>
std::vector<T>& operator *= (std::vector<T>& lhs, const std::vector<T>& rhs)
{
    lhs = std::move(lhs * rhs);
    return lhs;
}

using Keypad = std::map<char, std::vector<std::string>>;

const Keypad keys{{'2', {"a","b","c"}}, {'3', {"d","e","f"}}, {'4', {"g","h","i"}}, {'5',{"j","k","l"}},
        {'6', {"m","n","o"}}, {'7', {"p","q","r","s"}}, {'8',{"t","u","v"}}, {'9',{"w","x","y","z"}}};

int main ()
{
    std::cout << "Enter telephone number: \n";
    std::string telNumber{};
    getline(std::cin, telNumber);
    if((telNumber.size() != 7) || !(std::all_of(telNumber.begin(), telNumber.end(), ::isdigit)))
    //also check no 1 or 0
    {
        std::cout << "invalid number \n";
    }
    std::vector<std::vector<std::string>> telNumberKeys{};
    for (const auto& elem : telNumber)
    {
        telNumberKeys.push_back(keys.find(elem)->second);
    }
    auto result = telNumberKeys[0];

    for (size_t i = 1; i < telNumberKeys.size(); ++i)
    {
        result *= telNumberKeys[i];
    }
    std::cout << result.size();//sanity-check
}
I guess the OP has all the suggestions he needs anyway.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
#include <iostream>
#include <fstream>
#include <vector>
#include <string>
using namespace std;

//======================================================================

vector<int> getNumber()
{
   const int NDIGITS = 7;
   string line;

   cout << "Enter a " << NDIGITS <<"-digit number using the digits 2-9 only: ";
   getline( cin, line );
   while( line.size() != NDIGITS || line.find_first_not_of( "23456789" ) != string::npos )
   {
      cout << "Invalid number; try again: ";
      getline( cin, line );
   }

   vector<int> digits( NDIGITS );
   for ( int i = 0; i < NDIGITS; i++ ) digits[i] = line[i] - '0';
   return digits;
}

//======================================================================

string getFile()
{
   string line;
   cout << "Enter a filename for output: ";
   getline( cin, line );
   return line;
}

//======================================================================

int main()
{
   vector<string> keyboard = { "", "", "abc", "def", "ghi", "jkl", "mno", "pqrs", "tuv", "wxyz" };
   vector<int> digits = getNumber();
   string filename = getFile();
   vector<string> wordlist(1,"");
   vector<string> temp;

   for ( int i : digits )
   {
      for ( string s : wordlist )
      {
         for ( char c : keyboard[i] ) temp.push_back( s + c );
      }
      wordlist = temp;
      temp.clear();
   }

   ofstream out( filename );
   for ( string s : wordlist ) out << s << endl;
   out.close();
}

Topic archived. No new replies allowed.