Structs

I am taking an online class and, unfortunately is no online lecture both either written or video to go online with it. Just an assignment and the textbook. I have started an assignment on structs and the assignment is more challenging than what the book is preparing me for. I have also spent about 4 hours watching c++ videos online and still do not feel ready for the assignment.

I have some code in place but I don't think I am using my pointer to a struct member to to increment its count correctly. If I cout it the result looks like an address. Also, from here I am not sure what to do next. Any help is appreciated.

Instructions:
Note: Do not use classes or any variables of type string to complete this assignment
Write a program that reads in a sequence of characters entered by the user and terminated by a period ('.'). Your program should allow the user to enter multiple lines of input by pressing the enter key at the end of each line. The program should print out a frequency table, sorted in decreasing order by number of occurences, listing each letter that ocurred along with the number of times it occured. All non-alphabetic characters must be ignored. Any characters entered after the period ('.') should be left in the input stream unprocessed. No limit may be placed on the length of the input.

Use an array with a struct type as its base type so that each array element can hold both a letter and an integer. (In other words, use an array whose elements are structs with 2 fields.) The integer in each of these structs will be a count of the number of times the letter occurs in the user's input.

Let me say this another way in case this makes more sense. You will need to declare a struct with two fields, an int and a char. (This should be declared above main, so that it is global; that is, so it can be used from anywhere in your program.) Then you need to declare an array (not global!) whose elements are those structs. The int in each struct will represent the number of times that the char in the same struct has occurred in the input. This means the count of each int should start at 0 and each time you see a letter that matches the char field, you increment the int field.

Don't forget that limiting the length of the input is prohibited. If you understand the above paragraph you'll understand why it is not necessary to limit the length of the input.

The table should not be case sensitive -- for example, lower case 'a' and upper case 'A' should be counted as the same letter. Here is a sample run:

Enter a sequence of characters (end with '.'): do be Do bo. xyz

Letter: Number of Occurrences
o 3
d 2
b 2
e 1
Note: Your program must sort the array by descending number of occurrences. You may use any sort algorithm, but I would recommend using the selection sort from lesson 9.6. Be sure that you don't just sort the output. The array itself needs to be sorted. Don't use C++'s sort algorithm.

Submit your source code and some output to show that your code works.

Hints:
Each time you read a character you will increment the corresponding counter in your array. You should not have to traverse your entire array to do this. Normally we aren't all that concerned with writing some code that is slightly inefficient, but this is highly inefficient. If you search the array to match the character input by the user, you will lose a point or two.

You will want to be familiar with various C++ functions for dealing with characters such as isupper, islower, isalpha, toupper, and tolower. You should assume for this assignment that the ASCII character set is being used. (This will simplify your code somewhat.)

Note that your struct should be declared above main(), and also above your prototypes, so that your parameter lists can include variables that use the struct as their base type. It should be declared below any global consts.

You will not need to read the user's input into an array or a c-string or a string. The only variables you will need are the array of structs described above, one single char variable to store each single character as it is read, and perhaps a couple of loop counters.

I used to have several paragraphs trying to explain how the input will work in this assignment. I've decided to just give you the basics of the input loop instead. You should use this:

cout << "Please enter a sequence of characters terminated with a period ('.'): ";
cin >> ch;
while (ch != '.'){
[insert code here to count ch if it is a letter. Should be just 2 - 3 lines of code max.]
cin >> ch;
}

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
 include <iostream>
using namespace std;

struct MyStruct
{
	int countLetters = 0;
	char letters;
};

int main()
{
	char ch;
	int size = 0;
	MyStruct* array = new MyStruct[size];

	cout << "Please enter a sequence of characters terminated with a period ('.'): ";
	cin >> ch;
	
	while (ch != '.') {
		//[insert code here to count ch if it is a letter.Should be just 2 - 3 lines of code max.]
		if (ch != '.') {
			array->countLetters++;
			cout << array->countLetters;
		}
		cin >> ch;
	}

	delete[] array;

	system("pause");

	return 0;
}
The whole struct thing seems like a red herring to me.

1
2
3
4
5
6
7
8
9
int frequency[26] = { 0 };
while ( cin >> ch && ch != '.' ) {
   if ( isalpha(ch) ) {
      frequency[tolower(ch)-'a']++;
   }
}
for ( int i = 0 ; i < 26 ; i++ ) {
    cout << (char)(i+'a') << "=" << frequency[i] << endl;
}


Sure, you can have a struct, but for efficiency, it's going to look like
1
2
3
4
5
6
7
8
9
struct foo {
  char letter;
  int count;
} frequency[26] = {
  { 'a', 0 },
  { 'b', 0 },
  { 'c', 0 },
  // use a loop to initialise it
};

That would be a global array correct. Here the Instructions say:

You will need to declare a struct with two fields, an int and a char. (This should be declared above main, so that it is global; that is, so it can be used from anywhere in your program.) Then you need to declare an array (not global!) whose elements are those structs. The int in each struct will represent the number of times that the char in the same struct has occurred in the input. This means the count of each int should start at 0 and each time you see a letter that matches the char field, you increment the int field.

So, I was picturing a dynamic array of structs, which is what I tried to set up. I have only used one once before and I am not sure how to work it in this example.
Sure, you can do this too.
1
2
3
4
5
6
7
8
9
10
11
12
struct foo {
  char letter;
  int count;
};

int main()
  struct foo frequency[26] = {
    { 'a', 0 },
    { 'b', 0 },
    { 'c', 0 },
    // use a loop to initialise it
  };

The struct is a type definition. The definition has to be available for everyone that will use that type. Hence the global scope.

To require the use of struct makes sense, because there is a need to sort the data and in int frequency[26]; the letters are implicit and thus won't cope with the sort. Furthermore, sorting objects by specific attributes is educational.

If you search the array to match the character input by the user, you will lose a point or two.

This shows that one should use the implicit letter-index mapping during the input.

You know exactly how many unique letters you can have. We have assumed english 26.
That is why there is no need to use dynamic array.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
#include <iostream>

struct Foo {
  char letter;
  int count;
};

int main() {
  Foo frequency[26] {}; // no keyword 'struct' required
  for ( int c = 0; c < 26; ++c ) {
    frequency[c].letter = ...
  }
  // assert: frequency[tolower(ch)-'a'].letter == ch

  // input

  // sort

  // print the ones that occur
  for ( auto f : frequency ) {
    if ( f.count == 0 ) break;
    std::cout << f.letter << '\t' << f.count << '\n';
  }
}
The whole struct thing seems like a red herring to me.
It has to be a struct because you need to sort it by frequency and output in descending order of frequency.

StoneJax, I'm not surprised that you're having trouble with this assignment. I've used something similar as an interview question. The thing that trips people up is that when building the frequency table, you need to access the data by letter, but when sorting, you need to sort by frequency.

Going over your code:
Line 1: include should be #include
Line 7: I'd call this field letter instead of letters since it represents a single letter.
Line 14: There's no need to create array with new. Just make an array of 26 items:
MyStruct array[26];
Line 20: Don't forget to skip non-letters. The continue statement is handy for that:
if (!isalpha(ch)) continue;
Line 21: This isn't necessary since the while loop guarantees that it isn't '.'
Line 22: Once you're certain that ch is a letter, you need to do some math to figure out which item to increment in the array. tolower(ch) will convert upper case to lower, guaranteeing that you have a lower case letter. Then if you subtract the ascii code for 'a' you'll be left with the index into the array, so it's:
array[tolower(ch)-'a'].countLetters++;
Line 28: Since we removed the new above, you remove the delete.

Put a big comment at line 28: // Sort the array here Don't actually write code to sort it yet.

After line 28, write code to print out the array. For now, it won't be properly sorted, but if you print it out, it will help debug the code that creates the array. After all, your sorting code will NEVER work properly if the input array isn't correct!

TBD:
- initialize the items in array.
I am not allowed to use break (except with switch) or continue. He wants single entry single exit for loops or functions. Although this is an if statement so I'll have to look into that.

Here is my updated code. I got it so it seems to be counting the correct number of letters with out counting white space or special characters. I am not making the connection on what to do with the ascii code.
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
#include <iostream>
using namespace std;

struct MyStruct
{
	int countLetters = 1;
	char letter;
};

int main()
{
	char ch;
	const int SIZE = 26;

	MyStruct array[SIZE];
	MyStruct *ptr = array;

	cout << "Please enter a sequence of characters terminated with a period ('.'): ";
	cin >> ch;
	
	while (ch != '.') {

		if (isalpha(ch)) {
			cout << "test 1: " << ptr-> countLetters++;

			cout << endl;
		}
	cout << "test 2: " << array[tolower(ch) - 'a'].countLetters;
	cout << endl;
	cin >> ch;
	}

	// Sort array here

	system("pause");
	return 0;
}
You don't need that pointer. Furthermore, the syntax:
1
2
3
4
5
6
7
8
9
10
11
ptr->countLetters
// is same as
(ptr+0)->countLetters
// is same as
(*ptr).countLetters
// is same as
(*(ptr+0)).countLetters
// is same as
ptr[0].countLetters
// and since ptr==array, is same as
array[0].countLetters


If we leave the output out, then you have effectively written:
1
2
3
4
5
6
7
MyStruct array[SIZE];
cin >> ch;
while ( ch != '.' ) {
  if ( isalpha(ch) ) {
    array[0].countLetters++;
  }
}

That does not look right.

What is the value of array[0].letter?
What is the value of array[0].countLetters, if input has 0 letters? If input has 1 letter?


You could have output as separate function:
1
2
3
4
5
void print( MyStruct* table, size_t N ) {
  for ( size_t c = 0; c < N; ++c ) {
    std::cout << table[c].letter << '\t' << table[c].countLetters << '\n';
  }
}

That makes it easier to show the table before and after sort.

The condition in that loop is c < N. The loop ends when the condition is false.
You can make a more complex condition by combining simple ones with boolean operators.

c < N && foo
That is true only if c < N is true and foo is true.

For example: c < N && 3 < table[c].countLetters ends the loop when you encounter a letter that did occur less than four times.

Note that 3 < table[c].countLetters && c < N would contain an error.
If all letters have occurred many times, then the loop reaches iteration, where c == N.
The left side of && is evaluated before the right side.
Therefore, the loop would evaluate 3 < table[N].countLetters and table[N] is outside of the array table. An out-of-range error. Undefined behaviour.

Why is c < N && 3 < table[c].countLetters safe then?
The && is lazy. If the left side is false, then right side is not evaluated at all, because we know that result of and will be false.
Topic archived. No new replies allowed.