diff --git a/01/02/2018/hello-world/index.html b/01/02/2018/hello-world/index.html new file mode 100644 index 00000000..41d60e8f --- /dev/null +++ b/01/02/2018/hello-world/index.html @@ -0,0 +1,767 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Hello World | Liam's Blog + + + + + + + + + + + + + + + + + + +
+
+ + + +
+
+
+
+ + +
+ + + + + + + + +
+ + + +
+ + + + + + + +
+ + + +

Hello World

+ + + +
+ + + + + +
+ + + + + +

Welcome to Hexo! This is your very first post. Check documentation for more info. If you get any problems when using Hexo, you can find the answer in troubleshooting or you can ask me on GitHub.

+

Quick Start

Create a new post

1
$ hexo new "My New Post"
+

More info: Writing

+

Run server

1
$ hexo server
+

More info: Server

+

Generate static files

1
$ hexo generate
+

More info: Generating

+

Deploy to remote sites

1
$ hexo deploy
+

More info: Deployment

+ + +
+ + + + + + + + + + + +
+ + + +
+ + + +
+ +
+
+ + +
+ + + + + + + + + +
+ + + + + + + + + +
+
+ + + + +
+ + +
+ + + + +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/01/03/2018/Accelerated-C-Solutions-to-Exercises-Chapter-2-Part3/index.html b/01/03/2018/Accelerated-C-Solutions-to-Exercises-Chapter-2-Part3/index.html new file mode 100644 index 00000000..a9758c02 --- /dev/null +++ b/01/03/2018/Accelerated-C-Solutions-to-Exercises-Chapter-2-Part3/index.html @@ -0,0 +1,865 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Accelerated C++ Solutions to Exercises (Chapter 2 Part3) | Liam's Blog + + + + + + + + + + + + + + + + + + +
+
+ + + +
+
+
+
+ + +
+ + + + + + + + +
+ + + +
+ + + + + + + +
+ + + +

Accelerated C++ Solutions to Exercises (Chapter 2 Part3)

+ + + +
+ + + + + +
+ + + + + +

Exercise 2-6

What does the following code do?

1
2
3
4
5
6
int i = 0;
while (i < 10)
{
i += 1;
std::cout << i << std::endl;
}

+

Solution & Results

The program writes 10 rows of numbers, starting from 1 to 10.

+

The while statement starts from testing the condition and then executes the while body if the condition is true. It stops executing the while body until the condition becomes false. Let’s analyse the first iteration

+
    +
  • First, the condition of the first time iteration is true as 0 < 10 is true.
  • +
  • Second, the expression i += 1; is evaluated, and the variable i becomes 1 after the execution.
  • +
  • then, the following statement is executed and the variable i is written on the output device.
  • +
  • finally, the while loop starts all over again from testing the condition.
  • +
+

From above steps we have seen that

+
    +
  • the first number to output is 1.
  • +
  • i is increased by 1 each iteration.
  • +
+

Accordingly, the final iteration can be deducted

+
    +
  • when i = 9, the row of output is 9 and the condition is still true.
  • +
  • after the evaluation of i += 1;, i equals 10.
  • +
  • then the output is 10 in the following step.
  • +
  • the while statement starts again and tests the condition, but the condition 10 < 10 is false.
  • +
  • the while statement finishes.
  • +
+

Now I complete the program and test it

1
2
3
4
5
6
7
8
9
10
11
12
#include <iostream>

int main()
{
int i = 0;
while (i < 10)
{
i += 1;
std::cout << i << std::endl;
}
return 0;
}

+

As expected, it writes 10 rows of outputs from 1 to 10 with one number in each row.

1
2
3
4
5
6
7
8
9
10
1
2
3
4
5
6
7
8
9
10

+

Note that the cursor appears on the next line of the final number 10 due to the following manipulator endl.

+

We can also explain the program from the perspective of its goal. Condiser that, we want the program to print out 10 rows, each of which contains a number, starting from 1 to 10 orderly. The loop invariant can be expressed as: we have written i rows now and the number in current row is i. To verify the loop invariant, we need to verify it at two specific points:

+
    +
  1. the first point is before the first time that the condition is evaluated. In this case, it is correct as there is 0 output at current position.
  2. +
  3. the second point is before the end of the while body. Once the first statement is executed, i is increased by 1. To maintain the loop invariant, it needs writing a row which contains the number i. Therefore, the loop statement works as expected in each iteration.
  4. +
+

For clarity, I add comments for the program

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
#include <iostream>

int main()
{
// loop invariant: we have written i rows now and the number in current row is i

int i = 0;

while (i < 10)
{
// i changes with increment of 1
i += 1;

// to maintain the loop invariant, write 1 row
std::cout << i << std::endl;
}
return 0;
}

+

Analysis

See deatiled analysis in C++ - Looping and counting.

+
+

Exercise 2-7

Write a program to count down from 10 to -5.

+

Solution & Results

This exercise is similar to the program in the last exercise. There are 16 numbers in total and hence there should be overal 16 loop times. Naturally, we use the range [0, 16) to describe the loop statements. The loop invariant can be expressed as we have written i rows and the number that written in this row is j.

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

int main()
{
// The loop invariant can be expressed as we have
// written i rows and the number written in this row is j.

int i = 0;

while (i < 16)
{
// write a row of outputs
cout << endl;

// to maintain the loop invariant, increase the value of i by adding 1
++i;
}
return 0;
}
+

What is the value of j? As mentioned above, the loop invariant needs to be verified at two specific points. First, before the first time that the condition is evaluated, we have written 0 rows. Second, before the end of the while body, we have written 1 row and the number should be 10. Therefore, the inital value of j should be 10. It is still not clear now. I’ll further verify the loop invariant in the second and third iteration.

+

The second iteration:

+
    +
  • before the the condition is evaluated, the loop invariant is correct as currently there is one row and the output is 10.
  • +
  • before the end of the while body, i increases by 1. The number to output is 9. To maintain the loop invariant, j should be decreased by 1.
  • +
+

The third iteration:

+
    +
  • before the the condition is evaluated, the loop invariant is correct as currently there are 2 rows and the second output is 9.
  • +
  • before the end of the while body, i increases by 1. The number to output is 8. To maintain the loop invariant, j should be decreased by 1.
  • +
+

It has been seen from above descriptions, each iteration i changes with increment by 1 while j changes with decrement by 1. Therefore, the sum of i and j should be constant, and hence j = 10 - i as j has initial value 10. In other words, the loop invariant is i + j = 10, of which the i represents the row number and j represents the number contained in the i row of outputs.

+

Accordingly, the complete program is

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

int main()
{
// loop invariant: we have written i rows and the number
// written in this row is j = 10-i.

int i = 0;
int j = 10;

while (i < 16)
{
// write a row of outputs
std::cout << j << std::endl;

// to maintain the loop invariant, increase the value
// of i by adding 1, change the value o j to 10 - i
++i;
j = 10 -i;
}
return 0;
}

+

The program works as expected with following outputs

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
10
9
8
7
6
5
4
3
2
1
0
-1
-2
-3
-4
-5

+
+

Exercise 2-8

Write a program to generate the product of the numbers in the range [1, 10).

+

Solution & Results

The product of the numbers in the range [1, 10) is

1
1 x 2 x 3 x 4 x 5 x 6 x 7 x 8 x 9

+

Essentially, it is a factorial of number 9. I can transform above expression as

1
9! = 1 x 2 x 3 x ... x 8 x 9

+

or

+
1
9! = 9 x 8!
+

Therefore, the calculation can be designed as a loop statement containing 8 times loops. In each iteration, we calculate the factorial of a number from 2 till 9. The loop invariant is that we have calculated the factorial i times and the number f. I’ll use the range [0, 8) to count the loops. The complete program is shown as below

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

int main()
{
int f = 1;
int product = 1;

// we have calculated the factorial i times and the number is f
for (int i = 0; i != 8; ++i)
{
// increase the value of f by 1
++f;

// to maintain the loop invariant, calculate the factorial of number f
product *= f;
}
std::cout << product << std::endl;

return 0;
}
+

The results is

1
362880

+
+

Exercise 2-9

Write a program that asks the user to enter two numbers and tells the user which number is larger than the other.

+

Solution & Results

It’s a simple exercise, and I’ll skip analysis and present the program directly

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
#include <iostream>
using std::cout;
using std::cin;
using std::endl;

int main()
{
cout << "Please enter two integers: ";
// read two numbers
int a, b;
cin >> a >> b;

if (a == b)
cout << a << " equals to " << b << endl;
else if (a > b)
cout << a << " is greater than " << b << endl;
else if (a < b)
cout << b << " is greater than " << a << endl;
return 0;
}

+

Test 1

1
2
Please enter two numbers: 5 8
8 is greater than 5

+

Test 2

1
2
Please enter two numbers: 4 2
4 is greater than 2

+

Test 3

1
2
Please enter two numbers: 100 100
100 equals to 100

+

Exercise 2-10

Explain each of the uses of std:: in the following program:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
#include <iostream>

int main()
{
int k = 0;
while (k != 5)
{
// invariant: we have written k asterisks so far
using std::cout;
cout << "*";
++k;
}
std::cout << std::endl;
// std:: is required here
return 0;
}

+

Solution & Results

The first std:: in line 9

1
using std::cout;

+

The using declaration qualify us to use the name cout, which is defined in the namespace std, directly instead of std::cout. However, the declaration is only valid within the block of the while statement as the curly braces form a name scope.

+

This also explains the requirements of the std:: in line 13. If one want to use the unqualified cout, endl inside the main function body, he should write the using declarations for each different name before the start of the main function. For example

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
#include <iostream>
using std::cout;
using std::endl;

int main()
{
int k = 0;
while (k != 5)
{
// invariant: we have written k asterisks so far
// using std::cout - is not required now
cout << "*";
++k;
}
std::cout << std::endl;
// std:: is not required now
return 0;
}

+

Now, both programs work well and give the results

1
*****

+

Analysis

See C++ - Getting Started.

+
+

References

Koenig, A. and Moo, B.E., 2000. Accelerated C++.

+

Lippman, S.B., Lajoie, J. and Moo, B.E., 2013. C++ Primer, 5th edt.

+ + +
+ + + + + + + + + + + +
+ + + +
+ + + +
+ +
+
+ + +
+ + + + + + + + + +
+ + + + + + + + + +
+
+ + + + +
+ + +
+ + + + +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/2018/02/02/C++ - Getting Started/index.html b/02/02/2018/C++ - Getting Started/index.html similarity index 50% rename from 2018/02/02/C++ - Getting Started/index.html rename to 02/02/2018/C++ - Getting Started/index.html index 71b60a16..dc1cfa96 100644 --- a/2018/02/02/C++ - Getting Started/index.html +++ b/02/02/2018/C++ - Getting Started/index.html @@ -1,50 +1,130 @@ - + + + + + + + - - + + + - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + - + - - - - - - + + + + + + + + + + + + + - - C++ - Getting Started | Liam's Blog @@ -54,218 +134,245 @@ - - -
-
+ -
-
- + + + + + +
+
-
+ - -
- - 0% -
- +
+
-
+
- - -
+
-
- +
+ + + + + + + + +
-
- + +
+ + +
-

- C++ - Getting Started -

+ + + +

C++ - Getting Started

+
+ @@ -273,6 +380,9 @@

+ + +

Introduction

To have a better understanding on the C++ programming language, I decided to learn it from the very begining and take detailed notes from the textbooks Accelerated C++: Practical Programming by Example and C++ Primer (5th Edition), and some online C++ tutorials. In addition, I plan to complete the exercises of each chapter in the Accelerated C++ with providing detailed codes analysis and implementation processes.
Please check the solutions to the first chapter exercises here: Accelerated C++ Solutions to Exercises Chapter 0

Hello, world!

Let’s start by writing a simple program, Hello, world!, and then analyse its structure.

1
2
3
4
5
6
7
8
9
10
11
12
/*
* A simple C++ Program
* Author: Mr. Nobody
*/
#include <iostream>

int main()
{
// output Hello, world!
std::cout << "Hello, world!" << std::endl;
return 0;
}
@@ -282,14 +392,12 @@

0.2 #, #include

Lines begining with # are preprocessor directives which are preprocessed before actual compilation, i.e. the compilation of the program itself. These preprocessor directives are not part of the program statement, and hence semicolons ;, marking the end of most statements in C++, are not required.

-

The directive #include gives preprocessor instructions to include a header file, i.e. copy the entire content of the specified header or file and insert. There are two ways to use **#include#:

-
1
2
#include <header>
#include "file"
+

The directive #include gives preprocessor instructions to include a header file, i.e. copy the entire content of the specified header or file and insert. There are two ways to use **#include#:

1
2
#include <header>
#include "file"

The angle-brackets <> are generally used to include a header of the standard library, e.g. iostream, string…, while the quotes “” include a file that is typically programmer-defined. For example, the program above includes a standard header named iostream to accomplish Input-Output (IO) as the C++ language doesn’t define any statements to do IO.

0.3 The main function

Every C++ program must contain a main function. The main function is being called when the operating system runs a program. As same as other functions, the main function also includes four elements: return type, function name, parameter list and function body.

The main function requires an int (i.e. integers) type return value. A return value of zero tells the implementation that the program ran successfully while a non-zero value indicates errors. The example above has an empty parameter list, showing that there is nothing between the parentheses (()).

The function body begins with an open curly brace ({) and ends with a close curly brace (}). The braces indicate that all the statements inside are part of the same function, of which the return statement terminates the execution of the function by returning a value to the function’s caller. Of course, the value that returned should has the same type as the defined return type of the function. In the case of main function, if the return statement is omitted, the implementation would assume a return value of zero (not recommended).

-

0.4 IO & using declarations

Before the return statement inside the braces, the first statement (as shown below) achieves the goal of this program.

-
1
std::cout << "Hello, world!" << std::endl;
+

0.4 IO & using declarations

Before the return statement inside the braces, the first statement (as shown below) achieves the goal of this program.

1
std::cout << "Hello, world!" << std::endl;

As mentioned above, the statement writes Hello, World! on the standard output. The std:: indicates the followed name is part of the namespace named std. The names std::cout and std::cin refer to the objects of the standard output stream (ostream) and input stream (istream), which had already been defined in the iostream library. Another two objects that are defined in the iostream library are cerr and clog. The cerr, representing standard error, is used to output warning or error messages. The clog is used to output the general information about the execution of the program. The name std::endl, which is a manipulator, ends the current line of output. The output operator (<<) and input operator (>>) indicate that what follows is inserted to std::cout and std::cin. To use these objects, firstly we need to include the associated standard header, namely iostream.

To simplify the usage of library names from namesapce std, one could use unqualified names cout or cin instead of std::cout or std::cin by the means of using declarations. For example, above program is exactly as same as the below program:

1
2
3
4
5
6
7
8
9
10
11
12
13
/*
* A simple C++ Program
* Author: Mr. Nobody
*/
#include <iostream>
using std::cout;
using std::endl;
int main()
{
// output Hello, world!
cout << "Hello, world!" << endl;
return 0;
}f
@@ -305,264 +413,411 @@

  1. string literals which enclosed in double quotes are generally not span lines.
  2. preprocessor directive should be put on its own line.
  3. -
  4. // type comments should ends at the end of the current line, and /***/ type comments cannot nest.
  5. +
  6. // type comments should ends at the end of the current line, and /*/ type comments cannot nest.

Next: C++ - Working with strings.

+

+ + + + + - -
- -
+

+ + + +
+ + + +
+ +
- + + + + + +
+ -
-
- - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + - + + - - + + + + + + + + + + - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/02/03/2018/C-Built-in-types-and-expressions/index.html b/02/03/2018/C-Built-in-types-and-expressions/index.html new file mode 100644 index 00000000..97444d79 --- /dev/null +++ b/02/03/2018/C-Built-in-types-and-expressions/index.html @@ -0,0 +1,873 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + C++ - Built-in types and expressions | Liam's Blog + + + + + + + + + + + + + + + + + + +
+
+ + + +
+
+
+
+ + +
+ + + + + + + + +
+ + + +
+ + + + + + + +
+ + + +

C++ - Built-in types and expressions

+ + + +
+ + + + + +
+ + + + + +

Arithmetic types

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Arithmetic types in C++
TypeMeaningMinimum size
boolbooleanNA
charcharacter8bits
wchar_twide character16bits
char16_tUnicode character16bits
char32_tUnicode character32bits
shortshort integer16bits
intinteger16bits
longlong integer32bits
long longlong integer64bits
floatsingle-precision floating-point6 significant digits
doubledouble-precision floating-point10 significant digits
long doubleextended-precision floating-point10 significant digits
+ + +
+ + + + + + + + + + + +
+ + + +
+ + + +
+ +
+
+ + +
+ + + + + + + + + +
+ + + + + + + + + +
+
+ + + + +
+ + +
+ + + + +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/02/03/2018/C-Working-with-batches-of-data/index.html b/02/03/2018/C-Working-with-batches-of-data/index.html new file mode 100644 index 00000000..f78a5df4 --- /dev/null +++ b/02/03/2018/C-Working-with-batches-of-data/index.html @@ -0,0 +1,919 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + C++ - Working with batches of data | Liam's Blog + + + + + + + + + + + + + + + + + + +
+
+ + + +
+
+
+
+ + +
+ + + + + + + + +
+ + + +
+ + + + + + + +
+ + + +

C++ - Working with batches of data

+ + + +
+ + + + + +
+ + + + + +

Imagine a course in which each student’s final exam counts for 40% of the final grade, the midterm exam counts for 20%, and the average homework grade makes up the remaining 40%. Now we are asked to write a program that reads a student’s exam and homework grades and computes a final grade.

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 <iomanip>
#include <ios>
#include <iostream>
#include <string>

using std::cin; using std::setprecision;
using std::cout; using std::string;
using std::endl; using std::streamsize;

int main()
{
// ask for and read the student's name
cout << "Please enter your first name: ";
string name;
cin >> name;
cout << "Hello, " << name << "!" << endl;

// ask for and read the midterm and final grades
cout << "Please enter you midterm and final exam grades: ";
double midterm, final;
cin >> midterm >> final;

// ask for the homework grades
cout << "Enter all your homework grades, "
"followed by end-of-file: ";

// the number and the sum of grades read so far
int count = 0;
double sum = 0;

// a variable into which to read
double x;

// invariant: we have read count grades so far, and sum is the sum of the first count grades
while (cin >> x)
{
++count;
sum += x;
}

// write the result
streamsize prec = cout.precision();
cout << "Your final grade is " << setprecision(3)
<< 0.2 * midterm + 0.4 * final + 0.4 * sum/count
<< setprecision(prec) << endl;
return 0;
}

+

More about IO system

The goal of above program is to compute a final grade, which is a simple math question-computing the weighted average. Let’s start from the #include directives

1
2
3
4
#include <iomanip>
#include <ios>
#include <iostream>
#include <string>

+

We are familar with which is the header that defines the standard input/output stream objects, and which is the header that defines string type objects. Correspondingly, objects cin , cout and endl are defined in the iostream library, and string is defined in the string class. These names are defined in the namespace std, and need to be declared in the form of std::name or using std::name before we can use them.

+

Similarly, the is a header that defines the type streamsize which represents sizes. The defines the manipulator setprecision which sets the decimal precision. Both names streamsize and setprecision are defined in the namespace std.

+

setprecision, shownpoint & fixed

setprecision is used to format floating-point values, such as float and double type values. It manipulates the stream by causing the subsequent output on that stream to be written with a given number of digits. The syntax is

1
<< setprecision(int n) <<

+

The parameter n determines the number of digits to be written. In this case, setprecision(3) means the output will remain three digits. Let’s do some experiments to explore more about setprecision

+

Experiment 1

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
#include <iomanip>
#include <ios>
#include <iostream>

using std::cin; using std::setprecision;
using std::cout; using std::endl;


int main()
{
int i = 12345;
float f1 = 12345;
float f2 = 3.148;;
float f3 = 0.03148;
float f4 = 0.03108;
float f5 = 0.03100;
float f6 = 1000.435;

cout << setprecision(3) << i << endl;
cout << f1 << endl;
cout << f2 << endl;
cout << f3 << endl;
cout << f4 << endl;
cout << f5 << endl;
cout << f6 << endl;

return 0;
}

+

The result is shown below

1
2
3
4
5
6
7
12345
1.23e+004
3.15
0.0315
0.0311
0.031
1e+003

+

It can be seen from this experiment that

+
    +
  1. setprecision doesn’t work on int type values
  2. +
  3. the parameter n (in this case is 3) controls the number of significant digits (i.e. count from the first non-zero number).
  4. +
  5. it follows the rounding principles.
  6. +
  7. it omits the trailing 0s.
  8. +
+

If one want to keep the trailing zero, the manipulator showpoint can be used. showpoint is declared in the ios library and its name is also in the namespace std. It sets the format flag and always includes the decimal point as well as the tail 0 for matching the precision. The showpoint flag can be unset with the noshowpoint manipulator.

+

If we want control the precision of the decimal part, we can use fixed manipulator to fixed the decimal part. Together with the setprecision(n), the number of digits in the fractional part will be fixed at n. If there is no enough numbers after the decimal point, zero will be added to match the precision.

+

To veryfy the usages of showpoint and fixed, I did tests as follows.

+

Experiment 2

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
#include <iomanip>
#include <ios>
#include <iostream>

using std::cin; using std::setprecision;
using std::cout; using std::fixed;
using std::endl; using std::showpoint;
using std::noshowpoint;


int main()
{
float f1 = 1000.00;
float f2 = 3.14800;
float f3 = 0.03148;


cout << showpoint << setprecision(4) << f1 << endl;
cout << f2 << endl;
cout << f3 << noshowpoint << endl;

// write a bank line to sepearate outputs
cout << endl;

cout << fixed << setprecision(6) << f1 << endl;
cout << f2 << endl;
cout << f3 << endl;

return 0;
}

+

The result is as analysed above

1
2
3
4
5
6
7
1000.
3.148
0.03148

1000.000000
3.148000
0.031480

+

streamsize

Once we changed the precision, the subsequent output would be formated to match the precision. If we want to change back, we can reset the precision to the original setting if we know the precision value. If we don’t know the previous setting of the cout, the cout.presicion returns us the value and its type is streamsize.

+

Without using setprecision, we could use cout.precision(n) to set the precision. The usage is shown as below

+

Experiment 3

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
#include <iomanip>
#include <ios>
#include <iostream>

using std::cin; using std::setprecision;
using std::cout; using std::fixed;
using std::endl; using std::streamsize;


int main()
{
float f1 = 1000.00;
float f2 = 3.14800;
float f3 = 0.03148;

// return the current precision
streamsize prec1 = cout.precision();

// set precision to 2
cout << setprecision(2);

// set precision to 3, return previous value
streamsize prec2 = cout.precision(3);

// the outputs should have three decimal digits
cout << fixed << f1 << endl;
cout << f2 << endl;
cout << f3 << endl;

// reset precision value to its previous value
cout.precision(prec2);

// the outputs should have two decimal digits
cout << endl;
cout << fixed << f1 << endl;
cout << f2 << endl;
cout << f3 << endl;

// reset the the initial precision value
cout.precision(prec1);

cout << endl;
cout << fixed << f1 << endl;
cout << f2 << endl;
cout << f3 << endl;

// check the original value of precision
cout << endl;
cout << "The origin precision value is: " << prec1 << endl;

return 0;
}

+

The comments show the expected results according to that

+
    +
  1. streamsize precision() returns the the value of the current floating-point precision.
  2. +
  3. streamsize precision(int n) sets the precision to a new value.
  4. +
+

The program gives result as I expected

1
2
3
4
5
6
7
8
9
10
11
12
13
1000.000
3.148
0.031

1000.00
3.15
0.03

1000.000000
3.148000
0.031480

The origin precision value is: 6

+

Return to while statement

Let’s get back to the example. The statements in the function body begin with writing a greeting as illustrated in previous chapters. Then, it reads two values into two variables midterm and final. The input operations can be chained as

1
cin >> midterm >> final;

+

This statement is equivalent to

1
2
cin >> midterm;
cin >> final;

+

The next statement shows a new form of writing string literals.

1
2
cout << "Enter all your homework grades, "
"followed by end-of-file: ";

+

It seems that two string lterals will be written, but in fact it has the same effect as the following statement

1
cout << "Enter all your homework grades, followed by end-of-file: ";

+

This is because two or more string literals separated only by whitespace are concatenated automatically.

+

What closely follow is the while loop.

1
2
3
4
5
6
7
8
9
10
11
12
int count = 0;
double sum = 0;

// a variable into which to read
double x;

// invariant: we have read count grades so far, and sum is the sum of the first count grades
while (cin >> x)
{
++count;
sum += x;
}

+

count is defined for counting the number of inputs as well as describing the loops. sum is defined for holding the summation of homework grades. Note that it is initialized with an int value 0 though it is a double type variable. Therefore, the int value will be converted automatically to double type with the fractional part of 0.

+

Since the number of homework grades is unkonwn, we could not use the while loop as before as we don’t know the number of loops. The while loop here makes it available to input multiple times continuously. As we know, the condition in a while loop should yield a value of bool, that is, true or false, otherwise the value (if available) will be converted to the type of bool. According to Koenig and Moo (2000), the istream class provides a conversion that can be used to convert cin to a value that can be used in a condition. In addition, the value depends on the iternal state of the istream object, which will remember whether the last attempt to read worked. Hence, the cin >> x will theoretically always yield a true value as long as we keep inputing right type values. In this example, it reminds us to send “EOF” signal, which will change the value to false, to stop the loop.

+

vector

The example above shows how to compute an average value of a batch of data. In reality, the goal is probably to compute the median, or other statistics in the data. However, the inputted data are not stored in above program and hence are unavailable to access. Naturally, the first step is to store the data and the second step is to access or compute the target value via algorithms. Now we learn how to deal with these problems with vector.

+

Defining and Initializing vectors

The vector is a class template. Before using the vector in a program, we need to include the header and qualify the name either explicitly or using using declaration. For example

1
2
#include <vector>
using std::vector

+

A vector is a container that holds a sequence of objects of the same type. The syntax of creating a vector is

1
vector<T> name; // default initialization

+

The name is the template name. The T insides the angle brackets represents the type of the contained objects. It could be built-in types like int, char, or class types such as string, or even vector which means that the element contained in this vector is also a vector.

+

The vector template defines how to initialze vectors. When a vector is defalut initialized, it has no elements, which creats an empty vector. We can also initialize vectors using copy initialization or direct initialization or list initialization (see the table below).

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Ways to initialize a vectorreference: Lippman etc. 2012
vector v1default initialization and creat an empty vector
vector v2(v1)copy all elements in v1 to v2
vector v2 = v1has same effect as the last one
vector v3(n, val)direct initialization with n elements of the same value val
vector v4(n)v4 has n copies of value-initialized object
vector v5{a, b, c}v5 has three elements initialized with initializers a, b, c respectively
vector v5 = {a, b, c}has same effect as the last one
+

What new usage here is the value-initialization

1
vector<T> v4(n)

+

The n here only provide the number of elements that will be contained in the vector. But there is no initializers provided. In this case, all elements will be initialized following the principle of default initialization determined by the type of the elements. For example

1
2
vector<int> v1(10); // 10 elements will be initialized with 0
vector<string> v2(10); // 10 elements will be initialized to empty string type objects

+

If the type of the contained elements does’t support default initialization, the initialization of the vector would be failure.

+

In addition, it is worth noting the difference between

1
(1)    vector<int> v1(10);

+

and

1
(2)    vector<int> v2{10};

+
1
(3)    vector<int> v3(10, 1);
+

and

1
(4)    vector<int> v4{10, 1};

+

The first one is value-initialization as explained above while the second is list initialization with initializer 10. The third one is direct initialization with value 1 and the total number of elements is 10. The fourth one is list initialization with initializers 10 and 1, and the total number of elements is 2.

+

However, there exist some special cases

1
2
3
(5)    vector<string> v5("hello");

(6) vector<string> v6{10}; // 10 value-initialized elements

+

Example (5) is incorrect as we can not copy the string lterals to a vector. Example (6) is correct but has the same effect as example (1) rather than list initialization. This is because 10 can not be used to initialize an element of string type, instead it can be used to initialize the vector with 10 value-initialized strings.

+

Now returing to the beigining of this post, I’ll define a vector for the purpose of holding all homework grades.

1
2
double x;
vector<double> homework;

+

Operations on vectors

Reading the elements

One feature of a vector is that it has variable size, which is particularly helpful for us when the number of elements is unknown. The member function push_back can add a new element at the end of the vector after the current last element. The usage is shown below

1
2
3
4
while(cin >> x)
{
homework.push_back(x);
}

+

The x is passed as an argument and its value is copied to the new element. As a result, the size of the vector homework is increased by 1.

+

Implementing algorithms

Now all data have been stored into a vector. Assuming the goal of the program is to compute the median of the data set. It is known that the median value depends on the number of the stored elements (i.e. the size of the vector).

+
    +
  1. if there exist an odd number of numbers, the median value is the value of the middle number.
  2. +
  3. if there exist an even number of numbers, there is no single middle number and the median value is the average value of two middle numbers.
  4. +
+

For a program, we also need to consider the case of no elements.

+
    +
  1. if there is no elements at present, throwing a warning and asking to input again.
  2. +
+

Similar as a string, the size of a vector can be obtained through its member function size. For example, homework.size() returns the size of the vector. The returned value has a type of vector::size_type. Now we can translate above conditions to real code

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
// obtain the size of homework
typedef vector<double>::size_type vec_size;
vecsize size = homework.size();

if (size == 0)
{ cout << "You must enter your grades. "
"Please try again." << endl;

// return 1 instead of 0 to indicate failure
return 1;
}
else
{
if (size % 2 == 0)
// compute median
else
// compute median
}

+

Note that an alternative for the condition size == 0 is the empty function which returns true if vector is empty else returns false. The usage is the same as that for strings.

+

Type Aliases

For convenience, we uses type alias instead of using vector::size_type directly in defining variables of such type. A type alias defines the name vec_size as a synonym for vector::size_type. There are two ways to define type alias

1
2
3
(1)    typedef double length; // length is a synonym for double

(2) using length = double; // length is a synonmy for double

+

The second method is the new feature of new standards c++2011.

+

sort function

Now the rest of the work is to find the median and basically speaking, is to sort the data set. This can be done by using a library algoritm.

1
2
3
#include <algorithm>
... // other code
sort(homework.begin(), homework.end());

+

The sort function is defined in the library algorithm and therefore the header is added. It sorts the values in a container in an nondecreasing order. The arguments to sort specify the range of the data to be sorted. begin and end are member functions of the vector and represents the first element and (one past)the last element in homework respectively.
They are iterators and will further discussed in chapter 6.

+

After we obtained a ordered sequence of values, now I illustrate how to determine the median value.
If size is an even number

+

Similar as a string, we can access individual elements using subscript operator([]) and the index uses an asymmetrical range from 0 to the size of homework (excluded). If the size of homework is an even number, then size is exactly devided by 2. Due to the index starts from 0 ranther than 1, the mid elements should be homework[size/2 - 1] and homework[size/1]. Therefore, the median value is the average of the corresponding two values. As shown in above graph, the number of elements of both sides equals to size/2 because

1
2
left side:  number = size/2 - 1 - 0 + 1 = size/2
right side: number = size - 1 - size/2 + 1 = size/2

+

If size is an odd number, the result of size/2 is in fact the value of (size-1)/2. Then the mid element is exact the homework[(size-1)/2] as both sides of this element has same number of elements(as shown in below graph). This can also be verified mathematically

1
2
left side:  number = (size-1)/2 - 1 - 0 + 1 = (size-1)/2
right side: number = size - 1 - {(size-1)/2 + 1} + 1 = (size-1)/2;

+

If size is an odd number

+

Now let’s translate the algorithms to real code

1
2
3
4
5
6
7
8
9
{ 
vec_sz mid = size/2;
double median;

if (size % 2 == 0)
median = (homework[mid] + homework[mid-1])/2.0;
else
median = homework[mid]
}

+

Note that when calculate the average of two mid elements, I divide by 2.0 rather than 2 for the purpose of avoiding the loss of precision.This is because the quotient of two integers will be an integer.

+

The conditional (or ternary) operator

An alternative statements for above if-else clause is to use the conditional (or ternary) operator (?:). The syntax is

+
(condition 1) ? expression 1 : expression 2
+

It means that if condition 1 evaluates to true, then expression 2 is evaluated, and if condition 1 evaluates to false, then expression 3 is evaluated instead.

+

Therefore, we can change above code as

1
2
3
4
5
6
{ 
vec_sz mid = size/2;
double median;

median = size % 2 == 0) ? (homework[mid] + homework[mid-1])/2.0 : median = homework[mid];
}

+

A complete program

Finally, I put all pieces of code together and obtained the complete program. Also, it works well when I test it.

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
#include <algorithm>
#include <iomanip>
#include <ios>
#include <iostream>
#include <string>
#include <vector>

using std::cin; using std::sort;
using std::cout; using std::streamsize;
using std::endl; using std::string;
using std::setprecision; using std::vector;

int main()
{
// ask for and read the student's name
cout << "Please enter your first name: ";
string name;
cin >> name;
cout << "Hello, " << name << "!" << endl;

// ask for and read the midterm and final grades
cout << "Please enter your midterm and final grades: ";
double midterm, final;
cin >> midterm >> final;

// ask for the student entered some homework grades
cout << "Enter all your homework grades, "
"followed by end-of-file: ";
double x;
vector<double> homework;

while(cin >> x)
homework.push_back(x);

// check that the student entered homework grades
typedef vector<double>::size_type vec_size;
vec_size size = homework.size();
if (size == 0)
{
cout << endl << "You must enter your grades. "
"Please try again." << endl;
return 1;
}

// sort the grades
sort(homework.begin(), homework.end());

// compute the median homework grade
vec_size mid = size/2;
double median;
median = size % 2 == 0 ? (homework[mid] + homework[mid - 1])/2
: homework[mid];

// compute and write the final grade
streamsize prec = cout.precision();
cout << "Your final grade is " << setprecision(3)
<< 0.2 * midterm + 0.4 * final + 0.4 * median
<< setprecision(prec) << endl;

return 0;
}

+

Test and results:

1
2
3
4
5
Please enter your first name: Bruce
Hello, Bruce!
Please enter your midterm and final grades: 80 90
Enter all your homework grades, followed by end-of-file: 50 60 70 80 90
Your final grade is 80

+ + +
+ + + + + + + + + + + +
+ + + +
+ + + +
+ +
+
+ + +
+ + + + + + + + + +
+ + + + + + + + + +
+
+ + + + +
+ + +
+ + + + +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/02/04/2018/C-Writing-generic-functions/index.html b/02/04/2018/C-Writing-generic-functions/index.html new file mode 100644 index 00000000..91d25041 --- /dev/null +++ b/02/04/2018/C-Writing-generic-functions/index.html @@ -0,0 +1,867 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + C++ - Writing generic functions | Liam's Blog + + + + + + + + + + + + + + + + + + +
+
+ + + +
+
+
+
+ + +
+ + + + + + + + +
+ + + +
+ + + + + + + +
+ + + +

C++ - Writing generic functions

+ + + +
+ + + + + +
+ + + + + +

Generic function - an example

Generic functions are functions written in a way that is independent of any particular type. When we use a generic program, we supply the type(s) or value(s) on which that instance of the program will operate(Lippman etc. 2012). In C++, we can create generic functions by defining template functions, allowing writing a single definition for a family of functions or types that behave similarly but have different types of paratmters. The only difference can be summarized as parameters, namely, template parameters. Take the median function as an example, its template can be defined as follows.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
template<class T>
T median(vector<T> v)
{
typedef typename vector<T>::size_type vec_sz;

vec_sz size = v.size();
if(size == 0)
throw domain_error("median of an empty vector");

sort(v.begin(), v.end());

vec_sz mid = size/2;

return size % 2 ? (v[mid] + v[mid - 1])/2 : v[mid];
}

+

From the definition, we observe

+
    +
  1. A template starts with keyword template followed by type parameter(s) enclosed by a pair of angle brackets.
  2. +
  3. type parameter(s) define the names that can be used within the scope of the function. They refer to types, not variables. In other words, the types of template parameters can be regarded as variables that have types specified by type parameters(s).
  4. +
  5. type parameter(s) are specified following the keyword class or typename.
  6. +
  7. the template tells implementation that vector::size_type is the name of a type by adding keyword typename before.
  8. +
+

When we call median and pass a template argument vector, the implementation will create and compile an instance of the function as if the function median(vector). This is so called template instantiation. But how is the template compiled?

+

When the compiler encounters the definition of a template, it doesn’t generate code. It generates only when the template is instantiated. Remembering that when we call a function, we need to supply only its declaration. But if we want to call the template, we need to supply not only its declaration but also its definition in order to instantiate it. Therefore, the header file of a template generally includes the source file also via a #include or directly. I found an article How To Organize Template Source Code, where gives detailed explinations on how to organize template source code and three solutions.

+

Due to the feature of generating code during instantiation, compilation errors may occur during three stages:

+
    +
  1. the first stage is when we compile the template itself. But the compiler can only detect some syntax errors like forgetting a semicolon, misspelling a variable name etc..
  2. +
  3. the second stage is when we use a template, before it is instantiated. The compiler typically will check that whether the number of the arguments is appropriate, whether two arguments that are supposed to have the same type do so.
  4. +
  5. the type-related errors mostly occurs during the third stage when the template is instantiated. Though implementations manage instantiation on their own ways, the type-related errors may be reported at link time due to that implementations assume the type is possibly defined in other unit and hence leave it to linker to resolve.
  6. +
+

In fact, we have applied many templates in previous programs, such as vector and list, and all standard library algorithms.

+

Algorithms and iterators

As shown in above example, the template is type independent. However, it is also clear that the function to instantiate requires its argument supporting all operations included in the function. In above case, the types stored in the vectors that are passed to the median function must support addition and division.

+

The standard algorithms are not limited by the type of containers and obviously are data-structure independent function templates. It is known that the parameters taken by algorithms includes iterators and others. This implies that iterators to pass should support certain operations used inside of the algorithms. But we also know that some containers may support operations that others do not. For example, vector support random access elements via iterators while list doesn’t. Therefore, it is important to restrict the right of iterators depending on operations that an algorithm include and operations different containers support. For this reason, the library defines five iterator categories with specifying the collection of iterator operations for each category. By doing so, we know exactly what effect of an algorithm can have on an container and which container can use which algorithms.

+

Sequential read-only access

Some algorithms only require iterators that can access elements sequentially. For example, the find algorithm:

+
1
2
3
4
5
6
7
template <class In, class X>
In find(In beg, In end, const X &x)
{
while (begin != end && *begin != x)
++begin;
return begin;
}
+

The iterators of type In access elements sequentially via
operations *, ++. Also, they can be compared using !=. Alternatively,

+
1
2
3
4
5
6
7
8
template <class In, class X>
In find(In begin, In end, const X &x)
{
if (begin == end || *begin == x)
return begin;
begin++;
return find(begin, end, x);
}
+

This version of find uses another strategy, recursively calling itself. We have observed that the iterators may also need to support operations ++(postfix) and ==.

+

Furthermore, the iterators ought to support member access via ->, e.g. it->first. It has the same effect as (*it).first.

+

In summary, iterators that offers sequential read-only access to elements of a container should supports ++(both prefix and postfix), == and !=, * and ->. Such iterators are named as input iterators. All standard container meet the requirements of input iterator.

+

Sequential write-only access

Some algorithms may require write elements of a sequence via iterators, for example

1
2
3
4
5
6
7
template <class In, class Out>
Out copy(In begin, In end, Out dest)
{
while(begin != end)
*dest++ = *begin++;
return dest;
}

+

The copy algorithm takes three iterators, the first two of which denote the range of a sequence to copy while the third iterator denotes the initial position of the destination. The iterators of type In offer sequential read-only access to elements and hence are input iterators. The type Out gives the third iterator the ability to write elements via *dest = and dest++. As with the find algorithm above, such iterators should also support ++dest.

+

The fact that iterators of type Out are used for output only also implies that

+
    +
  1. each element pointed by the iterator is written a value only once and then the iterator is incremented.

    +
  2. +
  3. the iterator can not be incremented twice without assignments to the elements that it refers to.

    +
  4. +
+

In summary, such iterators are output iterators. All standard containers as well as back_inserter meet the requirements of output iterator. Noting that in this function, the left side deference operator is used to write to the underlying elements while the right side deference operator is used to read the underlying elements only.

+

Sequential read-write access

There is another situation that we want to both read and write the elements of a sequence, but only sequentially. For example

1
2
3
4
5
6
7
8
9
10
template<class For, class X>
void replace(For beg, For end, const X &x, const X &y)
{
while (beg != end)
{
if (*beg == x)
*beg = y;
++beg;
}
}

+

The replace algorithm examines each element in the range [beg, end) and assigns value y to those elements that are equal to x. Apparently, iterators of type For should support all operations supported by an input operator as well as an output operator. Moreover, they can read and write the same elements multiple times. Such a type is a forward iterator. Some operations that such a type need to support are

+
    +
  1. *it (for both reading and writing)
  2. +
  3. ++ (both prefix and postfix)
  4. +
  5. == and !=
  6. +
  7. ->
  8. +
+

All standard containers meet the requirements of forward iterator.

+

Reversible access

All above iterators access elements in a container in a forward sequence. But some functions may require get elements in reverse order, for example

1
2
3
4
5
6
7
8
9
template <class Bi> void reverse(Bi begin, Bi end)
{
while (begin != end)
{
--end;
if (begin != end)
swap (*begin++, *end);
}
}

+

The iterators of type Bi moves backward from end to begin via . Then, the swap algorithm exchanges values of two elements. Such iterators meet all requirements of forward iterator and support (both prefix and postfix). They are bidirectional iterators. All standard containers meet the requirements of bidirectional iterators.

+

Random access

All above iterators access elements in a forward or backward sequence, however, some functions need to access elements starting from arbitrary positions. For example

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
template<class Ran, class X>
bool binary_search(Ran begin, Ran end, const X &x)
{
while (begin < end)
{
// find the midpoint of the range
Ran mid = begin + (end - begin)/2;

// see which part of the range contains x; keep looking only in that part
if (x < *mid)
end = mid;
else if (*mid <x)
begin = mid + 1;

// if we got here, then *mid == x so we are done
else
return true;
}
return false;
}

+

The binary search algorithm looks for a particular element in a sorted container. It always starts to search from the middle point of a sequence, which relies on the ability to do arithmetic on iterators. Such an iterator is called a random access iterator. Specifically, if p and q are random access iterators, they should support all operations that a bidirectional iterator supports, as well as following arithmetic operations

+
    +
  1. p + n, p - n, n + p
  2. +
  3. p-q
  4. +
  5. p[n] (equivalent to *(p + n))
  6. +
  7. p > q, p < q, p <= q, p >= q
  8. +
+

Standard sort algorithm requires random-access iterators. Therefore, vector and string can use standard sort function as their iterators are random-access iterators. list iterators are not random-access iterators and hence list defines its own member sort instead of using the standard sort.

+

Summary

From above analysis, we know that forward, bidirectional and random-access iterators are also valid input iterators as they all meet the requirements of input iterator. In addition, all forward, bidirectional and random-access iterators that are not constant iterators are also valid output iterators.

+

From the perspective of containers, different category of iterator makes some containers distinct, for example, list iterators are bidirectional iterators, forward_list iterators are forward_iterators, vector and string iterators are random access iterators.

+

But why we need input/output iterator? One reason is that not all iterators are assicoated with containers. For example, back_inserter() is an iterator that meet and only meet the requirements of output iterator.

+

Another typical example is that the standard library provides iterators that can be bound to input and output streams, namely, istream_iterator and ostream_iterator.
Apparently, istream_iterator is input iterator that allows us to read successive elements from an input stream.
ostream_iterator is output iterator that allows us to write sequentially to an output stream.

+
1
2
3
vector<int> v;
// read ints from the standard input and append them to v
copy(istream_iterator<int>(cin), istream_iterator<int>(), back_insert(v));
+

This example shows that the first istream_iterator is bound to cin and expects to read values of type int. But the second istream_iterator is not bound to any file.
This is because istream_iterator type has a default value with a sepcial property such that any istream_iterator that has reached end-of-file or is an error state will appear to be equal to the default value. Therefore, we can use the default value of a istream_iterator together with the first input iterator to denote the sequence in the input stream.

+

Similarly, we can use ostream_iterator to write elements to an output stream.

1
2
// write the elements of v each separated from the other by a space
copy(v.begin(), v.end(), ostream_iterator<int> (cout, " "));

+

This statement uses copy algorithm to copy the vector v onto the standard output. ostream_iterator is bound to cout with an additional argument, that is, a space in this case. This additional argument specifies a value to be written after each element and typically is a string literal.

+

Rewrite the split function

Now we apply the new knowledge learned above to rewrite the split function as a template such that it is data-structure independent.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
template <class Out>
void split(const string &str, Out os)
{
typedef string::const_iterator iter;

iter i = str.begin();
while(i != str.end())
{
// ignore leading spaces
i = find_if(i, str.end(), not_space);

// find end of next word
iter j = find_if(i, str.end(), space);

// copy the characters in [i,j)
if(i != str.end())
*os++ = string(i, j);
}
}

+

This function has return type void, i.e. nothing to return. If we want to store each word contained in a line of inputs into a vector, we just need to pass an output iterator. This is also true for a list. For example

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
int main()
{
// list<string> words;
vector<string> words;

string line;
while(getline(cin, line))
{
split(line, back_inserter(words));
}

for(vector<string>::const_iterator it = words.begin(); it != words.end(); ++it)
cout << *it << endl;
return 0;
}

+

Alternatively, we may write all words onto the standard output directly. I take this as an exercise and present a complete program below.

+

mainfunction.cpp

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
#include <iostream>
#include <iterator>
#include <string>
#include "split.h"

using std::cin; using std::ostream_iterator;
using std::cout; using std::string;
using std::endl; using std::back_inserter;

int main(){

string line;
while (getline(cin, line))
{
split(line, ostream_iterator<string>(cout, "\n"));
}

return 0;
}

+

split.h

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
#ifndef GUARD_SPLIT_H
#define GUARD_SPLIT_H

#include <string>
#include <algorithm>

// true if the argument is whitespace, false otherwise
bool space(char c)
{
return isspace(c);
}

// false is the argument is whitespace, true otherwise
bool not_space(char c)
{
return !isspace(c);
}

// template declaration and definition
template <class Out>
void split(const std::string &str, Out os)
{
typedef std::string::const_iterator iter;

iter i = str.begin();
while(i != str.end())
{
// ignore leading spaces
i = std::find_if(i, str.end(), not_space);

// find end of next word
iter j = std::find_if(i, str.end(), space);

// copy the characters in [i,j)
if(i != str.end())
*os++ = std::string(i, j);
i = j;
}
}
#endif /* GUARD_SPLIT_H */

+

Noting that I didn’t separate the definition and declaration of the split template to make them be visible to compiler in the point of instantiation.

+

Let’s type some words

1
2
3
4
what 
a
beautiful
name

+

The results are as expected

1
2
3
4
5
6
7
8
what
what
a
a
beautiful
beautiful
name
name

+

Again type

1
what a beautiful name

+

The program gives

1
2
3
4
what
a
beautiful
name

+ + +
+ + + + + + + + + + + +
+ + + +
+ + + +
+ +
+
+ + +
+ + + + + + + + + +
+ + + + + + + + + +
+
+ + + + +
+ + +
+ + + + +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/03/04/2018/Accelerated-C-Solutions-to-Exercises-Chapter-8/index.html b/03/04/2018/Accelerated-C-Solutions-to-Exercises-Chapter-8/index.html new file mode 100644 index 00000000..09402de4 --- /dev/null +++ b/03/04/2018/Accelerated-C-Solutions-to-Exercises-Chapter-8/index.html @@ -0,0 +1,810 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Accelerated C++ Solutions to Exercises(Chapter 8 Part 1) | Liam's Blog + + + + + + + + + + + + + + + + + + +
+
+ + + +
+
+
+
+ + +
+ + + + + + + + +
+ + + +
+ + + + + + + +
+ + + +

Accelerated C++ Solutions to Exercises(Chapter 8 Part 1)

+ + + +
+ + + + + +
+ + + + + +

Exercise 8-0

Compile, execute, and test the programs in this chapter.

+

Solution & Results

Please find the programs and analysis on Writing generic functions.

+

Exercise 8-1

Note that the various analysis functions we wrote in §6.2/110 share the same behavior; they differ only in terms of the functions they call to calculate the final grade. Write a template function, parameterized by the type of the grading function, and use that function to evaluate the grading schemes.

+

Solution & Results

To parameterize the type of the grading function, I define the type parameter of the analysis function template as Fun. Then what we need to do is replacing all revelant functions with f which is a variable that has type Fun. See the function template below:

+

New grades_analysis.h

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
#ifndef GUARD_GRADES_ANALYSIS_H
#define GUARD_GRADES_ANALYSIS_H

#include <vector>
#include <algorithm>
#include "Student_info.h"
#include "gradingSchemes.h"

template <class Fun>
double analysis(const std::vector<Student_info> &students, Fun &f)
{
std::vector<double> grades;

std::transform(students.begin(), students.end(), std::back_inserter(grades), f);
return median(grades);
}
#endif /* GUARD_GRADES_ANALYSIS_H */

+

The next step is to rewrite the write_analysis function as the original one defines a parameter to take various analysis functions.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
#ifndef GUARD_WRITE_ANALYSIS_H
#define GUARD_WRITE_ANALYSIS_H

#include <iostream>
#include <string>
#include <vector>
#include "Student_info.h"
#include "grades_analysis.h"

template <class Fun>
void write_analysis(std::ostream &out, const std::string &name, Fun &f,
const std::vector<Student_info> &did,
const std::vector<Student_info> &didnt)
{
out << name << ": median(did) = " << analysis(did, f)
<< ": median(didnt) = " << analysis(didnt, f) << std::endl;
}
#endif /* GUARD_WRITE_ANALYSIS_H */

+

Its necessary to put both declaration and definition into the header file for both two function templates due to the fact that they are required to be visible to the compiler in the point of instantiation.

+

Now we can pass different grading functions directly to the write_analysis function to generate analysis reports.

+

mainfunction.cpp

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
#include <iostream>			// to get the declaration of cin, cout, endl
#include <vector> // to get the declaration of vector
#include "Student_info.h" // to get the declaration of Student_info
#include "did_all_hw.h" // to get the declatation of the predicate on students' records
#include "write_analysis.h" // to get the declaration of write_analysis function
#include "gradingSchemes.h" // to get the declarations of various grading functions

using std::vector; using std::cout;
using std::cin; using std::endl;

int main()
{
// students who did and didn't do all their homework
vector<Student_info> did, didnt;

// read the student records and partition time
Student_info student;
while(read(cin, student))
{
if(did_all_hw(student))
did.push_back(student);
else
didnt.push_back(student);
}
// verify thatthe analyses will show us something
if(did.empty())
{
cout << "No student did all the homework!" << endl;
return 1;
}
if(didnt.empty())
{
cout << "Every student did all the homework!" << endl;
return 1;
}

// do the analysis
write_analysis(cout, "median", median_grade_aux, did, didnt);
write_analysis(cout, "average", average_grade, did, didnt);
write_analysis(cout, "median of homework turned in", optimistic_median, did, didnt);

return 0;
}

+

Other files are keep unchanged and can be found below.

+

did_all_hw.cpp

1
2
3
4
5
6
7
8
9
10
#include <algorithm>		// to get the declaration of find
#include "Student_info.h" // to get the declaration of Student_info
#include "did_all_hw.h" // to get the declaration of did_all_hw itself

using std::find;

bool did_all_hw(const Student_info &s)
{
return ((find(s.homework.begin(), s.homework.end(), 0)) == s.homework.end());
}

+

did_all_hw.h

1
2
3
4
5
6
7
8
#ifndef GUARD_DID_ALL_HW_H
#define GUARD_DID_ALL_HW_H

#include "Student_info.h"

bool did_all_hw(const Student_info &s);

#endif /* GUARD_DID_ALL_HW_H */

+

gradingSchemes.cpp

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
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
#include <algorithm>		// to get the declaration of remove_copy
#include <numeric> // to get the declaration of accumulate
#include <stdexcept> // to get the declaration of domain_error
#include <vector> // to get the declaration of vector
#include "Student_info.h" // to get the declaration of Student_info
#include "gradingSchemes.h" // to get the declaration of all functions here to keep consistent

using std::domain_error; using std::istream;
using std::vector; using std::sort;
using std::accumulate;

// final grade function returns weighted average of midterm exam grade,
// final exam grade, and homework grade which will be computed
// using different methods depending on grading schemes
double grade(double midterm, double final, double homework)
{
return 0.2*midterm + 0.4*final + 0.4*homework;
}

// fundermental functions 1: returns the median value of vector<double>
double median(vector<double> vec)
{
// get the size of the vector
typedef vector<double>::size_type vec_size;
vec_size size = vec.size();

// check whether the empty is empty
if (size == 0)
throw domain_error("median of an empty vector");

// sort the grades
sort(vec.begin(), vec.end());

// compute the median homework grade
vec_size mid = size/2;
return size % 2 == 0 ? (vec[mid] + vec[mid - 1])/2 : vec[mid];
}

// fundermental functions 2: returns the average value of vector<double>
double average(const vector<double> &v)
{
// check whether the empty is empty
if (v.empty())
{ throw domain_error("average of an empty vector");}

return accumulate(v.begin(), v.end(), 0.0) / v.size();
}


// grading scheme 1: final grade is based on the median homework grade
double median_grade(const Student_info &s)
{
return median_grade(s.midterm, s.final, s.homework);
}

// grading scheme 1: overloaded median_grade function
double median_grade(double midterm, double final, const vector<double> &hw)
{
if (hw.size() == 0)
throw domain_error("student has done no homework");
return grade(midterm, final, median(hw));
}

// grading scheme 1: auxiliary median_grade function
double median_grade_aux(const Student_info &s)
{
try{
return median_grade(s);
}catch (domain_error){
// students who did no homework at all, get 0 homework grade
return grade(s.midterm, s.final, 0);
}
}

// grading scheme 2: final grade is based on average homework grades
double average_grade(const Student_info &s)
{
try{
return grade(s.midterm, s.final, average(s.homework));
}catch (domain_error){
// students who did no homework at all, get 0 homework grade
return grade(s.midterm, s.final, 0);
}
}

// grading scheme 3: final grade is based on median of the completed homework grades,
// and students who did no homework at all will get 0 homework grade
double optimistic_median(const Student_info &s)
{
vector<double> nonzero;
remove_copy(s.homework.begin(), s.homework.end(), back_inserter(nonzero), 0);

if(nonzero.empty())
return grade(s.midterm, s.final, 0);
else
return grade(s.midterm, s.final, median(nonzero));
}

+

gradingSchemes.h

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
#ifndef GUARD_GRADING_SCHEMES_H
#define GUARD_GRADING_SCHEMES_H

#include<vector>
#include "Student_info.h"

double grade(double midterm, double final, double homework);
double median(std::vector<double> vec);
double average(const std::vector<double> &v);
double median_grade(const Student_info &s);
double median_grade(double midterm, double final, const std::vector<double> &hw);
double median_grade_aux(const Student_info &s);
double average_grade(const Student_info &s);
double optimistic_median(const Student_info &s);

#endif /* GUARD_GRADING_SCHEMES_H */

+

Student_info.cpp

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 "Student_info.h"
using std::vector; using std::istream;

// argument to the function sort
bool compare(const Student_info &x, const Student_info &y)
{
return x.name < y.name;
}

// read the info
istream & read(istream &is, Student_info &s)
{
// reads and store the student's name, midterm and final exam grades
is >> s.name >> s.midterm >> s.final;

// reads and store all homework grades
read_hw(is, s.homework);
return is;
}

// read all homework grades
istream & read_hw(istream &in, vector<double> &hw)
{
if (in){
//get rid of previous contents
hw.clear();

// read homework grades
double x;
while(in >> x)
hw.push_back(x);

// clear the stream so that input will work for the next student
in.clear();
}
return in;
}

+

Student_info.h

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
#ifndef GUARD_Student_info
#define GUARD_Student_info

#include<iostream>
#include<string>
#include<vector>

struct Student_info{
std::string name;
double midterm, final;
std::vector<double> homework;
};

bool compare(const Student_info &, const Student_info &);
std::istream & read(std::istream &, Student_info &);
std::istream & read_hw(std::istream &, std::vector<double> &);
#endif

+

I tested this program using the same inputs as we did in A simple test. It gives the same results as the original progam

1
2
3
4
5
6
7
median: median(did) = 46.1475: median(didnt) = 42.9273
average: median(did) = 45.4202: median(didnt) = 44.3273
median of homework turned in: median(did) = 46.1475: median(didnt) = 52.1273
```

# Exercise 8-2
Implement the following library algorithms, which we used in Chapter 6 and describedin §6.5/121. Specify what kinds of iterators they require. Try to minimize the number of distinct iterator operations that each function requires. After you have finished your implementation, see §B.3/321 to see how well you did.

+

equal(b, e, d) search(b, e, b2, e2)
find(b, e, t) find_if(b, e, p)
copy(b, e, d) remove_copy(b, e, d, t)
remove_copy_if(b, e, d, p) remove(b, e, t)
transform(b, e, d, f) partition(b, e, p)
accumulate(b, e, t)
```

+

Solution & Results

To be updated.

+ + +
+ + + + + + + + + + + +
+ + + +
+ + + +
+ +
+
+ + +
+ + + + + + + + + +
+ + + + + + + + + +
+
+ + + + +
+ + +
+ + + + +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/04/03/2018/Accelerated-C-Solutions-to-Exercise-Chapter-3/index.html b/04/03/2018/Accelerated-C-Solutions-to-Exercise-Chapter-3/index.html new file mode 100644 index 00000000..ccdaf406 --- /dev/null +++ b/04/03/2018/Accelerated-C-Solutions-to-Exercise-Chapter-3/index.html @@ -0,0 +1,892 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Accelerated C++ Solutions to Exercises(Chapter 3 Part 1) | Liam's Blog + + + + + + + + + + + + + + + + + + +
+
+ + + +
+
+
+
+ + +
+ + + + + + + + +
+ + + +
+ + + + + + + +
+ + + +

Accelerated C++ Solutions to Exercises(Chapter 3 Part 1)

+ + + +
+ + + + + +
+ + + + + +

Exercise 3-0

Compile, execute, and test the programs in this chapter

+

Solution & Results

This exercise has been accomplished in C++ - Working with batches of data with detailed explination.

+
+

Exercise 3-1

Suppose we wish to find the median of a collection of values. Assume that we have read some of the values so far, and that we have no idea how many values remain to be read. Prove that we cannot afford to discard any of the values that we have read. Hint: One proof strategy is to assume that we can discard a value, and then find values for the unread—and therefore unknown—part of our collection that would cause the median to be the value that we discarded.

+

Solution & Results

It is known that the median value of a data sample is sensitive to the number of elements. If the number is an odd number, the mid element is unique and the median value is the value of this mid element. However, if the number is an even number, there exist two mid elements and the median value is the average value of these two elements. Consider that, once a value is discarded, the number of the elements changes from odd (or even) to even (odd), and hence the median value would be inaccurate.

+

For example, follow sequence of values is part of a data sample,

1
2 8 3 4 9 7 0

+

the unknown part is

1
11 6 13 9

+

The ture median value is 7. If 0 is discarded, the median value becomes 7.5. If 7 is discarded, the median value is still 7. If 9 is discarded, the median value is 6.5. This indicates that the median value would be an unreliable measure for a data sample if any of values is discarded.

+
+

Exercise 3-2

Write a program to compute and print the quartiles (that is, the quarter of the numbers with the largest values, the next highest quarter, and so on) of a set of integers.

+

Solutions & Results

Quartiles of an ordered dataset are values that divide the data set into four equal parts, i.e. quarters. An intuitive strategy is to find the median value of the whole data set, and then find the median values of the divided two parts respecitively. As a result, there will exist three median values which are first quartile, second quartile and third quartile relative to the whole data set. Therefore, once we know how to compute the median value, we know how to compute quartiles. Now I’ll enter into the details of how to implement such a computing algorithm.

+

data preparation

At the very beiging stage, we need to store and sort all values

+
1
2
3
4
5
6
7
 cout << "Please enter a sequence of integers, "
"followed by end-of-file: ";
int x;
vector<int> integers; // to hold all values
while(cin >> x)
integers.push_back(x);
sort(integers.begin(), integers.end());
+

find the median value

Now we have a sorted data set. Due to the calculation of a median value depends on the number of elements in the data set, we need to consider several cases. The number of elements is the size of integers.

1
2
typedef vector<int>::size_type vec_size;
vec_size size = integers.size();

+
    +
  1. if there is no elements(i.e. size == 0), it is impossible to compute the median value.

    +
    1
    2
    3
    4
    5
    6
    if (size == 0)
    {
    cout << "You must enter at least one integer. "
    "Please try again.";
    return 0;
    }
    +
  2. +
  3. if size is an even number, the median value is the average value of two mid elements. In addition, size/2 is exactly devided and represent the number of each side elements.
    Due to the index of a vector starts from 0, the mid elements should be integers[size/2 - 1] and integers[size/2]. Therefore, the median value is the average of the corresponding two values. As shown in above graph, the number of elements of both sides equals to size/2 because

    +
    1
    2
    left side:  number = size/2 - 1 - 0 + 1 = size/2
    right side: number = size - 1 - size/2 + 1 = size/2
    +
  4. +
+

If size is an even number

+
    +
  1. if size is an odd number, the median value is the value of the unique mid element. size/2 yields the same value as (size-1)/2 in c++. Then the mid element is exact the integers[(size-1)/2] as both sides of this element has same number of elements(as shown in below graph). This can also be verified mathematically
    1
    2
    left side:  number = (size-1)/2 - 1 - 0 + 1 = (size-1)/2
    right side: number = size - 1 - {(size-1)/2 + 1} + 1 = (size-1)/2;
    +
  2. +
+

If size is an odd number

+

Now let’s translate the algorithm into real code

1
2
3
4
vec_size mid = size/2;
double Q2; // the median is in fact the second quartile denoted by Q2
Q2 = size % 2 == 0 ? (integers[mid - 1] + integers[mid])/2.0
: integers[mid];

+

Note that when calculate the average of two mid elements, I divide by 2.0 rather than 2 for the purpose of avoiding the loss of precision.This is because the quotient of two integers will be an integer.

+

find the first quartile

From above analysis, we have known how to find a median, i.e. the second quartile of the dataset. The median has divided the data into two equal groups: first group is from the smallest value to the median and the other one is from the median value to the largest value. Therefore, the middle value of the first group is in fact the first quartile(also known as lower quartile) and the middle value of the second group is the third quartile (also known as upper quartile). We can apply the same method to find both middle values. Similarly, the first step is to find the size (denoted by half_size) for both groups and discuss different cases.

+

From above analysis, we know that

+
    +
  1. if size is an even number, half_size == size/2.
  2. +
  3. if size is an odd number, half_size == (size-1)/2. See wikipedia-Quartile Method 1
  4. +
+
1
2
vec_size half_size; // defind a variable to represent the size of two equal groups.
half_size = size % 2 == 0 ? size/2 : (size-1)/2;
+

Now let’s find the middle value of the first group values.

+
    +
  1. if half_size == 0, then size == 1. In this case, the single element is all we have and hence all quartiles equals to the value of the single element.
  2. +
+
1
2
3
4
// variables to hold the first quartile and third quartile
double Q1, Q3;
if (half_size == 0)
Q1 = Q3 = integers[0];
+
    +
  1. if half_size is an even number, half_size/2 is exactly divided and the mid elements are integers[half_size/2] and integers[half_size/2 - 1].

    +
  2. +
  3. if half_size is an odd number, half_size/2 gives the value of (half_size - 1)/2. The middle value is integers[(half_size-1)/2].

    +
  4. +
+

Both two cases are exactly the same as finding the median for the whole dataset.

+
1
2
vec_size mid_first = half_size/2;
Q1 = half_size % 2 == 0 ? (integers[mid_first - 1] + integers[mid_first])/2.0 : integers[mid_first];
+

find the third quartile

The upper quartile is computed as same as the lower quartile except the index to be applied. If size is even, then the starting point for the second half is mid, while if size is odd, the starting point is mid+1. Therefore

1
2
3
vec_size mid_second = size % 2 == 0 ? (half_size/2 + mid) : (half_size/2 + mid + 1);
Q3 = half_size % 2 ? (integers[mid_second - 1] + integers[mid_second])/2.0
: integers[mid_second];

+

A complete program

Now let’s put all pieces of code togther and add appropriate headers as well s using declarations.

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
65
#include <algorithm>
#include <iomanip>
#include <ios>
#include <iostream>
#include <string>
#include <vector>

using std::cin; using std::sort;
using std::cout; using std::streamsize;
using std::endl; using std::string;
using std::setprecision; using std::vector;

int main()
{
// ask for and read integers
cout << "Please enter a sequence of integers, "
"followed by end-of-file: ";
int x;
vector<int> integers; // to hold all values

while(cin >> x)
integers.push_back(x);
sort(integers.begin(), integers.end());

// get the size of the dataset
typedef vector<int>::size_type vec_size;
vec_size size = integers.size();

if (size == 0)
{
cout << "You must enter at least one integer. "
"Please try again.";
return 0;
}

// find the median value which in fact is the second quartile denoted by Q2
vec_size mid = size/2;
double Q2;
Q2 = size % 2 == 0 ? (integers[mid - 1] + integers[mid])/2.0 : integers[mid];

// get the size of two equal groups.
vec_size half_size;
half_size = size % 2 == 0 ? size/2 : (size-1)/2;

// find the first quartile and third quartile denoted by Q1 and Q3 respectively
double Q1, Q3;
if (half_size == 0)
{
Q1 = Q3 = integers[0];
}
else
{
vec_size mid_first = half_size/2;
Q1 = half_size % 2 == 0 ? (integers[mid_first - 1] + integers[mid_first])/2.0 : integers[mid_first];

vec_size mid_second = size % 2 == 0 ? (half_size/2 + mid) : (half_size/2 + mid + 1);
Q3 = half_size % 2 == 0 ? (integers[mid_second - 1] + integers[mid_second])/2.0 : integers[mid_second];
}

streamsize prec = cout.precision();
cout << setprecision(3) << "The first quartile is: " << Q1 << endl;
cout << "The second quartile is: " << Q2 << endl;
cout << "The second quartile is: " << Q3 << setprecision(prec) << endl;
return 0;
}

+

Test performance

Test sequence 1: 1 2

1
2
3
4
Please enter a sequence of integers, followed by end-of-file: 1 2
The first quartile is: 1
The second quartile is: 1.5
The second quartile is: 2

+

Test sequence 2: 1 2 3

1
2
3
4
Please enter a sequence of integers, followed by end-of-file: 1 2 3
The first quartile is: 1
The second quartile is: 2
The second quartile is: 3

+

Test sequence 3: 1 2 3 4

1
2
3
4
Please enter a sequence of integers, followed by end-of-file: 1 2 3 4
The first quartile is: 1.5
The second quartile is: 2.5
The second quartile is: 3.5

+

Test sequence 3: 1 3 5 6 9 0 3 2 5 3 8

1
2
3
4
Please enter a sequence of integers, followed by end-of-file: 1 3 5 6 9 0 3 2 5 3 8 
The first quartile is: 2
The second quartile is: 3
The second quartile is: 6

+

Yeah, it works perfectly.

+
+

Exercise 3-3

Write a program to count how many times each distinct word appears in its input.

+

Solution & Results

Intuitive explanations

The purposes of this program is to write out each distinct word followed by its occurrence numbers in the input. And what we have is only unknown amount of words to be entered. Imagine that there is only one word(e.g. word1) in total, then the output will be:

1
word1 appears 1 times

+

Now in the case of two words, e.g. word1 and word2, if word1 is the same as word2, then the output will be:

1
word1 appears 2 times

+

otherwise

1
2
word1 appears 1 times
word2 appears 1 times

+

From these two cases, we have seen that

+
    +
  1. word1 needs to be stored and its occurrence needs to be recorded
  2. +
  3. word2 needs to be compared with word1. As a result, it will be discarded if they are the same and the occurrence number of word1 is increased by 1, and it will be stored if they are different and its occurrence number increases by 1.
  4. +
+

By analogy, following entered words will be compared with each stored word, and will be discarded if there already exist one same word otherwise will be stored, meanwhile, the corresponding occurrence numbers are adjusted. Finally, all stored words are distinct with eachother and their occurrence numbers have been clearly recorded. Now I enter the details of this program.

+

vectors and the structure

To hold each distinct word and the associated number of occurrence, I define two vectors whis have types of int and string respectively.

1
2
vector<int> counter;
vector<string> words;

+

The next step is to ask for enterring word by word and store the distinct word. This can be accomplished with a while statement. To write all distinct words as well as the occurrence numbers, we can loop through words and counter using index from 0 to words.size() - 1.
Therefore, the whole structure is

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
int main()
{
vector<int> counter;
vector<string> words;
typedef vector<string>::size_type vec_size;

string word;
while(cin >> word)
{
/* pseudocode
* if the word is a new distinct word
* words.push_back(word);
* counter.push_back(1);
* if the word already exists
* adjust the number of occurrence for
* the existed distinct word
*/
}

// if there is no inputs, send warning
if (words.empty())
{
cout << "You must input at least one word. Please try again.";
return 1;
}

for (vec_size i = 0; i != words.size(); ++i)
{
cout << words[i] << " appears " << counter[i] << " times" << endl;
}
return 0;
}

+

loop invariants

The only part that is needed is write the while body. The loop invariant is that words contains each distinct word entered and counter contains the associated number of occurrence. Therefore, two goals need to be accomplished inside the while loop:

+
    +
  1. check whether the current is distinct from all existed distinct words
  2. +
  3. adjust word and counter to maintain the loop invariant
  4. +
+

The first goal can be accomplished by comparing the current word with each word stored in words. In addition, a flag is set to indicate the status of the outcomes, that is, a distinct word or an existed word. Let’s see the code below

1
2
3
4
5
6
7
8
9
10
11
12
int flag = 0; // initial status
for (vec_size i = 0; i != words.size(); ++i)
{
if (word == words[i])
{
// if the word is already existed, discard the word but change the counter
++counter[i];

// change status show this word is not a distinct word
flag = 1;
}
}

+

The second goal is partly accomplished by the code above and the rest case is when the current word is a distinct word. Accordingly, the word should be stored into words and the initial value for occurrence number is 1.

1
2
3
4
5
if (flag == 0)
{
words.push_back(word);
counter.push_back(1);
}

+

Now the program is finished, and please find the complete version in the following part. I also did several tests and it works as expected. Note that I omitted the process that verify the correctness of the loop invariants here.

+

A complete 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
56
57
58
59
60
61
62
63
64
65
66
#include <string>
#include <vector>
#include <iostream>

using std::cin; using std::string;
using std::cout; using std::vector;
using std::endl;

int main()
{
// vectors for holding each distinct word and its occurrence numbers
vector<int> counter;
vector<string> words;

// using type alias for convenience
typedef vector<string>::size_type vec_size;

// ask for and read words one by one
cout << "Please enter a sequence of words, and followed by end-of-file: ";
string word;

// while loop invariant: words and counter contains each distinct word and the associated times
while(cin >> word)
{
// set flags to indicate whether the current word already exists
// flag == 0: the word is distinct from each existed distinct word
// flag == 1: the word is already exist
int flag = 0;

// to maintain the outer loop invariant, compare the current word
// with each existed distinct word using following for loops

// for loop invariant: the current word has been compared with the ith distinct word
for (vec_size i = 0; i != words.size(); ++i)
{
// adjust the number of occurrence by 1 for existed distinct words
if (word == words[i])
{
++counter[i];
flag = 1; // change flag value to indicate that this word is distinct
}
}

// maintain the outer loop invariant: store the new distinct word and its occurrence number
if (flag == 0)
{
words.push_back(word);
counter.push_back(1);
}
}

// send warning if there is no any words entered
if (words.empty())
{
cout << "You must input at least one word. Please try again.";
return 1;
}

// write a blank line to seperate the outputs
cout << endl;
for (vec_size i = 0; i != words.size(); ++i)
{
cout << words[i] << " appears " << counter[i] << " times" << endl;
}
return 0;
}
+

Test performance

Test 1: house

1
2
3
Please enter a sequence of words followed by end-of-file: house

house appears 1 times

+

Test 2: house number one and number two

1
2
3
4
5
6
7
Please enter a sequence of words followed by end-of-file: house number one and number two

house appears 1 times
number appears 2 times
one appears 1 times
and appears 1 times
two appears 1 times

+
+

Exercise 3-4

Write a program to report the length of the longest and shortest string in its input.

+

Solution & Results

Strategy 1

A very simple solution strategy to this exercise is that store the length of each string into a vector and then implement a library sort algorithm. I won’t go into details as it is simple. Please find the code and tests below.

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
#include <algorithm>
#include <string>
#include <vector>
#include <iostream>

using std::cin; using std::string;
using std::cout; using std::vector;
using std::endl;

int main()
{
// ask for words
cout << "Please enter words followed by end-of-file: ";
string word;

// creat a vector for holding the length of each string
typedef string::size_type word_size;
vector<word_size> length;

// read word and store the length
while(cin >> word)
{
length.push_back(word.size());
}

if (length.empty())
{
cout << "You must input at least one word."
"Please try again.";
return 1;
}

// sort all lengths in an nondecreasing order
sort(length.begin(), length.end());
cout << "The length of the shortest string is: " << length[0] << endl;
cout << "The length of the longest string is: " << length[length.size() - 1] << endl;
return 0;
}

+

Test 1: I am a good teacher

1
2
3
Please enter words followed by end-of-file: I am a good teacher 
The length of the shortest string is: 1
The length of the longest string is: 7

+

Test 2: what are you going to do

1
2
3
Please enter words followed by end-of-file: what are you going to do
The length of the shortest string is: 2
The length of the longest string is: 5

+

Strategy 2

Above strategy is computational inefficient due to the fact that it sorts all length values while we only need two extremes. An alternative strategy is to use insert sort algorithm but only sort one round for each of extremes. For example, now there is a sequence of integers

1
n1 n2 n3 n4 ... nx ny nz

+

Step 1: assumming the largest number is n1.

+

Step 2: compare n1 and n2, if n1 >= n2, we exchange their positions with eachother. But if n1 < n2, we keep their order and assume n2 is the largest number.

+

Step 3: compare the larger number of step 2 with n3. Deal with the comparison result as same as that in step 2.

+

Step 4: continue comparison until the last number. Now the number in the rightest position is the final result, i.e. the largest number.

+

By analogy, we can find the smallest number. In this case, the precedure is simpler as we don’t need to exchange their positions. For finding the largest number, we simply discard the smaller value in each comparison. It is pretty easy to understand and no more detailed description here. Please see the complete program below.

+
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
#include <iostream>
#include <string>

using std::cin; using std::endl;
using std::cout; using std::string;


int main()
{
// ask for inputs
cout << "Please enter words followed by end-of-file: ";
string word;
typedef string::size_type str_size;

// variables for holding the length values of the longest and shortest strings
str_size longestLength = 0;
str_size shortestLength = 0;

// read words one by one
while(cin >> word)
{
// insert sort to get the largest value and samllest value only
if (longestLength == 0 || longestLength < word.size())
longestLength = word.size();
if (shortestLength == 0 || shortestLength > word.size())
shortestLength = word.size();
}

// if there is no inputs, send warning
if (longestLength == 0)
{
cout << "You must enter at least one word. Please try again.";
return 1;
}

cout << "The length of the shortest string is: " << shortestLength << endl;
cout << "The length of the longest string is: " << longestLength << endl;
return 0;
}
+

Test 1: I am a good teacher

1
2
3
Please enter words followed by end-of-file: I am a good teacher 
The length of the shortest string is: 1
The length of the longest string is: 7

+

Test 2: what are you going to do

1
2
3
Please enter words followed by end-of-file: what are you going to do
The length of the shortest string is: 2
The length of the longest string is: 5

+

The results of above two programs are exactly the same.

+
+

To be continued.

+ + +
+ + + + + + + + + + + +
+ + + +
+ + + +
+ +
+
+ + +
+ + + + + + + + + +
+ + + + + + + + + +
+
+ + + + +
+ + +
+ + + + +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/04/04/2018/Accelerated-C-Solutions-to-Exercises-Chapter-8-Part-2/index.html b/04/04/2018/Accelerated-C-Solutions-to-Exercises-Chapter-8-Part-2/index.html new file mode 100644 index 00000000..991bc50f --- /dev/null +++ b/04/04/2018/Accelerated-C-Solutions-to-Exercises-Chapter-8-Part-2/index.html @@ -0,0 +1,840 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Accelerated C++ Solutions to Exercises(Chapter 8 Part 2) | Liam's Blog + + + + + + + + + + + + + + + + + + +
+
+ + + +
+
+
+
+ + +
+ + + + + + + + +
+ + + +
+ + + + + + + +
+ + + +

Accelerated C++ Solutions to Exercises(Chapter 8 Part 2)

+ + + +
+ + + + + +
+ + + + + +

Exercise 8-3

As we learned in §4.1.4/58, it can be expensive to return (or pass) a container by value. Yet the median function that we wrote in §8.1.1/140 passes the vector by value. Could we rewrite the median function to operate on iterators instead of passing the vector? If we did so, what would you expect the performance impact to be?

+

Solution & Results

Yes, we can rewrite the median function without passing the vector. See the median funtion template below

1
2
3
4
5
6
7
8
9
10
11
12
13
// median function template
template<class In>
typename In::value_type median(In begin, In end)
{
if(begin == end)
throw domain_error("median of an empty vector");

sort(begin, end);

typename In::difference_type size = end - begin;
In mid = begin + size/2;
return size % 2 == 0 ? (*mid + *(mid - 1))/2 : *mid;
}

+

The type parameter of the function template is In which will be infered from two arguments, i.e. two iterators that denote the range of a sequence. In addition, The Iterator::value_type gives the type of the element the iterator can point to. Therefore, we can set the return type using

1
typename In::value_type

+

If the container is vector, this expression is equivalent to

1
vector<double>::iterator::value_type

+

In the original version, all computations are based on values of type size_type. But now we don’t have such information anymore. Instead we can do computations with iterators. Firstly, we checks whether the container is empty using begin == end. The sort function is applied as same as that in the original version. Then we get the “size” of the container using end - begin, which gives the distance between the first element and one past the last elements. The result of such computation has type difference_type which is in fact a signed integer value.

+

Once we have the “size”, we can get the position of the mid point. Finally we can compute the median by accessing elements via dereference operation on iterators. One might wonder that why we get the position of the mid point using begin + size/2 rather than (begin + end)/2? This is because iterators doesn’t support additive operation between two iterators, but they (random access iterators) do support additive operation between an iterator and an integer value.

+

Now let’s perform following test and check what happens:

+

test 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
#include <iostream>	// to get the declaration of cout, endl
#include <vector> // to get the declaration of vector
#include <stdexcept> // to get the declaration of domain_error
#include <algorithm> // to get the declaration of sort

using std::cout; using std::vector;
using std::endl; using std::domain_error;
using std::sort;

// print function template
template<class T>
void print(T &contianer)
{
cout << *(contianer.begin());
for(typename T::const_iterator i = contianer.begin() + 1; i != contianer.end(); ++i)
cout << ' ' << *i;
cout << endl;
}

// median function template
template<class In>
typename In::value_type median(In begin, In end)
{
if(begin == end)
throw domain_error("median of an empty vector");

sort(begin, end);
typename In::difference_type size = end - begin;
In mid = begin + size/2;

return size % 2 == 0 ? (*mid + *(mid - 1))/2 : *mid;
}

int main()
{
// print a sequence
vector<double> vec{9, 6, 8, 3, 5, 7, 1, 2};

cout << "Original sequence: ";
print(vec);

// get the median of the original sequence
cout << "The median value is: " << median(vec.begin(), vec.end()) << endl;

// print the new sequence
cout << "Now the sequence becomes: ";
print(vec);
return 0;
}

+

results

1
2
3
Original sequence: 9 6 8 3 5 7 1 2
The median value is: 5.5
Now the sequence becomes: 1 2 3 5 6 7 8 9

+

The results show that we successfully compute the median value but change the original sequence due to operate on the original container directly. This is the cost that calling by reference instead of calling by value.

+

Exercise 8-4

Implement the swap function that we used in §8.2.5/148. Why did we call swap rather than exchange the values of beg and end directly? Hint: Try it and see.

+

Solution & Results

1
swap(*begin++, *end);
+

To swap two elements, generally we can write

1
2
3
4
T temp = *begin;
*begin = *end;
*end = x;
++begin;

+

where T is the type of the elements begin refers to.
Clearly, the key to solution is how to determine T. One way is to deduce the type from the varaible automatically using auto (or decltype) specifier. Another way is to use value_type to get the type of the element the iterator can point to. I tried both two methods and the test program is shown below

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
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
#include <iostream>		// to get the declaration of cout, endl
#include <algorithm> // to get the declaration of swap
#include <vector> // to get the declaration of vector

using std::cout; using std::endl;
using std::vector; using std::swap;

// print function template
template <class T>
void print(T &t)
{
for (typename T::const_iterator i = t.begin(); i != t.end(); ++i)
cout << *i << endl;
}

// original reverse function template
template<class Bi>
void reverse_ori(Bi begin, Bi end)
{
while (begin != end)
{
--end;
if (begin != end)
{
swap(*begin++, *end);
}

}
}

// revised reverse function version 1: using auto
template<class Bi>
void reverse_v1(Bi begin, Bi end)
{
while (begin != end)
{
--end;
if (begin != end)
{
auto temp = *begin;
*begin = *end;
*end = temp;
++begin;
}

}
}

// revised reverse function version 2: using value_type
template<class Bi>
void reverse_v2(Bi begin, Bi end)
{
typedef typename Bi::value_type valueType;
while (begin != end)
{
--end;
if (begin != end)
{
valueType temp = *begin;
*begin = *end;
*end = temp;
++begin;
}

}
}

int main()
{
vector<int> vec{1, 2, 3, 4, 5, 6, 7, 8, 9, 10};

// reverse
reverse_ori(vec.begin(), vec.end());
print(vec);

// reverse again
cout << endl;
reverse_v1(vec.begin(), vec.end());
print(vec);

// reverse again
cout << endl;
reverse_v2(vec.begin(), vec.end());
print(vec);

return 0;
}

+

This program gives following outputs

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
10
9
8
7
6
5
4
3
2
1

1
2
3
4
5
6
7
8
9
10

10
9
8
7
6
5
4
3
2
1

+

We have seen that both three method works fine, but why we use swap? The major limitation of other two methods is that they both copies objects to and from a variable, which may be not allowed for some highlevel objects. Also, it is not inefficient to do if we can avoid performing copies. The sandard swap algorithm is also an template and hence can determine the elements type automatically. As far as I know, it doesn’t rely on such temp variable as other two methods. I’ll give more explanations about swap once I know how exactly it works.

+

Exercise 8-5

Reimplement the gen_sentence and xref functions from Chapter 7 to use output iterators rather than writing their output directly to a vector . Test these new versions by writing programs that attach the output iterator directly to the standard output,and by storing the results in a list and a vector.

+

Solution & Results

To be updated.

+

Exercise 8-6

Suppose that m has type map , and that we encounter a call to copy(m.begin(), m.end(), back_inserter(x)). What can we say about the type of x ? What if the call were copy(x.begin(), x.end(), back_inserter(m)) instead?

+

Solution & Results

1
copy(m.begin(), m.end(), back_inserter(x));
+

This statement copy all elements of m into the destination sequence x via back_inserter. It is known that x should support push_back member function. In addition, the elements contained in m have type pair. Therefore, x should be a container that has type c<pair>. Standard contaniers that meet the first condition include vector, list, deque, string. But string can only store string literals. In summary, I presume that x can be

+
    +
  1. vector<pair>
  2. +
  3. list<pair>
  4. +
  5. deque<pair>
  6. +
+

To verify my expectation, I write a test program shown as below:

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
#include <iostream>		// to get the declaration of cout, endl
#include <vector> // to get the declaration of vector
#include <string> // to get the declaration of string
#include <list> // to get the declaration of list
#include <deque> // to get the declaration of deque
#include <map> // to get the declaration of map
#include <algorithm> // to get the declaration of copy
#include <iterator> // to get the declaration of back_inserter
#include <utility> // to get the declaration of pair

using std::cout; using std::copy;
using std::endl; using std::vector;
using std::map; using std::string;
using std::list; using std::pair;
using std::deque; using std::back_inserter;

template <class T>
void print(T &t)
{
for (typename T::const_iterator i = t.begin(); i != t.end(); ++i)
cout << i->first << ' ' << i->second << endl;
}

int main()
{
typedef pair<int, string> element;
vector<element> x;
list<element> y;
deque<element> z;
map<int, string> m{{1, "abc"}, {2, "bcd"}, {3, "def"}, {4, "hig"}, {5, "klm"}};
copy(m.begin(), m.end(), back_inserter(x));
copy(m.begin(), m.end(), back_inserter(y));
copy(m.begin(), m.end(), back_inserter(z));

print(x);
cout << endl;
print(y);
cout << endl;
print(z);

return 0;
}

+

The results show that all these three types of container work fine for the copy algorithm.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
1 abc
2 bcd
3 def
4 hig
5 klm

1 abc
2 bcd
3 def
4 hig
5 klm

1 abc
2 bcd
3 def
4 hig
5 klm

+

For the second function call

1
copy(x.begin(), x.end(), back_inserter(m));

+

I presume it doesn’t work because map doesn’t have member function push_back. We can correct this function call as

1
copy(x.begin(), x.end(), inserter(m, m.end());

+

If we add follow statements to above program

1
2
3
4
5

vector<element> n{{10, "abcd"}, {20, "bcde"}, {30, "defh"}};
map<int, string>::iterator it = m.end();
copy(n.begin(), n.end(), inserter(m, it));
print(m);

+

The modified program gives results as expected

1
2
3
4
5
6
7
8
1 abc
2 bcd
3 def
4 hig
5 klm
10 abcd
20 bcde
30 defh

+

Noting that, though we insert at the position where m.end() denotes, the new element may not appear starting from the end of the map. This is because that the map automatically sorts elements according to the values of keys.

+
+

Exercise 8-7

Why doesn’t the max function use two template parameters, one for each argument type?

+

Solution & Results

Let’s write the max function using two template parameters:

1
2
3
4
5
template<class T, class P>
P max(const T& left, const P& right)
{
return left > right ? left : right;
}

+

The biggest problem is how can we correctly set the return type. There are two types T and P. If we set return type as T, the value returned may has type P, and vice versa. Concequently, type coversion happens. However, the result of type conversion depends on the range of the values that the types permit. For example, we call the max function as follows

1
2
3
4
int main()
{
cout << max(3500.9, 'a');
}

+

If we set return type P, what happens of this program is unknown.

+

Therefore, such function template is non-practical and may lead to fatal errors.

+
+

Exercise 8-8

n the binary_search function in §8.2.6/148, why didn’t we write (begin + end)/ 2 instead of the more complicated begin + (end - begin) /2 ?

+

Solution & Results

This is because that iterators doesn’t support additive operation between two iterators. For random access iterators, they do support arithmetic operator + and - between an iterator and an ineger value, e.g.

1
2
3
iterator_i + integer_j
integer_j + iterator_i
iterator_i - integer_j

+

In addition, they support substracting an iterator from another, e.g.

1
iterator_i - iterator_j

+

Such operation yields an value of type difference_type, which in fact is an signed integer. Therefore, it is legal to do

1
begin + (end - begin) /2

+

as such expression is in fact the additive operation between an iterator and an iteger value.

+

But it makes no sence to do

1
(begin + end)/ 2

+
+

Reference

Koenig, A. and Moo, B.E., 2000. Accelerated C++.

+

Lippman, S.B., Lajoie, J. and Moo, B.E., 2013. C++ Primer, 5th edt.

+ + +
+ + + + + + + + + + + +
+ + + +
+ + + +
+ +
+
+ + +
+ + + + + + + + + +
+ + + + + + + + + +
+
+ + + + +
+ + +
+ + + + +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/05/03/2018/Accelerated-C-Solutions-to-Exercises-Chapter-3-Part-2/index.html b/05/03/2018/Accelerated-C-Solutions-to-Exercises-Chapter-3-Part-2/index.html new file mode 100644 index 00000000..209174a1 --- /dev/null +++ b/05/03/2018/Accelerated-C-Solutions-to-Exercises-Chapter-3-Part-2/index.html @@ -0,0 +1,817 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Accelerated C++ Solutions to Exercises(Chapter 3 Part 2) | Liam's Blog + + + + + + + + + + + + + + + + + + +
+
+ + + +
+
+
+
+ + +
+ + + + + + + + +
+ + + +
+ + + + + + + +
+ + + +

Accelerated C++ Solutions to Exercises(Chapter 3 Part 2)

+ + + +
+ + + + + +
+ + + + + +

Exercise 3-5

Write a program that will keep track of grades for several students at once. The program could keep two vectors in sync: The first should hold the student’s names, and the second the final grades that can be computed as input is read. For now, you should assume a fixed number of homework grades.

+

Solution & Results

The exercise tries to make the original program (see the first program) more practical. There are two more requirements compared with the original one: first, it requires computing the final grades for several students at once; second, it requires keep tracking both the students’ name and their final grades.

+

To meet the first requirements, we can add a while loop which allows us compute the final grades multiple times for different student. The condition will be an variable whose value depends on users. Let’s finish this first

1
2
3
4
5
6
7
8
9
10
11
12
13
14
// set initial status
bool running = true;
while(running)
{
// compound statements


char flag;
cout << "Do you want to check more students? Please input Y/N: ";
cin >> flag;

if (flag == 'Y') ;
else running = false;
}

+

To meet the second requirement, we can store both names and the final grades into two vectors.

1
2
vector<string> names;
vector<double> finalGrades;

+

Each time we store a student’s name, we will store his final grade after computation. When needs writing the outputs, we can use same index for both vectors as there is a one-to-one correspondence.

+

Below is the modified 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
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
#include <iomanip>
#include <ios>
#include <iostream>
#include <string>
#include <vector>

using std::cin; using std::setprecision;
using std::cout; using std::string;
using std::endl; using std::streamsize;
using std::vector;

int main()
{
vector<string> names;
vector<double> finalGrades;
bool running = true;
while(running)
{
// ask for and read the student's name
cout << "Please enter your first name: ";
string name;
cin >> name;

// store the name into names
names.push_back(name);
cout << "Hello, " << name << "!" << endl;

// ask for and read the midterm and final grades
cout << "Please enter you midterm and final exam grades: ";
double midterm, final;
cin >> midterm >> final;

// ask for the number of homework grades
cout << "Please enter the number of your homeworks: ";
int numofHomeworks;
cin >> numofHomeworks;

// ask for all homework grades
cout << "Enter all your homework grades: ";

// the number and the sum of grades read so far
int count = 0;
double sum = 0;

// a variable into which to read
double x;

// invariant: we have read count grades so far, and sum is the sum of the first count grades
while (count < numofHomeworks)
{
++count;
cin >> x;
sum += x;
}

// compute the fianl grade and store into finalGrades
double finalGrade = 0.2 * midterm + 0.4 * final + 0.4 * sum/count;
finalGrades.push_back(finalGrade);

// check condition
char flag;
cout << "Do you want to check more students? Please input Y/N: ";
cin >> flag;
if (flag == 'Y') ;
else running = false;
}

// prepare for writing outputs
typedef vector<string>::size_type vec_size;


for (vec_size i = 0; i != names.size(); ++i)
{
streamsize prec = cout.precision();
cout << names[i] << "'s final grade is: " << setprecision(3) << finalGrades[i]
<< setprecision(prec) << endl;
}
return 0;
}

+

It’s not complex once figure out the original program. I test the program and it works well (see below).
Test

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
Please enter your first name: Conor
Hello, Conor!
Please enter you midterm and final exam grades: 88 70
Please enter the number of your homeworks: 3
Enter all your homework grades: 65 76 81
Do you want to check more students? Please input Y/N: Y
Please enter your first name: Brendan
Hello, Brendan!
Please enter you midterm and final exam grades: 70 80
Please enter the number of your homeworks: 2
Enter all your homework grades: 90 85
Do you want to check more students? Please input Y/N: Y
Please enter your first name: Robin
Hello, Robin!
Please enter you midterm and final exam grades: 90 90
Please enter the number of your homeworks: 5
Enter all your homework grades: 80 70 60 50 40
Do you want to check more students? Please input Y/N: N

Conor's final grade is: 75.2
Brendan's final grade is: 81
Robin's final grade is: 78

+

Note that the final grades don’t retain the tail zeros and the dot point though we set precision arguments as 3. Please find more experiments about setprecision at Working with batches of data .

+
+

Exercise 3-6

The average-grade computation in the first program might divide by zero if the student didn’t enter any grades. Division by zero is undefined in C++, which means that the implementation is permitted to do anything it likes. What does your C++ implementation do in this case? Rewrite the program so that its behavior does not depend on how the implementation treats division by zero.

+

Solution & Results

To figure out how c++ implementation deals with division by 0, I did several experiments.
Experiment 1

1
2
3
4
5
6
7
8
9
10
#include <iostream>
using std::cout;
using std::endl;

int main(){
int i = 10;
int j = 0;
cout << i/j << endl;
returo 0;
}

+

The result shows that disivion by 0 with both integers makes the program crash.

+

Experiment 2

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
#include <iostream>
using std::cout;
using std::endl;

int main(){
int i = 10;
double j = 0;
cout << i/j << endl;

double x = 10;
int y = 0;
cout << x/y << endl;

double m = 10;
double n = 0;
cout << m/n << endl;

return 0;
}

+

This program works and returns three inf which means infinity.

+

Exerpriment 3

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
#include <iostream>
using std::cout;
using std::endl;

int main(){

double h = 0;
double k1 = 0;
int k2 = 0;

cout << h/k1 << endl;
cout << h/k2 << endl;

return 0;
}

+

The third program is the case of the original program and it returns nan which means Not-A-Number.

+

There is one question on quora Why does division by zero return INF (infinite) with floats, but makes the program crash with integers in C++?. Many answers can be found there but I can’t understand the mechanism well.

+

Anyway, division by 0 is a special case and should be treated seperately. For the original program, one way is to check the count before the division. For example, we can add below piece of code after the while loop finishes

1
2
3
4
5
   if (count == 0)
{
cout << "No homework grades entered, please try again.";
return 1;
}

+

Alternatively, we can set a default value for the average homework grade.

1
2
3
4
5
double homeworkGrade;
if (count == 0)
homeworkGrade = 0;
else
homeworkGrade = sum/count;

+

Now I apply the second method and present the modified program below.

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
#include <iomanip>
#include <ios>
#include <iostream>
#include <string>

using std::cin; using std::setprecision;
using std::cout; using std::string;
using std::endl; using std::streamsize;

int main()
{
// ask for and read the student's name
cout << "Please enter your first name: ";
string name;
cin >> name;
cout << "Hello, " << name << "!" << endl;

// ask for and read the midterm and final grades
cout << "Please enter you midterm and final exam grades: ";
double midterm, final;
cin >> midterm >> final;

// ask for the homework grades
cout << "Enter all your homework grades, "
"followed by end-of-file: ";

// the number and the sum of grades read so far
int count = 0;
double sum = 0;

// a variable into which to read
double x;

// invariant: we have read count grades so far, and sum is the sum of the first count grades
while (cin >> x)
{
++count;
sum += x;
}

// special case treatment
double homeworkGrade;
if (count == 0)
homeworkGrade = 0;
else
homeworkGrade = sum/count;

// write a blank line to seperate outputs
cout << endl;

// write the result
streamsize prec = cout.precision();
cout << "Your final grade is " << setprecision(3) <<
0.2 * midterm + 0.4 * final + 0.4 * homeworkGrade
<<setprecision(prec) << endl;
return 0;
}

+

Test

+
1
2
3
4
5
6
Please enter your first name: Brendan
Hello, Brendan!
Please enter you midterm and final exam grades: 95 77
Enter all your homework grades, followed by end-of-file:

Your final grade is 49.8
+

Now, it works better than the original one.

+
+

Reference

Koenig, A. and Moo, B.E., 2000. Accelerated C++.

+

Lippman, S.B., Lajoie, J. and Moo, B.E., 2013. C++ Primer, 5th edt.

+ + +
+ + + + + + + + + + + +
+ + + +
+ + + +
+ +
+
+ + +
+ + + + + + + + + +
+ + + + + + + + + +
+
+ + + + +
+ + +
+ + + + +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/2018/02/06/Accelerated-C-Solution-to-Exercise-Chapter 0/index.html b/06/02/2018/Accelerated-C-Solution-to-Exercise-Chapter 0/index.html similarity index 54% rename from 2018/02/06/Accelerated-C-Solution-to-Exercise-Chapter 0/index.html rename to 06/02/2018/Accelerated-C-Solution-to-Exercise-Chapter 0/index.html index a067d7e9..50fcd9ac 100644 --- a/2018/02/06/Accelerated-C-Solution-to-Exercise-Chapter 0/index.html +++ b/06/02/2018/Accelerated-C-Solution-to-Exercise-Chapter 0/index.html @@ -1,66 +1,147 @@ - + + + + + + + - - + + + - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + - + - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + - + + + + + + + + + + + + - - Accelerated C++ Solutions to Exercises(Chapter 0) | Liam's Blog @@ -70,218 +151,245 @@ - - -
-
+ -
-
- + + + + + +
+
-
+ - -
- - 0% -
- +
+
-
+
- - -
+
-
- +
+ + + + + + + + +
-
- + +
+ + +
-

- Accelerated C++ Solutions to Exercises(Chapter 0) -

+ + + +

Accelerated C++ Solutions to Exercises(Chapter 0)

+
+ @@ -289,6 +397,9 @@

+ + +

All the programs were compiled and excuted within the setting of Eclipse CDT and MinGW64


Exercise 0-0

Compile and run the Hello, world! program

@@ -296,14 +407,12 @@

Hello, world!

Analysis

See C++ - Getting Started.


-

Exercise 0-1

What does the following statement do?

-
1
3 + 4;
+

Exercise 0-1

What does the following statement do?

1
3 + 4;

Solution & Results

The expression statement yields 7 as its results because it contains two int type operands (i.e. 3 and 4), and one operator (i.e. addition). However, it has no side effects on the state of the program and the implementation. Hence, there should be nothing displayed on the console when the program is excuated. As expected, the graph below shows the result along with a warning description statement has no effect ‘3+4’.

Expression Statement: 3+4;

Analysis

See C++ - Getting Started.


-

Exercise 0-2

Write a program that, when run, writes

-
1
This (") is a quote, and this (\) is a backslash.
+

Exercise 0-2

Write a program that, when run, writes

1
This (") is a quote, and this (\) is a backslash.

Solution & Results

It seems that this program is exactly the same as the Hello, world! program in Exercise 0-0 once we replace the string literals. In doing so, however, the compiler reports errors as below graph shows.

Compilation errors

The the occurrence of errors is due to the facts:

@@ -326,7 +435,8 @@

- + + newline \n horizontal tab @@ -346,7 +456,7 @@

backslash \\ question mark -? +\? single quote \‘ @@ -355,10 +465,9 @@

\r formfeed \f - - - + +

One can choose another syntax for using Escape sequence: a backslash followed by by hexadecimal or octal digits while the value represents the numerical value of the character. For example, the linefeed command in C++ can be written in three ways:

@@ -367,7 +476,8 @@

- + + @@ -379,7 +489,8 @@

hexadecimal digits

-
character \n
\x0a
+ +

If a backslash followed by more than three octal digits, the escape sequence is only valid for the first three digits while the rest digits will be read as normal character. For example, ‘\12345’ is equivalent to ‘S’ followed by ‘4’ and ‘5’. But if a backslash followed by the hex digits, the escape sequence uses all the hex digits.


Exercise 0-3

The string literal “\t” represents a tab character; different C++ implementations display tabs in different ways. Experiment with your implementation to learn how it treats tabs.

@@ -404,8 +515,7 @@

Solution & Results

The program is invalid as shown below:

A invalid program

It is clear that curly braces are missing for a complete main function in this program. A correct main function should include a function body which is a block of statements enclosed by a pair of curly braces.

-

Analysis

If one has no any programming knowledge, he probabily can’t figure out the error only from the error reports:

-
1
expected initializer before 'std'
+

Analysis

If one has no any programming knowledge, he probabily can’t figure out the error only from the error reports:

1
expected initializer before 'std'

Why the compile error shows like this? A possible reason is that the compiler treats codes outside the function as declarations of variables. To define a variable, one should firstly specify the type specifier and then declare the name of the variable, and finally add a semicolon. Once the variable is created, it is initialized. Consider that the compiler treats int main() as a declaration of a variable, it will find that the initialization of this variable is failed due to lacking of a semicolon.


Exercise 0-6

Is this a valid program? Why or why not?

@@ -418,8 +528,7 @@

1
2
3
4
5
6
7
8
#include <iostream>
int main()
{
/*This is a comment that extends over several lines
because it uses /* and */ as its starting and ending delimiters */
std::cout << "Does this work?" << std::endl;
return 0;
}

Solution & Results

The program is invalid:

A invalid program

-

As mentioned in C++ - Getting Started, comment pairs cannot nest. The warning also confirms this restriction.

-
1
..\Exercise0_7.cpp:7:25: warning: "/*" within comment [-Wcomment]
+

As mentioned in C++ - Getting Started, comment pairs cannot nest. The warning also confirms this restriction.

1
..\Exercise0_7.cpp:7:25: warning: "/*" within comment [-Wcomment]

To Solve the problem, we can add single line comments before each lines of comments. Below graph gives the correct implementation:

A valid program

Analysis

See C++ - Getting Started.

@@ -451,258 +560,404 @@

References

Koenig, A. and Moo, B.E., 2000. Accelerated C++.

Lippman, S.B., Lajoie, J. and Moo, B.E., 2013. C++ Primer, 5th edt.

+

+ + + + + - -
- -
+

+ + + +
+ + + +
+ +
- + + + + + +
+ -
-
- - - - + + + + + + + + + + + + + - - - + + + + + + + + + + + + + + + + - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/06/03/2018/C-Organizing-programs-and-data/index.html b/06/03/2018/C-Organizing-programs-and-data/index.html new file mode 100644 index 00000000..240934bf --- /dev/null +++ b/06/03/2018/C-Organizing-programs-and-data/index.html @@ -0,0 +1,854 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + C++ - Organizing programs with functions | Liam's Blog + + + + + + + + + + + + + + + + + + +
+
+ + + +
+
+
+
+ + +
+ + + + + + + + +
+ + + +
+ + + + + + + +
+ + + +

C++ - Organizing programs with functions

+ + + +
+ + + + + +
+ + + + + +

Previous chapters mainly covers topics including

+
    +
  1. main function structure
  2. +
  3. statements such as expression statements and flow-of-control statements.
  4. +
  5. built-in types, such as int, float, double, bool and char.
  6. +
  7. standard library IO mechanism.
  8. +
  9. standard library string.
  10. +
  11. standard library vector.
  12. +
+

We have achieved several goals through certain statements and operations on objects of different types. However, the program becomes unmanageable along with increasingly complex functions and growing information. For this reason, this chapter introduces how to organize programs and data.

+

Functions

Basics

writing a function

If we break a program(e.g. A complete program) into pieces, we found that it is in fact constituted by data information, homework grade computation and final grade computation. Both computations can be organized as a function, which is a named block of code. The functions will be called When the computation results are needed. Let’s start with writing a function to compute the final grade, assuming that the homework grade has been computed.

1
2
3
4
5
// compute the final grade of a student
double grade(double midterm, double final, double homework)
{
return 0.2*midterm + 0.4*final + 0.4*homework;
}

+

Basically, it has the same structure as the main function except that we use empty parameter list in previous main function.

+

In general, a function includes four parts:

+
    +
  1. return type. In this case, it has return type of double.
  2. +
  3. function name. In this case, the function is names as grade.
  4. +
  5. parameter list enclosed in parentheses (). In this case, there are three parameters seperated by commas. All three parameters have type of double. They are defined like variables but only be created when the function is called.
  6. +
  7. function body enclosed in curly braces {}. The return statements returns the result to function caller.
  8. +
+

calling a function

When calling the function, the excution of function caller is suspended and execution of the called function begins. We must supply corresponding arguments for the purpose of initializing the parameters. In other words, arguments are the initializers for a function’s parameters. Arguments can be variables or expressions or even values. But they must be provided in the same order as well as the same type as the parameters. If we replace the computation in the original program, it would be like

1
2
cout << "You final grade is " << setprecision(3)
<< grade(midterm, final, sum/count) << setprecision(prec) << endl;

+

The first parameter midterm will be initialized by copying the value of argument midterm into it. So do the other parameters. This is what so called call by value. Essentially, these parameters are created in an area independent from the variables in the calling function though they have the same values. Therefore, if the function manipulate these parameters, it wouldn’t change values of the calling function variables. In addition, the parameters are local to the function and only exist start from calling the function to returning from the function. Therefore, it doesn’t matter that we use same name as the variable in the calling function.

+

Once the execution encounters the return statement in the function body, the execution of the function ends and back to the calling function.

+

Writing a median function

Now we consider writing a median function that computes the median value of the homework grades. Let’s list four parts of a median function:

+
    +
  1. the return type should be double.
  2. +
  3. it is named as median for clarity.
  4. +
  5. what we need for computation is only a vector of double type (assuming that we have read all grades).
  6. +
  7. computing algorithms.
  8. +
+

It’s pretty straightforward

1
2
3
4
double median(vector<double> vec)
{
// algorithms to be written
}

+

The algorithm in the original program is

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
// get the size of the vector
typedef vector<double>::size_type vec_size;
vec_size size = homework.size();

// check special case
if (size == 0)
{
cout << endl << "You must enter your grades. "
"Please try again." << endl;
return 1;
}

// sort the grades
sort(homework.begin(), homework.end());

// compute the median homework grade
vec_size mid = size/2;
double median;
median = size % 2 == 0 ? (homework[mid] + homework[mid - 1])/2 : homework[mid];

+

To write this piece of code into the function, we need to first change the variable name homework to vec as this function suites for more general cases. Nevertheless, you don’t have to do it if you dislike. The second step is to remove the variable median and add return because what this function need to do to return the median value. The last step is to change the code that deals with the case of empty vector.

1
2
if (size == 0)
throw domain_error("median of an empty vector");

+

This is because the original code can not be used here due to it returns another value 1 (unless we change the function structure). In real word programming, throw an exception is a more general way to complain. The usage is explained in next part. Now the function is accomplished as shown below

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
// function to compute the median of a vector<double>
double median(vector<double> vec)
{
// get the size of the vector
typedef vector<double>::size_type vec_size;
vec_size size = vec.size();

// check whether the empty is empty
if (size == 0)
throw domain_error("median of an empty vector");

// sort the grades
sort(vec.begin(), vec.end());

// compute the median homework grade
vec_size mid = size/2;
return size % 2 == 0 ? (vec[mid] + vec[mid - 1])/2 : vec[mid];
}

+

try blocks and Exception handling

“Exceptions are run-time anomalies—such as losing a database connection or encountering unexpected input—that exist outside the normal functioning of a program.” - Lippman etc. 2012

+

throw expressions

throw expressions is used to detect the exceptions, which is followed by an exception onject that describes the problems that it encounters. It stops the execution of the current function and passes an exception object to the caller for handling it.

1
2
3
4
// the detecting part
if (size == 0)
// throw raises exceptions
throw domain_error("median of an empty vector");

+

For example, the exception object domian_error contains the information of that the caller can use to act on the exception. It is a type that the standard library defines in header for use in reporting the logic error: argument is out side the values that the function can accept. What closely follows is a string enclosed by parentheses to describe the problem.

+

the try block

The try block is the handling part uses to deal with an exception. Once the exception is thrown, it catches the exception and handle it according to the type of the exception object. The general syntax is

1
2
3
4
5
6
7
try{
// statements including the detecting part
} catches(exception object1){
// handler-statements
} catches(exception object2){
// handler-statements
} ...

+

The catch clause handles the exception and hence is termed as “exception handler”. If the statements between try and catch don’t throw any exceptions during execution, the program ignores the handler-statements and continue to next part.

+

It is worthing noting that each pair of curly braces forms a name scope. The application of the try block will be finished at the end of this post.

+

Finish the grade function

Now we can embed the median function in the grade function.

1
2
3
4
5
6
7
// function to compute the final grade which is the weighted average grade of medterm exam grade, final exam grade and the median homework grade
double grade(double midterm, double final, const vector<double> &hw)
{
if (hw.size() == 0)
throw domain_error("student has done no homework");
return grade(midterm, final, median(hw));
}

+

reference and call by reference

It has been noted that this grade function differs from the previous one mentioned at the beigining in the part of parameter list. The third parameter here has a compound type with modifier reference. Recalling that to declare a variable needs a type and a name. More generally, a declaration is a base type followed by a list of declarators including a name and an optional type modifier.

1
2
3
4
5
    base type modifier name 
```
A variable declared in above form has a type named **compound type** which is built from the base type. In this case, the third parameter has a type of **vector<double>** with modifier **reference** which indicates that the onject named **hw** refers to its **initializer**. In other words, a reference is a **alias** and **hw** is simply another name for the argument to be passed. In addition, a reference to a reference is in fact that both references refer to the original object. For example
```c++
vector<double> &hw1 = hw; // hw1 is another name for the vector homework

+

In contrast to call by value, this is termed as call by reference. When we operate on a reference, we actually operate on the object that the reference refers. For the purpose of computational effiency, passing argument by reference can avoid copies, particularly for objects of large containers or class types. But, it is not a good habit to modify the value of the object that the reference refers. It’s complete ok in this case as the object is passed by copy in the median function where we will operate on the homework grades. Beyond this, there is a const qualifier before the reference, which restrict the values of the object to be changed when operating on the reference.

+

overloaded function

Recalling the previous grade function

1
2
3
4
5
6
```c++
// compute the final grade of a student
double grade(double midterm, double final, double homework)
{
return 0.2*midterm + 0.4*final + 0.4*homework;
}

+

These two functions have the same name but different parameter list. This is termed as function overloading with either different types or numbers of parameters. If two function only differs in return type, functions can not be overloaded. When calling the overloaded function, the complier determines which function to call according to the supplied arguments and the defined parameters in each funciton.

+

Writing a reading function

Finally, we need to solve the problem that how to read home work grades into a vector. The oringal code is

1
2
3
4
5
6
double x;
vector<double> homework;

// enter homework grades followed by end-of-file
while(cin >> x)
homework.push_back(x);

+

So, what’s this function should return?
Obviously, the purpose is to fill the vector homework and therefore it should return a filled vector. Beyond this, the function is required to return another value to the stream to indicate whether the attempted input was successful.
Intuitively, it works like this:

+

Funtion work flow

+

But it is hard to deal with two returns in one function and alternatively we can define a parameter as a reference type for the purpose of changing the values in homework directly. See the code below

1
2
3
4
5
6
// read the homework grades from an input stream into a vector homework
istream & read_hw(istream &in, vector<double> &hw)
{
// statements to be filled
return in;
}

+

lvalue

We are familar with the second parameter which refers to its initializer to be passed, i.e. the vector homework. Since we intend to modify the passed arguments, the const qualifier has been dropped. There is an important difference between a const reference and a nonconst reference. For a const reference, the arguments to be passed can be any value while a non const reference can only refer to a lvalue object (i.e. a nontemporary object). Any expressions that generate arithmetic values are not lvalue. For example

+
1
2
3
4
int i = 10;
int &j = i; // correct: j is bound to i
int &m = 10; // error: initializer must be an nontemporary object
const int &n = 10; // correct: a const reference
+

member function clear

The first parameter is also a type of non-const reference which refers to the object cin. This is because we hope to change its internal state. As a result, the return type is also a reference as in is a reference. Another reason is that there is no copy or assign for IO objects.

+

Now we consider read entered grades into homework. Remember that We propose to write a program that can deal with multiple students’ records. One problem is that the vector might contain the grades of the last student. To keep the vector empty, we use hw.clear() to discard any contents the vector might have had.

+

Similarly, we also need to keep the cin be valid for each student. In previous chapter, We have explained that once we finishes typing in the homework grades a signal end-of-file needs to be sent for terminating the loop. The signal will change the internal state of the cin to be false. In addition, end-of-file is not the only input that can stop the loop. If we enter values of an improper type, the library would mark the input stream as being in failure state as well. For this reason, we use in.clear() to clear the error state of cin after finishing the input for one student. Note that both the vector and the object of istream have member function clear but the effects are completely different. The function is shown below

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
// read homework grades from an input stream inti a vector
istream & read_hw(istream &in, vector<double> &hw)
{
if (in){
//get rid of previous contents
hw.clear();

// read homework grades
double x;
while(in >> x)
hw.push_back(x);

// clear the stream so that input will work for the next student
in.clear();
}
return in;
}

+

A complete program

Up to now, we have changed the computations in the original program to functions including a function to homework grades, a function to calculate the median of homework grades and a function to calculate the final grade. A complete program is presented below

+
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
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
// include directives
#include<iostream> // to get declaration of cin, cout, endl,
#include<istream> // to get declaration of istream
#include<string> // to get declaration of string
#include<vector> // to get declaration of vector
#include<algorithm>// to get declaration of sort
#include<ios> // to get declaration of streamsize
#include<stdexcept>// to get declaration of domain_error
#include<iomanip> // to get declaration of setprecision

// add using declarations
using std::cin; using std::setprecision;
using std::cout; using std::streamsize;
using std::endl; using std::domain_error;
using std::string; using std::istream;
using std::vector; using std::sort;

// declare functions
istream & read_hw(istream &in, vector<double> &hw);
double median(vector<double> vec);
double grade(double midterm, double final, double homework);
double grade(double midterm, double grade, const vector<double> &hw);

// main function
int main()
{
// ask for and read the student's name
cout << "Please enter your first name: ";
string name;
cin >> name;
cout << "Hello, " << name << "!" << endl;

// ask for and read the midterm and final grades
cout << "Please enter your midterm and final exam grades: ";
double midterm, final;
cin >> midterm >> final;

// ask for the homework grades
cout << "Enter all your homework grades, followed by end-of-file: ";

// read the homework grades
vector<double> homework;
read_hw(cin, homework);

// compute and generate the final grade, if possible
try {
double final_grade = grade(midterm, final, homework);
streamsize prec = cout.precision();
cout << "Your final grade is " << setprecision(3)
<< final_grade << setprecision(prec) <<endl;
} catch(domain_error) {
cout << endl << "You must enter your grade. Please trt again." << endl;
return 1;
}
return 0;
}

// define function to read homework grade
istream & read_hw(istream &in, vector<double> &hw)
{
if (in){
//get rid of previous contents
hw.clear();

// read homework grades
double x;
while(in >> x)
hw.push_back(x);

// clear the stream so that input will work for the next student
in.clear();
}
return in;
}

// define function to calculate median value
double median(vector<double> vec)
{
// get the size of the vector
typedef vector<double>::size_type vec_size;
vec_size size = vec.size();

// check whether the empty is empty
if (size == 0)
throw domain_error("median of an empty vector");

// sort the grades
sort(vec.begin(), vec.end());

// compute the median homework grade
vec_size mid = size/2;
return size % 2 == 0 ? (vec[mid] + vec[mid - 1])/2 : vec[mid];
}

// define a function to calculate final grade
double grade(double midterm, double final, const vector<double> &hw)
{
if (hw.size() == 0)
throw domain_error("student has done no homework");
return grade(midterm, final, median(hw));
}

//define a function to calculate final grade (function overloading)
double grade(double midterm, double final, double homework)
{
return 0.2*midterm + 0.4*final + 0.4*homework;
}
+

See from above program, why the statements inside the try block are not organized as such form

1
2
3
4
5
6
7
try {
streamsize_prec = cout.precision();
cout << "Your final grade is " << setprecision(3)
<< grade(midterm, final, homework)
<< setprecision(prec) <<endl;
} catch...
...

+

In doing so, we probably can’t control the outputs as the grade function may be called after or before the string literals depending on the implementation. Also, if any exception is thrown, the precision may not be reset back to the original value as expected.

+
+

To be continued.

+ + +
+ + + + + + + + + + + +
+ + + +
+ + + +
+ +
+
+ + +
+ + + + + + + + + +
+ + + + + + + + + +
+
+ + + + +
+ + +
+ + + + +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/06/04/2018/C-Defining-new-types/index.html b/06/04/2018/C-Defining-new-types/index.html new file mode 100644 index 00000000..0fa6ee6a --- /dev/null +++ b/06/04/2018/C-Defining-new-types/index.html @@ -0,0 +1,868 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + C++ - Defining new types | Liam's Blog + + + + + + + + + + + + + + + + + + +
+
+ + + +
+
+
+
+ + +
+ + + + + + + + +
+ + + +
+ + + + + + + +
+ + + +

C++ - Defining new types

+ + + +
+ + + + + +
+ + + + + +

Rewrite Student_info in class type

In chapter 4, we learned how to organize data with a single data structure:

1
2
3
4
5
struct Student_info{
std::string name;
double midterm, final;
std::vector<double> homework;
};

+

The type Stuent_info holds information including name, midterm, final exam grades and a bunch of homework grades for a student. It help us to easily access the information of one student. Then, we wrote several functions to compute final grades based on the objects of such type. However, our program that uses such data type has several limitations

+
    +
  1. users of our program have to follow some conventions. If we want to use a newly created object of Student_info, we need to ensure that we have read data into it. If one want to check whether the object contains information, he can only look at the actual data members in it. This requires users knowing well about the iternal structure of such type that the object belongs to. In other words, if I am a user but not a programmer, I need to learn each details about how to implement such type in a program.

    +
  2. +
  3. there is no data protection mechanism. Users might want to keep students’ information unchanged once read in. However, we don’t provide such mechanism in the original program.

    +
  4. +
  5. There is no universal interface for users. Function such as read** is closely connected with a Student_info object as they can change the state of an object. We might like to put them into a single header file for providing convenience for other users. However, we don’t have such structure that provids a universal interface for users.

    +
  6. +
+

Now we learn how to deal with these problems with a new data type-class. class type is a mechanism that combines related data values into a data structure. It is an abstract data type that similar to Student_info. But it also provides an interface that allows us to operate an object, e.g. an Student_info, while hiding all deatils of the object.

+

For example, we are familar with vector which is a class that provides a set of operations, such as push_back, erase for users. But we don’t know how exactly these functions are implemented.

+

Member function

As analysed above, we may don’t need some details of the students’ information as well as how read function deal with these information. What users need to know is how to use the relevant functions. Therefore, we need to design such interface.

1
2
3
4
5
6
7
8
9
struct Student_info
{
std::string name;
double midterm, final;
std::vector<double> homework;

std::istream & read(std::istream &);
double grade() const;
};

+

Let’s say we have such an Student_info object named record. It has four data members name, midterm, final and homework. In addition, there are two member functions named read and grade which let us to read a record from an input stream and calculate the final grade for the object. Now, though we didn’t define any such structure before, we can presume how to use it. By the analogy of other class such as vector, I can store information for one student by calling its member function read:

1
record.read(cin);

+

Again, we can calculate the final grade for record by calling member function grade():

1
record.grade();

+

Member functions can be defined inside or outside the class definition. Member functions defined inside are implicitly inline to avoid function call overhead. Apparently, member functions above are declared only and defined outside of the class. Let’s look at the read function:

1
2
3
4
5
6
istream & Student_info::read(istream &in)
{
in >> name >> midterm >> final;
read_hw(in, homework);
return in;
}

+

Comparing with the original version:

+
    +
  1. the name of the function is Student_info::read because it is declared inside of and as a member of Student_info.
  2. +
  3. we don’t need to pass the Student_info object as an argument to the function as the function is a member of Student_info.
  4. +
  5. the member function can access data members directly using name, midterm etc. instead of record.name, record.midterm etc..
  6. +
+

Now let’s look at the grade member:

1
2
3
4
double Student_info::grade() const
{
return ::grade(midterm, final, homework);
}

+

It is similar to the read member in terms of putting :: in front of the name and accessing data members without any qualification. But there are two differences:

+

First, putting :: in front of grade when call it means that the grade function is a version that is not a member of class Student_info. Second, we put a qualifier const after the parameter list. What’s that mean? Recalling the original version

1
double grade(const Student_info &) {...}

+

We pass Student_info object by reference to const to avoid changing the argument to pass. In the new version, we don’t need to pass the Student_info* object any more as the function itself is a member function. Therefore, we add the const after the parameter list for the same purpose, that is, to avoid changing the state of data members of the Student_info object. Noting that, the class object may not be created with const but is referenced to const in a function. Then, the function treat the object as if it were const**.

+

Protection

Though we provide interface for our class, users still can access data members directly and might meddle the implementation unintentionally. Therefore it is probably sensible to restrict users’ rights such that they can only access data members through member functions. The idea behind this process (i.e. data hiding) is called encapsulation. C++ supports encapsulation with two access specifiers(aka. protection lables):

+
    +
  1. public specifier. Members defined after a keyword public are accessible to all parts of the program. This is typically used to specify members that define the interface to the class.
  2. +
  3. private specifier. Members defined after a keyword private are accessible to the member functions of the class only. This is typically used to specify members that involves the implementation.
  4. +
+

Therefore, we can change our class as follows:

1
2
3
4
5
6
7
8
9
10
11
12
13
class Student_info 
{
public:
// interface goes here
double grade() const;
std::istream & read(std::istream &);

private:
// implementation goes here
std::string name;
double midterm, final;
std::vector<double> homework;
};

+

As explained above, We have specified member functions as public and data memmers as private. It has been noted that we use keyword class instead struct to define the class type. Both two keywords are ok for defining a class type. The only difference is the default access level. A class may define members before its first protection lable. if we use class, those members are default private while if we use struct, those members are default public. Accordingly, the class defined above is equivalent to

1
2
3
4
5
6
7
8
9
10
class Student_info
{
std::string name;
double midterm, final;
std::vector<double> homework;

public:
double grade() const;
std::istream & read(std::istream &);
};

+

In general, if we don’t intend to restrict data access, we use struct. But if we intend to have private members, we use class.

+

Accessor functions

Now we have successfully hidden data memebers but the problem next is that We fail to access name as well. We can set member name to public, however, what we really need is only the read access rather than write access. Alternatively, we can write another member function that returns the name but is not allowed to rewrite the name object.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
class Student_info 
{
public:
// interface goes here
double grade() const;
std::istream & read(std::istream &);
std::string name() const { return n; }

private:
// implementation goes here
std::string n;
double midterm, final;
std::vector<double> homework;
};

+

As mentioned above, member functions defined inside are implicitly inline to avoid function call overhead. In this class, we define member name inside the class to imply compiler that this member function should be expanded inline at anywhere it is called.

+

Functions such as name are often called accessor functions. It seems that such function breaks the encapsulation that we were trying to achieve. Therefore, such function is privided only when accessors are part of the abtract interface of the class. In this case, the abtraction is that of a student and a corresponding final grade. Obviously, name is part of the abstract interface.

+

Accordingly, the compare function changes to:

1
2
3
4
bool compare(const Student_info & x, Student_info & y)
{
return x.name() < y.name();
}

+

Testing for empty

There is another problem we may concern when using such class object. For example, if we call the member grade without calling read first, we would get an exception due to homework is empty. A traditional solution is to catch the exception and let users know what leads to the exception. But again, this may require users knowing about the iternal structure of the class object. Alternatively, we can provide a public member function named valid:

1
2
3
4
5
6
class Student_info()
{
public:
bool valid() const { return !homework.empty(); }
// as before
}

+

This member function tells the state of the object: if valid function returns true, it indicates that the object contains valid data, i.e. at least one homework grade; if valid returns false, it indicates the object is invalid for computing the final grades as there is no any homework grade. Users can check the state of the class object before the grade function call, thereby avoiding a potential exception.

+

Constructors

It is known that when we create an object, it should be default initialized or assigned with an appropriate value. For example, when we define a string without an initializer, we get an empty string. So, what happens when we create a class object?

+

The class type supports defining how to initialize an class onject through constructors which are special member functions. There is no way to explicitly call constructors. Instead creating an class object automatically calls an appropriate constructor as a side effect.

+

If we do not define any constructors, the compiler will synthesize one for us. The synthesized constructor initializes the data member to a value depending on how the object is created. Specifically, if a class object is created as a local variable, then the data members will be default initialized. If a class object is used to initialize a container element, either as a side effect of adding new element to a map, or as the elements of container defined to have a given size, then the member will be value-initialized determined by a class type itself.

+

The rules below summarize how a class type initializes its data members:

+
    +
  1. if an object is of a class type that defines one or more constructors, then the appropriate constructor determines how data memebers will be initialized.

    +
  2. +
  3. if an object is of built-in type, then value-initializing it sets it to zero, and default-initializing it gives it an undefined value.

    +
  4. +
  5. In the case that an object is of a class type that doesn’t define any constructor, value- or default-initializing the object value, or default-initializes each of its data members. This process will be recursive if any data member is of a class type with its own constructor.

    +
  6. +
+

In our example, the Student_info class type is the case 3. If we define a local variable that is of such class type, n and homework are default-initialized and concequently yields an empty string and vector respectively. However, default-initializing midterm and final leads to undefined values. To ensure all data members have sensible values at all times, we should define constructors for our class type. Let’s look at two constructors:

1
2
3
4
5
6
7
class Student_info 
{
public:
Student_info (); // construct an empty Student_info object
Student_info (std::istream); // construct one by reading from input stream
// as before
}

+

We add two public members functions that are both named Student_info , that is, the name of the class type itself, and both have no return type. These two features distinguish constructors from other member functions. It can also be observed that constructors has two different versions, the first constructor of which takes no argument while the second takes input stream object as argument. Corresponding, we can write our code like

1
2
Student_info s;          // an empty Student_info
Student_info s2(cin); // initialize s2 by reading from cin

+

The default constructor

The first constructor

1
Student_info s;

+

is known as default constructor which takes no arguments and ensures that all data members are normally initialized through:

+
1
Student_info::Student_info(): midterm(0), final(0) {}
+

This definition uses new syntax: the contents between : and { are a sequence of initializers which initializes each given data member with the value that appears inside of the corresponding parenthese. This default constructor doesn’t explictly initialize n and homework members as they are initialized implicitly. In specific, n is initialized by the string default constructor and homework is initialized by the vector default constructor.

+

When we create a new class object, the implementation proceeds following steps:

+
    +
  1. allocates memory to hold the object
  2. +
  3. initializes the object as the constructor defines
  4. +
  5. executes the constructor body
  6. +
+

The implementation initializes every data member of every object even if some members are not explicitly initialized with the constructor initializer list.

+

Constructors with arguments

1
Student_info::Student_info(istream &is) { read(is); }
+

There is no initializer provided for each data member and hence n and homework will be initialized by the default constructors for string and vector, respectively. But midterm and fianl are undefined if the object is default-initialized otherwise are value-initialized to 0. Nevertheless, the function body gives new values to these data members by calling read function.

+

Class-based grading program

Now we have successfully defined a class-based Student_info type. Apparently, using the class is different from using the original structure. Therefore, the last step is to rewrite the main function and organsize files.

+

mainfunction.cpp

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
#include <algorithm>		// to get the declaration of max, sort
#include <iomanip> // to get the declaration of setprecision
#include <iostream> // to get the declaration of streamsize
#include <stdexcept> // to get the declatation of domain_error
#include <string> // to get the declaration of string
#include <vector> // to get the declaration of vector
#include "Student_info.h" // to get the declaration of Student_info
#include "grade.h" // to get the declatation of grading functions

using std::cin; using std::setprecision;
using std::cout; using std::sort;
using std::endl; using std::streamsize;
using std::domain_error; using std::string;
using std::max; using std::vector;

int main()
{
vector<Student_info> students;
Student_info record;
string::size_type maxlen = 0;

// read and store all the records, and find the length of the longest name
while(record.read(cin))
{
maxlen = max(maxlen, record.name().size());
students.push_back(record);
}

// alphabetize the records
sort(students.begin(), students.end(), compare);

// write each line of outpurs
for (vector<Student_info>::const_iterator it = students.begin();
it != students.end(); ++it)
{
// write the name, blanks
cout << (*it).name() << string(maxlen + 1 - (*it).name().size(), ' ');

// compute and write the final grade
try{
double final_grade = (*it).grade();
streamsize prec = cout.precision();
cout << setprecision(3) << final_grade << setprecision(prec);
} catch(domain_error e){
cout << e.what();
}
cout << endl;
}
return 0;
}

+

Student_info.h

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
#ifndef GUARD_STUDENT_INFO
#define GUARD_STUDENT_INFO

#include <string>
#include <iostream>
#include <vector>

class Student_info
{
public:
Student_info (); // default constructor
Student_info (std::istream &); // constructor with argument
std::string name() const { return n; } // inline member function return name
bool valid() const { return !homework.empty(); } // inline member function check state
std::istream & read(std::istream &); // member function read in data
double grade() const; // member function calculate final grade

private:
std::string n;
double midterm, final;
std::vector<double> homework;
};

std::istream & read_hw(std::istream &, std::vector<double> &); // nonmember function read data into a vector
bool compare(const Student_info &, const Student_info &); // nonmember function compare two string

#endif

+

Student_info.cpp

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
#include "Student_info.h"
#include "grade.h"

using std::vector; using std::istream;

// construct an empty Student_info object
Student_info::Student_info (): midterm(0), final(0) { }

// construct one by reading from input stream
Student_info::Student_info (std::istream & in) { read(in); }

// member function read data from input stream
std::istream & Student_info::read(std::istream &in)
{
// reads and store the student's name, midterm and final exam grades
in >> n >> midterm >> final;

// reads and store all homework grades
read_hw(in, homework);
return in;
}

// member function grade
double Student_info::grade() const
{
return ::grade(midterm, final, homework);
}

// nonmember function compare
bool compare(const Student_info &x, const Student_info &y)
{
return x.name() < y.name();
}

// nonmember function read_hw
istream & read_hw(istream &in, vector<double> &hw)
{
if (in){
//get rid of previous contents
hw.clear();

// read homework grades
double x;
while(in >> x)
hw.push_back(x);

// clear the stream so that input will work for the next student
in.clear();
}
return in;
}

+

grade.h

1
2
3
4
5
6
7
8
9
#ifndef GUARD_GRADE_H
#define GUARD_GRADE_H

#include<vector>

double grade(double, double, const std::vector<double> &);
double grade(double, double, double);
double median(std::vector<double>);
#endif

+

grade.cpp

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 <algorithm>
#include <stdexcept>
#include <vector>
#include "grade.h"

using std::domain_error; using std::istream;
using std::vector; using std::sort;

// grade function 1
double grade(double midterm, double final, const vector<double> &hw)
{
if (hw.empty())
throw domain_error("student has done no homework");
return grade(midterm, final, median(hw));
}

// grade function 2
double grade(double midterm, double final, double homework)
{
return 0.2*midterm + 0.4*final + 0.4*homework;
}

// compute the median of vector<double>
double median(vector<double> vec)
{
// check whether the vec is empty
if (vec.begin() == vec.end())
throw domain_error("median of an empty vector");

// sort the grades
sort(vec.begin(), vec.end());

// compute the median homework grade
vector<double>::difference_type size = vec.end() - vec.begin();
vector<double>::const_iterator mid = vec.begin() + size/2;
return size % 2 == 0 ? (*mid + *(mid - 1))/2 : *mid;
}

+

Test

1
2
3
4
5
6
7
8
9
10
11
12
13
Inputs

Robin 90 87 79 88 81 73 45
Brendan 70 69 88 100 91 75 66
Arsenii 99 87 89 88 74 90 70
Liam 83 66 100 76 87 91 78

Outputs

Arsenii 89.8
Brendan 76.8
Liam 77.8
Robin 84.4
+

The program works as same as the original program and delivers same results.

+ + +
+ + + + + + + + + + + +
+ + + +
+ + + +
+ +
+
+ + +
+ + + + + + + + + +
+ + + + + + + + + +
+
+ + + + +
+ + +
+ + + + +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/06/05/2018/Accelerated-C-Solutions-to-Exercises-Chapter-11/index.html b/06/05/2018/Accelerated-C-Solutions-to-Exercises-Chapter-11/index.html new file mode 100644 index 00000000..1badd3af --- /dev/null +++ b/06/05/2018/Accelerated-C-Solutions-to-Exercises-Chapter-11/index.html @@ -0,0 +1,816 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Accelerated C++ Solutions to Exercises (Chapter 11) | Liam's Blog + + + + + + + + + + + + + + + + + + +
+
+ + + +
+
+
+
+ + +
+ + + + + + + + +
+ + + +
+ + + + + + + +
+ + + +

Accelerated C++ Solutions to Exercises (Chapter 11)

+ + + +
+ + + + + +
+ + + + + +

Exercise 11-0

Compile, execute, and test the programs in this chapter.

+

Solution & Results

Please find the programs and analysis in Defining abstract data types(Part 2).

+

Exercise 11-1, 11-2, 11-3, 11-4

11-1: The Student_info structure that we defined in Chapter 9 did not define a copy constructor, assignment operator, or destructor. Why not?

+

11-2: That structure did define a default constructor. Why?

+

11-3: What does the synthesized assignment operator for Student_info objects do?

+

11-4: How many members does the synthesized Student_info destructor destroy?

+

Solution & Results

Recalling the Student_info class:

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
#ifndef GUARD_STUDENT_INFO
#define GUARD_STUDENT_INFO

#include <string>
#include <iostream>
#include <vector>

class Student_info
{
public:
Student_info (); // default constructor
Student_info (std::istream &); // constructor with argument
std::string name() const { return n; } // inline member function return name
bool valid() const { return !homework.empty(); } // inline member function check state
std::istream & read(std::istream &); // member function read in data
double grade() const; // member function calculate final grade

private:
std::string n;
double midterm, final;
std::vector<double> homework;
};

std::istream & read_hw(std::istream &, std::vector<double> &); // nonmember function read data into a vector
bool compare(const Student_info &, const Student_info &); // nonmember function compare two string

#endif

+

If we don’t explicitly define a copy constructor, assignment operator and destructor, the compiler will synthesizes default versions of the unspecified operation. In this case, members midterm, final are built-in type variables and hence are copied and assigned by copying or assigning their value. But the destructors for built-in types do nothing. Members string and vector are class type variables and hence are copied, assigned, or destoryed by calling the constructor, assignment operator, and destructor for the data element. It is known that both these two standard classes define the corresponding behaviours in their headers. Therefore, it is unnecessary to define these operations in our class again. When the computer evaluates an assignment, for example

1
2
3
4
5
6
7
8
9
10
Student_info record(cin);   // construct from input stream
Student_info record_copy; // construct an empty object
record_copy = record; // assignment
```
it calls the default assignment operators for each data member as if:
```c++
n = record.n; // call assignment operator defined in the string class
midterm = record.m; // assign values
final = record.final; // assign values
homework = record.homework; // call assignment operator defined in the vector class

+

These operations typically involves obliterating the values of the left-hand side operand and then copying values from right-hand side operand into the left-hand side operand. By analogy, we know how the synthesized copy constructor work. When a Student_info class object is destructed, the synthesized destructor detroyes its data members by calling their destructors respectively. For midterm and final, their destructors have no work to do. Therefore, the synthesized Student_info destructor destroyes two data members.

+

The compiler will synthesize a default constructor for us if and only if we don’t explicitly define any constructors, even a copy constructor. In this case, we explicitly define a contructor with argument and hence no synthesized version for us. In addition, we do need a user-defined default constructor as the built-in types in local scope are undefined following the synthesized operation.

+

Exercise 11-5

Instrument the Student_info class to count how often objects are created, copied,assigned, and destroyed. Use this instrumented class to execute the student record programs from Chapter 6. Using the instrumented Student_info class will let you see how many copies the library algorithms are doing. Comparing the number of copies will let you estimate what proportion of the cost differences we saw are accounted for by the use of each library class. Do this instrumentation and analysis.

+

Solution & Results

To be filled.

+
+

Exercise 11-6, 11-7

Add an operation to remove an element from a Vec and another to empty the entire Vec. These should behave analogously to the erase and clear operations on vectors.

+

Once you’ve added erase and clear to Vec, you can use that class instead of vector in most of the earlier programs in this book. Rewrite the Student_info programs from Chapter 9 and the programs that work with character pictures from Chapter 5 to use Vecs instead of vectors.

+

Solution & Results

The original version can be found in C++ - Defining abstract data types(Part 2). The program below only shows the new contents including the erase functions and the clear function.

+

Vec.h

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
#ifndef GUARD_VEC_H
#define GUARD_VEC_H

#include <cstddef>
#include <algorithm>
#include <memory>

template <class T> class Vec{
public:
// as before

// erase function
iterator erase(iterator iter);

// overloaded erase function
iterator erase(iterator beg, iterator end);

// clear function
void clear() { erase(begin(), end()); }

private:
// as before
};

template <class T>
typename Vec<T>::iterator Vec<T>::erase(iterator iter){
if (iter + 1 != avail)
std::uninitialized_copy(iter + 1, avail, iter);
--avail;
alloc.destroy(avail);
return iter;
}

template <class T>
typename Vec<T>::iterator Vec<T>::erase(iterator first, iterator last){
if(last != avail)
std::uninitialized_copy(last, avail, first);
iterator new_avail = avail - (last - first);
iterator it = new_avail;
while (it != avail)
alloc.destroy(it++);
avail = new_avail;
return first;
}

+

The first erase function takes one parameter, an iterator, and removes the element pointed by the iterator. The second erase function takes two iterators, denoting a range [first, last), and removes all elements in this range. Both erase functions return an iterator pointing to the new location of the element that followed the last element erased by the function call. Noting that the position of limit keeps unchanged and hence the capacity of this vector remains the same. I only destroy these elements but do not free the space because the destructor will free the space occupied by the range [data, limit). The clear function calls the erase function and erase all elements in the range [first(), end()). The test program below shows that all three members work as expected.

+

mainfunction.cpp

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 "Vec.h"

using std::cout; using std::cin;
using std::endl;

int main()
{
Vec<double> v;

// stores 0-9 into the Vec
for(int i = 0; i != 10; ++i)
v.push_back(i);

// traverse
cout << "The original list is: ";
for(auto i: v)
cout << i << " ";
cout <<"\n";

// erase one by one starting from begin()
Vec<double>::iterator i = v.begin();
while(i != v.end())
{
v.erase(i);
for(auto i: v)
cout << i << " ";
cout << "\n";
}

Vec<double> v1(10, 10);
cout << "The size of v1 is: " << v1.size() << "\n";

// erase first 5 elements
v1.erase(v1.begin(), v1.begin() + 5);
cout << "The size of v1 is: " << v1.size() << "\n";
cout << "The rest elements are: ";
for(auto i: v1)
cout << i << " ";
cout << "\n";

// clear the Vec
v1.clear();
cout << "The size of v1 is: " << v1.size() << "\n";

return 0;
}

+

Outputs

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
The original list is: 0 1 2 3 4 5 6 7 8 9 
1 2 3 4 5 6 7 8 9
2 3 4 5 6 7 8 9
3 4 5 6 7 8 9
4 5 6 7 8 9
5 6 7 8 9
6 7 8 9
7 8 9
8 9
9

The size of v1 is: 10
The size of v1 is: 5
The rest elements are: 10 10 10 10 10
The size of v1 is: 0

+

It is easy to rewrite the Student_info programs from Chapter 9 and the programs that work with character pictures from Chapter 5. No more discussion here.

+
+

Exercise 11-8

Write a simplified version of the standard list class and its associated iterator.

+

Solution & Results

To be filled.

Exercise 11-9

The grow function in §11.5.1/208 doubles the amount of memory each time it needsmore. Estimate the efficiency gains of this strategy. Once you’ve predicted how much of a difference it makes, change the grow function appropriately and measure the difference.

+

Solution & Results

There is an article written by the authors Andrew Koenig and Barbara E. Mooon about this topic C++ Made Easier: How Vectors Grow.

+ + +
+ + + + + + + + + + + +
+ + + +
+ + + +
+ +
+
+ + +
+ + + + + + + + + +
+ + + + + + + + + +
+
+ + + + +
+ + +
+ + + + +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/06/05/2018/Defining-abstract-data-types-Part-2/index.html b/06/05/2018/Defining-abstract-data-types-Part-2/index.html new file mode 100644 index 00000000..96768d02 --- /dev/null +++ b/06/05/2018/Defining-abstract-data-types-Part-2/index.html @@ -0,0 +1,777 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + C++ - Defining abstract data types(Part 2) | Liam's Blog + + + + + + + + + + + + + + + + + + +
+
+ + + +
+
+
+
+ + +
+ + + + + + + + +
+ + + +
+ + + + + + + +
+ + + +

C++ - Defining abstract data types(Part 2)

+ + + +
+ + + + + +
+ + + + + +

The full version of Vec class template described in C++ - Defining abstract data types is presented below.

+

Vec.h

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
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
#ifndef GUARD_VEC_H
#define GUARD_VEC_H

#include <iostream> // std::cout, std::endl
#include <cstddef> // std::size_t
#include <algorithm> // std::max
#include <memory> // std::allocator, std::uninitialized_fill, std::uninitialized_copy

template <class T> class Vec{
public:
// member types
typedef T* iterator;
typedef const T* const_iterator;
typedef std::size_t size_type;
typedef T value_type;

// constructors
Vec() {
std::cout << "calling default constructor" << std::endl;
create();
}

explicit Vec(size_type n, const T& t = T()) {
std::cout << "calling the explicit constructor" << std::endl;
create(n, t);
}

// copy constructor,
Vec(const Vec& v) {
std::cout << "calling copy constructor" << std::endl;
create(v.begin(), v.end());
}

//assignment operator
Vec& operator=(const Vec&);

// destructor
~Vec() {
std::cout << "calling destructor" << std::endl;
uncreate();
}

// indexing operator
const T& operator[](size_type i) const {
std::cout << "calling operation[]" << endl;
return data[i];
}

// push_back function
void push_back(const T& t){
if(avail == limit)
grow();
unchecked_append(t);
}

// size function
size_type size() const { return avail - data; }

// begin(), end() function
iterator begin() { return data; }
const_iterator begin() const { return data; }
iterator end() { return avail; }
const_iterator end() const { return avail; }

private:
iterator data; // first element in the Vec
iterator avail; // (one past) the last element in the Vec
iterator limit; // (one past) the allocated memory

// facilities for memory allocation
std::allocator<T> alloc; // object to handle memory allocation

// allocate and initialize the underlying array
void create();
void create(size_type, const T&);
void create(const_iterator, const_iterator);

// destroy the elements in the array and free the memory
void uncreate();

// support functions for push_back
void grow();
void unchecked_append(const T&);
};

// initialize data members to nullptr
template <class T> void Vec<T>::create()
{
data = avail = limit = nullptr;
}

// create and initialize data members with a size and a value
template <class T> void Vec<T>::create(size_type n, const T& val)
{
data = alloc.allocate(n);
limit = avail = data + n;
std::uninitialized_fill(data, limit, val);
}

// create and initialize data members by copying values from an input sequence
template <class T> void Vec<T>::create(const_iterator i, const_iterator j)
{
data = alloc.allocate(j - i);
limit = avail = std::uninitialized_copy(i, j, data);
}

// destruct the class object using destroy and deallocate functions
template <class T> void Vec<T>::uncreate()
{
if(!data){
// destroy the elements in reverse order
iterator it = avail;
while(it != data)
alloc.destroy(--it);
alloc.deallocate(data, limit - data);
}
// reset pointers to indicate that Vec is empty again
data = limit = avail = 0;
}

// assign values from right-hand operand to the left-hand operand
template <class T>
Vec<T>& Vec<T>::operator= (const Vec& rhs)
{
std::cout << "calling operator= function" << std::endl;

// check for self-assignment
if(&rhs != this)
{
// free the array in the left-hand side
uncreate();

// copy elements from the right-hand to the left-hand side
create(rhs.begin(), rhs.end());
}
return *this;
}

// reallocate storage to hold more elements
template <class T> void Vec<T>::grow()
{
// when growing, allocate twice as much as space as currently in use
size_type new_size = std::max(2*(limit-data), ptrdiff_t(1));

// allocate new space and copy existing elements to the new space
iterator new_data = alloc.allocate(new_size);
iterator new_avail = std::uninitialized_copy(data, avail, new_data);

// return the old space
uncreate();

// reset pointers to point to the newly allocated space
data = new_data;
avail = new_avail;
limit = data + new_size;
}

// add new element at the end of the vector
template <class T> void Vec<T>::unchecked_append(const T& val)
{
alloc.construct(avail++, val);
}
#endif /* GUARD_VEC_H */

+

Test

+

Now, let’s test our Vec class to see how does it work.

+

main.cpp

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
#include <iostream>
#include "Vec.h"

using std::cout; using std::cin;
using std::endl;

int main()
{
{
Vec<int> v; // call default constructor
Vec<int> v1(10, 100); // call explicit constructor
Vec<int> v2(v1); // call copy constructor
v = v1; // call assignment operator

// the destructor is expected to be called three times
}

cout << endl;
{
Vec<int> v; // call default constructor
if(v.size() == 0){ // test size
v.push_back(10); // test push_back function
}

// test indexing operator
cout << "The first element is: " << v[0] << "\n";

// call the destructor
}

cout << endl;
{
Vec<int> v(5, 100); // call explicit constructor

// test iterator
for(Vec<int>::iterator it = v.begin(); it != v.end(); ++it)
cout << *(it) << " ";

cout << "\n";
// call the destructor
}
return 0;
}

+

Outputs

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
calling default constructor
calling the explicit constructor
calling copy constructor
calling operator= function
calling destructor
calling destructor
calling destructor

calling default constructor
calling operation[]
The first element is: 10
calling destructor

calling the explicit constructor
100 100 100 100 100
calling destructor

+

The test program generates outputs as expected. As mentioned in last post, the operator= function returns a reference to the new constructed class object can be more efficient than returning the value directly. The reason behind this is that returning a value unnecessarily calls the copy constructor and destructor. Let’s verify this by evaluating following statements within the setting of returning by value:

1
2
3
Vec<int> v;			// call default constructor
Vec<int> v1(10, 100); // call explicit constructor
v = v1; // call assignment operator

+

Outputs

1
2
3
4
5
6
7
calling default constructor
calling the explicit constructor
calling operator= function
calling copy constructor
calling destructor
calling destructor
calling destructor

+

Comparing with the original version, the outputs are the same. However, the previous program evalutes four statements including a copy construction as well. This program only creates two objects: v and v1, but additionally calls the copy constructor and destructor once for each after calling the assignment operator. The results confirms our expectation.

+ + +
+ + + + + + + + + + + +
+ + + +
+ + + +
+ +
+
+ + +
+ + + + + + + + + +
+ + + + + + + + + +
+
+ + + + +
+ + +
+ + + + +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/08/03/2018/Organizing-programs-and-data-Part-2/index.html b/08/03/2018/Organizing-programs-and-data-Part-2/index.html new file mode 100644 index 00000000..bc405d3b --- /dev/null +++ b/08/03/2018/Organizing-programs-and-data-Part-2/index.html @@ -0,0 +1,855 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + C++ - Organizing programs with data structures | Liam's Blog + + + + + + + + + + + + + + + + + + +
+
+ + + +
+
+
+
+ + +
+ + + + + + + + +
+ + + +
+ + + + + + + +
+ + + +

C++ - Organizing programs with data structures

+ + + +
+ + + + + +
+ + + + + +

The program we have accomplished in last chapter is good enough for computing one students’ final grade, however, is unpractical in reality when it comes to generating a final grade report for a class. Assuming that we have a file that records all students’ information including their names, midterm and final exam grades, and homework grades. For example

1
2
3
Robin 90 87 79 88 81 73 45
Brendan 70 69 88 100 91 75 66
...

+

The program is required to compute the final grade for each student and generate a report like

1
2
Bredan 76.8
Robin 84.4

+

In specific, there are three requirements

+
    +
  1. in the final grade, the mediter exam grade counts for 20%, the final exam grade counts for 40%, and the median homework grade counts for 40%.
  2. +
  3. the output follows an alphabetical order according to the names.
  4. +
  5. the final grades are vertically aligned.
  6. +
+

A similar program has been done in Exercise 3-5, which can keep track of grades for several students at once though it uses the average homework grade rather than median value. The whole structure is simply a while loop. The program in last chapter teaches us how to fullfill the first requirement with functions. Now we focus on how to meet the second the third requirements.

+

Data struct

Last chapter mainly introdues how to write functions to deal with computations as well as data reading. However, the information such as name, medterm and final exam grades are still left there. If more information such as age, weight and grade need to be added, the program would be bloated. In fact, all these information can be integrated as a user-defined data structures as follows

1
2
3
4
5
struct Student_info {
string name;
double midterm, final;
vector<double> homework;
} objectName;

+

The code defines a struct that contains a group of data members. Student_info is the name of this type. Each data member is declared with a type and a name. objectName is an object of such type. Another way to declare an object is

1
Student_info objectName;

+

Note that there must be a semicolon at the end of the curly braces when defining a struct type.

+

It has been observed that each object of such type holds information for one student. We can store all students’ information into a vector, e.g. vector record.

+

reading data

The function that an object of Student_info reads data is similar to that for a vector.

1
2
Student_info record;
read(cin, record);

+

The read function

1
2
3
4
5
6
7
8
9
istream & read(istream &is, Student_info &s)
{
// reads and store the student's name, midterm and final exam grades
is >> s.name >> s.midterm >> s.final;

// reads and store all homework grades
read_hw(is, s.homework);
return is;
}

+

Since the function is similar to the read_hw function defined in this page, no more discussed here.

+

grade function

Now the data have been stored into a sturct and concequently the grade function becomes
overloaded function 1

1
2
3
4
double grade(const Student_info &s)
{
return grade(s.meterm, s.final, s.homework)
}

+

overloaded function 2

1
2
3
4
5
6
double grade(double midterm, double final, const vector<double> &hw)
{
if (hw.size() == 0)
throw domain_error("student has done no homework");
return grade(midterm, final, median(hw));
}

+

overloaded function 3

1
2
3
4
double grade(double midterm, double final, double homework)
{
return 0.2*midterm + 0.4*final + 0.4*homework;
}

+

stores all structs into a vector

Once the record finishes reading data for inputs, we can store it into a vector.

1
2
3
4
5
6
7
vector<Student_info> students;
Student_info record;

while(read(cin, record))
{
students.push_back(record);
}

+

alphabetize students

Up to now, we have finished the code for all computations and data reading. The next step is to sort the students in an alphabetical order according to students’ names. In previous chapters, we uses the standard algorithm sort to accomplish sorting the homework. It can also be used to sort the students, but before we apply it we need to learn how it works on the homework.

+

homework is a vector that contains all values of homework grades. The sort function compares objects in the vector using <. It is clear when using < to compare two numerical values but doesn’t works for the element type of a struct. Regarding to this case, the sort function provides an optional argument, a predicate, for us to define the ways to compare elements.

+

A predicate is a function that typically yields a true value of type bool. Let’s see how is it defined

1
2
3
4
bool compare(const Student_info &x, const Student_info &y)
{
return x.name < y.name;
}

+

The usage of the sort function is

1
sort(students.begin(), students.end, compare);

+

It means that the sort function will compare elements in the students only according to its member name rather than using < directly. As for the effect of < on strings, the expression is evaluated to be true if x.name is alphabetically ahead of y.name. Specifically, when compare two strings

+
    +
  1. the result is the result of comparing the first character at which the strings differ.
  2. +
  3. if all characters of one string equal to the corresponding characters of another string, then the shorter one is less than the longer one.
  4. +
+

align the final grade vertically

Now we deal with the third requirement. Each line of outputs is formed by s.name, a blank string, and the grade. The key to solve this problem is to write a blank string with appropriate length such that all lines have same total length ahead of the final grades while the total length depends on the longest name. The minimum number of spaces between a name and a grade is one. The process can be logically divided into three steps

+
    +
  1. find the longest name
  2. +
  3. calculate the total length ahead of the grade: the size of the longest name plus one(space).
  4. +
  5. Create a blank string for each line with length: the total length minus the size of each name.
  6. +
+

To find the longest name, we use another the max function defined in the header . The syntax is

1
max(el, e2);

+

It returns the larger one of two expressions which yield values of the same type. The comparison is similar to Exercise 3-4 strategy 2.

+

A complete program

Above steps show the core technicals that deals with three requirements mentioned at the begining. The new program built on struct and functions is presented as below

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
int main()
{
vector<Student_info> students;
Student_info record;
string::size_type maxlen = 0;

// read and store all the records, and find the length of the longest name
while(read(cin, record))
{
maxlen = max(maxlen, record.name.size());
students.push_back(record);
}

// alphabetize the records
sort(students.begin(), students.end(), compare);

// write each line of outpurs
for (vector<Student_info>::size_type i = 0; i != students.size(); ++i)
{
// write the name, blanks
cout << students[i].name << string(maxlen + 1 - students[i].name.size(), ' ');

// compute and write the final grade
try{
double final_grade = grade(students[i]);
streamsize prec = cout.precision();
cout << setprecision(3) << final_grade << setprecision(prec);
} catch(domain_error e){
cout << e.what();
}
cout << endl;
}
return 0;
}

+

In this program, we uses a new function what to write the diagnostic message if an exception is thrown. The catch clause named the diagnostic message as e, i.e. the object that contains the message. The message can be obtained from what().

+

Seperate compilation

Strictly speaking, above program is not a complete program as it doesn’t work when it is executed. Because we haven’t add the functions to be called in this program. It can be done by putting all stuff into a single file, which however may increase complexity and reduce readability. Alternatively, we can seperate the program into several files and compile these files seperately. In fact, we uses seperate compilation since the first program. For example, we can use IO class objects by means of including the header and declarations rather than defining the type in our programs. How this is done? Let’s write our own header files!

+

header file and source file

To support seperate compilation, C++ distinguishes declarations and definitions which allows muliple files sharing one definition. For example, if we want to seperate the median function from above program, we need to put its definition into a source file named median.cpp (depending on your c++ implementations), and put its declarations into a header file named median.h. By doing so, the median function is allowed to be accessed in programs as long as we include its header file like

1
#include "median.h"

+

The header file is enclosed by double quotes rather than angle brackets, which makes it distinct from the standard library headers. The source file is created as follows

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
// source file for median function 
#include <algorithm> // to get declaration of sort
#include <stdexcept> // to get declaration of domain_error
#include <vector> // to get declaration of vector
#include <median.h>

// declarations for names
using std::domain_error;
using std::vector;
using std::sort;

// compute the median of vector<double>
double median(vector<double> vec)
{
// get the size of the vector
typedef vector<double>::size_type vec_size;
vec_size size = vec.size();

// check whether the empty is empty
if (size == 0)
throw domain_error("median of an empty vector");

// sort the grades
sort(vec.begin(), vec.end());

// compute the median homework grade
vec_size mid = size/2;
return size % 2 == 0 ? (vec[mid] + vec[mid - 1])/2 : vec[mid];
}

+

Note this file includes all needed headers for the median function itself. It contains both the function declarations and definitions, which allows the compiler to check the consistency between the declarations and definitions. The header file can be written as

1
2
3
4
5
6
7
#ifndef GUARD_median_h
#define GUARD_median_h

#include<vector>
double median(std::vector<double>);

#endif

+

There are several new points here. First, the file include the needed header , and use std::vector instead of using std::vector. This is because we are not sure whether a user want a using declaration in their program as once we add using declaration, all programs that include this header file get a using std::vector. This has been also emphasized in C++ - Getting Started.
Second, #ifndef directive responsible for checking whether GUARD_median_h is defined. GUARD_median_h (aka. header guard) is the name of a preprocessor variable that has two status: defined or not defined. The #define directive takes the name and defines it as a preprocessor variable. ifndef is true if the preprocessor variable is undefined and the preprocessor will process following contents until encounter endif. If ifndef is false, subsquent attempts to include median.h will be overlooked to avoid multiple inclusion.

+

Reorganize the final grade program

The reminder of this post aims to reorganize the final grade program applying the technical, seperate compilation,introduced in this post. Let’s list all functions and data structures needed in this program.

+
    +
  1. read function to read students’ information.
  2. +
  3. read_hw function to read homework grades for each student.
  4. +
  5. compare function as an optional argument in sort.
  6. +
  7. three grade functions(overloaded) to compute the final grade.
  8. +
  9. median function to compute median of homework grades.
  10. +
  11. Beyond above functions, we also defined a data structure, Student_info to hold student’s information.
  12. +
+

Logically speaking, these entities can be divided into two groups:

+
    +
  • group 1 including 6, 1, 2, 3 deals with information
  • +
  • group 2 including 4, 5 deals with computation
  • +
+

Therefore, we can package two groups into two independent files seperately.

+

Group 1

Student_info.h

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
#ifndef GUARD_Student_info
#define GUARD_Student_info

// Student_info.header file
#include<iostream>
#include<string>
#include<vector>

struct Student_info{
std::string name;
double midterm, final;
std::vector<double> homework;
};

bool compare(const Student_info &, const Student_info &);
std::istream & read(std::istream &, Student_info &);
std::istream & read_hw(std::istream &, std::vector<double> &);
#endif

+

Student_info.cpp

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
// source file for Student_info related functions
#include "Student_info.h"
using std::vector; using std::istream;

bool compare(const Student_info &x, const Student_info &y)
{
return x.name < y.name;
}

istream & read(istream &is, Student_info &s)
{
// reads and store the student's name, midterm and final exam grades
is >> s.name >> s.midterm >> s.final;

// reads and store all homework grades
read_hw(is, s.homework);
return is;
}

istream & read_hw(istream &in, vector<double> &hw)
{
if (in){
//get rid of previous contents
hw.clear();

// read homework grades
double x;
while(in >> x)
hw.push_back(x);

// clear the stream so that input will work for the next student
in.clear();
}
return in;
}

+

Group 2

grade.h

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
#ifndef GUARD_grade_h
#define GUARD_grade_h

// grade.h
#include<vector>
#include "Student_info.h"

double grade(const Student_info &);
double grade(double, double, const std::vector<double> &);
double grade(double, double, double);
double median(std::vector<double>);
#endif

**grade.cpp**
```c++
#include <algorithm>
#include <stdexcept>
#include <vector>
#include "grade.h"
#include "student_info.h"

using std::domain_error; using std::istream;
using std::vector; using std::sort;

// grade function 1
double grade(const Student_info &s)
{
return grade(s.midterm, s.final, s.homework);
}

// grade function 2
double grade(double midterm, double final, const vector<double> &hw)
{
if (hw.size() == 0)
throw domain_error("student has done no homework");
return grade(midterm, final, median(hw));
}

// grade function 3
double grade(double midterm, double final, double homework)
{
return 0.2*midterm + 0.4*final + 0.4*homework;
}

// compute the median of vector<double>
double median(vector<double> vec)
{
// get the size of the vector
typedef vector<double>::size_type vec_size;
vec_size size = vec.size();

// check whether the empty is empty
if (size == 0)
throw domain_error("median of an empty vector");

// sort the grades
sort(vec.begin(), vec.end());

// compute the median homework grade
vec_size mid = size/2;
return size % 2 == 0 ? (vec[mid] + vec[mid - 1])/2 : vec[mid];
}

+

Test

1
2
3
4
5
6
7
8
9
10
11
12
13
Inputs:

Robin 90 87 79 88 81 73 45
Brendan 70 69 88 100 91 75 66
Arsenii 99 87 89 88 74 90 70
Liam 83 66 100 76 87 91 78

Outputs:

Arsenii 89.8
Brendan 76.8
Liam 77.8
Robin 84.4
+ +
+ + + + + + + + + + + +
+ + + +
+ + + +
+ +
+
+ + +
+ + + + + + + + + +
+ + + + + + + + + +
+
+ + + + +
+ + +
+ + + + +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/08/04/2018/Accelerated-C-Solutions-to-Exercises-Chapter-9/index.html b/08/04/2018/Accelerated-C-Solutions-to-Exercises-Chapter-9/index.html new file mode 100644 index 00000000..00b532db --- /dev/null +++ b/08/04/2018/Accelerated-C-Solutions-to-Exercises-Chapter-9/index.html @@ -0,0 +1,858 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Accelerated C++ Solutions to Exercises(Chapter 9) | Liam's Blog + + + + + + + + + + + + + + + + + + +
+
+ + + +
+
+
+
+ + +
+ + + + + + + + +
+ + + +
+ + + + + + + +
+ + + +

Accelerated C++ Solutions to Exercises(Chapter 9)

+ + + +
+ + + + + +
+ + + + + +

Exercise 9-0

Compile, execute, and test the programs in this chapter.

+

Solution & Results

Please find the solution and analysis in Defining new types.

+

Exercise 9-1

Reimplement the Student_info class so that it calculates the final grade when reading the student’s record, and stores that grade in the object. Reimplement the grade function to use this precomputed value.

+

Solution & Results

A similar program has been completed in exercise 4-6. Now I’ll write a new grading program based on class type. The strategy can be logically divided into three parts:

+
    +
  1. abstract data members
  2. +
  3. design interface and write member functions
  4. +
  5. access Control
  6. +
+

data members and constructors

The original program uses a class type that has four data members: name, midterm, final and homework. This exercise requires us to store the final grade in the class object, which implies that we need another data member grade representing the final grade. There seems no need to store midterm, fina and homework anymore due to that the final grade is computed when reading the student’s record. I didn’t remove them merely as theoretically these information should be kept for a student. Now, we have part of our new class type

1
2
3
4
5
6
7
class Student_info{
std::string n;
double midterm, final, g;
std::vector<double> homework;

// to be filled by defining/declaring member functions
};

+

The first I considered was to define constructors when desigining an proper interface. The purpose is to allow us to initialize the class object through:

1
2
Student_info record;        // create an empty object
Student_info record(cin); // create and initialize an object by reading from input stream

+

The first one needs a default constructor that is responsible for initializing data members. Following code shows their declarations and definitions that are put outside the class.

+

declaring constructors

1
2
Student_info ();        // construct an empty object 
Student_info (std::istream &); // construct an object by reading from istream

+

defining constructors

1
2
3
4
5
6
7
8
9
Student_info::Student_info(): midterm(0), final(0), g(0) {}
Student_info::Student_info (std::istream & in) { read(in); }
```
Nothing that all three **double** type variables are value-initialized to 0 while **homework** is initialized by default constructor of **vector<double>** type, leading to an empty **homework**.

### other member functions and protection
The next is to define a member function to read data into the class object. The declaration is as same as the previous one:
```c++
std::istream & read(std::istream &);

+

When we define the read member, we should meet the requirement that computing the final grade in the process of reading.

1
2
3
4
5
6
7
8
9
10
11
12
istream & Student_info::read(istream &is)
{
// reads and store the student's name, midterm and final exam grades
is >> n >> midterm >> final;
if (is)
{
// reads and store all homework grades
read_hw(is, homework);
g = !homework.empty() ? ::grade(midterm, final, homework) : 0;
}
return is;
}

+

Noting that I deal with the case of empty homework with an if statement which computes the final grade if the homwork is not empty and otherwise sets the final grade to 0. The purpose is to avoid the exception when reading data. Alternatively, I define extra two functions to check the validity of final grade and “catch” the state of the object. These two functions are defined inside the class

1
2
bool valid() const { return !homework.empty(); }
std::string state() const { return valid() ? "valid" : "invalid: student has done no homework"; }

+

This allows users to seperate valid records and invalid records.

+

Finally, we define members to get the name and grade for the purpose that prevents users access data members directly.

1
2
std::string name() const { return n; }
double grade() const { return g; }

+

All member functions except read and constructors are not allowed to change data members by adding qualifier const after the parameter list. To seperate the data abstraction and interface, we control access using specifier public and private.

+

Organize files

Now let’s complete the header file Student_info.h and the corresponding source file.

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
#ifndef GUARD_STUDENT_INFO
#define GUARD_STUDENT_INFO

#include<iostream>
#include<string>
#include<vector>

class Student_info{
public:
// constructors
Student_info ();
Student_info (std::istream &);

// member functions to check state
bool valid() const { return !homework.empty(); }
std::string state() const { return valid() ? "valid" : "invalid: student has done no homework"; }

// member functions read and functions to get name, grade and
std::string name() const { return n; }
double grade() const { return g; }
std::istream & read(std::istream &);

private:
std::string n;
double midterm, final, g;
std::vector<double> homework;
};

// nonmember functions
bool compare(const Student_info &, const Student_info &);
std::istream & read_hw(std::istream &, std::vector<double> &);
#endif

+

Student_info.cpp

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
#include <stdexcept>
#include "Student_info.h"
#include "grade.h"

using std::vector; using std::cin;
using std::istream; using std::cout;
using std::domain_error;

// construct one by reading from input stream
Student_info::Student_info(): midterm(0), final(0), g(0) {}
Student_info::Student_info (std::istream & in) { read(in); }

// member function read
istream & Student_info::read(istream &is)
{
// reads and store the student's name, midterm and final exam grades
is >> n >> midterm >> final;
if (is)
{
// reads and store all homework grades
read_hw(is, homework);
g = !homework.empty() ? ::grade(midterm, final, homework) : 0;
}
return is;
}

// nonmember function compare two strings
bool compare(const Student_info &x, const Student_info &y)
{
return x.name() < y.name();
}

// nonmember function read data into a vector
istream & read_hw(istream &in, vector<double> &hw)
{
// read homework grades
hw.clear();
double x;
while(in >> x)
hw.push_back(x);

// clear the stream so that input will work for the next student
in.clear();

return in;
}

+

Now we can write the main function.

+

mainfunction.cpp

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
#include <algorithm>		// to get the declaration of max, sort
#include <iomanip> // to get the declaration of setprecision
#include <iostream> // to get the declaration of cin, cout, endl
#include <stdexcept> // to get the declaration of domain_error
#include <string> // to get the declaration of string
#include <vector> // to get the declaration of vector
#include "Student_info.h" // to get the declaration of Student_info

using std::cin; using std::setprecision;
using std::cout; using std::sort;
using std::endl; using std::streamsize;
using std::domain_error; using std::string;
using std::max; using std::vector;

int main()
{
vector<Student_info> students;
Student_info record;
string::size_type maxlen = 0;

// read and store all the records, and find the length of the longest name
while(record.read(cin))
{
maxlen = max(maxlen, record.name().size());
students.push_back(record);
}


// alphabetize the records
sort(students.begin(), students.end(), compare);

// write each line of outpurs
for (vector<Student_info>::const_iterator it = students.begin();
it != students.end(); ++it)
{
// write the name, blanks
cout << (*it).name() << string(maxlen + 1 - (*it).name().size(), ' ');
// compute and write the final grade
double final_grade = (*it).grade();
streamsize prec = cout.precision();
cout << setprecision(3) << final_grade << setprecision(prec);

if(!(*it).valid())
cout << string(4,' ') << (*it).state() << endl;
else
cout << endl;
}
return 0;
}

+

There is nothing new except that I deal with exception with member functions valid() and state() instead the try block.

+

Finally, I present the header file and source file that contains overloaded grade function and median function.

+

grade.h

1
2
3
4
5
6
7
8
9
#ifndef GUARD_GRADE_H
#define GUARD_GRADE_H

#include<vector>

double grade(double, double, const std::vector<double> &);
double grade(double, double, double);
double median(std::vector<double>);
#endif

+

grade.cpp

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 <algorithm>
#include <stdexcept>
#include <vector>
#include "grade.h"

using std::domain_error; using std::istream;
using std::vector; using std::sort;

// grade function 1
double grade(double midterm, double final, const vector<double> &hw)
{
if (hw.empty())
throw domain_error("student has done no homework");
return grade(midterm, final, median(hw));
}

// grade function 2
double grade(double midterm, double final, double homework)
{
return 0.2*midterm + 0.4*final + 0.4*homework;
}

// compute the median of vector<double>
double median(vector<double> vec)
{
// check whether the vec is empty
if (vec.begin() == vec.end())
throw domain_error("median of an empty vector");

// sort the grades
sort(vec.begin(), vec.end());

// compute the median homework grade
vector<double>::difference_type size = vec.end() - vec.begin();
vector<double>::const_iterator mid = vec.begin() + size/2;
return size % 2 == 0 ? (*mid + *(mid - 1))/2 : *mid;
}

+

Test

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
Inputs

Nqacg 32.4444 16.3838 43
Kmgsk 89.2525 14.7374 32
Awhof 73.7071 73.8485
Thyyp 92.7172 47.5556
Zvxxc 66.9091 69.6162 0
Asezo 67.8182 32.6364 10
Evawh 77.798 54.9596 13
Qhwir 75.4242 93.5758
Nbcpz 71.6263 75.8182 47
Dbevs 67.4949 75.3434 31

Outputs

Asezo 30.6
Awhof 0 invalid: student has done no homework
Dbevs 56
Evawh 42.7
Kmgsk 36.5
Nbcpz 63.5
Nqacg 30.2
Qhwir 0 invalid: student has done no homework
Thyyp 0 invalid: student has done no homework
Zvxxc 41.2
+

Users can rewrite the main function to generate a better formatted report according to their own preference, using member functions valid() and state().

+
+

Exercise 9-2

If we define the name function as a plain, nonconst member function, what other functions in our system must change and why?

+

Solution & Results

If we define the name function as a nonconst member function, we need to change the compare function as well.

1
2
3
4
bool compare(const Student_info &x, const Student_info &y)
{
return x.name() < y.name();
}

+

See from the compare function above, the arguments are passed by reference to const objects. In other words, the compare function treats Student_info object as const. If it calls non-const member functions (i.e. name()), there is no guarantee that it doesn’t modify the object, which is a potential conflict with its definition. THerefore, this is not allowed by the compiler. To correct it, we can remove the qualifer const to release its restriction on arguments as shown below.

1
2
3
4
bool compare(Student_info &x, Student_info &y)
{
return x.name() < y.name();
}

+
+

Exercise 9-3, 9-4

9-3: Our grade function was written to throw an exception if a user tried to calculate agrade for a Student_info object whose values had not yet been read. Users who care are expected to catch this exception. Write a program that triggers the exception but does not catch it. Write a program that catches the exception.

+

9-4: Rewrite your program from the previous exercise to use the valid function, thereby avoiding the exception altogether.

+

Solution & Results

The key is to use member function valid() to avoid the exception. This exercise is simple and hence no further analysis. I only write the main function here and please find other files in Defining new types.

+

Trigger the exception

1
2
3
4
5
6
7
8
9
10
11
12
13
#include "Student_info.h"
#include <stdexcept>
#include <iostream>

using std::domain_error;
using std::cout;

int main()
{
Student_info record;
record.grade();
return 0;
}

+

catch the exception

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
#include "Student_info.h"
#include <stdexcept>
#include <iostream>

using std::domain_error;
using std::cout;

int main()
{
Student_info record;
try{
record.grade();
}catch(domain_error e){
cout << e.what();
}
return 0;
}

+

avoid the exception

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
#include "Student_info.h"
#include <stdexcept>
#include <iostream>

using std::domain_error;
using std::cout;
using std::cin;

int main()
{
Student_info record (cin);
if(record.valid())
cout << record.grade();
return 0;
}

+
+

Exercise 9-5

Write a class and associated functions to generate grades for students who take thecourse for pass/fail credit. Assume that only the midterm and final grades matter, and that astudent passes with an average exam score greater than 60. The report should list the students in alphabetical order, and indicate P or F as the grade.

+

Solution & Results

My strategy is to define a new grade function:

+

declaration

1
std::string grade() const;

+

definition

1
2
3
4
string Student_info::grade() const
{
return (midterm + final)/2 > 60 ? "P" : "F";
}

+

So, when calling this member, it gives the letter grade P or F. The revised class type is defined as follows

+

Student_info.h

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
#ifndef GUARD_STUDENT_INFO
#define GUARD_STUDENT_INFO

#include <string>
#include <iostream>
#include <vector>

class Student_info
{
public:
Student_info (); // default constructor
Student_info (std::istream &); // constructor with argument
std::string name() const { return n; } // inline member function return name
std::istream & read(std::istream &); // member function read in data
std::string grade() const;

private:
std::string n;
double midterm, final;
};

std::istream & read_hw(std::istream &, std::vector<double> &); // nonmember function read data into a vector
bool compare(const Student_info &, const Student_info &); // nonmember function compare two string

#endif

**Student_info.cpp**
```c++
#include "Student_info.h"

using std::vector; using std::string;
using std::istream;

// construct an empty Student_info object
Student_info::Student_info (): midterm(0), final(0) { }

// construct one by reading from input stream
Student_info::Student_info (std::istream & in) { read(in); }

// member function read data from input stream
std::istream & Student_info::read(std::istream &in)
{
// reads and store the student's name, midterm and final exam grades
in >> n >> midterm >> final;
return in;
}

// member function grade
string Student_info::grade() const
{
return (midterm + final)/2 > 60 ? "P" : "F";
}

// nonmember function compare
bool compare(const Student_info &x, const Student_info &y)
{
return x.name() < y.name();
}

+

Noting that there is no computations as previous version. What we need to do is merely to remove the needless code.

+

Finally, I present the test function followed by the test results

+

mainfunction.cpp

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
#include <algorithm>		// to get the declaration of max, sort
#include <string> // to get the declaration of string
#include <vector> // to get the declaration of vector
#include "Student_info.h" // to get the declaration of Student_info

using std::cin;
using std::cout; using std::sort;
using std::endl; using std::string;
using std::max; using std::vector;

int main()
{
vector<Student_info> students;
Student_info record;
string::size_type maxlen = 0;

// read and store all the records, and find the length of the longest name
while(record.read(cin))
{
maxlen = max(maxlen, record.name().size());
students.push_back(record);
}

// alphabetize the records
sort(students.begin(), students.end(), compare);

// write each line of outpurs
for (vector<Student_info>::const_iterator it = students.begin();
it != students.end(); ++it)
{
// write the name, blanks
cout << (*it).name() << string(maxlen + 1 - (*it).name().size(), ' ');

// compute and write the final grade
cout << (*it).grade() << endl;
}
return 0;
}

+

Test

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

Xdvdr 55.404 28.7778
Qlyys 91.9192 60.0404
Iutlc 12.9697 61.202
Jygsc 58.2424 99.5657
Wxilm 85.0606 57.2424
Lshfy 34.8687 65.9697
Ujruj 41.8182 89.6364
Orbac 3.58586 56.8788
Fyhub 99 65.2828

Outputs

Fyhub P
Iutlc F
Jygsc P
Lshfy F
Orbac F
Qlyys P
Qzaen P
Ujruj P
Wxilm P
Xdvdr F

+
+

Exercise 9-6

Rewrite the grading program for the pass/fail students so that the report shows all the students who passed, followed by all the students who failed.

+

Solution & Results

One possible solution is that using ths standard library algorithm stable_partition to rearrange the elements (i.e. student records) such that all passing grades preceds all failing grades.

1
stable_partition(students.begin(), students.end(), pgrade);

+

The pgrade is defined as follows

1
2
3
4
5
6
7
8
9
bool fgrade(const Student_info &s)
{
return s.grade() < 60;
}

bool pgrade(const Student_info &s)
{
return !fgrade(s);
}

+

These two function can be declared in the file Student_info.h as nonmember function( see Defining new types). I’ll present the main function and a simple test in below part.

+

mainfunction.cpp

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 <algorithm>		// to get the declaration of max, sort
#include <iomanip> // to get the declaration of setprecision
#include <iostream> // to get the declaration of streamsize
#include <algorithm> // to get the declaration of stable_partition
#include <string> // to get the declaration of string
#include <vector> // to get the declaration of vector
#include "Student_info.h" // to get the declaration of Student_info

using std::cin;
using std::cout; using std::setprecision;
using std::endl; using std::streamsize;
using std::max; using std::vector;
using std::stable_partition; using std::string;

int main()
{
vector<Student_info> students;
Student_info record;
string::size_type maxlen = 0;

// read and store all the records, and find the length of the longest name
while(record.read(cin))
{
maxlen = max(maxlen, record.name().size());
students.push_back(record);
}

// students who passed followed by all student who failed
stable_partition(students.begin(), students.end(), pgrade);

// write each line of outpurs
for (vector<Student_info>::const_iterator it = students.begin();
it != students.end() ; ++it)
{
if((*it).valid())
{
// write the name, blanks
cout << (*it).name() << string(maxlen + 1 - (*it).name().size(), ' ');

// compute and write the final grade
double final_grade = (*it).grade();
streamsize prec = cout.precision();
cout << setprecision(3) << final_grade << setprecision(prec) << endl;
}
}
return 0;
}

+

Test

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
Inputs

Nmlox 42.3434 61.0808 66
Mljwc 39.2727 80.3636 62
Omzml 78.9596 39.8283 42
Buvdm 20.7273 26.8384 35
Lczfw 14.6162 72.1717 75
Bloic 50.2525 60.798 44
Hewvl 59.4646 98.4141 73
Nunsg 95.1414 95.9596 8
Gkaqw 97.7071 85.6263 93
Isohi 49.3434 21.9293 63
Tduzm 92.1111 18.4343 31
Koede 82.404 36.0101 75
Igfab 57.6061 90.9899 15
Ejtaa 93.0404 27.8586 28
Iwhgb 97.6364 44.3333 4
Frbhw 40.7677 68.4747 19
Hsskh 9.44444 90.2121 25
Yvyel 2 20.6566 67
Ktnaa 95.596 19.8586 84
Pdjjb 37.5455 68.303 57

Outputs

Mljwc 64.8
Lczfw 61.8
Hewvl 80.5
Nunsg 60.6
Gkaqw 91
Koede 60.9
Ktnaa 60.7
Nmlox 59.3
Omzml 48.5
Buvdm 28.9
Bloic 52
Isohi 43.8
Tduzm 38.2
Igfab 53.9
Ejtaa 41
Iwhgb 38.9
Frbhw 43.1
Hsskh 48
Yvyel 35.5
Pdjjb 57.6

+
+

Exercise 9-7

The read_hw function §4.1.3/57 solves a general problem (reading a sequence ofvalues into a vector) even though its name suggests that it should be part of theimplementation of Student_info. Of course, we could change its name—but suppose,instead, that you wanted to integrate it with the rest of the Student_info code, in order to clarify that it was not intended for public access despite its apparent generality? How would you do so?

+

Solution & Results

My solution is to add the read_hw as the member function. To control the access, I specify the member as private. Accordingly, the revised Student_info.h is

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
#ifndef GUARD_STUDENT_INFO
#define GUARD_STUDENT_INFO

#include <string>
#include <iostream>
#include <vector>

class Student_info
{
public:
Student_info (); // default constructor
Student_info (std::istream &); // constructor with argument
std::string name() const { return n; } // inline member function return name
bool valid() const { return !homework.empty(); }// inline member function check state
std::istream & read(std::istream &); // member function read in data
double grade() const; // member function calculate final grade

private:
std::string n;
double midterm, final;
std::vector<double> homework;

// private member function read data into a vector
std::istream & read_hw(std::istream &, std::vector<double> &);
};
// nonmember function compare two string
bool compare(const Student_info &, const Student_info &);
#endif

+

Correspondingly, when we define the read_hw outside the class, we need to specify the name scope using Student_info::read_hw. The new read_hw is given below

+
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
istream & Student_info::read_hw(istream &in, vector<double> &hw)
{
if (in){
//get rid of previous contents
hw.clear();

// read homework grades
double x;
while(in >> x)
hw.push_back(x);

// clear the stream so that input will work for the next student
in.clear();
}
return in;
}
+

All other files keep unchanged and can be found in Defining new types.

+
+

Reference

Koenig, A. and Moo, B.E., 2000. Accelerated C++.

+

Lippman, S.B., Lajoie, J. and Moo, B.E., 2013. C++ Primer, 5th edt.

+ + +
+ + + + + + + + + + + +
+ + + +
+ + + +
+ +
+
+ + +
+ + + + + + + + + +
+ + + + + + + + + +
+
+ + + + +
+ + +
+ + + + +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/08/05/2018/C-Using-inheritance-and-dynamic-binding/index.html b/08/05/2018/C-Using-inheritance-and-dynamic-binding/index.html new file mode 100644 index 00000000..4a1ee1c7 --- /dev/null +++ b/08/05/2018/C-Using-inheritance-and-dynamic-binding/index.html @@ -0,0 +1,844 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + C++ - Using inheritance and dynamic binding | Liam's Blog + + + + + + + + + + + + + + + + + + +
+
+ + + +
+
+
+
+ + +
+ + + + + + + + +
+ + + +
+ + + + + + + +
+ + + +

C++ - Using inheritance and dynamic binding

+ + + +
+ + + + + +
+ + + + + +

Inheritance

In this chapter, we intend to extend our grading program such that it meets the new requirements: students can take undergraduate or graduate credit while graduate students have to write a thesis in addition to the homework and exams. In other words, a record for graduate credit is the same as for undergraduate credit except that it has extra properties related to the thesis. This problem can be abstracted and solved by a mechanism called inheritance, which is one of the cornerstones of OOP.

+

Specifically, we’ll write two classes, the first class is the abstraction of the core requirements and is named Core while the second class represents the requirements for graduate credit and hence named Grad. The Grad class captures extra requirements but has same core requirements as the Core class. Therefore, We write two classes such that the Grad class can inherit the properties from the Core class. Typically, we say that the Grad class is derived from or inherits from the base class, i.e. the Core class here. let’s see how to define these two classes:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
class Core{
public:
Core();
Core(std::istream&);
std::string name() const;
std::istream& read(std::istream&);
double grade() const;

private:
std::istream& read_common(std::istream&);
std::string n;
double midterm, final;
std::vector<double> homework;
};

+
1
2
3
4
5
6
7
8
9
class Grad: public Core{
public:
Grad();
Grad(std::istream&);
double grade() const;
std::istream& read(std::istream&);
private:
double thesis;
};
+

Since Grad class inherits from Core class, every member of Core is also a member of Grad, except for the constructor, assignment operator, and destructor. The Grad class also has its own members, such as the thesis and its own constructors. It can also redefine members from the base class, such as the grade and read function.

+

The keyword public in public Core means that Grad inherits from Core is part of its interface rather than its implementation. In other words, the public interface to Core becomes part of the public interface to Grad. For example, if we have a Grad object, we can call its name member thought Grad doesn’t define its own name function.

+

Beyond the four data members from the Core class, Grad has a member thesis and calculates grade() using different algorithm. It have two constructors, and four member functions, two of which redefine the corresponding members of Core, and name and read_common functions.

+

Protection revisited

As it stands, four data members as well as the read_common function are inaccessible to member functions in Grad as they are private and only available to the Core members and its friends. But we do need these data members and read_common for defining the grade and read functions in the Grad. To achieve this goal, we rewrite the Core class using a protection lable:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
class Core{
// public members are available for users of the derived class
public:
Core();
Core(std::istream&);
std::string name() const;
std::istream& read(std::istream&);
double grade() const;

// protected members are available for member functions of the derived class but not available for users
protected:
std::istream& read_common(std::istream&);
double midterm, final;
std::vector<double> homework;

// only available for members of the class itself and its friends.
private:
std::string n;
};

+

The protected members are available for derived classes but still inaccssible users of the classes. n is still private but Grad can access the name by calling its member function name.

+

Operations

The next is to implement four constructors(each class has one default constructor and one constructor with arguments) and six operations including common functions name, read_common, and read and grade for two class respectively. We’ll read the thesis grade closely after the final exam grade but precede the homework grades.

+
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
// name function
string Core::name() const { return n; }

// grade function for Core
double Core::grade() const {
return ::grade(midterm, final, homework);
}

// read_common function
istream& Core::read_common(istream& in){
in >> n >> midterm >> final;
return in;
}

// read function for Core
istream& Core::read(istream& in){
read_common(in);
read_hw(in, homework);
return in;
}
+
1
2
3
4
5
6
7
8
9
10
11
12
// read for Grad
istream& Grad::read(istream& in){
Core::read_common(in);
in >> thesis;
read_hw(in, Core::homework);
return in;
}

// grade for Grad
double Grad::grade() const{
return min(Core::grade(), thesis);
}
+

The Grad::grade function shows that we calculate the final grade as the lesser between the grade excluding thesis, and thesis. Though we can call members of Core directly, we’d better explicitly call some functions for avoiding ambiguity. For example, if we don’t explicitly call Core::grade(), the compiler may use the Grad::grade dirctly.

+

Inheritance and constructors

Derived objects are constructed by(Koenig and Moo 2000):
1. Allocating space for the entire object (base-class members as well as derived members)
2. Calling the base-class constructor to initialize the base-class part(s) of the object
3. Initializing the members of the derived class as directed by the constructor initializer
4. Executing the body of the derived-class constructor, if any

+

Clearly, the constructor of a derived class not only constructs its own members but also constructs data members of the base class.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
class Core{
public:
// default constructor for Core
Core(): midterm(0), final(0) {}

// build a Core from an istream
Core(istream& is){ read(is); }

// ...
};

class Grad: public Core{
public:
// default constructor for Grad: first implicitly calls the default constructor Core::Core()
Grad(): thesis(0) {}

// build a Grad from istream: first implicitly calls Core::Core()
Grad(std::istream& is) { read(is); }

//...
};

+

For example, when execute

1
Grad g; // create an empty object

+

The computer allocates enough space to hold five data members for the Grad object, run the Core default constructor to initialize the data members in the Core part of g, and then run the default constructor of Grad. Again, when execute

1
Grad g(cin);

+

the computer will run the Core default constructor, followed by the Grad::Grad(istream&) constructor to read values into five data memners.

+

Polymorphism and virtual functions

There is also a support function for the Student_info program, that is, the compare function that acts as the predicate of the std::sort algorithm.

1
2
3
bool compare(const Core& c1, const Core& c2){
return c1.name() < c2.name();
}

+

How does it work on the Grad class objects? For example

1
2
3
4
5
6
7
8
9
Grad g(cin);    // read a Grad record
Grad g2(cin); // read a Grad record

Core c(cin); // read a Core record
Core c2(cin); // read a Core record

compare(g, g2); // compare two Grad records
compare(c, c2); // compare two Core records
compare(g, c); // compare Grad record with a Core record

+

The compare function can take two Core objects as well as two Grad objects, even one Core and one Grad. For the function body, it makes sence as any Grad object has a member name, which it inherits from the base. But why we can pass a Grad object to a function expecting a Core&? The reason is that Grad is inherited from Core and hence has a Core part. Then, we can bind compare‘s reference parameters to the Core portions of Grad objects, as if we bind them to plain Core objects.

+

Obtaining a value without knowing the object’s type

The compare function described above works properly. However, if we intend to compare the grade rather than the name, the function seems inappropriate for Grad objects as two classes have different grade function. A right logical manner is that the compare function can invoke the right grade function according to the type of the object that we pass, only at the stage of run time. To support this kind of run time selection, C++ provides virtual functions:

1
2
3
4
class Core{
public:
virtual double grade() const; // virtual added
};

+

When we call compare(grade-version), the implementation will determine which version of the grade should execute by looking at the actual type of the objects to which the reference c1 and c2 are bound. If the argument is Grad,then it calls Grad::grade and calls Core::grade otherwise.

+

Noting that the keyword virtual may be used only inside the class definition. If we seperate the declaration and definition, we do not need to repeatedly use it in the definition.

+

Dynamic binding

Another point about the virtual is that it is relevant only when the function is called through a reference or a pointer. If we call the function on behalf of the object, then we know the exact type of the object at compile time. In contrast, a reference or a pointer to a base class object may refer or point to a base-class object or to an object of a type derived from the base class. Assuming we write compare_grades:

1
2
3
4
// incorrect implementation
bool compare_grades(Core c1, Core c2){
return c1.grade() < c2.grade();
}

+

In this case, we know exactly that both two objects are Core type. Even we call the function with Grad objects, the Grad objects will be cut down to its Core part and a copy of that part will be passed to the compare_grades function. This case is known as statically bound, that is, the calls to Grad are bound at compile to Core::grade. Obviously, the dynamic binding is that the function is dynamically bound at run time. If we call a virtual function through a pointer or a reference, the version of virtual function to use depends on the type of the object which the reference or pointer is bound.

1
2
3
4
5
6
7
8
9
Core c;
Grad g;
Core* p;
Core& r = q;

c.grade(); // statically bound to Core::grade()
g.grade(); // statically bound to Grad::grade()
p->grade(); // dynamically bound, depending on the type of the object to which p points
r.grade(); // dynamically bound, depending on the type of the object to which r refers

+

The fact that we can use a derived type where a pointer or reference to the base is expected is an example of polymorphism, meaning of many form. When we call the virtual function by a pointer or reference, we make a polymorphic call. We’ll make the read function virtual as well and then the version of the read function to be called depends on the type of the object on which it is invoked.

+

Recap

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
class Core{
public:
Core(): midterm(0), final(0) {}
Core(std::istream& is) { read(is); }

std::string name() const;

virtual std::istream& read(std::istream&);
virtual double grade() const;

protected:
// accessible to derived classes
std::istream& read_common(std::istream&);
double midterm, final;
std::vector<double> homework;

private:
// accessible only to Core
std::string n;
};

class Grad:: public Core{
public:
Grad(): thesis(0) {}
Grad(std::istream& is) { read(is); }

double grade() const;
std::istream& read(std::istream);

private:
double thesis;
};
bool compare(const Core&, const Core&);
+

Using inheritance to solve our problem

Now we can write our student grading prorgam described in chapter 9. The problem is how can we write a program that can handle with both Core objects and Grad objects. To achieve our goal, we need to eliminate these type dependencies(Koenig and Moo 2000):

+

1. The definition of the vector in which we store the elements as we read them
2. The definition of the local temporary into which we read the records
3. The read function
4. The grade function

+

Now we’ll see how to solve these problems.

+

Containers of virtually unkown type

Consider if we define a vector as follows:

1
2
vector<Core> students;      // must hold Core objects, not polymorphic types
Core record; // Core object, not a type derived from Core

+

It is impossible to hold the Grad objects as we explicitly declare that a vector hold objects of type Core. Then, if we call the read function or grade function we indeed call Core::read or Core::grade. However, we have mentioned in above section, if we call those functions through pointers or references, these functions are dynamically bound at run time, depending on the type of the object which the reference or pointer is bound. Therefore, a natural solution is that define a vector that stores the pointer to each element rather than the element itself.

1
2
vector<Core*> students;
Core* record;

+

Howvever, this doesn’t work as no one knows where the record points initially. If the computer executes data reading, the program would crash.

1
while(record->read(cin)) { // crash! }

+

Now we provide a verbose solution to this problem: let users manually control the type of the object. We use lable U to represent that the type is a Core object, and use G to represent that the type is a Grad object. Before we implement this strategy, we should rewrite our compare function such that it can sort two pointers.

1
2
3
bool compare_Core_ptrs(const Core* cp1, const Core* cp2){
return compare(*cp1, *cp2);
}

+

Noting that we can’t name this predicate as compare as we cannot pass an overloaded function as a template argument. Now, let’s see the whole 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
// this work almost work
int main(){
vector<Core*> students; // store pointers, not objects
Core* record;
char ch;
string::size_type maxlen = 0;

// read and store the data
while(cin >> ch){
if(ch == 'U')
record = new Core; // allocate a Core object
else
record = new Grad; // allocate a Grad object
record->read(cin); // virtual call
maxlen = max(maxlen, record->name().size()); // dereference
students.push_back(record);
}

// pass the version of compare that works on pointers
sort(student.begin(), student.end(), compare_Core_ptrs);

// write the names and grades
for (vector<Core*>::size_type i = 0; i != student.size(); ++i){
cout << students[i]->name()
<< string(maxlen + 1 - students[i]->name.size(), ' ');
try{
double final_grade = students[i]->grade();
streamsize prec = cout.precision();
cout << setprecision(3) << final_grade
<< setprecision(prec) << endl;
}catch(domain_error e){
cout << e.what() << endl;
}
delete student[i]; // free the object allocating when reading
}
return 0;
}

+

Virtual destructors

Above program almost works. The only problem occurs when we delete the object. When we store each pointer, we store each as Core* though they may point to a Grad. Therefore, the delete operation can only delete pointers to Core. To solve this problem, we define a virtual destructor:

1
2
3
4
5
class Core{
public:
virtual ~Core() {}
// as before
};

+

Now, when we execute delete students[i], the destructor that will be run depends on the type of the object to which student[i] actually points. A virtual destructor is needed any time it is possible that an object of derived type is destroyed through a pointer to base.

+

A simple handle class

The above approach does solve the problem but seems complex. Users have to manage the pointers and memory properly to avoid potential bugs. we can use the technique handle class to encapsulate the pointer to Core:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
class Student_info{
public:
// constructors and copy control
Student_info(): cp(0) {}
Student_info(std::istream& is): cp(0) { read(is); }
Student_info(const Student_info&);
Student_info& operator=(const Student_info&);
~Student_info() { delete cp; }

// operations
std::istream& read(std::istream&);
std::string name() const {
if(cp) return cp->name();
else throw std::runtime_error("unitialized Student");
}
double grade() const{
if(cp) return cp->grade();
else throw std::runtime_error("unitialized Student");
}
static bool compare (const Student_info& s1, const Student_info& s2){
return s1.name() < s2.name(); }
private
Core* cp;
};

+

Now the Student_info object represents either a Core or Grad. This handle class hids the details of implementations related to pointers as used in above program, and provides an interface that is consistent with the Core and Grad. Users do not need to worry about memory management any more as all has been done by the handle class. The novelty is that we define the compare function as a static member which is associated with a class rather than a particular object.
We can call it through Student_info::compare() directly even without creating any object first. Therefore, static function member cannot access nonstatic data members of objects of the class as there is no object associated with the function and hence no members to use.

+

Reading the handle

The first constructor construct a nullptr. The second constructor constructs an object from the input stream, relying on the read function:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
istream& Student_info::read(istream& is){
delete cp; // delete previous object, if any

char ch; // get record type
is >> ch;

if(ch == 'U'){
cp = new Core(is); // construct Core from istream
}
else{
cp = new Grad(is); // construct Grad from istream
}
return is;
}

+

The read function allocate the space and construct the right type object according to the information from input stream. It starts by freeing the existing object (if any) to which the handle object was previously bound. It is worth noting that if cp is a nullptr, we still can use delete without causing any error.

+

Copying the handle objects

It also defines the copy constuctor and assignment operator. These two operations typically need to allocate new objects and then initialize or assign values from the object from which we are copying. But the problem is how can we know the type of the object from which we are copying? The object, i.e. cp, may point to a Core or a Grad. The solution is to define a new virtual function:

1
2
3
4
5
6
7
class Core{
friend class Student_info;

protected:
virtual Core* clone() const { return new Core(*this); }
// as before
};

+

The clone() function creates a new object that holds copies of the values in the original. The Core doesn’t have a user-defined copy constructor but have a synthesized copy constructor which copies each member from the existing Core object into the newly created object. The member is inaccessible to users and non-derived classes. Therefore, we declare the Student_info as a friend. Then all members of the Student_info are friends of Core. The Grad class inherits this member, but will return a new Grad:

1
2
3
4
5
class Grad: public Core{
protected:
Grad* clone() const { return new Grad(*this); }
// as before
};

+

In general, when we redefine a member function from the base class, we keep the parameter list and the return type unchanged. However, if the base-class function returns a pointer (or reference) to a base class, then the derived-class function can return a pointer or reference to a corresponding derived class.

+

In addition to above, the derived class doesn’t inherit the friend class from the base class. In this case, it is unnecessary to declare the Student_info as the friend class of Grad due to the fact that the Student_info class never refers to Grad::clone directly instead through the virtual function defined in Core.

+

The copy constructor and assignment operator are defined as follows:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
Student_info::Student_info(const Student_info& s): cp(0) {
if (s.cp) cp = s.cp->clone();
}

Student_info& Student_info:: operator=(const Student_info& s){
if(&s != this){
delete cp;
if(s.cp)
cp = s.cp->clone();
else
cp = 0;
}
return *this;
}

+

One may wonder that why we can access the private member cp of object s. It is because that private only restricts data access from other classes. In other words, if both objects are instances of the same class, they are allowed to access private members with each other.

+

Using the handle class

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
int main(){
vector<Student_info> students;
Student_info record;
string::size_type maxlen = 0;

// read and store the data
while (record.read(cin)){
maxlen = max(maxlen, record.name().size());
students.push_back(record);
}

// alphabetize the student records
sort(students.begin(), students.end(), Student_info::compare);

// write the names and grades
for (vector<Student_info>::size_type i = 0;
i != students.size(); ++i){
cout << students[i].name()
<< string(maxlen + 1 - students[i].name.size(), ' ');
try{
double final_grade = students[i].grade();
streamsize prec = cout.precision();
cout << setprecision(3) << final_grade
<< setprecision(prec) << endl;
}catch (domain_error e){
cout << e.what() << endl;
}
}
return 0;
}
+

Now the program takes either a undergraduate record or a graduate record. It first reads the character that says what kind of record we are about to read, then creates a new object and initializes it from input stream. Then it stores objects and deal with sorting, printing as same as the previous program. When it exits from the main, all created objects are deleted automatically through the destructor defined in the Student_info class.

+

Subtleties

We are allowed to store Core or Grad objects into a vector due to the fact that push_back function takes a reference to the vector‘s value type. But the result is that the vector only stores the Core part of a Grad object.

+

If we want to declare a virtual function, we must give it the same interface in the base and the derived classes.

+ + +
+ + + + + + + + + + + +
+ + + +
+ + + +
+ +
+
+ + +
+ + + + + + + + + +
+ + + + + + + + + +
+
+ + + + +
+ + +
+ + + + +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/09/03/2018/Accelerated-C-Solutions-to-Exercises-Chapter-4/index.html b/09/03/2018/Accelerated-C-Solutions-to-Exercises-Chapter-4/index.html new file mode 100644 index 00000000..7f9a2247 --- /dev/null +++ b/09/03/2018/Accelerated-C-Solutions-to-Exercises-Chapter-4/index.html @@ -0,0 +1,848 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Accelerated C++ Solutions to Exercises(Chapter 4 Part 1) | Liam's Blog + + + + + + + + + + + + + + + + + + +
+
+ + + +
+
+
+
+ + +
+ + + + + + + + +
+ + + +
+ + + + + + + +
+ + + +

Accelerated C++ Solutions to Exercises(Chapter 4 Part 1)

+ + + +
+ + + + + +
+ + + + + +

Exercise 4-0

Compile, execute, and test the programs in this chapter

+

Solution & Results

This exercise has been done presented in Organizing programs with functions and Organizing programs with data structures.

+
+

Exercise 4-1

We noted in §4.2.3/65 that it is essential that the argument types in a call to maxmatch exactly. Will the following code work? If there is a problem, how would you fix it?

1
2
3
int maxlen;
Student_info s;
max(s.name.size(), maxlen);

+

Solution & Results

It doesn’t work. There exist two problems here.

+

First, the max function defined in standard header requires that both arguments must have the same type. But in this piece of code, s.name.size() has the type of string::size_type while maxlen is a int type. To fix this, we need to define maxlen as a variable of type string::size_type.

+

Second, it should be initialized as a variable of built-in type(assuming no problem with type int) is undefined if it is not initialized explicitly. For a variable of other types, the default value (if available, otherwise the variable is undefined) depends on how each type defines. I did a simple experiment as follows

1
2
3
4
5
6
7
8
9
10
11
#include<iostream>
#include<string>

using std::cout;
using std::string;

int main(){
string::size_type y;
cout << y;
return 0;
}

+

The result is

1
4200939

+

with warning

1
'y' is used uninitialized in this function

+

The experiment shows that the type of string::size_type doesn’t support default initialization. To fixed this, we need to explicitly initialize it.

+

The correct code should be:

1
2
3
string::size_type maxlen = 0;
Student_info s;
max(s.name.size(), maxlen);

+
+

Exercise 4-2, 4-3

4-2: Write a program to calculate the squares of int values up to 100. The program should write two columns: The first lists the value; the second contains the square of that value.Use setw to manage the output so that the values line up in columns.

+

4-3: What happens if we rewrite the previous program to allow values up to but not including 1000 but neglect to change the arguments to setw? Rewrite the program to be more robust in the face of changes that allow i to grow without adjusting the setw arguments.

+

Solution & Result

algorithms

Exercise 4-3 is a generalized version of 4-2. Specifically, the program is required to write two colomns as follows

1
2
3
4
5
6
7
8
9
  0        0
1 1
2 4
3 9
... ...
99 9801
100 10000
... ...
n n*n

+

Each line contains an integer value followed by the square of the integer. The range of integers in the first column starts from 0 to 1000 (excluded). Most importantly, each line should be formated such that the values line up in columns.

+

The key to the solution is to find the longest number in each column. Then, we can set the width of the first column as the number of digits of the corresponding longest number. Analogously, the width of the second column will be set as the number of digits of the longest number in it, with an additional space to seperate from the first column.

+

For an ascending sequence, the longest numbers in both columns depend on the largest number. For exercise 4-2, the largest number is 100 and 10000 (square of 100) in the first and second column, respectively. We can write and format each line as below shows

1
2
3
4
5
for (int i = 0; i != 101; ++i)
{
// 100 has three digits and 10000 has 5 digits, 1 additional space for seperate two columns
cout << setw(3) << i << setw(6) << i*i << endl;
}

+

Exercise 4-3 requires more flexibility such that no needs to change setw arguments when the largest number changes. Naturally, the key is to compute the number of digits of a user-defined largest number, e,g. defined as maxNum.

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
// hold the number of digits of maxNum and its square
int n = 0;
int m = 0;

// hold the values of the largest number and its square
int j = maxNum;
int k = maxNum*maxNum;
// bounds check
if (j >= 1000 || j < 0)
{
cout << "Enter a number greater than or equal to 0 and less than 1000. Please try again ";

return 1;
}

// computations
if (j == 0)
{
m = n = 1;
}
else
{
// loop invariant: we have counted n digits of the value j
while (j != 0)
{
// each operation j reduces one digit
j /= 10;

// maintain the loop invariant
++n;
}

// loop invariant: we have counted n digits of the value j
while (k != 0)
{
k /= 10;
++m;
}
}

+

organize the program with functions

Obviously, above code is partly repeated. I’ll rewrite the computations as a function which returns the number of digits of an entered value.
width.cpp

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
// function to compute the number of digits of an integer value
#include "width.h"

int width(int num)
{
if (num == 0)
return 1;
else
{
int n = 0;
while(num != 0)
{
num /= 10;
++n;
}
return n;
}
}

+

width.h

1
2
3
4
5
#ifndef GUARD_width_h
#define GUARD_width_h

int width(int);
#endif

+

mainfunction.cpp

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
#include <iostream>
#include <iomanip>
#include "width.h"

using std::cin; using std::cout;
using std::endl; using std::setw;

int main()
{
// asks to enter the upper bound
cout << "Enter a number greater than or equal to 0 and less than 1000: ";

// read the largest number
int maxNum;
cin >> maxNum;

// bounds check
if (maxNum >= 1000 || maxNum < 0)
{
cout << "The entered value is beyond the allowed value range. Please try again.";
return 1;
}

// write the outputs
for (int i = 0; i != maxNum + 1; ++i)
{
cout << setw(width(maxNum)) << i
<< setw(width(maxNum*maxNum) + 1) << i*i << endl;
}
return 0;
}

+

Test performance

Test 1: the upper limit is 10

1
2
3
4
5
6
7
8
9
10
11
12
Enter a number greater than or equal to 0 and less than 1000: 10
0 0
1 1
2 4
3 9
4 16
5 25
6 36
7 49
8 64
9 81
10 100

+

Test 2: the upper limit is 101

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
Enter a number greater than or equal to 0 and less than 1000: 101
0 0
1 1
2 4
3 9
.. ..
90 8100
91 8281
92 8464
93 8649
94 8836
95 9025
96 9216
97 9409
98 9604
99 9801
100 10000
101 10201

+

Test 3: the upper limit is 1000

1
2
Enter a number greater than or equal to 0 and less than 1000: 1000
The entered value is beyond the allowed value range. Please try again.

+

It can be seen from these tests that the program perform as expected.

+
+

Exercise 4-4

Now change your squares program to use double values instead of ints. Use manipulators to manage the output so that the values line up in columns

+

Solution & Results

The difference between this exercise and last exercise is that the function width defined above can not compute the number of digits of a double value. Admittedly, I didn’t found very good strategy to complete this project. I circument the problem applying the type conversion technique. Specifically

+
    +
  1. divide the maxNum/maxNum*maxNum into integer part and fractional part.
  2. +
  3. compute the number of digits of the integer part using same function as shown above.
  4. +
  5. control the fractional part with an additional user-defined variable places which represents the number of places after the dot point.
  6. +
+

The arguments of *setw for the first column is:

1
width(maxNum) + 1 + places

+

The number 1 leaves room for the decimal point.

+

The arguments of *setw for the first column is:

1
width(maxNum*maxNum) + 1 + 1 + places

+

One of two values of 1 is added for the decimal point while the other one is added for seperating from the first column. I didn’t change anything in the width function described above and hence the arguments to be passed are converted to int type, leading to that the returned value of width is the width of the part before the decimal point.

+

Beyond this, the program asks to enter an initial value and an value of the increment for numbers of the first column. For example, we set the initial value as 0.0 and increment as 0.5, the first column becomes

1
2
3
4
5
0.0
0.5
1.0
...
n

+

where n is the largest number (i.e. maxNum) that available for outputs in the range of [0.0, 1000). It will be computed in the program.

+

To format outputs, I uses function fixed together with setprecision to fixed number of decimal places and showpoint to enable the display of trailing 0. A detailed comparison between these three can be found in C++ - Working with batches of data.

+

Please find the midth.cpp and midth.h in exercise 4-2/3. The main function file is shown below.

+
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
// Accelerated C++ Solutions Exercises 4-4
#include <iostream>
#include <iomanip>
#include "width.h"

using std::cin; using std::endl;
using std::cout; using std::setw;
using std::setprecision; using std::fixed;
using std::streamsize; using std::showpoint;
using std::noshowpoint;

int main()
{
// asks toset the range for outputs
double minNum, incrementByValue;
cout << "Enter an intial value greater than or equal to 0.0 and less than 1000.0: ";
cin >> minNum;
cout <<"Enter the increment for each line of outputs: ";
cin >> incrementByValue;

// bounds check
if (minNum >= 1000.0 || minNum < 0.0)
{
cout << "The entered value is beyond the allowed value range. Please try again.";
return 1;
}

// asks to enter a value that determines decimal places
cout << "How many places you want to keep after decimal point?\n"
"Enter an integer to determine decimal places: ";
int places;
cin >> places;

// get the largest value that available for outputs
double maxNum = minNum;
while ((maxNum + incrementByValue) < 1000.0)
{
maxNum += incrementByValue;
}

// write the outputs
for (double i = minNum; i < 1000.0; i += incrementByValue)
{
streamsize prec = cout.precision();
cout << showpoint << fixed << setprecision(places)
<< setw(width(maxNum) + places + 1) << i
<< setw(width(maxNum*maxNum) + places + 2) << i*i
<< setprecision(prec) << noshowpoint << endl;
}
return 0;
}
+

It is worth noting that we cannot use equality and inequality operators in the floating point value conditions as floating values cannot be precisely represented in the computer world. Due to this limitation, I change the condition i != 1000 to i < 1000.0. I did several tests to show how is the performance.

+

Test 1

1
2
3
4
5
6
7
8
9
10
11
12
13
14
Enter an intial value greater than or equal to 0.0 and less than 1000.0: 0
Enter the increment for each line of outputs: 100
How many places you want to keep after decimal point?
Enter an integer to determine decimal places: 0
0. 0.
100. 10000.
200. 40000.
300. 90000.
400. 160000.
500. 250000.
600. 360000.
700. 490000.
800. 640000.
900. 810000.

+

Test 2

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
Enter an intial value greater than or equal to 0.0 and less than 1000.0: 0.5234
Enter the increment for each line of outputs: 0.5
How many places you want to keep after decimal point?
Enter an integer to determine decimal places: 4
0.5234 0.2739
1.0234 1.0473
1.5234 2.3207
2.0234 4.0941
2.5234 6.3675
3.0234 9.1409
3.5234 12.4143
4.0234 16.1877
4.5234 20.4611
5.0234 25.2345
5.5234 30.5079
6.0234 36.2813
6.5234 42.5547
.... ....
996.0234 992062.6133
996.5234 993058.8867
997.0234 994055.6601
997.5234 995052.9335
998.0234 996050.7069
998.5234 997048.9803
999.0234 998047.7537
999.5234 999047.0271

+

Test 3

1
2
3
4
5
6
Enter an intial value greater than or equal to 0.0 and less than 1000.0: 999
Enter the increment for each line of outputs: 1
How many places you want to keep after decimal point?
Enter an integer to determine decimal places: 1
999.0 998001.0
```

+
+

Exercise 4-5

Write a function that reads words from an input stream and stores them in a vector. Use that function both to write programs that count the number of words in the input, and to count how many times each word occurred.

+

Solution & Results

This exercise is a variant of Chapter 3 Exercise 3-3. I uses exactly the same solution strategy in this project. Therefore, no more discussion here. The code and tests can be found below.

+

mainfunction.cpp

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
#include <iostream>
#include <string>
#include <vector>
#include <iomanip>
#include <stdexcept>
#include "wordsRead.h"

using std::cin; using std::string;
using std::cout; using std::vector;
using std::endl; using std::setw;
using std::domain_error;

int main()
{
// hold the information of each words
vector<words_info> words;

// type alias
typedef vector<words_info>::size_type vec_size;
typedef string::size_type str_size;
str_size word_size = 0;

// count the number of words
int totalNum = 0;

// asks to enter words
cout << "Please enter words: ";


try{
// read, count and store words
wordsRead(cin, words, totalNum, word_size);

// write the total number of inputs
cout << "The total number of words is: " << totalNum << endl;

// write each distinct word and its occurrence number
for (vec_size i = 0; i != words.size(); ++i)
{
// format each line
int n = 9 + word_size - words[i].wordName.size();
cout << words[i].wordName << setw(n) << " appears "
<< words[i].count << " times"<< endl;
}

}catch(domain_error){
cout << "You must enter at least one word. Please try again.";
}
return 0;
}

+

wordsRead.cpp

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
// function to read, count and store words
#include <iostream>
#include <string>
#include <vector>
#include <stdexcept>
#include "wordsRead.h"

using std::istream; using std::domain_error;
using std::string; using std::vector;

istream & wordsRead(istream &is, vector<words_info> &words, int &totalNum, string::size_type &word_size)
{
words_info word;

// loop invariant: we have read totalNum words now
while(is >> word.wordName)
{
// maintain the loop invariant
++totalNum;

// find the size of the longest word
if (word_size < word.wordName.size())
word_size = word.wordName.size();

// set flag to find each distinct word
// flag == true: the word is distinct
// flag == false: the word exists
bool flag = true;
for (vector<words_info>::size_type i = 0; i != words.size(); ++i)
{
// compare with previous distinct words
if (word.wordName == words[i].wordName)
{
++words[i].count;
flag = false;
}
}

// if the word has not been entered, store
if (flag == true)
{
word.count = 1;
words.push_back(word);
}

}

if (totalNum == 0)
throw domain_error("No input");
return is;
}

+

wordsRead.h

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
#ifndef GUARD_wordsRead_h
#define GUARD_wordsRead_h

#include <iostream>
#include <string>
#include <vector>

struct words_info{
std::string wordName;
int count;
};

std::istream & wordsRead(std::istream &, std::vector<words_info> &words, int &, std::string::size_type &);


#endif /* GUARD_wordsRead_h */

+

Test Performance

Test 1

1
2
3
4
5
6
7
8
9
10
11
Please enter words: I am a good teacher and you are a good student
The total number of words is: 11
I appears 1 times
am appears 1 times
a appears 2 times
good appears 2 times
teacher appears 1 times
and appears 1 times
you appears 1 times
are appears 1 times
student appears 1 times

+

Test 2

1
2
3
4
5
6
7
8
9
10
11
12
13
Please enter words: what happens depends on the range of the values that the types permit
The total number of words is: 13
what appears 1 times
happens appears 1 times
depends appears 1 times
on appears 1 times
the appears 3 times
range appears 1 times
of appears 1 times
values appears 1 times
that appears 1 times
types appears 1 times
permit appears 1 times

+
+

To be continued.

+ + +
+ + + + + + + + + + + +
+ + + +
+ + + +
+ +
+
+ + +
+ + + + + + + + + +
+ + + + + + + + + +
+
+ + + + +
+ + +
+ + + + +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/09/05/2018/C-Managing-memory-almost-automatically/index.html b/09/05/2018/C-Managing-memory-almost-automatically/index.html new file mode 100644 index 00000000..5da000a9 --- /dev/null +++ b/09/05/2018/C-Managing-memory-almost-automatically/index.html @@ -0,0 +1,849 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + C++ - Managing memory (almost) automatically | Liam's Blog + + + + + + + + + + + + + + + + + + +
+
+ + + +
+
+
+
+ + +
+ + + + + + + + +
+ + + +
+ + + + + + + +
+ + + +

C++ - Managing memory (almost) automatically

+ + + +
+ + + + + +
+ + + + + +

In last chapter, we write a new class named Student_info to encapsulate the pointer to Core so that we do not need to concern about the memory management. Now we’ll further improve our class by seperating the class into two classes: one is a pure interface class and the other is a single pointerlike class which manages the underlying memory. The purpose to do so is that we then can use the pointerlike class with mutiple interface classes. In addition, by doing so, we can avoid copying objects unnecessarily. So what do we mean by saying copy an object? If an object x refers to an object y, does copying x cause y to be copied too ?

+
    +
  1. if y is a memer of x, the answer must be yes
  2. +
  3. if x is nothing but a pointer to y, the answer is no.
  4. +
+

This chapter defines three versions of our pointerlike class, each of which differs from the others in how it defines copying.

+

Handles that copy their objects

It is known that pointer is a primitive, low-level data structure. Working with pointers directly may leads to severe mistakes due to the fact that pointers are independent of the objects to which they point(Koenig and Moo 2000):

+

1. Copying a pointer doesn’t copy the corresponding object, leading to surprises if two pointers inadvertently point to the same object.

1
2
3
4
5
int* p = new int(10);   // p: pointer to an int object that has value 10
int* q = p; // q: points to the same int object
*q = 100; // if we modify the object pointed by q
cout << *p; // we inevitably changes the object pointed by p
// the output is 100

+

2. Destroying a pointer doesn’t destroy its object, leading to memory leaks.

1
2
3
void nameless(size_t n){
int* p = new int[n];// local variable p is destroyed when this function
} // finishes, however, the dynamically allocated // array still exists on the heap.

+

3. Deleting an object without destroying a pointer to it leads to a dangling pointer, which causes undefined behavior if the program uses the pointer.

1
2
3
4
5
int* p = new int(10);   // p: pointer to an int object that has value 10
int* q = p; // q: points to the same int object
delete p; // destroy the object pointed by p
p = nullptr; // p points to nowhere now
*q = 100; // undefined behavior as the object pointed by q has been destroyed.

+

4. Creating a pointer without initializing it leaves the pointer unbound, which also causes undefined behavior if the program uses it.

1
2
int* p;                 // unnitialized variable p, which is unbound to any object
*p = 100; // undefined behavior

+

The Student_info class allows us to use pointers without worrying about above problems. Now we still let Student_info to provide the interface, but makes the handle class be independent of the type of the object that it manages. The properties that our class will provide are :

+

1. A Handle is a value that refers to an object.

+

2. We can copy a Handle object.

+

3. We can test a Handle object to determine whether it is bound to another object.

+

4. We can use a Handle to trigger polymorphic behavior when it points to an object of a class that belongs to an inheritance hierarchy. That is, if we call a virtual function through our class, we want the implementation to choose the function to run dynamically, just as if we’d called the function through a real pointer.

+

Our Handle class will take over the memory management and therefore, we should attach only one Handle to any object, and we should not access the object directly through a built-in pointer. To tackle problems when using a built-in pointer,

+
    +
  1. When we copy a Handle object, we’ll make a new copy of the object so that each Handle points to its own copy, such as what the copy constructor does in the Student_info class, calling clone() to create a new object.
  2. +
  3. When we destroy a Handle, it will destroy the associated object, such as what the destructor does in the Student_info.
  4. +
  5. We allows users to create unbound Handles but we will throw an exception if the user attempts to access the object to which an unbound Handle refers. Users who want to avoid the exception can test to see whether the Handle is bound, for example, the operations defined in the Student_info class check whether the handle object was bound to a real object.
  6. +
+

A generic Handle class

Now, let’s write the Handle class:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
template <class T> class Handle{
public:
Handle(): p(0) {}
Handle(const Handle& s): p(0) {
if (s.p) p = s.p->clone();
}

Handle& operator=(const Handle& rhs){
if(&rhs != this){
delete p;
p = rhs.p ? rhs.p->clone() : 0;
}
return *this;
}
~Handle() { delete p; }

Handle(T* t): p(t) { }
operator bool() const { return p; } // type conversion
T& operator*() const;
T& operator->() const;
private:
T* p;
};

+

Firstly, we observe that the Handle is a class template and can accommodate to any type. For example, Handle holds a pointer to an object of Core type.

+

The default constructor initializes the pointer to a nullptr. The copy constructor lets the Handle object refers to a newly created object that has the same value as the object pointed by the passed argument. The operator= is samilar to the copy constructor except that it destroyes the original object pointed by the Handle object. The destructor is obvious. All these four members are defined exactly the same as those defined in the Student_info class.

+

The other constructor that takes an argument lets us to bind the pointer to an actual object:

1
Handle<Core> student(new Grad);

+

Finally, we define three operator functions: the first one operator bool() tests the value of a Handle, and returns true if the Handle is bound to an object, and false otherwise(in fact converts the Handle type to a bool type value); The other two deine operator* and operator-> which give access to the object bound to the Handle:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
template <class T>
T& Handle<T>::operator* () const{
if(p)
return *p;
else
throw runtime_error("unbound Handle");
}

template <class T>
T* Handle<T>::operator->() const {
if(p)
return p;
else
throw runtime_error("unbound Handle");
}

+

The operator* allows us to access *student.p by using *student. It yields a reference to the bound object.
The -> operator is used to access a member whose name appears in its right operand from an object named by its left operand. It returns a value that can be treated as a pointer. Therefore, if x is a value that defines operator->, then

1
x->y;

+

is equivalent to

1
(x.operator->())->y;

+

is equivalent to

1
x.p->y; // p is pointer data member, for example, p is Core*

+

By doing so, we can use the Handle object as if we are using a pointer to the associated object. Both operator* and operator-> yield either a reference or a pointer, through which we obtain dynamic binding. For example, if we execute student->grade(), we’re calling grade() through student.p, that is, a pointer. The particular version of grade to be run depends on the type of the object to which student.p points to. Similarly, if we execute (*student).grade(), we’re calling grade() through a reference to the object and so the implementation will decide which particular version of the function to call.

+

Using a generic Handle

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
int main(){
vector< Handle<Core> > students; // changed type
Handle<Core> record;
char ch;
string::size_type maxlen = 0;

// read and store the data
while(cin >> ch){
if (ch == 'U')
record = new Core; ( // allocate a Core object
else
record = new Grad; // allocate a Grad object
record->read(cin); // Handle<T>::->, then virtual call to read
maxlen = max(maxlen, record->name().size());
students.push_back(record);
}

// write the names and grades
for (vector< Handle<Core> >::size_type i = 0; i != students.size(); ++i){
// students[i] is a Handle, which we deference to call the function
cout << students[i]->name()
<< string(maxlen + 1 - students[i]->name().size(), ' ');
try{
double final_grade = students[i]->grade();
streamsize prec = cout.precision();
cout << setprecision(3) << final_grade
<< setprecision(prec) << endl;
}catch (domain_error e){
cout << e.what() << endl;
}
}
return 0;
}
+

We can rewrite our Student_info class to a pure interface class:

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
class Student_info{
Student_info () { } // calls default constructor of Handle<Core>
Student_info (std::istream& is) { read(is); }
// no copy, assign, or destructor: they're no longer needed

std::istream& read(std::istream);

std::string name() const {
if(cp)
return cp->name();
else
throw runtime_error("uninitialized Student");
}

double grade() const {
if(cp)
return cp->grade()
else
throw runtime_error("uninitialized Student");
}
static bool compare(const Student_info& s1, const Student_info& s2){
return s1.name() < s2.name();
}
private:
Handle<Core> cp;
};

Since the **Handle** class defines constructor, copy constructor, assignment operator, as well as destructor, we do not need to define these members for our **Student_info** again. Here we need one step more, that is, to redefine the **read** function:
```c++
istream& Student_info::read(istream& is){
char ch;
is >> ch;
if(ch == 'U')
cp = new Core(is); // implicitly converts from a Core* to a Handle<Core> through Handle::Handle(T*)
else // then assigns the value from the temporary Handle object to cp
cp = new Grad(is);

return is;
}

+

when we execute the cp = new Core(is), the right-hand side creates a new Core object from input stream, which we implicitly convert to a Handle using Handle(T*) constructor. That Handle value is then assigned to cp by calling the assignment operator. The assignment constructs and destroys an extra copy of the Core object that we created. The reason behind this is that copying or assigning a Handle object always makes a new copy of the object that the Handle points to. Doing so can avoid the dangling pointer as each Handle only points to its own object, however, may also make uncessary copies like above assignment operation.

+

Reference-counted handles

This section solves above problem by providing a Handle class that does not copy the underlying object when the Handle itself is copied. To avoid danglling pointer problem, we’ll need to free that object at the point when the last Handle that points to it goes away. We’ll use a reference count to keep track of how many objects refer to another object. Each time we create a new handle that refers to our target object, we increment the reference count object, while each time a referring object goes away we decrement the reference count. Finally, when the last referring object goes away, we know that it is safe to destroy the target object.

+
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
template <class T> class Ref_handle{
public:
// manage reference count as well as pointer
Ref_handle(): refer(new size_t(1)), p(0) {}
Ref_handle(T* t): refptr(new size_t(1)), p(t) {}
Ref_handle(const Ref_handle& h): refptr(h.refptr), p(h.p){
++*refptr;
}

Ref_handle& operator=(const Ref_handle&);
~Ref_handle();

// as before
operator bool() const { return p; }
T& operator*() const {
if(p)
return *p;
throw std::runtime_error("unbound Ref_handle");
}

T* operator->() const{
if(p)
return p;
throw std::runtime_error("unbound Ref_handle");
}

private:
T* p;
size_t* refptr; // newly added
};
+

Above code shows our Ref_handle class:

+
    +
  1. we add a new pointer, refptr, to to the new handle class to track the reference count
  2. +
  3. if we default construct a Ref_handle object or construct from an existed T, we initialized *refptr to 1
  4. +
  5. if we construct a Ref_handle object from another Ref_handle, we do not copy the underlying object but instead only copy the value of the pointer from the passed argument. Then, our Ref_handle object points to the same object as the passed argument. In addition, we let the refptr points to the counter object pointed by h.refptr, then increment the counter value by 1 as there is a new pointer Ref_handle.p points to the object pointed by h.p now.
  6. +
  7. the assignment operator also modifies the counter object instead of copying the underlying object:

    +
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    template <class T>
    Ref_handle<T>& Ref_handle<T>::operator=(const Ref_handle& rhs){
    ++*rhs.refptr;
    // free the left-hand side, destroy pointers if appropriate
    if(--*refptr == 0){
    delete refptr;
    delete p;
    }

    // copy in values from the right-hand side
    refptr = rhs.refptr;
    p = rhs.p;
    return *this;
    }
    +

    the assignment operation typically involves obliterating the value of the left-hand side operand. If the operand is a pointer, we execute delete p to free the space occupied by the object that pointed by p, if there is no other pointers points to the same object. Therefore, we executes delete p as well as delete refptr conditional on –*refptr == 0. If –*refptr == 0 is false, we do not execute delete operation, but we still need to decrement the counter object pointed by refptr, which has been done by executing –*refptr ==0. However, we also need to avoid self-assignment and hence we increment *refptr first.

    +

    The next step is to bind our Ref_handle to the object that pointed by the passed argument. Like what the copy constructor does, we copy pointers but don’t copy the underlying object.

    +
  8. +
  9. the destructor checks whether the Ref_handle object being destroyed is the last one bound to its T object:
    1
    2
    3
    4
    5
    6
    template <class T> Ref_handle<T>::~Ref_handle(){
    if (--*refptr == 0){
    delete refptr;
    delete p;
    }
    }
    +
  10. +
+

This version of *Ref_handle class works well for classes that can share state between copies of different objects, however, cannot provide valuelike behavior like the Handle class described in last section. It does avoid needless copying, however, avoid all copying even we want to copy the underlying data. Next, we discuss how to write a Handle that let us decide when to share data.

+

Handles that let you decide when to share data

Now we write our last version of generic handle class, which not only preserves the performance of Ref_handle but also provides the valuelike behavior of Handles. In general, the new handle class, named as Ptr, will copy the object if we are about to change the contents, but only if there is another handle attached to the same object.

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
template <class T> class Ptr{
public:
// new member to copy the object conditionally when needed
void make_unique(){
if(*refptr != 1){
--*refptr;
refptr = new size_t(1);
p = p ? p->clone() : 0;
}
}

// the rest of the class looks like Ref_handle except for its name
Ptr(): refptr(new size_t(1)), p(0) {}
Ptr(T* t): refptr(new size_t(1)), p(t) {}
Ptr(const Ptr& h): refptr(h.refptr), p(h.p) {
++*refptr;
}

Ptr& operator=(const Ptr&);
~Ptr();
operator bool() const { return p; }
T& operator*() const;
T* operator->() const;

private:
T* p;
size_t* refptr;
};

+

This new Ptr class has the mostly same members and implementations as the Ref_handle class, except that it defines a new make_unique function. The make_unique function calls the clone() function to copy the underlying object only in the condition that the reference count is not 1. More specific, if *refptr == 1, then it means that there is no any other Ptr objects are bound to the underlying object, and hence there is no need to do a underlying copy again;but if *refptr != 1, it means that there still other Ptr(s) are bound to the underlying object, and hence it is necessary to make our Ptr points to its own object to avoid bring changes to the object pointed by other Ptr(s). If we intend to add a function that can change the contents of the underlying object, we should call make_unique to make a copy of the underlying object.

+

An improvement on controllable handles

There is one problem when we use above Ptr to deal with some classes that do not have a member function clone(). In such case, we will define an intermediary global function that we can both call and create:

1
2
3
4
template <class T> 
T* clone(const T* tp){
return tp->clone();
}

+

Then we can change our make_unique member to call it

1
2
3
4
5
6
7
8
template <class T>
void Ptr<T>::make_unique(){
if(*refptr != 1){
--*refptr;
refptr = new size_t(1);
p = p ? clone(p) : 0; // call global version of clone
}
}

+

The new clone function does’t make any change to the make_unique function and hence works well for our Student_info class. Another example, the Vec doesn’t provide a clone function, how can we define an intermediary function to let the Ptr< Vec > work?

1
2
3
4
template <>
Vec<char>* clone(const Vec<char>* vp){
return new Vec<char>(*vp);
}

+

The novelty here is template<>, which indicates a function is a template specialization. The template specialization means that the template is a particular version of a template function for the argument type. If we pass clone a Vec*, the compiler will use this specialized version of clone. If we pass other types of pointers, it will instantiate the general template form of clone.

+ + +
+ + + + + + + + + + + +
+ + + +
+ + + +
+ +
+
+ + +
+ + + + + + + + + +
+ + + + + + + + + +
+
+ + + + +
+ + +
+ + + + +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/09/05/2018/Sorting-Algorithms-C-Implementations/index.html b/09/05/2018/Sorting-Algorithms-C-Implementations/index.html new file mode 100644 index 00000000..e929b1d5 --- /dev/null +++ b/09/05/2018/Sorting-Algorithms-C-Implementations/index.html @@ -0,0 +1,797 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Sorting Algorithms C++ Implementations-Selection Sort | Liam's Blog + + + + + + + + + + + + + + + + + + +
+
+ + + +
+
+
+
+ + +
+ + + + + + + + +
+ + + +
+ + + + + + + +
+ + + +

Sorting Algorithms C++ Implementations-Selection Sort

+ + + +
+ + + + + +
+ + + + + +

Selection Sort

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
/*-----------------------------------------------------------------------------
* Created on: 9 May 2018 || Author: Liam
*-----------------------------------------------------------------------------
* This file contains three generic functions that implement
* the selection sort algorithm, and a test program
*
* Logical steps:
* 1. select the minimum value from the sequence
* 2. put the minimun value in the first position
* 3. ignore the first position and sort the remaining
* sequence by repetitively executing step 1 and 2
* till the last number
*
* Complexity analysis:
* time complexity: Best, Average and Worst-case = O(n^2)
* auxiliary space: worst-case = O(1)
*
*-----------------------------------------------------------------------------
*/
+

Implementations

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
#ifndef SORTINGALGORITHMS_H_
#define SORTINGALGORITHMS_H_

#include <cstddef>
#include <iterator>
#include <algorithm>

// built-in array-based version
template <typename T>
void SelectionSort(T* p, std::size_t n){
if(n > 0){
for(std::size_t i = 0; i != n-1; ++i){
std::size_t imin = i;
for(std::size_t j = imin+1; j != n; ++j){
if(p[i] > p[j])
imin = j;
}
T temp = p[i];
p[i] = p[imin];
p[imin] = temp;
}
}
}

// iterator-based version STL style (C++11)
template <typename ForwardIterator>
void SelectionSort(ForwardIterator begin, ForwardIterator end){
for (; begin != end; ++begin){
auto imin = std::min_element(begin, end);
if(imin != begin)
std::iter_swap(begin, imin);
}
}

// iterator-based version with comparator
template <typename ForwardIterator, typename Comparator>
void SelectionSort(ForwardIterator begin, ForwardIterator end,
Comparator comp){
for (; begin != end; ++begin){
auto imin = std::min_element(begin, end, comp);
if(imin != begin)
std::iter_swap(begin, imin);
}
}
#endif /* SORTINGALGORITHMS_H_ */

+

Test Program-main.cpp

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
65
66
67
68
69
70
71
#include <iostream>	// std::cout, endl
#include <string> // std::string
#include <list> // std::list
#include <vector> // std::vector
#include "SortingAlgorithms.h"

using std::cout; using std::cin;
using std::endl; using std::list;
using std::vector; using std::string;

// struct defined for testing
struct student{
string name;
int age;
};

// comparator 1
template<class T>
bool compare_age(const T& x, const T& y){
return x.age < y.age;
}

// comparator 2
template<class T>
bool compare_name(const T& x, const T& y){
return x.name < y.name;
}

int main(){
// test 1: array-based version
double arr[10] = {10, 9, 8, 7, 6, 5, 4, 3, 2, 1};
SelectionSort(arr, arr+ 10);
cout << "Sorted array: ";
for(int i = 0; i != 10; ++i)
cout << arr[i] << " ";
cout << "\n";

// test 2: iterator-based version
string str("eclipseworkspace");
SelectionSort(str.begin(), str.end());
cout << "Sorted string " << str << "\n";

list<int> l{10, 9, 8, 7, 6, 5, 4, 3, 2, 1};
SelectionSort(l.begin(), l.end());
cout << "Sorted list: ";
for (auto i: l)
cout << i << " ";

cout << endl;

// test 3: user-defined comparator
vector<student> students;
while(cin){
student temp;
cin >> temp.name >> temp.age;
if(cin)
students.push_back(temp);
}

SelectionSort(students.begin(), students.end(), compare_age<student>);
cout << "Sorted vector according to age:\n";
for (auto i: students)
cout << i.name << " " << i.age << "\n";

SelectionSort(students.begin(), students.end(), compare_name<student>);
cout << "Sorted vector according to name:\n";
for (auto i: students)
cout << i.name << " " << i.age << "\n";

return 0;
}

+

Outputs:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
Sorted array: 1 2 3 4 5 6 7 8 9 10 
Sorted string acceeeiklopprssw
Sorted list: 1 2 3 4 5 6 7 8 9 10
John 18
Mike 16
Liam 23
Anna 26
Bobo 21
Sorted vector according to age:
Mike 16
John 18
Bobo 21
Liam 23
Anna 26
Sorted vector according to name:
Anna 26
Bobo 21
John 18
Liam 23
Mike 16

+ + +
+ + + + + + + + + + + +
+ + + +
+ + + +
+ +
+
+ + +
+ + + + + + + + + +
+ + + + + + + + + +
+
+ + + + +
+ + +
+ + + + +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/10/04/2018/C-Managing-memory-and-low-level-data-structures/index.html b/10/04/2018/C-Managing-memory-and-low-level-data-structures/index.html new file mode 100644 index 00000000..a673eeb2 --- /dev/null +++ b/10/04/2018/C-Managing-memory-and-low-level-data-structures/index.html @@ -0,0 +1,919 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + C++ - Managing memory and low-level data structures | Liam's Blog + + + + + + + + + + + + + + + + + + +
+
+ + + +
+
+
+
+ + +
+ + + + + + + + +
+ + + +
+ + + + + + + +
+ + + +

C++ - Managing memory and low-level data structures

+ + + +
+ + + + + +
+ + + + + +

Pointers

Introduction

pointer is a compound type that points to another base type object. A pointer is a value that represents the address of the object that it points to. What is the address of an object? The address of an object denotes the part of the computer’s memory that contains the object. For example, if x is an object, then &x is the address of x and & is the address operator. Further, if p is a pointer, then *p is the value of the object that p points to and is the *deference operator.

+

We are familar with these two operators& and *, however, may feel comfused about their meanings. Generally, these rules can be summarized as follows:

+
    +
  1. when the & is used as part of a declaration, e.g.

    +
    int i = 10;
    +int &x = i;
    +

    & follows a type and x is a reference.

    +
  2. +
  3. when the & is used as in an expression, e.g.

    +
    int *p = &i;
    +

    & acts as an address operator.

    +
  4. +
  5. when the * is used as part of a declaration, e.g.

    +
    int *p = &i;
    +

    * follows a type and p is a pointer.

    +
  6. +
  7. when the * is used in an expression, e.g.

    +
    int i = *p;
    +*p = i;
    +

    it acts as an deference operator and yields the value of the object that p points to.

    +
  8. +
+

It can be observed that a pointer can indirect access the value an object like a reference. However, a reference itself is not an object while a pointer itself is an object. From the third rule above, we know how to define a pointer. For example

1
int *p;

+

p is a pointer that points to int type object. In other words, p has type int*. As other built-in types, a pointer might point to an unknown object unless we initialize it. Typically,we can initialize a pointer as a null pointer which means that the pointer does’t point to any object. There are several ways to do this:

+
1
2
3
int *p = 0;         // the constant 0 can be converted to a pointer type
int *p = nullptr; // c++11 supports
int *p = NULL; // must include header <cstdlib>
+

These three statements lead to an equivalent result. If we want to assign other values to a pointer, typically we uses the address-of operator:

1
2
3
4
5
int i = 10;
int j = 20;
int *p = &i; // p points to i
int *q = &j; // q points to j
p = q; // p points to j now

+

Pointers to functions

It is known that functions are not objects and hence there is no way to copy or assign them, or to pass them as arguments directly. But we do “pass” functions as arguments in previous chapter, for example, the write_analysis function (see declaration below) takes function as its second parameter.

1
2
3
4
void write_analysis(std::ostream &out, const std::string &name,
double analysis(const std::vector<Student_info> &),
const std::vector<Student_info> &did,
const std::vector<Student_info> &didnt);

+

In fact this is achieved by taking the address of a function, that is, the pointer to the function. Let’s go into some details about the pointers to functions.

+

The syntax to define a pointer to function is, for example

1
int (*fp)(int);

+

Then, if we deference fp and call it with int argument, the result is an int type value.
Another fact is that what we can do with a function is to take its address or call it. Therefore, when we use a function but is not to call it, we are assumed to be take its address, either using & or not. For example,

+
1
2
3
4
int next (int n)
{
return n + 1;
}
+
1
2
3
// these two statements are equivalent
fp = next;
fp = &next;
+

Further, when we call the next function with an int variable i,

1
2
3
// these two statements are equivalent
int x = fp(i);
int x = (*fp)(i);

+

Finally, when we have a function that takes another function as a parameter, the compiler will translate the parameter to be a pointer. Taking the write_analysis as an example, the parameter

1
double analysis(const std::vector<Student_info> &)

+

is in fact equivalent to

1
double (*analysis)(const std::vector<Student_info> &)

+

As shown below, when we “pass” another function as an argument, analysis points to a function named median_analysis.

1
analysis = median_analysis; // == analysis = &median_analysis

+

What if we want to write a function that returns a function pointer?
The simplest way is to use type alias

1
typedef double (*analysis_fp) (const vector<Student_info>&);

+

analysis_fp is the name of the type of a pointer to function. Then

1
2
// get_analysis_ptr returns a pointer to an analysis function
analysis_fp get_analysis_ptr();

+

declares a function get_analysis_ptr() that returns a pointer to an analysis function. This statement is equivalent to

1
double (*get_analysis_ptr()) (const vector<Student_info> &);

+

From this, we can see that get_analysis_ptr has a parameter list and hence it is a function. In addition, there is an * before it, which indicates that the function returns a pointer. Furthermore, the returned pointer also has a parameter list and hence the returned pointer points to a function that takes parameter const vector & and returns a double type value.

+

Now let’s look at an example

1
2
3
4
5
6
7
template <class In, class Pred>
In find_if(In begin, In end, Pred f)
{
while (begin != end && !f(*begin))
++begin;
return begin;
}

+

Suppose we have a predicate function

1
2
3
4
bool is_negative(int n)
{
return n < 0;
}

+

Let’s instanitialize the template with a vector named v

1
vector<int>::iterator i = find_if(v.begin(), v.end(), is_negative)

+

We use is_negative instead of &is_negative due to that the name of the function turns into a pointer to the function autimatially.

+
1
2
3
4
5

# Arrays
**Array** is part of the core language rather than part of the standard library. An **array** is a kind of container that similar to a **vector**, but has fixed length. When we create an **array** object, we should specify the length of the array. For example, we can create an array that contains three elements
```c++
double coords[3];
+

Alternatively, we can use a const object to denote the size of the array

1
2
const size_t NDim = 3;
double coords[NDim];

+

size_t is a fundamental unsigned integer type that can be used to represent the size of an array. It is defined in the header . The reason to use size_t is that array is not a class type and has no member function like size_type. It is worth noting that we use const to ensure Ndim is fixed and known at compilation time.

+

coords is the name of the array, and in fact it is a pointer that points to the first element of the array. Hence, we can assign a value to the first element through

1
*coords = 1.5; // set the initial element of coords to 1.5

+

Pointer arithmetic

Another fact is that a pointer is a random access iterator. Therefore, we can access the mth element (if available) through

1
*(coords + m)

+

The pointer that points to one past the last element is coords + NDim. In other words, [coords, coords + NDim) denotes the range of address of the array.

+

Now, if we want to copy all elements of the array into a vector named v, we can use the copy algorithm as shown as follows

1
2
vector<double> v;
copy(coords, coords + NDim, back_inserter(v));

+

Alternatively, we can construct v directly as a copy of the elements in coords using

1
vector<double> v(coords, coords + NDim);

+

C++ 2011 library provides begin and end functions to get the initial pointer and the off-the-end pointer:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
int *beg = begin(coords);   // points to the initial elements
int *last = end(coords); // points to one past the last element
···

Similar to **difference_type**, **ptrdiff_t** is an signed itegeral type that represents the distance between two **pointers**. **ptrdiff_t** is also defined in the header <cstddef>.

## Indexing
It is known now that **array** supports random access iterators and naturally supports indexing. Therefore, the **n**th emelent (if available) is **coords[n]** and ***coords = coords[0]**.

## Array initialization
Array supports list initialization, for example
```c++
// these two statements have same effect
int a[10] = {1, 2, 3, 4, 5, 6, 7, 8, 9, 0};
int a[] = {1, 2, 3, 4, 5, 6, 7, 8, 9, 0};

+

If we don’t specify the number of elements contained in the array, compiler will infer the number from the number of the supplied the initializers. But if we specify the number exactly, the number of initializers must not exceed the specified size. So, what if we specifies the size but provides initializers less than the specified number?

1
int a[10] = {1, 2, 3};

+

Then, the compiler will value initialize the rest elements. In this case, the elements have built-in type int and hence are set to 0.

+

String literals revisited

As we mentioned earlier, String literals are not strings. In fact, a string literal is an array of const char with one more element, i.e. ‘\0’ ,than the number of characters in the literal. ‘\0’ is a null character. Therefore, when we create a string literal, we should specify the size one larger than it should be for the purpose of holding the extral null character. For example, a string literal Hello is created through

1
2
3
// two ways to initialize a string literal
const char hello[] = {'H', 'e', 'l', 'l', 'o', '\0'};
const char hello[6] = "Hello";

+

The null character marks the end of the literal. When we want to get the number of characters excluding the null character, we can use the function strlen defined in . The strlen might has following implementation

1
2
3
4
5
6
7
8
// Exanple implementation of standard-library function
size_t strlen(const char *p)
{
size_t size = 0;
while (*p++ != '\0')
++size;
return size;
}

+

Now, if we want to copy the string literal Hello into a string type object, we can use

1
2
3
4
// three equivalent ways to copy the string literal into a string
string s(hello); // variable hello represents "Hello"
string s("Hello");
string s(hello, hello + strlen(hello)); // treats hello as the begin iterator

+

Initializing arrays of character pointer

Essentially, a string literal is just a convenient way of writing the address of the initial character of a null-terminated sequence of characters. Now let’s look at an example that shows how to generate an appropriate letter grade according to a numeric grade. The letter grades and numeric grades have following mapping relations:

1
2
If the grade is at least  97  94  90  87  84  80  77  74  70  60  0
then the letter grade is A+ A A- B+ B B- C+ C C- D F

+

The program is shown below and let’s analyse it step by step.

+
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
string letter_grade(double grade)
{
// range posts for numberic grades
static const double numbers[] = {97, 94, 90, 87, 84, 80, 77, 74, 70, 60, 0};

// name for the letter grades
static const char* const letters[] = {"A+", "A", "A-", "B+"
"B", "B-", "C+", "C", "C-", "D", "F"};

// compute the number of grades given the size of the array
// and the size of a single element
static const size_t ngrades = sizeof(numbers)/sizeof(*numbers);

// given a numberic grade, find the associated letter grade
for (size_t i = 0; i < ngrades; ++i)
{
if (grade >= numbers[i])
return letters[i];
}
return "?\?\?";
}
+

The function itself takes a double type value (i.e. a numeric grade), and returns a string (i.e. a letter grade). The first step is to construct two objects that hold the numeric grades and the letter grades, respectively. It is simple if we use vector or list or map as we know the numeric grade int type and the letter grade is string type. What if we use array? There is nothing new if we use string type, for example,

1
static const string letters[] = {"A+", "A", "A-", "B+", "B", "B-", "C+", "C", "C-", "D", "F"};

+

But if we treat each letter grade as a a sequence of characters, we can store them as the program shows

1
static const char* const letters[] = {"A+", "A", "A-", "B+", "B", "B-", "C+", "C", "C-", "D", "F"};

+

It can be observed that:

+
    +
  1. the static means that the object is initialized only once and exists in the whole process till the program terminates.
  2. +
  3. the letters is an array of const pointers to const char. Each element is a string literal, i.e. an alternative way of writing the address of the initial character. Therefore, each element is in fact can be regarded as a pointer that points to the initial letter of the string literal. For example, letters[0] gives the first letter grade A+ while *(letters[0]) gives the initial letter of the first letter grade as letters[0] is a pointer as well.
  4. +
  5. The first const means that the array elements are constant. The second const means that the address are constant.
  6. +
  7. the next statement introduces sizeof which is a function that returns a size_t type value indicating how much memory an object occupied. Therefore, sizeof(numbers) gives the number of bytes that the object numbers while sizeof(*numbers) yields the number of bytes that each element consumes. The result of the division yields a value that is the number of elements contained in the object (i.e. array numbers).
  8. +
  9. finally, using a for statement a corresponding letter grade according to the input. If there exists such a grade, return it, otherwise, return ???.
  10. +
+

Arguments to main

As mentioned in chapter 0, the main function can take arguments like other functions if it defines parameters. A conventional way is to pass a sequence of stings to main as an argument. For example, when we pass say Hello, world as an argument, the program give outputs

1
Hello, world

+

How to achieve this? This is done by giving two parameters:

+
    +
  1. an int type parameter named argc which is a value that denotes the number of pointers that pointed by argv.
  2. +
  3. another one is named as argv which is a pointer to a pointer to char. For example, the pointer letters described above. letters points to the initial element, that is, the pointer to char A as the pointer to a string literal points to the initial char of the sequence. Therefore, we can define this parameter as
  4. +
+
1
char* argv[]
+

or

+
1
char** argv
+

These two expression are equivalent. Now, let’s look at the main with arguments argc and argv:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
int main(int argc, char** argv)
{
// if there are arguments, write them
if(argv > 1)
{
// declare i outside the for because we need it after the loop
int i;

// write all but the last entry and argv[i] is a char*
for (i = 1; i < argc - 1; ++i)
cout << argv[i] << " ";

// write the last entry but not a space
cout << argv[i] << endl;
}
}

+

Assuming we pass Hello, world as an argument, argc will be 3 (why ?) and argv points to the first element, that is, another pointer to “Hello,” that is, character ‘H’. argv[0] can be regarded as the name (i.e. pointer again) of the first string literal, and hence we can access each string literal through argv[i], where i < argc.

+

You might wonder that why argc is 3 and why we use range [1, argv) rather than [0, argv) as we did before? This is because when we pass arguments, the program name will be automatically passed as one element. Specifically, if we pass sequence please give me a number, there will be 5 string literal in total, but argc = 6 because:

+
1
2
3
4
5
6
argv points to pointer argv[0] points to "program name"
argv[1] points to "please"
argv[2] points to "give"
argv[3] points to "me"
argv[4] points to "a"
argv[5] points to "number"
+

This shows why we use the range [1, argv) to access each element.

+

Reading and writing files

By now, we are familar with four iostream class objects cin, cout, clog, and cerr. This part introduces another IO facilitiy ifstream and ofstream class to deal with multiple input and output files. Specifically, the ofstream class object allows us to write data into a file, while iftream allows us to read data from a file. We can use these objects like what we operate on cin and cout such as that they also support << and >> operators as well as getline.

+

When defining an ifstream or oftream object, we can associate the object with a file that we intend to write or read. Naturally we supply the file name which can be a string (C++11) or a string literal. This can be done either on construction or by calling member function open. For example,

1
2
3
// two ways to open a file named s
ifstream in(s);
ifstream.open(s);

+

The below program shows how to copy the contents of a file named in to a file named out.

1
2
3
4
5
6
7
8
9
10
int main()
{
ifstream infile("in");
ofstream outfile("out");

string s;
while (getline(infile, s))
outfile << s << endl;
return 0;
}

+

The first statement creates an ifstream object infile and associates it with a file named in. Similarly, the second statement creates an ofstream object outfile and associates it with a file named out. Both file names are string literals, i.e. pointers to the initial character of null-terminated array. If file in doesn’t exist, there won’t be a file in being created. However, if out doesn’t exist, it will be create to hold the outputs.

+

If we don’t want to use string literal as name, one solution is to store the name in a string and use member function c_str to get the pointer to the array that contains a null-terminated sequence of characters (i.e., a C-string) representing the current value of the string object_ Reference to std::string::c_str. For example, file is a string variable that contains the name of a file to be read, we then associate the file with a ifstream object through

1
ifstream infile(file.c_str());

+

Finally, let’s look at another program that produces a copy of all files whose names are given as arguments to main.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
int main(int argc, char** argv)
{
int fail_count = 0;
// for each file in the input list
for (int i = 1; i < argc; ++i)
{
ifstream in(argv[i]);

// if it exits, write its contents, otherwise generate an error message
if(in)
{
string s;
while(getline(in, s))
cout << s << endl;
}
else
{
cerr << "cannot open file " << argv[i] << endl;
++fail_count;
}
}
return fail_count;
}

+

The program logic is simple: first get a name from the arguments argv, then open a same name file; if the file exists, the contents of it will be written on the output device, but if there is no such file, count and record such case with a variable fail_count; finally, returns fail_count, by then, the value of fail_count represents the number of non-existent files; if fail_count is not 0, it indicates that the program terminates abnormally.

+

It is worth noting that when a ifstream object fails to associate the corresponding file, the state of the input stream is set to fail. Therefore, the if condition if(in) is very useful and helpful for us to check whether our operations on the file are valid.

+

Three kinds of memory management

The first kind is called automatic memory management: the system allocates memory for a local variable when it executes the variable’s definition, and deallocates that memory automatically at the end of the block that contains the definition. Therefore, we should note that once a variable has been dealloated, any pointers to it are invalid. For example,

1
2
3
4
5
6
7
// this function deliberately yields an invalid pointer
// it is intended as a negative example-don't do this!
int* invalid_pointer()
{
int x;
return &x; // instant disaster
}

+

This function intends to return the address of local variable x. However, the return statement ends the execution of the block and hence deallocates the memory of x, resulting that &x is invalid. To solve this problem, we can use another kine memory management, i.e. statically allocated.

1
2
3
4
5
6
// This function is completely legitimate
int* pointer_to_static()
{
static int x;
return &x;
}

+

When a local object is specified as static, it is created only the first time when its definition is executed and won’t be destroied until meet the end of the program. Therefore, the function returns a valid pointer of object x.

+

The shortcoming of this kind memory management is that each call of such function obtains the same pointer. If we want to get a different pointer each time, we can choose an alternative memory management, the dynamic allocation.

+

If T is an object type, then new T is an expression that

+
    +
  1. allocates an T type object
  2. +
  3. default-initializes the object
  4. +
  5. yields a pointer to this newly allocated object
  6. +
  7. the object exists since it is created till either the end of the program or the execution of delete p, where p is a copy of the pointer returned by the expression.
  8. +
+

For example,

1
2
// allocate an unnamed object of type int, and initialize it to 42
int *p = new int(42);

+

We can change the value of the object by manipulating the pointer p

1
2
// change the object to 43
++*p;

+

If we want to delete it, we do

1
2
// after this execution, the occupied memory is freed and p becomes an invalid pointer
delete p;

+

Now let’s revisit the problem we met above and write a function that can return different pointer each time

1
2
3
4
int *pointer_to_dynamic()
{
return new int(0);
}

+

This function returns a brand new pointer each time. But do not forget to release the memory through delete p.

+

Allocating and deallocating an array

By analogy, we can allocate an array that contains T type values.

1
T* p = new T[n];

+

n is a non-negative integral value and new T[n] allocates an array of n objects of type T. There is also a returned pointer that points to the initial element of the array. Each object of the array is default-initialized. This means that if T is a built-in type, then the elements are undefined while if T is a class type, then the elements are initialized by the default constructor defined in that class type.

+

If n equals to 0, then the new return a valid off-the-end pointer as there is no element contained in the array. To deallocate the memory, we use delete[] p. Here is an example

1
2
3
T* p = new T[n];
vector<T> v(p, p + n); // copy elements in the array to vector
delete[] p;

+

The new allocates an array and stay around until that the program terminates or executes the delete[]. Before deallocating the array, the system destroys each element in reverse order. Let’s look at a function

1
2
3
4
5
6
7
8
9
10
char* duplicate_share(const char* p)
{
// allocate enough space; remember to add one for the null
size_t length = strlen(p) + 1;
char* result = new char[length];

// copy into our newly allocated space and return pointer to first element
copy(p, p + length, result);
return result;
}

+

This function takes a pointer to char as an argument and returns a point to char. It actually copy all chars from one string literal into a new string literal. Firstly, it allocates memory for the new array of chars, then apply copy algorithm to copy each char in the original array into the newly created array. Noting that strlen gives the number of non-null characters contained in a string literal. However, when we allocate the memory for a new string literal, we need to add one position more for holding the null-character. As for the deallocation of dynamic allocation, we will discuss more in next chapter.

+
+

Test examples

Now I test severl programs analysed above. The first program tests arguments to main and letter_grade function.

+

Test_1.cpp

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>		// to get the declaration of cout, endl
#include <string> // to get the declaration of string
#include <cstddef> // to get the declaration of size_t

using std::cout; using std::endl;
using std::string; using std::size_t;

// function declaration
string letter_grade(double);

// main function with non-empty parameter list
int main(int argc, char** argv)
{
// if there are arguments, write them
if(argc > 1)
{
// declare i outside the for because we need it after the loop
int i;

// write all but the last entry and argv[i] is a char*
for (i = 1; i < argc - 1; ++i)
cout << argv[i] << " ";

// write the last entry but not a space
cout << argv[i] << endl;
}

cout << "my letter grade is: " << letter_grade(75) << endl;

return 0;
}

string letter_grade(double grade)
{
// range posts for numberic grades
static const double numbers[] = {97, 94, 90, 87, 84, 80, 77, 74, 70, 60, 0};

// name for the letter grades
static const char* const letters[] = {"A+", "A", "A-", "B+",
"B", "B-", "C+", "C", "C-", "D", "F"};

// compute the number of grades given the size of the array
// and the size of a single element
static const size_t ngrades = sizeof(numbers)/sizeof(*numbers);

// given a numberic grade, find the associated letter grade
for (size_t i = 0; i < ngrades; ++i)
{
if (grade >= numbers[i])
return letters[i];
}
return "?\?\?";
}

+

Test Results

1
2
3
4
5
6
7
8
9
Inputs

command line arguments: please tell me the true
numeric grade: 75

Outputs

please tell me the truth
my letter grade is: C-

+

The second program produces a copy of all files whose names are given as arguments to main.

+

test_2.cpp

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
#include <iostream>		// to get the declaration of cout, endl, cerr
#include <string> // to get the declaration string getline
#include <fstream> // to get the declaration of ifstream

using std::cout; using std::endl;
using std::cerr; using std::string;
using std::getline; using std::ifstream;

int main(int argc, char** argv)
{
int fail_count = 0;
// for each file in the input list
for (int i = 1; i < argc; ++i)
{
ifstream in(argv[i]);

// if it exits, write its contents, otherwise generate an error message
if(in)
{
string s;
while(getline(in, s))
cout << s << endl;
}
else
{
cerr << "cannot open file " << argv[i] << endl;
++fail_count;
}
}
return fail_count;
}

+

I firstly create a file named in and write following sequences into it

1
2
3
what are you going to do
to be or not to be
that is a question

+

Then I set the commond line arguments as: please tell me the truth in
Once I click the run button, the program below gives following results

1
2
3
4
5
6
7
8
cannot open file please
cannot open file tell
cannot open file me
cannot open file the
cannot open file truth
what are you going to do
to be or not to be
that is a question

+

The last program tests that we can use the duplicate_share function to produce a copy of a sequence of characters.

+
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
#include <iostream>	// to get the declaration of cout,
#include <cstring> // to get the declaration of strlen
#include <cstddef> // to get the declaration of size_t
#include <algorithm>// to get the declaration of copy

using std::cout; using std::endl;
using std::strlen; using std::size_t;
using std::copy;

char* duplicate_share(const char* p)
{
// allocate enough space; remember to add one for the null
size_t length = strlen(p) + 1;
char* result = new char[length];

// copy into our newly allocated space and return pointer to first element
copy(p, p + length, result);
return result;
}

int main()
{
// constructe an array
char s[] = "computational";

// get a copy
char* s_copy = duplicate_share(s);

// verify the copy
cout << s_copy << endl;
}
+

The program works as we expected and gives results

1
computational

+
+ + +
+ + + + + + + + + + + +
+ + + +
+ + + +
+ +
+
+ + +
+ + + + + + + + + +
+ + + + + + + + + +
+
+ + + + +
+ + +
+ + + + +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/11/02/2018/C-Working-with-strings/index.html b/11/02/2018/C-Working-with-strings/index.html new file mode 100644 index 00000000..7ecfe4f9 --- /dev/null +++ b/11/02/2018/C-Working-with-strings/index.html @@ -0,0 +1,861 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + C++ - Working with strings | Liam's Blog + + + + + + + + + + + + + + + + + + +
+
+ + + +
+
+
+
+ + +
+ + + + + + + + +
+ + + +
+ + + + + + + +
+ + + +

C++ - Working with strings

+ + + +
+ + + + + +
+ + + + + +

Variables, Initialization & Declarations

Conventionally, I present the program that provided in Accelerated C++: Practical Programming by Example here:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
// ask for a person's name, and greet the person
#include <iostream>
#include <string>

int main()
{
// ask for the person's name
std::cout << "Please enter your first name: ";

// read the name
std::string name; // define name
std::cin >> name; // read into

// write a greeting
std::cout << "Hello, " << name << "!" << std::endl;
return 0;
}

+

The program is based on Hello, world! and is improved to say Hello to anyone you specified. It asks you to input a name (e.g. Batman) and then output as follows:

1
Hello, Batman!

+

Variables

To realize this function, a variable is defined to hold input. According to Koenig (2000), “a variable is an object that has a name” while “an object is a part of the computer’s memory that has a type”. Therefore, a variable should have:

+
    +
  1. name: for identifying the object that is needed to be manipulated.
  2. +
  3. type: determines the size and layout of the variable’s memory, range of values that can be stored within the memory, and operations that can be applied to the variable.
  4. +
+

In this case, the variable is named name and its type is std::string. The std:: means that the string type is defined in namespace std. Therefore, we need to include the standard header string as same as include the header iostream at the begining of this program. This statement also indicates the syntax of defining variables: a type specifier followed by one name (or more names sepreated by commas), and ends with a semicolon.

+

As this program shows, the variable name is defined within the function body and hence it is a local variable, i.e. the variable is only valid when the function body is executed. Once the computer reachers the }, it destroys the variable name and returns the memory that the variable occupied during its lifetime.

+

Initialization

An object is initialized and gets a specified value (i.e. initializer) when it is created. C++ defines several types of initializations. The example provided above uses default initialization in defining the variable name.

+

Default initialization

The default initialization means that no initializer is applied to a variable when it is defined. In the case of default initialization, the variable gets a default value which depends on its type and may also depends on where the variable is defined. Note that the default value of an object of built-in type, e.g. arithmetic type, depends on where it is defined:

+
    +
  • variables defined outside any function body are initialized to zero (or null character for char type?).
  • +
  • in general, variables of built-in type defined inside a function are uninitialized.
  • +
+

For example, I write a program as below for testing:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
#include<iostream>
using std::cout;
using std::endl;

int i; bool h; char m;

int main()
{
int j; bool k; char n;
cout << i << ' ' << j << endl;
cout << h << ' ' << k << endl;
cout << m << ' ' << n << endl;;
return 0;
}

+

The results is:

1
2
3
0 4201179
0 0
a

+

with warnings:

1
2
3
'j' is used uninitialized in this function [-Wuninitialized]
'k' is used uninitialized in this function [-Wuninitialized]
'n' is used uninitialized in this function [-Wuninitialized]

+

Clearly, it will result severe errors if we try to access the value or copy the value of such uninitialized variables.

+

Each class defines different ways to initialize objects of the class type. If default initialization is allowed, the default value of an object is determined by the class. For example, the default initialization of a string leads to an empty string (i.e. no characters). In the first program, the variable name is the empty string.

+

Copy & Direct initialization

Another way to initialize variables is using =, that is copy initialize by copying the initializer on the right side into the created object. For example:

1
2
3
int i = 10;
char j = 'a';
string s1 = "Hello";

+

The values used to initialize objects can also be expressions:

1
2
int i = 10;
int j = i*10;

+

The copy initialization seems like assignment, however, is completely different. As Lippman etc. (2012) point out: “Initialization happens when a variable is given a value when it is created. Assignment obliterates an object’s current value and replaces that value with a new one.”

+

We can also initialize above objects without using “=”:

1
2
3
int i(10);
char j('a');
string s1("Hello");

+

This way is named direct initialization. The string type variables can also be initialized using an alternative form:

1
2
3
4
5
6
7
8
// direct initialization, s1 is aaaaa
string s1(5, 'a');
// copy initialization, s2 is aaaaa
string s2 = string(5, 'a');

// decomposition of s2
string s0(5, 'a'); // s0 is aaaaa
string s2 = s0; // copy s0 into s2

+

The differences between copy initialization and direct initialization will not be disscussed until chapter 9;

+

List initialization

The C++ 11 standards supports an alternative form for the copy initialization and direct initialization using curly braces. For example:

1
2
3
4
int i = {10}; // same as _int i = 10;_
int j{10}; // same as _int j(10);_
string s1 = {"Hello"}; // same as _string s1 = "Hello";_
string s2 {"Hello"}; // same as _string s2("Hello");_

+

This form is known as list initialization. The major difference between list initialization and above methods is that list initialization doesn’t allow narrowing for the conversion of built-in type variables. To show this property, I performed an experiment using following codes:

+
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
#include<iostream>
using std::cout;
using std::endl;

int main()
{
double i = 10.9876;
int num1 = i;
int num2(i);
int num3{i};

cout << num1 << endl; // ok but result is truncated: 10
cout << num2 << endl; // ok but result is truncated: 10
cout << num3 << endl; // warning: narrowing conversion of 'i' from 'double' to 'int' inside { }
return 0;
}
+

Hence, list initialization is a more reliable approach compared with other alternatives.

+

Declarations

As analysed above, the definition of a variable requires type, name and initializers. For convenience, C++ supports separate compilation, that is, allows a program to be split into several files and compiled independently. In fact, we have already used the separate compilation in previous examples, such as the usage of IO system. The std::cout is defined in the header iostream but can be used in our programs through simply including the header file. To realize this function, a variable needs declaration which requires stating the type and name. The difference between declaration and definition is that the variable may be explicitly initialized in its definition. To declare a a variable only, we need add extern and don’t explicitly initialize it:

1
2
3
extern int i; // declares but not defines i
int i; // declares and defines i
extern int i = 10; // declares and defines i

+

But it should be noted that(Lippman etc. 2012):

+
    +
  • It is an error to provide an initializer on an extern inside a function.
  • +
  • Variables must be defined exactly once but can be declared many times.
  • +
+

Operations on strings

Reading & Writing strings

To read the inputs into name, this program uses std::cin and operator >>. It will discard whitespace characters such as space, tab and backspace, and then reads chars into name until it encounters another whitespace. For example, following three piece of inputs yield same outputs when we run the program:

1
2
3
Bruce
[a tab space]Bruce
Bruce Lee

+

Results:

1
2
3
Hello, Bruce!
Hello, Bruce!
Hello, Bruce!

+

The IO library accumulates and stores the characters using an internal data structure named buffer, and flushes the buffer by writing the contents to the output device only when necessary. There are three events that cause the system to flush the buffer:

+
    +
  • the buffer might be full and the library will flush it automatically.
  • +
  • the library might be asked to read from the standard input stream. Then, it will flush the buffer immediately. This indicates another side effect of input operation.
  • +
  • when we explicitly say to do so: e.g. std::endl.
  • +
+

The input operator >> is also left-associative and returns the left-hand operands as their results. Therefore, multiple reads can be done like below codes :

1
2
3
string s1, s2; // define two string type variables
cin >> s1 >> s2; // reads from cin into s1 and s2 seprately
cout << s1 << s2 << endl; // writes two strings

+

In some cases, we would like to read chars as well as whitespaces. To fulfill this, we need to use getline instead of >> with the syntax:

1
std::getline(std::cin, name)

+

The getline function will read chars into variable name until it encounters line feed (note that the line feed will also be read but not stored into the variable). I modified the program using getline:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
#include <iostream>
#include <string>

int main()
{
// ask for the person's name
std::cout << "Please enter your full name: ";

// read the name
std::string name; // define name
std::getline(std::cin, name); // read into

// write a greeting
std::cout << "Hello, " << name << "!" << std::endl;
return 0;
}

+

When I run the program and input “Bruce Lee”, it results:

1
2
Please enter your first name: Bruce Lee
Hello, Bruce Lee!

+

The string size and concatenate operations

Now we go into a little bit complex operations and aim to produce a framed greeting like below:

1
2
3
4
5
6
Please enter your full name: Bruce Lee 
*********************
* *
* Hello, Bruce Lee! *
* *
*********************

+

Let’s provide the program first:

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
// ask for a person's name, and generate a framed greeting
#include <iostream>
#include <string>

int main()
{
std::cout << "Please enter your full name: ";
std::string name;
std::getline(std::cin, name);

// build the message that we intend to write
const std::string greeting = "Hello, " + name + "!";

// build the second and forth lines of the output
const std::string spaces(greeting.size(), ' ');
const std::string second = "* " + spaces + " *";

// build the first and fifth lines of the output
const std::string first(second.size(), '*');

// write it all
std::cout << std::endl;
std::cout << first << std::endl;
std::cout << second << std::endl;
std::cout << "* " << greeting << " *" << std::endl;
std::cout << second << std::endl;
std::cout << first << std::endl;

return 0;
}

+

The first three statements are exactly the same as those in the last program. However, we define a new string type variable named greeting and initialize it with the message that we will write using opetator +:

1
const std::string greeting = "Hello, " + name + "!";

+

The keyword const means that the value of the variable keeps constant since the first time read in. The + concatenate two strings (may also be one string and one string literals but cannot be two string literals) into a single string. As mentioned in previous chapter, operators have different effect on operands depending on the types of operands, which is commonly termed as overloaded. Same as operators >> and <<, + is also left-associative.

+

The remainder parts of the frame is simply constituted by asterisks and spaces while the numbers of asterisks and spaces are determined by the size of greeting. Recalling the ways to initialize a string type variable filled by same chars(e.g. ‘a’):

1
string s0(5, 'a'); // s0 is aaaaa

+

What we need here is to figure out the size of greeting. greeting.size() is a member function of the object. By calling this member function, we will obtain an integer that means the number of chars in greeting. The returned value is in fact not a int type value, but a string::size_type. In this case, we know that this value is unsigned and hence cann’t compare this value with other signed values for avoiding errors. If we don’t know what exactly the type is, we can use type deduction with specifiers auto and decltype. For example:

1
auto len = greeting.size();

+

The variable len is string::size_type. Similarly:

1
decltype(greeting.size) len;

+

The differences of these two specifiers are:

+
    +
  • the variable that defined using auto must have initializer.
  • +
  • auto‘s type deduction ignores top-level consts but keeps low-level consts. But decltype‘s type deduction returns type including top-level consts. (This will be discussed again in chapter 10).
  • +
+

Random access using a subscript

If there needs to access some characters of the string, we can use the subscript operator([]) to access and sequentially operate on individual characters. Inside the operator, an index value is required to denote the position of the character to be accessed. The operation returns a reference to the character of the given position.

+

The index value should be a value of type string::size_type, and its range should be >= 0 and < size(). In other words, it use asymmetric range [0, size()). To show more about the random access, I did a experiment as below

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
#include <iostream>
#include <vector>

using std::cin; using std::string;
using std::cout; using std::endl;

int main()
{
string str = {"abcdef"};

// obtain the size of str1
string::size_type strSize = str.size();

// define an signed type variable
int x = 0;

// write the string one character by one character
cout << str[x] << str[1] << str[2] << str[3]
<<str[4] << str[strSize-1] << endl;

// access the character beyond the range of [0, strSize-1)
cout << str[strSize] << str[strSize + 1] << str[10] << endl;

return 0;
}

+

Results:

1
2
abcdef
b

+

The example above shows that it leads to unknown results if we use a subscript that beyond the range of [0, size()). In addition, it works if we use a integer value has a signed type (in contrast, the string::size_type is unsigned integeral value).
This is because the index value of the signed type can be converted to the type of string::size_type.

+

Other operations

empty function returns a bool type value that indicates whether the string type variable is empty.

1
greeting.empty(); // if greeting is empty, it returns 1, otherwise returns 0;

+

<, <=, >, >= “test whether one string is less than, less than or equal to, greater than, or greater than or equal to another. These operators use the same strategy as a (case-sensitive) dictionary” (Lippman etc. 2012, p80).

+
+

Next: C++ - Looping and counting.

+ + +
+ + + + + + + + + + + +
+ + + +
+ + + +
+ +
+
+ + +
+ + + + + + + + + +
+ + + + + + + + + +
+
+ + + + +
+ + +
+ + + + +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/11/03/2018/Accelerated-C-Solutions-to-Exercises-Chapter-4-Part-2/index.html b/11/03/2018/Accelerated-C-Solutions-to-Exercises-Chapter-4-Part-2/index.html new file mode 100644 index 00000000..3312d0d4 --- /dev/null +++ b/11/03/2018/Accelerated-C-Solutions-to-Exercises-Chapter-4-Part-2/index.html @@ -0,0 +1,910 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Accelerated C++ Solutions to Exercises(Chapter 4 Part 2) | Liam's Blog + + + + + + + + + + + + + + + + + + +
+
+ + + +
+
+
+
+ + +
+ + + + + + + + +
+ + + +
+ + + + + + + +
+ + + +

Accelerated C++ Solutions to Exercises(Chapter 4 Part 2)

+ + + +
+ + + + + +
+ + + + + +

Exercise 4-6

Rewrite the Student_info structure to calculate the grades immediately and store only the final grade.

+

Solution & Results

I didn’t understand very well about the original program when I went through it first time. Now I try to rewrite it in this program with detailed explinations on each step. The strategy can be divided into three parts:

+
    +
  1. analyse the requirements
  2. +
  3. break the ultimate goal into smaller ones that are logically connected
  4. +
  5. design algorithms to accomplish small goals
  6. +
  7. logically verify the correctness of each part and put all together
  8. +
  9. test the performance
  10. +
+

Requirements interpretation

What we have?

The program asks to enter a file that contains multiple students’ information including name, midterm exam grade, final exam grades and a sequence of homework grades. The example below shows an input sample

1
2
3
4
Robin 90 87 79 88 81 73 45 
Brendan 70 69 88 100 91 75 66
Arsenii 99 87 89 88 74 90 70
Liam 83 66 100 76 87 91 78

+

Note that the number of students is unkown and the number of homework grades is unknown as well.

+

What we need to do?

The program is required to generate a final grade report for a class. Specifically

+
    +
  1. each line of the report contains the information of one student, including name and a final grade.
  2. +
  3. the final grade is the weighted average of midterm, final and homework grades, of which the mediter exam grade counts for 20%, the final exam grade counts for 40%, and the median homework grade counts for 40%.
  4. +
  5. each line of outputs follows an alphabetical order according to the names.
  6. +
  7. the final grades are vertically aligned.
  8. +
+

An output sample can be found here

1
2
3
4
Arsenii 89.8
Brendan 76.8
Liam 77.8
Robin 84.4

+

others info

We are required to define a data structure to hold each student’s information including name and his final grade. This can be finished immediately.

1
2
3
4
5
// a structure contains name and final grade
struct Student_info{
string name;
double grade;
};

+

It can be seen that Student_info in fact have all information of outputs. Therefore, once it is filled with correct information, the rest is to format the report to be written.

+

Decompose the program

Overall structure

Assuming that we have filled the information for one student(i.e. one object of Student_info), we need to store it for making preparations for generating the report. As the number of student is unknown, vector is flexible enough to hold each Student_info object. Thus, the overall structure of this program can be

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
int main()
{
// to hold the information for each student
vector<Student_info> students;

// one Student_info object to be filled
Student_info record;

// loop invariant: we have stored students.size() students already
while (// condition statement)
{
// put the recorded student into the vector students
students.push_back(record);
}

// write statements to format the report
}

+

What the condition for this while loop should be? The condition for us to store record is that it has been filled(i.e. both name and grade contains correct information). Therefore, the condition shoule be true if record is successfully filled, and should be false if the attempt is failure.

+

There are two members in an object of type Student_info, one is name and the other one is grade. name comes from supplied information while grade can be computed using the information. It is necessary to write a function that not only can read and deal with supplied information but also meet the requirments of being the condition of above while loop. Let’s declare this function

1
istream &read(istream &is, Student_info &s);

+

The first parameter is a reference that refers to the object of input stream (i.e. istream). The second parameter is also a reference that refers to record. The reason to pass arguments by reference rather than value is that:

+
    +
  1. the function needs to return a value that shows whether the reading attempt is successful. It is the pre-condition for filling record.
  2. +
  3. the istream objects can’t be copied.
  4. +
  5. the function needs to return the filled record.
  6. +
  7. By passing by reference, we can avoid seting two return values.
  8. +
+

As the condition of the while loop, read function returns an istream type object which is evaluated to be true if it is valid and otherwise it is evaluted to be false. Therefore, read function should return a valid istream object after finishing filling the record, and return an invalid istream object after finishing filling all records.

+

Fill in the information

Let’s firstly consider how to fill the information for on student. The name can be stored directly into s.name. To hold midterm and final grades, we define two double type variables while to hold homework grades we define a variable of type vector.

+
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
istream & read(istream &is, Student_info &s)
{
// reads and store the student's name, midterm and final exam grades
double midterm, final;
is >> s.name >> midterm >> final;

// reads and store all homework grades
vector<double> hw;

// call functions that store all homework grades
// call functions that compute the final grade

// return a valid is if both name and grade contain correct information
return is;
}
+

To compute the final grade, what we lack is the median value of homework grades. But before we can compute the median grade, we need to store all possible homework grades using a function similar to read.

1
2
3
4
5
6
7
8
9
10
11
istream & read_hw(istream &in, vector<double> &hw)
{
// read homework grades
double x;

// loop invariant: we have read hw.size() homework grades
while(in >> x)
hw.push_back(x);

return in;
}

+

This function is ok if there is only one student but doesn’t work for the input file shown above. When it finishes reading all homework grades of the first student, it terminates because that it encounters next student’s name rather than a double value, leading to the input stream being failure state. Therefore, we can add in.clear() before return in to reset the error state to good.

1
2
// clear the stream so that input will work for the next student
in.clear();

+

Computations

Once we obtained the homework grades, we can compute the median value and then the final grade. The algorithms for these computations are not complicated. The function below will be called in the read function.

+
1
2
3
4
5
6
double grade(double midterm, double final, const vector<double> &hw)
{
if (hw.size() == 0)
throw domain_error("student has done no homework");
return grade(midterm, final, median(hw));
}
+

In this function, two functions are called. One is the median(hw) function which returns the median value, and anther one is overloaded grade function that returns the final grade which is again returned to the read function. Two functions are presented as follows

+
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
// function to calculate median
double median(vector<double> vec)
{
// get the size of the vector
typedef vector<double>::size_type vec_size;
vec_size size = vec.size();

// check whether the empty is empty
if (size == 0)
throw domain_error("median of an empty vector");

// sort the grades
sort(vec.begin(), vec.end());

// compute the median homework grade
vec_size mid = size/2;
return size % 2 == 0 ? (vec[mid] + vec[mid - 1])/2 : vec[mid];
}
+
1
2
3
4
5
// function to calculate final grade
double grade(double midterm, double final, double homework)
{
return 0.2*midterm + 0.4*final + 0.4*homework;
}
+

How does eof works?

Now, we can complete the read function and the while loop. Noting that I add throw statement after finishing reading the homework grades. Correspondingly, I add the try block to catch any exception that might be thrown when reading students’ information.

+
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
istream & read(istream &is, Student_info &s)
{
// reads and store the student's name, midterm and final exam grades
double midterm, final;
is >> s.name >> midterm >> final;

// reads and store all homework grades
vector<double> hw;
read_hw(is, hw);
if(hw.empty())
throw domain_error("student has done no homework");

// get the value of final grade
s.grade = grade(midterm, final, hw);

// return a valid is if both name and grade contain correct information
return is;
}
+
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
int main()
{
// to hold the information for each student
vector<Student_info> students;

// one Student_info object to be filled
Student_info record;

// loop invariant: we have stored students.size() students already
try{
while(read(cin, record))
{
maxlen = max(maxlen, record.name.size());
students.push_back(record);
}
}catch(domain_error e){
cout << e.what() << endl;
}

// write statements to format the report
}
+

Theoretically, we have finished most parts of the program. The next step is to format outputs if above functions work fine. Let’s logically verify this part with following steps

+
    +
  1. when the computer executes the while loop, the condition is evaluated.
  2. +
  3. the computer enter into the read function. The function stores name, midterm and final, and create an empty object hw.
  4. +
  5. then the computer enters into the read_hw function. This function creates an object x and starts another while loop.
  6. +
  7. the condition in >> x is evaluated. In most cases, the condition is true until it encounters next student’s name. As a result, the input stream is changed to failure state. By then, it has stored all homework grades of the first student into the vector hw.
  8. +
  9. the state of in is reset to be good by using in.clear().
  10. +
  11. in is returned and the implementation goes back to the function read.
  12. +
  13. grade function is called. Inside this function, another grade function is called and median function is called.
  14. +
  15. s.grade is obtained, and is is returned. By then, record contains correct information of the first student.
  16. +
  17. step 1 continues, is is evaluated to be true which indicates that record is filled successfully.
  18. +
  19. the computer moves to the while body and record is stored into students. By then, both the name and final grade of the first student is available for us.
  20. +
  21. the while loop starts over again from step 1 - 10.
  22. +
+

Yeah, it works fine by now. However, how to stop it? Typically we can stop a while loop by sending a signal eof to set the input stream being a failure state. Let’s type Ctrl+Z/D and see how it works.

+
    +
  1. same as above
  2. +
  3. same as above. Though name, midterm are uninitialized, the implementation would’t stop at this position.
  4. +
  5. same as above
  6. +
  7. the condition in >> x is evaluated to be false due to the eof
  8. +
  9. same as above. By then, hw is still empty.
  10. +
  11. same as above
  12. +
  13. grade function is called. An exception is thrown due to the fact of step 5.
  14. +
  15. the program fails
  16. +
+

So, what’s the problem? The immediate cause is the empty hw. We actually even don’t want to create the hw since we want to terminate the while loop immediately. Therefore, we need to add a condition like follow code shows

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
istream & read(istream &is, Student_info &s)
{
// reads and store the student's name, midterm and final exam grades
double midterm, final;
is >> s.name >> midterm >> final;

// to check whether eof is sent
if(is)
{
// reads and store all homework grades
vector<double> hw;
read_hw(is, hw);
if(hw.empty())
throw domain_error("student has done no homework");

// get the value of final grade
s.grade = grade(midterm, final, hw);

// return a valid is if both name and grade contain correct information
}
return is;
}

+

Why don’t add the if condition at the very begining of the read function? It is because that the input stream hasn’t start to read anything at that position. Now we recheck each step after sending eof signal.

+
    +
  1. same as above
  2. +
  3. midterm and final is created.
  4. +
  5. eof is read and the state of is is set to be failure.
  6. +
  7. condition is is evaluated to be false
  8. +
  9. return is. The computer goes back the step 1.
  10. +
  11. the condition in step 1 is evaluated to be false, while loop stops.
  12. +
+

Now, this part of code should work as expected.

+

Formatting the outputs

The next is to format each line of outputs according to requirements analysed above.
To alphabetize the outputs, we can use following code

1
sort(students.begin(), students.end(), compare);

+

where compare is defined as follows

+
1
2
3
4
bool compare(const Student_info &x, const Student_info &y)
{
return x.name < y.name;
}
+

To line up the final grades, we need to find the longest name and hence add one statement in the first while loop. maxlen will finally record the length of the longest name.

1
maxlen = max(maxlen, record.name.size());

+

The final grade can be obtained via

1
double final_grade = students[i].grade;

+

####

+

Put all together

Following gives each file of the program. No more discussion here.
mainfunction.cpp

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
#include <algorithm>
#include <iomanip>
#include <ios>
#include <iostream>
#include <stdexcept>
#include <string>
#include <vector>
#include "Student_info.h"
#include "grade.h"

using std::cin; using std::setprecision;
using std::cout; using std::sort;
using std::endl; using std::streamsize;
using std::domain_error; using std::string;
using std::max; using std::vector;

int main()
{
vector<Student_info> students;
Student_info record;
string::size_type maxlen = 0;

// read and store all the records, and find the length of the longest name
try{
while(read(cin, record))
{
maxlen = max(maxlen, record.name.size());
students.push_back(record);
}
}catch(domain_error e){
cout << e.what() << endl;
}

// alphabetize the records
sort(students.begin(), students.end(), compare);

// write each line of outpurs
for (vector<Student_info>::size_type i = 0; i != students.size(); ++i)
{
// write the name, blanks
cout << students[i].name << string(maxlen + 1 - students[i].name.size(), ' ');

// compute and write the final grade
double final_grade = students[i].grade;
streamsize prec = cout.precision();
cout << setprecision(3) << final_grade << setprecision(prec) << endl;
}
return 0;
}

+

Student_info.cpp

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
#include "Student_info.h"
#include "grade.h"
using std::vector; using std::istream; using std::cin; using std::cout;

// function to define the additional arguments in max
bool compare(const Student_info &x, const Student_info &y)
{
return x.name < y.name;
}

// function to read data for inputs and fill information
istream & read(istream &is, Student_info &s)
{
// reads and store the student's name, midterm and final exam grades
double midterm, final;
is >> s.name >> midterm >> final;
if (is)
{
// reads and store all homework grades
vector<double> hw;
read_hw(is, hw);
if(hw.empty())
throw domain_error("student has done no homework");

// get the final grade
s.grade = grade(midterm, final, hw);
}
return is;
}

// function to read homework grades
istream & read_hw(istream &in, vector<double> &hw)
{
// read homework grades
double x;
while(in >> x)
hw.push_back(x);

// clear the stream so that input will work for the next student
in.clear();

return in;
}

+

Student_info.h

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
#ifndef GUARD_Student_info
#define GUARD_Student_info

// Student_info.header file
#include<iostream>
#include<string>
#include<vector>

struct Student_info{
std::string name;
double grade;
};

bool compare(const Student_info &, const Student_info &);
std::istream & read(std::istream &, Student_info &);
std::istream & read_hw(std::istream &, std::vector<double> &);
#endif

+

grade.cpp

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
#include <algorithm>
#include <stdexcept>
#include <vector>
#include "grade.h"

using std::domain_error; using std::istream;
using std::vector; using std::sort;

// grade function 1
double grade(double midterm, double final, const vector<double> &hw)
{
if (hw.size() == 0)
throw domain_error("student has done no homework");
return grade(midterm, final, median(hw));
}

// grade function 2
double grade(double midterm, double final, double homework)
{
return 0.2*midterm + 0.4*final + 0.4*homework;
}

// compute the median of vector<double>
double median(vector<double> vec)
{
// get the size of the vector
typedef vector<double>::size_type vec_size;
vec_size size = vec.size();

// check whether the empty is empty
if (size == 0)
throw domain_error("median of an empty vector");

// sort the grades
sort(vec.begin(), vec.end());

// compute the median homework grade
vec_size mid = size/2;
return size % 2 == 0 ? (vec[mid] + vec[mid - 1])/2 : vec[mid];
}

+

grade.h

1
2
3
4
5
6
7
8
9
10
#ifndef GUARD_grade_h
#define GUARD_grade_h

// grade.h
#include<vector>

double grade(double, double, const std::vector<double> &);
double grade(double, double, double);
double median(std::vector<double>);
#endif

+

Performance test

1
2
3
4
5
6
7
8
9
10
11
12
13
Inputs

Robin 90 87 79 88 81 73 45
Brendan 70 69 88 100 91 75 66
Arsenii 99 87 89 88 74 90 70
Liam 83 66 100 76 87 91 78

Outputs:

Arsenii 89.8
Brendan 76.8
Liam 77.8
Robin 84.4
+

It performs as same as the original program provided in Chapter 4.

+
+

Exercise 4-7

Write a program to calculate the average of the numbers stored in a vector.

+

Solution & Results

It’s a simple project and the solution can be divided into three steps

+
    +
  1. define a function to read and store data into a vector.
  2. +
  3. define a function to compute the average value.
  4. +
  5. set precision to format the double value of outputs.
  6. +
+

All details can be found in other exercises and hence no more explinations here.

+

Below shos the only file including both functions and main function.

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
#include <iostream>
#include <vector>
#include <stdexcept>
#include <iomanip>

using std::cin; using std::cout;
using std::endl; using std::domain_error;
using std::vector; using std::istream;
using std::streamsize; using std::setprecision;

// define function to read data into a vector
istream &read(istream &is, vector<double> &doubleValues)
{
double number;
while(is >> number)
doubleValues.push_back(number);
return is;

}

// define function to compute the average of a data sequence
double average(const vector<double> &doubleValues)
{
if (doubleValues.size() == 0)
throw domain_error("An empty vector");
typedef vector<double>::size_type vec_size;
double sum = 0;
for (vec_size i = 0; i != doubleValues.size(); ++i)
sum += doubleValues[i];
return sum/doubleValues.size();
}

int main()
{
vector<double> doubleValues;
read(cin, doubleValues);
try{
double averageValue = average(doubleValues);
streamsize prec = cout.precision();
cout << setprecision(3) << averageValue
<< setprecision (prec) << endl;
}catch(domain_error){
cout << "You must enter at least 1 number. Pleast try again.";
}
return 0;
}

+

Test

1
2
3
4
5
Input: 
1.2 5.89 9.33 12.7 7.8 4

Output:
6.82

+
+

Exercise 4-8

If the following code is legal, what can we infer about the return type of f?

1
double d = f()[n];

+

Solution & Results

From the code, we can infer

+
    +
  1. f()[n] is possible a value of type int or float or double or bool.
  2. +
  3. f() supports accessing the element using subscript and hence it could be a container.
  4. +
  5. f() has the form of a function.
  6. +
+

According to above analysis, f() could be a function that returns a container which can hold values of types listed above. As far as I know, the container is vector and returns a type of vector(inside types including int, float, double and bool).

+

To verify my analysis, I wrote a simple program shown as below

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>
#include <vector>

using std::cout;
using std::cin;
using std::vector;
using std::endl;

// funtion to return a vector<double>
vector<double> f(vector<double> number)
{
number.push_back(11);
number.push_back(20.5);
return number;
}

int main(){
vector<double> number;
double x = f(number)[0];
double y = f(number)[1];
cout << x << endl;
cout << y << endl;
return 0;
}

+

It works as expected and gives results

1
2
11
20.5

+

If I change the type double to any of types listed above, the program works fine.

+
+

Reference

Koenig, A. and Moo, B.E., 2000. Accelerated C++.

+

Lippman, S.B., Lajoie, J. and Moo, B.E., 2013. C++ Primer, 5th edt.

+ + +
+ + + + + + + + + + + +
+ + + +
+ + + +
+ +
+
+ + +
+ + + + + + + + + +
+ + + + + + + + + +
+
+ + + + +
+ + +
+ + + + +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/12/04/2018/Accelerated-C-Solutions-to-Exercises-Chapter-10/index.html b/12/04/2018/Accelerated-C-Solutions-to-Exercises-Chapter-10/index.html new file mode 100644 index 00000000..3b0da461 --- /dev/null +++ b/12/04/2018/Accelerated-C-Solutions-to-Exercises-Chapter-10/index.html @@ -0,0 +1,834 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Accelerated C++ Solutions to Exercises(Chapter 10) | Liam's Blog + + + + + + + + + + + + + + + + + + +
+
+ + + +
+
+
+
+ + +
+ + + + + + + + +
+ + + +
+ + + + + + + +
+ + + +

Accelerated C++ Solutions to Exercises(Chapter 10)

+ + + +
+ + + + + +
+ + + + + +

Exercise 10-0

Compile, execute, and test the programs in this chapter.

+

Solution & Results

Please click here Managing memory and low-level data structures for codes and analysis.

+
+

Exercise 10-1

Rewrite the student-grading program from §9.6/166 to generate letter grades.

+

Solution & Results

Since the letter grades function computes a letter grade based on a numerical grade, I would like to add it as a non-member function of the Student_info class to avoid repetitively computing the final grade. When need calculate the letter grades, we simply call the function as follows:

1
2
double final_grade = it->grade(); // it is a pointer to the Student_info object
cout << letter_grade(final_grade); // print the letter grade

+

or

1
cout << letter_grade(it->grade());

+

All files are presented at below including mainfunction.cpp, Student_info.h, Student_info.cpp, grade.h, grade.cpp.

+

mainfunction.cpp

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
// Accelerated C++ Solutions Exercises 10-1
#include <algorithm> // to get the declaration of max, sort
#include <iomanip> // to get the declaration of setprecision
#include <iostream> // to get the declaration of streamsize
#include <stdexcept> // to get the declatation of domain_error
#include <string> // to get the declaration of string
#include <vector> // to get the declaration of vector
#include "Student_info.h" // to get the declaration of Student_info

using std::cin; using std::setprecision;
using std::cout; using std::sort;
using std::endl; using std::streamsize;
using std::domain_error; using std::string;
using std::max; using std::vector;
using std::fixed;

int main()
{
vector<Student_info> students;
Student_info record;
string::size_type maxlen = 0;

// read and store all the records, and find the length of the longest name
while(record.read(cin))
{
maxlen = max(maxlen, record.name().size());
students.push_back(record);
}

// alphabetize the records
sort(students.begin(), students.end(), compare);

// write each line of outpurs
for (vector<Student_info>::const_iterator it = students.begin();
it != students.end(); ++it)
{
// write the name, blanks
cout << (*it).name() << string(maxlen + 1 - (*it).name().size(), ' ');

// compute and write the final grade
try{
double final_grade = (*it).grade();
streamsize prec = cout.precision();
cout << fixed << setprecision(1) << final_grade << setprecision(prec);
cout << '\t' << letter_grade(final_grade); // new added
} catch(domain_error e){
cout << e.what();
}
cout << endl;
}
return 0;
}

+

Student_info.h

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
#ifndef GUARD_STUDENT_INFO
#define GUARD_STUDENT_INFO

#include <string>
#include <iostream>
#include <vector>

class Student_info
{
public:
Student_info (); // default constructor
Student_info (std::istream &); // constructor with argument
std::string name() const { return n; } // inline member function return name
bool valid() const { return !homework.empty(); } // inline member function check state
std::istream & read(std::istream &); // member function read in data
double grade() const; // member function calculate final grade


private:
std::string n;
double midterm, final;
std::vector<double> homework;
};

std::istream & read_hw(std::istream &, std::vector<double> &); // nonmember function read data into a vector
bool compare(const Student_info &, const Student_info &); // nonmember function compare two string
std::string letter_grade(double); // new added: nonmember function gives a letter grade

#endif

+

Student_info.cpp

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
65
66
67
68
69
70
71
72
73
74
75
76
77
#include "Student_info.h"
#include "grade.h"
#include <string>

using std::vector;
using std::istream;
using std::string;

// construct an empty Student_info object
Student_info::Student_info (): midterm(0), final(0) { }

// construct one by reading from input stream
Student_info::Student_info (std::istream & in) { read(in); }

// member function read data from input stream
std::istream & Student_info::read(std::istream &in)
{
// reads and store the student's name, midterm and final exam grades
in >> n >> midterm >> final;

// reads and store all homework grades
read_hw(in, homework);
return in;
}

// member function grade
double Student_info::grade() const
{
return ::grade(midterm, final, homework);
}

// nonmember function compare
bool compare(const Student_info &x, const Student_info &y)
{
return x.name() < y.name();
}

// nonmember function read_hw
istream & read_hw(istream &in, vector<double> &hw)
{
if (in){
//get rid of previous contents
hw.clear();

// read homework grades
double x;
while(in >> x)
hw.push_back(x);

// clear the stream so that input will work for the next student
in.clear();
}
return in;
}

// new added: non-member function to calculate the letter grade
string letter_grade(double grade)
{
// range posts for numberic grades
static const double numbers[] = {97, 94, 90, 87, 84, 80, 77, 74, 70, 60, 0};

// name for the letter grades
static const char* const letters[] = {"A+", "A", "A-", "B+",
"B", "B-", "C+", "C", "C-", "D", "F"};

// compute the number of grades given the size of the array
// and the size of a single element
static const std::size_t ngrades = sizeof(numbers)/sizeof(*numbers);

// given a numberic grade, find the associated letter grade
for (std::size_t i = 0; i < ngrades; ++i)
{
if (grade >= numbers[i])
return letters[i];
}
return "?\?\?";
}

+

grade.h

1
2
3
4
5
6
7
8
9
#ifndef GUARD_GRADE_H
#define GUARD_GRADE_H

#include<vector>

double grade(double, double, const std::vector<double> &);
double grade(double, double, double);
double median(std::vector<double>);
#endif

+

grade.cpp

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 <algorithm>
#include <stdexcept>
#include <vector>
#include "grade.h"

using std::domain_error; using std::istream;
using std::vector; using std::sort;

// grade function 1
double grade(double midterm, double final, const vector<double> &hw)
{
if (hw.empty())
throw domain_error("student has done no homework");
return grade(midterm, final, median(hw));
}

// grade function 2
double grade(double midterm, double final, double homework)
{
return 0.2*midterm + 0.4*final + 0.4*homework;
}

// compute the median of vector<double>
double median(vector<double> vec)
{
// check whether the vec is empty
if (vec.begin() == vec.end())
throw domain_error("median of an empty vector");

// sort the grades
sort(vec.begin(), vec.end());

// compute the median homework grade
vector<double>::difference_type size = vec.end() - vec.begin();
vector<double>::const_iterator mid = vec.begin() + size/2;
return size % 2 == 0 ? (*mid + *(mid - 1))/2 : *mid;
}

+

Test

+
1
2
3
4
5
6
7
8
9
10
11
12
13
Inputs:

Robin 90 87 79 88 81 73 45
Brendan 70 69 88 100 91 75 66
Arsenii 99 87 89 88 74 90 70
Liam 83 66 100 76 87 91 78

Outputs:

Arsenii 89.8 B+
Brendan 76.8 C
Liam 77.8 C+
Robin 84.4 B
+
+

Exercise 10-2, 10-3

10-2: Rewrite the median function from §8.1.1/140 so that we can call it with either a vector or a built-in array. The function should allow containers of any arithmetic type.

+

10-3: Write a test program to verify that the median function operates correctly. Ensure that calling median does not change the order of the elements in the container.

+

Solution & Results

There are two requirements: first is that, the median function can calculate the median value by taking either a vector or built-in array; second is that, the container can be container of any arithmetic type.

+

Apparently, our median function should be a function template. The first condition implies that the parameters should be two pointers that denote the range of inputs. Unlike the standard vector, built-in array doesn’t provide member functions like begin() and end(). We cannot simply pass the name of the built-in array to the median function as the name of the array is mere the pointer to the initial element. Alternatively, we can denote its range with [arr, arr+n), where arr is the name of the array and n is the size of the array. The first type parameter of our tempalte represents the type of supplied pointers and will be inferred from the supplied pointers in the process of instantiation.

+

The second condition implies that the type of the elements contained in the container should be supplied as we cannot infer the value type from pointers. Therefore, another type parameter of the function template represents the value type of the container and will be infered from the third function parameter, which is defined as any element of the container. As the following declaration shows, Pointer and T represents the pointer type and value type. The function defines three parameters, first two of which denotes the range of inputs while the third is a const reference to the first element contained in the container.

1
2
template<class Pointer, class T>
T median(Pointer begin, Pointer end, const T& initialElement);

+

Once we have the pointers, we can implement the algorithm as with the previous version. But to avoid changing the original sequence, we’d better construct a new vector to hold the input sequence. The code below gives the full program. I test it by calling the function template with a standard vector and a built-in array that contains the same elements as the vector. As expected, they yield same median value. If changing the value type to int, the function template works as well.

+
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
#include <vector>	// std::vector
#include <iostream> // std::cout, std::endl
#include <stdexcept> // std::domain_error
#include <algorithm> // copy
#include <cstddef> // size_t

using std::vector; using std::size_t;
using std::cout; using std::copy;
using std::domain_error; using std::endl;

template<class Pointer, class T>
T median(Pointer begin, Pointer end, const T& initialElement)
{
if(begin == end)
throw domain_error("median of an empty container");
vector<T> v(begin, end);
typename vector<T>::difference_type size = v.end() - v.begin();
sort(v.begin(), v.end());
typename vector<T>::const_iterator mid = v.begin() + size/2;
return size % 2 == 0 ? (*mid + *(mid-1)) / 2 : *mid;
}

int main()
{
vector<double> vec{53, 56, 23, 78, 90, 89, 34, 12, 41, 48};
size_t n = vec.size();
double arr[n];
copy(vec.begin(), vec.end(), arr);
cout << median(vec.begin(), vec.end(), vec[0]) << endl;
cout << median(arr, arr + n, arr[0]) << endl;
return 0;
}
+

Test Results

1
2
50.5
50.5

+
+

Exercise 10-4, 10-5, 10-6

10-4: Write a class that implements a list that holds strings.

+

10-5: Write a bidirectional iterator for your String_list class.

+

10-6: Test the class by rewriting the split function to put its output into a String_list.

+

Solution & Results

define the String_list class

The standard list provides all the possible operations and what we need to do is merely to encapsulate our data, a list, by hiding the data and instead providing an appropriate interface. I intend to implement a subset of the list class. Specifically, we can use the String_list class as follows:

+

Interface

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
// construct an empty container
String_list s;

// construct a container with a size and a value
String_list s1(10, "Hello");

// return iterator to begining and end
s1.begin();
s1.end();

// construct a container with a range
String_list s2(s1.begin(), s1.end());

// copy construct a container
String_list s3(s2);

// assignment
s = s1;

// check the status of the container
s1.empty();

// check the size
s1.size();

// clear
s1.chear();

// add one element to end
s1.push_back("Hello");

+

All these operations are obvious and we can directly work with list in our implementations. You can add more operations to this String_list class as long as the operations are supported by the standard list class. Let’s see how our String_list class is implemented:

+

String_list.h

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
#ifndef STRING_LIST_H_
#define STRING_LIST_H_

#include <list>
#include <string>
#include <cstddef>

class String_list{
private:
typedef std::list<std::string> container;
container SL;

public:
typedef container::iterator iterator;
typedef container::const_iterator const_iterator;
typedef container::value_type value_type;
typedef container::size_type size_type;

// default constructor: construct an empty String_list
String_list() = default;

// constructor that takes a size and a value
explicit String_list(size_type n, const value_type& val = value_type()): SL(n, val) {}
String_list(const_iterator beg, const_iterator end): SL(beg, end) {}

// bidirectional iterator and const bidirectional iterator
iterator begin() { return SL.begin(); }
const_iterator begin() const { return SL.cbegin(); }
iterator end() { return SL.end(); }
const_iterator end() const {return SL.cend(); }

// empty
bool empty() const { return SL.empty(); }

// size
size_type size() const { return SL.size(); }

// clear
void clear() { SL.clear(); }

// push_back
void push_back(const value_type& v) { SL.push_back(v); }
};

#endif /* STRING_LIST_H_ */

+

It is worth noting that the class defines three constructors but ignores the copy constructor, assignment operator and the destructor. As a result, the compiler will synthesize copy constructor, assignment operator and destructor for us. The synthesized operations depend on the definition of the data member. For example, the compiler will synthesize the copy constructor by calling the default copy constructor of the list class. Therefore, we do not need to worry about that. The synthesized operations also work on the default constructor. I explicitly define the default constructor because that we need other constructors. If we didn’t explicitly define the default constructor, the compiler won’t synthesize for us due to the existence of other constructors.

+

Another point is that the class implicitly supports the conversion from a string literal to a string type. This property is inherent in the standard string class and is further explained in chapter 12.

+

The last point is that the iterator returned by begin() and end() are all bidirectional iterator. This has been defined in the standard list class. We can traverse forward or backward the container and access or rewrite the elements using the returned iterators.

+

rewrite the split function

The original split function copies its output into the output stream directly. We can replace the outstream object with our String_list. Rather than passing an ostream_iterator as the argument to the split function, we can pass a reference to the container as the argument.
Therefore, the declaration of the revisied split function is:

1
2
template <class container>
void split(const std::string &, container&);

+

Then we can store each seperated word into the container by calling its push_back function. Please see the code below:

+

Split.h

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
#ifndef GUARD_SPLIT_H
#define GUARD_SPLIT_H

#include <string>
#include <algorithm>
#include "String_list.h"

// true if the argument is whitespace, false otherwise
bool space(char c)
{
return isspace(c);
}

// false is the argument is whitespace, true otherwise
bool not_space(char c)
{
return !isspace(c);
}

// template declaration and definition
template <class container>
void split(const std::string &str, container& c)
{
typedef std::string::const_iterator iter;

iter i = str.begin();
while(i != str.end())
{
// ignore leading spaces
i = std::find_if(i, str.end(), not_space);

// find end of next word
iter j = std::find_if(i, str.end(), space);

// copy the characters in [i,j) and store into container
if(i != str.end())
c.push_back(std::string(i, j));
i = j;
}
}
#endif /* GUARD_SPLIT_H */

+

Test

I wrote a test program to test each members of our String_list class and the revised split function.

+

main.cpp

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
65
66
67
68
69
70
#include <iostream>		// std::cin, endl, cout
#include <string> // std::string
#include "String_list.h" // String_list
#include "split.h" // split

using std::cout; using std::cin;
using std::endl; using std::string;

int main(){
// construct an empty container
String_list words;

// test the empty function
if(words.empty())
cout << "This is an empty container\n"
"Please enter a sentence: ";

// test the split function, and the push_back member of the String_list
string line;
while (getline(cin, line))
{
split(line, words);
}

cout << "There are " << words.size() << " word(s) in total: ";
for(auto i: words) {
cout << i << " ";
}

// test bidirectional iterators
cout << "\nprint all words in reverse order: ";
String_list::iterator rbeg = --words.end();
String_list::iterator rend = --words.begin();
while(rbeg != rend)
{
cout << *rbeg-- << " ";
}

// test the constructor that takes two input iterators
String_list words_copy(words.begin(), words.end());
if(!words_copy.empty()){
cout << "\nThe size of the container is: " << words_copy.size();
cout << "\nThe elements contained in the String_list are: ";
for(auto i: words_copy)
cout << i << " ";
}

// test the clear function
words.clear();
if(words.empty())
cout << "\nThe container now is empty again";

// test the constructor that takes a size and a value
String_list words_new(10, "Hello");

// test the default copy constructor
String_list words_new_copy(words_new);
cout << "\nThe container contains: ";
for(auto i: words_new_copy) {
cout << i << " ";
}

// test the default assignment operator
words = words_new;
cout << "\nNow the container words contains: ";
for(auto i: words) {
cout << i << " ";
}
return 0;
}

+

The program above is pretty straightforward and gives following results as expected:

1
2
3
4
5
6
7
8
9
This is an empty container
Please enter a sentence: Stack is one of the rudimentary data structures that use pointers
There are 11 word(s) in total: Stack is one of the rudimentary data structures that use pointers
print all words in reverse order: pointers use that structures data rudimentary the of one is Stack
The size of the container is: 11
The elements contained in the String_list are: Stack is one of the rudimentary data structures that use pointers
The container now is empty again
The container contains: Hello Hello Hello Hello Hello Hello Hello
Now the container words contains: Hello Hello Hello Hello Hello Hello Hello

+
+

Reference

Koenig, A. and Moo, B.E., 2000. Accelerated C++.

+

Lippman, S.B., Lajoie, J. and Moo, B.E., 2013. C++ Primer, 5th edt.

+ + +
+ + + + + + + + + + + +
+ + + +
+ + + +
+ +
+
+ + +
+ + + + + + + + + +
+ + + + + + + + + +
+
+ + + + +
+ + +
+ + + + +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/13/03/2018/C-Sequential-Containers/index.html b/13/03/2018/C-Sequential-Containers/index.html new file mode 100644 index 00000000..79bec3e3 --- /dev/null +++ b/13/03/2018/C-Sequential-Containers/index.html @@ -0,0 +1,926 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + C++ - Sequential Containers (Part 1) | Liam's Blog + + + + + + + + + + + + + + + + + + +
+
+ + + +
+
+
+
+ + +
+ + + + + + + + +
+ + + +
+ + + + + + + +
+ + + +

C++ - Sequential Containers (Part 1)

+ + + +
+ + + + + +
+ + + + + +

Recalling the student-grading program in Organizing programs with data structures, and thinking about how to separate students into two categories, passed and failed according to their final grades. Let’s define pass/fail criteria in final grades.

1
2
3
4
5
// predicate to determine whether a student failed
bool fgrade(const Student_info &s)
{
return grade(s) < 60;
}

+

If the computed grade is less than 60, it is a failing grade and the predicate yields a true value.

+

vector

An indices approach

The next is to examine each element in students based on the function fgrade, and store the records for the students who failed and who passed seperately.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
vector<Student_info> extract_fails(vector<Student_info> &students)
{
vector<Student_info> pass, fail;

for (vector<Student_info>::size_type i = 0;
i != students.size(); ++i)
{
if (fgrade(students[i]))
fail.push_back(students[i]);
else
pass.push_back(students[i]);
}
students = pass;
return fail;
}

+

This function returns a vector named fail which contains the records for students who failed, and changes the original students to a vector which contains only the records for students who passed. It can be seen that the vector pass exists temporarily and seems to be superfluous from the perspective of the limited memory.

+

Alternatively, we can remove the pass and operate on the original student directly. The member function erase defined in the class template vector allows users to remove elements, either a single or a range, from a vector. Now let’s see how to change above code using erase.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
vector<Student_info> extract_fails(vector<Student_info> &students)
{
vector<Student_info> fail;
vector<Student_info>::size_type i = 0;

// loop invariant: [0, i) of students represent passing grades
while(i != students.size())
{
if(fgrade(students[i]))
{
fail.pushback(students[i]);
students.erase(students.begin() + i);
}
else
++i;
}
return fail;
}

+

This function only creates one vector to hold the records for students who failed. The logic is quite straightforward:

+
    +
  1. when the first iteration starts, i is 0 and students.size() yields the original length of the vector.
  2. +
  3. then, the while body is executed. If the ith student failed, the record will be stored into fail, and also be erased from the original vector. Noting that there are two side effects: first, the length of the vector decreases by 1; the index of all reminder elements move forward by 1. By then,the element students[1] in the original vector now becomes students[0] in the new vector as the original studnets[0] has been removed. Therefore, the examination starts from i = 0 again.
  4. +
  5. if the ith student passed, the record will be kept and the next loop examines the following element, i.e. students[i+1]. Therefore, i increases by 1 before the end of the current loop preparing for next loop.
  6. +
+

What’s new here is the arguments of erase function.

1
students.erase(students.begin() + i);

+

We have mentioned in previous chapters that students.begin() denotes the initial element (i.e. corresponding to index 0) in the vector students. Increasing by i, the arguments denotes the ith element. The reason to use students.begin() is that the parameter type defined in erase is const_iterator (this is discussed laster).

+

Due to the length of the vector is not fixed, the students.size() should be used in the condition as it always yields the current length when it is evaluated. If a precomputed size is used, e.g. using size defined as below to replace students.size(), students[i] may refer to nonexist elements or the loop becomes endless once any of elements is removed.

1
vector<Student_info>::size_type size = students.size();

+

An iterators approach

It has been observed that each function defined above access elements from the vector in sequential order. Also it is known that indices allows us to access at random. From the perspective of the library, using indices has the same effect as that we request permissions to access elements randomly. In other words, indices privide the ability that we don’t want to use or we don’t need at all.

+

Beyond indices, C++ supports another mechanism iterators that we can use to access elements in a vector. Not all standard containers support indices, but all standard containers supports iterators.

+

iterators provide more flexibility in supporting certain operations depending on different types of containers.

+

In specific, an iterator is a value that

+
    +
  • identifies a container and an element in the container
  • +
  • let us examine the value stored in that element
  • +
  • provides operations for moving between elements in the container
  • +
  • restricts the available operations in ways that correspond to what the container can handle efficiently.
  • +
+

the syntax

If we use indices for the iteration, for example,

1
2
3
for (vector<Student_info>::size_type i = 0; 
i != students.size(); ++i)
cout << students[i].name << endl;

+

alternatively, we can use iterators also

1
2
for (vector<Student_info>::const_iterator iter = students.begin(); iter != students.end(); ++iter)
cout << (*iter).name << endl;

+

From above code, we know that the type of the iter is
vector::const_iterator. More general, vector defines two associated itertor types:

1
2
vector<type>::const_iterator
vector<type>::iterator

+

The difference is that iterators of type const_iterator have only read access. If we want to use an iterator to change values in a vector, we should define it using iterator type.

+

In addition, iter is initialized with the value of students.begin(). begin and end function return values that denotes the begining or one past the end of a container, respectively. Noting that students.begin() returns a value of type iterator but is converted to the type const_iterator in above code.

+

The next is to increase the value of iter using ++iter. The effect of the increment operator on iter is up to how the iterator type defines. As a result, the iterator denotes the next element in the container after this expression is executed.

+

The statement in the for body shows how to indirect access to the elements in students.

1
cout << (*iter).name;

+

The dereference operator () applies to iter and returns an *lvalue (i.e. the element) to which the iterator refers.

+

Alternatively, we can dereference an iterator and fetch the element via

1
iter->name

+

It has same effect as (*iter).name.

+

rewrite functions using iterators

Now let’s use iterators to rewrite the extract_fails function.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
vector<Student_info> extract_fails(vector<Student_info> &students)
{
vector<Student_info> fail;
vector<Student_info>::const_iterator iter = students.begin();

// loop invariant: [0, i) of students represent passing grades
while(iter != students.end())
{
if(fgrade(*iter))
{
fail.pushback(*iter);
iter = students.erase(iter);
}
else
++iter;
}
return fail;
}

+

As mentioned earlier, we pass iter directly to the erase function as the parameter has type of const_iterator.

+

Noting that calling erase function invalidates all iterators that refer to elements after the one that was just removed. But the erase function will return an iterator that is positioned on the element that follows the erased one. Therefore, the statement becomes

1
iter = students.erase(iter);

+

Iterators also support equility (=) and inequaility (!=) operations. Two iterators are equal if they denote the same element or they are off-the-end iterator for the same container.

+

Iterator arithmetic

All standard containers support above iterator operations. But iterators for vector and string also support additional operations as shown below.

+
    +
  1. iter +/n. Adding/substracting an integer n to an iterator yields an iterator that denotes the nth emelemt (if available) after/before the original element, or one past the end of the container.

    +
  2. +
  3. iter1 - iter2. Substracting two iterators yields an number when added to the right-hand operand yields the left operand. The iterators must denote elements within the container or one past the end of the container.

    +
  4. +
  5. >, >=, <, <=. One iterator is less than another if it denotes an element that appears before the element that denoted by the other iterator. Both iterators must denote elements within the container or one past the end of the container.

    +
  6. +
+

list

vector allows us to arbitrarily access elements efficiently. Moreover, it performs well when adding or deleting the element at the end of the vector. However, vector is not a good choice when it come to inserting or removing elements from the interior of the vector, such as functions illustrated above.

+

To deal with such problems, the standard library provides another data structure list for us to efficiently insert or delete elements anywhere in the container. On the other hand, list doesn’t support random access via indices. It only supports bidirectional sequential access via iterators.

+

The list type is defined in the standard header . Same as the vector, list is also a class template. In addition, list and vector share many operations. For example, we can use list to rewrite the function extract_fails.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
// use list instead of vector
list<Student_info> extract_fails(list<Student_info> &students)
{
list<Student_info> fails;
list<Student_info>::iterator iter = students.begin();

while(iter != students.end())
{
if (fgrade(*iter))
{
fail.push_back(*iter);
iter = students.erase(iter);
}
else
++iter;
}
return fail;
}

+

The code above has no distinct difference comparing with the vector version. However, the important difference between lists and vectors is that how they affect on iterators.

+
    +
  1. For vectors, the erase operation invalidates all iterators that refer to elements including the one was just removed and the subsequent elements. It is because that when removing one element, all following elements move one position towards the one erased. The push_back operation invalidates all iterators due to the fact the entirely vector might be reallocated to a new area for holding the new element.

    +
  2. +
  3. For lists, the erase and push_back operations do not invalidate other iterators except the one that was erased.

    +
  4. +
+

Another difference is that the list type does’t support the standard sort function. It has its own member funciton sort which can be called as follows

1
2
list <Student_info> students;
students.sort(compare);

+

Taking strings apart

A string can be regarded as a special container that only contains characters. It supports many operations, such as random access through indices and iterators, similar to a vector. Therefore, we can access and operate on a specific character. In addition, the standard header defines a bunch of functions that can be used to deal with single character in a string. The table below gives the details of each function

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
cctype functionsSource: Lippman etc. 2012, p.82
isalnum(c)true if c is a letter or a digit
isalpha(c)true if c is a letter
iscntrl(c)true if c is a control character
isdigit(c)true if c is a digit
isgraph(c)true if c is not a space but is printable
islower(c)true if c is a lowercase letter
isprint(c)true if c is a printable character
ispunct(c)true if c is a punctuation character
isspace(c)true if c is whitespace
isupper(c)true if c is an uppercase letter
isxdigit(c)true if c is a hexademical digit
tolower(c)if c is an uppercase letter, it returns the lowercase letter; otherwise returns c unchanged
toupper(c)if c is a lowercase letter, it returns the uppercase letter; otherwise returns c unchanged
+

For example, we can write a funciton to extract each word from a string like

1
The wealth of the mind is the only wealth

+

We can use a string member function substr to construct a new string object with its value initialized to a copy of a substring of the original string. substr defines two parameters, of which one is the inital position of the substring in the original string and the other one is the length of the substring. Therefore, the key to solve this project is to find the index of the begining of each word and the length. It can be observed that each word starts from a nonwhitespace character and ends with a character followed by whitespace. Assuming the first character of a word is positioned at indice i and the whitespace that closely follows the end of the word has indice j, the length of the word will be j - i (imagine the half-open range).

+

The solution is provided in the book and shown as below

+
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
vector<string> split(const string &s)
{
vector<string> ret;
typedef string::size_type string_size;
string_size i = 0;

// invariant: we have processed characters [original value of i, i)
while(i != s.size())
{
// ignore leading blanks
// invariant: characters in range [original i, current i) are all spaces
while(i != s.size() && isspace(s[i]))
++i;

// find end of the next word
string_size j = i;

// invariant: none of the characters in range [original j, current j)
while(j != s.size() && !isspace(s[j]))
++j;
// if we found some nonwhitespace characters
if (i != j)
{
// copy from s starting at i and taking j - i characters
ret.push_back(s.substr(i, j-i));
i = j;
}
}
return ret;
}
+

Noting that, when the function try to recognize the last word, the third while loop will stop because of j == s.size() even if !isspace(s[j]) is still true in the case that no whitespaces follows the last word. I add #include directives and using declarations, and test the program using the example string described above.

+
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
#include<iostream>
#include<vector>
#include<string>

using std::cout; using std::vector;
using std::endl; using std::string;

// function declaration
vector<string> split(const string &s);

int main()
{
// define a string contains words separated by whitespaces
string s{"The wealth of the mind is the only wealth"};

// define a vector to hold splitted words
vector<string> words = split(s);

// write each line that contains one word
for (vector<string>::size_type i = 0; i != words.size(); ++i)
cout << words[i] << endl;

return 0;
}

// Please define the split function here
+

Test results

1
2
3
4
5
6
7
8
9
The
wealth
of
the
mind
is
the
only
wealth

+
+

To be continued.

+ + +
+ + + + + + + + + + + +
+ + + +
+ + + +
+ +
+
+ + +
+ + + + + + + + + +
+ + + + + + + + + +
+
+ + + + +
+ + +
+ + + + +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/14/03/2018/Accelerated-C-Solutions-to-Exercises-Chapter-5/index.html b/14/03/2018/Accelerated-C-Solutions-to-Exercises-Chapter-5/index.html new file mode 100644 index 00000000..87d031c6 --- /dev/null +++ b/14/03/2018/Accelerated-C-Solutions-to-Exercises-Chapter-5/index.html @@ -0,0 +1,841 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Accelerated C++ Solutions to Exercises(Chapter 5 Part 1) | Liam's Blog + + + + + + + + + + + + + + + + + + +
+
+ + + +
+
+
+
+ + +
+ + + + + + + + +
+ + + +
+ + + + + + + +
+ + + +

Accelerated C++ Solutions to Exercises(Chapter 5 Part 1)

+ + + +
+ + + + + +
+ + + + + +

Exercise 5-0

Compile, execute, and test the programs in this chapter

+

Solution & Results

Please find solutions and detailed analysis in Chapter 5 Sequential Containers .

+

Exercise 5-1

Design and implement a program to produce a permuted index. A permuted index is one in which each phrase is indexed by every word in the phrase. So, given the following input,

1
2
The quick brown fox 
jumped over the fence

+

the output would be

1
2
3
4
5
6
7
8
      The quick     brown fox 
jumped over the fence
The quick brown fox
jumped over the fence
jumped over the fence
The quick brown fox
jumped over the fence
The quick brown fox

+

A good algorithm is suggested in The AWK Programming Language by Aho, Kernighan, and Weinberger (Addison-Wesley, 1988). That solution divides the problem into three steps:

+
    +
  1. Read each line of the input and generate a set of rotations of that line. Each rotation puts the next word of the input in the first position and rotates the previous first word to the end of the phrase. So the output of this phase for the first line of our input would be
    1
    2
    3
    4
    The quick brown fox
    quick brown fox The
    brown fox The quick
    fox The quick brown
    +
  2. +
+

Of course, it will be important to know where the original phrase ends and where the rotated beginning begins.

+
    +
  1. Sort the rotations.
  2. +
  3. Unrotate and write the permuted index, which involves finding the separator, putting the phrase back together, and writing it properly formatted.
  4. +
+

Solution & Results

program logic

The solution strategy provided above is quite straightforward. But before implementing such strategy, several stylized facts observed from the example should be listed.

+
    +
  1. the output can be divided into two groups.
  2. +
  3. the right group of lines contains the key words (i.e. the alphabetically indexed words). The key word can be any word of a phrase. Therefore, when the first word of a phrase is indexed, the right part contains a complete phrase.
  4. +
  5. the left part and right part of the same line are complementary each other, which leads to a complete phrase. When the right part contains a complete phrase, the left part contains nothing but spaces.
  6. +
+

As described above, we can split the first phrase as follows

1
2
3
4
                    The quick brown fox
The quick brown fox
The quick brown fox
The quick brown fox

+

Rather than generating rotations, I would like to split one phrase into all possible combinations. Each combination will be stored into a data structure defined as below.

1
2
3
4
struct line{
std::string left; // contains the left part of a phrase
std::string right;// contains the right part of a phrase
};

+

In addition, we can define a vector to hold all combinations (i.e. objects of type line), covering all phrases to be entered.

1
vector<line> combinations;

+

Once all combinations have been stored into the vector, we can sort the vector according to the value of the right part of each combination.

1
sort(combinations.begin(), combinations.end(), compare);

+
1
2
3
4
bool compare(const line &x, const line &y)
{
return x.right < y.right;
}
+

By doing so, we have alphabetized all key words. The next is to format the left parts. The content of left part of each line has been fixed as the left part is bound with the right part. All left parts are lined up vertically on the right side while their left side are padded out with certain amount of spaces depending on the longest string of the left parts.

+

Assuming the length of the longest string is obtained and stored in maxlen, the left part of outputs for each line will be

1
string(maxlen - combinations[z].left.size(), ' ') + combinations[z].left

+

It is worth noting that the permuted index is case-insensitive. Therefore, the strings that compared in the compare function should be converted to lower-case letters. In addition, a line of phrase is easily to be splitted if there is no extra spaces before or after the the line, or between words. However, a common case is that users enter more spaces than needed. Therefore, it is necessary to clean needless spaces before we split one line.

+

In summary, the program can be divided into five logic parts

+
    +
  1. read in multiple lines of inputs
  2. +
  3. clean each line for removing needless spaces
  4. +
  5. split each line into all possible combinations
  6. +
  7. store all combinations for all lines and get maxlen.
  8. +
  9. sort and print each line
  10. +
+

files

The main function is shown below

+

mainfunction.cpp

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
#include<iostream>      // to get declaration of cin, cout and endl
#include<string> // to get declaration of string and getline
#include<vector> // to get declaration of vector
#include<algorithm> // to get declaration of sort
#include "cleaning.h"
#include "line.h"

using std::cin; using std::vector;
using std::cout; using std::string;
using std::endl; using std::getline;
using std::sort;


int main()
{
// hold combinations
vector<line> combinations;

// hold the length of the longest string
string::size_type maxlen = 0;

// read in
string words;
while(getline(cin, words))
{
// split each line, store all combinations, record maxlen
if(!words.empty())
split(cleaning(words), combinations, maxlen);
}

// format the outputs
cout << endl;
sort(combinations.begin(), combinations.end(), compare);

// write each line of outputs: left part + 4 spaces + right part
for (vector<line>::size_type z = 0; z != combinations.size(); ++z)
cout << (string(maxlen - combinations[z].left.size(), ' ') + combinations[z].left)
<< " " << combinations[z].right << endl;
return 0;
}

+

I put the split function and compare funtion into one file named line.

+

line.cpp

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
#include <algorithm>
#include <string>
#include <vector>
#include <cctype>
#include "line.h"

using std::string; using std::tolower;
using std::vector; using std::isupper;
using std::max;

// function to get rotations of a line and store all rotations into vector<line>
void split(string words, vector<line> &combs, string::size_type &maxlen)
{
// store the original lines
line originLine;
originLine.right = words;
originLine.left = "";
combs.push_back(originLine);
maxlen = max(maxlen, originLine.left.size());

// store the splitted lines
string::size_type i = 0;
while (i != words.size())
{
line rotateLine;
if (isspace(words[i]))
{
rotateLine.right = words.substr(i+1, words.size() - i);
rotateLine.left = words.substr(0, i);
combs.push_back(rotateLine);
maxlen = max(maxlen, rotateLine.left.size());
}
++i;
}
}

// change characters to lowercase letters
string tolowerString(string c)
{
for (string::iterator iter = c.begin(); iter != c.end(); ++iter)
{
if (isupper(*iter))
*iter = tolower(*iter);
}
return c;
}

// define the arguments for sort function
bool compare(const line &x, const line &y)
{
return tolowerString(x.right) < tolowerString(y.right);
}

+

line.h

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
#ifndef GUARD_LINE_H
#define GUARD_LINE_H

#include <string>
#include <vector>

struct line{
std::string left;
std::string right;
};

void split(std::string words, std::vector<line> &combs, std::string::size_type &maxlen);
std::string tolowerString(std::string x);
bool compare(const line &x, const line &y);

#endif /* GUARD_LINE_H */

+

The final part is the cleaning function that is used to remove extra spaces.

+

cleaning.cpp

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
// this function removes needless spaces for a sentence
#include <string>
#include <cctype>
#include "cleaning.h"

using std::string;
using std::isspace;

string cleaning(string words)
{
string::iterator iter = words.begin();

// remove spaces before the real sentence begins
while(isspace(*iter))
iter = words.erase(iter);

// remove extra spaces between two words
while((iter+1) != words.end())
{
if (isspace(*iter) && isspace(*(iter + 1)))
iter = words.erase(iter);
else
++iter;
}

// remove the trailing space
if(isspace(*iter))
words.erase(iter);
return words;
}

+

cleaning.h

1
2
3
4
5
6
7
8
#ifndef GUARD_CLEANING_H
#define GUARD_CLEANING_H

#include <string>

std::string cleaning(std::string words);

#endif /* GUARD_CLEANING_H */

+

Test performance

Let’s test the required inputs

1
2
3
4
5
6
7
8
9
10
11
The quick brown fox 
jumped over the fence

The quick brown fox
jumped over the fence
The quick brown fox
jumped over the fence
jumped over the fence
The quick brown fox
jumped over the fence
The quick brown fox

+

To show the robustness of this program, I choose a text fragment from The Declaration of Independence as the inputs:

+
1
2
3
4
5
6
7
8
9
He has refused for a long time, 
after such dissolution,
to cause others to be elected;
whereby the legislative powers,
incapable of annihilation,
have returned to the people at large for their exercise;
the State remaining in the meantime exposed
to all the dangers of invasion from
without and convulsion within.
+

The resulted output perfectly follows the requirement of the permuted index

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
                            He has refused for    a long time,
after such dissolution,
to all the dangers of invasion from
without and convulsion within.
incapable of annihilation,
have returned to the people at large for their exercise;
to cause others to be elected;
to cause others to be elected;
without and convulsion within.
to all the dangers of invasion from
after such dissolution,
to cause others to be elected;
have returned to the people at large for their exercise;
the State remaining in the meantime exposed
He has refused for a long time,
have returned to the people at large for their exercise;
to all the dangers of invasion from
He has refused for a long time,
have returned to the people at large for their exercise;
He has refused for a long time,
the State remaining in the meantime exposed
incapable of annihilation,
to all the dangers of invasion from
have returned to the people at large for their exercise;
whereby the legislative powers,
He has refused for a long time,
the State remaining in the meantime exposed
incapable of annihilation,
to all the dangers of invasion from
to cause others to be elected;
have returned to the people at large for their exercise;
whereby the legislative powers,
He has refused for a long time,
the State remaining in the meantime exposed
have returned to the people at large for their exercise;
the State remaining in the meantime exposed
after such dissolution,
to all the dangers of invasion from
whereby the legislative powers,
the State remaining in the meantime exposed
have returned to the people at large for their exercise;
the State remaining in the meantime exposed
have returned to the people at large for their exercise;
He has refused for a long time,
to all the dangers of invasion from
to cause others to be elected;
to cause others to be elected;
have returned to the people at large for their exercise;
whereby the legislative powers,
without and convulsion within.
without and convulsion within.

+
+

To be continued.

+ + +
+ + + + + + + + + + + +
+ + + +
+ + + +
+ +
+
+ + +
+ + + + + + + + + +
+ + + + + + + + + +
+
+ + + + +
+ + +
+ + + + +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/14/03/2018/C-Sequential-Containers-Part-2/index.html b/14/03/2018/C-Sequential-Containers-Part-2/index.html new file mode 100644 index 00000000..627cdfa3 --- /dev/null +++ b/14/03/2018/C-Sequential-Containers-Part-2/index.html @@ -0,0 +1,824 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + C++ - Sequential Containers (Part 2) | Liam's Blog + + + + + + + + + + + + + + + + + + +
+
+ + + +
+
+
+
+ + +
+ + + + + + + + +
+ + + +
+ + + + + + + +
+ + + +

C++ - Sequential Containers (Part 2)

+ + + +
+ + + + + +
+ + + + + +

Putting strings together

This part provides a series of exercises about various concatenations of strings. The topic is similar to that in Chpater 2 Looping and counting.

+

Exercise 1

Briefly speaking, the first exercise requires us to design a program that can add a frame for a picture (shown as below).

+

The original picture

1
2
3
4
5
this is an
example
to
illustrate
framing

+

The framed picture

1
2
3
4
5
6
7
**************
* this is an *
* example *
* to *
* illustrate *
* framing *
* ************

+

The original picture is in fact formed by several lines of strings, of which each string is an element stored in the vector named p. The framed picture adds four edges for the original picture with asterisks. There is one space between the left-edge and the initial character of the strings, and one space between the right-edge and the end of the longest string.

+

Exercise 2

Once we have the framed picture, we can vertically concatenate it with the original one. The program should generate a final picture like this

1
2
3
4
5
6
7
8
9
10
11
12
this is an
example
to
illustrate
framing
**************
* this is an *
* example *
* to *
* illustrate *
* framing *
**************

+

Two pictures are lined up along the left-hand border.

+

Exercise 3

We can also perform horizontal concatenation on two pictures like the below picture shows

1
2
3
4
5
6
7
this is an **************
example * this is an *
to * example *
illustrate * to *
framing * illustrate *
* framing *
**************

+

Two pictures are lined up along the top border. In addition, there is a blank column that seperates two pictures.

+

Solutions

The book provides solutions to each exercise listed above, I’ll put all together and write a program that can generate above pictures once.

+

The framed picture

Framing a picture is an old question. The solution strategy can be divided into three steps

+
    +
  1. obtain the length (denoted by maxlen) of the longest string in p as the width of the framed picture depends on maxlen.
  2. +
  3. create a string object that filled by asterisks for generating the top line and bottom line. The number of the astersiks is maxlen + 4.
  4. +
  5. the middle lines are formed by 1 space character and the corresponding string in p, and a certain number of spaces. The number of spaces is the difference between the maxlen and the size of the corresponding string to be concatenated.
  6. +
+

Below shows the function that gives the length of the longest string in a object of vector. I uses iterators instead of indices for all functions.

1
2
3
4
5
6
7
8
9
string::size_type width(const vector<string> &v)
{
string::size_type maxlen = 0;
for (vector<string>::const_iterator iter = v.begin(); iter != v.end(); ++iter)
{
maxlen = max(maxlen, (*iter).size());
}
return maxlen;
}

+

The function below generates the framed picture while keeping the original picture unchanged.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
vector<string> frame(const vector<string> &v)
{
// to hold each rows of framed picture to be written
vector<string> ret;
// get the length of the longest string
string::size_type maxlen = width(v);

// first line of outputs
string border(maxlen + 4, '*');
ret.push_back(border);

for (vector<string>::const_iterator iter = v.begin(); iter != v.end(); ++iter)
{
// new rows except two border lines
string temp = "* " + (*iter) + string(maxlen - (*iter).size(), ' ') + " *";
ret.push_back(temp);
}

// bottom line of outputs
ret.push_back(border);
return ret;
}

+

Vertical concatenation

In fact we can generate the required picture without concatenation. The first step is to generate the original picture and then the second step is to generate the framed picture. They are closely connect if no blank line between two outputs. This implies that we can create a new object of verctor and copy all strings contained in two pictures into it. The function is shown below.

1
2
3
4
5
6
7
8
9
vector<string> vcat(const vector<string> &top, const vector<string> &bottom)
{
// copy the top picture
vector<string> ret = top;

// copy the bottom picture one line by one line
ret.insert(ret.end(), bottom.begin(), bottom.end());
return ret;
}

+

What’s new here is the insert function. vector, string and list all support insert function. The first parameter of this insert function means that the new elements will be inserted into a position that before the parameter specifies. The second and the third parameters indicate that the elements in the range of [bottom.begin(), bottom.end()) will be copied and inserted. This increases the length of vector. It has the same effect as the for loops shown below.

1
2
3
4
for (vector<string>::const_iterator i = bottom.begin(); i != bottom.end(); ++i)
{
ret.push_back((*i));
}

+

Horizontal concatenation

Similar to vertical voncatenation, we can generate the required picture without concatenation through alternately write rows of the original picture and the framed picture until all rows from two pictures are written. If the number of rows of two pictures are different, the one that has less number of rows will be replenished with blank strings.

+

Therefore, the key to the solution is to concatenate two strings, of which one from the original picture and another from the framed picture. As we did in the fisrt exercise, we need to pad enough spaces for each row of the original picture. Now, the number of spaces will be the difference between maxlen + 1 and the size of each string itself as we want one space column between two pictures. The code below show the function.

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
vector<string> hcat(const vector<string> &left, const vector<string> &right)
{
// to hold the each line of outputs
vector<string> ret;

// add one space column between two pictures
string::size_type width1 = width(left) + 1;

// iterators to look at elements from left and right respectively
vector<string>::const_iterator iter_i = left.begin(), iter_j = right.begin();

// continue until we've see all rows from two pictures
while (iter_i != left.end() || iter_j != right.end())
{
// construct new string to hold characters from two pictures
string s;

// copy a row from left side picture
if (iter_i != left.end())
{
s = (*iter_i);
++iter_i;
}
// pad to full width
s += string(width1 - s.size(), ' ');

// copy a row from right picture
if (iter_j != left.end())
{
s += (*iter_j);
++iter_j;
}

// store s into vector to form a new picture
ret.push_back(s);
}
return ret;
}

+

A complete program

I package all three pictures into one file and the width function into another file. In addition, I define a function more to deal with output. All files are displayed below with detailed comments for each step.
output.cpp

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
// function to write each elements from a vector<string>
#include <iostream> // to get declaration of cout and endl
#include <string> // to get declaration of string
#include <vector> // to get declaration of vector
#include "output.h"

using std::cout; using std::string;
using std::endl; using std::vector;

void output(const vector<string> &pics)
{
// loop thru the vector and write elements one by one
for (vector<string>::const_iterator iter = pics.begin(); iter != pics.end(); ++iter)
{
cout << (*iter) << endl;
}
}

+

output.h

1
2
3
4
5
6
7
8
9
#ifndef GUARD_OUTPUT_H
#define GUARD_OUTPUT_H

#include <string>
#include <vector>

void output(const std::vector<std::string> &pics);

#endif /* GUARD_OUTPUT_H */

+

width.cpp

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
// function returns the size of the longest string in a vector<string>
#include <string> // to get declaration of string
#include <vector> // to get declaration of vector
#include <algorithm> // to get declaration of max
#include "width.h" // to get declaration of the function itself

using std::string; using std::vector; using std::max;

string::size_type width(const vector<string> &v)
{
string::size_type maxlen = 0;
for (vector<string>::const_iterator iter = v.begin(); iter != v.end(); ++iter)
{
maxlen = max(maxlen, (*iter).size());
}
return maxlen;
}

+

width.h

1
2
3
4
5
6
7
8
9
#ifndef GUARD_WIDTH_H
#define GUARD_WIDTH_H

#include <string>
#include <vector>

std::string::size_type width(const std::vector<std::string> &v);

#endif /* GUARD_WIDTH_H */

+

pics.cpp

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
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
// functions that generate different pictures
#include <string> // to get declaration of string
#include <vector> // to get declaration of vector
#include "width.h"
#include "pics.h"

using std::string; using std::vector;

// function to generate a framed picture
vector<string> frame(const vector<string> &v)
{
// to hold each rows of framed picture to be written
vector<string> ret;
// get the length of the longest string
string::size_type maxlen = width(v);

// first line of outputs
string border(maxlen + 4, '*');
ret.push_back(border);

for (vector<string>::const_iterator iter = v.begin(); iter != v.end(); ++iter)
{
// new rows except two border lines
string temp = "* " + (*iter) + string(maxlen - (*iter).size(), ' ') + " *";
ret.push_back(temp);
}

// bottom line of outputs
ret.push_back(border);
return ret;
}

// function to vertically concatenate two pictures
vector<string> vcat(const vector<string> &top, const vector<string> &bottom)
{
// copy the top picture
vector<string> ret = top;

// copy the bottom picture one line by one line
ret.insert(ret.end(), bottom.begin(), bottom.end());
return ret;
}

// function to horizontally concatenate two pictures
vector<string> hcat(const vector<string> &left, const vector<string> &right)
{
// to hold the each line of outputs
vector<string> ret;

// add one space column between two pictures
string::size_type width1 = width(left) + 1;

// iterators to look at elements from left and right respectively
vector<string>::const_iterator iter_i = left.begin(), iter_j = right.begin();

// continue until we've see all rows from two pictures
while (iter_i != left.end() || iter_j != right.end())
{
// construct new string to hold characters from two pictures
string s;

// copy a row from left side picture
if (iter_i != left.end())
{
s = (*iter_i);
++iter_i;
}
// pad to full width
s += string(width1 - s.size(), ' ');

// copy a row from right picture
if (iter_j != right.end())
{
s += (*iter_j);
++iter_j;
}

// store s into vector to form a new picture
ret.push_back(s);
}
return ret;
}

+

pics.h

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
#ifndef GUARD_PICS_H
#define GUARD_PICS_H

#include <string>
#include <vector>

std::vector<std::string> frame(const std::vector<std::string> &v);

std::vector<std::string> vcat(const std::vector<std::string> &top,
const std::vector<std::string> &bottom);

std::vector<std::string> hcat(const std::vector<std::string> &left,
const std::vector<std::string> &right);

#endif /* GUARD_PICS_H */

+

mainfunction.cpp

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
#include <iostream>		// to get declaration of cout and endl
#include <string> // to get declaration of string
#include <vector> // to get declaration of vector
#include "width.h"
#include "pics.h"
#include "output.h"

using std::cout; using std::vector;
using std::endl; using std::string;

int main()
{
// to hold the original picture
vector<string> p;
p.push_back("this is an");
p.push_back("example");
p.push_back("to");
p.push_back("illustrate");
p.push_back("framing");

vector<string> p1 = frame(p);
vector<string> p2 = vcat(p, p1);
vector<string> p3 = hcat(p, p1);


output(p); // print the original picture
cout << endl; // write a blank line to seperate two pictures
output(p1); // print the framed picture
cout << endl;
output(p2); // print the vertically concatenated picture of p and p1
cout << endl;
output(p3); // print the horizontally concatenated picture of p and p1

return 0;
}

+

Test performance

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
this is an
example
to
illustrate
framing

**************
* this is an *
* example *
* to *
* illustrate *
* framing *
**************

this is an
example
to
illustrate
framing
**************
* this is an *
* example *
* to *
* illustrate *
* framing *
**************

this is an **************
example * this is an *
to * example *
illustrate * to *
framing * illustrate *
* framing *
**************
+

The program works perfectly.

+ + +
+ + + + + + + + + + + +
+ + + +
+ + + +
+ +
+
+ + +
+ + + + + + + + + +
+ + + + + + + + + +
+
+ + + + +
+ + +
+ + + + +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/14/04/2018/C-Defining-abstract-data-types/index.html b/14/04/2018/C-Defining-abstract-data-types/index.html new file mode 100644 index 00000000..884600ad --- /dev/null +++ b/14/04/2018/C-Defining-abstract-data-types/index.html @@ -0,0 +1,907 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + C++ - Defining abstract data types (Part 1) | Liam's Blog + + + + + + + + + + + + + + + + + + +
+
+ + + +
+
+
+
+ + +
+ + + + + + + + +
+ + + +
+ + + + + + + +
+ + + +

C++ - Defining abstract data types (Part 1)

+ + + +
+ + + + + +
+ + + + + +

The vector class

This chapter mainly teaches us about how to define our own “vec” class follwing the standard library vector class template. Specifically, our vec class will provide an interface that allows following operations:

+
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
// construct a vector of T type
vector<T> v; // empty vector
vector<T> v(100); // vector with 100 elements

// obtain the names of the types used by the vector
vector<T>::const_iterator b, e;
vector<T>::size_type i = 0;

// use size and the index operator to look at each element in the vector
for (i = 0; i != v.size(); ++i)
cout << v[i].name(); // if T has a member name

// return iterators positioned on the first and one past the last element
b = v.begin();
e = v.end();
+

Implementing the Vec class

The standard library vector is a class template. Similarly, we define a class template to represent our vector to hold various types. We are familar with how to define a function template, now let’s see how to define a class template.

1
2
3
4
5
6
template <class T> class Vec{
public:
// interface
private:
// implementation
};

+

Similar to the definition of a function template, a class template begins with the keyword template follwed by the template parameters list. In this case, there is one type parameter named T. Then we define the class as we did before, assuming that there will be public and private parts to write our interface and implementation respectively.

+

Now we consider the data members for our vector class. Vector is a container that can hold multiple elements. A natural solution goes to a dynamically allocated array. So what information we need for the implementation of our Vec class? The functions begin, end and size imply that we might need to store the address of the initial element, one past the address of the last element and the number of elements. But once we know the address of the first element and one past the last element, we could compute the size easily. Let’s add two data members:

1
2
3
4
5
6
7
template <class T> class Vec{
public:
// interface
private:
T* data; // first element in the Vec
T* limit; // one past the last element in the Vec
};

+

Constructors

From the interface we intend to provide, we know we need to define at least two constructors,

1
2
3
// construct a vector of T type
Vec<T> v; // using default constructor
Vec<T> v(100); // using constructor that takes a size

+

The default constructor leads to an empty Vec and hence there is no need to allocate space to hold the elements. Two data members can be initialized to null pointers. For the constructor that takes a size, we should allocate certain amount of storage for holding the elements. Two data members will be initialized to the corresponding addresses of that space. Each element will be initialized to a value given by the default constructor of Type T. There is also a case that users provide the initial values for the elements, for example

1
Vec<T> v(100, 1);   // using constructor that takes a size and an initial value

+

If so, we would initialize each element with the provided value. The constructor that takes a size and an initial value can be regarded as the special case of the constructor that only takes a size. The code below shows the definition of the constructors:

1
2
3
4
5
6
7
8
9
10
template <class T> class Vec{
public:
Vec() { create() };
explicit Vec(size_type n, const T& val = T()) { create(n, val); }
// remaining interface

private:
T* data
T* limit;
};

+

As we haven’t talk about how to dynamically allocate space for our object, the details of implementations of each constructor will be discussed later. What we need to know here is that the constructors call another function create to initialize our data members and the elements. For the default constructor, create() initializes all data members to null pointers. For the second constructor, create(n, val) allocates enough space, and initializes all data members as well as each element with size n and value val. The second constructor takes two arguments, one is the size n and another is the value that to use in initializing the elements.

+

If there is no user-supplied value, val is assigned with an default value given by the default constructor of type T. One may speculate that if T is built-in type and the vector is allocated at local scope, then the elements are uninitialized as default-initializing an built-in object gives it an undefined value. However, we also know that when we create a standard vector with size only, the compiler initializes each element to 0. So, where there might be problems? Let’s do a simple experiment first.

1
2
3
4
5
6
7
8
9
10
11
12
13
#include <iostream>
using std::cout; using std::endl;

int main()
{
int x;
int y = int();

double m;
double n = double();

cout << x << '\n' << y << '\n' << m << '\n' << n << endl;
};

+

Outputs

1
2
3
4
1954310794
0
-2.2854e+251
0

+

From above example, we observe that a built-in type local variable is undefined in the case of default initialization. In contrast, int() and double() doesn’t default initialize the corresponding objects, but performs value-initialization. This means that T() only invokes default constructor if it is user-declared and otherwise it performs value-initialization. If T is built-in type, objects are zero initialized.

+

It also has been observed that we use a keyword explicit as the begining of the definition of the second constructor. This keyword only makes sence when the constructor takes a single argument, that is, the size. It indicates that the compiler will use the constructor only in the case that the user expressly invokes the constructor.

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
Vec<int> v(100); // ok, explicitly construct the Vec from an int
Vec<int> v = 100; // error: implicitly comstruct the Vec and copy
```

More about the **explicit** will be discussed in chapter 12.

## Type definitions
This section defines types for our **Vec** class including **const_iterator, iterator, size_type** and **value_type**. It is known that our **Vec** class is build upon the dynamic allocated array. In addition, pointers supports the random-access-iterator operations. Therefore, we can define the types **iterator** and **const_iterator** based on pointers. For **size_type**, we can define based on **size_t**. Apparently, The **value_type** is **T**. Now let's see the code

```c++
template <class T> class Vec{
public:
typedef T* iterator;
typedef const T* const_iterator;
typedef size_t size_type;
typedef T value_type;

Vec() { create(); }
explicit Vec(size_type n, const T& val = T()) { create(n, val); }
// remaining interface

private:
iterator data;
iterator limit;
};

+

Index and size

1
2
for (i = 0; i != v.size(); ++i)
cout << v[i].name(); // if T has a member name
+

The size function returns a value that represents the number of elements in a Vec.

+

The indexing operation is supported through the subscript operator [] and hence we should define an overloaded operator as we define other function: it has a name, takes arguments, and specifies a return type.

+

The name of such operator is obtained by appending the symbol [] to the word operator, that is, operator[].

+

If the operator is a function that is not a member function, then the function has as many arguments as the operator has operands. The first argument is bound to the left bound and the second is bound to the right operand. If the operator is defined as a member function, its left operand is implicitly bound to the object on which the operator invoked. In this case, the subscript operator is typically a member function. We can call it with v[i], meaning that v is the object on which it operates and i is an argument that should has type Vec::size_type.

+

As for the return type, the operator function ought to return a reference to the element in the Vec.

+
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
template <class T> class Vec{
public:
typedef T* iterator;
typedef const T* const_iterator;
typedef size_t size_type;
typedef T value_type;

Vec() { create(); }
explicit Vec(size_type n, const T& val = T()) { create(n, val); }

// operations: size and index
size_type size() const { return limit - data; }

T& operator[](size_type i) { return data[i]; }
const T& operator[](size_type i) const { return data[i] }

private:
iterator data;
iterator limit;
};
+

There are sevral key points here:

+
    +
  1. the result of (limit - data) has type ptrdiff_T, which is converted to size_type.
  2. +
  3. taking the size of a Vec doesn’t change the Vec and hence we define it as a const member.
  4. +
  5. we define two version of the operator function: one for const Vec objects and the other for nonconst Vec. It seems impossible to overload the operator function as both version have same parameter list. However, as mentioned above, the object itself is also an implicit argument to the function. Therefore, one function takes the const Vec object as an argument while the other one takes the nonconst Vec object as an argument.
  6. +
+

Operations that return iterators

Next is to define member functions begin() and end(). Similar to the operator function, we need to define two versions for both functions, one version returns const_iterator so that users cannot modify the Vec by operating on the iterator; another one returns an iterator that is not restricted by qualifier const, so that users can write elements into the Vec through the iterator if they want to. The improved code is shown below.

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
template <class T> class Vec{
public:
typedef T* iterator;
typedef const T* const_iterator;
typedef size_t size_type;
typedef T value_type;

Vec() { create(); }
explicit Vec(size_type n, const T& val = T()) { create(n, val); }

// operations: size and index
size_type size() const { return limit - data; }

T& operator[](size_type i) { return data[i]; }
const T& operator[](size_type i) const { return data[i] }

// function to return iterators
iterator begin() { return data; }
const_iterator begin() const { return data; }

iterator end() { return limit; }
const_iterator end() const { return limit; }

private:
iterator data;
iterator limit;
};
```

---
# Copy control
In chapter 9, we have learned how to initialize a class object when it is created. But we haven't talked about what happens when a class object is copied, assigned and destroyed. When we define the **Student_info** class, we didn't define these operations as well. We can presume that the compiler will synthesize definitions for us. Now this section focus on how can we define these operations and how the synthesized operations exactly work.

## Copy constructor
Two ways to implicitly copy a class object: one is that passing an object by value to a function; the other way is that returning an object by value. For example
```c++
vector<int> v;
double d;
d = median(v); // copy v into the parameter in median

string line;
vector<string> words = split(line); // copy the return from split into words

+

Sometimes we also explicitly copy an object, for example using it to initialize another object.

1
2
vector<Student_info> vs;
vector<Student_info> vs_copy = vs; // copy vs into vs_copy

+

Both above copy behaviors are controlled by a special constructor called the copy constructor.

+

copy constructor is also a member function that has the same name as the name of class. It takes a single argument that has the same type as the class itself. In addition, the parameter is a const reference to the object to pass due to that the copy constructor should not change the object being copied from. Therefore, we can declare the copy constructor as shown below

1
2
3
4
5
template <class T> class Vec {
public:
Vec (const Vec& v); // copy constructor
// as before
};

+

When we copy a class object, we’ll need to allocate new space and then copy the contents from the source into the newly allocated storage. This is because we do not intend to change the object being copied from. For example, if we simple copy two data members, we may change the value of the elements due to the fact that the copied pointers still points to the elements contained in the object being copied from. As with the constructors, we will ask the overloaded create function to manage the memory and the details of the copy operations.

1
2
3
4
5
template <class T> class Vec {
public:
Vec (const Vec& v) { create(v.begin(), v.end()); };
// as before
};

+

Assignment

Like the subscript operator, the assignment operator = needs to be defined for providing us the assignment operations. The name of the assignment operator function is operator=. The argument taken by such operator function is as same as the argument taken by copy constructor above. What about the return type? We return a reference to the left operand.

1
2
3
4
5
template <class T> class Vec{
public:
Vec& operator= (const Vec&);
// as before
};

+

It is worth noting the difference between assignment and the copy constructor. assignment always involves obliterating an existing value of the left-hand side, and then replacing it with a new value, i.e. the right-side hand. What they have in common is that both of them need to assign each of the data values. As mentioned above, we cannot assign the value of pointers to the left-hand side because that doing so would bring potential change for the right-hand side.

+

There might be another problem when using the assignment operator, that is how to handle self-assignment. For example:

1
2
vector<int> x(100, 10);
x = x;

+

The assignment operator function will firstly obliterate the value of left-hand side then assign the value of right-hand side to the left-hand side. However, once we destroy the elements and free the space, we cannot create a new object that has the same value as the right-hand side due to both sides operands refer to the same space. To avoid this case, we add a if statement before implemeting the assignment. The code below gives the implementation of the assignment operator function:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
template <class T>
Vec<T>& Vec<T>::operator= (const Vec& rhs)
{
// check for self-assignment
if(&rhs != this)
{
// free the array in the left-hand side
uncreate();

// copy elements from the right-hand to the left-hand side
create(rhs.begin(), rhs.end());
}
return *this;
}

+

The code above introduces several new ideas:

+

First, the operator= is defined as a function template and the type parameter infers from the type parameter of the class template Vec.

+

Second, the return type as well as the function name are defined explicitly due to that this member function is defined outside the class. The declaration uses Vec& rather than Vec& is due to that the type parameter is implicit when we are within the scope of the template. This also explains why we use the function name Vec::operator=. Once we specifies that the function is a member of class Vec, we can omit the type parameter when defines its parameter const Vec& rhs.

+

Third, the if condition uses a keyword this to test whether the assigment happens between two same objects. this is a pointer that points to the object of which operator= is a member. It is valid only inside a member function. Hence, the condition means that if the address of the object (left-hand side) is as same as the address (denoted by &rhs) of the right-hand object, the assignment behavior won’t be executed.

+

Forth, if the leff-hand operand and the right-hand operand are not the same object, we destroy the elements and free the space first through uncreate() and then allocate new space and copy values from rhs like what the copy constructor does.

+

Finally, it is necessary to explain why we intend to return a reference to the left-side object. Why not return void directly? Why don’t we return a value? Move to see more discussion.

+

One reason is that to keep consistent with the default setting of the C++ compiler in regarding to the built-assignment operator. Another reason is that setting the return type to void doesn’t allow continues assignment. For example,

1
2
3
vector<int> x(100, 10);
vector<int> y, z;
y = z = x; // continues assignment

+

Apparently, we don’t have to return a reference, instead we can return a value. Let’s take an example,

1
2
3
Vec<int> x(100, 10);
vector<int> y;
y = x; // calls assignment operator once, calls copy constructor once, calls destructor once

+

It can be presumed that returning an object involves calling three functions: first, assignment operator function is called and a temporary object is created, then, the return statement calls copy constructor to create a new object, finally, the destructor is called to destroy the temporary value and free the space. We’ll introduce the destructor later and will pose an experiment to verify these expectations.

+

Assignment is not initialization

Now we can summarize the difference between initialization and assignment. It can be observed that the operator = has different effect in various contexts. The default setting of = invokes copy constructor and then creating a new object,which is another form of initialization. The operator= described above invokes assignment that always obliterates the privious value first.

+

Initialization happens

+
    +
  1. In variable declaration

    +
    1
    string y;  // default initialization
    +
  2. +
  3. For function parameters on entry to a function

    +
    1
    2
    vector<int> v;
    median(v); // the parameter is copy-initialized
    +
  4. +
  5. For the return value of a function on return from the function

    +
    1
    2
    string line;
    vector<string> words = split(line); // the return value is copy-initialized, the variable is then copy-initialized
    +
  6. +
  7. In constructor initializers

    +
    1
    2
    string url_ch = "@#$%^&**((";   // copy initialization
    string spaces(url_ch.size(), ' '); // direct initialization
    +
  8. +
+

Let’s see another example

1
2
3
4
vector<string> split(const string&); // function declaration
vector<string> v; // default initialization

v = split(line); // on entry, initialization of split's parameter from line; on exit, both initialization of the return value and assignment to v

+

The split function returns an object of type vector. As analysed above, it involves calling both copy constructor (at the call site) and the assignment operator function.

+

Destructor

It is known that when we allocate a space with new, we should destroy the values and free the space with delete. Therefore, it is necessary to define a member function to do the same job. In general, the destructor will be called automatically when:

+
    +
  1. a local variable go out of scope.
  2. +
  3. members of an object are destryoed when the object of which they are a part is destroyed.
  4. +
  5. elements in a container are destoryed when the container is destroyed.
  6. +
  7. the delete operator applied to an object.
  8. +
  9. temporary objects are destroyed.
  10. +
+

Taking an example,

1
2
3
4
5
vector<string> split(const string& str){
vector<string> ret;
// split str into words and store in ret
return ret;
}

+

The variable ret is destroyed when the implementation encounters the return statement because it goes out of the scope. Now let’s see how to define a destructor:

1
2
3
4
5
template <class T> class Vec{
public:
~Vec() { uncreate() };
// as before
}

+

The name of the destructor is as same as the name of the class itself, but prefixed by a tilde(~). There is no arguments taken by the destructor. To destroy the object and free the space, the destructor calls the uncreate() function, which is similar to the behavior of the assignment operator in obliterating the previous value.

+

Default operations

What happens if we do not explicitly define a copy constructor, assignment operator, or destructor? In such case, the compiler will synthesizes default versions of the unspecified operation. Some general rules(koening and Moo 2000):

+

1. the default version are defined to operate recursively-copying, assigning or destroying each data element according to the appropriate rules for the type of that data element.
2. Members that are of class type are copied, assigned, or destoryed by calling the constructor, assignment operator, and destructor for the data element.
3. Members that are of built-in type are copied and assigned by copying or assigning their value. The destructor for built-in types has no work to do-even if the type is a pointer. Destoring a pointer through the default constructor doesn’t free the space at which the pointer points, resulting a memory leak as the occupied space is impossible to free.

+

Recalling the Student_info class defined in chapter 9:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
class Student_info
{
public:
Student_info (); // default constructor
Student_info (std::istream &); // constructor with argument
std::string name() const { return n; } // inline member function return name
bool valid() const { return !homework.empty(); } // inline member function check state
std::istream & read(std::istream &); // member function read in data
double grade() const; // member function calculate final grade

private:
std::string n;
double midterm, final;
std::vector<double> homework;
};

+

If we copy an object of Student_info, the synthesized copy constructor copies four data members. It invokes the string, vector copy constructors to copy the member name and homeworks respectively. It copies the two double values, midterm, final, directly. Similar procedures happen when we do assignment.

+

Noting that if a class defines any constructor explicitly, either a constructor or a copy constructor, the compiler will not synthesize a default constructor for that class. It is wise to provide a default constructor for the data type that to be used as a data member of a class that relies on the synthsized default constructor. We explicitly provide the default constructor in above class Student_info.

+

If a class needs a destructor, it almost surely needs a copy constructor as well as assignment operator. To control every copy of object of class T, we should define:

1
2
3
4
T::T(); // one or more constructors, perhaps with arguments
T::~T(); // the destructor
T::T(const T&); // the copy constructor
T::operator= (const T&); // the assignment operator

+

Dynamic Vecs

This section focus on designing a dynamic Vec class through providing the push_back function which we are familiar with when using the standard vector. Theoretically, the push_back function can allocate new space to hold one more element and then we copy all elements into the new space while constructing a new last element from the argument to push_back. However, doing so would be inefficient when we call the push_back many times. One strategy is to allocate more storage than we need when necessary, that is, when we exhaust the preallocate storage. Specifically, each time the push_back allocate new space, it allocate twice as much as the current space.

+

For example, if we create a Vec with 100 elements, then call the push_back function for the first time, it will allocate a new space that can hold 200 elements. It then copies the original 100 elements into the new space with constructing the last element from the argument. There are still more space left for holding 99 elements more and hence the function do not need to allocate more space in next 99 calls. Moreover, the extral space keep uninitialized.

+

What we need to track is the address of the first element, the one past of the last constructed element, and the end of the new allocate storage(i.e. one past the available element). We’ll denote these address with three pointers, data, avail, limit respectively. The range [data, avail) contains all elements while the range [avail, limit) is the uninitialized storage. Now let’s write the push_back function:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
template <class T> class Vec{
public:
void push_back(const T& val){
if(avail == limit) // get space if needed
grow();
unchecked_append(val); // append the new element
}

private:
iterator data; // as before, the pointer to the first element in the Vec
iterator avail; // pointer to one past the last constructed element
iterator limit; // now points to one past the available element

// rest of the class interface and implementation as before
};

+

grow() will double the space for us. unchecked_append(val) constructs the last element from the argument to push_back function. Correspondingly, we refresh the data members.

+

Flexible memory management

We have basically completed the design of our Vec class template. However, we haven’t talked about the real implementation, that is, how exactly allocate new space. As memtioned in chapter 10, we can dynamically manage memory through built-in operators new and delete (or new[] and delete[]). However, there are several shortcomings if we use such operators to manage memory for our Vec class.

+
    +
  1. if we use new[], it always initialize every element of a T array by using T::T(). If we want to initialize ourselves, we would have to initialize each element twice.
  2. +
  3. if push_back allocates new space, we want to keep the range [avail, limit) uninitialized. However, if we use new[], we cannot control this anymore.
  4. +
+

The standard header provides a class named allocator, that allocates a block of uninitialized memory that intended to contain objects of type T and returns a pointer to the initial element of that memory. In addition, allocator also defines members including functions to construct objects, destroy obejcts and deallocate the memory. Therefore, programmers can manage the allocated space directly and determine the unitialized space. Here introduces four member functions and two non-member functions of the allocator class:

1
2
3
4
5
6
7
8
9
10
11
template <class T> class allocator{
public:
T* allocate(size_t);
void deallocator(T*, size_t);
void construct(T*, const T&);
void destroy(T*);

// ...
};
template<class Out, class T> void uninitialized_fill(Out, Out, const T&);
template<class In, class Out> Out uninitialized_copy(In, In, Out);

+
    +
  1. the allocate member allocates typed but uninitialized storage to hold the requested number of elements. It returns a pointer that has type T and denotes the initial address of the storage.
  2. +
  3. the deallocator frees this uninitialized storage with taking the pointer given by allocate and the size.
  4. +
  5. construct and destroy construct or destroy a single object in the uninitialized space.
  6. +
  7. the first algorithm uninitialized_fill fills this uninitialized space with value from the third argument. The first two arguments denote the range of the space that to be filled.
  8. +
  9. the second algorithm uninitialized_copy copies values from a sequence specified by the first two arguments into a target sequence denoted by the third argument. The range pointed by the third argument should large enough to hold all elements contained in the range specified by the first two arguments. It finally returns a pointer to one past the last constructed element.

    +
  10. +
  11. both two algorithms assumes that the target range contains raw storage rather than elements that already hold values.

    +
  12. +
+

To obtain an allocator of the right type at the compiler time, we’ll add to our Vec class an allocator member. By doing so, we can use above member functions to provide efficient and flexible memory management for our Vec class.

+

The final Vec class

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
template <class T> class Vec{
public:
// member types
typedef T* iterator;
typedef const T* const_iterator;
typedef size_t size_type;
typedef T value_type;

// constructors
Vec() { create(); }
explicit Vec(size_type n, const T& t = T()) { create(n, t); }

// copy constructor, assignment operator, destructor
Vec(const Vec& v) { create(v.begin(), v.end()); }
Vec& operator=(const Vec&);
~Vec() { uncreate(); }

// indexing operator
const T& operator[](size_type i) const { return data[i]; }

// push_back function
void push_back(const T& t){
if(avail == limit)
grow();
unchecked_append(t);
}

// size function
size_type size() const { return avail - data; }

// begin(), end() function
iterator begin() { return data; }
const_iterator begin() const { return data; }
iterator end() { return avail; }
const_iterator end() const { return avail; }

private:
iterator data; // first element in the Vec
iterator avail; // (one past) the last element in the Vec
iterator limit; // (one past) the allocated memory

// facilities for memory allocation
allocator<T> alloc; // object to handle memory allocation

// allocate and initialize the underlying array
void create();
void create(size_type, const T&);
void create(const_iterator, const iterator);

// destroy the elements in the array and free the memory
void uncreate();

// support functions for push_back
void grow();
void unchecked_append(const T&);
};
+

We should note that there are four conditions (aka. class invariants) that guarantees a valid Vec:

+
    +
  1. data points at our initial element, if we have any, and is zero otherwise.
  2. +
  3. data <= avail <= limit.
  4. +
  5. Elements have been constructed in the range[data, avail).
  6. +
  7. Elements have not been constructed in the range[avail, limit).
  8. +
+

Now the next is to write the implementation of different version of create functions while maintaining above class invariants.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
template <class T> void Vec<T>::create()
{
data = avail = limit;
}

template <class T> void Vec<T>::create(size_type n, const T& val)
{
data = alloc.allocate(n);
limit = avail = data + n;
uninitialized_fill(data, limit, val);
}

template <class T> void Vec<T>::create(const_iterator i, const_iterator, j)
{
data = alloc.allocate(j - i);
limit = avail = uninitialized_copy(i, j, data);
}

+

The first version of create is used for initializing an empty Vec. The second one that takes a size and a value creates a Vec by allocating enough memory to hold n elements through alloc.allocate(n), and initializes all elements with val by applying the algorithm uninitialized_fill. The third version is used for copy-initialization, which takes two iterators that denote the sequence from which to copy. It calls uninitialized_copy algorithm to copy all values of the elements in [i, j) into [data, avail).

+

The destructor calls the uncreate member to destroy the elements and free the space that allocated by create.

1
2
3
4
5
6
7
8
9
10
11
12
template <class T> void Vec<T>::uncreate()
{
if(!data){
// destroy the elements in reverse order
iterator it = avail;
while(it != data)
alloc.destroy(--it);
alloc.deallocate(data, limit - data);
}
// reset pointers to indicate that Vec is empty again
data = limit = avail = 0;
}

+

The uncreate function first checks whether the data is 0. This is because that alloc.deallocate requires a non-zero pointer. There are two steps to destruct the Vec: the first step is that calling the destroy function to destroy each object contained in the Vec; the second step is that calling deallocate function to free the previous allocated storage. As deallocate doesn’t destroy elements in an array, it is crucial to call destroy function first which calls the destructor of the target element to release resource that might be occupied by the target element. It is known that there is no destructor for built-in types, so how does the destroy function work? It is presumed that the destroy function treats built-in objects and other objects in different manner. It still needs further research.

+

Finally, we write functions to support our push_back member.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
template <class T> void Vec<T>::grow()
{
// when growing, allocate twice as much as space as currently in use
size_type new_size = max(2*(limit-data), ptrdiff_t(1));

// allocate new space and copy existing elements to the new space
iterator new_data = alloc.allocate(new_size);
iterator new_avail = uninitialized_copy(data, avail, new_data);

// return the old space
uncreate();

// reset pointers to point to the newly allocated space
data = new_data;
avail = new_avail;
limit = data + new_size;
}

// assumes avail points at allocated, but uninitialized space
template <class T> void Vec<T>::unchecked_append(const T& val)
{
alloc.construct(avail++, val);
}

+

Now we have really completed our Vec class. The next post presents some tests on our Vec type from different perspectives.

+ + +
+ + + + + + + + + + + +
+ + + +
+ + + +
+ +
+
+ + +
+ + + + + + + + + +
+ + + + + + + + + +
+
+ + + + +
+ + +
+ + + + +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/14/05/2018/C-Revisiting-character-pictures/index.html b/14/05/2018/C-Revisiting-character-pictures/index.html new file mode 100644 index 00000000..7753ae13 --- /dev/null +++ b/14/05/2018/C-Revisiting-character-pictures/index.html @@ -0,0 +1,768 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + C++ - Revisiting character pictures | Liam's Blog + + + + + + + + + + + + + + + + + + +
+
+ + + +
+
+
+
+ + +
+ + + + + + + + +
+ + + +
+ + + + + + + +
+ + + +

C++ - Revisiting character pictures

+ + + +
+ + + + + +
+ + + + + + + +
+ + + + + + + + + + + +
+ + + +
+ + + +
+ +
+
+ + +
+ + + + + + + + + +
+ + + + + + + + + +
+
+ + + + +
+ + +
+ + + + +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/14/05/2018/Sorting-Algorithms-C-Implementations-Bubble-Sort/index.html b/14/05/2018/Sorting-Algorithms-C-Implementations-Bubble-Sort/index.html new file mode 100644 index 00000000..06656ef8 --- /dev/null +++ b/14/05/2018/Sorting-Algorithms-C-Implementations-Bubble-Sort/index.html @@ -0,0 +1,797 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Sorting Algorithms C++ Implementations - Bubble Sort | Liam's Blog + + + + + + + + + + + + + + + + + + +
+
+ + + +
+
+
+
+ + +
+ + + + + + + + +
+ + + +
+ + + + + + + +
+ + + +

Sorting Algorithms C++ Implementations - Bubble Sort

+ + + +
+ + + + + +
+ + + + + +

Bubble Sort

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
/*-----------------------------------------------------------------------------
* main.cpp || Created on: 13 May 2018 || Author: Liam
*-----------------------------------------------------------------------------
* this program tests the bubble sort algorithm implemented in three ways
*
* Logic:
* 1. each time find the largest number of the unsorted sequence and put it at
* the last position of the sequence. Then the unsorted sequence becomes the
* sequence, which is the original sequence excludes the last element.
* * compare adjacent numbers one pair by one pair from the begining
* position to the second last position, and exchange the position of
* two numbers if left-side number is larger than the right-side number
* * after each iteration, right-most position of the unsorted sequence
* holds the largest element, and hence the unsorted sequence becomes
* the sequence, which is the original sequence excludes the last
* element.
* 2. repectively perform step 1. There are two cases that the iteration stops:
* * the unsorted sequence only has one number left
* * there is no any exchange happens in last iteration, which means that
* all elements are in order already. Therefore, we do not need to
* perform step 1 again. In this case, we get the best time complexity
* if the sequence is completely sorted after the first iteration.
*
* Complexity analysis:
* time complexity: Best = O(n), Average, Worst = O(n^2)
* auxiliary space: worst-case = O(1)
*-----------------------------------------------------------------------------
*/
+

Implementations

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
 #ifndef SORTINGALGORITHMS_H_
#define SORTINGALGORITHMS_H_

#include <cstddef>
#include <algorithm>
#include <iterator>

// array based version
template <typename T>
void BubbleSort(T* p, std::size_t n){
if (n == 0) return;
for(std::size_t i = n-1; i != 0; --i){
bool flag = true;
for(std::size_t j = 0; j != i; ++j){
if(p[j] > p[j+1]){
T temp = p[j];
p[j] = p[j+1];
p[j+1] = temp;
flag = false;
}
}
if(flag == true) break;
}
}

// iterator based version STL style (c++11)
template <typename BidirectionalIterator>
void BubbleSort(BidirectionalIterator begin, BidirectionalIterator end){
if(begin == end) return;
for(auto endIter = std::prev(end); endIter != begin; --endIter){
bool flag = true;
for(auto iter = begin; iter != endIter; ++iter){
auto iterNext = std::next(iter);
if(*iter > *iterNext){
std::iter_swap(iter, iterNext);
flag = false;
}
}
if(flag == true) break;
}
}

// iterator based version with user-defined comparator
template <typename BidirectionalIterator, typename Comparator>
void BubbleSort(BidirectionalIterator begin, BidirectionalIterator end,
Comparator comp){
if(begin == end) return;
for(auto endIter = std::prev(end); endIter != begin; --endIter){
bool flag = true;
for(auto iter = begin; iter != endIter; ++iter){
auto iterNext = std::next(iter);
if(comp(*iterNext, *iter)){
std::iter_swap(iter, iterNext);
flag = false;
}
}
if(flag == true) break;
}
}

#endif /* SORTINGALGORITHMS_H_ */

+

Test Program-main.cpp

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
65
66
67
68
69
70
71
#include <iostream>
#include <vector>
#include <string>
#include <list>
#include "SortingAlgorithms.h"

using std::cout; using std::vector;
using std::endl; using std::string;
using std::list; using std::cin;

// struct defined for testing
struct student{
string name;
int age;
};

// comparator 1
template<class T>
bool compare_age(const T& x, const T& y){
return x.age < y.age;
}

// comparator 2
template<class T>
bool compare_name(const T& x, const T& y){
return x.name < y.name;
}

int main(){
// test 1: array-based version
double arr[10] = {10, 9, 8, 7, 6, 5, 4, 3, 2, 1};
BubbleSort(arr, arr+ 10);
cout << "Sorted array: ";
for(int i = 0; i != 10; ++i)
cout << arr[i] << " ";
cout << "\n";

// test 2: iterator-based version
string str("eclipseworkspace");
BubbleSort(str.begin(), str.end());
cout << "Sorted string " << str << "\n";

list<int> l{10, 9, 8, 7, 6, 5, 4, 3, 2, 1};
BubbleSort(l.begin(), l.end());
cout << "Sorted list: ";
for (auto i: l)
cout << i << " ";

cout << endl;

// test 3: user-defined comparator
vector<student> students;
while(cin){
student temp;
cin >> temp.name >> temp.age;
if(cin)
students.push_back(temp);
}

BubbleSort(students.begin(), students.end(), compare_age<student>);
cout << "Sorted vector according to age:\n";
for (auto i: students)
cout << i.name << " " << i.age << "\n";

BubbleSort(students.begin(), students.end(), compare_name<student>);
cout << "Sorted vector according to name:\n";
for (auto i: students)
cout << i.name << " " << i.age << "\n";

return 0;
}

+

Outputs:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
Sorted array: 1 2 3 4 5 6 7 8 9 10 
Sorted string acceeeiklopprssw
Sorted list: 1 2 3 4 5 6 7 8 9 10
John 18
Mike 16
Liam 23
Anna 26
Bobo 21
Sorted vector according to age:
Mike 16
John 18
Bobo 21
Liam 23
Anna 26
Sorted vector according to name:
Anna 26
Bobo 21
John 18
Liam 23
Mike 16

+ + +
+ + + + + + + + + + + +
+ + + +
+ + + +
+ +
+
+ + +
+ + + + + + + + + +
+ + + + + + + + + +
+
+ + + + +
+ + +
+ + + + +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/15/04/2018/Implementing-the-C-STL-Algorithms-Part-1-Simple-Find-Algorithms/index.html b/15/04/2018/Implementing-the-C-STL-Algorithms-Part-1-Simple-Find-Algorithms/index.html new file mode 100644 index 00000000..daacdf10 --- /dev/null +++ b/15/04/2018/Implementing-the-C-STL-Algorithms-Part-1-Simple-Find-Algorithms/index.html @@ -0,0 +1,819 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Implementing the C++ STL Algorithms-Part 1: Simple Find Algorithms | Liam's Blog + + + + + + + + + + + + + + + + + + +
+
+ + + +
+
+
+
+ + +
+ + + + + + + + +
+ + + +
+ + + + + + + +
+ + + +

Implementing the C++ STL Algorithms-Part 1: Simple Find Algorithms

+ + + +
+ + + + + +
+ + + + + +

find(beg, end, val)

Possible implementation

1
2
3
4
5
6
7
template <class InputIterator, class T>
InputIterator find(InputIterator beg, InputIterator end, const T& val)
{
while(beg != end && *beg != val)
++beg;
return beg;
}
+

Key points

    +
  1. parameters beg and end are two Input iterators,denoting that the range searched is [beg, end). val is the value to search for in the range.
  2. +
  3. the algorithm returns an iterator to the first element in the range [beg, end) equal to val. If no such element is found, the function returns end.
  4. +
  5. pointers are random access iterators and hence are also valid input iterators. Therefore, the algorithm can also be applied to the built-in array.
  6. +
  7. complexity: linear
  8. +
+

Test 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
// test my find algorithm
#include <iostream> // cout, endl
#include <vector> // vector
#include <cstring> // strlen
#include "my_algorithms.h" // my_find

using std::cout; using std::vector;
using std::endl; using std::find;
using std::strlen;

int main()
{
// to find an int type element in a vector
vector<int> vec{2, 4, 87, 9, 35, 77, 60};
vector<int>::iterator it = my_find(vec.begin(), vec.end(), 60);
if(it != vec.end())
cout << "Element is found in vec: " << *it << endl;
else
cout << "Element is not found in vec" << endl;

// to find an char type element in an array
char arr[] = "computational";
char* p = my_find(arr, arr+strlen(arr), 'u');
if(p != arr+strlen(arr))
cout << "Element is found in arr: " << *p << endl;
else
cout << "Element is not found in arr" << endl;
return 0;
}
+

Outputs

1
2
Element is found in vec: 60
Element is found in arr: u

+
+

find_if(beg, end, UnaryPred)

Possible implementation

1
2
3
4
5
6
7
template <class InputIterator, class UnaryPred>
InputIterator my_find_if(InputIterator beg, InputIterator end, UnaryPred pred)
{
while(beg != end && !pred(*beg))
++beg;
return beg;
}
+

Key points

    +
  1. beg and end are two Input iterators denoting that the range searched is [beg, end). UnaryPred is a predicate on elements in the range. Each time it takes one of the elements, and then returns a value convertible to bool.
  2. +
  3. the algorithm returns an iterator to the first element in the range for which the pred returns true. If there is no such element, the function returns end.
  4. +
  5. there is no way to copy, assign, or pass a function as an argument directly due to a function is not an object. In fact, when we pass a function, the compiler uses the pointer to function instead of using the function directly. In addition, we can call a pointer to a function with or withour deferencing the pointer. Therefore, in this function template, the argument can either be a function “object”, that is, UnaryPred pred; or a function pointer, that is, UnaryPred* pred; or a function reference, that is, UnaryPred& pred. All these three cases allows us to call the function through pred(*beg).
  6. +
  7. complexity: linear
  8. +
+

Test 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
#include <iostream>			// cout, endl
#include <vector> // vector
#include <cstring> // strlen
#include <cctype> // isupper
#include "my_algorithms.h" // my_find_if

using std::cout; using std::vector;
using std::endl; using std::find;
using std::strlen; using std::isupper;

// the predication 1
bool IsEven(const int &i)
{
return i % 2 == 0;
}

// the predication 2
bool Isupper(const char &c)
{
return isupper(c);
}

int main()
{
// find the first even number in vec
vector<int> vec{2, 4, 87, 9, 35, 77, 60};
vector<int>::iterator it = my_find_if(vec.begin(), vec.end(), IsEven);
if(it != vec.end())
cout << "The first even number in vec is: " << *it << endl;
else
cout << "There is no even number in vec" << endl;

// find the first upper-case letter in arr
char arr[] = "abceFghI";
char* p = my_find_if(arr, arr + strlen(arr), Isupper);
if(p != arr + strlen(arr))
cout << "The first upper-case letter in arr is: " << *p << endl;
else
cout << "There is no upper-case letter in arr" << endl;

return 0;
}
+

Outputs

1
2
The first even number in vec is: 2
The first upper-case letter in arr is: F

+

find_if_not(beg, end, UnaryPred)

Possible implementation

1
2
3
4
5
6
7
template <class InputIterator, class UnaryPred>
InputIterator my_find_if_not(InputIterator beg, InputIterator end, UnaryPred pred)
{
while(beg != end && pred(*beg))
++beg;
return beg;
}
+

In contrary to the find_if algorithm, this function returns an iterator to the first element in the range for which pred returns false. If pred returns true for all elements, the function returns end.

+

Test program

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
#include <iostream>			// cout, endl
#include <vector> // vector
#include "my_algorithms.h" // my_find_if_not
bool IsEven(const int &i)
{
return i % 2 == 0;
}

int main()
{
// find the first even number in vec
vector<int> vec{2, 4, 87, 9, 35, 77, 60};
vector<int>::iterator it = my_find_if_not(vec.begin(), vec.end(), IsEven);
if(it != vec.end())
cout << "The first odd number in vec is: " << *it << endl;
else
cout << "There is no odd number in vec" << endl;
return 0;
}
+

Outputs

1
The first odd number in vec is: 87

+
+

count(beg, end, UnaryPred)

+ +
+ + + + + + + + + + + +
+ + + +
+ + + +
+ +
+
+ + +
+ + + + + + + + + +
+ + + + + + + + + +
+
+ + + + +
+ + +
+ + + + +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/15/05/2018/Sorting-Algorithms-C-Implementations-Insertion-Sort/index.html b/15/05/2018/Sorting-Algorithms-C-Implementations-Insertion-Sort/index.html new file mode 100644 index 00000000..c5db7abc --- /dev/null +++ b/15/05/2018/Sorting-Algorithms-C-Implementations-Insertion-Sort/index.html @@ -0,0 +1,797 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Sorting Algorithms C++ Implementations - Insertion Sort | Liam's Blog + + + + + + + + + + + + + + + + + + +
+
+ + + +
+
+
+
+ + +
+ + + + + + + + +
+ + + +
+ + + + + + + +
+ + + +

Sorting Algorithms C++ Implementations - Insertion Sort

+ + + +
+ + + + + +
+ + + + + +

Insertion Sort

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
/*----------------------------------------------------------------------------- 
* main.cpp || Created on: 15 May 2018 || Author: Liam
*-----------------------------------------------------------------------------
* this program tests three versions of insertion sort implementations.
*
* Logic:
* 1. the sequence can always be divided into two parts: sorted and unsorted.
* We loop thru from the second position to the last position, before each
* loop, the elements on the left-side of the position are sorted. we can
* call this position as sortedIndex. After each loop, [0, sortedIndex] is
* sorted, and then we forward the sortedIndex 1 position.
* 2. in each loop, an embeded iteration starts from the initial position to
* the prev of the sortedIndex, or in reverse order, from the prev of the
* sortedIndex to the begining, comparing each value denoted in the range
* with the value denoted by sortedIndex. When found the first element that
* is greater than the value denoted by sortedIndex, we insert here
* the element denoted by sortedIndex.
*
* Complexity analysis:
* time complexity: Best = O(n), Average, Worst O(n^2)
* auxiliary space: worst-case = O(1)
*
*-----------------------------------------------------------------------------
*/
+

Implementations

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
#ifndef SORTINGALGORITHMS_H_
#define SORTINGALGORITHMS_H_

#include <cstddef>
#include <algorithm>
#include <iterator>

// array-based version
template <class T>
void InsertionSort(T* p, std::size_t n){
if (n == 0) return;
for(std::size_t i = 1; i != n; ++i){
T value = p[i];
std::size_t sortedIndex = i;
// the left-side of the sortedIndex is sorted
while(sortedIndex >0 && p[sortedIndex - 1] > value){
p[sortedIndex] = p[sortedIndex - 1];
sortedIndex = sortedIndex - 1;
}
p[sortedIndex] = value;
}
}

// iterator-based version STL-stype (C++11)
template <typename ForwardIterator>
void InsertionSort(ForwardIterator begin, ForwardIterator end){
if(begin == end) return;
for (auto iter = std::next(begin); iter != end; ++iter){
auto insertPoint = std::upper_bound(begin, iter, *iter); // logN
std::rotate(insertPoint, iter, std::next(iter)); // N
}
}

// iterator-based version with comparator
template <typename ForwardIterator, typename Comparator>
void InsertionSort(ForwardIterator begin, ForwardIterator end,
Comparator comp){
if(begin == end) return;
for (auto iter = std::next(begin); iter != end; ++iter){
auto insertPoint = std::upper_bound(begin, iter, *iter, comp);
std::rotate(insertPoint, iter, std::next(iter));
}
}

#endif /* SORTINGALGORITHMS_H_ */

+

Test program-main.cpp

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
65
66
67
68
69
70
71
#include <iostream> // std::cin, cout, endl
#include <vector> // std::vector
#include <string> // std::string
#include <list> // std::list
#include "SortingAlgorithms.h"

using std::cout; using std::vector;
using std::endl; using std::string;
using std::list; using std::cin;

// struct defined for testing
struct student{
string name;
int age;
};

// comparator 1
template<class T>
bool compare_age(const T& x, const T& y){
return x.age < y.age;
}

// comparator 2
template<class T>
bool compare_name(const T& x, const T& y){
return x.name < y.name;
}

int main(){
// test 1: array-based version
double arr[10] = {10, 9, 8, 7, 6, 5, 4, 3, 2, 1};
InsertionSort(arr, arr+ 10);
cout << "Sorted array: ";
for(int i = 0; i != 10; ++i)
cout << arr[i] << " ";
cout << "\n";

// test 2: iterator-based version
string str("eclipseworkspace");
InsertionSort(str.begin(), str.end());
cout << "Sorted string " << str << "\n";

list<int> l{10, 9, 8, 7, 6, 5, 4, 3, 2, 1};
InsertionSort(l.begin(), l.end());
cout << "Sorted list: ";
for (auto i: l)
cout << i << " ";

cout << endl;

// test 3: user-defined comparator
vector<student> students;
while(cin){
student temp;
cin >> temp.name >> temp.age;
if(cin)
students.push_back(temp);
}

InsertionSort(students.begin(), students.end(), compare_age<student>);
cout << "Sorted vector according to age:\n";
for (auto i: students)
cout << i.name << " " << i.age << "\n";

InsertionSort(students.begin(), students.end(), compare_name<student>);
cout << "Sorted vector according to name:\n";
for (auto i: students)
cout << i.name << " " << i.age << "\n";

return 0;
}

+

Outputs

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
Sorted array: 1 2 3 4 5 6 7 8 9 10 
Sorted string acceeeiklopprssw
Sorted list: 1 2 3 4 5 6 7 8 9 10
John 18
Mike 16
Liam 23
Anna 26
Bobo 21
Sorted vector according to age:
Mike 16
John 18
Bobo 21
Liam 23
Anna 26
Sorted vector according to name:
Anna 26
Bobo 21
John 18
Liam 23
Mike 16

+ + +
+ + + + + + + + + + + +
+ + + +
+ + + +
+ +
+
+ + +
+ + + + + + + + + +
+ + + + + + + + + +
+
+ + + + +
+ + +
+ + + + +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/17/03/2018/Accelerated-C-Solutions-to-Exercises-Chapter-5-Part-2/index.html b/17/03/2018/Accelerated-C-Solutions-to-Exercises-Chapter-5-Part-2/index.html new file mode 100644 index 00000000..baba0a34 --- /dev/null +++ b/17/03/2018/Accelerated-C-Solutions-to-Exercises-Chapter-5-Part-2/index.html @@ -0,0 +1,890 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Accelerated C++ Solutions to Exercises(Chapter 5 Part 2) | Liam's Blog + + + + + + + + + + + + + + + + + + +
+
+ + + +
+
+
+
+ + +
+ + + + + + + + +
+ + + +
+ + + + + + + +
+ + + +

Accelerated C++ Solutions to Exercises(Chapter 5 Part 2)

+ + + +
+ + + + + +
+ + + + + +

Exercise 5-2, 5-3, 5-4

5-2: Write the complete new version of the student-grading program, which extracts records for failing students, using vectors. Write another that uses lists. Measure the performance difference on input files of ten lines, 1,000 lines, and 10,000 lines.

+

5-3: By using a typedef, we can write one version of the program that implements either a vector-based solution or a list-based one. Write and test this version of the program.

+

5-4: Look again at the driver functions you wrote in the previous exercise. Note that it ispossible to write a driver that differs only in the declaration of the type for the data structure that holds the input file. If your vector and list test drivers differ in any other way, rewrite them so that they differ only in this declaration.

+

Solution

I’ll give solution to 5-4 directly as these three exercises are closely connected. The strategy can be divided into three steps:

+
    +
  1. write drivers for using list or vector to hold input files.
  2. +
  3. add the function to extract records for failing students, and add the drivers for files where the declaration of specified container is needed.
  4. +
  5. change indices to iterators if necessary because list doesn’t support access elements via indices.
  6. +
  7. measure the performance difference of two containers on input files of 10 lines, 1000 lines and 10000 lines.
  8. +
+

Step 1

To switch the use of list and vector, I follows the suggestion of 5-3;

1
2
// typedef list<Student_info> info;
typedef vector<Student_info> info;

+

The alias info can represent either the type list or vector and hence allows us switch from one version to another simply via modifying this declaration.

+

However, the type declared above is not only used in the file that contains the main function, but may also needed in other files. Therefore, I separate the declaration from the main function and create a header for this reason.
info.h

1
2
3
4
5
6
7
8
9
10
11
#ifndef GUARD_INFO_H
#define GUARD_INFO_H

#include <list>
#include <vector>
#include "Student_info.h"

//typedef std::vector<Student_info> info;
typedef std::list<Student_info> info;

#endif /* GUARD_INFO_H */

+

All files that want to use the name info include the header where declares the name info.

+

Step 2

This step adds two functions: one is to extract the records for failing students; another one is a predicate on failing grades.
It can be observed that the header “info.h” is included for the purpose of defining the container fail to holding information of the failing students.
fails.cpp

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
#include "Student_info.h"
#include "grade.h"
#include "fails.h"
#include "info.h"

// the predicate for students who failed
bool fgrade(const Student_info &s)
{
return grade(s) < 60;
}

// function to extract the failed student records
info extract_fails(info &students)
{
info fail;
info::iterator iter = students.begin();

while(iter != students.end())
{
if(fgrade(*iter))
{
fail.push_back(*iter);
iter = students.erase(iter);
}
else
{
++iter;
}
}
return fail;
}

+

fails.h

1
2
3
4
5
6
7
8
9
10
#ifndef GUARD_FAILS_H
#define GUARD_FAILS_H

#include "Student_info.h"
#include "info.h"

bool fgrade(const Student_info &s);
info extract_fails(info &students);

#endif /* GUARD_FAILS_H*/

+

Step 3

The last change needs to be done is using iterators instead of indices. To avoid messy, I rewrite the output section as a function named print.

+

print.cpp

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>
#include <iomanip>
#include <string>
#include "grade.h"
#include "print.h"
#include "info.h"

using std::cout; using std::setprecision;
using std::endl; using std::streamsize;
using std::string;

void print(const info &records, const string::size_type &maxlen)
{
for (info::const_iterator iter = records.begin(); iter != records.end(); ++iter)
{
// write the name, blanks
cout << (*iter).name << string(maxlen + 1 - (*iter).name.size(), ' ');

// compute and write the final grade
double final_grade = grade(*iter);
streamsize prec = cout.precision();
cout << setprecision(3) << final_grade << setprecision(prec) << endl;
}
}

+

print.h

1
2
3
4
5
6
7
8
9
#ifndef GUARD_PRINT_H
#define GUARD_PRINT_H

#include <string>
#include "info.h"

void print(const info &records, const std::string::size_type &maxlen);

#endif /* GUARD_PRINT_H */

+

Note that I didn’t add try block here as the exceptions may be thrown early in the process of extracting the failing records.

+

Step 4

To measure the performance of the vector based program and list based program, I uses members of the time library . Specifically:

1
2
3
4
5
6
7
8
typedef std::chrono::high_resolution_clock Clock;
Clock::time_point startTime = Clock::now(); // get current time
info fails = extract_fails(students); // extract records for failing students
Clock::time_point endTime = Clock::now(); // get current time

cout << "It took me "
<< std::chrono::duration_cast<std::chrono::seconds>(endTime - startTime).count()
<< " seconds" << endl;

+

The usage of std::chrono::high_resolution_clock refers to
high_resolution_clock.

+

A complete program

By now, I have introduced the main changes relative to the original program. Therefore, the complete program includes following files

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
header filessource files
info.h
fails.hfails.cpp
print.hprint.cpp
Student_info.hStudent_info.cpp
grade.hgrade.cpp
mainfunction.cpp
+

I present the rest files in below. Noting that there are two major differences between the vector-based version and the list-based version. First is the declaration metioned above. The second is that the usage of sort function for two types of containers are different, for vectors, the statement is

1
sort(students.begin(), students.end(), compare);

+

while for lists,

1
students.sort(compare);

+

Strictly speaking, my solution doesn’t meet the requirements of 5-4. But I haven’t find a better one.

+

mainfunction.cpp

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
65
66
67
68
69
70
71
72
73
74
75
76
#include <algorithm>		// to get declaration of max, sort
#include <iostream> // to get declaration of cin, cout, endl
#include <stdexcept> // to get declaration of domain_error
#include <string> // to get declaration of string
#include <chrono>
#include "Student_info.h"
#include "grade.h"
#include "fails.h"
#include "print.h"
#include "info.h"


using std::cin; using std::string;
using std::cout; using std::max;
using std::endl; using std::sort;
using std::domain_error;

int main()
{
info students;
Student_info record;
string::size_type maxlen = 0;

// read and store all the records, and find the length of the longest name
while(read(cin, record))
{
maxlen = max(maxlen, record.name.size());
students.push_back(record);
}

try{
// measure the performance
typedef std::chrono::high_resolution_clock Clock;
Clock::time_point startTime = Clock::now(); // get current time
info fails = extract_fails(students); // extract records for failing students
Clock::time_point endTime = Clock::now(); // get current time

cout << "It took me "
<< std::chrono::duration_cast<std::chrono::seconds>(endTime - startTime).count()
<< " seconds" << endl;

// write each line of outputs for passing students
if (!students.empty())
{
//alphabetize the records

//sort(students.begin(), students.end(), compare);
students.sort(compare);
cout << "Students who passed: " << endl;
print(students, maxlen);
}
else
cout << "What a pity! all students failed.";

// write a blank line
cout << endl;

// // write each line of outputs for failing students
if(!fails.empty())
{
//alphabetize the records

//sort(fails.begin(), fails.end(), compare);
fails.sort(compare);
cout << "Students who failed: " << endl;
print(fails, maxlen);
}
else
cout << "Congratulations! all students passed.";

}catch(domain_error e){
cout << e.what();
}

return 0;
}

+

Student_info.cpp

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 <vector>
#include <iostream>
#include "Student_info.h"

using std::vector; using std::istream;

bool compare(const Student_info &x, const Student_info &y)
{
return x.name < y.name;
}

istream & read(istream &is, Student_info &s)
{
// reads and store the student's name, midterm and final exam grades
is >> s.name >> s.midterm >> s.final;

// reads and store all homework grades
read_hw(is, s.homework);
return is;
}

istream & read_hw(istream &in, vector<double> &hw)
{
if (in){
//get rid of previous contents
hw.clear();

// read homework grades
double x;
while(in >> x)
hw.push_back(x);

// clear the stream so that input will work for the next student
in.clear();
}
return in;
}

+

Student_info.h

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
#ifndef GUARD_Student_info
#define GUARD_Student_info

// Student_info.header file
#include<iostream>
#include<string>
#include<vector>

struct Student_info{
std::string name;
double midterm, final;
std::vector<double> homework;
};

bool compare(const Student_info &, const Student_info &);
std::istream & read(std::istream &, Student_info &);
std::istream & read_hw(std::istream &, std::vector<double> &);
#endif

+

grade.cpp

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 <algorithm>
#include <stdexcept>
#include <vector>
#include "grade.h"
#include "Student_info.h"

using std::domain_error; using std::istream;
using std::vector; using std::sort;

// grade function 1
double grade(const Student_info &s)
{
return grade(s.midterm, s.final, s.homework);
}

// grade function 2
double grade(double midterm, double final, const vector<double> &hw)
{
if (hw.size() == 0)
throw domain_error("student has done no homework");
return grade(midterm, final, median(hw));
}

// grade function 3
double grade(double midterm, double final, double homework)
{
return 0.2*midterm + 0.4*final + 0.4*homework;
}

// compute the median of vector<double>
double median(vector<double> vec)
{
// get the size of the vector
typedef vector<double>::size_type vec_size;
vec_size size = vec.size();

// check whether the empty is empty
if (size == 0)
throw domain_error("median of an empty vector");

// sort the grades
sort(vec.begin(), vec.end());

// compute the median homework grade
vec_size mid = size/2;
return size % 2 == 0 ? (vec[mid] + vec[mid - 1])/2 : vec[mid];
}

+

grade.h

1
2
3
4
5
6
7
8
9
10
11
12
#ifndef GUARD_grade_h
#define GUARD_grade_h

// grade.h
#include<vector>
#include "Student_info.h"

double grade(const Student_info &);
double grade(double, double, const std::vector<double> &);
double grade(double, double, double);
double median(std::vector<double>);
#endif

+

Results

I also wrote a naive program that “randomly” generates thousands of names and grades.

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
#include <cstdlib>
#include <iostream>
#include <string>

using std::cout; using std::endl;
using std::string;

int main()
{
string initials{"ABCDEFGHIJKLMNOPQRSTUVWXYZ"};
string letters{"abcdefghijklmnopqrstuvwxyz"};

for (int i = 0; i != 10000; ++i)
{
int x = rand() % 26;
int y = rand() % 26;
int z = rand() % 26;
int p = rand() % 26;
int q = rand() % 26;
double midterm = rand() % 100 + (rand() % 100)/99.0;
double final = rand() % 100 + (rand() % 100)/99.0;
double homework = rand() % 100 + (rand() % 100)/99.0;

string name{initials[x], letters[y], letters[z], letters[p], letters[q]};
cout << name << ' ' << midterm << ' ' << final << ' ' << homework << endl;
}

return 0;
}

+

The table below gives the performance difference of two version pograms on input files of ten lines, 1,000 lines, and 10,000 lines.

+ + + + + + + + + + + + + + + + + + + + + + + + + +
Number of linesvectorlist
100.00107 seconds0.00007 seconds
10000.136711 seconds0.003036 seconds
100000.826406 seconds0.015598 seconds
+

Apparently, the list based program has much better performance than the vector based program, with using much less time in extracting the failing students’ records regardless of the file size. In addition, along with the increase of file size, the time taken by the list version program increases much slower than that taken by the vector version.

+
+

To be continued.

+ + +
+ + + + + + + + + + + +
+ + + +
+ + + +
+ +
+
+ + +
+ + + + + + + + + +
+ + + + + + + + + +
+
+ + + + +
+ + +
+ + + + +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/17/05/2018/Sorting-Algorithms-C-Implementations-Merge-Sort/index.html b/17/05/2018/Sorting-Algorithms-C-Implementations-Merge-Sort/index.html new file mode 100644 index 00000000..7547be65 --- /dev/null +++ b/17/05/2018/Sorting-Algorithms-C-Implementations-Merge-Sort/index.html @@ -0,0 +1,797 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Sorting Algorithms C++ Implementations - Merge Sort | Liam's Blog + + + + + + + + + + + + + + + + + + +
+
+ + + +
+
+
+
+ + +
+ + + + + + + + +
+ + + +
+ + + + + + + +
+ + + +

Sorting Algorithms C++ Implementations - Merge Sort

+ + + +
+ + + + + +
+ + + + + +

Merge Sort

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
/*----------------------------------------------------------------------------- 
* main.cpp || Created on: 16 May 2018 || Author: Liam
*-----------------------------------------------------------------------------
* this program tests three implementation of the merge sort algorithm.
*
* Logic:
* 1. recursively partition the sequence a mid point, until that there is only
* one element left, that is when it cannot be partitioned further.
* 2. each pair of partitioned parts will be rearranged in non-decreasing order.
*
* Complexity analysis:
* time complexity: Best, Average, Worst = O(nlogn)
* auxiliary space: worst-case = O(n)
*
*-----------------------------------------------------------------------------
*/
+

Implementations:

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
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
#ifndef SORTINGALGORITHMS_H_
#define SORTINGALGORITHMS_H_

#include <cstddef>
#include <vector>
#include <iterator>
#include <algorithm>

// array-based version
template <typename T>
void Merge(T* left, std::size_t left_size, T* right, std::size_t right_size){
T left_copy[left_size];
T right_copy[right_size];

/* O(n): n = left_size + right_size */
for(std::size_t i = 0; i != left_size; ++i)
left_copy[i] = left[i];
for(std::size_t i = 0; i != right_size; ++i)
right_copy[i] = right[i];

/* O(n): n = left_size + right_size */
std::size_t i, j, k;
i = j = k = 0;
while(i != left_size && j != right_size){
if(left_copy[i] <= right_copy[j]){
left[k] = left_copy[i];
++i;
}else{
left[k] = right_copy[j];
++j;
}
++k;
}

while(i != left_size){
left[k] = left_copy[i];
++i;
++k;
}

while(j != right_size){
left[k] = right_copy[j];
++j;
++k;
}
}

template <typename T>
void MergeSort(T* p, std::size_t n){
std::size_t mid = n/2;
if(mid == 0) return;

MergeSort(p, mid);
MergeSort(p + mid, n - mid);

/* O(n): n = n*/
Merge(p, mid, p + mid, n - mid);
}

// iterator-based version STL style (C++11)
template <typename ForwardIterator>
void Merge(ForwardIterator begin, ForwardIterator midIterator,
ForwardIterator end){
typedef typename std::iterator_traits<ForwardIterator>::value_type Type;

/* O(n), n = end - begin*/
std::vector<Type> left(begin, midIterator);
std::vector<Type> right(midIterator, end);

auto iter_l = left.begin();
auto iter_r = right.begin();

/* O(n), n = end - begin*/
while(iter_l != left.end() && iter_r != right.end()){
*begin++ = *iter_l <= *iter_r ? *iter_l++ : *iter_r++;
}

std::copy(iter_l, left.end(), begin);
std::copy(iter_r, right.end(), begin);
}

template <typename ForwardIterator>
void MergeSort(ForwardIterator begin, ForwardIterator end){

// O(1) for random access iterator
// O(n) for others: n = end - begin
auto mid = std::distance(begin, end)/2;
if(mid == 0) return;

// O(1) for random access iterator
// O(n) for others: n = end - begin
auto midIterator = std::next(begin, mid);
MergeSort(begin, midIterator);
MergeSort(midIterator, end);

// O(n): n = end - begin
Merge(begin, midIterator, end);
}

// iterator-based version with user-defined comparator
template <typename ForwardIterator, typename Comparator>
void Merge(ForwardIterator begin, ForwardIterator midIterator,
ForwardIterator end, Comparator comp){
typedef typename std::iterator_traits<ForwardIterator>::value_type Type;

/* O(n), n = end - begin*/
std::vector<Type> left(begin, midIterator);
std::vector<Type> right(midIterator, end);

auto iter_l = left.begin();
auto iter_r = right.begin();

/* O(n), n = end - begin*/
while(iter_l != left.end() && iter_r != right.end()){
*begin++ = comp(*iter_l, *iter_r) ? *iter_l++ : *iter_r++;
}

std::copy(iter_l, left.end(), begin);
std::copy(iter_r, right.end(), begin);

}

template <typename ForwardIterator, typename Comparator>
void MergeSort(ForwardIterator begin, ForwardIterator end, Comparator comp){

// O(1) for random access iterator
// O(n) for others: n = end - begin
auto mid = std::distance(begin, end)/2;
if(mid == 0) return;

// O(1) for random access iterator
// O(n) for others: n = end - begin
auto midIterator = std::next(begin, mid);
MergeSort(begin, midIterator, comp);
MergeSort(midIterator, end, comp);

// O(n): n = end - begin
Merge(begin, midIterator, end, comp);
}

#endif /* SORTINGALGORITHMS_H_ */

+

Test program-main.cpp

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
65
66
67
68
69
70
71
#include <iostream>	// std::cout, endl
#include <string> // std::string
#include <list> // std::list
#include <vector> // std::vector
#include "SortingAlgorithms.h"

using std::cout; using std::cin;
using std::endl; using std::list;
using std::vector; using std::string;

// struct defined for testing
struct student{
string name;
int age;
};

// comparator 1
template<class T>
bool compare_age(const T& x, const T& y){
return x.age <= y.age;
}

// comparator 2
template<class T>
bool compare_name(const T& x, const T& y){
return x.name <= y.name;
}

int main(){
// test 1: array-based version
double arr[10] = {10, 9, 8, 7, 6, 5, 4, 3, 2, 1};
MergeSort(arr, 10);
cout << "Sorted array: ";
for(int i = 0; i != 10; ++i)
cout << arr[i] << " ";
cout << "\n";

// test 2: iterator-based version
string str("eclipseworkspace");
MergeSort(str.begin(), str.end());
cout << "Sorted string " << str << "\n";

list<int> l{10, 9, 8, 7, 6, 5, 4, 3, 2, 1};
MergeSort(l.begin(), l.end());
cout << "Sorted list: ";
for (auto i: l)
cout << i << " ";

cout << endl;

// test 3: user-defined comparator
vector<student> students;
while(cin){
student temp;
cin >> temp.name >> temp.age;
if(cin)
students.push_back(temp);
}

MergeSort(students.begin(), students.end(), compare_age<student>);
cout << "Sorted vector according to age:\n";
for (auto i: students)
cout << i.name << " " << i.age << "\n";

MergeSort(students.begin(), students.end(), compare_name<student>);
cout << "Sorted vector according to name:\n";
for (auto i: students)
cout << i.name << " " << i.age << "\n";

return 0;
}

+

Outputs:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
Sorted array: 1 2 3 4 5 6 7 8 9 10 
Sorted string acceeeiklopprssw
Sorted list: 1 2 3 4 5 6 7 8 9 10
John 18
Mike 16
Liam 23
Anna 26
Bobo 21
Sorted vector according to age:
Mike 16
John 18
Bobo 21
Liam 23
Anna 26
Sorted vector according to name:
Anna 26
Bobo 21
John 18
Liam 23
Mike 16

+ + +
+ + + + + + + + + + + +
+ + + +
+ + + +
+ +
+
+ + +
+ + + + + + + + + +
+ + + + + + + + + +
+
+ + + + +
+ + +
+ + + + +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/18/05/2018/Sorting-Algorithms-C-Implementations-Quick-Sort/index.html b/18/05/2018/Sorting-Algorithms-C-Implementations-Quick-Sort/index.html new file mode 100644 index 00000000..24dc4f2e --- /dev/null +++ b/18/05/2018/Sorting-Algorithms-C-Implementations-Quick-Sort/index.html @@ -0,0 +1,797 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Sorting Algorithms C++ Implementations - Quick Sort | Liam's Blog + + + + + + + + + + + + + + + + + + +
+
+ + + +
+
+
+
+ + +
+ + + + + + + + +
+ + + +
+ + + + + + + +
+ + + +

Sorting Algorithms C++ Implementations - Quick Sort

+ + + +
+ + + + + +
+ + + + + +

Quick Sort

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
/*----------------------------------------------------------------------------- 
* main.cpp || Created on: 18 May 2018 || Author: Liam
*-----------------------------------------------------------------------------
* this program tests three implementations of the quick sort algorithm
*
* Logic:
* 1. select an element from the sequence as the pivot and rearrange the
* sequence such that all elements less than the pivot are towards the left
* of it and all elements greater than the pivot are towards the right of it.
* 2. above process is called partitioning of the sequence. we recursively
* partition the sequence till that there is only one element left in each
* segment.
* 3. for convenience, we can always select the last element as the pivot. If
* so, we may encounter the case that the position of the pivot
* is still at the end (or begining) of the rearranged sequence. Such case is
* the worst case and the time complexity is O(n^). To achieve an average
* case, in which the time complexity is O(nlogn), we can randomly select
* the pivot and then put the pivot at the end of the sequence for the
* rearangement.
*
* Complexity analysis:
* time complexity:
*
* Best case: always be balanced at the midpoint in each partition
* T(n) = 2T(n/2) + cn
* = 2^k T(n/2^k) + kcn
* where k = logn
* therefore = O(nlogn)
*
* Average case: when randomly selected as any one of the position,
* the partition index or iterator is an average index
* T(n) = T(n - i) + T(i - 1) + cn
* = 1/n * summation(T(n - i) + T(i - 1)) + cn
* = O(nlogn)
*
* Worst case: unblanced in each partition
* T(n) = T(n - 1) + cn
* = T(n - 2) + c(n - 1) + cn
* = T(n - k) + c(n - k + 1 + ... + n)
* where k = n-1
* therefore
* T(n) = T(1) + c*(2 + 3 + ... + n)
* = O(n^2)
*
* auxiliary space: (non-stable) worst-case = O(1)
*-----------------------------------------------------------------------------
*/
+

Implementations

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
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
 #ifndef SORTINGALGORITHMS_H_
#define SORTINGALGORITHMS_H_

#include <ctime>
#include <cstddef>
#include <cstdlib>
#include <iterator>
#include <stdexcept>
#include <algorithm>
#include <iostream>

// array-based version, first always is the index to indicate the
// first position of a sequence, last is the index to indicate the
// last position of a sequence. partitionIndex is the split point
template <typename T>
std::size_t Partition(T*p, const std::size_t first, const std::size_t last){
T pivot = p[last];
std::size_t partitionIndex = first;
for(std::size_t i = first; i != last; ++i){
if(p[i] <= pivot){
T temp = p[i];
p[i] = p[partitionIndex];
p[partitionIndex] = temp;
++partitionIndex;
}
}
T temp = p[partitionIndex];
p[partitionIndex] = p[last];
p[last] = temp;
return partitionIndex;
}

template <typename T>
void QuickSort(T*p, const std::size_t first, const std::size_t last){
if(first >= last) return;
std::size_t partitionIndex = Partition(p, first, last);
if(partitionIndex != 0){
QuickSort(p, first, partitionIndex - 1);
}
QuickSort(p, partitionIndex + 1, last);
}

// iterator-based version STL style (C++11)
// partitionIter denotes the position of the
// split point
template<typename BidirrectionalIterator>
BidirrectionalIterator Partition(BidirrectionalIterator begin,
BidirrectionalIterator end){
auto pivot = std::prev(end);
auto partitionIter = begin;

for(auto iter = begin; iter != pivot; ++iter){
if(*iter <= *pivot){
std::iter_swap(iter, partitionIter);
++partitionIter;
}
}
std::iter_swap(partitionIter, pivot);
return partitionIter;
}

template <typename BidirrectionalIterator>
void QuickSort(BidirrectionalIterator begin, BidirrectionalIterator end){
if(std::distance(begin, end) <= 1) return;
auto partitionIter = Partition(begin, end);
if(std::prev(partitionIter) != begin){
QuickSort(begin, partitionIter);
}
QuickSort(std::next(partitionIter), end);
}

// user-defined comparator + improved using random selected pivot
// return a random integer in the range [0, n)
int nrand(int n)
{
if(n <= 0 || n > RAND_MAX)
throw std::domain_error("Argument to nrand is out of range");

const int bucket_size = RAND_MAX /n;
int r;

do r = rand() / bucket_size;
while(r >= n);

return r;
}

template<typename BidirrectionalIterator, typename Comparator>
BidirrectionalIterator Partition(BidirrectionalIterator begin,
BidirrectionalIterator end, Comparator comp){
std::srand (std::time(nullptr));
auto randomIndex = nrand(std::distance(begin, end));
std::cout << randomIndex << "nihao" << std::endl;
auto pivot = std::prev(end);
std::iter_swap(pivot, std::next(begin, randomIndex));

auto partitionIter = begin;
for(auto iter = begin; iter != pivot; ++iter){
if(comp(*iter, *pivot)){
std::iter_swap(iter, partitionIter);
++partitionIter;
}
}
std::iter_swap(partitionIter, pivot);
return partitionIter;
}

template <typename BidirrectionalIterator, typename Comparator>
void QuickSort(BidirrectionalIterator begin, BidirrectionalIterator end,
Comparator comp){
if(std::distance(begin, end) <= 1) return;
auto partitionIter = Partition(begin, end, comp);
if(std::prev(partitionIter) != begin){
QuickSort(begin, partitionIter, comp);
}
QuickSort(std::next(partitionIter), end, comp);
}

#endif /* SORTINGALGORITHMS_H_ */

+

Test program-main.cpp

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
65
66
67
68
69
70
71
#include <iostream>	// std::cout, endl
#include <string> // std::string
#include <list> // std::list
#include <vector> // std::vector
#include "SortingAlgorithms.h"

using std::cout; using std::cin;
using std::endl; using std::list;
using std::vector; using std::string;

// struct defined for testing
struct student{
string name;
int age;
};

// comparator 1
template<class T>
bool compare_age(const T& x, const T& y){
return x.age < y.age;
}

// comparator 2
template<class T>
bool compare_name(const T& x, const T& y){
return x.name < y.name;
}

int main(){
// test 1: array-based version
double arr[10] = {10, 9, 8, 7, 6, 5, 4, 3, 2, 1};
QuickSort(arr, 0, 9);
cout << "Sorted array: ";
for(int i = 0; i != 10; ++i)
cout << arr[i] << " ";
cout << "\n";

// test 2: iterator-based version
string str("eclipseworkspace");
QuickSort(str.begin(), str.end());
cout << "Sorted string " << str << "\n";

list<int> l{10, 9, 8, 7, 6, 5, 4, 3, 2, 1};
QuickSort(l.begin(), l.end());
cout << "Sorted list: ";
for (auto i: l)
cout << i << " ";

cout << endl;

// test 3: user-defined comparator
vector<student> students;
while(cin){
student temp;
cin >> temp.name >> temp.age;
if(cin)
students.push_back(temp);
}

QuickSort(students.begin(), students.end(), compare_age<student>);
cout << "Sorted vector according to age:\n";
for (auto i: students)
cout << i.name << " " << i.age << "\n";

QuickSort(students.begin(), students.end(), compare_name<student>);
cout << "Sorted vector according to name:\n";
for (auto i: students)
cout << i.name << " " << i.age << "\n";

return 0;
}

+

Outputs:

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
Sorted array: 1 2 3 4 5 6 7 8 9 10 
Sorted string acceeeiklopprssw
Sorted list: 1 2 3 4 5 6 7 8 9 10
John 18
Mike 16
Liam 23
Anna 26
Bobo 21
2nihao
1nihao
1nihao
Sorted vector according to age:
Mike 16
John 18
Bobo 21
Liam 23
Anna 26
2nihao
1nihao
Sorted vector according to name:
Anna 26
Bobo 21
John 18
Liam 23
Mike 16

+ + +
+ + + + + + + + + + + +
+ + + +
+ + + +
+ +
+
+ + +
+ + + + + + + + + +
+ + + + + + + + + +
+
+ + + + +
+ + +
+ + + + +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/19/03/2018/Accelerated-C-Solutions-to-Exercises-Chapter-5-Part-3/index.html b/19/03/2018/Accelerated-C-Solutions-to-Exercises-Chapter-5-Part-3/index.html new file mode 100644 index 00000000..ea450e1e --- /dev/null +++ b/19/03/2018/Accelerated-C-Solutions-to-Exercises-Chapter-5-Part-3/index.html @@ -0,0 +1,875 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Accelerated C++ Solutions to Exercises(Chapter 5 Part 3) | Liam's Blog + + + + + + + + + + + + + + + + + + +
+
+ + + +
+
+
+
+ + +
+ + + + + + + + +
+ + + +
+ + + + + + + +
+ + + +

Accelerated C++ Solutions to Exercises(Chapter 5 Part 3)

+ + + +
+ + + + + +
+ + + + + +

Exercise 5-5

Write a function named center(const vector&) that returns a picture in which all the lines of the original picture are padded out to their full width, and the padding is as evenly divided as possible between the left and right sides of the picture. What are the properties of pictures for which such a function is useful? How can you tell whether a given picture has those properties?

+

Solution & Results

The full width is the size of the longest string and can be obtained via a for loop.

1
2
3
4
5
for (vector<string>::const_iterator iter = p.begin();
iter != p.end(); ++iter)
{
maxlen = max(maxlen, (*iter).size());
}

+

The key is to compute the number of spaces (denoted by paddingLeft) needed for the left side padding. If we want that the padding is as evenly divided as possible, paddingLeft can be set as half of maxlen - (*iter).size(), which is the total number of spaces needed to pad out to the full width of a line.

+

If (maxlen - (*iter).size()) is even, the numbers of spaces on both sides of the original string are the same (i.e. the case of that padding is evenly divided).

+

If (maxlen - (*iter).size()) is odd, paddingLeft is in fact has the value of ((maxlen - (*iter).size()) - 1)/2, and hence is one space less that the right side padding.

+

In summary, the program logic is

+
    +
  1. find maxlen.
  2. +
  3. compute paddingLeft.
  4. +
  5. construct a new line based on the string in the original picture and paddingLeft spaces.
  6. +
  7. store each new line into a vector and return it once finishes.
  8. +
+

The complete program and tests can be found below.

+

mainfunction.cpp

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 <string>
#include <vector>
#include <algorithm>

using std::cout; using std::string;
using std::endl; using std::vector;
using std::max;

// function declaration
vector<string> center(const vector<string> &p);

int main()
{
// create an original picture
vector<string> p;
p.push_back("this is an");
p.push_back("example");
p.push_back("to");
p.push_back("illustrate");
p.push_back("framing");

// generate the centered picture
vector<string> np = center(p);
for(vector<string>::const_iterator iter = np.begin();
iter != np.end(); ++iter)
cout << *iter << endl;

return 0;
}

// define the function that returns a centered picture
vector<string> center(const vector<string> &p)
{
vector<string> centeredPicture;
string::size_type maxlen = 0;

// get the length of the longest string
for (vector<string>::const_iterator iter = p.begin();
iter != p.end(); ++iter)
{
maxlen = max(maxlen, (*iter).size());
}

// pad out the left side
for (vector<string>::const_iterator iter = p.begin();
iter != p.end(); ++iter)
{
string::size_type paddingLeft = (maxlen - (*iter).size())/2;
string s = string(paddingLeft, ' ') + (*iter);
centeredPicture.push_back(s);
}

return centeredPicture;
}

+

Test Results

1
2
3
4
5
this is an
example
to
illustrate
framing

+
+

Exercise 5-6

Rewrite the extract_fails function from §5.1.1/77 so that instead of erasing each failing student from the input vector v, it copies the records for the passing students to the beginning of v, and then uses the resize function to remove the extra elements from the end of v. How does the performance of this version compare with the one in §5.1.1/77?

+

Solution & Results

The original function is

+

Method 1

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
vector<Student_info> extract_fails_method1(vector<Student_info>& students)
{
vector<Student_info> fail;
vector<Student_info>::size_type i = 0;
// invariant:elements [0, i) of students represent passing grades
while (i != students.size())
{
if (fgrade(students[i]))
{
fail.push_back(students[i]);
students.erase(students.begin() + i);
}
else
++i;
}
return fail;
}

+

The revised version is

+

Method 2

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
vector<Student_info> extract_fails_method2(vector<Student_info>& students)
{

vector<Student_info> fail;
vector<Student_info>::size_type i = 0, j = students.size();
// invariant:elements [0, i) of students represent passing grades
while (i != students.size())
{
if (fgrade(students[i]))
{
fail.push_back(students[i]};
}
else
{
// the size increases by 1
students.insert(students.begin(), students[i]);
// the indice move forward by 1
++i;
}
++i;
}
students.resize(j - fail.size());
return fail;
}

+

The next step is to measure the performance of these two function, using the same methodology as that applied in Exercise 5-4. I add above two methods into the file fails.cpp and fails.h as alternatives. To avoid redundancy, I only present the file that contains the main function in below.

+
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
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
// Accelerated C++ Solutions Exercises 5-2. 5-3, 5-4
#include <algorithm> // to get declaration of max, sort
#include <iostream> // to get declaration of cin, cout, endl
#include <stdexcept> // to get declaration of domain_error
#include <string> // to get declaration of string
#include <chrono>
#include "Student_info.h"
#include "grade.h"
#include "fails.h"
#include "print.h"
#include "info.h"


using std::cin; using std::string;
using std::cout; using std::max;
using std::endl; using std::sort;
using std::domain_error;

int main()
{
info students;
Student_info record;
string::size_type maxlen = 0;

// read and store all the records, and find the length of the longest name
while(read(cin, record))
{
maxlen = max(maxlen, record.name.size());
students.push_back(record);
}

try{
info students_copy = students;

// measure the performance for method1
typedef std::chrono::high_resolution_clock Clock;
Clock::time_point startTime = Clock::now(); // get current time
info fails = extract_fails_method1(students); // extract records for failing students
Clock::time_point endTime = Clock::now(); // get current time

cout << "Method 1 took me "
<< std::chrono::duration_cast<std::chrono::duration<double>>(endTime - startTime).count()
<< " seconds" << endl;

// measure the performance for method2
startTime = Clock::now(); // get current time
fails = extract_fails_method2(students_copy); // extract records for failing students
endTime = Clock::now(); // get current time

cout << "Method 2 took me "
<< std::chrono::duration_cast<std::chrono::duration<double>>(endTime - startTime).count()
<< " seconds" << endl;

// write each line of outputs for passing students
if (!students.empty())
{
//alphabetize the records

sort(students.begin(), students.end(), compare);
// students.sort(compare);
cout << "Students who passed: " << endl;
print(students, maxlen);
}
else
cout << "What a pity! all students failed.";

// write a blank line
cout << endl;

// write each line of outputs for failing students
if(!fails.empty())
{
//alphabetize the records

sort(fails.begin(), fails.end(), compare);
//fails.sort(compare);
cout << "Students who failed: " << endl;
print(fails, maxlen);
}
else
cout << "Congratulations! all students passed.";

}catch(domain_error e){
cout << e.what();
}

return 0;
}
+

The table below gives the comparison of these two methods, showing that method 2 performs slightly better than method 1. The result is in fact not reliable as it strongly depends on the number of passing students and failing students contained in the raw data. More experiments need to be done for more robust results.
|Number of lines|Method 1-erase| Method 2-insert&resize|
| :— | :— | :— |
|10|0.000 seconds|0.000 seconds|
|1000|0.147114 seconds|0.117083 seconds|
|10000|0.848616 seconds|0.735503 seconds|

+
+

Exercise 5-7

Given the implementation of frame in §5.8.1/93, and the following code fragment

1
2
vector<string> v;
frame(v);

+

describe what happens in this call. In particular, trace through how both the width functiona nd the frame function operate. Now, run this code. If the results differ from your expectations, first understand why your expectations and the program differ, and then change one to match the other.

+

Solution & Results

Let’t recall the frame function

+
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
vector<string> frame(const vector<string> &v)
{
// to hold each rows of framed picture to be written
vector<string> ret;
// get the length of the longest string
string::size_type maxlen = width(v);

// first line of outputs
string border(maxlen + 4, '*');
ret.push_back(border);

for (vector<string>::const_iterator iter = v.begin(); iter != v.end(); ++iter)
{
// new rows except two border lines
string temp = "* " + (*iter) + string(maxlen - (*iter).size(), ' ') + " *";
ret.push_back(temp);
}

// bottom line of outputs
ret.push_back(border);
return ret;
}
+

Once calling this function with passing an empty vector v, I expected following procedures happen one after another.

+
    +
  1. the argument is passed by const reference. v refers to the empty vector v.
  2. +
  3. an empty vector ret is created.
  4. +
  5. an object of type string::size_type is created and named as maxlen. maxlen is initialized with a value returned by width function.
  6. +
  7. the computer enters into width function (as shown below). The arguments is also passed by const reference and hence parameter v refers to the empty vector v defined in the main function.

    +

    4.1. object maxlen is created and initialized with value of 0.

    +

    4.2. the computer enters into a for loop. The init-statement is evaluated and iter is initialized as v.begin(). Then then condition is evaluated and the result is false as v.begin() == v.end() due to the fact that no any elements in the container.

    +

    4.3 for loop quits and maxlen that equals to 0 is returned.

    +
    1
    2
    3
    4
    5
    6
    7
    8
    9
    string::size_type width(const vector<string> &v)
    {
    string::size_type maxlen = 0;
    for (vector<string>::const_iterator iter = v.begin(); iter != v.end(); ++iter)
    {
    maxlen = max(maxlen, (*iter).size());
    }
    return maxlen;
    }
    +
  8. +
  9. the computer goes back to the frame function and maxlen is initialized with value 0 (step 3 finishes).

    +
  10. +
  11. object border is created and initialized with 4 asterisks. Then, it is stored into vector ret.
  12. +
  13. the next for loop is similar to the for loop inside the width function. It quits and has no any effects.
  14. +
  15. object border is stored into vector ret again. By then, ret has two elements, both of which are strings filled by 4 asterisks.
  16. +
  17. ret is returned. The computer goes back to the function caller.
  18. +
+

According to my expection, the picture returned by frame only consist of two lines of strings each formed by 4 asterisks. Let’s verify this the program displayed below.

+

A complete 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
#include <string>
#include <vector>
#include <iostream>
#include <algorithm>

using std::string; using std::cout;
using std::endl; using std::max;
using std::vector;

// function declarations
string::size_type width(const vector<string> &v);
vector<string> frame(const vector<string> &v);

int main()
{
vector<string> v;
vector<string> p = frame(v);
for (vector<string>::iterator iter = p.begin(); iter != p.end(); ++iter)
cout << *iter <<endl;
return 0;
}

// please fill this part with the width function described above
// please fill this part with the frame function described above

+

As expected, the program produces following outputs

+
1
2
****
****
+
+

Exercise 5-8

In the hcat function from §5.8.3/95, what would happen if we defined s outside the scope of the while? Rewrite and execute the program to confirm your hypothesis.

+

Solution & Results

Code analysis

Recalling the hcat function:

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
vector<string> hcat(const vector<string> &left, const vector<string> &right)
{
// to hold the each line of outputs
vector<string> ret;

// add one space column between two pictures
string::size_type width1 = width(left) + 1;

// iterators to look at elements from left and right respectively
vector<string>::const_iterator iter_i = left.begin(), iter_j = right.begin();

// continue until we've see all rows from two pictures
while (iter_i != left.end() || iter_j != right.end())
{
// construct new string to hold characters from two pictures
string s;

// copy a row from left side picture
if (iter_i != left.end())
{
s = (*iter_i);
++iter_i;
}
// pad to full width
s += string(width1 - s.size(), ' ');

// copy a row from right picture
if (iter_j != left.end())
{
s += (*iter_j);
++iter_j;
}

// store s into vector to form a new picture
ret.push_back(s);
}
return ret;
}

+

Let’s analyse what happens if we define s outside the scope of the while loop:

+
    +
  1. s is an empty string before entering into the while loop.
  2. +
  3. the first iteration:

    +

    2.1. s is assigned with the copy of the first element from the left picture, and then is padded out to the full width (left side).

    +

    2.2. s is concatenated with the first element from the right picture.

    +

    2.3. s is stored into vector ret to formatted the first line of the new picture.

    +
  4. +
  5. the nth interation:

    +

    case 1: the numbers of rows of both pictures are the same. The process of the rest iterations are similar to the first iteration until the while condition is evaluated to false. As a result, all rows of both pictures are copied into the new picture. The function has the same effect as the original one, where s is defined inside the while loop.

    +

    case 2: the left side picture has less rows than the right side picture. When iter_i == left.end() but iter_j != right.end(), the first if statements are ignored but the next statement (shown below) will result in compilation errors.

    +
    1
    s += string(width1 - s.size(), ' ');
    +

    remembering that s.size() is the summation of widths of both sides, i.e. width1 plus the width of the right side picture. Therefore, width1 - s.size() is negative.

    +

    case 3: if left side picture has more rows than the right side picture. The process of the rest iterations are similar to case 1. The function has same effect as the original function, where s is defined inside of the while loop.

    +
  6. +
+

Rewrite the original program

Now, let’s verify above expectations using following program, covering three cases and two functions(i.e. the original one and the modified one). The files of the program includes mainfunction.cpp, pics.cpp, pics.h, width.cpp. width.h, print.cpp and print.h. More details about the original program can be found in putting strings together.

+

mainfunction.cpp

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
#include <iostream>
#include <string>
#include <vector>
#include "pics.h"
#include "print.h"

using std::cout; using std::endl;
using std::vector; using std::string;

int main()
{
vector<string> pic1;
pic1.push_back("aaaaaa");
pic1.push_back("bbbbbbbbbbbbb");
pic1.push_back("ccc");

vector<string> pic2;
pic2.push_back("ddddddddddddd");
pic2.push_back("eeeeeee");
pic2.push_back("fffffffffff");
pic2.push_back("ggggg");

vector<string> left, right;
// Test case 1
left = right = pic1;

// Test case 2
// left = pic1; right = pic2;

// Test case 3
// left = pic2; right = pic1;

vector<string> hcatPicture1 = hcat_function1(left, right);
vector<string> hcatPicture2 = hcat_function2(left, right);

cout << "The original function produces a picture shown as follows: " << endl;
print(hcatPicture1);
cout << endl;
cout << "The modified function produces a picture shown as follows: " << endl;
print(hcatPicture2);

return 0;
}

+

pics.cpp

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
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
// functions that generate different pictures
#include <string> // to get declaration of string
#include <vector> // to get declaration of vector
#include "width.h"
#include "pics.h"

using std::string; using std::vector;

// to horizontally concatenate two pictures: original function
vector<string> hcat_function1(const vector<string> &left, const vector<string> &right)
{
// to hold the each line of outputs
vector<string> ret;

// add one space column between two pictures
string::size_type width1 = width(left) + 1;

// iterators to look at elements from left and right respectively
vector<string>::const_iterator iter_i = left.begin(), iter_j = right.begin();

// continue until we've see all rows from two pictures
while (iter_i != left.end() || iter_j != right.end())
{
// construct new string to hold characters from two pictures
string s;

// copy a row from left side picture
if (iter_i != left.end())
{
s = (*iter_i);
++iter_i;
}
// pad to full width
s += string(width1 - s.size(), ' ');

// copy a row from right picture
if (iter_j != left.end())
{
s += (*iter_j);
++iter_j;
}

// store s into vector to form a new picture
ret.push_back(s);
}
return ret;
}

// to horizontally concatenate two pictures: modified function
vector<string> hcat_function2(const vector<string> &left, const vector<string> &right)
{
// to hold the each line of outputs
vector<string> ret;

// add one space column between two pictures
string::size_type width1 = width(left) + 1;

// iterators to look at elements from left and right respectively
vector<string>::const_iterator iter_i = left.begin(), iter_j = right.begin();

// construct new string to hold characters from two pictures
string s;
// continue until we've see all rows from two pictures
while (iter_i != left.end() || iter_j != right.end())
{
// copy a row from left side picture
if (iter_i != left.end())
{
s = (*iter_i);
++iter_i;
}
// pad to full width
s += string(width1 - s.size(), ' ');

// copy a row from right picture
if (iter_j != right.end())
{
s += (*iter_j);
++iter_j;
}

// store s into vector to form a new picture
ret.push_back(s);
}
return ret;
}

+

pics.h

1
2
3
4
5
6
7
8
9
10
11
12
13
#ifndef GUARD_PICS_H
#define GUARD_PICS_H

#include <string>
#include <vector>

std::vector<std::string> hcat_function1(const std::vector<std::string> &left,
const std::vector<std::string> &right);

std::vector<std::string> hcat_function2(const std::vector<std::string> &left,
const std::vector<std::string> &right);

#endif /* GUARD_PICS_H */

+

width.cpp

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
// function returns the size of the longest string in a vector<string>
#include <string> // to get declaration of string
#include <vector> // to get declaration of vector
#include <algorithm> // to get declaration of max
#include "width.h" // to get declaration of the function itself

using std::string; using std::vector; using std::max;


string::size_type width(const vector<string> &v)
{
string::size_type maxlen = 0;
for (vector<string>::const_iterator iter = v.begin(); iter != v.end(); ++iter)
{
maxlen = max(maxlen, (*iter).size());
}
return maxlen;
}

+

width.h

1
2
3
4
5
6
7
8
9
#ifndef GUARD_WIDTH_H
#define GUARD_WIDTH_H

#include <string>
#include <vector>

std::string::size_type width(const std::vector<std::string> &v);

#endif /* GUARD_WIDTH_H */

+

print.cpp

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
// function to write each elements from a vector<string>
#include "print.h"
#include <iostream> // to get declaration of cout and endl
#include <string> // to get declaration of string
#include <vector> // to get declaration of vector

using std::cout; using std::string;
using std::endl; using std::vector;

void print(const vector<string> &pics)
{
// loop thru the vector and write elements one by one
for (vector<string>::const_iterator iter = pics.begin(); iter != pics.end(); ++iter)
{
cout << (*iter) << endl;
}
}

+

print.h

1
2
3
4
5
6
7
8
9
#ifndef GUARD_OUTPUT_H
#define GUARD_OUTPUT_H

#include <string>
#include <vector>

void print(const std::vector<std::string> &pics);

#endif /* GUARD_OUTPUT_H */

+

Test results

Case 1

1
2
3
4
5
6
7
8
9
The original function produces a picture shown as follows: 
aaaaaa aaaaaa
bbbbbbbbbbbbb bbbbbbbbbbbbb
ccc ccc

The modified function produces a picture shown as follows:
aaaaaa aaaaaa
bbbbbbbbbbbbb bbbbbbbbbbbbb
ccc ccc

+

Case 2

1
2
3
4
This application has requested the Runtime to terminate it in an unusual way. 
Please contact the application's support team for more information.
terminate called after throwing an instance of 'std::length_error'
what(): basic_string::_M_create

+

Case 3

1
2
3
4
5
6
7
8
9
10
11
The original function produces a picture shown as follows: 
ddddddddddddd aaaaaa
eeeeeee bbbbbbbbbbbbb
fffffffffff ccc
ggggg

The modified function produces a picture shown as follows:
ddddddddddddd aaaaaa
eeeeeee bbbbbbbbbbbbb
fffffffffff ccc
ggggg

+

The results of all three cases are exactly as I expected and analysed in last section.

+
+

To be continued.

+ + +
+ + + + + + + + + + + +
+ + + +
+ + + +
+ +
+
+ + +
+ + + + + + + + + +
+ + + + + + + + + +
+
+ + + + +
+ + +
+ + + + +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/19/05/2018/C-Implementations-Singly-Linked-List/index.html b/19/05/2018/C-Implementations-Singly-Linked-List/index.html new file mode 100644 index 00000000..ea1512fd --- /dev/null +++ b/19/05/2018/C-Implementations-Singly-Linked-List/index.html @@ -0,0 +1,884 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + C++ Implementations: Singly Linked List | Liam's Blog + + + + + + + + + + + + + + + + + + +
+
+ + + +
+
+
+
+ + +
+ + + + + + + + +
+ + + +
+ + + + + + + +
+ + + +

C++ Implementations: Singly Linked List

+ + + +
+ + + + + +
+ + + + + +

Logical View

How to efficiently store a sequence of values (aka. a List) of a given type is crucial for any non-trivial program due to the limited memory. The way we store or organsize data introduces an important concept, that is, data structures. A data structure is proposed to manage data in a specific way so that we can use it efficiently according to specific needs.

+

In C++/C, the most common facility we use is so called built-in array, which stores a given number of elements in a contiguous memory block. All elements are stored in certain order indicated by their addresses. When we want to access any one of the elements, we pass the order of that element to the computer. Then the computer will calculate the address of that element based on the initial address of the array, and then return us the associated value. Since the addresses are contiguous and the size of memory occupied by the given type is fixed, we can always get any element in constant time: there is only one arithmetic operation needed. Naturally, we can modify any one of elements in constant time as well. However, the shortingcoming is obvious: an array has fixed length. There are two ways to solve this problem, one is that we can preset a large enough size for the array, however, doing so is very inefficient in terms of memory usage. Another way is to dynamically allocated an array when it is needed. However, doing so possibly invalidates all pointers/iterators as all elements are moved to a new allocated storage, which is also very inefficient in terms of time cost(O(n)). Even if the allocated storage is sufficient, any modifications of the array, such as, insert or delete element, also invalidates parts or all of the pointers/iterators, and hence these operations have high time cost: O(n).

+

An alternative data structure is named Linked List, which stores data in a noncontinuous memory space through a manner that each element are connected and ordered by pointers. More specific, an element of a singly linked list is an single object (aka. Linked Node) that contains two members: one is the data stored in the object, and the other is the address (i.e. a pointer variable) of the next element.

+

Basic Operations

The entrance of a singly linked list is a pointer to the Head Node, i.e. the first Linked Node. If the linked list is empty, the pointer to the Head Node is a nullptr. To access one element, we have to start from the Head Node and move one Node by one Node. Therefore, unlike the array, the time to access elements of the linked list is proportational to the size of the linked list, that is, the time complexity is O(n). Naturally if we want to insert or delete one element into/from the list, we have to find the position or the specific element first. The time complexity is also O(n). One advantage compared to the array is that it doesn’t worry about the size of the storage. We do not need to preset the length of the list and hence no extra memory is wasted. But it does need extra memory for storing the pointer variable in each Linked Node. Another advantage is that the operations such as insert or delete doesn’t change other elements, which is crucial for us when we manipulate an object by pointers or iterators. Now let’s briefly summarize these two elementary data structures: Array and Linked List.

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
ArrayLinked List
Cost of accessing an element of an elementO(1)O(n)
Memory requirementFixed size: used memory and unused memoryNo unused memory but need extra memory for pointer variables
Cost of Inserting or deleting an elementInserting at beging: O(n); Insering at the end: O(1) if there exists unused memory but O(n) is the array is full; Inserting at middle: O(n)Inserting at begining:O(1); Inserting at the end: O(n); Inserting at middle: O(n)
Source:mycodeschool
+

From above table, we can see that whether to use an array or a linked list depends on that what is the most frequent operation the program performs and what is the size of the data structure.

+

Implementations

To define a abstract data type based on the singly linked list, the first step is to write the Linked Node:

1
2
3
4
5
template <typename T>
struct Node{
T data;
Node* next;
};

+

The Node struct is templated for satisfying different underlying types. It contains two items: one is named data which for storing values of T type and ther other named next which is a pointer to a Node object. Next we define the class type based on the singly linked list model described above. In fact, we can incorporate the Node type into our singly linked list class as a private member for the purpose of hidding the implementation details. As mentioned above, the only information we know is the pointer to the Head Node, therefore, I declare a private member ptrToHead to indicate the address of the Head Node. Also, I define a unsigned integer to count the number of elements stored in a linked list. For the sake of convenience, I write a private function create to create a Node with a particular value. Following code shows the full view of the SinglyLinkedList class template. Noting that this implementation is not a STL style implementation.

+
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
template <typename T>
class SinglyLinkedList{
struct Node; // forward declaration
template<typename X>
friend void reverse(SinglyLinkedList<X>&,
typename SinglyLinkedList<X>::Node*)
public:

SinglyLinkedList(): ptrToHead(nullptr), count(0) { // create an empty linked_list
std::cout << "default constructor" << std::endl;
}
explicit SinglyLinkedList(const size_type, const T& val = T()); // create an linked list with size
SinglyLinkedList(const SinglyLinkedList&); // copy constructor
SinglyLinkedList& operator= (const SinglyLinkedList&); // assignment operator
~SinglyLinkedList(); // destructor
void clear(); // clear
Node* begin() { return ptrToHead; } // get pointer to Head Node
const Node* begin() const { return ptrToHead; }
bool empty() const { return ptrToHead == nullptr; } // check whether is empty
size_type size() const { return count; } // get the size of the list
void push_front(const T&); // insert at begining
void push_back(const T&); // insert at the end
void insert(size_type, const T&); // insert at the nth position
void pop_front(); // delete at the begining
void pop_back(); // delete the last element
void erase(size_type); // delete at nth position
void reverse(); // reverse the order iteratively
void remove(const T&); // remove elements with specific values

private:
// nested Node type
struct Node{
T data;
Node* next;
};

// data members
Node* ptrToHead;
size_type count;

// create a new Node
Node* create(const T& val){
Node* new_node = new Node;
new_node->data = val;
new_node->next = nullptr;
return new_node;
}
};
+

Let’s define the special members first:

+

constructors

The default constructor initializes the ptrToHead to nullptr and count to 0, hence constructs an empty linked list. The second constructor constructs a linked list with a size and a value of T type. If no value supplied, the data member in each Node will be default-initialized or value-initialized depending on whether there exists a user-defined default constructor in the definition of T type. Also I specify this constructor as explicit to avoid potential type conversion. To validate my implementation, I instrument these special members by adding outputs when these members are called.

+
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
// constructor that takes a size and a value: O(n)
template <class T>
SinglyLinkedList<T>::SinglyLinkedList(size_type n, const T& val){
std::cout << "constructor with parameters" << std::endl;
if(n > 0){
ptrToHead = create(val);
count = 1;
Node* temp = ptrToHead;
while(count != n){
temp->next = create(val);
temp = temp->next;
++count;
}
}
}
+

The logic is simple: first create the Head Node and store its address into ptrToHead, at the same time, update the count to 1 representing that currently we have one element in our linked list; Then, create the rest Nodes and link one by one. We use a temporary pointer to move forward as we don’t want to modify the ptrToHead. The time cost is proportional to the length of the linked list and therefore the time complexity is big oh of n, where n represents the number of elements stored in the linked list.

+

Next is the copy constructor which controls the copy operation. The definition is similar to above constructor except that, the copy constructor constructs an object using values stored in the argument. If the argument is an empty linked list, we construct an empty linked list as well through the initialization list.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
// copy constructor: O(n)
template <class T>
SinglyLinkedList<T>::SinglyLinkedList(const SinglyLinkedList& l): ptrToHead(nullptr), count(0) {
std::cout << "copy constructor" << std::endl;
if(!l.empty()){
ptrToHead = create(l.begin()->data);
count = 1;
Node* temp1 = ptrToHead;
const Node* temp2 = l.begin();
while(count != l.size())
{
temp1->next = create(temp2->next->data);
temp1 = temp1->next;
temp2 = temp2->next;
++count;
}
}
}

+

The assignment operator is similar to our copy costructor except that it needs to obliterate the old values stored in the left-hand side operand. If the argument is an empty linked list, we only need to execute the clear() function to delete all nodes (if exist) to get an empty linked list.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
// assignment operator: O(n)
template <class T>
SinglyLinkedList<T>& SinglyLinkedList<T>::operator= (const SinglyLinkedList& l){
std::cout << "assignment operator" << std::endl;
if(&l != this){
clear();
if(!l.empty()){
ptrToHead = create(l.begin()->data);
count = 1;
Node* temp1 = ptrToHead;
const Node* temp2 = l.begin();
while(count != l.size())
{
temp1->next = create(temp2->next->data);
temp1 = temp1->next;
temp2 = temp2->next;
++count;
}
}
}
return *this;
}

+

destructor & clear member

The destructor is implemented by executing the clear() function.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
// destructor
template <class T>
SinglyLinkedList<T>::~SinglyLinkedList() {
std::cout << "destructor" << std::endl;
clear();
}

// clear function: the time complexity O(n)
template <class T>
void SinglyLinkedList<T>::clear(){
Node* current = ptrToHead;
while(current != nullptr)
{
ptrToHead = ptrToHead->next;
delete current;
current = ptrToHead;
--count;
}
}

+

Due to the ptrToHead is the only entrance for us, we cannot delete it directly. Therefore, we do it by virtue of a temporary pointer, which points to the Node to be deleted. For example, let it points to the Head Node, then we can release the ptrToHead and let the ptrToHead points to our next Node, and then we clear the Head Node by delete the temporary pointer.

+

push_front, push_back, insert

The push_front means that we can add a new element at the very begining of our linked list. It is simple to do this, making the ptrToHead points to our new Node. But noting that when the linked list is not empty, we should stores the address of the original Head Node into the next member of our new Node.

1
2
3
4
5
6
7
8
9
// insert at begining: O(1)
template <class T>
void SinglyLinkedList<T>::push_front(const T& val) {
Node* new_node = create(val);
if(ptrToHead != nullptr)
new_node->next = ptrToHead;
ptrToHead = new_node;
++count;
}

+

The case of appending a new Node at the end of the linked list is a little bit complex. The function is shown below.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
// insert at the end: O(n)
template <class T>
void SinglyLinkedList<T>::push_back(const T& val) {
Node* new_node = create(val);
if(ptrToHead == nullptr){
ptrToHead = new_node;
++count;
pointToHead->data << std::endl;
return;
}

Node* temp = ptrToHead;
while(temp->next != nullptr)
temp = temp->next;
temp->next = new_node;
++count;
}

+

If the linked list is empty, we simply create a new Node and let ptrToHead points to the new Node. If the linked list is not empty, we need to let the next member of the last Node points to the newly created Node. To find the address of the last Node, we use the condition:

1
temp->next == nullptr

+

This condition stops the while loop and by then, temp is the address of the last Node.

+

Now we consider the case that inserting a new Node at the nth position, where n is in the range [1, size()]. The case that we insert a new Node at the first position can be tanckled by the push_front function.

+

The graph below illustrate a 5-Node linked list and the case that we intend to insert the new Node at a non-Head position. For example, we insert at the third position, that is, when n == 3:

+

Singly Linked List Example

+

We can observe that:

+
    +
  1. the newly created Node becomes the third Node and the original third Node becomes the fourth Node.
  2. +
  3. before inserting

    +
    1
    add2->next == add2
    +

    after inserting

    +
    1
    add2->next == addx
    +
  4. +
  5. to link following Nodes, we let

    +
    1
    addx->next == add3
    +
  6. +
+

Now everything is clear: if we want to insert at the nth position, we have to find the n-1 position and link the n-1th Node to the newly created Node. Let’s starting from the ptrToHead and show the relations between Nodes, Addresses and Iteration times:

1
2
3
4
5
6
7
        counter i       Address         Node
when i = 0 ptrToHead 1st Node
i = 1 add2 2ed Node
i = 2 add3 3ed Node
... ... ...
i = n - 2 add(n - 1) n - 1 Node
i = n - 1 addn n Node

+

Obviously, we will stop our loop when i == n - 2 and by then we can manipulate the n - 1 Node. Following code shows a full view of the insert function.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
// insert at the nth position, n belongs to [1, size()]: O(n)
// the positions means that we add the new node after (n-1)th position
template <class T>
void SinglyLinkedList<T>::insert(size_type position, const T& val){
if(position < 1 || position > size())
throw std::domain_error("Invalid Position");
else if(position == 1)
push_front(val);
else{
Node* new_node = create(val);
Node* temp = ptrToHead;
for(size_type i = 0; i != position - 2; ++i)
temp = temp->next;
new_node->next = temp->next;
temp->next = new_node;
++count;
}
}

+

pop_front, pop_back, erase

The idea behind these three functions are similar to the inserting operations described above. Hence no further discussion here.

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
// delete at the begining: O(1)
template <class T>
void SinglyLinkedList<T>::pop_front(){
if(ptrToHead == nullptr){
throw std::domain_error("Empty Linked List");
}

Node* temp = ptrToHead;
ptrToHead = ptrToHead->next;
delete temp;
--count;
}

// delete the last element: O(n)
template <class T>
void SinglyLinkedList<T>::pop_back(){
if(ptrToHead == nullptr){
throw std::domain_error("Empty Linked List");
}
erase(size());
}

// delete at nth position: O(n)
// n belongs to [1, size()], the position after n - 1
template <class T>
void SinglyLinkedList<T>::erase(size_type position){
if(position < 1 || position > size())
throw std::domain_error("Invalid Position");
else if(position == 1)
pop_front();
else{
Node* current = ptrToHead;
for (size_type i = 0; i != position-2; ++i)
current = current->next;
Node* temp = current->next;
current-> next = temp->next;
delete temp;
--count;
}
}

## remove
// remove elements with specific values: O(n^2)
template <class T>
void SinglyLinkedList<T>::remove(const T& val){
Node* current = ptrToHead;
size_type i = 0;
while(current != nullptr){
if(current->data == val){
current = current->next;
erase(i + 1);
}
else{
current = current->next;
++i;
}
}
}

The remove function allows us to remove all elements that contains data values equal to a supplied value. My solution is to find the positions of the Nodes that should be removed and then call the **erase** function by passing the position. Taking an example(see below graph), suppose one want to remove any element that has value **v3**, we should find the previous position, that is, **2ed Node**, and then break the link and rebuilt the link between **2ed Node** and **4th Node** through:

+

temp = add2->next
temp = temp->next

1
2
3
4

![Remove operation](/images/remove.PNG)

If we loop through the linked list starting from the **ptrToHead**, the mapping relations are as follows:

+
counter i       values           position
+

when i = 0 v1 1
i = 1 v2 2
i = 2 v3 3
… … …
i = n - 2 v(n - 1) n - 1
i = n - 1 vn n

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
When i == 2, the position that should be deleted is positioned at **i+1**. Therefore, we pass **i+1** to the **erase** function. But noting that before we delete the Node, we should let **current** points to the Node that closely followed the Node to be deleted. In addition, we should not advance the counter **i** as now the Node pointed by **current** hasn't be checked. But if a Node doesn't satisfy the condition, we move forward the **current** as well as the counter **i**. 

## reverse
In our **SinglyLinkedList**, we define two **reverse** functions, one of which is a member defined based on iteration while the other one is a non-member function defined based on recursion.
```c++
// reverse the order: iteration version O(n)
template <class T>
void SinglyLinkedList<T>::reverse(){
Node* prev = nullptr;
Node* current = ptrToHead;
Node* next;
while(current != nullptr){
next = current->next;
current->next = prev;
prev = current;
current = next;
}
ptrToHead = prev;
}

+

The basic idea behind the reverse function is that let each Node stores the address of the previous Node.
The initial information is that:

+
    +
  1. the previous address for the Head Node is nullptr
  2. +
  3. the current address for the Head Node is ptrToHead
  4. +
+

There are four steps in each iteration:

+
    +
  1. temporarily stores the address of next Node as next = current->next will be rewrite
  2. +
  3. rewrite the current->next with the previous address
  4. +
  5. in next iteration, the previous address is the current address in this iteration, hence let prev = current
  6. +
  7. in next iteration, the current address is the next address in this iteration, hence let current = next
  8. +
+

The recursive version starts from the last Node but still uses the same idea. Don’t forget to add this function template as the friend of our SinglyLinkedList class.

1
2
3
4
5
6
7
8
9
10
11
12
13
// reverse the order: recursion version O(n)
// space complexity: O(n)
template<typename X>
void reverse(SinglyLinkedList<X>& l, typename SinglyLinkedList<X>::Node* p){
if(p->next == nullptr || l.empty()){
l.ptrToHead = p;
return;
}
reverse(l, p->next);
typename SinglyLinkedList<X>::Node* q = p->next;
q->next = p;
p->next = nullptr;
}

+

Test

I have tested every member in this class template and the results show that this SinglyLinkedList works perfectly. Please find the test program and results below:

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
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
/*
* this program tests all operations that provided by the
* SinglyLinkedList<T> class
* created by Liam on: 28 Apr 2018
*/

#include <iostream>
#include "SinglyLinkedList.h"

using std::endl; using std::cout;

template <class T>
void print(T& l){
auto it = l.begin();
for(; it != nullptr; it = it->next){
cout << it->data << " ";
}
cout << endl;
}

int main(){

{ // construct an empty linked list
SinglyLinkedList<int> s;
if(s.empty())
cout << "s is an empty linked list\n"
"the size of s1 is: " << s.size() << endl;

// call destructor once reaches the end of this block
}
cout << endl;

{ // construct a linked list that contains 10 elements, all values are 100
SinglyLinkedList<int> s(10, 100);

// construct a linked list by copying from s
SinglyLinkedList<int> s_copy(s);
if(!s.empty() && !s_copy.empty()){
cout << "the size of s is: " << s.size() << endl;
cout << "the size of s_copy is: " << s_copy.size() << endl;
}

// print the contents of s
cout << "all elements in s: ";
print(s);

// call destructor twice
}
cout << endl;

{ // assignment
SinglyLinkedList<int> s(10, 100);
SinglyLinkedList<int> s_copy;
s_copy = s;

// print the contents of s
}
cout << endl;

{ // push front
SinglyLinkedList<double> s;
for(int i = 5; i != 0; --i)
s.push_front(i);

cout << "after adding elements at front, s becomes: ";
print(s);

// push back
for(int i = 5; i != 0; --i)
s.push_back(i);

cout << "after adding elements at the end, s becomes: ";
print(s);


// insert at position 5
for(int i = 5; i != 0; --i)
s.insert(i, 0);

cout << "after inserting elements in-between, s becomes: ";
print(s);

// delete from the begining
for(int i = 5; i != 0; --i)
s.pop_front();

cout << "after deleting from the begining, s becomes: ";
print(s);

// delete from the end
for(int i = 5; i != 0; --i)
s.pop_back();

cout << "after deleting from the end, s becomes: ";
print(s);

// erase at in-between positions
for(int i = 3; i != 0; --i)
s.erase(3);

cout << "after deleting from other positions, s becomes: ";
print(s);

}
cout << endl;

{ // remove
SinglyLinkedList<int> s(5, 5);
for(int i = 5; i != 0; --i)
s.insert(5, i);

cout << "at present, s contains following elements: ";
print(s);

s.remove(5);
cout << "after removing all elements equal 5, s becomes: ";
print(s);
}
cout << endl;

{ // test reverse function
SinglyLinkedList<int> s;
for(int i = 0; i != 10; ++i)
s.push_back(i);

cout << "at present, s contains following elements: ";
print(s);

s.reverse();
cout << "reverse: ";
print(s);

s.reverse();
cout << "reverse again: ";
print(s);
}

return 0;
}

+

Outputs:

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
default constructor
s is an empty linked list
the size of s1 is: 0
destructor

constructor with parameters
copy constructor
the size of s is: 10
the size of s_copy is: 10
all elements in s: 100 100 100 100 100 100 100 100 100 100
destructor
destructor

constructor with parameters
default constructor
assignment operator
destructor
destructor

default constructor
after adding elements at front, s becomes: 1 2 3 4 5
after adding elements at the end, s becomes: 1 2 3 4 5 5 4 3 2 1
after inserting elements in-between, s becomes: 0 1 0 2 0 3 0 4 0 5 5 4 3 2 1
after deleting from the begining, s becomes: 3 0 4 0 5 5 4 3 2 1
after deleting from the end, s becomes: 3 0 4 0 5
after deleting from other positions, s becomes: 3 0
destructor

constructor with parameters
at present, s contains following elements: 5 5 5 5 1 2 3 4 5 5
after removing all elements equal 5, s becomes: 1 2 3 4
destructor

default constructor
at present, s contains following elements: 0 1 2 3 4 5 6 7 8 9
reverse: 9 8 7 6 5 4 3 2 1 0
reverse again: 0 1 2 3 4 5 6 7 8 9
destructor

+
+ + +
+ + + + + + + + + + + +
+ + + +
+ + + +
+ +
+
+ + +
+ + + + + + + + + +
+ + + + + + + + + +
+
+ + + + +
+ + +
+ + + + +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/20/03/2018/Accelerated-C-Solutions-to-Exercise-Chapter-5-Part-4/index.html b/20/03/2018/Accelerated-C-Solutions-to-Exercise-Chapter-5-Part-4/index.html new file mode 100644 index 00000000..6876053a --- /dev/null +++ b/20/03/2018/Accelerated-C-Solutions-to-Exercise-Chapter-5-Part-4/index.html @@ -0,0 +1,831 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Accelerated C++ Solutions to Exercises(Chapter 5 Part 4) | Liam's Blog + + + + + + + + + + + + + + + + + + +
+
+ + + +
+
+
+
+ + +
+ + + + + + + + +
+ + + +
+ + + + + + + +
+ + + +

Accelerated C++ Solutions to Exercises(Chapter 5 Part 4)

+ + + +
+ + + + + +
+ + + + + +

Exercise 5-9

Write a program to write the lowercase words in the input followed by the uppercase words.

+

Solution & Results

The solution strategy is pretty straightforward: check each entered word to see whether it contains one or more uppercase letters, and store words into two containers according to the check results. I present the code and test performance in below.

+

A complete 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
#include <iostream>
#include <string>
#include <vector>

using std::cin; using std::cout;
using std::endl; using std::string;
using std::vector;

// function declaration
bool pureLowercaseWords(const string &word); // a predicate on words
void print(const vector<string> &Words); // print elements in a vector one by one

int main()
{
vector<string> lowercase_Words; // for holding words that contains only lower case letters
vector<string> uppercase_Words; // for holding words that contains at lease one upper case letters
string word;
while(cin >> word)
{
if(!word.empty())
{
if(pureLowercaseWords(word))
lowercase_Words.push_back(word);
else
uppercase_Words.push_back(word);
}
}
cout << "Words that contain only lowercase letters: " << endl;
print(lowercase_Words);
cout << "Words that contain at lease one uppercase letters: " << endl;
print(uppercase_Words);
return 0;
}

bool pureLowercaseWords(const string &word)
{
for (string::const_iterator iter = word.begin(); iter != word.end(); ++iter)
{
if (isupper(*iter))
return false;
}
return true;
}

void print(const vector<string> &Words);
{
for (vector<string>::const_iterator iter = Words.begin(); iter != Words.end(); ++iter)
cout << *iter << endl;
}

+

Test
with inputs: University of Oxford is one of best universities all over the world according to QS Ranking

+

The program works as expected:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
Words that contain only lowercase letters: 
of
is
one
of
best
universities
all
over
the
world
according
to
Words that contain at lease one uppercase letters:
University
Oxford
QS
Ranking

+
+

Exercise 5-10

Palindromes are words that are spelled the same right to left as left to right. Write aprogram to find all the palindromes in a dictionary. Next, find the longest palindrome.

+

Solution & Results

This project has two goals:

+
    +
  1. telling whether a word is a palindrome.
  2. +
  3. find the longest palindrome.
  4. +
+

If a word is not a palindromes, we dicard it directly. On the contrary, if a word is a palindromes, we need to store it for generating a final report. To find the longest palindrome, we can compare the size of two words and keep the longer word, finally obtaining the longest palindrome(or the first observed longest one).

+

To determine whether a word is a palindrome, one key point is that whether the palindromes are case sensitive. I treat the palindromes as case insensitive in this program and hence the predicate on a word is based on the lowercase(or uppercase) version of a word. Another point is that whether a single character is a palindrome. I regard it as a palindrome and treat a single character as a one-letter word.

+

Apparently, the key is to find an algorithm that can identify a palindrome. According to the definition, palindromes are words that are spelled the same right to left as left to right. A possible solution is to divide the characters in a word into two groups, which are symmetrically located at two side of a mid point, and compare them one pair by one pair. If all pairs are same, the word is a palindromes.

+

Intuitively, if the number of characters in a word is odd, then, there exists one unique mid point, which splits two groups of characters. The graph below shows this case and illustrates the relationship of one pair of characters regarding to their corresponding iterators.

+

A word contains odd number characters

+

Clearly, this process can be translated to a while loop and the stopping point is the mid point (i.e. the position that iterator word.begin() + size/2 refers to).

+

Similarly, if the number of characters in a word is even, characters can be exactly splitted into two groups, i.e. word.size()/2 pairs.

+

A word contains even number characters

+

It can be seen from above graph, the stopping point is also the position that word.begin() + size/2 refers to. Before this point, the last pair of characters(i.e. two mid elements) are compared.

+

Now the predicate can be write as:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
bool isPalindromes(const string &word)
{
typedef string::const_iterator iter;
iter startPosition = word.begin();
iter endPosition = word.end() - 1;
iter midPosition = word.begin() + word.size()/2;
while (startPosition != midPosition)
{
if(tolower(*startPosition) != tolower(*endPosition))
return false;
++startPosition;
--endPosition;
}
return true;
}

+

Firstly, I choose to compare two endpoints and then move both points 1 position forward to the middle in the following iteration. Finally, the while loop stops when all pairs of characters have been compared.

+

Once finishes this function, we can write the main function directly. The code below shows the complete program. Note that I seperate the condition word.size() == 1 from isPalindromes(word) simply for the purpose of computational efficiency.

+
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 <algorithm>
#include <iostream>
#include <string>
#include <vector>

using std::cin; using std::cout;
using std::endl; using std::string;
using std::vector; using std::max;

// function declarations
bool isPalindromes(const string &word);
void print(const vector<string> &palindromes);

int main()
{
vector<string> palindromes; // hold all identified palindromes
string longest; // hold the first observed longest palindrome

// read words
string word;
while(cin >> word)
{
if(word.size() == 1 || isPalindromes(word))
{
if(longest.size() < word.size())
longest = word;
palindromes.push_back(word);
}
}

// print all palindromes
cout << "Following words are palindromes:" << endl;
print(palindromes);

// print the longest
cout << "The longest palindrome(first observed) is:\n" << longest << endl;
return 0;
}

void print(const vector<string> &palindromes)
{
for(vector<string>::const_iterator iter = palindromes.begin();
iter != palindromes.end(); ++iter)
cout << *iter << endl;
}

// please add the predicate here
+

Now let’s test how the program performs. I entered following words successively:

+
1
Rotator Anna Oxford Civic Sampling Kayak Level Madam Mom Noon shape racecar radar Redder Refer Repaper Please Rotor again Sagas impossible Solos distribution Stats Tenet Wow
+

The results are displayed below:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
Following words are palindromes:
Rotator
Anna
Civic
Kayak
Level
Madam
Mom
Noon
racecar
radar
Redder
Refer
Repaper
Rotor
Sagas
Solos
Stats
Tenet
Wow
The longest palindrome(first observed) is:
Rotator

+

Yeah, the program works perfectly.

+

Exercise 5-11

In text processing it is sometimes useful to know whether a word has any ascenders or descenders. Ascenders are the parts of lowercase letters that extend above the text line;in the English alphabet, the letters b, d, f, h, k, l, and t have ascenders. Similarly, the descenders are the parts of lowercase letters that descend below the line; In English,theletters g, j, p, q, and y have descenders. Write a program to determine whether a word has any ascenders or descenders. Extend that program to find the longest word in the dictionary that has neither ascenders nor descenders.

+

Solution & Results

The idea to solve this exercise is similar to that described in above exercise. The first step is to check whether a word contains any ascenders or descenders, by means of comparing each character in the word to ascenders or descenders. The second step is to store the word into corresponding vector according to the check results in step 1. The longest word that has neither ascenders nor descenders can be found using the same method as above.

+

I present the complete program below followed by a performance test. Noting that I defined two global variables which can be accessed anywhere inside of the file. But they cannot be modified as I add const in defining them. The purpose is to avoid that these two objects will be created repeatedly if they are put into the precidate. This problem can be easily solved with specifier static, which will be introduced in chapter 6.

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
65
66
67
68
69
70
71
72
// Accelerated C++ Solutions Exercises 5-11
#include <algorithm>
#include <iostream>
#include <string>
#include <vector>

using std::cin; using std::cout;
using std::endl; using std::string;
using std::vector; using std::max;

// global variables
const string ascenders = "bdfhlt";
const string descenders = "gjpqy";

// function declarations
bool noAscDes(const string &word);
void print(const vector<string> &words);

int main()
{
vector<string> asc_des; // hold words that contain any ascenders or descenders
vector<string> no_asc_des; // hold words that has neither ascenders nor descenders
string longest_no; // find the longest word that has neither ascenders nor descenders

// read words
string word;
while(cin >> word)
{
if(noAscDes(word))
{
if (longest_no.size() < word.size())
longest_no = word;
no_asc_des.push_back(word);
}
else
asc_des.push_back(word);
}

cout << "Following words contain either ascenders or descenders:" << endl;
print(asc_des);
cout << "Following words contain neither ascenders nor descenders:" << endl;
print(no_asc_des);
cout << "The longest word that has neither ascenders nor descenders is:\n" << longest_no << endl;

return 0;
}

bool noAscDes(const string &word)
{
typedef string::const_iterator iter;
for (iter i = word.begin(); i != word.end(); ++i)
{
for(iter j = ascenders.begin(); j != ascenders.end(); ++j)
{
if (*i == *j)
return false;
}
for (iter k = descenders.begin(); k != descenders.end(); ++k)
{
if (*i == *k)
return false;
}
}
return true;
}

void print(const vector<string> &words)
{
for(vector<string>::const_iterator iter = words.begin();
iter != words.end(); ++iter)
cout << *iter << endl;
}

+

Test results

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
Inputs:
throughout the course of history there have been many famous speeches that changed the world from on the mount to the inaugural speeches of modern leaders their words have become an inspiration to millions of people

Outputs:
Following words contain either ascenders or descenders:
throughout
the
of
history
there
have
been
many
famous
speeches
that
changed
the
world
from
the
mount
to
the
inaugural
speeches
of
modern
leaders
their
words
have
become
inspiration
to
millions
of
people
Following words contain neither ascenders nor descenders:
course
on
an
The longest word that has neither ascenders nor descenders is:
course

+

As above results show, It works fine.

+
+

References

Koenig, A. and Moo, B.E., 2000. Accelerated C++.

+

Lippman, S.B., Lajoie, J. and Moo, B.E., 2013. C++ Primer, 5th edt.

+ + +
+ + + + + + + + + + + +
+ + + +
+ + + +
+ +
+
+ + +
+ + + + + + + + + +
+ + + + + + + + + +
+
+ + + + +
+ + +
+ + + + +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/2018/02/01/hello-world/index.html b/2018/02/01/hello-world/index.html deleted file mode 100644 index 4915c6c2..00000000 --- a/2018/02/01/hello-world/index.html +++ /dev/null @@ -1,532 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - Hello World | Liam's Blog - - - - - - - - - - - - -
-
- -
-
- - -
- - -

If history repeats itself, and the unexpected always happens, how incapable must Man be of learning from experience?—George Bernard Shaw.

-
- - -
- - - - -
-
- - -
- - 0% -
- - - - -
-
-
- - -
- - -
- - - -
- - - - - -
-

- Hello World -

- - -
- - - - -
- - -

Welcome to Hexo! This is your very first post. Check documentation for more info. If you get any problems when using Hexo, you can find the answer in troubleshooting or you can ask me on GitHub.

-

Quick Start

Create a new post

1
$ hexo new "My New Post"
- -

More info: Writing

-

Run server

1
$ hexo server
- -

More info: Server

-

Generate static files

1
$ hexo generate
- -

More info: Generating

-

Deploy to remote sites

1
$ hexo deploy
- -

More info: Deployment

- -
- - - - - - -
- -
- - - - -
- - - - -
- - -
- - - - -
- - - - - - - - -
-
- - -
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/2018/02/11/C-Working-with-strings/index.html b/2018/02/11/C-Working-with-strings/index.html deleted file mode 100644 index d7a5601b..00000000 --- a/2018/02/11/C-Working-with-strings/index.html +++ /dev/null @@ -1,632 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - C++ - Working with strings | Liam's Blog - - - - - - - - - - - - -
-
- -
-
- - -
- - -

If history repeats itself, and the unexpected always happens, how incapable must Man be of learning from experience?—George Bernard Shaw.

-
- - -
- - - - -
-
- - -
- - 0% -
- - - - -
-
-
- - -
- - -
- - - -
- - - - - -
-

- C++ - Working with strings -

- - -
- - - - -
- - -

Variables, Initialization & Declarations

Conventionally, I present the program that provided in Accelerated C++: Practical Programming by Example here:

-
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
// ask for a person's name, and greet the person
#include <iostream>
#include <string>

int main()
{
// ask for the person's name
std::cout << "Please enter your first name: ";

// read the name
std::string name; // define name
std::cin >> name; // read into

// write a greeting
std::cout << "Hello, " << name << "!" << std::endl;
return 0;
}
-

The program is based on Hello, world! and is improved to say Hello to anyone you specified. It asks you to input a name (e.g. Batman) and then output as follows:

-
1
Hello, Batman!
-

Variables

To realize this function, a variable is defined to hold input. According to Koenig (2000), “a variable is an object that has a name” while “an object is a part of the computer’s memory that has a type”. Therefore, a variable should have:

-
    -
  1. name: for identifying the object that is needed to be manipulated.
  2. -
  3. type: determines the size and layout of the variable’s memory, range of values that can be stored within the memory, and operations that can be applied to the variable.
  4. -
-

In this case, the variable is named name and its type is std::string. The std:: means that the string type is defined in namespace std. Therefore, we need to include the standard header string as same as include the header iostream at the begining of this program. This statement also indicates the syntax of defining variables: a type specifier followed by one name (or more names sepreated by commas), and ends with a semicolon.

-

As this program shows, the variable name is defined within the function body and hence it is a local variable, i.e. the variable is only valid when the function body is executed. Once the computer reachers the }, it destroys the variable name and returns the memory that the variable occupied during its lifetime.

-

Initialization

An object is initialized and gets a specified value (i.e. initializer) when it is created. C++ defines several types of initializations. The example provided above uses default initialization in defining the variable name.

-

Default initialization

The default initialization means that no initializer is applied to a variable when it is defined. In the case of default initialization, the variable gets a default value which depends on its type and may also depends on where the variable is defined. Note that the default value of an object of built-in type, e.g. arithmetic type, depends on where it is defined:

-
    -
  • variables defined outside any function body are initialized to zero (or null character for char type?).
  • -
  • in general, variables of built-in type defined inside a function are uninitialized.
  • -
-

For example, I write a program as below for testing:

-
1
2
3
4
5
6
7
8
9
10
11
12
13
14
#include<iostream>
using std::cout;
using std::endl;

int i; bool h; char m;

int main()
{
int j; bool k; char n;
cout << i << ' ' << j << endl;
cout << h << ' ' << k << endl;
cout << m << ' ' << n << endl;;
return 0;
}
-

The results is:

-
1
2
3
0 4201179
0 0
a
- -

with warnings:

-
1
2
3
'j' is used uninitialized in this function [-Wuninitialized]
'k' is used uninitialized in this function [-Wuninitialized]
'n' is used uninitialized in this function [-Wuninitialized]
-

Clearly, it will result severe errors if we try to access the value or copy the value of such uninitialized variables.

-

Each class defines different ways to initialize objects of the class type. If default initialization is allowed, the default value of an object is determined by the class. For example, the default initialization of a string leads to an empty string (i.e. no characters). In the first program, the variable name is the empty string.

-

Copy & Direct initialization

Another way to initialize variables is using =, that is copy initialize by copying the initializer on the right side into the created object. For example:

-
1
2
3
int i = 10;
char j = 'a';
string s1 = "Hello";
-

The values used to initialize objects can also be expressions:

-
1
2
int i = 10;
int j = i*10;
-

The copy initialization seems like assignment, however, is completely different. As Lippman etc. (2012) point out: “Initialization happens when a variable is given a value when it is created. Assignment obliterates an object’s current value and replaces that value with a new one.”

-

We can also initialize above objects without using “=”:

-
1
2
3
int i(10);
char j('a');
string s1("Hello");
-

This way is named direct initialization. The string type variables can also be initialized using an alternative form:

-
1
2
3
4
5
6
7
8
// direct initialization, s1 is aaaaa
string s1(5, 'a');
// copy initialization, s2 is aaaaa
string s2 = string(5, 'a');

// decomposition of s2
string s0(5, 'a'); // s0 is aaaaa
string s2 = s0; // copy s0 into s2
-

The differences between copy initialization and direct initialization will not be disscussed until chapter 9;

-

List initialization

The C++ 11 standards supports an alternative form for the copy initialization and direct initialization using curly braces. For example:

-
1
2
3
4
int i = {10}; // same as _int i = 10;_
int j{10}; // same as _int j(10);_
string s1 = {"Hello"}; // same as _string s1 = "Hello";_
string s2 {"Hello"}; // same as _string s2("Hello");_
-

This form is known as list initialization. The major difference between list initialization and above methods is that list initialization doesn’t allow narrowing for the conversion of built-in type variables. To show this property, I performed an experiment using following codes:

-
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
#include<iostream>
using std::cout;
using std::endl;

int main()
{
double i = 10.9876;
int num1 = i;
int num2(i);
int num3{i};

cout << num1 << endl; // ok but result is truncated: 10
cout << num2 << endl; // ok but result is truncated: 10
cout << num3 << endl; // warning: narrowing conversion of 'i' from 'double' to 'int' inside { }
return 0;
}
-

Hence, list initialization is a more reliable approach compared with other alternatives.

-

Declarations

As analysed above, the definition of a variable requires type, name and initializers. For convenience, C++ supports separate compilation, that is, allows a program to be split into several files and compiled independently. In fact, we have already used the separate compilation in previous examples, such as the usage of IO system. The std::cout is defined in the header iostream but can be used in our programs through simply including the header file. To realize this function, a variable needs declaration which requires stating the type and name. The difference between declaration and definition is that the variable may be explicitly initialized in its definition. To declare a a variable only, we need add extern and don’t explicitly initialize it:

-
1
2
3
extern int i; // declares but not defines i
int i; // declares and defines i
extern int i = 10; // declares and defines i
-

But it should be noted that(Lippman etc. 2012):

-
    -
  • It is an error to provide an initializer on an extern inside a function.
  • -
  • Variables must be defined exactly once but can be declared many times.
  • -
-

Operations on strings

Reading & Writing strings

To read the inputs into name, this program uses std::cin and operator >>. It will discard whitespace characters such as space, tab and backspace, and then reads chars into name until it encounters another whitespace. For example, following three piece of inputs yield same outputs when we run the program:

-
1
2
3
Bruce
[a tab space]Bruce
Bruce Lee
-

Results:

-
1
2
3
Hello, Bruce!
Hello, Bruce!
Hello, Bruce!
-

The IO library accumulates and stores the characters using an internal data structure named buffer, and flushes the buffer by writing the contents to the output device only when necessary. There are three events that cause the system to flush the buffer:

-
    -
  • the buffer might be full and the library will flush it automatically.
  • -
  • the library might be asked to read from the standard input stream. Then, it will flush the buffer immediately. This indicates another side effect of input operation.
  • -
  • when we explicitly say to do so: e.g. std::endl.
  • -
-

The input operator >> is also left-associative and returns the left-hand operands as their results. Therefore, multiple reads can be done like below codes :

-
1
2
3
string s1, s2; // define two string type variables
cin >> s1 >> s2; // reads from cin into s1 and s2 seprately
cout << s1 << s2 << endl; // writes two strings
-

In some cases, we would like to read chars as well as whitespaces. To fulfill this, we need to use getline instead of >> with the syntax:

-
1
std::getline(std::cin, name)
-

The getline function will read chars into variable name until it encounters line feed (note that the line feed will also be read but not stored into the variable). I modified the program using getline:

-
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
#include <iostream>
#include <string>

int main()
{
// ask for the person's name
std::cout << "Please enter your full name: ";

// read the name
std::string name; // define name
std::getline(std::cin, name); // read into

// write a greeting
std::cout << "Hello, " << name << "!" << std::endl;
return 0;
}
-

When I run the program and input “Bruce Lee”, it results:

-
1
2
Please enter your first name: Bruce Lee
Hello, Bruce Lee!
- -

The string size and concatenate operations

Now we go into a little bit complex operations and aim to produce a framed greeting like below:

-
1
2
3
4
5
6
Please enter your full name: Bruce Lee 
*********************
* *
* Hello, Bruce Lee! *
* *
*********************
-

Let’s provide the program first:

-
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
// ask for a person's name, and generate a framed greeting
#include <iostream>
#include <string>

int main()
{
std::cout << "Please enter your full name: ";
std::string name;
std::getline(std::cin, name);

// build the message that we intend to write
const std::string greeting = "Hello, " + name + "!";

// build the second and forth lines of the output
const std::string spaces(greeting.size(), ' ');
const std::string second = "* " + spaces + " *";

// build the first and fifth lines of the output
const std::string first(second.size(), '*');

// write it all
std::cout << std::endl;
std::cout << first << std::endl;
std::cout << second << std::endl;
std::cout << "* " << greeting << " *" << std::endl;
std::cout << second << std::endl;
std::cout << first << std::endl;

return 0;
}
-

The first three statements are exactly the same as those in the last program. However, we define a new string type variable named greeting and initialize it with the message that we will write using opetator +:

-
1
const std::string greeting = "Hello, " + name + "!";
-

The keyword const means that the value of the variable keeps constant since the first time read in. The + concatenate two strings (may also be one string and one string literals but cannot be two string literals) into a single string. As mentioned in previous chapter, operators have different effect on operands depending on the types of operands, which is commonly termed as overloaded. Same as operators >> and <<, + is also left-associative.

-

The remainder parts of the frame is simply constituted by asterisks and spaces while the numbers of asterisks and spaces are determined by the size of greeting. Recalling the ways to initialize a string type variable filled by same chars(e.g. ‘a’):

-
1
string s0(5, 'a'); // s0 is aaaaa
-

What we need here is to figure out the size of greeting. greeting.size() is a member function of the object. By calling this member function, we will obtain an integer that means the number of chars in greeting. The returned value is in fact not a int type value, but a string::size_type. In this case, we know that this value is unsigned and hence cann’t compare this value with other signed values for avoiding errors. If we don’t know what exactly the type is, we can use type deduction with specifiers auto and decltype. For example:

-
1
auto len = greeting.size();
-

The variable len is string::size_type. Similarly:

-
1
decltype(greeting.size) len;
-

The differences of these two specifiers are:

-
    -
  • the variable that defined using auto must have initializer.
  • -
  • auto‘s type deduction ignores top-level consts but keeps low-level consts. But decltype‘s type deduction returns type including top-level consts. (This will be discussed again in chapter 10).
  • -
-

Random access using a subscript

If there needs to access some characters of the string, we can use the subscript operator([]) to access and sequentially operate on individual characters. Inside the operator, an index value is required to denote the position of the character to be accessed. The operation returns a reference to the character of the given position.

-

The index value should be a value of type string::size_type, and its range should be >= 0 and < size(). In other words, it use asymmetric range [0, size()). To show more about the random access, I did a experiment as below

-
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
#include <iostream>
#include <vector>

using std::cin; using std::string;
using std::cout; using std::endl;

int main()
{
string str = {"abcdef"};

// obtain the size of str1
string::size_type strSize = str.size();

// define an signed type variable
int x = 0;

// write the string one character by one character
cout << str[x] << str[1] << str[2] << str[3]
<<str[4] << str[strSize-1] << endl;

// access the character beyond the range of [0, strSize-1)
cout << str[strSize] << str[strSize + 1] << str[10] << endl;

return 0;
}
-

Results:

-
1
2
abcdef
b
-

The example above shows that it leads to unknown results if we use a subscript that beyond the range of [0, size()). In addition, it works if we use a integer value has a signed type (in contrast, the string::size_type is unsigned integeral value).
This is because the index value of the signed type can be converted to the type of string::size_type.

-

Other operations

empty function returns a bool type value that indicates whether the string type variable is empty.

-
1
greeting.empty(); // if greeting is empty, it returns 1, otherwise returns 0;
-

<, <=, >, >= “test whether one string is less than, less than or equal to, greater than, or greater than or equal to another. These operators use the same strategy as a (case-sensitive) dictionary” (Lippman etc. 2012, p80).

-
-

Next: C++ - Looping and counting.

- -
- - - - - - -
- -
- - - - -
- - - - -
- - -
- - - - -
- - - - - - - - -
-
- - -
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/2018/02/22/Accelerated-C-Solutions-to-Exercises-Chapter-1/index.html b/2018/02/22/Accelerated-C-Solutions-to-Exercises-Chapter-1/index.html deleted file mode 100644 index d5957575..00000000 --- a/2018/02/22/Accelerated-C-Solutions-to-Exercises-Chapter-1/index.html +++ /dev/null @@ -1,630 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - Accelerated C++ Solutions to Exercises(Chapter 1) | Liam's Blog - - - - - - - - - - - - -
-
- -
-
- - -
- - -

If history repeats itself, and the unexpected always happens, how incapable must Man be of learning from experience?—George Bernard Shaw.

-
- - -
- - - - -
-
- - -
- - 0% -
- - - - -
-
-
- - -
- - -
- - - -
- - - - - -
-

- Accelerated C++ Solutions to Exercises(Chapter 1) -

- - -
- - - - -
- - -

Exercise 1-0

Compile, execute, and test the programs in this chapter.

-

Solution & Results

The first program:

-
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
// ask for a person's name, and greet the person
#include <iostream>
#include <string>

int main()
{
// ask for the person's name
std::cout << "Please enter your first name: ";

// read the name
std::string name; // define name
std::cin >> name; // read into

// write a greeting
std::cout << "Hello, " << name << "!" << std:: endl;
return 0;
}
-

Test: type Liam according to the prompt and the console window displays as follows:

-
1
2
Please enter your first name: Liam
Hello, Liam!
-

The second 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
// ask for a person's name, and generate a framed greeting
#include <iostream>
#include <string>

int main()
{
std::cout << "Please enter your first name: ";
std::string name;
std::cin >> name;

// build the message that we intend to write
const std::string greeting = "Hello, " + name + "!";

// build the second and forth lines of the output
const std::string spaces(greeting.size(), ' ');
const std::string second = "* " + spaces + " *";

// build the first and fifth lines of the output
const std::string first(second.size(), '*');

// write it all
std::cout << std::endl;
std::cout << first << std::endl;
std::cout << second << std::endl;
std::cout << "* " << greeting << " *" << std::endl;
std::cout << second << std::endl;
std::cout << first << std::endl;

return 0;
}
-

Test: type Liam according to the prompt and the console window displays as follows:

-
1
2
3
4
5
6
7
Please enter your first name: Liam

****************
* *
* Hello, Liam! *
* *
****************
- -

Analysis

See C++ - Working with strings.

-
-

Exercise 1-1

Are the following definitions valid? Why or why not?

-
1
2
const std::string hello = "Hello";
const std::string message = hello + ", world" + "!";
-

Solution & Results

Yes, above definitions are valid.

-

The first statement defines a string type variable named hello and initialize the variable with copying string literals Hello into it. The second statement defines a variable named message and copy the value of the right side of the = into it. The right side is an expression that the concatenation operator + operates on the object hello and two string literals. First, the operator is left-associative and hence:

-
1
2
const std::string message = hello + ", world" + "!";
= (hello + ", world") + "!";
-

Due to the fact that the result of (hello + “, world”) is also a string type object, the whole expression is simply to concatenate a string and a string literals, which is legal and valid. The test is shown below:

-
1
2
3
4
5
6
7
8
9
10
11
12
13
#include <iostream>
#include <string>
using std::cout; using std::endl; using std::string;

int main()
{
const std::string hello = "Hello";
const std::string message = hello + ", world" + "!";
cout << hello << endl;
cout << message << endl;
return 0;

}
-

The program runs ok and display results on the console window as below:

-
1
2
Hello
Hello, world!
-

Analysis

As stated by Lippman ect. (2012): “When we mix strings and string or character literals, at least one operand to each + operator must be of string type”. For more analysis, please see C++ - Working with strings.

-
-

Exercise 1-2

Are the following definitions valid? Why or why not?

-
1
2
const std::string exclam = "!";
const std::string message = "Hello" + ", world" + exclam;
-

Solution & Results

The definition of exclam is valid but the definition of message is illegal and hence invalid. This is because that the operator + is left-associative and cannot concatenate two string literals. Follow program shows my test:

-
1
2
3
4
5
6
7
8
9
10
11
12
13
#include <iostream>
#include <string>
using std::cout; using std::endl; using std::string;

int main()
{
const std::string exclam = "!";
const std::string message = "Hello" + ", world" + exclam;
cout << exclam << endl;
cout << message << endl;
return 0;

}
-

As expected, the compilation reports errors as below:

-
1
invalid operands of types 'const char [6]' and 'const char [8]' to binary 'operator+'.
-

Analysis

See Exercise 1-1 and
C++ - Working with strings.

-
-

Exercise 1-3

Is the following program valid? If so, what does it do? If not, why not?

-
1
2
3
4
5
6
7
8
9
10
11
#include <iostream>
#include <string>

int main()
{
{ const std::string s = "a string";
std::cout << s << std::endl; }
{ const std::string s = "another string";
std::cout << s << std::endl; }
return 0;
}
-

Solution & Results

Yes, it is a valid program.
The program intends to print two different strings, both of which have the same name s within the main function body.
I firstly present the result when running the program:

-
1
2
a string
another string
-

These two varibles are not conflict because their names have different scopes that formed by two pairs of curly braces. Specifically:

-
    -
  • Both variables are local variables as they are inside of the main function.
  • -
  • The first s is visible from its declaration until the end of its scope, that is, the scope formed by the first nested curly braces.
  • -
  • The second s is visible from its declaration until the end of the second nested curly braces.
  • -
  • Two names refer to different entities in different scope.
  • -
-

From the perspective of memory management, the first variable exists only when the part of the program within the first nested braces is executing, and disappears and returns the memory it occupied once the computer reaches the end of the braces. It has limited lifetime and so does the second variable.

-

Analysis

See C++ - Working with strings.

-
-

Exercise 1-4

Question 1

Is the following program valid?

-
1
2
3
4
5
6
7
8
9
10
11
#include <iostream>
#include <string>

int main()
{
{ const std::string s = "a string"; //outer scope
std::cout << s << std::endl;
{ const std::string s = "another string"; // inner scope
std::cout << s << std::endl; }}
return 0;
}
-

Solution & Results

Yes, the program is valid.
In constrast with the last program, the scopes of two names are not independent with eachother but are nested:

-
    -
  • The scope of the first s is the outer scope, containing the other scope, that is, the inner scope where the second s is in.
  • -
  • The name that declared in the outer scope can be can be accessed and reused in the nested scope, i.e. inner scope.
  • -
-

Therefore, the logic of this program can be described as:

-
    -
  1. the first variable is defined and initialized with string literals “a string” and is printed out in the following statement.
  2. -
  3. “the variable” is redefined in the inner scope and initialized with string literals another string. Then, it is printed out in the following step.
  4. -
  5. the second s is destroyed at the end of its scope, that is, the first right brace (}).
  6. -
  7. the first s is still available after the first right brace, but is destroyed once the computer reaches the second right brace.
  8. -
-

Note that two variables are different and the second variable doesn’t overwriting the first one though they both use the name s. To confirm this, I simply add one statement between the first right curly brace and second right curly brace:

-
1
std::cout << s <<std::endl;
-

Then, I run the program and it yields:

-
1
2
3
a string
another string
a string
-

Question 2

what if we change } } to };} in the third line from the end?

-
1
2
3
4
5
6
7
8
9
10
11
#include <iostream>
#include <string>

int main()
{
{ const std::string s = "a string";
std::cout << s << std::endl;
{ const std::string s = "another string";
std::cout << s << std::endl; };}
return 0;
}
-

Solution & Results

The program is still valid after adding an semicolon betwween the first and second right curly brace. The semicolon typically working as a statement terminator in C++. In this case, it does nothing except forming a null statement. This program leads to the same results as the program that without adding a semicolon.

-
1
2
a string
another string
-

Analysis

I didn’t found very good interpretations about the role of the semicolon in C++. For more analysis, please move to What is the semicolon in C++? and What is the function of semicolon in C++? , where some good answers have been provided by the forum users.

-
-

Exercise 1-5

Is this program valid? If so, what does it do? If not, say why not, and rewrite it to be valid.

-
1
2
3
4
5
6
7
8
9
10
11
12
13
#include <iostream>
#include <string>

int main()
{
{ std::string s = "a string";
{ std::string x = s + ", really";
std::cout << s << std::endl;
}
std::cout << x << std::endl;
}
return 0;
}
-

Solution & Results

No, this program is invalid. S
imilarly as last two exercises, this question tries to test my understanding on the scope of a name. The scope of name s is outter scope and can be accessed in the inner scope where the variable x is defined. Therefore, it is correct that initializing x with a expression which oncatenates a string and string literals. It is also ok to output s inside the nested scope. However, the statement std::cout << x << std::endl; would be invalid due to the fact that x doesn’t exist anymore once the computer reaches the end of its scope, that is, the first right curly brace. As expected, when running this program, error occurs:

-
1
'x' was not declared in this scope
-

To correct it, we can simply put the statement:std::cout << x << std::endl; into the inner scope, or directly remove the nested curly braces as long as the name is not redefined in a same scope. Both corrections work well and following results can be seen on the console window:

-
1
2
a string
a string, really
-

Analysis

See Exercise 1-3, Exercise 1-4 and
C++ - Working with strings.

-
-

Exercise 1-6

What does the following program do if, when it asks you for input, you type two names(for example, Samuel Beckett)? Predict the behavior before running the program, then try it.

-
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
#include <iostream>
#include <string>

int main()
{ // prompt that asks for input
std::cout << "What is your name? ";

// define an string type variable named **name** which is empty initially.
std::string name;

// read the contents into **name**
std::cin >> name;

// output greetings as well as asking for input
std::cout << "Hello, " << name
<< std::endl << "And what is yours?";

// read new contents into the same variable
std::cin >> name;

// output greeting
std::cout << "Hello, " << name
<< "; nice to meet you too!" << std::endl;
return 0;
}
-

Solution & Results

The program intends to create a greeting conversation between two people. For For clarity, I added comments for each statement first. Let’s analyse this program step by step:

-
    -
  1. once click the running button, the computer reads the first statement and stores the contents *What is your name? * into buffer.
  2. -
  3. the cin statement triggers the flush of cout, and user can type two names Samuel Beckett according to the prompt. I predict following contents would be written on the output device:
    1
    What is your name? Samuel Beckett
  4. -
  5. then, the cin reads from the first character untill it encounters the whitespace. Therefore, only Samuel is stored into name.
  6. -
  7. the fouth statement would flush the cout because of the manipulator endl. Then, the console windows should have following outputs:
    1
    Hello, Samuel
  8. -
  9. the fifth statement cin flushes buffer and following sentence will be printed out straight after above contents.
    1
    And what is yours?
  10. -
  11. then, cin starts reading the rest content Beckett and stores into name. The new name Beckett rewrites this variable.
  12. -
  13. finally, the last buffer flush happens once the computer reads std::endl. Following contents are expected to appear on the console window.
    1
    Hello, Beckett; nice to meet you too!
  14. -
  15. finished.
  16. -
-

As ecpected, the final results of this program is shown as follows:

-
1
2
3
What is your name? Samuel Beckett
Hello, Samuel
And what is yours?Hello, Beckett; nice to meet you too!
-

Analysis

I am not entirly sure about my analysis, and may update this in the futuer if I get new ideas.

-
-

References

Koenig, A. and Moo, B.E., 2000. Accelerated C++.

-

Lippman, S.B., Lajoie, J. and Moo, B.E., 2013. C++ Primer, 5th edt.

- -
- - - - - - -
- -
- - - - -
- - - - -
- - -
- - - - -
- - - - - - - - -
-
- - -
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/2018/02/22/Accelerated-C-Solutions-to-Exercises-Chapter-2/index.html b/2018/02/22/Accelerated-C-Solutions-to-Exercises-Chapter-2/index.html deleted file mode 100644 index 98d7f853..00000000 --- a/2018/02/22/Accelerated-C-Solutions-to-Exercises-Chapter-2/index.html +++ /dev/null @@ -1,567 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - Accelerated C++ Solutions to Exercises(Chapter 2 Part 1) | Liam's Blog - - - - - - - - - - - - -
-
- -
-
- - -
- - -

If history repeats itself, and the unexpected always happens, how incapable must Man be of learning from experience?—George Bernard Shaw.

-
- - -
- - - - -
-
- - -
- - 0% -
- - - - -
-
-
- - -
- - -
- - - -
- - - - - -
-

- Accelerated C++ Solutions to Exercises(Chapter 2 Part 1) -

- - -
- - - - -
- - -

Exercise 2-0

Compile and run the program presented in this chapter

-

Solution & Results

This exercise has been accomplished in C++ - Looping and counting with detailed explination.

-
-

Exercise 2-1

Change the framing program so that it writes its greeting with no separation from the frame.

-

Solution & Results

This can be easily accomplished by changing the number of the blanks to 0.

-
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
#include <iostream>
#include <string>

// using namespace std and names
using std::cout; using std::cin;
using std::endl; using std::string;

int main()
{
// ask for the person's name
cout << "Please enter your first name: ";

// read the name
string name;
cin >> name;

// build the message that we intend to write
const string greeting = "Hello, " + name + "!";

// the number of blanks surrounding the greeting
// change to 0 to meet the requirement
const int pad = 0;

// the number of rows and columns
const int rows = pad * 2 + 3;
const string::size_type cols = greeting.size() + pad * 2 + 2;

// write a blank line to separate the output from the input
cout << endl;

// write rows rows of output
// invariant: we have written r rows so far
for (int r = 0; r != rows; ++r)
{
string::size_type c = 0;
// we have written c characters so far in the current row
while (c != cols)
{
// are we on the border?
if (r == 0 || r == rows - 1 ||
c == 0 || c == cols - 1)
{
cout << "*";
++c;
}
else
{ // is it time to write the greeting?
if (r == pad + 1 && c == pad + 1)
{
cout << greeting;
c += greeting.size();
}
else
{
cout <<" ";
++c;
}
}
}
cout << endl;
}
return 0;
}
-

Again, once we typed Bruce after the input prompt, the program writes below outputs on the console window

-
1
2
3
4
5
Please enter your first name: Bruce

***************
*Hello, Bruce!*
***************
-

Analysis

See deatiled analysis in C++ - Looping and counting.

-
-

Exercise 2-2, 2-3

2-2. Change the framing program so that it uses a different amount of space to seperate sides from the greeting than it uses to seperate the top and botton borders from the greeting.

-

2-3. Rewrite the framing program to ask the user to supply the amount of spacing to leave between the frame and the greeting.

-

These two exercises will be answered together.

-

Solution & Results

It has been seen from the program (as shown in Exercise 2-1), the variable pad controls the spaces. Therefore, it is necessary to replace the pad with two seperate variables for controling the spaces on the leftside and rightside, and the spaces on the upside and downside, respectively. For example, I define two variables with initializers 2 and 3 seperately

-
1
2
const int lr_sides_pad  = 2; // controls left and right blanks
const int tb_sides_pad = 3; // controls top and bottom blanks
-

Correspondingly, the size of rows becomes

-
1
const int rows = tb_sides_pad * 2 + 3
-

and the size of cols becomes

-
1
const string::size_type cols = greeting.size() + lr_sides_pad * 2 + 2;
-

In addition, the condition for determining the position of the greeting changes to

-
1
r == tb_sides_pad + 1 && c == lr_sides_pad + 1
-

Up to now, I have replaced all pad with above two new variables. However, the frame size is still predetermined in the program and not very flexible. We further change it so that the size can meet each user’s preference. We need to redifine the variables and assign user-defined values to them.

-
1
2
3
4
5
6
7
8
9
10
11
12
13
// ask for the number of blanks that on the left or right side
cout << "Please enter the number of spaces between the greeting and the left border (or the right boder): ";

// read the number of blanks that on the left or right side
int lr_sides_pad;
cin >> lr_sides_pad;

// ask for the number of blanks that on the upper or below side
cout << "Please enter the number of spaces between the greeting and the top border (or the bottom boder): ";

// read the number of blanks that on the left or right side
int tb_sides_pad;
cin >> tb_sides_pad;
- -

Now the program becomes

-
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
65
66
67
68
69
70
71
72
73
74
#include <iostream>
#include <string>

// using namespace std and names
using std::cout; using std::cin;
using std::endl; using std::string;

int main()
{
// ask for the person's name
cout << "Please enter your first name: ";

// read the name
string name;
cin >> name;

// build the message that we intend to write
const string greeting = "Hello, " + name + "!";

// ask for the number of blanks that on the left or right side
cout << "Please enter the number of spaces between the greeting and the left border (or the right boder): ";

// read the number of blanks that on the left or right side
int lr_sides_pad;
cin >> lr_sides_pad;

// ask for the number of blanks that on the upper or below side
cout << "Please enter the number of spaces between the greeting and the top border (or the bottom boder): ";

// read the number of blanks that on the left or right side
int tb_sides_pad;
cin >> tb_sides_pad;

// the number of rows and columns
const int rows = tb_sides_pad * 2 + 3;
const string::size_type cols = greeting.size() + lr_sides_pad * 2 + 2;

// write a blank line to separate the output from the input
cout << endl;

// write rows rows of output
// invariant: we have written r rows so far
for (int r = 0; r != rows; ++r)
{
string::size_type c = 0;
// we have written c characters so far in the current row
while (c != cols)
{
// are we on the border?
if (r == 0 || r == rows - 1 ||
c == 0 || c == cols - 1)
{
cout << "*";
++c;
}
else
{ // is it time to write the greeting?
if (r == tb_sides_pad + 1 &&
c == lr_sides_pad + 1)
{
cout << greeting;
c += greeting.size();
}
else
{
cout <<" ";
++c;
}
}
}
cout << endl;
}
return 0;
}
-

I performed three tests and present the results here

-

Test 1: input: Bruce 2 3

1
2
3
4
5
6
7
8
9
10
11
12
13
Please enter your first name: Bruce
Please enter the number of spaces between the greeting and the left border (or the right border): 2
Please enter the number of spaces between the greeting and the top border (or the bottom border): 3

*******************
* *
* *
* *
* Hello, Bruce! *
* *
* *
* *
*******************
-

Test 2: input: Bruce 4 0

1
2
3
4
5
6
7
Please enter your first name: Bruce
Please enter the number of spaces between the greeting and the left border (or the right border): 4
Please enter the number of spaces between the greeting and the top border (or the bottom border): 0

***********************
* Hello, Bruce! *
***********************
-

Test 3: input: Bruce 5 5

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
Please enter your first name: Bruce
Please enter the number of spaces between the greeting and the left border (or the right border): 5
Please enter the number of spaces between the greeting and the top border (or the bottom border): 5

*************************
* *
* *
* *
* *
* *
* Hello, Bruce! *
* *
* *
* *
* *
* *
*************************
-

Analysis

See deatiled analysis in C++ - Looping and counting.

-
-

To be continued.

- -
- - - - - - -
- -
- - - - -
- - - - -
- - -
- - - - -
- - - - - - - - -
-
- - -
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/2018/02/23/C-Looping-and-counting/index.html b/2018/02/23/C-Looping-and-counting/index.html deleted file mode 100644 index 7c772d1d..00000000 --- a/2018/02/23/C-Looping-and-counting/index.html +++ /dev/null @@ -1,723 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - C++ - Looping and counting | Liam's Blog - - - - - - - - - - - - -
-
- -
-
- - -
- - -

If history repeats itself, and the unexpected always happens, how incapable must Man be of learning from experience?—George Bernard Shaw.

-
- - -
- - - - -
-
- - -
- - 0% -
- - - - -
-
-
- - -
- - -
- - - -
- - - - - -
-

- C++ - Looping and counting -

- - -
- - - - -
- - -

In last chapter, the authors Koenig and Moo presents how to write a program that can produce a framed greeting. This chapter is to make the program more flexible so that we can change the size of the frame easily. Let’s have a look at the framed greeting again:

-
1
2
3
4
5
********************
* *
* Hello, Estragon! *
* *
********************
-

It has been observed that the greeting work has following characteristics:

-
    -
  • it consist of three elements: the greeting itself, asterisks and spaces.
  • -
  • the greeting itself will be fixed once we finished inputing.
  • -
  • the greeting itself is centrally located, meaning that it is surrounded by blank rows and the same number of black columns.
  • -
  • the outmost rows and columns are constituted by asterisks.
  • -
-

Clearly, the size of the frame is determined by the length of the greeting itself and the number of blank lines(i.e. spaces in this case). Now, we can define a variable named pad that represents the length of spaces (e.g. 1):

-
1
const int pad = 1;
-

Then, we can indicate the number of rows with defining another variable:

-
1
const int rows = 1 + 2 + 2 * pad;
-

The number 1 means the row of the greeting itself and the number 2 means two edges formed by asterisks. Once we know the number of rows, we can write the program similar as the the original one which defines several variables for each row and prints one by one. However, we need to change the code every time we change the frame size. This problem can be tackled with using iterative statements which will fully utilize the information listed above including the position of each type of elements.

-

While statement & for statement

A while statement repeatedly executes a given statement as long as a pre-dertimined condition is true. The syntax of a while statement is

-
1
2
while (condition)
statement
-

The processing of while statement starts from testing the condition and then executes the statement (aka, the while body) if the condition is true, and it starts all over again until the condition becomes false. Our purpose is to write each row, totalling rows times. Hence, what the program need here is a counting variable which is used to control the outputs.

-
1
2
3
4
5
6
7
int r = 0; // counting variable with initial value 0
while (r != rows)
{
// write a row of output
std::cout << std::endl;
++r;
}
-

The while loop requires a single statement but the logic of our program need more. Hence, we can use a compound statement which refers to a block formed by a pair of curly braces. As shown above, We can write a sequence of statements and declarations or empty statement in the block. Note that the block forms a scope and the right brace (}) indicates the end of the statement.

-

The condition is an expression that returns bool type value (true or false). When the condition yields an arithmetic type value, the value will be converted to a value of bool type:

-
    -
  1. zero values convert to false
  2. -
  3. non-zero values convert to true
  4. -
-

The inequality operator (!=) test the inequality between r and rows. The table below gives the logical and rational operators (all returns a value of bool type):

- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
AssociativityOperatorFunction
Right!Logical NOT
Left<less than
Left<=less than or equal
Left>greater than
Left>=greater than or equal
Left==equality
Left!=inequality
Left&&Logical AND
Left||Logical OR
Source: C++ Primer 2012
-

The ++() is the increment (decrement) operator which adds (subtract) 1 to the variable r. The expression is equivalent to

-
1
r = r + 1;
-

If we want to add another variable with varing values each time, we can use compound assignment operators (refer to arithmetic operators)

- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
+=-=*=/=%/
For example, x += y; is equivalent to
1
x = x + y;
Comparing with the ordinary assignment, compound assignment only evaluate the left operand once while the ordinary assignment evaluate twice.
-

Let’s get back to the while statement. According to the condition, the counting process will starts from 0 till rows-1 and the statement is expected to be executed rows times. In this case, an alternative loop structure can also be considered, a for statement

-
1
2
for (init-statement; condition; expression) // for header
statement // for body
-

The for header controls the for body which contains the statement that needs to be executed. The init-statement can only be one of three statements: declaration statement, an expression statement and anull statement(using a semicolon).
The processing order of the for statement is

-
    -
  1. at the begining of the loop, init-statement will be executed.
  2. -
  3. then, condition will be evaluated and the for body would be executed if the condition returns true. Otherwise, the loop terminates.
  4. -
  5. if condition is true, the last step of the loop is to execute expression.
  6. -
-

In this case, the for statement can be write as

-
1
2
3
4
5
for (int r; r != rows; ++r)
{
// write a row of output
std::cout << std::endl;
}
-

Loop invariant

To verify the correctness of the loops, the book introduces two techniques. The first is that the condition must be false when the loop finishes. In this case, when the while statement finishes, r != rows is false. Another one is to use loop invariant which is a property that is true before and after each loop. In this example, the loop invariant is not the core language but a comment before the while statement:

-
1
// invariant: we have written r rows so far
-

The loop invariant shows that the while statement works as expected, and in turn the program should meet the requirements that invariant is true in each iteration. There are two specific points for verify this:

-
    -
  1. the first point is before the first time that the condition is evaluated. In this example, we know it is correct as there is no output yet.
  2. -
  3. the second point is before the end of the while body. Once the while body is executed, one row will be written. To meet the requirement, we increase the value of r by adding 1 each time.
  4. -
-

When the while statement finishes, there will be rows lines are written as r = rows. The below piece of code shows how the loop invariant plays its role.

-
1
2
3
4
5
6
7
8
9
10
11
12
13
14
// invariant: we have written r rows so far
int r = 0;
// setting r to 0 makes the invariant true
while (r != rows)
{
// we can assume the invariant is true here

// writting a row of output makes the invariant false
std::cout << std::endl;

// incrementing r makes the invariant true again
++r;
}
// we can conclude that the invariant is true here
-

Up to now, we have figured how to control the loops for producing right number of rows. Similarly, we can use while loop or for loop to produce every character of each specific row. By the analogy, the first step is to define a variable to representing the number of columns. But the difference is that the number of columns is not only determined by blanks but also by the size of the greeting itself.

-
1
const std::string::size_type cols = greeting.size() + pad * 2 + 2;
-

The variable cols is defined as a std::string::size_type to keep consistent with the type of greeting.size(). This knowledge has been introduced in my last notes. We can also use type specifiers auto or decltype. For example

-
1
const auto cols = greeting.size() + pad*2 + 2;
-

What if we use int type directly? Then the variable might be insufficient if one input a name that is long enough. Now we can write the structure for writing a specific row.

-
1
2
3
4
5
6
7
8
std::string::size_type c = 0;

// invariant: we have written c characters so far in the current row
while (c != cols)
{
// write one or more characters
// adjust the value of c to maintain the invariant
}
-

if statements

Now We already have the whole structure of the program. What we need next is to determine what character is needed to be written for each row and column. As listed at the begining of this notes, we can utilize the characteristic of the target. I’ll further transform the information to plain english for clarify the logic of the program. There are three components in the outputs: asterisk, spaces and the greeting. The asterisks form the border of the frame and will be written when the loop is processing

-
    -
  • the first row or
  • -
  • the last row or
  • -
  • the first column or
  • -
  • the last column
  • -
-

These conditions can be expressed by means of the logical operators and relational operators

-
1
r == 0 || r == rows - 1 || c == 0 || cols - 1
-

The relational operators == have lower precedence than the arithmetic operators. Therefore, r == rows - 1 is equivalent to r == (rows - 1).

-

The logical OR operator has lower precedence than the relational operators. Therefore, above expression means

-
1
(r == 0) || (r == rows - 1) || (c == 0) || (cols - 1)
-

Moreover, the logical OR (as well as the logical AND) is left associative and the expression is evluated using the short-circuit strategy, that is, “the right side of an || is evaluated if and only if the left side is false (true for the logical AND)” (Lippman ect. 2012, p.154).

-

To evaluate above condition, we use the if statement

-
1
2
if (condition) 
statement
-

or if else statement

-
1
2
3
4
if (condition)
statement1
else
statement2
-

or nested if statement

-
1
2
3
4
if (condition1)
statement1
else if (condition2)
statement2
-

Now we have got a complete structure for printing out the border

-
1
2
3
4
5
6
7
8
9
10
11
if (r == 0 || r == rows - 1 || c == 0 || cols - 1)
{
std::cout << "*";
++c;
}
else
{
if (other condition)
// write one or more noborder characters
// adjust the value of c to maintain the invariant
}
-

By the analogy, we can determine the condition for outputing the greeting

-
1
r == pad + 1 && c == pad + 1
-

Note that we don’t need to write the condition of the each character of the greeting as once we find the row and the initial position we can print out the variable directly. Therefore, the value of the counting variable c will add a value of greeting.size() to maintain the invairant. Finally, it is not necessary to find the condition for the space characters as the remainder of the frame can only be spaces. Let’s combine three situations together

-
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
if (r == 0 || r == rows - 1 || c == 0 || cols - 1)
{
std::cout << "*";
++c;
}
else
{
if (r == pad + 1 && c == pad + 1)
{
std::cout << greeting;
c += greeting.size();
}
else
{
std::cout <<" ";
++c;
}
}
-

One common mistake in using if statement is that missing the curly braces after if or else when there are multiple statements. We should pay much attention on this in the case of nested if statements as there may exist more if branches than else branches. It is confusing how each else match with the if branches, which is commonly termed as dangling else. In the default seeting, C++ specifies that each else is matched with the closest preceding unmatched if. To avoid the ambiguity, it is recommended to control execution with curly braces.

-

The complete framing program

Now we can put all pieces together to get the complete framing program. Note there are several ways to organize the if statements and both while loop and for loop are available. Therefore, the program below is only one of the correct programs.

-
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
#include <iostream>
#include <string>

// using namespace std and names
using std::cout; using std::cin;
using std::endl; using std::string;

int main()
{
// ask for the person's name
cout << "Please enter your first name: ";

// read the name
string name;
cin >> name;

// build the message that we intend to write
const string greeting = "Hello, " + name + "!";

// the number of blanks surrounding the greeting
const int pad = 2;

// the number of rows and columns
const int rows = pad * 2 + 3;
const string::size_type cols = greeting.size() + pad * 2 + 2;

// write a blank line to separate the output from the input
cout << endl;

// write rows rows of output
// invariant: we have written r rows so far
for (int r = 0; r != rows; ++r)
{
// we have written c characters so far in the current row
string::size_type c = 0;
while (c != cols)
{
// are we on the border?
if (r == 0 || r == rows - 1 ||
c == 0 || c == cols - 1)
{
cout << "*";
++c;
}
else
{ // is it time to write the greeting?
if (r == pad + 1 && c == pad + 1)
{
cout << greeting;
c += greeting.size();
}
else
{
cout <<" ";
++c;
}
}
}
cout << endl;
}
return 0;
}
-

Now we can test the program with changing the number of blanks to 2, and type Bruce after the input prompt. It gives the outputs as we expected.

-
1
2
3
4
5
6
7
8
9
Please enter your first name: Bruce

*******************
* *
* *
* Hello, Bruce! *
* *
* *
*******************
-
- - - - - - -
- -
- - - - -
- - - - -
- - -
- - - - -
- - - - - - - - -
-
- - -
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/2018/02/25/Accelerated-C-Solutions-to-Exercises-Chapter-2-Part2/index.html b/2018/02/25/Accelerated-C-Solutions-to-Exercises-Chapter-2-Part2/index.html deleted file mode 100644 index edeb6ea8..00000000 --- a/2018/02/25/Accelerated-C-Solutions-to-Exercises-Chapter-2-Part2/index.html +++ /dev/null @@ -1,604 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - Accelerated C++ Solutions to Exercises (Chapter 2 Part2) | Liam's Blog - - - - - - - - - - - - -
-
- -
-
- - -
- - -

If history repeats itself, and the unexpected always happens, how incapable must Man be of learning from experience?—George Bernard Shaw.

-
- - -
- - - - -
-
- - -
- - 0% -
- - - - -
-
-
- - -
- - -
- - - -
- - - - - -
-

- Accelerated C++ Solutions to Exercises (Chapter 2 Part2) -

- - -
- - - - -
- - -

Exercise 2-4

The framing program writes the mostly blank lines that seperate the borders from the greeting one character at a time. Change the program so that it writes all the spaces needed in a single output expression.

-

Solution & Results

Without considering the frame itself, there only exist two types of row. The first type is the rows that full of spaces. The second type is the row that contains the greeting and spaces symmetricly located on two sides of the greeting. Therefore, one of the possible solutions is to write one row directly each time instead of writing one character. First, I defines two variables representing two blank strings

-
1
2
3
   // two blank strings containing different number of spaces
const string blankStringTopBottom(cols - 2, ' ');
const string blankStringLeftRight(pad, ' ');
-

The first string is in fact the first type of row and can be written directly given certain conditions. The second row can be obtained by means of catenate operations

-
1
2
   // the string that contains both the greeting and blanks
const string greetingRow = blankStringLeftRight + greeting + blankStringLeftRight;
-

To maintain the loop invariant, the c need to be adjusted after each output. Due to one row is written each time (ignoring the borders), c increases cols - 1 every time.

-
1
c += cols - 2;
-

The modified program is

-
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
65
66
#include <iostream>
#include <string>

// using namespace std and names
using std::cout; using std::cin;
using std::endl; using std::string;

int main()
{
// ask for the person's name
cout << "Please enter your first name: ";

// read the name
string name;
cin >> name;

// build the message that we intend to write
const string greeting = "Hello, " + name + "!";

// the number of blanks surrounding the greeting
const int pad = 1;

// the number of rows and columns
const int rows = pad * 2 + 3;
const string::size_type cols = greeting.size() + pad * 2 + 2;

// two blank strings containing different number of spaces
const string blankStringTopBottom(cols - 2, ' ');
const string blankStringLeftRight(pad, ' ');

// the string that contains both the greeting and blanks
const string greetingRow = blankStringLeftRight + greeting + blankStringLeftRight;


// write a blank line to separate the output from the input
cout << endl;

// write rows rows of output
// invariant: we have written r rows so far
for (int r = 0; r != rows; ++r)
{
string::size_type c = 0;
// we have written c characters so far in the current row
while (c != cols)
{
// are we on the border?
if (r == 0 || r == rows - 1 ||
c == 0 || c == cols - 1)
{
cout << "*";
++c;
}
else
{ // is it time to write the greeting row?
if (r == pad + 1)
cout << greetingRow;
// is it time to write the non-greeting rows?
else
cout << blankStringTopBottom;
c += cols - 2;
}
}
cout << endl;
}
return 0;
}
-

A simple test has been done and the outputs are written on the console window as below

-
1
2
3
4
5
6
7
Please enter your first name: Bruce

*****************
* *
* Hello, Bruce! *
* *
*****************
-

Analysis

See deatiled analysis in C++ - Looping and counting.

-
-

Exercise 2-5

Write a set of “*” characters so that they form a square, a rectangle, and a triangle.

-

Solution & Results

The design ideas

The correct program for this exercise is non-unique as the requirements are loose. It doesn’t specify the details of each shape. I tried to design a program that is flexible enough to meet more needs and preferences. For example, users may ask the program to write various rectangles (or squares/triangles) with different lengths or different appearance such as solid or hollow shapes. In summary, I intend to design a program with following properties

-
    -
  1. provide choices for three types of shapes, i.e. square, rectangle and triangle.
  2. -
    • -
    • for a square, the length of the side is customizable
    • -
    • for a rectangle, the length and the width are customizable, and the base can be the long side or the short side.
    • -
    • for a triangle, the height is customizable and the triangle is a equilateral triangle.
    • -
    -
  3. -
  4. for all shapes, there are two types of appearance: solid and hollow.
  5. -
-

The Primary Structure

Due to there are three types of shape, we can use if else statement as one of the choices of the primary structure and provide three banches for the three shapes. First, flags are needed for indicating what shape type is to be written. For example:

-
1
2
3
4
5
/* flags for what shape to print
* shapeType == 1, print a square shape
* shapeType == 2, print a rectangle shape
* shapeType == 3, print a triangle shape
*/
-

In addition, each type of shape can be a solid shape or hollow shape. Therefore, a choice for the appearance will be provided for users before the computer enters into each branch. For other properties, such as lengths of sides and heights, are not the same for each types of shape and will be solved in the branches. Therefore, the whole structure of the program is

-
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
int main()
{
// select a shape
cout << "Select a shape type\n";
cout << "1. A Square\n2. A Rectangle\n3. A Triangle\n";
cout << "Please enter 1 or 2 or 3: ";

// read the shape type
int shapeType;
cin >> shapeType;

// write a blank line for clarity
cout << endl;

// select appearance
cout << "Do you want a solid shape?\n";
cout << "yes: a solid shape\nno: a hollow shape\n";
cout << "Please enter y or n: ";

// read the shape appearance
char shapeAppearance;
cin >> shapeAppearance;

// write a blank line for clarity
cout << endl;

if (shapeType == 1)
{
// statements for generating a square
}
else if (shapeType == 2)
{
// statements for generating a rectangle
}
else if (shapeType == 3)
{
// statements for generating a triangles
}
return 0;
}
- -

Algorithms

A square shape

The core part of the program is how to impelment the algorithms that can generate different shapes. I starts with the implementation of writing a square. Assuming the edge length of the square is 10, the program aims to generate two different squares as follows

-
1
2
3
4
5
6
7
8
9
10
**********          **********
* * **********
* * **********
* * **********
* * and **********
* * **********
* * **********
* * **********
* * **********
********** **********
-

Note that for all the shapes, the length or height means the number of columns or rows formed by asterisks (and spaces).

-

The algorithm is similar as that in the framed greeting program except that it doesn’t need to write the greeting itself. Essentially, there are two types of string:one is full filled with asterisks and another one is filled by blanks and two asterisks located at both ends of the blanks. The structure could be a for loop or while loop, which is up to your preference. Firstly, I define the variables that are needed.

-
1
2
3
4
5
6
7
8
9
10
// read in edge length for a square
int edgeLength;
cin >> edgeLength;

// string variable type 1: full of asterisks
string asteriskString(edgeLength, '*');

// string variable type 2: blanks and asterisks
string blankString(edgeLength - 2, ' ');
string mixedString = "*" + blankString + "*";
-

The solid square shape is simple and only needs one type of the string type vairable. If we choose one of the loop statements, the loop invariant can be expressed as: we have written r rows of output, of which r is the counting variable.

-
1
2
3
4
5
6
// invariant: we have written r rows of output
for (int r = 0; r != edgeLength; ++r)
{
// write a row whose length is the edge length
cout << asteriskString << endl;
}
-

To maintaint the loop invariant, r is increased by 1 after each iteration. Once the for loop finishes, the condition r != edgeLength is false and r = edgeLength. Then, the loop invariant becomes: we have written edgeLength rows of outputs, which keeps the correctness of our loop statement.

-

For a hollow square shape, the output of each row is conditional on its position.

-
1
2
3
4
5
// is it the first row or last row of outputs?
if (r == 0 || r == edgeLength - 1)
cout << asteriskString << endl;
else
cout << mixedString << endl;
-

Now, we can combine two cases together

-
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
   if (shapeType == 1)
{
cout << "Please enter the edge length: ";

// read the edge length
int edgeLength;
cin >> edgeLength;

// write a blank line to separate inputs and outputs
cout << endl;

// string type 1: full of asterisks
string asteriskString(edgeLength, '*');

// string type 2: blanks and asterisks
string blankString(edgeLength - 2, ' ');
string mixedString = "*" + blankString + "*";

for (int r = 0; r != edgeLength; ++r)
{
// is it rows of a solid shape or
// the first/ last row of a hollow shape ?

if (shapeAppearance == 'y' || r == 0 || r == edgeLength - 1)
// write a row whose length is the edge length
cout << asteriskString << endl;
else
cout << mixedString << endl;
}
}
-

A rectangle shape

Square shapes can be regarded as a special case of rectangles. We can simply modify the above algorithm to generate a rectangle. I use base lenght and height instead of the length and width for convenience.

-
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
else if (shapeType == 2)
{
cout << "Please enter the base length and height\n";
cout << "The base lenght = ";

// read the base length
int baseLength;
cin >> baseLength;

cout << "The height = ";

// read the height
int height;
cin >> height;

// write a blank line to separate inputs and outputs
cout << endl;

// string type 1: full of asterisks
string asteriskString(baseLength, '*');

// string type 2: blanks and asterisks
string blankString(baseLength - 2, ' ');
string mixedString = "*" + blankString + "*";

for (int r = 0; r != height; ++r)
{
// is it rows of a solid shape or
// the first/ last row of a hollow shape ?

if (shapeAppearance == 'y' || r == 0 || r == height - 1)
cout << asteriskString << endl;
else
cout << mixedString<< endl;
}
}
- -

A triangle shape

For a triangle, the ideal result is to print out follow two shapes, providing the height is 4.

-
1
2
3
4
   *                   *
*** and * *
***** * *
******* *******
-

The solid trangle shape is formed only by asterisks ignoring the outer blanks. However, the number of asterisks of each row is different with eachother. From the table below, we can see a pattern in the numbers of asterisks of the rows.

-

| The rth Row| The number of columns of each row |
| :—: | :—: | :—: |
|0|1|
|1|3|
|2|5|
|3|7|
|4|9|
|…|…|
|r|(r*2 + 1)|
|Height|loop finishes|

-

The table shows that when the program writes the rth row, it needs to write asterisks in a total number of r*2 + 1.
It also shows that the number of columns is height*2 - 1.

-

For the hollow triangle shapes, there are two types of rows: the first type is rows filled only by asterisks such as the first row and the last row; another type is rows formed by blanks with two asterisks located at both ends of the blanks. The table gives the pattern in the numbers of blanks of the rows.

-

| The rth Row| The number of blanks in each row |
| :—: | :—: | :—: |
|0|0|
|1|1|
|2|3|
|3|5|
|4|7|
|…|…|
|r|((r-1)*2 + 1)|
|Height|loop finishes|

-

Correspondingly, I define variables inside a for loop as below

-
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
for (int r = 0; r != height; ++r)
{
// is it the row of a solid shape, or the first or the last row of the hollow shape
if (shapeAppearance == 'y' || r == 0 || r == height - 1)
{
// string type 1: full of asterisks
string asteriskString(r*2 + 1, '*');
}
else
{
// string type 2: blanks and asterisks
string blankString(((r-1)*2 + 1), ' ');
string mixedString = "*" + blankString + "*";
}
}
-

What we need next is to find the condition for writing each row as each row has different initial position. Before the real start of each row, we only need to output spaces. The table below gives the pattern of the initial column position of each row.

-

| The rth Row| The column number of the initial position |
| :—: | :—: | :—: |
|height | loop finishes|
|height - 1| 0|
|height - 2|1|
|…|…|
|r|height - r - 1|
|…|…|
|0|height - 1|

-

The table shows that when the loop processes the column number height - r - 1, it starts to write the strings that I defined above. When we incorporate the column loops into the row loops, the program becomes

-
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
else if (shapeType == 3)
{
// read the height
cout << "Please enter the height: ";
int height;
cin >> height;

// write a blank line to separate inputs and outputs
cout << endl;

// loop invariant: we have written r rows now
for (int r = 0; r != height; ++r)
{
// loop invariant: we have written c characters so far in the current line
int c = 0;

while(c != (height*2 - 1))
{ // // is it the row of a solid shape, or the first or the last row of the hollow shape
if (shapeAppearance == 'y'
|| r == 0
|| r == height - 1)
{
// is it time to write the real row?
if (c == height - r - 1)
{
// string type 1: full of asterisks
string asteriskString(r*2 + 1, '*');
cout << asteriskString;

// maintain the loop invariant
c += (r*2 + 1);
}
else
{
cout << ' ';
// maintain the loop invariant
++c;
}
}
else
{
// is it time to write the real row?
if (c == height - r - 1)
{
// string type 2: blanks and asterisks
string blankString(((r-1)*2 + 1), ' ');
string mixedString = "*" + blankString + "*";

cout << mixedString;
// maintain the loop invariant
c += ((r-1)*2 + 1 + 2);
}
else
{
cout << ' ';
++c;
}
}
}
cout << endl;
}
}
-

A complete program and tests

Let’s put all pieces together

-
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
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
//Accelerated C++ Solutions Exercises 2-5
#include <iostream>
#include <string>

// using namespace std and names
using std::cout; using std::cin;
using std::endl; using std::string;

int main()
{
// select a shape
cout << "Select a shape type\n";
cout << "1. A Square\n2. A Rectangle\n3. A Triangle\n";
cout << "Please enter 1 or 2 or 3: ";

// read the shape type
int shapeType;
cin >> shapeType;

// write a blank line for clarity
cout << endl;

// select appearance
cout << "Do you want a solid shape?\n";
cout << "yes: a solid shape\nno: a hollow shape\n";
cout << "Please enter y or n: ";

// read the shape appearance
char shapeAppearance;
cin >> shapeAppearance;

// write a blank line for clarity
cout << endl;

if (shapeType == 1)
{
cout << "Please enter the edge length: ";
int edgeLength;
cin >> edgeLength;

// write a blank line to separate inputs and outputs
cout << endl;

// string type 1: full of asterisks
string asteriskString(edgeLength, '*');

// string type 2: blanks and asterisks
string blankString(edgeLength - 2, ' ');
string mixedString = "*" + blankString + "*";

for (int r = 0; r != edgeLength; ++r)
{
if (shapeAppearance == 'y' || r == 0 || r == edgeLength - 1)
// write a row whose length is the edge length
cout << asteriskString << endl;
else
cout << mixedString << endl;
}
}
else if (shapeType == 2)
{
cout << "Please enter the base length and height\n";
cout << "The base lenght = ";

// read the base length
int baseLength;
cin >> baseLength;

cout << "The height = ";

// read the height
int height;
cin >> height;

// write a blank line to separate inputs and outputs
cout << endl;

// string type 1: full of asterisks
string asteriskString(baseLength, '*');

// string type 2: blanks and asterisks
string blankString(baseLength - 2, ' ');
string mixedString = "*" + blankString + "*";

for (int r = 0; r != height; ++r)
{
if (shapeAppearance == 'y' || r == 0 || r == height - 1)
cout << asteriskString << endl;
else
cout << mixedString<< endl;
}
}
else if (shapeType == 3)
{
cout << "Please enter the height: ";
int height;
cin >> height;

// write a blank line to separate inputs and outputs
cout << endl;

// loop invariant: we have written r rows now
for (int r = 0; r != height; ++r)
{
// loop invariant: we have written c characters so far in the current line
int c = 0;

while(c != (height*2 - 1))
{
if (shapeAppearance == 'y' || r == 0 || r == height - 1)
{
if (c == height - r - 1)
{
// string type 1: full of asterisks
string asteriskString(r*2 + 1, '*');
cout << asteriskString;

// maintain the loop invariant
c += (r*2 + 1);
}
else
{
cout << ' ';
// maintain the loop invariant
++c;
}
}
else
{
if (c == height - r - 1)
{
// string type 2: blanks and asterisks
string blankString(((r-1)*2 + 1), ' ');
string mixedString = "*" + blankString + "*";

cout << mixedString;
// maintain the loop invariant
c += ((r-1)*2 + 1 + 2);
}
else
{
cout << ' ';
++c;
}
}
}
cout << endl;
}
}
return 0;
}
-

Note that I used the same variables names asteriskString, blankString and mixedString in all three branches, but it does not matter the correctness as each branch is an independent block. I did three tests and the program works as expected. Please see the results below

-

Test 1

-
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
Select a shape type
1. A Square
2. A Rectangle
3. A Triangle
Please enter 1 or 2 or 3: 1

Do you want a solid shape?
yes: a solid shape
no: a hollow shape
Please enter y or n: y

Please enter the edge length: 5

*****
*****
*****
*****
*****
-

Test 2

-
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
Select a shape type
1. A Square
2. A Rectangle
3. A Triangle
Please enter 1 or 2 or 3: 2

Do you want a solid shape?
yes: a solid shape
no: a hollow shape
Please enter y or n: n

Please enter the base length and height
The base lenght = 8
The height = 4

********
* *
* *
********
-

Test 3

-
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
Select a shape type
1. A Square
2. A Rectangle
3. A Triangle
Please enter 1 or 2 or 3: 3

Do you want a solid shape?
yes: a solid shape
no: a hollow shape
Please enter y or n: n

Please enter the height: 6

*
* *
* *
* *
* *
***********
- -
-

To be continued.

- -
- - - - - - -
- -
- - - - -
- - - - -
- - -
- - - - -
- - - - - - - - -
-
- - -
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/2018/03/01/Accelerated-C-Solutions-to-Exercises-Chapter-2-Part3/index.html b/2018/03/01/Accelerated-C-Solutions-to-Exercises-Chapter-2-Part3/index.html deleted file mode 100644 index d6844b4f..00000000 --- a/2018/03/01/Accelerated-C-Solutions-to-Exercises-Chapter-2-Part3/index.html +++ /dev/null @@ -1,627 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - Accelerated C++ Solutions to Exercises (Chapter 2 Part3) | Liam's Blog - - - - - - - - - - - - -
-
- -
-
- - -
- - -

If history repeats itself, and the unexpected always happens, how incapable must Man be of learning from experience?—George Bernard Shaw.

-
- - -
- - - - -
-
- - -
- - 0% -
- - - - -
-
-
- - -
- - -
- - - -
- - - - - -
-

- Accelerated C++ Solutions to Exercises (Chapter 2 Part3) -

- - -
- - - - -
- - -

Exercise 2-6

What does the following code do?

-
1
2
3
4
5
6
int i = 0;
while (i < 10)
{
i += 1;
std::cout << i << std::endl;
}
-

Solution & Results

The program writes 10 rows of numbers, starting from 1 to 10.

-

The while statement starts from testing the condition and then executes the while body if the condition is true. It stops executing the while body until the condition becomes false. Let’s analyse the first iteration

-
    -
  • First, the condition of the first time iteration is true as 0 < 10 is true.
  • -
  • Second, the expression i += 1; is evaluated, and the variable i becomes 1 after the execution.
  • -
  • then, the following statement is executed and the variable i is written on the output device.
  • -
  • finally, the while loop starts all over again from testing the condition.
  • -
-

From above steps we have seen that

-
    -
  • the first number to output is 1.
  • -
  • i is increased by 1 each iteration.
  • -
-

Accordingly, the final iteration can be deducted

-
    -
  • when i = 9, the row of output is 9 and the condition is still true.
  • -
  • after the evaluation of i += 1;, i equals 10.
  • -
  • then the output is 10 in the following step.
  • -
  • the while statement starts again and tests the condition, but the condition 10 < 10 is false.
  • -
  • the while statement finishes.
  • -
-

Now I complete the program and test it

-
1
2
3
4
5
6
7
8
9
10
11
12
#include <iostream>

int main()
{
int i = 0;
while (i < 10)
{
i += 1;
std::cout << i << std::endl;
}
return 0;
}
-

As expected, it writes 10 rows of outputs from 1 to 10 with one number in each row.

-
1
2
3
4
5
6
7
8
9
10
1
2
3
4
5
6
7
8
9
10
-

Note that the cursor appears on the next line of the final number 10 due to the following manipulator endl.

-

We can also explain the program from the perspective of its goal. Condiser that, we want the program to print out 10 rows, each of which contains a number, starting from 1 to 10 orderly. The loop invariant can be expressed as: we have written i rows now and the number in current row is i. To verify the loop invariant, we need to verify it at two specific points:

-
    -
  1. the first point is before the first time that the condition is evaluated. In this case, it is correct as there is 0 output at current position.
  2. -
  3. the second point is before the end of the while body. Once the first statement is executed, i is increased by 1. To maintain the loop invariant, it needs writing a row which contains the number i. Therefore, the loop statement works as expected in each iteration.
  4. -
-

For clarity, I add comments for the program

-
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
#include <iostream>

int main()
{
// loop invariant: we have written i rows now and the number in current row is i

int i = 0;

while (i < 10)
{
// i changes with increment of 1
i += 1;

// to maintain the loop invariant, write 1 row
std::cout << i << std::endl;
}
return 0;
}
-

Analysis

See deatiled analysis in C++ - Looping and counting.

-
-

Exercise 2-7

Write a program to count down from 10 to -5.

-

Solution & Results

This exercise is similar to the program in the last exercise. There are 16 numbers in total and hence there should be overal 16 loop times. Naturally, we use the range [0, 16) to describe the loop statements. The loop invariant can be expressed as we have written i rows and the number that written in this row is j.

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

int main()
{
// The loop invariant can be expressed as we have
// written i rows and the number written in this row is j.

int i = 0;

while (i < 16)
{
// write a row of outputs
cout << endl;

// to maintain the loop invariant, increase the value of i by adding 1
++i;
}
return 0;
}
-

What is the value of j? As mentioned above, the loop invariant needs to be verified at two specific points. First, before the first time that the condition is evaluated, we have written 0 rows. Second, before the end of the while body, we have written 1 row and the number should be 10. Therefore, the inital value of j should be 10. It is still not clear now. I’ll further verify the loop invariant in the second and third iteration.

-

The second iteration:

-
    -
  • before the the condition is evaluated, the loop invariant is correct as currently there is one row and the output is 10.
  • -
  • before the end of the while body, i increases by 1. The number to output is 9. To maintain the loop invariant, j should be decreased by 1.
  • -
-

The third iteration:

-
    -
  • before the the condition is evaluated, the loop invariant is correct as currently there are 2 rows and the second output is 9.
  • -
  • before the end of the while body, i increases by 1. The number to output is 8. To maintain the loop invariant, j should be decreased by 1.
  • -
-

It has been seen from above descriptions, each iteration i changes with increment by 1 while j changes with decrement by 1. Therefore, the sum of i and j should be constant, and hence j = 10 - i as j has initial value 10. In other words, the loop invariant is i + j = 10, of which the i represents the row number and j represents the number contained in the i row of outputs.

-

Accordingly, the complete program is

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

int main()
{
// loop invariant: we have written i rows and the number
// written in this row is j = 10-i.

int i = 0;
int j = 10;

while (i < 16)
{
// write a row of outputs
std::cout << j << std::endl;

// to maintain the loop invariant, increase the value
// of i by adding 1, change the value o j to 10 - i
++i;
j = 10 -i;
}
return 0;
}
-

The program works as expected with following outputs

-
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
10
9
8
7
6
5
4
3
2
1
0
-1
-2
-3
-4
-5
- -
-

Exercise 2-8

Write a program to generate the product of the numbers in the range [1, 10).

-

Solution & Results

The product of the numbers in the range [1, 10) is

-
1
1 x 2 x 3 x 4 x 5 x 6 x 7 x 8 x 9
-

Essentially, it is a factorial of number 9. I can transform above expression as

-
1
9! = 1 x 2 x 3 x ... x 8 x 9
-

or

-
1
9! = 9 x 8!
-

Therefore, the calculation can be designed as a loop statement containing 8 times loops. In each iteration, we calculate the factorial of a number from 2 till 9. The loop invariant is that we have calculated the factorial i times and the number f. I’ll use the range [0, 8) to count the loops. The complete program is shown as below

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

int main()
{
int f = 1;
int product = 1;

// we have calculated the factorial i times and the number is f
for (int i = 0; i != 8; ++i)
{
// increase the value of f by 1
++f;

// to maintain the loop invariant, calculate the factorial of number f
product *= f;
}
std::cout << product << std::endl;

return 0;
}
-

The results is

-
1
362880
- -
-

Exercise 2-9

Write a program that asks the user to enter two numbers and tells the user which number is larger than the other.

-

Solution & Results

It’s a simple exercise, and I’ll skip analysis and present the program directly

-
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
#include <iostream>
using std::cout;
using std::cin;
using std::endl;

int main()
{
cout << "Please enter two integers: ";
// read two numbers
int a, b;
cin >> a >> b;

if (a == b)
cout << a << " equals to " << b << endl;
else if (a > b)
cout << a << " is greater than " << b << endl;
else if (a < b)
cout << b << " is greater than " << a << endl;
return 0;
}
-

Test 1

-
1
2
Please enter two numbers: 5 8
8 is greater than 5
-

Test 2

-
1
2
Please enter two numbers: 4 2
4 is greater than 2
-

Test 3

-
1
2
Please enter two numbers: 100 100
100 equals to 100
-

Exercise 2-10

Explain each of the uses of std:: in the following program:

-
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
#include <iostream>

int main()
{
int k = 0;
while (k != 5)
{
// invariant: we have written k asterisks so far
using std::cout;
cout << "*";
++k;
}
std::cout << std::endl;
// std:: is required here
return 0;
}
-

Solution & Results

The first std:: in line 9

-
1
using std::cout;
-

The using declaration qualify us to use the name cout, which is defined in the namespace std, directly instead of std::cout. However, the declaration is only valid within the block of the while statement as the curly braces form a name scope.

-

This also explains the requirements of the std:: in line 13. If one want to use the unqualified cout, endl inside the main function body, he should write the using declarations for each different name before the start of the main function. For example

-
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
#include <iostream>
using std::cout;
using std::endl;

int main()
{
int k = 0;
while (k != 5)
{
// invariant: we have written k asterisks so far
// using std::cout - is not required now
cout << "*";
++k;
}
std::cout << std::endl;
// std:: is not required now
return 0;
}
-

Now, both programs work well and give the results

-
1
*****
-

Analysis

See C++ - Getting Started.

-
-

References

Koenig, A. and Moo, B.E., 2000. Accelerated C++.

-

Lippman, S.B., Lajoie, J. and Moo, B.E., 2013. C++ Primer, 5th edt.

- -
- - - - - - -
- -
- - - - -
- - - - -
- - -
- - - - -
- - - - - - - - -
-
- - -
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/2018/03/02/C-Built-in-types-and-expressions/index.html b/2018/03/02/C-Built-in-types-and-expressions/index.html deleted file mode 100644 index 4ba44e4a..00000000 --- a/2018/03/02/C-Built-in-types-and-expressions/index.html +++ /dev/null @@ -1,615 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - C++ - Built-in types and expressions | Liam's Blog - - - - - - - - - - - - -
-
- -
-
- - -
- - -

If history repeats itself, and the unexpected always happens, how incapable must Man be of learning from experience?—George Bernard Shaw.

-
- - -
- - - - -
-
- - -
- - 0% -
- - - - -
-
-
- - -
- - -
- - - -
- - - - - -
-

- C++ - Built-in types and expressions -

- - -
- - - - -
- - -

Arithmetic types

- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
Arithmetic types in C++
TypeMeaningMinimum size
boolbooleanNA
charcharacter8bits
wchar_twide character16bits
char16_tUnicode character16bits
char32_tUnicode character32bits
shortshort integer16bits
intinteger16bits
longlong integer32bits
long longlong integer64bits
floatsingle-precision floating-point6 significant digits
doubledouble-precision floating-point10 significant digits
long doubleextended-precision floating-point10 significant digits
- -
- - - - - - -
- -
- - - - -
- - - - -
- - -
- - - - -
- - - - - - - - -
-
- - -
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/2018/03/02/C-Working-with-batches-of-data/index.html b/2018/03/02/C-Working-with-batches-of-data/index.html deleted file mode 100644 index 2557edad..00000000 --- a/2018/03/02/C-Working-with-batches-of-data/index.html +++ /dev/null @@ -1,693 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - C++ - Working with batches of data | Liam's Blog - - - - - - - - - - - - -
-
- -
-
- - -
- - -

If history repeats itself, and the unexpected always happens, how incapable must Man be of learning from experience?—George Bernard Shaw.

-
- - -
- - - - -
-
- - -
- - 0% -
- - - - -
-
-
- - -
- - -
- - - -
- - - - - -
-

- C++ - Working with batches of data -

- - -
- - - - -
- - -

Imagine a course in which each student’s final exam counts for 40% of the final grade, the midterm exam counts for 20%, and the average homework grade makes up the remaining 40%. Now we are asked to write a program that reads a student’s exam and homework grades and computes a final grade.

-
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 <iomanip>
#include <ios>
#include <iostream>
#include <string>

using std::cin; using std::setprecision;
using std::cout; using std::string;
using std::endl; using std::streamsize;

int main()
{
// ask for and read the student's name
cout << "Please enter your first name: ";
string name;
cin >> name;
cout << "Hello, " << name << "!" << endl;

// ask for and read the midterm and final grades
cout << "Please enter you midterm and final exam grades: ";
double midterm, final;
cin >> midterm >> final;

// ask for the homework grades
cout << "Enter all your homework grades, "
"followed by end-of-file: ";

// the number and the sum of grades read so far
int count = 0;
double sum = 0;

// a variable into which to read
double x;

// invariant: we have read count grades so far, and sum is the sum of the first count grades
while (cin >> x)
{
++count;
sum += x;
}

// write the result
streamsize prec = cout.precision();
cout << "Your final grade is " << setprecision(3)
<< 0.2 * midterm + 0.4 * final + 0.4 * sum/count
<< setprecision(prec) << endl;
return 0;
}
-

More about IO system

The goal of above program is to compute a final grade, which is a simple math question-computing the weighted average. Let’s start from the #include directives

-
1
2
3
4
#include <iomanip>
#include <ios>
#include <iostream>
#include <string>
-

We are familar with which is the header that defines the standard input/output stream objects, and which is the header that defines string type objects. Correspondingly, objects cin , cout and endl are defined in the iostream library, and string is defined in the string class. These names are defined in the namespace std, and need to be declared in the form of std::name or using std::name before we can use them.

-

Similarly, the is a header that defines the type streamsize which represents sizes. The defines the manipulator setprecision which sets the decimal precision. Both names streamsize and setprecision are defined in the namespace std.

-

setprecision, shownpoint & fixed

setprecision is used to format floating-point values, such as float and double type values. It manipulates the stream by causing the subsequent output on that stream to be written with a given number of digits. The syntax is

-
1
<< setprecision(int n) <<
-

The parameter n determines the number of digits to be written. In this case, setprecision(3) means the output will remain three digits. Let’s do some experiments to explore more about setprecision

-

Experiment 1

-
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
#include <iomanip>
#include <ios>
#include <iostream>

using std::cin; using std::setprecision;
using std::cout; using std::endl;


int main()
{
int i = 12345;
float f1 = 12345;
float f2 = 3.148;;
float f3 = 0.03148;
float f4 = 0.03108;
float f5 = 0.03100;
float f6 = 1000.435;

cout << setprecision(3) << i << endl;
cout << f1 << endl;
cout << f2 << endl;
cout << f3 << endl;
cout << f4 << endl;
cout << f5 << endl;
cout << f6 << endl;

return 0;
}
-

The result is shown below

-
1
2
3
4
5
6
7
12345
1.23e+004
3.15
0.0315
0.0311
0.031
1e+003
-

It can be seen from this experiment that

-
    -
  1. setprecision doesn’t work on int type values
  2. -
  3. the parameter n (in this case is 3) controls the number of significant digits (i.e. count from the first non-zero number).
  4. -
  5. it follows the rounding principles.
  6. -
  7. it omits the trailing 0s.
  8. -
-

If one want to keep the trailing zero, the manipulator showpoint can be used. showpoint is declared in the ios library and its name is also in the namespace std. It sets the format flag and always includes the decimal point as well as the tail 0 for matching the precision. The showpoint flag can be unset with the noshowpoint manipulator.

-

If we want control the precision of the decimal part, we can use fixed manipulator to fixed the decimal part. Together with the setprecision(n), the number of digits in the fractional part will be fixed at n. If there is no enough numbers after the decimal point, zero will be added to match the precision.

-

To veryfy the usages of showpoint and fixed, I did tests as follows.

-

Experiment 2

-
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
#include <iomanip>
#include <ios>
#include <iostream>

using std::cin; using std::setprecision;
using std::cout; using std::fixed;
using std::endl; using std::showpoint;
using std::noshowpoint;


int main()
{
float f1 = 1000.00;
float f2 = 3.14800;
float f3 = 0.03148;


cout << showpoint << setprecision(4) << f1 << endl;
cout << f2 << endl;
cout << f3 << noshowpoint << endl;

// write a bank line to sepearate outputs
cout << endl;

cout << fixed << setprecision(6) << f1 << endl;
cout << f2 << endl;
cout << f3 << endl;

return 0;
}
-

The result is as analysed above

-
1
2
3
4
5
6
7
1000.
3.148
0.03148

1000.000000
3.148000
0.031480
-

streamsize

Once we changed the precision, the subsequent output would be formated to match the precision. If we want to change back, we can reset the precision to the original setting if we know the precision value. If we don’t know the previous setting of the cout, the cout.presicion returns us the value and its type is streamsize.

-

Without using setprecision, we could use cout.precision(n) to set the precision. The usage is shown as below

-

Experiment 3

-
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
#include <iomanip>
#include <ios>
#include <iostream>

using std::cin; using std::setprecision;
using std::cout; using std::fixed;
using std::endl; using std::streamsize;


int main()
{
float f1 = 1000.00;
float f2 = 3.14800;
float f3 = 0.03148;

// return the current precision
streamsize prec1 = cout.precision();

// set precision to 2
cout << setprecision(2);

// set precision to 3, return previous value
streamsize prec2 = cout.precision(3);

// the outputs should have three decimal digits
cout << fixed << f1 << endl;
cout << f2 << endl;
cout << f3 << endl;

// reset precision value to its previous value
cout.precision(prec2);

// the outputs should have two decimal digits
cout << endl;
cout << fixed << f1 << endl;
cout << f2 << endl;
cout << f3 << endl;

// reset the the initial precision value
cout.precision(prec1);

cout << endl;
cout << fixed << f1 << endl;
cout << f2 << endl;
cout << f3 << endl;

// check the original value of precision
cout << endl;
cout << "The origin precision value is: " << prec1 << endl;

return 0;
}
-

The comments show the expected results according to that

-
    -
  1. streamsize precision() returns the the value of the current floating-point precision.
  2. -
  3. streamsize precision(int n) sets the precision to a new value.
  4. -
-

The program gives result as I expected

-
1
2
3
4
5
6
7
8
9
10
11
12
13
1000.000
3.148
0.031

1000.00
3.15
0.03

1000.000000
3.148000
0.031480

The origin precision value is: 6
-

Return to while statement

Let’s get back to the example. The statements in the function body begin with writing a greeting as illustrated in previous chapters. Then, it reads two values into two variables midterm and final. The input operations can be chained as

-
1
cin >> midterm >> final;
-

This statement is equivalent to

-
1
2
cin >> midterm;
cin >> final;
-

The next statement shows a new form of writing string literals.

-
1
2
cout << "Enter all your homework grades, "
"followed by end-of-file: ";
-

It seems that two string lterals will be written, but in fact it has the same effect as the following statement

-
1
cout << "Enter all your homework grades, followed by end-of-file: ";
-

This is because two or more string literals separated only by whitespace are concatenated automatically.

-

What closely follow is the while loop.

-
1
2
3
4
5
6
7
8
9
10
11
12
int count = 0;
double sum = 0;

// a variable into which to read
double x;

// invariant: we have read count grades so far, and sum is the sum of the first count grades
while (cin >> x)
{
++count;
sum += x;
}
-

count is defined for counting the number of inputs as well as describing the loops. sum is defined for holding the summation of homework grades. Note that it is initialized with an int value 0 though it is a double type variable. Therefore, the int value will be converted automatically to double type with the fractional part of 0.

-

Since the number of homework grades is unkonwn, we could not use the while loop as before as we don’t know the number of loops. The while loop here makes it available to input multiple times continuously. As we know, the condition in a while loop should yield a value of bool, that is, true or false, otherwise the value (if available) will be converted to the type of bool. According to Koenig and Moo (2000), the istream class provides a conversion that can be used to convert cin to a value that can be used in a condition. In addition, the value depends on the iternal state of the istream object, which will remember whether the last attempt to read worked. Hence, the cin >> x will theoretically always yield a true value as long as we keep inputing right type values. In this example, it reminds us to send “EOF” signal, which will change the value to false, to stop the loop.

-

vector

The example above shows how to compute an average value of a batch of data. In reality, the goal is probably to compute the median, or other statistics in the data. However, the inputted data are not stored in above program and hence are unavailable to access. Naturally, the first step is to store the data and the second step is to access or compute the target value via algorithms. Now we learn how to deal with these problems with vector.

-

Defining and Initializing vectors

The vector is a class template. Before using the vector in a program, we need to include the header and qualify the name either explicitly or using using declaration. For example

-
1
2
#include <vector>
using std::vector
-

A vector is a container that holds a sequence of objects of the same type. The syntax of creating a vector is

-
1
vector<T> name; // default initialization
-

The name is the template name. The T insides the angle brackets represents the type of the contained objects. It could be built-in types like int, char, or class types such as string, or even vector which means that the element contained in this vector is also a vector.

-

The vector template defines how to initialze vectors. When a vector is defalut initialized, it has no elements, which creats an empty vector. We can also initialize vectors using copy initialization or direct initialization or list initialization (see the table below).

- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
Ways to initialize a vectorreference: Lippman etc. 2012
vector v1default initialization and creat an empty vector
vector v2(v1)copy all elements in v1 to v2
vector v2 = v1has same effect as the last one
vector v3(n, val)direct initialization with n elements of the same value val
vector v4(n)v4 has n copies of value-initialized object
vector v5{a, b, c}v5 has three elements initialized with initializers a, b, c respectively
vector v5 = {a, b, c}has same effect as the last one
-

What new usage here is the value-initialization

-
1
vector<T> v4(n)
-

The n here only provide the number of elements that will be contained in the vector. But there is no initializers provided. In this case, all elements will be initialized following the principle of default initialization determined by the type of the elements. For example

-
1
2
vector<int> v1(10); // 10 elements will be initialized with 0
vector<string> v2(10); // 10 elements will be initialized to empty string type objects
-

If the type of the contained elements does’t support default initialization, the initialization of the vector would be failure.

-

In addition, it is worth noting the difference between

-
1
(1)    vector<int> v1(10);
-

and

-
1
(2)    vector<int> v2{10};
-
1
(3)    vector<int> v3(10, 1);
-

and

-
1
(4)    vector<int> v4{10, 1};
-

The first one is value-initialization as explained above while the second is list initialization with initializer 10. The third one is direct initialization with value 1 and the total number of elements is 10. The fourth one is list initialization with initializers 10 and 1, and the total number of elements is 2.

-

However, there exist some special cases

-
1
2
3
(5)    vector<string> v5("hello");

(6) vector<string> v6{10}; // 10 value-initialized elements
-

Example (5) is incorrect as we can not copy the string lterals to a vector. Example (6) is correct but has the same effect as example (1) rather than list initialization. This is because 10 can not be used to initialize an element of string type, instead it can be used to initialize the vector with 10 value-initialized strings.

-

Now returing to the beigining of this post, I’ll define a vector for the purpose of holding all homework grades.

-
1
2
double x;
vector<double> homework;
-

Operations on vectors

Reading the elements

One feature of a vector is that it has variable size, which is particularly helpful for us when the number of elements is unknown. The member function push_back can add a new element at the end of the vector after the current last element. The usage is shown below

-
1
2
3
4
while(cin >> x)
{
homework.push_back(x);
}
-

The x is passed as an argument and its value is copied to the new element. As a result, the size of the vector homework is increased by 1.

-

Implementing algorithms

Now all data have been stored into a vector. Assuming the goal of the program is to compute the median of the data set. It is known that the median value depends on the number of the stored elements (i.e. the size of the vector).

-
    -
  1. if there exist an odd number of numbers, the median value is the value of the middle number.
  2. -
  3. if there exist an even number of numbers, there is no single middle number and the median value is the average value of two middle numbers.
  4. -
-

For a program, we also need to consider the case of no elements.

-
    -
  1. if there is no elements at present, throwing a warning and asking to input again.
  2. -
-

Similar as a string, the size of a vector can be obtained through its member function size. For example, homework.size() returns the size of the vector. The returned value has a type of vector::size_type. Now we can translate above conditions to real code

-
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
// obtain the size of homework
typedef vector<double>::size_type vec_size;
vecsize size = homework.size();

if (size == 0)
{ cout << "You must enter your grades. "
"Please try again." << endl;

// return 1 instead of 0 to indicate failure
return 1;
}
else
{
if (size % 2 == 0)
// compute median
else
// compute median
}
-

Note that an alternative for the condition size == 0 is the empty function which returns true if vector is empty else returns false. The usage is the same as that for strings.

-

Type Aliases

For convenience, we uses type alias instead of using vector::size_type directly in defining variables of such type. A type alias defines the name vec_size as a synonym for vector::size_type. There are two ways to define type alias

-
1
2
3
(1)    typedef double length; // length is a synonym for double

(2) using length = double; // length is a synonmy for double
-

The second method is the new feature of new standards c++2011.

-

sort function

Now the rest of the work is to find the median and basically speaking, is to sort the data set. This can be done by using a library algoritm.

-
1
2
3
#include <algorithm>
... // other code
sort(homework.begin(), homework.end());
-

The sort function is defined in the library algorithm and therefore the header is added. It sorts the values in a container in an nondecreasing order. The arguments to sort specify the range of the data to be sorted. begin and end are member functions of the vector and represents the first element and (one past)the last element in homework respectively.
They are iterators and will further discussed in chapter 6.

-

After we obtained a ordered sequence of values, now I illustrate how to determine the median value.
If size is an even number

-

Similar as a string, we can access individual elements using subscript operator([]) and the index uses an asymmetrical range from 0 to the size of homework (excluded). If the size of homework is an even number, then size is exactly devided by 2. Due to the index starts from 0 ranther than 1, the mid elements should be homework[size/2 - 1] and homework[size/1]. Therefore, the median value is the average of the corresponding two values. As shown in above graph, the number of elements of both sides equals to size/2 because

-
1
2
left side:  number = size/2 - 1 - 0 + 1 = size/2
right side: number = size - 1 - size/2 + 1 = size/2
-

If size is an odd number, the result of size/2 is in fact the value of (size-1)/2. Then the mid element is exact the homework[(size-1)/2] as both sides of this element has same number of elements(as shown in below graph). This can also be verified mathematically

-
1
2
left side:  number = (size-1)/2 - 1 - 0 + 1 = (size-1)/2
right side: number = size - 1 - {(size-1)/2 + 1} + 1 = (size-1)/2;
-

If size is an odd number

-

Now let’s translate the algorithms to real code

-
1
2
3
4
5
6
7
8
9
{ 
vec_sz mid = size/2;
double median;

if (size % 2 == 0)
median = (homework[mid] + homework[mid-1])/2.0;
else
median = homework[mid]
}
-

Note that when calculate the average of two mid elements, I divide by 2.0 rather than 2 for the purpose of avoiding the loss of precision.This is because the quotient of two integers will be an integer.

-

The conditional (or ternary) operator

An alternative statements for above if-else clause is to use the conditional (or ternary) operator (?:). The syntax is

-
(condition 1) ? expression 1 : expression 2

It means that if condition 1 evaluates to true, then expression 2 is evaluated, and if condition 1 evaluates to false, then expression 3 is evaluated instead.

-

Therefore, we can change above code as

-
1
2
3
4
5
6
{ 
vec_sz mid = size/2;
double median;

median = size % 2 == 0) ? (homework[mid] + homework[mid-1])/2.0 : median = homework[mid];
}
- -

A complete program

Finally, I put all pieces of code together and obtained the complete program. Also, it works well when I test it.

-
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
#include <algorithm>
#include <iomanip>
#include <ios>
#include <iostream>
#include <string>
#include <vector>

using std::cin; using std::sort;
using std::cout; using std::streamsize;
using std::endl; using std::string;
using std::setprecision; using std::vector;

int main()
{
// ask for and read the student's name
cout << "Please enter your first name: ";
string name;
cin >> name;
cout << "Hello, " << name << "!" << endl;

// ask for and read the midterm and final grades
cout << "Please enter your midterm and final grades: ";
double midterm, final;
cin >> midterm >> final;

// ask for the student entered some homework grades
cout << "Enter all your homework grades, "
"followed by end-of-file: ";
double x;
vector<double> homework;

while(cin >> x)
homework.push_back(x);

// check that the student entered homework grades
typedef vector<double>::size_type vec_size;
vec_size size = homework.size();
if (size == 0)
{
cout << endl << "You must enter your grades. "
"Please try again." << endl;
return 1;
}

// sort the grades
sort(homework.begin(), homework.end());

// compute the median homework grade
vec_size mid = size/2;
double median;
median = size % 2 == 0 ? (homework[mid] + homework[mid - 1])/2
: homework[mid];

// compute and write the final grade
streamsize prec = cout.precision();
cout << "Your final grade is " << setprecision(3)
<< 0.2 * midterm + 0.4 * final + 0.4 * median
<< setprecision(prec) << endl;

return 0;
}
-

Test and results:

-
1
2
3
4
5
Please enter your first name: Bruce
Hello, Bruce!
Please enter your midterm and final grades: 80 90
Enter all your homework grades, followed by end-of-file: 50 60 70 80 90
Your final grade is 80
-
- - - - - - -
- -
- - - - -
- - - - -
- - -
- - - - -
- - - - - - - - -
-
- - -
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/2018/03/04/Accelerated-C-Solutions-to-Exercise-Chapter-3/index.html b/2018/03/04/Accelerated-C-Solutions-to-Exercise-Chapter-3/index.html deleted file mode 100644 index de4a70a9..00000000 --- a/2018/03/04/Accelerated-C-Solutions-to-Exercise-Chapter-3/index.html +++ /dev/null @@ -1,664 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - Accelerated C++ Solutions to Exercises(Chapter 3 Part 1) | Liam's Blog - - - - - - - - - - - - -
-
- -
-
- - -
- - -

If history repeats itself, and the unexpected always happens, how incapable must Man be of learning from experience?—George Bernard Shaw.

-
- - -
- - - - -
-
- - -
- - 0% -
- - - - -
-
-
- - -
- - -
- - - -
- - - - - -
-

- Accelerated C++ Solutions to Exercises(Chapter 3 Part 1) -

- - -
- - - - -
- - -

Exercise 3-0

Compile, execute, and test the programs in this chapter

-

Solution & Results

This exercise has been accomplished in C++ - Working with batches of data with detailed explination.

-
-

Exercise 3-1

Suppose we wish to find the median of a collection of values. Assume that we have read some of the values so far, and that we have no idea how many values remain to be read. Prove that we cannot afford to discard any of the values that we have read. Hint: One proof strategy is to assume that we can discard a value, and then find values for the unread—and therefore unknown—part of our collection that would cause the median to be the value that we discarded.

-

Solution & Results

It is known that the median value of a data sample is sensitive to the number of elements. If the number is an odd number, the mid element is unique and the median value is the value of this mid element. However, if the number is an even number, there exist two mid elements and the median value is the average value of these two elements. Consider that, once a value is discarded, the number of the elements changes from odd (or even) to even (odd), and hence the median value would be inaccurate.

-

For example, follow sequence of values is part of a data sample,

-
1
2 8 3 4 9 7 0
-

the unknown part is

-
1
11 6 13 9
-

The ture median value is 7. If 0 is discarded, the median value becomes 7.5. If 7 is discarded, the median value is still 7. If 9 is discarded, the median value is 6.5. This indicates that the median value would be an unreliable measure for a data sample if any of values is discarded.

-
-

Exercise 3-2

Write a program to compute and print the quartiles (that is, the quarter of the numbers with the largest values, the next highest quarter, and so on) of a set of integers.

-

Solutions & Results

Quartiles of an ordered dataset are values that divide the data set into four equal parts, i.e. quarters. An intuitive strategy is to find the median value of the whole data set, and then find the median values of the divided two parts respecitively. As a result, there will exist three median values which are first quartile, second quartile and third quartile relative to the whole data set. Therefore, once we know how to compute the median value, we know how to compute quartiles. Now I’ll enter into the details of how to implement such a computing algorithm.

-

data preparation

At the very beiging stage, we need to store and sort all values

-
1
2
3
4
5
6
7
 cout << "Please enter a sequence of integers, "
"followed by end-of-file: ";
int x;
vector<int> integers; // to hold all values
while(cin >> x)
integers.push_back(x);
sort(integers.begin(), integers.end());
-

find the median value

Now we have a sorted data set. Due to the calculation of a median value depends on the number of elements in the data set, we need to consider several cases. The number of elements is the size of integers.

-
1
2
typedef vector<int>::size_type vec_size;
vec_size size = integers.size();
-
    -
  1. if there is no elements(i.e. size == 0), it is impossible to compute the median value.

    -
    1
    2
    3
    4
    5
    6
    if (size == 0)
    {
    cout << "You must enter at least one integer. "
    "Please try again.";
    return 0;
    }
    -
  2. -
  3. if size is an even number, the median value is the average value of two mid elements. In addition, size/2 is exactly devided and represent the number of each side elements.
    Due to the index of a vector starts from 0, the mid elements should be integers[size/2 - 1] and integers[size/2]. Therefore, the median value is the average of the corresponding two values. As shown in above graph, the number of elements of both sides equals to size/2 because

    -
    1
    2
    left side:  number = size/2 - 1 - 0 + 1 = size/2
    right side: number = size - 1 - size/2 + 1 = size/2
    -

    If size is an even number

    -
  4. -
  5. if size is an odd number, the median value is the value of the unique mid element. size/2 yields the same value as (size-1)/2 in c++. Then the mid element is exact the integers[(size-1)/2] as both sides of this element has same number of elements(as shown in below graph). This can also be verified mathematically

    -
    1
    2
    left side:  number = (size-1)/2 - 1 - 0 + 1 = (size-1)/2
    right side: number = size - 1 - {(size-1)/2 + 1} + 1 = (size-1)/2;
    -

    If size is an odd number

    -
  6. -
-

Now let’s translate the algorithm into real code

-
1
2
3
4
vec_size mid = size/2;
double Q2; // the median is in fact the second quartile denoted by Q2
Q2 = size % 2 == 0 ? (integers[mid - 1] + integers[mid])/2.0
: integers[mid];
-

Note that when calculate the average of two mid elements, I divide by 2.0 rather than 2 for the purpose of avoiding the loss of precision.This is because the quotient of two integers will be an integer.

-

find the first quartile

From above analysis, we have known how to find a median, i.e. the second quartile of the dataset. The median has divided the data into two equal groups: first group is from the smallest value to the median and the other one is from the median value to the largest value. Therefore, the middle value of the first group is in fact the first quartile(also known as lower quartile) and the middle value of the second group is the third quartile (also known as upper quartile). We can apply the same method to find both middle values. Similarly, the first step is to find the size (denoted by half_size) for both groups and discuss different cases.

-

From above analysis, we know that

-
    -
  1. if size is an even number, half_size == size/2.
  2. -
  3. if size is an odd number, half_size == (size-1)/2. See wikipedia-Quartile Method 1
  4. -
-
1
2
vec_size half_size; // defind a variable to represent the size of two equal groups.
half_size = size % 2 == 0 ? size/2 : (size-1)/2;
-

Now let’s find the middle value of the first group values.

-
    -
  1. if half_size == 0, then size == 1. In this case, the single element is all we have and hence all quartiles equals to the value of the single element.
  2. -
-
1
2
3
4
// variables to hold the first quartile and third quartile
double Q1, Q3;
if (half_size == 0)
Q1 = Q3 = integers[0];
-
    -
  1. if half_size is an even number, half_size/2 is exactly divided and the mid elements are integers[half_size/2] and integers[half_size/2 - 1].

    -
  2. -
  3. if half_size is an odd number, half_size/2 gives the value of (half_size - 1)/2. The middle value is integers[(half_size-1)/2].

    -
  4. -
-

Both two cases are exactly the same as finding the median for the whole dataset.

-
1
2
vec_size mid_first = half_size/2;
Q1 = half_size % 2 == 0 ? (integers[mid_first - 1] + integers[mid_first])/2.0 : integers[mid_first];
- -

find the third quartile

The upper quartile is computed as same as the lower quartile except the index to be applied. If size is even, then the starting point for the second half is mid, while if size is odd, the starting point is mid+1. Therefore

-
1
2
3
vec_size mid_second = size % 2 == 0 ? (half_size/2 + mid) : (half_size/2 + mid + 1);
Q3 = half_size % 2 ? (integers[mid_second - 1] + integers[mid_second])/2.0
: integers[mid_second];
- -

A complete program

Now let’s put all pieces of code togther and add appropriate headers as well s using declarations.

-
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
65
#include <algorithm>
#include <iomanip>
#include <ios>
#include <iostream>
#include <string>
#include <vector>

using std::cin; using std::sort;
using std::cout; using std::streamsize;
using std::endl; using std::string;
using std::setprecision; using std::vector;

int main()
{
// ask for and read integers
cout << "Please enter a sequence of integers, "
"followed by end-of-file: ";
int x;
vector<int> integers; // to hold all values

while(cin >> x)
integers.push_back(x);
sort(integers.begin(), integers.end());

// get the size of the dataset
typedef vector<int>::size_type vec_size;
vec_size size = integers.size();

if (size == 0)
{
cout << "You must enter at least one integer. "
"Please try again.";
return 0;
}

// find the median value which in fact is the second quartile denoted by Q2
vec_size mid = size/2;
double Q2;
Q2 = size % 2 == 0 ? (integers[mid - 1] + integers[mid])/2.0 : integers[mid];

// get the size of two equal groups.
vec_size half_size;
half_size = size % 2 == 0 ? size/2 : (size-1)/2;

// find the first quartile and third quartile denoted by Q1 and Q3 respectively
double Q1, Q3;
if (half_size == 0)
{
Q1 = Q3 = integers[0];
}
else
{
vec_size mid_first = half_size/2;
Q1 = half_size % 2 == 0 ? (integers[mid_first - 1] + integers[mid_first])/2.0 : integers[mid_first];

vec_size mid_second = size % 2 == 0 ? (half_size/2 + mid) : (half_size/2 + mid + 1);
Q3 = half_size % 2 == 0 ? (integers[mid_second - 1] + integers[mid_second])/2.0 : integers[mid_second];
}

streamsize prec = cout.precision();
cout << setprecision(3) << "The first quartile is: " << Q1 << endl;
cout << "The second quartile is: " << Q2 << endl;
cout << "The second quartile is: " << Q3 << setprecision(prec) << endl;
return 0;
}
- -

Test performance

Test sequence 1: 1 2

-
1
2
3
4
Please enter a sequence of integers, followed by end-of-file: 1 2
The first quartile is: 1
The second quartile is: 1.5
The second quartile is: 2
-

Test sequence 2: 1 2 3

-
1
2
3
4
Please enter a sequence of integers, followed by end-of-file: 1 2 3
The first quartile is: 1
The second quartile is: 2
The second quartile is: 3
-

Test sequence 3: 1 2 3 4

-
1
2
3
4
Please enter a sequence of integers, followed by end-of-file: 1 2 3 4
The first quartile is: 1.5
The second quartile is: 2.5
The second quartile is: 3.5
-

Test sequence 3: 1 3 5 6 9 0 3 2 5 3 8

-
1
2
3
4
Please enter a sequence of integers, followed by end-of-file: 1 3 5 6 9 0 3 2 5 3 8 
The first quartile is: 2
The second quartile is: 3
The second quartile is: 6
-

Yeah, it works perfectly.

-
-

Exercise 3-3

Write a program to count how many times each distinct word appears in its input.

-

Solution & Results

Intuitive explanations

The purposes of this program is to write out each distinct word followed by its occurrence numbers in the input. And what we have is only unknown amount of words to be entered. Imagine that there is only one word(e.g. word1) in total, then the output will be:

-
1
word1 appears 1 times
-

Now in the case of two words, e.g. word1 and word2, if word1 is the same as word2, then the output will be:

-
1
word1 appears 2 times
-

otherwise

-
1
2
word1 appears 1 times
word2 appears 1 times
-

From these two cases, we have seen that

-
    -
  1. word1 needs to be stored and its occurrence needs to be recorded
  2. -
  3. word2 needs to be compared with word1. As a result, it will be discarded if they are the same and the occurrence number of word1 is increased by 1, and it will be stored if they are different and its occurrence number increases by 1.
  4. -
-

By analogy, following entered words will be compared with each stored word, and will be discarded if there already exist one same word otherwise will be stored, meanwhile, the corresponding occurrence numbers are adjusted. Finally, all stored words are distinct with eachother and their occurrence numbers have been clearly recorded. Now I enter the details of this program.

-

vectors and the structure

To hold each distinct word and the associated number of occurrence, I define two vectors whis have types of int and string respectively.

-
1
2
vector<int> counter;
vector<string> words;
-

The next step is to ask for enterring word by word and store the distinct word. This can be accomplished with a while statement. To write all distinct words as well as the occurrence numbers, we can loop through words and counter using index from 0 to words.size() - 1.
Therefore, the whole structure is

-
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
int main()
{
vector<int> counter;
vector<string> words;
typedef vector<string>::size_type vec_size;

string word;
while(cin >> word)
{
/* pseudocode
* if the word is a new distinct word
* words.push_back(word);
* counter.push_back(1);
* if the word already exists
* adjust the number of occurrence for
* the existed distinct word
*/
}

// if there is no inputs, send warning
if (words.empty())
{
cout << "You must input at least one word. Please try again.";
return 1;
}

for (vec_size i = 0; i != words.size(); ++i)
{
cout << words[i] << " appears " << counter[i] << " times" << endl;
}
return 0;
}
-

loop invariants

The only part that is needed is write the while body. The loop invariant is that words contains each distinct word entered and counter contains the associated number of occurrence. Therefore, two goals need to be accomplished inside the while loop:

-
    -
  1. check whether the current is distinct from all existed distinct words
  2. -
  3. adjust word and counter to maintain the loop invariant
  4. -
-

The first goal can be accomplished by comparing the current word with each word stored in words. In addition, a flag is set to indicate the status of the outcomes, that is, a distinct word or an existed word. Let’s see the code below

-
1
2
3
4
5
6
7
8
9
10
11
12
int flag = 0; // initial status
for (vec_size i = 0; i != words.size(); ++i)
{
if (word == words[i])
{
// if the word is already existed, discard the word but change the counter
++counter[i];

// change status show this word is not a distinct word
flag = 1;
}
}
-

The second goal is partly accomplished by the code above and the rest case is when the current word is a distinct word. Accordingly, the word should be stored into words and the initial value for occurrence number is 1.

-
1
2
3
4
5
if (flag == 0)
{
words.push_back(word);
counter.push_back(1);
}
- -

Now the program is finished, and please find the complete version in the following part. I also did several tests and it works as expected. Note that I omitted the process that verify the correctness of the loop invariants here.

-

A complete 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
56
57
58
59
60
61
62
63
64
65
66
#include <string>
#include <vector>
#include <iostream>

using std::cin; using std::string;
using std::cout; using std::vector;
using std::endl;

int main()
{
// vectors for holding each distinct word and its occurrence numbers
vector<int> counter;
vector<string> words;

// using type alias for convenience
typedef vector<string>::size_type vec_size;

// ask for and read words one by one
cout << "Please enter a sequence of words, and followed by end-of-file: ";
string word;

// while loop invariant: words and counter contains each distinct word and the associated times
while(cin >> word)
{
// set flags to indicate whether the current word already exists
// flag == 0: the word is distinct from each existed distinct word
// flag == 1: the word is already exist
int flag = 0;

// to maintain the outer loop invariant, compare the current word
// with each existed distinct word using following for loops

// for loop invariant: the current word has been compared with the ith distinct word
for (vec_size i = 0; i != words.size(); ++i)
{
// adjust the number of occurrence by 1 for existed distinct words
if (word == words[i])
{
++counter[i];
flag = 1; // change flag value to indicate that this word is distinct
}
}

// maintain the outer loop invariant: store the new distinct word and its occurrence number
if (flag == 0)
{
words.push_back(word);
counter.push_back(1);
}
}

// send warning if there is no any words entered
if (words.empty())
{
cout << "You must input at least one word. Please try again.";
return 1;
}

// write a blank line to seperate the outputs
cout << endl;
for (vec_size i = 0; i != words.size(); ++i)
{
cout << words[i] << " appears " << counter[i] << " times" << endl;
}
return 0;
}
-

Test performance

Test 1: house

-
1
2
3
Please enter a sequence of words followed by end-of-file: house

house appears 1 times
-

Test 2: house number one and number two

-
1
2
3
4
5
6
7
Please enter a sequence of words followed by end-of-file: house number one and number two

house appears 1 times
number appears 2 times
one appears 1 times
and appears 1 times
two appears 1 times
- -
-

Exercise 3-4

Write a program to report the length of the longest and shortest string in its input.

-

Solution & Results

Strategy 1

A very simple solution strategy to this exercise is that store the length of each string into a vector and then implement a library sort algorithm. I won’t go into details as it is simple. Please find the code and tests below.

-
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
#include <algorithm>
#include <string>
#include <vector>
#include <iostream>

using std::cin; using std::string;
using std::cout; using std::vector;
using std::endl;

int main()
{
// ask for words
cout << "Please enter words followed by end-of-file: ";
string word;

// creat a vector for holding the length of each string
typedef string::size_type word_size;
vector<word_size> length;

// read word and store the length
while(cin >> word)
{
length.push_back(word.size());
}

if (length.empty())
{
cout << "You must input at least one word."
"Please try again.";
return 1;
}

// sort all lengths in an nondecreasing order
sort(length.begin(), length.end());
cout << "The length of the shortest string is: " << length[0] << endl;
cout << "The length of the longest string is: " << length[length.size() - 1] << endl;
return 0;
}
-

Test 1: I am a good teacher

-
1
2
3
Please enter words followed by end-of-file: I am a good teacher 
The length of the shortest string is: 1
The length of the longest string is: 7
-

Test 2: what are you going to do

-
1
2
3
Please enter words followed by end-of-file: what are you going to do
The length of the shortest string is: 2
The length of the longest string is: 5
-

Strategy 2

Above strategy is computational inefficient due to the fact that it sorts all length values while we only need two extremes. An alternative strategy is to use insert sort algorithm but only sort one round for each of extremes. For example, now there is a sequence of integers

-
1
n1 n2 n3 n4 ... nx ny nz
-

Step 1: assumming the largest number is n1.

-

Step 2: compare n1 and n2, if n1 >= n2, we exchange their positions with eachother. But if n1 < n2, we keep their order and assume n2 is the largest number.

-

Step 3: compare the larger number of step 2 with n3. Deal with the comparison result as same as that in step 2.

-

Step 4: continue comparison until the last number. Now the number in the rightest position is the final result, i.e. the largest number.

-

By analogy, we can find the smallest number. In this case, the precedure is simpler as we don’t need to exchange their positions. For finding the largest number, we simply discard the smaller value in each comparison. It is pretty easy to understand and no more detailed description here. Please see the complete program below.

-
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
#include <iostream>
#include <string>

using std::cin; using std::endl;
using std::cout; using std::string;


int main()
{
// ask for inputs
cout << "Please enter words followed by end-of-file: ";
string word;
typedef string::size_type str_size;

// variables for holding the length values of the longest and shortest strings
str_size longestLength = 0;
str_size shortestLength = 0;

// read words one by one
while(cin >> word)
{
// insert sort to get the largest value and samllest value only
if (longestLength == 0 || longestLength < word.size())
longestLength = word.size();
if (shortestLength == 0 || shortestLength > word.size())
shortestLength = word.size();
}

// if there is no inputs, send warning
if (longestLength == 0)
{
cout << "You must enter at least one word. Please try again.";
return 1;
}

cout << "The length of the shortest string is: " << shortestLength << endl;
cout << "The length of the longest string is: " << longestLength << endl;
return 0;
}
-

Test 1: I am a good teacher

-
1
2
3
Please enter words followed by end-of-file: I am a good teacher 
The length of the shortest string is: 1
The length of the longest string is: 7
-

Test 2: what are you going to do

-
1
2
3
Please enter words followed by end-of-file: what are you going to do
The length of the shortest string is: 2
The length of the longest string is: 5
-

The results of above two programs are exactly the same.

-
-

To be continued.

- -
- - - - - - -
- -
- - - - -
- - - - -
- - -
- - - - -
- - - - - - - - -
-
- - -
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/2018/03/05/Accelerated-C-Solutions-to-Exercises-Chapter-3-Part-2/index.html b/2018/03/05/Accelerated-C-Solutions-to-Exercises-Chapter-3-Part-2/index.html deleted file mode 100644 index 5b059202..00000000 --- a/2018/03/05/Accelerated-C-Solutions-to-Exercises-Chapter-3-Part-2/index.html +++ /dev/null @@ -1,570 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - Accelerated C++ Solutions to Exercises(Chapter 3 Part 2) | Liam's Blog - - - - - - - - - - - - -
-
- -
-
- - -
- - -

If history repeats itself, and the unexpected always happens, how incapable must Man be of learning from experience?—George Bernard Shaw.

-
- - -
- - - - -
-
- - -
- - 0% -
- - - - -
-
-
- - -
- - -
- - - -
- - - - - -
-

- Accelerated C++ Solutions to Exercises(Chapter 3 Part 2) -

- - -
- - - - -
- - -

Exercise 3-5

Write a program that will keep track of grades for several students at once. The program could keep two vectors in sync: The first should hold the student’s names, and the second the final grades that can be computed as input is read. For now, you should assume a fixed number of homework grades.

-

Solution & Results

The exercise tries to make the original program (see the first program) more practical. There are two more requirements compared with the original one: first, it requires computing the final grades for several students at once; second, it requires keep tracking both the students’ name and their final grades.

-

To meet the first requirements, we can add a while loop which allows us compute the final grades multiple times for different student. The condition will be an variable whose value depends on users. Let’s finish this first

-
1
2
3
4
5
6
7
8
9
10
11
12
13
14
// set initial status
bool running = true;
while(running)
{
// compound statements


char flag;
cout << "Do you want to check more students? Please input Y/N: ";
cin >> flag;

if (flag == 'Y') ;
else running = false;
}
-

To meet the second requirement, we can store both names and the final grades into two vectors.

-
1
2
vector<string> names;
vector<double> finalGrades;
-

Each time we store a student’s name, we will store his final grade after computation. When needs writing the outputs, we can use same index for both vectors as there is a one-to-one correspondence.

-

Below is the modified 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
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
#include <iomanip>
#include <ios>
#include <iostream>
#include <string>
#include <vector>

using std::cin; using std::setprecision;
using std::cout; using std::string;
using std::endl; using std::streamsize;
using std::vector;

int main()
{
vector<string> names;
vector<double> finalGrades;
bool running = true;
while(running)
{
// ask for and read the student's name
cout << "Please enter your first name: ";
string name;
cin >> name;

// store the name into names
names.push_back(name);
cout << "Hello, " << name << "!" << endl;

// ask for and read the midterm and final grades
cout << "Please enter you midterm and final exam grades: ";
double midterm, final;
cin >> midterm >> final;

// ask for the number of homework grades
cout << "Please enter the number of your homeworks: ";
int numofHomeworks;
cin >> numofHomeworks;

// ask for all homework grades
cout << "Enter all your homework grades: ";

// the number and the sum of grades read so far
int count = 0;
double sum = 0;

// a variable into which to read
double x;

// invariant: we have read count grades so far, and sum is the sum of the first count grades
while (count < numofHomeworks)
{
++count;
cin >> x;
sum += x;
}

// compute the fianl grade and store into finalGrades
double finalGrade = 0.2 * midterm + 0.4 * final + 0.4 * sum/count;
finalGrades.push_back(finalGrade);

// check condition
char flag;
cout << "Do you want to check more students? Please input Y/N: ";
cin >> flag;
if (flag == 'Y') ;
else running = false;
}

// prepare for writing outputs
typedef vector<string>::size_type vec_size;


for (vec_size i = 0; i != names.size(); ++i)
{
streamsize prec = cout.precision();
cout << names[i] << "'s final grade is: " << setprecision(3) << finalGrades[i]
<< setprecision(prec) << endl;
}
return 0;
}
-

It’s not complex once figure out the original program. I test the program and it works well (see below).
Test

-
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
Please enter your first name: Conor
Hello, Conor!
Please enter you midterm and final exam grades: 88 70
Please enter the number of your homeworks: 3
Enter all your homework grades: 65 76 81
Do you want to check more students? Please input Y/N: Y
Please enter your first name: Brendan
Hello, Brendan!
Please enter you midterm and final exam grades: 70 80
Please enter the number of your homeworks: 2
Enter all your homework grades: 90 85
Do you want to check more students? Please input Y/N: Y
Please enter your first name: Robin
Hello, Robin!
Please enter you midterm and final exam grades: 90 90
Please enter the number of your homeworks: 5
Enter all your homework grades: 80 70 60 50 40
Do you want to check more students? Please input Y/N: N

Conor's final grade is: 75.2
Brendan's final grade is: 81
Robin's final grade is: 78
-

Note that the final grades don’t retain the tail zeros and the dot point though we set precision arguments as 3. Please find more experiments about setprecision at Working with batches of data .

-
-

Exercise 3-6

The average-grade computation in the first program might divide by zero if the student didn’t enter any grades. Division by zero is undefined in C++, which means that the implementation is permitted to do anything it likes. What does your C++ implementation do in this case? Rewrite the program so that its behavior does not depend on how the implementation treats division by zero.

-

Solution & Results

To figure out how c++ implementation deals with division by 0, I did several experiments.
Experiment 1

-
1
2
3
4
5
6
7
8
9
10
#include <iostream>
using std::cout;
using std::endl;

int main(){
int i = 10;
int j = 0;
cout << i/j << endl;
returo 0;
}
-

The result shows that disivion by 0 with both integers makes the program crash.

-

Experiment 2

-
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
#include <iostream>
using std::cout;
using std::endl;

int main(){
int i = 10;
double j = 0;
cout << i/j << endl;

double x = 10;
int y = 0;
cout << x/y << endl;

double m = 10;
double n = 0;
cout << m/n << endl;

return 0;
}
-

This program works and returns three inf which means infinity.

-

Exerpriment 3

-
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
#include <iostream>
using std::cout;
using std::endl;

int main(){

double h = 0;
double k1 = 0;
int k2 = 0;

cout << h/k1 << endl;
cout << h/k2 << endl;

return 0;
}
-

The third program is the case of the original program and it returns nan which means Not-A-Number.

-

There is one question on quora Why does division by zero return INF (infinite) with floats, but makes the program crash with integers in C++?. Many answers can be found there but I can’t understand the mechanism well.

-

Anyway, division by 0 is a special case and should be treated seperately. For the original program, one way is to check the count before the division. For example, we can add below piece of code after the while loop finishes

-
1
2
3
4
5
   if (count == 0)
{
cout << "No homework grades entered, please try again.";
return 1;
}
-

Alternatively, we can set a default value for the average homework grade.

-
1
2
3
4
5
double homeworkGrade;
if (count == 0)
homeworkGrade = 0;
else
homeworkGrade = sum/count;
-

Now I apply the second method and present the modified program below.

-
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
#include <iomanip>
#include <ios>
#include <iostream>
#include <string>

using std::cin; using std::setprecision;
using std::cout; using std::string;
using std::endl; using std::streamsize;

int main()
{
// ask for and read the student's name
cout << "Please enter your first name: ";
string name;
cin >> name;
cout << "Hello, " << name << "!" << endl;

// ask for and read the midterm and final grades
cout << "Please enter you midterm and final exam grades: ";
double midterm, final;
cin >> midterm >> final;

// ask for the homework grades
cout << "Enter all your homework grades, "
"followed by end-of-file: ";

// the number and the sum of grades read so far
int count = 0;
double sum = 0;

// a variable into which to read
double x;

// invariant: we have read count grades so far, and sum is the sum of the first count grades
while (cin >> x)
{
++count;
sum += x;
}

// special case treatment
double homeworkGrade;
if (count == 0)
homeworkGrade = 0;
else
homeworkGrade = sum/count;

// write a blank line to seperate outputs
cout << endl;

// write the result
streamsize prec = cout.precision();
cout << "Your final grade is " << setprecision(3) <<
0.2 * midterm + 0.4 * final + 0.4 * homeworkGrade
<<setprecision(prec) << endl;
return 0;
}
-

Test

-
1
2
3
4
5
6
Please enter your first name: Brendan
Hello, Brendan!
Please enter you midterm and final exam grades: 95 77
Enter all your homework grades, followed by end-of-file:

Your final grade is 49.8
-

Now, it works better than the original one.

-
-

Reference

Koenig, A. and Moo, B.E., 2000. Accelerated C++.

-

Lippman, S.B., Lajoie, J. and Moo, B.E., 2013. C++ Primer, 5th edt.

- -
- - - - - - -
- -
- - - - -
- - - - -
- - -
- - - - -
- - - - - - - - -
-
- - -
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/2018/03/06/C-Organizing-programs-and-data/index.html b/2018/03/06/C-Organizing-programs-and-data/index.html deleted file mode 100644 index b105cec1..00000000 --- a/2018/03/06/C-Organizing-programs-and-data/index.html +++ /dev/null @@ -1,613 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - C++ - Organizing programs with functions | Liam's Blog - - - - - - - - - - - - -
-
- -
-
- - -
- - -

If history repeats itself, and the unexpected always happens, how incapable must Man be of learning from experience?—George Bernard Shaw.

-
- - -
- - - - -
-
- - -
- - 0% -
- - - - -
-
-
- - -
- - -
- - - -
- - - - - -
-

- C++ - Organizing programs with functions -

- - -
- - - - -
- - -

Previous chapters mainly covers topics including

-
    -
  1. main function structure
  2. -
  3. statements such as expression statements and flow-of-control statements.
  4. -
  5. built-in types, such as int, float, double, bool and char.
  6. -
  7. standard library IO mechanism.
  8. -
  9. standard library string.
  10. -
  11. standard library vector.
  12. -
-

We have achieved several goals through certain statements and operations on objects of different types. However, the program becomes unmanageable along with increasingly complex functions and growing information. For this reason, this chapter introduces how to organize programs and data.

-

Functions

Basics

writing a function

If we break a program(e.g. A complete program) into pieces, we found that it is in fact constituted by data information, homework grade computation and final grade computation. Both computations can be organized as a function, which is a named block of code. The functions will be called When the computation results are needed. Let’s start with writing a function to compute the final grade, assuming that the homework grade has been computed.

-
1
2
3
4
5
// compute the final grade of a student
double grade(double midterm, double final, double homework)
{
return 0.2*midterm + 0.4*final + 0.4*homework;
}
-

Basically, it has the same structure as the main function except that we use empty parameter list in previous main function.

-

In general, a function includes four parts:

-
    -
  1. return type. In this case, it has return type of double.
  2. -
  3. function name. In this case, the function is names as grade.
  4. -
  5. parameter list enclosed in parentheses (). In this case, there are three parameters seperated by commas. All three parameters have type of double. They are defined like variables but only be created when the function is called.
  6. -
  7. function body enclosed in curly braces {}. The return statements returns the result to function caller.
  8. -
-

calling a function

When calling the function, the excution of function caller is suspended and execution of the called function begins. We must supply corresponding arguments for the purpose of initializing the parameters. In other words, arguments are the initializers for a function’s parameters. Arguments can be variables or expressions or even values. But they must be provided in the same order as well as the same type as the parameters. If we replace the computation in the original program, it would be like

-
1
2
cout << "You final grade is " << setprecision(3)
<< grade(midterm, final, sum/count) << setprecision(prec) << endl;
-

The first parameter midterm will be initialized by copying the value of argument midterm into it. So do the other parameters. This is what so called call by value. Essentially, these parameters are created in an area independent from the variables in the calling function though they have the same values. Therefore, if the function manipulate these parameters, it wouldn’t change values of the calling function variables. In addition, the parameters are local to the function and only exist start from calling the function to returning from the function. Therefore, it doesn’t matter that we use same name as the variable in the calling function.

-

Once the execution encounters the return statement in the function body, the execution of the function ends and back to the calling function.

-

Writing a median function

Now we consider writing a median function that computes the median value of the homework grades. Let’s list four parts of a median function:

-
    -
  1. the return type should be double.
  2. -
  3. it is named as median for clarity.
  4. -
  5. what we need for computation is only a vector of double type (assuming that we have read all grades).
  6. -
  7. computing algorithms.
  8. -
-

It’s pretty straightforward

-
1
2
3
4
double median(vector<double> vec)
{
// algorithms to be written
}
-

The algorithm in the original program is

-
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
// get the size of the vector
typedef vector<double>::size_type vec_size;
vec_size size = homework.size();

// check special case
if (size == 0)
{
cout << endl << "You must enter your grades. "
"Please try again." << endl;
return 1;
}

// sort the grades
sort(homework.begin(), homework.end());

// compute the median homework grade
vec_size mid = size/2;
double median;
median = size % 2 == 0 ? (homework[mid] + homework[mid - 1])/2 : homework[mid];
-

To write this piece of code into the function, we need to first change the variable name homework to vec as this function suites for more general cases. Nevertheless, you don’t have to do it if you dislike. The second step is to remove the variable median and add return because what this function need to do to return the median value. The last step is to change the code that deals with the case of empty vector.

-
1
2
if (size == 0)
throw domain_error("median of an empty vector");
-

This is because the original code can not be used here due to it returns another value 1 (unless we change the function structure). In real word programming, throw an exception is a more general way to complain. The usage is explained in next part. Now the function is accomplished as shown below

-
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
// function to compute the median of a vector<double>
double median(vector<double> vec)
{
// get the size of the vector
typedef vector<double>::size_type vec_size;
vec_size size = vec.size();

// check whether the empty is empty
if (size == 0)
throw domain_error("median of an empty vector");

// sort the grades
sort(vec.begin(), vec.end());

// compute the median homework grade
vec_size mid = size/2;
return size % 2 == 0 ? (vec[mid] + vec[mid - 1])/2 : vec[mid];
}
-

try blocks and Exception handling

“Exceptions are run-time anomalies—such as losing a database connection or encountering unexpected input—that exist outside the normal functioning of a program.” - Lippman etc. 2012

-

throw expressions

throw expressions is used to detect the exceptions, which is followed by an exception onject that describes the problems that it encounters. It stops the execution of the current function and passes an exception object to the caller for handling it.

-
1
2
3
4
// the detecting part
if (size == 0)
// throw raises exceptions
throw domain_error("median of an empty vector");
-

For example, the exception object domian_error contains the information of that the caller can use to act on the exception. It is a type that the standard library defines in header for use in reporting the logic error: argument is out side the values that the function can accept. What closely follows is a string enclosed by parentheses to describe the problem.

-

the try block

The try block is the handling part uses to deal with an exception. Once the exception is thrown, it catches the exception and handle it according to the type of the exception object. The general syntax is

-
1
2
3
4
5
6
7
try{
// statements including the detecting part
} catches(exception object1){
// handler-statements
} catches(exception object2){
// handler-statements
} ...
-

The catch clause handles the exception and hence is termed as “exception handler”. If the statements between try and catch don’t throw any exceptions during execution, the program ignores the handler-statements and continue to next part.

-

It is worthing noting that each pair of curly braces forms a name scope. The application of the try block will be finished at the end of this post.

-

Finish the grade function

Now we can embed the median function in the grade function.

-
1
2
3
4
5
6
7
// function to compute the final grade which is the weighted average grade of medterm exam grade, final exam grade and the median homework grade
double grade(double midterm, double final, const vector<double> &hw)
{
if (hw.size() == 0)
throw domain_error("student has done no homework");
return grade(midterm, final, median(hw));
}
-

reference and call by reference

It has been noted that this grade function differs from the previous one mentioned at the beigining in the part of parameter list. The third parameter here has a compound type with modifier reference. Recalling that to declare a variable needs a type and a name. More generally, a declaration is a base type followed by a list of declarators including a name and an optional type modifier.

-
1
2
3
4
5
    base type modifier name 
```
A variable declared in above form has a type named **compound type** which is built from the base type. In this case, the third parameter has a type of **vector<double>** with modifier **reference** which indicates that the onject named **hw** refers to its **initializer**. In other words, a reference is a **alias** and **hw** is simply another name for the argument to be passed. In addition, a reference to a reference is in fact that both references refer to the original object. For example
```c++
vector<double> &hw1 = hw; // hw1 is another name for the vector homework
-

In contrast to call by value, this is termed as call by reference. When we operate on a reference, we actually operate on the object that the reference refers. For the purpose of computational effiency, passing argument by reference can avoid copies, particularly for objects of large containers or class types. But, it is not a good habit to modify the value of the object that the reference refers. It’s complete ok in this case as the object is passed by copy in the median function where we will operate on the homework grades. Beyond this, there is a const qualifier before the reference, which restrict the values of the object to be changed when operating on the reference.

-

overloaded function

Recalling the previous grade function

-
1
2
3
4
5
6
```c++
// compute the final grade of a student
double grade(double midterm, double final, double homework)
{
return 0.2*midterm + 0.4*final + 0.4*homework;
}
-

These two functions have the same name but different parameter list. This is termed as function overloading with either different types or numbers of parameters. If two function only differs in return type, functions can not be overloaded. When calling the overloaded function, the complier determines which function to call according to the supplied arguments and the defined parameters in each funciton.

-

Writing a reading function

Finally, we need to solve the problem that how to read home work grades into a vector. The oringal code is

-
1
2
3
4
5
6
double x;
vector<double> homework;

// enter homework grades followed by end-of-file
while(cin >> x)
homework.push_back(x);
-

So, what’s this function should return?
Obviously, the purpose is to fill the vector homework and therefore it should return a filled vector. Beyond this, the function is required to return another value to the stream to indicate whether the attempted input was successful.
Intuitively, it works like this:

-

Funtion work flow

-

But it is hard to deal with two returns in one function and alternatively we can define a parameter as a reference type for the purpose of changing the values in homework directly. See the code below

-
1
2
3
4
5
6
// read the homework grades from an input stream into a vector homework
istream & read_hw(istream &in, vector<double> &hw)
{
// statements to be filled
return in;
}
-

lvalue

We are familar with the second parameter which refers to its initializer to be passed, i.e. the vector homework. Since we intend to modify the passed arguments, the const qualifier has been dropped. There is an important difference between a const reference and a nonconst reference. For a const reference, the arguments to be passed can be any value while a non const reference can only refer to a lvalue object (i.e. a nontemporary object). Any expressions that generate arithmetic values are not lvalue. For example

-
1
2
3
4
int i = 10;
int &j = i; // correct: j is bound to i
int &m = 10; // error: initializer must be an nontemporary object
const int &n = 10; // correct: a const reference
-

member function clear

The first parameter is also a type of non-const reference which refers to the object cin. This is because we hope to change its internal state. As a result, the return type is also a reference as in is a reference. Another reason is that there is no copy or assign for IO objects.

-

Now we consider read entered grades into homework. Remember that We propose to write a program that can deal with multiple students’ records. One problem is that the vector might contain the grades of the last student. To keep the vector empty, we use hw.clear() to discard any contents the vector might have had.

-

Similarly, we also need to keep the cin be valid for each student. In previous chapter, We have explained that once we finishes typing in the homework grades a signal end-of-file needs to be sent for terminating the loop. The signal will change the internal state of the cin to be false. In addition, end-of-file is not the only input that can stop the loop. If we enter values of an improper type, the library would mark the input stream as being in failure state as well. For this reason, we use in.clear() to clear the error state of cin after finishing the input for one student. Note that both the vector and the object of istream have member function clear but the effects are completely different. The function is shown below

-
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
// read homework grades from an input stream inti a vector
istream & read_hw(istream &in, vector<double> &hw)
{
if (in){
//get rid of previous contents
hw.clear();

// read homework grades
double x;
while(in >> x)
hw.push_back(x);

// clear the stream so that input will work for the next student
in.clear();
}
return in;
}
-

A complete program

Up to now, we have changed the computations in the original program to functions including a function to homework grades, a function to calculate the median of homework grades and a function to calculate the final grade. A complete program is presented below

-
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
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
// include directives
#include<iostream> // to get declaration of cin, cout, endl,
#include<istream> // to get declaration of istream
#include<string> // to get declaration of string
#include<vector> // to get declaration of vector
#include<algorithm>// to get declaration of sort
#include<ios> // to get declaration of streamsize
#include<stdexcept>// to get declaration of domain_error
#include<iomanip> // to get declaration of setprecision

// add using declarations
using std::cin; using std::setprecision;
using std::cout; using std::streamsize;
using std::endl; using std::domain_error;
using std::string; using std::istream;
using std::vector; using std::sort;

// declare functions
istream & read_hw(istream &in, vector<double> &hw);
double median(vector<double> vec);
double grade(double midterm, double final, double homework);
double grade(double midterm, double grade, const vector<double> &hw);

// main function
int main()
{
// ask for and read the student's name
cout << "Please enter your first name: ";
string name;
cin >> name;
cout << "Hello, " << name << "!" << endl;

// ask for and read the midterm and final grades
cout << "Please enter your midterm and final exam grades: ";
double midterm, final;
cin >> midterm >> final;

// ask for the homework grades
cout << "Enter all your homework grades, followed by end-of-file: ";

// read the homework grades
vector<double> homework;
read_hw(cin, homework);

// compute and generate the final grade, if possible
try {
double final_grade = grade(midterm, final, homework);
streamsize prec = cout.precision();
cout << "Your final grade is " << setprecision(3)
<< final_grade << setprecision(prec) <<endl;
} catch(domain_error) {
cout << endl << "You must enter your grade. Please trt again." << endl;
return 1;
}
return 0;
}

// define function to read homework grade
istream & read_hw(istream &in, vector<double> &hw)
{
if (in){
//get rid of previous contents
hw.clear();

// read homework grades
double x;
while(in >> x)
hw.push_back(x);

// clear the stream so that input will work for the next student
in.clear();
}
return in;
}

// define function to calculate median value
double median(vector<double> vec)
{
// get the size of the vector
typedef vector<double>::size_type vec_size;
vec_size size = vec.size();

// check whether the empty is empty
if (size == 0)
throw domain_error("median of an empty vector");

// sort the grades
sort(vec.begin(), vec.end());

// compute the median homework grade
vec_size mid = size/2;
return size % 2 == 0 ? (vec[mid] + vec[mid - 1])/2 : vec[mid];
}

// define a function to calculate final grade
double grade(double midterm, double final, const vector<double> &hw)
{
if (hw.size() == 0)
throw domain_error("student has done no homework");
return grade(midterm, final, median(hw));
}

//define a function to calculate final grade (function overloading)
double grade(double midterm, double final, double homework)
{
return 0.2*midterm + 0.4*final + 0.4*homework;
}
- -

See from above program, why the statements inside the try block are not organized as such form

-
1
2
3
4
5
6
7
try {
streamsize_prec = cout.precision();
cout << "Your final grade is " << setprecision(3)
<< grade(midterm, final, homework)
<< setprecision(prec) <<endl;
} catch...
...
-

In doing so, we probably can’t control the outputs as the grade function may be called after or before the string literals depending on the implementation. Also, if any exception is thrown, the precision may not be reset back to the original value as expected.

-
-

To be continued.

- -
- - - - - - -
- -
- - - - -
- - - - -
- - -
- - - - -
- - - - - - - - -
-
- - -
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/2018/03/08/Organizing-programs-and-data-Part-2/index.html b/2018/03/08/Organizing-programs-and-data-Part-2/index.html deleted file mode 100644 index e2016d23..00000000 --- a/2018/03/08/Organizing-programs-and-data-Part-2/index.html +++ /dev/null @@ -1,618 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - C++ - Organizing programs with data structures | Liam's Blog - - - - - - - - - - - - -
-
- -
-
- - -
- - -

If history repeats itself, and the unexpected always happens, how incapable must Man be of learning from experience?—George Bernard Shaw.

-
- - -
- - - - -
-
- - -
- - 0% -
- - - - -
-
-
- - -
- - -
- - - -
- - - - - -
-

- C++ - Organizing programs with data structures -

- - -
- - - - -
- - -

The program we have accomplished in last chapter is good enough for computing one students’ final grade, however, is unpractical in reality when it comes to generating a final grade report for a class. Assuming that we have a file that records all students’ information including their names, midterm and final exam grades, and homework grades. For example

-
1
2
3
Robin 90 87 79 88 81 73 45
Brendan 70 69 88 100 91 75 66
...
-

The program is required to compute the final grade for each student and generate a report like

-
1
2
Bredan 76.8
Robin 84.4
-

In specific, there are three requirements

-
    -
  1. in the final grade, the mediter exam grade counts for 20%, the final exam grade counts for 40%, and the median homework grade counts for 40%.
  2. -
  3. the output follows an alphabetical order according to the names.
  4. -
  5. the final grades are vertically aligned.
  6. -
-

A similar program has been done in Exercise 3-5, which can keep track of grades for several students at once though it uses the average homework grade rather than median value. The whole structure is simply a while loop. The program in last chapter teaches us how to fullfill the first requirement with functions. Now we focus on how to meet the second the third requirements.

-

Data struct

Last chapter mainly introdues how to write functions to deal with computations as well as data reading. However, the information such as name, medterm and final exam grades are still left there. If more information such as age, weight and grade need to be added, the program would be bloated. In fact, all these information can be integrated as a user-defined data structures as follows

-
1
2
3
4
5
struct Student_info {
string name;
double midterm, final;
vector<double> homework;
} objectName;
-

The code defines a struct that contains a group of data members. Student_info is the name of this type. Each data member is declared with a type and a name. objectName is an object of such type. Another way to declare an object is

-
1
Student_info objectName;
-

Note that there must be a semicolon at the end of the curly braces when defining a struct type.

-

It has been observed that each object of such type holds information for one student. We can store all students’ information into a vector, e.g. vector record.

-

reading data

The function that an object of Student_info reads data is similar to that for a vector.

-
1
2
Student_info record;
read(cin, record);
-

The read function

-
1
2
3
4
5
6
7
8
9
istream & read(istream &is, Student_info &s)
{
// reads and store the student's name, midterm and final exam grades
is >> s.name >> s.midterm >> s.final;

// reads and store all homework grades
read_hw(is, s.homework);
return is;
}
-

Since the function is similar to the read_hw function defined in this page, no more discussed here.

-

grade function

Now the data have been stored into a sturct and concequently the grade function becomes
overloaded function 1

-
1
2
3
4
double grade(const Student_info &s)
{
return grade(s.meterm, s.final, s.homework)
}
-

overloaded function 2

-
1
2
3
4
5
6
double grade(double midterm, double final, const vector<double> &hw)
{
if (hw.size() == 0)
throw domain_error("student has done no homework");
return grade(midterm, final, median(hw));
}
-

overloaded function 3

-
1
2
3
4
double grade(double midterm, double final, double homework)
{
return 0.2*midterm + 0.4*final + 0.4*homework;
}
-

stores all structs into a vector

Once the record finishes reading data for inputs, we can store it into a vector.

-
1
2
3
4
5
6
7
vector<Student_info> students;
Student_info record;

while(read(cin, record))
{
students.push_back(record);
}
-

alphabetize students

Up to now, we have finished the code for all computations and data reading. The next step is to sort the students in an alphabetical order according to students’ names. In previous chapters, we uses the standard algorithm sort to accomplish sorting the homework. It can also be used to sort the students, but before we apply it we need to learn how it works on the homework.

-

homework is a vector that contains all values of homework grades. The sort function compares objects in the vector using <. It is clear when using < to compare two numerical values but doesn’t works for the element type of a struct. Regarding to this case, the sort function provides an optional argument, a predicate, for us to define the ways to compare elements.

-

A predicate is a function that typically yields a true value of type bool. Let’s see how is it defined

-
1
2
3
4
bool compare(const Student_info &x, const Student_info &y)
{
return x.name < y.name;
}
-

The usage of the sort function is

-
1
sort(students.begin(), students.end, compare);
-

It means that the sort function will compare elements in the students only according to its member name rather than using < directly. As for the effect of < on strings, the expression is evaluated to be true if x.name is alphabetically ahead of y.name. Specifically, when compare two strings

-
    -
  1. the result is the result of comparing the first character at which the strings differ.
  2. -
  3. if all characters of one string equal to the corresponding characters of another string, then the shorter one is less than the longer one.
  4. -
-

align the final grade vertically

Now we deal with the third requirement. Each line of outputs is formed by s.name, a blank string, and the grade. The key to solve this problem is to write a blank string with appropriate length such that all lines have same total length ahead of the final grades while the total length depends on the longest name. The minimum number of spaces between a name and a grade is one. The process can be logically divided into three steps

-
    -
  1. find the longest name
  2. -
  3. calculate the total length ahead of the grade: the size of the longest name plus one(space).
  4. -
  5. Create a blank string for each line with length: the total length minus the size of each name.
  6. -
-

To find the longest name, we use another the max function defined in the header . The syntax is

-
1
max(el, e2);
-

It returns the larger one of two expressions which yield values of the same type. The comparison is similar to Exercise 3-4 strategy 2.

-

A complete program

Above steps show the core technicals that deals with three requirements mentioned at the begining. The new program built on struct and functions is presented as below

-
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
int main()
{
vector<Student_info> students;
Student_info record;
string::size_type maxlen = 0;

// read and store all the records, and find the length of the longest name
while(read(cin, record))
{
maxlen = max(maxlen, record.name.size());
students.push_back(record);
}

// alphabetize the records
sort(students.begin(), students.end(), compare);

// write each line of outpurs
for (vector<Student_info>::size_type i = 0; i != students.size(); ++i)
{
// write the name, blanks
cout << students[i].name << string(maxlen + 1 - students[i].name.size(), ' ');

// compute and write the final grade
try{
double final_grade = grade(students[i]);
streamsize prec = cout.precision();
cout << setprecision(3) << final_grade << setprecision(prec);
} catch(domain_error e){
cout << e.what();
}
cout << endl;
}
return 0;
}
-

In this program, we uses a new function what to write the diagnostic message if an exception is thrown. The catch clause named the diagnostic message as e, i.e. the object that contains the message. The message can be obtained from what().

-

Seperate compilation

Strictly speaking, above program is not a complete program as it doesn’t work when it is executed. Because we haven’t add the functions to be called in this program. It can be done by putting all stuff into a single file, which however may increase complexity and reduce readability. Alternatively, we can seperate the program into several files and compile these files seperately. In fact, we uses seperate compilation since the first program. For example, we can use IO class objects by means of including the header and declarations rather than defining the type in our programs. How this is done? Let’s write our own header files!

-

header file and source file

To support seperate compilation, C++ distinguishes declarations and definitions which allows muliple files sharing one definition. For example, if we want to seperate the median function from above program, we need to put its definition into a source file named median.cpp (depending on your c++ implementations), and put its declarations into a header file named median.h. By doing so, the median function is allowed to be accessed in programs as long as we include its header file like

-
1
#include "median.h"
-

The header file is enclosed by double quotes rather than angle brackets, which makes it distinct from the standard library headers. The source file is created as follows

-
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
// source file for median function 
#include <algorithm> // to get declaration of sort
#include <stdexcept> // to get declaration of domain_error
#include <vector> // to get declaration of vector
#include <median.h>

// declarations for names
using std::domain_error;
using std::vector;
using std::sort;

// compute the median of vector<double>
double median(vector<double> vec)
{
// get the size of the vector
typedef vector<double>::size_type vec_size;
vec_size size = vec.size();

// check whether the empty is empty
if (size == 0)
throw domain_error("median of an empty vector");

// sort the grades
sort(vec.begin(), vec.end());

// compute the median homework grade
vec_size mid = size/2;
return size % 2 == 0 ? (vec[mid] + vec[mid - 1])/2 : vec[mid];
}
-

Note this file includes all needed headers for the median function itself. It contains both the function declarations and definitions, which allows the compiler to check the consistency between the declarations and definitions. The header file can be written as

-
1
2
3
4
5
6
7
#ifndef GUARD_median_h
#define GUARD_median_h

#include<vector>
double median(std::vector<double>);

#endif
-

There are several new points here. First, the file include the needed header , and use std::vector instead of using std::vector. This is because we are not sure whether a user want a using declaration in their program as once we add using declaration, all programs that include this header file get a using std::vector. This has been also emphasized in C++ - Getting Started.
Second, #ifndef directive responsible for checking whether GUARD_median_h is defined. GUARD_median_h (aka. header guard) is the name of a preprocessor variable that has two status: defined or not defined. The #define directive takes the name and defines it as a preprocessor variable. ifndef is true if the preprocessor variable is undefined and the preprocessor will process following contents until encounter endif. If ifndef is false, subsquent attempts to include median.h will be overlooked to avoid multiple inclusion.

-

Reorganize the final grade program

The reminder of this post aims to reorganize the final grade program applying the technical, seperate compilation,introduced in this post. Let’s list all functions and data structures needed in this program.

-
    -
  1. read function to read students’ information.
  2. -
  3. read_hw function to read homework grades for each student.
  4. -
  5. compare function as an optional argument in sort.
  6. -
  7. three grade functions(overloaded) to compute the final grade.
  8. -
  9. median function to compute median of homework grades.
  10. -
  11. Beyond above functions, we also defined a data structure, Student_info to hold student’s information.
  12. -
-

Logically speaking, these entities can be divided into two groups:

-
    -
  • group 1 including 6, 1, 2, 3 deals with information
  • -
  • group 2 including 4, 5 deals with computation
  • -
-

Therefore, we can package two groups into two independent files seperately.

-

Group 1

Student_info.h

-
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
#ifndef GUARD_Student_info
#define GUARD_Student_info

// Student_info.header file
#include<iostream>
#include<string>
#include<vector>

struct Student_info{
std::string name;
double midterm, final;
std::vector<double> homework;
};

bool compare(const Student_info &, const Student_info &);
std::istream & read(std::istream &, Student_info &);
std::istream & read_hw(std::istream &, std::vector<double> &);
#endif
-

Student_info.cpp

-
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
// source file for Student_info related functions
#include "Student_info.h"
using std::vector; using std::istream;

bool compare(const Student_info &x, const Student_info &y)
{
return x.name < y.name;
}

istream & read(istream &is, Student_info &s)
{
// reads and store the student's name, midterm and final exam grades
is >> s.name >> s.midterm >> s.final;

// reads and store all homework grades
read_hw(is, s.homework);
return is;
}

istream & read_hw(istream &in, vector<double> &hw)
{
if (in){
//get rid of previous contents
hw.clear();

// read homework grades
double x;
while(in >> x)
hw.push_back(x);

// clear the stream so that input will work for the next student
in.clear();
}
return in;
}
-

Group 2

grade.h

-
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
#ifndef GUARD_grade_h
#define GUARD_grade_h

// grade.h
#include<vector>
#include "Student_info.h"

double grade(const Student_info &);
double grade(double, double, const std::vector<double> &);
double grade(double, double, double);
double median(std::vector<double>);
#endif

**grade.cpp**
```c++
#include <algorithm>
#include <stdexcept>
#include <vector>
#include "grade.h"
#include "student_info.h"

using std::domain_error; using std::istream;
using std::vector; using std::sort;

// grade function 1
double grade(const Student_info &s)
{
return grade(s.midterm, s.final, s.homework);
}

// grade function 2
double grade(double midterm, double final, const vector<double> &hw)
{
if (hw.size() == 0)
throw domain_error("student has done no homework");
return grade(midterm, final, median(hw));
}

// grade function 3
double grade(double midterm, double final, double homework)
{
return 0.2*midterm + 0.4*final + 0.4*homework;
}

// compute the median of vector<double>
double median(vector<double> vec)
{
// get the size of the vector
typedef vector<double>::size_type vec_size;
vec_size size = vec.size();

// check whether the empty is empty
if (size == 0)
throw domain_error("median of an empty vector");

// sort the grades
sort(vec.begin(), vec.end());

// compute the median homework grade
vec_size mid = size/2;
return size % 2 == 0 ? (vec[mid] + vec[mid - 1])/2 : vec[mid];
}
-

Test

1
2
3
4
5
6
7
8
9
10
11
12
13
Inputs:

Robin 90 87 79 88 81 73 45
Brendan 70 69 88 100 91 75 66
Arsenii 99 87 89 88 74 90 70
Liam 83 66 100 76 87 91 78

Outputs:

Arsenii 89.8
Brendan 76.8
Liam 77.8
Robin 84.4
-
- - - - - - -
- -
- - - - -
- - - - -
- - -
- - - - -
- - - - - - - - -
-
- - -
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/2018/03/09/Accelerated-C-Solutions-to-Exercises-Chapter-4/index.html b/2018/03/09/Accelerated-C-Solutions-to-Exercises-Chapter-4/index.html deleted file mode 100644 index fcfb44bd..00000000 --- a/2018/03/09/Accelerated-C-Solutions-to-Exercises-Chapter-4/index.html +++ /dev/null @@ -1,622 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - Accelerated C++ Solutions to Exercises(Chapter 4 Part 1) | Liam's Blog - - - - - - - - - - - - -
-
- -
-
- - -
- - -

If history repeats itself, and the unexpected always happens, how incapable must Man be of learning from experience?—George Bernard Shaw.

-
- - -
- - - - -
-
- - -
- - 0% -
- - - - -
-
-
- - -
- - -
- - - -
- - - - - -
-

- Accelerated C++ Solutions to Exercises(Chapter 4 Part 1) -

- - -
- - - - -
- - -

Exercise 4-0

Compile, execute, and test the programs in this chapter

-

Solution & Results

This exercise has been done presented in Organizing programs with functions and Organizing programs with data structures.

-
-

Exercise 4-1

We noted in §4.2.3/65 that it is essential that the argument types in a call to maxmatch exactly. Will the following code work? If there is a problem, how would you fix it?

-
1
2
3
int maxlen;
Student_info s;
max(s.name.size(), maxlen);
- -

Solution & Results

It doesn’t work. There exist two problems here.

-

First, the max function defined in standard header requires that both arguments must have the same type. But in this piece of code, s.name.size() has the type of string::size_type while maxlen is a int type. To fix this, we need to define maxlen as a variable of type string::size_type.

-

Second, it should be initialized as a variable of built-in type(assuming no problem with type int) is undefined if it is not initialized explicitly. For a variable of other types, the default value (if available, otherwise the variable is undefined) depends on how each type defines. I did a simple experiment as follows

-
1
2
3
4
5
6
7
8
9
10
11
#include<iostream>
#include<string>

using std::cout;
using std::string;

int main(){
string::size_type y;
cout << y;
return 0;
}
-

The result is

-
1
4200939
-

with warning

-
1
'y' is used uninitialized in this function
-

The experiment shows that the type of string::size_type doesn’t support default initialization. To fixed this, we need to explicitly initialize it.

-

The correct code should be:

-
1
2
3
string::size_type maxlen = 0;
Student_info s;
max(s.name.size(), maxlen);
-
-

Exercise 4-2, 4-3

4-2: Write a program to calculate the squares of int values up to 100. The program should write two columns: The first lists the value; the second contains the square of that value.Use setw to manage the output so that the values line up in columns.

-

4-3: What happens if we rewrite the previous program to allow values up to but not including 1000 but neglect to change the arguments to setw? Rewrite the program to be more robust in the face of changes that allow i to grow without adjusting the setw arguments.

-

Solution & Result

algorithms

Exercise 4-3 is a generalized version of 4-2. Specifically, the program is required to write two colomns as follows

-
1
2
3
4
5
6
7
8
9
  0        0
1 1
2 4
3 9
... ...
99 9801
100 10000
... ...
n n*n
-

Each line contains an integer value followed by the square of the integer. The range of integers in the first column starts from 0 to 1000 (excluded). Most importantly, each line should be formated such that the values line up in columns.

-

The key to the solution is to find the longest number in each column. Then, we can set the width of the first column as the number of digits of the corresponding longest number. Analogously, the width of the second column will be set as the number of digits of the longest number in it, with an additional space to seperate from the first column.

-

For an ascending sequence, the longest numbers in both columns depend on the largest number. For exercise 4-2, the largest number is 100 and 10000 (square of 100) in the first and second column, respectively. We can write and format each line as below shows

-
1
2
3
4
5
for (int i = 0; i != 101; ++i)
{
// 100 has three digits and 10000 has 5 digits, 1 additional space for seperate two columns
cout << setw(3) << i << setw(6) << i*i << endl;
}
-

Exercise 4-3 requires more flexibility such that no needs to change setw arguments when the largest number changes. Naturally, the key is to compute the number of digits of a user-defined largest number, e,g. defined as maxNum.

-
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
// hold the number of digits of maxNum and its square
int n = 0;
int m = 0;

// hold the values of the largest number and its square
int j = maxNum;
int k = maxNum*maxNum;
// bounds check
if (j >= 1000 || j < 0)
{
cout << "Enter a number greater than or equal to 0 and less than 1000. Please try again ";

return 1;
}

// computations
if (j == 0)
{
m = n = 1;
}
else
{
// loop invariant: we have counted n digits of the value j
while (j != 0)
{
// each operation j reduces one digit
j /= 10;

// maintain the loop invariant
++n;
}

// loop invariant: we have counted n digits of the value j
while (k != 0)
{
k /= 10;
++m;
}
}
-

organize the program with functions

Obviously, above code is partly repeated. I’ll rewrite the computations as a function which returns the number of digits of an entered value.
width.cpp

-
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
// function to compute the number of digits of an integer value
#include "width.h"

int width(int num)
{
if (num == 0)
return 1;
else
{
int n = 0;
while(num != 0)
{
num /= 10;
++n;
}
return n;
}
}
-

width.h

-
1
2
3
4
5
#ifndef GUARD_width_h
#define GUARD_width_h

int width(int);
#endif
-

mainfunction.cpp

-
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
#include <iostream>
#include <iomanip>
#include "width.h"

using std::cin; using std::cout;
using std::endl; using std::setw;

int main()
{
// asks to enter the upper bound
cout << "Enter a number greater than or equal to 0 and less than 1000: ";

// read the largest number
int maxNum;
cin >> maxNum;

// bounds check
if (maxNum >= 1000 || maxNum < 0)
{
cout << "The entered value is beyond the allowed value range. Please try again.";
return 1;
}

// write the outputs
for (int i = 0; i != maxNum + 1; ++i)
{
cout << setw(width(maxNum)) << i
<< setw(width(maxNum*maxNum) + 1) << i*i << endl;
}
return 0;
}
-

Test performance

Test 1: the upper limit is 10

-
1
2
3
4
5
6
7
8
9
10
11
12
Enter a number greater than or equal to 0 and less than 1000: 10
0 0
1 1
2 4
3 9
4 16
5 25
6 36
7 49
8 64
9 81
10 100
-

Test 2: the upper limit is 101

-
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
Enter a number greater than or equal to 0 and less than 1000: 101
0 0
1 1
2 4
3 9
.. ..
90 8100
91 8281
92 8464
93 8649
94 8836
95 9025
96 9216
97 9409
98 9604
99 9801
100 10000
101 10201
-

Test 3: the upper limit is 1000

-
1
2
Enter a number greater than or equal to 0 and less than 1000: 1000
The entered value is beyond the allowed value range. Please try again.
-

It can be seen from these tests that the program perform as expected.

-
-

Exercise 4-4

Now change your squares program to use double values instead of ints. Use manipulators to manage the output so that the values line up in columns

-

Solution & Results

The difference between this exercise and last exercise is that the function width defined above can not compute the number of digits of a double value. Admittedly, I didn’t found very good strategy to complete this project. I circument the problem applying the type conversion technique. Specifically

-
    -
  1. divide the maxNum/maxNum*maxNum into integer part and fractional part.
  2. -
  3. compute the number of digits of the integer part using same function as shown above.
  4. -
  5. control the fractional part with an additional user-defined variable places which represents the number of places after the dot point.
  6. -
-

The arguments of *setw for the first column is:

-
1
width(maxNum) + 1 + places
-

The number 1 leaves room for the decimal point.

-

The arguments of *setw for the first column is:

-
1
width(maxNum*maxNum) + 1 + 1 + places
-

One of two values of 1 is added for the decimal point while the other one is added for seperating from the first column. I didn’t change anything in the width function described above and hence the arguments to be passed are converted to int type, leading to that the returned value of width is the width of the part before the decimal point.

-

Beyond this, the program asks to enter an initial value and an value of the increment for numbers of the first column. For example, we set the initial value as 0.0 and increment as 0.5, the first column becomes

-
1
2
3
4
5
0.0
0.5
1.0
...
n
-

where n is the largest number (i.e. maxNum) that available for outputs in the range of [0.0, 1000). It will be computed in the program.

-

To format outputs, I uses function fixed together with setprecision to fixed number of decimal places and showpoint to enable the display of trailing 0. A detailed comparison between these three can be found in C++ - Working with batches of data.

-

Please find the midth.cpp and midth.h in exercise 4-2/3. The main function file is shown below.

-
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
// Accelerated C++ Solutions Exercises 4-4
#include <iostream>
#include <iomanip>
#include "width.h"

using std::cin; using std::endl;
using std::cout; using std::setw;
using std::setprecision; using std::fixed;
using std::streamsize; using std::showpoint;
using std::noshowpoint;

int main()
{
// asks toset the range for outputs
double minNum, incrementByValue;
cout << "Enter an intial value greater than or equal to 0.0 and less than 1000.0: ";
cin >> minNum;
cout <<"Enter the increment for each line of outputs: ";
cin >> incrementByValue;

// bounds check
if (minNum >= 1000.0 || minNum < 0.0)
{
cout << "The entered value is beyond the allowed value range. Please try again.";
return 1;
}

// asks to enter a value that determines decimal places
cout << "How many places you want to keep after decimal point?\n"
"Enter an integer to determine decimal places: ";
int places;
cin >> places;

// get the largest value that available for outputs
double maxNum = minNum;
while ((maxNum + incrementByValue) < 1000.0)
{
maxNum += incrementByValue;
}

// write the outputs
for (double i = minNum; i < 1000.0; i += incrementByValue)
{
streamsize prec = cout.precision();
cout << showpoint << fixed << setprecision(places)
<< setw(width(maxNum) + places + 1) << i
<< setw(width(maxNum*maxNum) + places + 2) << i*i
<< setprecision(prec) << noshowpoint << endl;
}
return 0;
}
-

It is worth noting that we cannot use equality and inequality operators in the floating point value conditions as floating values cannot be precisely represented in the computer world. Due to this limitation, I change the condition i != 1000 to i < 1000.0. I did several tests to show how is the performance.

-

Test 1

-
1
2
3
4
5
6
7
8
9
10
11
12
13
14
Enter an intial value greater than or equal to 0.0 and less than 1000.0: 0
Enter the increment for each line of outputs: 100
How many places you want to keep after decimal point?
Enter an integer to determine decimal places: 0
0. 0.
100. 10000.
200. 40000.
300. 90000.
400. 160000.
500. 250000.
600. 360000.
700. 490000.
800. 640000.
900. 810000.
-

Test 2

-
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
Enter an intial value greater than or equal to 0.0 and less than 1000.0: 0.5234
Enter the increment for each line of outputs: 0.5
How many places you want to keep after decimal point?
Enter an integer to determine decimal places: 4
0.5234 0.2739
1.0234 1.0473
1.5234 2.3207
2.0234 4.0941
2.5234 6.3675
3.0234 9.1409
3.5234 12.4143
4.0234 16.1877
4.5234 20.4611
5.0234 25.2345
5.5234 30.5079
6.0234 36.2813
6.5234 42.5547
.... ....
996.0234 992062.6133
996.5234 993058.8867
997.0234 994055.6601
997.5234 995052.9335
998.0234 996050.7069
998.5234 997048.9803
999.0234 998047.7537
999.5234 999047.0271
- -

Test 3

-
1
2
3
4
5
6
Enter an intial value greater than or equal to 0.0 and less than 1000.0: 999
Enter the increment for each line of outputs: 1
How many places you want to keep after decimal point?
Enter an integer to determine decimal places: 1
999.0 998001.0
```
- -
-

Exercise 4-5

Write a function that reads words from an input stream and stores them in a vector. Use that function both to write programs that count the number of words in the input, and to count how many times each word occurred.

-

Solution & Results

This exercise is a variant of Chapter 3 Exercise 3-3. I uses exactly the same solution strategy in this project. Therefore, no more discussion here. The code and tests can be found below.

-

mainfunction.cpp

-
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
#include <iostream>
#include <string>
#include <vector>
#include <iomanip>
#include <stdexcept>
#include "wordsRead.h"

using std::cin; using std::string;
using std::cout; using std::vector;
using std::endl; using std::setw;
using std::domain_error;

int main()
{
// hold the information of each words
vector<words_info> words;

// type alias
typedef vector<words_info>::size_type vec_size;
typedef string::size_type str_size;
str_size word_size = 0;

// count the number of words
int totalNum = 0;

// asks to enter words
cout << "Please enter words: ";


try{
// read, count and store words
wordsRead(cin, words, totalNum, word_size);

// write the total number of inputs
cout << "The total number of words is: " << totalNum << endl;

// write each distinct word and its occurrence number
for (vec_size i = 0; i != words.size(); ++i)
{
// format each line
int n = 9 + word_size - words[i].wordName.size();
cout << words[i].wordName << setw(n) << " appears "
<< words[i].count << " times"<< endl;
}

}catch(domain_error){
cout << "You must enter at least one word. Please try again.";
}
return 0;
}
- -

wordsRead.cpp

-
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
// function to read, count and store words
#include <iostream>
#include <string>
#include <vector>
#include <stdexcept>
#include "wordsRead.h"

using std::istream; using std::domain_error;
using std::string; using std::vector;

istream & wordsRead(istream &is, vector<words_info> &words, int &totalNum, string::size_type &word_size)
{
words_info word;

// loop invariant: we have read totalNum words now
while(is >> word.wordName)
{
// maintain the loop invariant
++totalNum;

// find the size of the longest word
if (word_size < word.wordName.size())
word_size = word.wordName.size();

// set flag to find each distinct word
// flag == true: the word is distinct
// flag == false: the word exists
bool flag = true;
for (vector<words_info>::size_type i = 0; i != words.size(); ++i)
{
// compare with previous distinct words
if (word.wordName == words[i].wordName)
{
++words[i].count;
flag = false;
}
}

// if the word has not been entered, store
if (flag == true)
{
word.count = 1;
words.push_back(word);
}

}

if (totalNum == 0)
throw domain_error("No input");
return is;
}
-

wordsRead.h

-
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
#ifndef GUARD_wordsRead_h
#define GUARD_wordsRead_h

#include <iostream>
#include <string>
#include <vector>

struct words_info{
std::string wordName;
int count;
};

std::istream & wordsRead(std::istream &, std::vector<words_info> &words, int &, std::string::size_type &);


#endif /* GUARD_wordsRead_h */
- -

Test Performance

Test 1

-
1
2
3
4
5
6
7
8
9
10
11
Please enter words: I am a good teacher and you are a good student
The total number of words is: 11
I appears 1 times
am appears 1 times
a appears 2 times
good appears 2 times
teacher appears 1 times
and appears 1 times
you appears 1 times
are appears 1 times
student appears 1 times
-

Test 2

-
1
2
3
4
5
6
7
8
9
10
11
12
13
Please enter words: what happens depends on the range of the values that the types permit
The total number of words is: 13
what appears 1 times
happens appears 1 times
depends appears 1 times
on appears 1 times
the appears 3 times
range appears 1 times
of appears 1 times
values appears 1 times
that appears 1 times
types appears 1 times
permit appears 1 times
- -
-

To be continued.

- -
- - - - - - -
- -
- - - - -
- - - - -
- - -
- - - - -
- - - - - - - - -
-
- - -
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/2018/03/11/Accelerated-C-Solutions-to-Exercises-Chapter-4-Part-2/index.html b/2018/03/11/Accelerated-C-Solutions-to-Exercises-Chapter-4-Part-2/index.html deleted file mode 100644 index d782179d..00000000 --- a/2018/03/11/Accelerated-C-Solutions-to-Exercises-Chapter-4-Part-2/index.html +++ /dev/null @@ -1,681 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - Accelerated C++ Solutions to Exercises(Chapter 4 Part 2) | Liam's Blog - - - - - - - - - - - - -
-
- -
-
- - -
- - -

If history repeats itself, and the unexpected always happens, how incapable must Man be of learning from experience?—George Bernard Shaw.

-
- - -
- - - - -
-
- - -
- - 0% -
- - - - -
-
-
- - -
- - -
- - - -
- - - - - -
-

- Accelerated C++ Solutions to Exercises(Chapter 4 Part 2) -

- - -
- - - - -
- - -

Exercise 4-6

Rewrite the Student_info structure to calculate the grades immediately and store only the final grade.

-

Solution & Results

I didn’t understand very well about the original program when I went through it first time. Now I try to rewrite it in this program with detailed explinations on each step. The strategy can be divided into three parts:

-
    -
  1. analyse the requirements
  2. -
  3. break the ultimate goal into smaller ones that are logically connected
  4. -
  5. design algorithms to accomplish small goals
  6. -
  7. logically verify the correctness of each part and put all together
  8. -
  9. test the performance
  10. -
-

Requirements interpretation

What we have?

The program asks to enter a file that contains multiple students’ information including name, midterm exam grade, final exam grades and a sequence of homework grades. The example below shows an input sample

-
1
2
3
4
Robin 90 87 79 88 81 73 45 
Brendan 70 69 88 100 91 75 66
Arsenii 99 87 89 88 74 90 70
Liam 83 66 100 76 87 91 78
-

Note that the number of students is unkown and the number of homework grades is unknown as well.

-

What we need to do?

The program is required to generate a final grade report for a class. Specifically

-
    -
  1. each line of the report contains the information of one student, including name and a final grade.
  2. -
  3. the final grade is the weighted average of midterm, final and homework grades, of which the mediter exam grade counts for 20%, the final exam grade counts for 40%, and the median homework grade counts for 40%.
  4. -
  5. each line of outputs follows an alphabetical order according to the names.
  6. -
  7. the final grades are vertically aligned.
  8. -
-

An output sample can be found here

-
1
2
3
4
Arsenii 89.8
Brendan 76.8
Liam 77.8
Robin 84.4
-

others info

We are required to define a data structure to hold each student’s information including name and his final grade. This can be finished immediately.

-
1
2
3
4
5
// a structure contains name and final grade
struct Student_info{
string name;
double grade;
};
-

It can be seen that Student_info in fact have all information of outputs. Therefore, once it is filled with correct information, the rest is to format the report to be written.

-

Decompose the program

Overall structure

Assuming that we have filled the information for one student(i.e. one object of Student_info), we need to store it for making preparations for generating the report. As the number of student is unknown, vector is flexible enough to hold each Student_info object. Thus, the overall structure of this program can be

-
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
int main()
{
// to hold the information for each student
vector<Student_info> students;

// one Student_info object to be filled
Student_info record;

// loop invariant: we have stored students.size() students already
while (// condition statement)
{
// put the recorded student into the vector students
students.push_back(record);
}

// write statements to format the report
}
-

What the condition for this while loop should be? The condition for us to store record is that it has been filled(i.e. both name and grade contains correct information). Therefore, the condition shoule be true if record is successfully filled, and should be false if the attempt is failure.

-

There are two members in an object of type Student_info, one is name and the other one is grade. name comes from supplied information while grade can be computed using the information. It is necessary to write a function that not only can read and deal with supplied information but also meet the requirments of being the condition of above while loop. Let’s declare this function

-
1
istream &read(istream &is, Student_info &s);
-

The first parameter is a reference that refers to the object of input stream (i.e. istream). The second parameter is also a reference that refers to record. The reason to pass arguments by reference rather than value is that:

-
    -
  1. the function needs to return a value that shows whether the reading attempt is successful. It is the pre-condition for filling record.
  2. -
  3. the istream objects can’t be copied.
  4. -
  5. the function needs to return the filled record.
  6. -
  7. By passing by reference, we can avoid seting two return values.
  8. -
-

As the condition of the while loop, read function returns an istream type object which is evaluated to be true if it is valid and otherwise it is evaluted to be false. Therefore, read function should return a valid istream object after finishing filling the record, and return an invalid istream object after finishing filling all records.

-

Fill in the information

Let’s firstly consider how to fill the information for on student. The name can be stored directly into s.name. To hold midterm and final grades, we define two double type variables while to hold homework grades we define a variable of type vector.

-
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
istream & read(istream &is, Student_info &s)
{
// reads and store the student's name, midterm and final exam grades
double midterm, final;
is >> s.name >> midterm >> final;

// reads and store all homework grades
vector<double> hw;

// call functions that store all homework grades
// call functions that compute the final grade

// return a valid is if both name and grade contain correct information
return is;
}
- -

To compute the final grade, what we lack is the median value of homework grades. But before we can compute the median grade, we need to store all possible homework grades using a function similar to read.

-
1
2
3
4
5
6
7
8
9
10
11
istream & read_hw(istream &in, vector<double> &hw)
{
// read homework grades
double x;

// loop invariant: we have read hw.size() homework grades
while(in >> x)
hw.push_back(x);

return in;
}
-

This function is ok if there is only one student but doesn’t work for the input file shown above. When it finishes reading all homework grades of the first student, it terminates because that it encounters next student’s name rather than a double value, leading to the input stream being failure state. Therefore, we can add in.clear() before return in to reset the error state to good.

-
1
2
// clear the stream so that input will work for the next student
in.clear();
- -

Computations

Once we obtained the homework grades, we can compute the median value and then the final grade. The algorithms for these computations are not complicated. The function below will be called in the read function.

-
1
2
3
4
5
6
double grade(double midterm, double final, const vector<double> &hw)
{
if (hw.size() == 0)
throw domain_error("student has done no homework");
return grade(midterm, final, median(hw));
}
-

In this function, two functions are called. One is the median(hw) function which returns the median value, and anther one is overloaded grade function that returns the final grade which is again returned to the read function. Two functions are presented as follows

-
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
// function to calculate median
double median(vector<double> vec)
{
// get the size of the vector
typedef vector<double>::size_type vec_size;
vec_size size = vec.size();

// check whether the empty is empty
if (size == 0)
throw domain_error("median of an empty vector");

// sort the grades
sort(vec.begin(), vec.end());

// compute the median homework grade
vec_size mid = size/2;
return size % 2 == 0 ? (vec[mid] + vec[mid - 1])/2 : vec[mid];
}
-
1
2
3
4
5
// function to calculate final grade
double grade(double midterm, double final, double homework)
{
return 0.2*midterm + 0.4*final + 0.4*homework;
}
- -

How does eof works?

Now, we can complete the read function and the while loop. Noting that I add throw statement after finishing reading the homework grades. Correspondingly, I add the try block to catch any exception that might be thrown when reading students’ information.

-
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
istream & read(istream &is, Student_info &s)
{
// reads and store the student's name, midterm and final exam grades
double midterm, final;
is >> s.name >> midterm >> final;

// reads and store all homework grades
vector<double> hw;
read_hw(is, hw);
if(hw.empty())
throw domain_error("student has done no homework");

// get the value of final grade
s.grade = grade(midterm, final, hw);

// return a valid is if both name and grade contain correct information
return is;
}
-
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
int main()
{
// to hold the information for each student
vector<Student_info> students;

// one Student_info object to be filled
Student_info record;

// loop invariant: we have stored students.size() students already
try{
while(read(cin, record))
{
maxlen = max(maxlen, record.name.size());
students.push_back(record);
}
}catch(domain_error e){
cout << e.what() << endl;
}

// write statements to format the report
}
-

Theoretically, we have finished most parts of the program. The next step is to format outputs if above functions work fine. Let’s logically verify this part with following steps

-
    -
  1. when the computer executes the while loop, the condition is evaluated.
  2. -
  3. the computer enter into the read function. The function stores name, midterm and final, and create an empty object hw.
  4. -
  5. then the computer enters into the read_hw function. This function creates an object x and starts another while loop.
  6. -
  7. the condition in >> x is evaluated. In most cases, the condition is true until it encounters next student’s name. As a result, the input stream is changed to failure state. By then, it has stored all homework grades of the first student into the vector hw.
  8. -
  9. the state of in is reset to be good by using in.clear().
  10. -
  11. in is returned and the implementation goes back to the function read.
  12. -
  13. grade function is called. Inside this function, another grade function is called and median function is called.
  14. -
  15. s.grade is obtained, and is is returned. By then, record contains correct information of the first student.
  16. -
  17. step 1 continues, is is evaluated to be true which indicates that record is filled successfully.
  18. -
  19. the computer moves to the while body and record is stored into students. By then, both the name and final grade of the first student is available for us.
  20. -
  21. the while loop starts over again from step 1 - 10.
  22. -
-

Yeah, it works fine by now. However, how to stop it? Typically we can stop a while loop by sending a signal eof to set the input stream being a failure state. Let’s type Ctrl+Z/D and see how it works.

-
    -
  1. same as above
  2. -
  3. same as above. Though name, midterm are uninitialized, the implementation would’t stop at this position.
  4. -
  5. same as above
  6. -
  7. the condition in >> x is evaluated to be false due to the eof
  8. -
  9. same as above. By then, hw is still empty.
  10. -
  11. same as above
  12. -
  13. grade function is called. An exception is thrown due to the fact of step 5.
  14. -
  15. the program fails
  16. -
-

So, what’s the problem? The immediate cause is the empty hw. We actually even don’t want to create the hw since we want to terminate the while loop immediately. Therefore, we need to add a condition like follow code shows

-
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
istream & read(istream &is, Student_info &s)
{
// reads and store the student's name, midterm and final exam grades
double midterm, final;
is >> s.name >> midterm >> final;

// to check whether eof is sent
if(is)
{
// reads and store all homework grades
vector<double> hw;
read_hw(is, hw);
if(hw.empty())
throw domain_error("student has done no homework");

// get the value of final grade
s.grade = grade(midterm, final, hw);

// return a valid is if both name and grade contain correct information
}
return is;
}
-

Why don’t add the if condition at the very begining of the read function? It is because that the input stream hasn’t start to read anything at that position. Now we recheck each step after sending eof signal.

-
    -
  1. same as above
  2. -
  3. midterm and final is created.
  4. -
  5. eof is read and the state of is is set to be failure.
  6. -
  7. condition is is evaluated to be false
  8. -
  9. return is. The computer goes back the step 1.
  10. -
  11. the condition in step 1 is evaluated to be false, while loop stops.
  12. -
-

Now, this part of code should work as expected.

-

Formatting the outputs

The next is to format each line of outputs according to requirements analysed above.
To alphabetize the outputs, we can use following code

-
1
sort(students.begin(), students.end(), compare);
-

where compare is defined as follows

-
1
2
3
4
bool compare(const Student_info &x, const Student_info &y)
{
return x.name < y.name;
}
- -

To line up the final grades, we need to find the longest name and hence add one statement in the first while loop. maxlen will finally record the length of the longest name.

-
1
maxlen = max(maxlen, record.name.size());
-

The final grade can be obtained via

-
1
double final_grade = students[i].grade;
- -

####

-

Put all together

Following gives each file of the program. No more discussion here.
mainfunction.cpp

-
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
#include <algorithm>
#include <iomanip>
#include <ios>
#include <iostream>
#include <stdexcept>
#include <string>
#include <vector>
#include "Student_info.h"
#include "grade.h"

using std::cin; using std::setprecision;
using std::cout; using std::sort;
using std::endl; using std::streamsize;
using std::domain_error; using std::string;
using std::max; using std::vector;

int main()
{
vector<Student_info> students;
Student_info record;
string::size_type maxlen = 0;

// read and store all the records, and find the length of the longest name
try{
while(read(cin, record))
{
maxlen = max(maxlen, record.name.size());
students.push_back(record);
}
}catch(domain_error e){
cout << e.what() << endl;
}

// alphabetize the records
sort(students.begin(), students.end(), compare);

// write each line of outpurs
for (vector<Student_info>::size_type i = 0; i != students.size(); ++i)
{
// write the name, blanks
cout << students[i].name << string(maxlen + 1 - students[i].name.size(), ' ');

// compute and write the final grade
double final_grade = students[i].grade;
streamsize prec = cout.precision();
cout << setprecision(3) << final_grade << setprecision(prec) << endl;
}
return 0;
}
-

Student_info.cpp

-
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
#include "Student_info.h"
#include "grade.h"
using std::vector; using std::istream; using std::cin; using std::cout;

// function to define the additional arguments in max
bool compare(const Student_info &x, const Student_info &y)
{
return x.name < y.name;
}

// function to read data for inputs and fill information
istream & read(istream &is, Student_info &s)
{
// reads and store the student's name, midterm and final exam grades
double midterm, final;
is >> s.name >> midterm >> final;
if (is)
{
// reads and store all homework grades
vector<double> hw;
read_hw(is, hw);
if(hw.empty())
throw domain_error("student has done no homework");

// get the final grade
s.grade = grade(midterm, final, hw);
}
return is;
}

// function to read homework grades
istream & read_hw(istream &in, vector<double> &hw)
{
// read homework grades
double x;
while(in >> x)
hw.push_back(x);

// clear the stream so that input will work for the next student
in.clear();

return in;
}
-

Student_info.h

-
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
#ifndef GUARD_Student_info
#define GUARD_Student_info

// Student_info.header file
#include<iostream>
#include<string>
#include<vector>

struct Student_info{
std::string name;
double grade;
};

bool compare(const Student_info &, const Student_info &);
std::istream & read(std::istream &, Student_info &);
std::istream & read_hw(std::istream &, std::vector<double> &);
#endif
-

grade.cpp

-
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
#include <algorithm>
#include <stdexcept>
#include <vector>
#include "grade.h"

using std::domain_error; using std::istream;
using std::vector; using std::sort;

// grade function 1
double grade(double midterm, double final, const vector<double> &hw)
{
if (hw.size() == 0)
throw domain_error("student has done no homework");
return grade(midterm, final, median(hw));
}

// grade function 2
double grade(double midterm, double final, double homework)
{
return 0.2*midterm + 0.4*final + 0.4*homework;
}

// compute the median of vector<double>
double median(vector<double> vec)
{
// get the size of the vector
typedef vector<double>::size_type vec_size;
vec_size size = vec.size();

// check whether the empty is empty
if (size == 0)
throw domain_error("median of an empty vector");

// sort the grades
sort(vec.begin(), vec.end());

// compute the median homework grade
vec_size mid = size/2;
return size % 2 == 0 ? (vec[mid] + vec[mid - 1])/2 : vec[mid];
}
-

grade.h

-
1
2
3
4
5
6
7
8
9
10
#ifndef GUARD_grade_h
#define GUARD_grade_h

// grade.h
#include<vector>

double grade(double, double, const std::vector<double> &);
double grade(double, double, double);
double median(std::vector<double>);
#endif
- -

Performance test

1
2
3
4
5
6
7
8
9
10
11
12
13
Inputs

Robin 90 87 79 88 81 73 45
Brendan 70 69 88 100 91 75 66
Arsenii 99 87 89 88 74 90 70
Liam 83 66 100 76 87 91 78

Outputs:

Arsenii 89.8
Brendan 76.8
Liam 77.8
Robin 84.4
-

It performs as same as the original program provided in Chapter 4.

-
-

Exercise 4-7

Write a program to calculate the average of the numbers stored in a vector.

-

Solution & Results

It’s a simple project and the solution can be divided into three steps

-
    -
  1. define a function to read and store data into a vector.
  2. -
  3. define a function to compute the average value.
  4. -
  5. set precision to format the double value of outputs.
  6. -
-

All details can be found in other exercises and hence no more explinations here.

-

Below shos the only file including both functions and main function.

-
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
#include <iostream>
#include <vector>
#include <stdexcept>
#include <iomanip>

using std::cin; using std::cout;
using std::endl; using std::domain_error;
using std::vector; using std::istream;
using std::streamsize; using std::setprecision;

// define function to read data into a vector
istream &read(istream &is, vector<double> &doubleValues)
{
double number;
while(is >> number)
doubleValues.push_back(number);
return is;

}

// define function to compute the average of a data sequence
double average(const vector<double> &doubleValues)
{
if (doubleValues.size() == 0)
throw domain_error("An empty vector");
typedef vector<double>::size_type vec_size;
double sum = 0;
for (vec_size i = 0; i != doubleValues.size(); ++i)
sum += doubleValues[i];
return sum/doubleValues.size();
}

int main()
{
vector<double> doubleValues;
read(cin, doubleValues);
try{
double averageValue = average(doubleValues);
streamsize prec = cout.precision();
cout << setprecision(3) << averageValue
<< setprecision (prec) << endl;
}catch(domain_error){
cout << "You must enter at least 1 number. Pleast try again.";
}
return 0;
}
-

Test

-
1
2
3
4
5
Input: 
1.2 5.89 9.33 12.7 7.8 4

Output:
6.82
- -
-

Exercise 4-8

If the following code is legal, what can we infer about the return type of f?

-
1
double d = f()[n];
-

Solution & Results

From the code, we can infer

-
    -
  1. f()[n] is possible a value of type int or float or double or bool.
  2. -
  3. f() supports accessing the element using subscript and hence it could be a container.
  4. -
  5. f() has the form of a function.
  6. -
-

According to above analysis, f() could be a function that returns a container which can hold values of types listed above. As far as I know, the container is vector and returns a type of vector(inside types including int, float, double and bool).

-

To verify my analysis, I wrote a simple program shown as below

-
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>
#include <vector>

using std::cout;
using std::cin;
using std::vector;
using std::endl;

// funtion to return a vector<double>
vector<double> f(vector<double> number)
{
number.push_back(11);
number.push_back(20.5);
return number;
}

int main(){
vector<double> number;
double x = f(number)[0];
double y = f(number)[1];
cout << x << endl;
cout << y << endl;
return 0;
}
-

It works as expected and gives results

-
1
2
11
20.5
-

If I change the type double to any of types listed above, the program works fine.

-
-

Reference

Koenig, A. and Moo, B.E., 2000. Accelerated C++.

-

Lippman, S.B., Lajoie, J. and Moo, B.E., 2013. C++ Primer, 5th edt.

- -
- - - - - - -
- -
- - - - -
- - - - -
- - -
- - - - -
- - - - - - - - -
-
- - -
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/2018/03/13/C-Sequential-Containers/index.html b/2018/03/13/C-Sequential-Containers/index.html deleted file mode 100644 index 487eec52..00000000 --- a/2018/03/13/C-Sequential-Containers/index.html +++ /dev/null @@ -1,685 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - C++ - Sequential Containers (Part 1) | Liam's Blog - - - - - - - - - - - - -
-
- -
-
- - -
- - -

If history repeats itself, and the unexpected always happens, how incapable must Man be of learning from experience?—George Bernard Shaw.

-
- - -
- - - - -
-
- - -
- - 0% -
- - - - -
-
-
- - -
- - -
- - - -
- - - - - -
-

- C++ - Sequential Containers (Part 1) -

- - -
- - - - -
- - -

Recalling the student-grading program in Organizing programs with data structures, and thinking about how to separate students into two categories, passed and failed according to their final grades. Let’s define pass/fail criteria in final grades.

-
1
2
3
4
5
// predicate to determine whether a student failed
bool fgrade(const Student_info &s)
{
return grade(s) < 60;
}
-

If the computed grade is less than 60, it is a failing grade and the predicate yields a true value.

-

vector

An indices approach

The next is to examine each element in students based on the function fgrade, and store the records for the students who failed and who passed seperately.

-
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
vector<Student_info> extract_fails(vector<Student_info> &students)
{
vector<Student_info> pass, fail;

for (vector<Student_info>::size_type i = 0;
i != students.size(); ++i)
{
if (fgrade(students[i]))
fail.push_back(students[i]);
else
pass.push_back(students[i]);
}
students = pass;
return fail;
}
-

This function returns a vector named fail which contains the records for students who failed, and changes the original students to a vector which contains only the records for students who passed. It can be seen that the vector pass exists temporarily and seems to be superfluous from the perspective of the limited memory.

-

Alternatively, we can remove the pass and operate on the original student directly. The member function erase defined in the class template vector allows users to remove elements, either a single or a range, from a vector. Now let’s see how to change above code using erase.

-
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
vector<Student_info> extract_fails(vector<Student_info> &students)
{
vector<Student_info> fail;
vector<Student_info>::size_type i = 0;

// loop invariant: [0, i) of students represent passing grades
while(i != students.size())
{
if(fgrade(students[i]))
{
fail.pushback(students[i]);
students.erase(students.begin() + i);
}
else
++i;
}
return fail;
}
-

This function only creates one vector to hold the records for students who failed. The logic is quite straightforward:

-
    -
  1. when the first iteration starts, i is 0 and students.size() yields the original length of the vector.
  2. -
  3. then, the while body is executed. If the ith student failed, the record will be stored into fail, and also be erased from the original vector. Noting that there are two side effects: first, the length of the vector decreases by 1; the index of all reminder elements move forward by 1. By then,the element students[1] in the original vector now becomes students[0] in the new vector as the original studnets[0] has been removed. Therefore, the examination starts from i = 0 again.
  4. -
  5. if the ith student passed, the record will be kept and the next loop examines the following element, i.e. students[i+1]. Therefore, i increases by 1 before the end of the current loop preparing for next loop.
  6. -
-

What’s new here is the arguments of erase function.

-
1
students.erase(students.begin() + i);
-

We have mentioned in previous chapters that students.begin() denotes the initial element (i.e. corresponding to index 0) in the vector students. Increasing by i, the arguments denotes the ith element. The reason to use students.begin() is that the parameter type defined in erase is const_iterator (this is discussed laster).

-

Due to the length of the vector is not fixed, the students.size() should be used in the condition as it always yields the current length when it is evaluated. If a precomputed size is used, e.g. using size defined as below to replace students.size(), students[i] may refer to nonexist elements or the loop becomes endless once any of elements is removed.

-
1
vector<Student_info>::size_type size = students.size();
- -

An iterators approach

It has been observed that each function defined above access elements from the vector in sequential order. Also it is known that indices allows us to access at random. From the perspective of the library, using indices has the same effect as that we request permissions to access elements randomly. In other words, indices privide the ability that we don’t want to use or we don’t need at all.

-

Beyond indices, C++ supports another mechanism iterators that we can use to access elements in a vector. Not all standard containers support indices, but all standard containers supports iterators.

-

iterators provide more flexibility in supporting certain operations depending on different types of containers.

-

In specific, an iterator is a value that

-
    -
  • identifies a container and an element in the container
  • -
  • let us examine the value stored in that element
  • -
  • provides operations for moving between elements in the container
  • -
  • restricts the available operations in ways that correspond to what the container can handle efficiently.
  • -
-

the syntax

If we use indices for the iteration, for example,

-
1
2
3
for (vector<Student_info>::size_type i = 0; 
i != students.size(); ++i)
cout << students[i].name << endl;
-

alternatively, we can use iterators also

-
1
2
for (vector<Student_info>::const_iterator iter = students.begin(); iter != students.end(); ++iter)
cout << (*iter).name << endl;
-

From above code, we know that the type of the iter is
vector::const_iterator. More general, vector defines two associated itertor types:

-
1
2
vector<type>::const_iterator
vector<type>::iterator
-

The difference is that iterators of type const_iterator have only read access. If we want to use an iterator to change values in a vector, we should define it using iterator type.

-

In addition, iter is initialized with the value of students.begin(). begin and end function return values that denotes the begining or one past the end of a container, respectively. Noting that students.begin() returns a value of type iterator but is converted to the type const_iterator in above code.

-

The next is to increase the value of iter using ++iter. The effect of the increment operator on iter is up to how the iterator type defines. As a result, the iterator denotes the next element in the container after this expression is executed.

-

The statement in the for body shows how to indirect access to the elements in students.

-
1
cout << (*iter).name;
-

The dereference operator () applies to *iter** and returns an lvalue (i.e. the element) to which the iterator refers.

-

Alternatively, we can dereference an iterator and fetch the element via

-
1
iter->name
-

It has same effect as (*iter).name.

-

rewrite functions using iterators

Now let’s use iterators to rewrite the extract_fails function.

-
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
vector<Student_info> extract_fails(vector<Student_info> &students)
{
vector<Student_info> fail;
vector<Student_info>::const_iterator iter = students.begin();

// loop invariant: [0, i) of students represent passing grades
while(iter != students.end())
{
if(fgrade(*iter))
{
fail.pushback(*iter);
iter = students.erase(iter);
}
else
++iter;
}
return fail;
}
-

As mentioned earlier, we pass iter directly to the erase function as the parameter has type of const_iterator.

-

Noting that calling erase function invalidates all iterators that refer to elements after the one that was just removed. But the erase function will return an iterator that is positioned on the element that follows the erased one. Therefore, the statement becomes

-
1
iter = students.erase(iter);
-

Iterators also support equility (=) and inequaility (!=) operations. Two iterators are equal if they denote the same element or they are off-the-end iterator for the same container.

-

Iterator arithmetic

All standard containers support above iterator operations. But iterators for vector and string also support additional operations as shown below.

-
    -
  1. iter +/n. Adding/substracting an integer n to an iterator yields an iterator that denotes the nth emelemt (if available) after/before the original element, or one past the end of the container.

    -
  2. -
  3. iter1 - iter2. Substracting two iterators yields an number when added to the right-hand operand yields the left operand. The iterators must denote elements within the container or one past the end of the container.

    -
  4. -
  5. >, >=, <, <=. One iterator is less than another if it denotes an element that appears before the element that denoted by the other iterator. Both iterators must denote elements within the container or one past the end of the container.

    -
  6. -
-

list

vector allows us to arbitrarily access elements efficiently. Moreover, it performs well when adding or deleting the element at the end of the vector. However, vector is not a good choice when it come to inserting or removing elements from the interior of the vector, such as functions illustrated above.

-

To deal with such problems, the standard library provides another data structure list for us to efficiently insert or delete elements anywhere in the container. On the other hand, list doesn’t support random access via indices. It only supports bidirectional sequential access via iterators.

-

The list type is defined in the standard header . Same as the vector, list is also a class template. In addition, list and vector share many operations. For example, we can use list to rewrite the function extract_fails.

-
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
// use list instead of vector
list<Student_info> extract_fails(list<Student_info> &students)
{
list<Student_info> fails;
list<Student_info>::iterator iter = students.begin();

while(iter != students.end())
{
if (fgrade(*iter))
{
fail.push_back(*iter);
iter = students.erase(iter);
}
else
++iter;
}
return fail;
}
-

The code above has no distinct difference comparing with the vector version. However, the important difference between lists and vectors is that how they affect on iterators.

-
    -
  1. For vectors, the erase operation invalidates all iterators that refer to elements including the one was just removed and the subsequent elements. It is because that when removing one element, all following elements move one position towards the one erased. The push_back operation invalidates all iterators due to the fact the entirely vector might be reallocated to a new area for holding the new element.

    -
  2. -
  3. For lists, the erase and push_back operations do not invalidate other iterators except the one that was erased.

    -
  4. -
-

Another difference is that the list type does’t support the standard sort function. It has its own member funciton sort which can be called as follows

-
1
2
list <Student_info> students;
students.sort(compare);
-

Taking strings apart

A string can be regarded as a special container that only contains characters. It supports many operations, such as random access through indices and iterators, similar to a vector. Therefore, we can access and operate on a specific character. In addition, the standard header defines a bunch of functions that can be used to deal with single character in a string. The table below gives the details of each function

- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
cctype functionsSource: Lippman etc. 2012, p.82
isalnum(c)true if c is a letter or a digit
isalpha(c)true if c is a letter
iscntrl(c)true if c is a control character
isdigit(c)true if c is a digit
isgraph(c)true if c is not a space but is printable
islower(c)true if c is a lowercase letter
isprint(c)true if c is a printable character
ispunct(c)true if c is a punctuation character
isspace(c)true if c is whitespace
isupper(c)true if c is an uppercase letter
isxdigit(c)true if c is a hexademical digit
tolower(c)if c is an uppercase letter, it returns the lowercase letter; otherwise returns c unchanged
toupper(c)if c is a lowercase letter, it returns the uppercase letter; otherwise returns c unchanged
-

For example, we can write a funciton to extract each word from a string like

-
1
The wealth of the mind is the only wealth
-

We can use a string member function substr to construct a new string object with its value initialized to a copy of a substring of the original string. substr defines two parameters, of which one is the inital position of the substring in the original string and the other one is the length of the substring. Therefore, the key to solve this project is to find the index of the begining of each word and the length. It can be observed that each word starts from a nonwhitespace character and ends with a character followed by whitespace. Assuming the first character of a word is positioned at indice i and the whitespace that closely follows the end of the word has indice j, the length of the word will be j - i (imagine the half-open range).

-

The solution is provided in the book and shown as below

-
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
vector<string> split(const string &s)
{
vector<string> ret;
typedef string::size_type string_size;
string_size i = 0;

// invariant: we have processed characters [original value of i, i)
while(i != s.size())
{
// ignore leading blanks
// invariant: characters in range [original i, current i) are all spaces
while(i != s.size() && isspace(s[i]))
++i;

// find end of the next word
string_size j = i;

// invariant: none of the characters in range [original j, current j)
while(j != s.size() && !isspace(s[j]))
++j;
// if we found some nonwhitespace characters
if (i != j)
{
// copy from s starting at i and taking j - i characters
ret.push_back(s.substr(i, j-i));
i = j;
}
}
return ret;
}
-

Noting that, when the function try to recognize the last word, the third while loop will stop because of j == s.size() even if !isspace(s[j]) is still true in the case that no whitespaces follows the last word. I add #include directives and using declarations, and test the program using the example string described above.

-
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
#include<iostream>
#include<vector>
#include<string>

using std::cout; using std::vector;
using std::endl; using std::string;

// function declaration
vector<string> split(const string &s);

int main()
{
// define a string contains words separated by whitespaces
string s{"The wealth of the mind is the only wealth"};

// define a vector to hold splitted words
vector<string> words = split(s);

// write each line that contains one word
for (vector<string>::size_type i = 0; i != words.size(); ++i)
cout << words[i] << endl;

return 0;
}

// Please define the split function here
-

Test results

-
1
2
3
4
5
6
7
8
9
The
wealth
of
the
mind
is
the
only
wealth
-
-

To be continued.

- -
- - - - - - -
- -
- - - - -
- - - - -
- - -
- - - - -
- - - - - - - - -
-
- - -
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/2018/03/14/Accelerated-C-Solutions-to-Exercises-Chapter-5/index.html b/2018/03/14/Accelerated-C-Solutions-to-Exercises-Chapter-5/index.html deleted file mode 100644 index 83a929ad..00000000 --- a/2018/03/14/Accelerated-C-Solutions-to-Exercises-Chapter-5/index.html +++ /dev/null @@ -1,608 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - Accelerated C++ Solutions to Exercises(Chapter 5 Part 1) | Liam's Blog - - - - - - - - - - - - -
-
- -
-
- - -
- - -

If history repeats itself, and the unexpected always happens, how incapable must Man be of learning from experience?—George Bernard Shaw.

-
- - -
- - - - -
-
- - -
- - 0% -
- - - - -
-
-
- - -
- - -
- - - -
- - - - - -
-

- Accelerated C++ Solutions to Exercises(Chapter 5 Part 1) -

- - -
- - - - -
- - -

Exercise 5-0

Compile, execute, and test the programs in this chapter

-

Solution & Results

Please find solutions and detailed analysis in Chapter 5 Sequential Containers .

-

Exercise 5-1

Design and implement a program to produce a permuted index. A permuted index is one in which each phrase is indexed by every word in the phrase. So, given the following input,

-
1
2
The quick brown fox 
jumped over the fence
-

the output would be

-
1
2
3
4
5
6
7
8
      The quick     brown fox 
jumped over the fence
The quick brown fox
jumped over the fence
jumped over the fence
The quick brown fox
jumped over the fence
The quick brown fox
- -

A good algorithm is suggested in The AWK Programming Language by Aho, Kernighan, and Weinberger (Addison-Wesley, 1988). That solution divides the problem into three steps:

-
    -
  1. Read each line of the input and generate a set of rotations of that line. Each rotation puts the next word of the input in the first position and rotates the previous first word to the end of the phrase. So the output of this phase for the first line of our input would be

    -
    1
    2
    3
    4
    The quick brown fox
    quick brown fox The
    brown fox The quick
    fox The quick brown
    -

    Of course, it will be important to know where the original phrase ends and where the rotated beginning begins.

    -
  2. -
  3. Sort the rotations.

    -
  4. -
  5. Unrotate and write the permuted index, which involves finding the separator, putting the phrase back together, and writing it properly formatted.

    -
  6. -
-

Solution & Results

program logic

The solution strategy provided above is quite straightforward. But before implementing such strategy, several stylized facts observed from the example should be listed.

-
    -
  1. the output can be divided into two groups.
  2. -
  3. the right group of lines contains the key words (i.e. the alphabetically indexed words). The key word can be any word of a phrase. Therefore, when the first word of a phrase is indexed, the right part contains a complete phrase.
  4. -
  5. the left part and right part of the same line are complementary each other, which leads to a complete phrase. When the right part contains a complete phrase, the left part contains nothing but spaces.
  6. -
-

As described above, we can split the first phrase as follows

-
1
2
3
4
                    The quick brown fox
The quick brown fox
The quick brown fox
The quick brown fox
-

Rather than generating rotations, I would like to split one phrase into all possible combinations. Each combination will be stored into a data structure defined as below.

-
1
2
3
4
struct line{
std::string left; // contains the left part of a phrase
std::string right;// contains the right part of a phrase
};
-

In addition, we can define a vector to hold all combinations (i.e. objects of type line), covering all phrases to be entered.

-
1
vector<line> combinations;
-

Once all combinations have been stored into the vector, we can sort the vector according to the value of the right part of each combination.

-
1
sort(combinations.begin(), combinations.end(), compare);
- -
1
2
3
4
bool compare(const line &x, const line &y)
{
return x.right < y.right;
}
-

By doing so, we have alphabetized all key words. The next is to format the left parts. The content of left part of each line has been fixed as the left part is bound with the right part. All left parts are lined up vertically on the right side while their left side are padded out with certain amount of spaces depending on the longest string of the left parts.

-

Assuming the length of the longest string is obtained and stored in maxlen, the left part of outputs for each line will be

-
1
string(maxlen - combinations[z].left.size(), ' ') + combinations[z].left
- -

It is worth noting that the permuted index is case-insensitive. Therefore, the strings that compared in the compare function should be converted to lower-case letters. In addition, a line of phrase is easily to be splitted if there is no extra spaces before or after the the line, or between words. However, a common case is that users enter more spaces than needed. Therefore, it is necessary to clean needless spaces before we split one line.

-

In summary, the program can be divided into five logic parts

-
    -
  1. read in multiple lines of inputs
  2. -
  3. clean each line for removing needless spaces
  4. -
  5. split each line into all possible combinations
  6. -
  7. store all combinations for all lines and get maxlen.
  8. -
  9. sort and print each line
  10. -
-

files

The main function is shown below

-

mainfunction.cpp

-
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
#include<iostream>      // to get declaration of cin, cout and endl
#include<string> // to get declaration of string and getline
#include<vector> // to get declaration of vector
#include<algorithm> // to get declaration of sort
#include "cleaning.h"
#include "line.h"

using std::cin; using std::vector;
using std::cout; using std::string;
using std::endl; using std::getline;
using std::sort;


int main()
{
// hold combinations
vector<line> combinations;

// hold the length of the longest string
string::size_type maxlen = 0;

// read in
string words;
while(getline(cin, words))
{
// split each line, store all combinations, record maxlen
if(!words.empty())
split(cleaning(words), combinations, maxlen);
}

// format the outputs
cout << endl;
sort(combinations.begin(), combinations.end(), compare);

// write each line of outputs: left part + 4 spaces + right part
for (vector<line>::size_type z = 0; z != combinations.size(); ++z)
cout << (string(maxlen - combinations[z].left.size(), ' ') + combinations[z].left)
<< " " << combinations[z].right << endl;
return 0;
}
- -

I put the split function and compare funtion into one file named line.

-

line.cpp

-
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
#include <algorithm>
#include <string>
#include <vector>
#include <cctype>
#include "line.h"

using std::string; using std::tolower;
using std::vector; using std::isupper;
using std::max;

// function to get rotations of a line and store all rotations into vector<line>
void split(string words, vector<line> &combs, string::size_type &maxlen)
{
// store the original lines
line originLine;
originLine.right = words;
originLine.left = "";
combs.push_back(originLine);
maxlen = max(maxlen, originLine.left.size());

// store the splitted lines
string::size_type i = 0;
while (i != words.size())
{
line rotateLine;
if (isspace(words[i]))
{
rotateLine.right = words.substr(i+1, words.size() - i);
rotateLine.left = words.substr(0, i);
combs.push_back(rotateLine);
maxlen = max(maxlen, rotateLine.left.size());
}
++i;
}
}

// change characters to lowercase letters
string tolowerString(string c)
{
for (string::iterator iter = c.begin(); iter != c.end(); ++iter)
{
if (isupper(*iter))
*iter = tolower(*iter);
}
return c;
}

// define the arguments for sort function
bool compare(const line &x, const line &y)
{
return tolowerString(x.right) < tolowerString(y.right);
}
- -

line.h

-
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
#ifndef GUARD_LINE_H
#define GUARD_LINE_H

#include <string>
#include <vector>

struct line{
std::string left;
std::string right;
};

void split(std::string words, std::vector<line> &combs, std::string::size_type &maxlen);
std::string tolowerString(std::string x);
bool compare(const line &x, const line &y);

#endif /* GUARD_LINE_H */
- -

The final part is the cleaning function that is used to remove extra spaces.

-

cleaning.cpp

-
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
// this function removes needless spaces for a sentence
#include <string>
#include <cctype>
#include "cleaning.h"

using std::string;
using std::isspace;

string cleaning(string words)
{
string::iterator iter = words.begin();

// remove spaces before the real sentence begins
while(isspace(*iter))
iter = words.erase(iter);

// remove extra spaces between two words
while((iter+1) != words.end())
{
if (isspace(*iter) && isspace(*(iter + 1)))
iter = words.erase(iter);
else
++iter;
}

// remove the trailing space
if(isspace(*iter))
words.erase(iter);
return words;
}
- -

cleaning.h

-
1
2
3
4
5
6
7
8
#ifndef GUARD_CLEANING_H
#define GUARD_CLEANING_H

#include <string>

std::string cleaning(std::string words);

#endif /* GUARD_CLEANING_H */
-

Test performance

Let’s test the required inputs

-
1
2
3
4
5
6
7
8
9
10
11
The quick brown fox 
jumped over the fence

The quick brown fox
jumped over the fence
The quick brown fox
jumped over the fence
jumped over the fence
The quick brown fox
jumped over the fence
The quick brown fox
-

To show the robustness of this program, I choose a text fragment from The Declaration of Independence as the inputs:

-
1
2
3
4
5
6
7
8
9
He has refused for a long time, 
after such dissolution,
to cause others to be elected;
whereby the legislative powers,
incapable of annihilation,
have returned to the people at large for their exercise;
the State remaining in the meantime exposed
to all the dangers of invasion from
without and convulsion within.
- -

The resulted output perfectly follows the requirement of the permuted index

-
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
                            He has refused for    a long time,
after such dissolution,
to all the dangers of invasion from
without and convulsion within.
incapable of annihilation,
have returned to the people at large for their exercise;
to cause others to be elected;
to cause others to be elected;
without and convulsion within.
to all the dangers of invasion from
after such dissolution,
to cause others to be elected;
have returned to the people at large for their exercise;
the State remaining in the meantime exposed
He has refused for a long time,
have returned to the people at large for their exercise;
to all the dangers of invasion from
He has refused for a long time,
have returned to the people at large for their exercise;
He has refused for a long time,
the State remaining in the meantime exposed
incapable of annihilation,
to all the dangers of invasion from
have returned to the people at large for their exercise;
whereby the legislative powers,
He has refused for a long time,
the State remaining in the meantime exposed
incapable of annihilation,
to all the dangers of invasion from
to cause others to be elected;
have returned to the people at large for their exercise;
whereby the legislative powers,
He has refused for a long time,
the State remaining in the meantime exposed
have returned to the people at large for their exercise;
the State remaining in the meantime exposed
after such dissolution,
to all the dangers of invasion from
whereby the legislative powers,
the State remaining in the meantime exposed
have returned to the people at large for their exercise;
the State remaining in the meantime exposed
have returned to the people at large for their exercise;
He has refused for a long time,
to all the dangers of invasion from
to cause others to be elected;
to cause others to be elected;
have returned to the people at large for their exercise;
whereby the legislative powers,
without and convulsion within.
without and convulsion within.
- -
-

To be continued.

- -
- - - - - - -
- -
- - - - -
- - - - -
- - -
- - - - -
- - - - - - - - -
-
- - -
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/2018/03/14/C-Sequential-Containers-Part-2/index.html b/2018/03/14/C-Sequential-Containers-Part-2/index.html deleted file mode 100644 index 659c4613..00000000 --- a/2018/03/14/C-Sequential-Containers-Part-2/index.html +++ /dev/null @@ -1,587 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - C++ - Sequential Containers (Part 2) | Liam's Blog - - - - - - - - - - - - -
-
- -
-
- - -
- - -

If history repeats itself, and the unexpected always happens, how incapable must Man be of learning from experience?—George Bernard Shaw.

-
- - -
- - - - -
-
- - -
- - 0% -
- - - - -
-
-
- - -
- - -
- - - -
- - - - - -
-

- C++ - Sequential Containers (Part 2) -

- - -
- - - - -
- - -

Putting strings together

This part provides a series of exercises about various concatenations of strings. The topic is similar to that in Chpater 2 Looping and counting.

-

Exercise 1

Briefly speaking, the first exercise requires us to design a program that can add a frame for a picture (shown as below).

-

The original picture

-
1
2
3
4
5
this is an
example
to
illustrate
framing
-

The framed picture

-
1
2
3
4
5
6
7
**************
* this is an *
* example *
* to *
* illustrate *
* framing *
* ************
-

The original picture is in fact formed by several lines of strings, of which each string is an element stored in the vector named p. The framed picture adds four edges for the original picture with asterisks. There is one space between the left-edge and the initial character of the strings, and one space between the right-edge and the end of the longest string.

-

Exercise 2

Once we have the framed picture, we can vertically concatenate it with the original one. The program should generate a final picture like this

-
1
2
3
4
5
6
7
8
9
10
11
12
this is an
example
to
illustrate
framing
**************
* this is an *
* example *
* to *
* illustrate *
* framing *
**************
-

Two pictures are lined up along the left-hand border.

-

Exercise 3

We can also perform horizontal concatenation on two pictures like the below picture shows

-
1
2
3
4
5
6
7
this is an **************
example * this is an *
to * example *
illustrate * to *
framing * illustrate *
* framing *
**************
-

Two pictures are lined up along the top border. In addition, there is a blank column that seperates two pictures.

-

Solutions

The book provides solutions to each exercise listed above, I’ll put all together and write a program that can generate above pictures once.

-

The framed picture

Framing a picture is an old question. The solution strategy can be divided into three steps

-
    -
  1. obtain the length (denoted by maxlen) of the longest string in p as the width of the framed picture depends on maxlen.
  2. -
  3. create a string object that filled by asterisks for generating the top line and bottom line. The number of the astersiks is maxlen + 4.
  4. -
  5. the middle lines are formed by 1 space character and the corresponding string in p, and a certain number of spaces. The number of spaces is the difference between the maxlen and the size of the corresponding string to be concatenated.
  6. -
-

Below shows the function that gives the length of the longest string in a object of vector. I uses iterators instead of indices for all functions.

-
1
2
3
4
5
6
7
8
9
string::size_type width(const vector<string> &v)
{
string::size_type maxlen = 0;
for (vector<string>::const_iterator iter = v.begin(); iter != v.end(); ++iter)
{
maxlen = max(maxlen, (*iter).size());
}
return maxlen;
}
-

The function below generates the framed picture while keeping the original picture unchanged.

-
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
vector<string> frame(const vector<string> &v)
{
// to hold each rows of framed picture to be written
vector<string> ret;
// get the length of the longest string
string::size_type maxlen = width(v);

// first line of outputs
string border(maxlen + 4, '*');
ret.push_back(border);

for (vector<string>::const_iterator iter = v.begin(); iter != v.end(); ++iter)
{
// new rows except two border lines
string temp = "* " + (*iter) + string(maxlen - (*iter).size(), ' ') + " *";
ret.push_back(temp);
}

// bottom line of outputs
ret.push_back(border);
return ret;
}
-

Vertical concatenation

In fact we can generate the required picture without concatenation. The first step is to generate the original picture and then the second step is to generate the framed picture. They are closely connect if no blank line between two outputs. This implies that we can create a new object of verctor and copy all strings contained in two pictures into it. The function is shown below.

-
1
2
3
4
5
6
7
8
9
vector<string> vcat(const vector<string> &top, const vector<string> &bottom)
{
// copy the top picture
vector<string> ret = top;

// copy the bottom picture one line by one line
ret.insert(ret.end(), bottom.begin(), bottom.end());
return ret;
}
-

What’s new here is the insert function. vector, string and list all support insert function. The first parameter of this insert function means that the new elements will be inserted into a position that before the parameter specifies. The second and the third parameters indicate that the elements in the range of [bottom.begin(), bottom.end()) will be copied and inserted. This increases the length of vector. It has the same effect as the for loops shown below.

-
1
2
3
4
for (vector<string>::const_iterator i = bottom.begin(); i != bottom.end(); ++i)
{
ret.push_back((*i));
}
-

Horizontal concatenation

Similar to vertical voncatenation, we can generate the required picture without concatenation through alternately write rows of the original picture and the framed picture until all rows from two pictures are written. If the number of rows of two pictures are different, the one that has less number of rows will be replenished with blank strings.

-

Therefore, the key to the solution is to concatenate two strings, of which one from the original picture and another from the framed picture. As we did in the fisrt exercise, we need to pad enough spaces for each row of the original picture. Now, the number of spaces will be the difference between maxlen + 1 and the size of each string itself as we want one space column between two pictures. The code below show the function.

-
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
vector<string> hcat(const vector<string> &left, const vector<string> &right)
{
// to hold the each line of outputs
vector<string> ret;

// add one space column between two pictures
string::size_type width1 = width(left) + 1;

// iterators to look at elements from left and right respectively
vector<string>::const_iterator iter_i = left.begin(), iter_j = right.begin();

// continue until we've see all rows from two pictures
while (iter_i != left.end() || iter_j != right.end())
{
// construct new string to hold characters from two pictures
string s;

// copy a row from left side picture
if (iter_i != left.end())
{
s = (*iter_i);
++iter_i;
}
// pad to full width
s += string(width1 - s.size(), ' ');

// copy a row from right picture
if (iter_j != left.end())
{
s += (*iter_j);
++iter_j;
}

// store s into vector to form a new picture
ret.push_back(s);
}
return ret;
}
- -

A complete program

I package all three pictures into one file and the width function into another file. In addition, I define a function more to deal with output. All files are displayed below with detailed comments for each step.
output.cpp

-
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
// function to write each elements from a vector<string>
#include <iostream> // to get declaration of cout and endl
#include <string> // to get declaration of string
#include <vector> // to get declaration of vector
#include "output.h"

using std::cout; using std::string;
using std::endl; using std::vector;

void output(const vector<string> &pics)
{
// loop thru the vector and write elements one by one
for (vector<string>::const_iterator iter = pics.begin(); iter != pics.end(); ++iter)
{
cout << (*iter) << endl;
}
}
-

output.h

-
1
2
3
4
5
6
7
8
9
#ifndef GUARD_OUTPUT_H
#define GUARD_OUTPUT_H

#include <string>
#include <vector>

void output(const std::vector<std::string> &pics);

#endif /* GUARD_OUTPUT_H */
- -

width.cpp

-
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
// function returns the size of the longest string in a vector<string>
#include <string> // to get declaration of string
#include <vector> // to get declaration of vector
#include <algorithm> // to get declaration of max
#include "width.h" // to get declaration of the function itself

using std::string; using std::vector; using std::max;

string::size_type width(const vector<string> &v)
{
string::size_type maxlen = 0;
for (vector<string>::const_iterator iter = v.begin(); iter != v.end(); ++iter)
{
maxlen = max(maxlen, (*iter).size());
}
return maxlen;
}
-

width.h

-
1
2
3
4
5
6
7
8
9
#ifndef GUARD_WIDTH_H
#define GUARD_WIDTH_H

#include <string>
#include <vector>

std::string::size_type width(const std::vector<std::string> &v);

#endif /* GUARD_WIDTH_H */
- -

pics.cpp

-
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
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
// functions that generate different pictures
#include <string> // to get declaration of string
#include <vector> // to get declaration of vector
#include "width.h"
#include "pics.h"

using std::string; using std::vector;

// function to generate a framed picture
vector<string> frame(const vector<string> &v)
{
// to hold each rows of framed picture to be written
vector<string> ret;
// get the length of the longest string
string::size_type maxlen = width(v);

// first line of outputs
string border(maxlen + 4, '*');
ret.push_back(border);

for (vector<string>::const_iterator iter = v.begin(); iter != v.end(); ++iter)
{
// new rows except two border lines
string temp = "* " + (*iter) + string(maxlen - (*iter).size(), ' ') + " *";
ret.push_back(temp);
}

// bottom line of outputs
ret.push_back(border);
return ret;
}

// function to vertically concatenate two pictures
vector<string> vcat(const vector<string> &top, const vector<string> &bottom)
{
// copy the top picture
vector<string> ret = top;

// copy the bottom picture one line by one line
ret.insert(ret.end(), bottom.begin(), bottom.end());
return ret;
}

// function to horizontally concatenate two pictures
vector<string> hcat(const vector<string> &left, const vector<string> &right)
{
// to hold the each line of outputs
vector<string> ret;

// add one space column between two pictures
string::size_type width1 = width(left) + 1;

// iterators to look at elements from left and right respectively
vector<string>::const_iterator iter_i = left.begin(), iter_j = right.begin();

// continue until we've see all rows from two pictures
while (iter_i != left.end() || iter_j != right.end())
{
// construct new string to hold characters from two pictures
string s;

// copy a row from left side picture
if (iter_i != left.end())
{
s = (*iter_i);
++iter_i;
}
// pad to full width
s += string(width1 - s.size(), ' ');

// copy a row from right picture
if (iter_j != right.end())
{
s += (*iter_j);
++iter_j;
}

// store s into vector to form a new picture
ret.push_back(s);
}
return ret;
}
-

pics.h

-
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
#ifndef GUARD_PICS_H
#define GUARD_PICS_H

#include <string>
#include <vector>

std::vector<std::string> frame(const std::vector<std::string> &v);

std::vector<std::string> vcat(const std::vector<std::string> &top,
const std::vector<std::string> &bottom);

std::vector<std::string> hcat(const std::vector<std::string> &left,
const std::vector<std::string> &right);

#endif /* GUARD_PICS_H */
- -

mainfunction.cpp

-
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
#include <iostream>		// to get declaration of cout and endl
#include <string> // to get declaration of string
#include <vector> // to get declaration of vector
#include "width.h"
#include "pics.h"
#include "output.h"

using std::cout; using std::vector;
using std::endl; using std::string;

int main()
{
// to hold the original picture
vector<string> p;
p.push_back("this is an");
p.push_back("example");
p.push_back("to");
p.push_back("illustrate");
p.push_back("framing");

vector<string> p1 = frame(p);
vector<string> p2 = vcat(p, p1);
vector<string> p3 = hcat(p, p1);


output(p); // print the original picture
cout << endl; // write a blank line to seperate two pictures
output(p1); // print the framed picture
cout << endl;
output(p2); // print the vertically concatenated picture of p and p1
cout << endl;
output(p3); // print the horizontally concatenated picture of p and p1

return 0;
}
-

Test performance

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
this is an
example
to
illustrate
framing

**************
* this is an *
* example *
* to *
* illustrate *
* framing *
**************

this is an
example
to
illustrate
framing
**************
* this is an *
* example *
* to *
* illustrate *
* framing *
**************

this is an **************
example * this is an *
to * example *
illustrate * to *
framing * illustrate *
* framing *
**************
-

The program works perfectly.

- -
- - - - - - -
- -
- - - - -
- - - - -
- - -
- - - - -
- - - - - - - - -
-
- - -
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/2018/03/17/Accelerated-C-Solutions-to-Exercises-Chapter-5-Part-2/index.html b/2018/03/17/Accelerated-C-Solutions-to-Exercises-Chapter-5-Part-2/index.html deleted file mode 100644 index 8707c7c4..00000000 --- a/2018/03/17/Accelerated-C-Solutions-to-Exercises-Chapter-5-Part-2/index.html +++ /dev/null @@ -1,653 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - Accelerated C++ Solutions to Exercises(Chapter 5 Part 2) | Liam's Blog - - - - - - - - - - - - -
-
- -
-
- - -
- - -

If history repeats itself, and the unexpected always happens, how incapable must Man be of learning from experience?—George Bernard Shaw.

-
- - -
- - - - -
-
- - -
- - 0% -
- - - - -
-
-
- - -
- - -
- - - -
- - - - - -
-

- Accelerated C++ Solutions to Exercises(Chapter 5 Part 2) -

- - -
- - - - -
- - -

Exercise 5-2, 5-3, 5-4

5-2: Write the complete new version of the student-grading program, which extracts records for failing students, using vectors. Write another that uses lists. Measure the performance difference on input files of ten lines, 1,000 lines, and 10,000 lines.

-

5-3: By using a typedef, we can write one version of the program that implements either a vector-based solution or a list-based one. Write and test this version of the program.

-

5-4: Look again at the driver functions you wrote in the previous exercise. Note that it ispossible to write a driver that differs only in the declaration of the type for the data structure that holds the input file. If your vector and list test drivers differ in any other way, rewrite them so that they differ only in this declaration.

-

Solution

I’ll give solution to 5-4 directly as these three exercises are closely connected. The strategy can be divided into three steps:

-
    -
  1. write drivers for using list or vector to hold input files.
  2. -
  3. add the function to extract records for failing students, and add the drivers for files where the declaration of specified container is needed.
  4. -
  5. change indices to iterators if necessary because list doesn’t support access elements via indices.
  6. -
  7. measure the performance difference of two containers on input files of 10 lines, 1000 lines and 10000 lines.
  8. -
-

Step 1

To switch the use of list and vector, I follows the suggestion of 5-3;

-
1
2
// typedef list<Student_info> info;
typedef vector<Student_info> info;
-

The alias info can represent either the type list or vector and hence allows us switch from one version to another simply via modifying this declaration.

-

However, the type declared above is not only used in the file that contains the main function, but may also needed in other files. Therefore, I separate the declaration from the main function and create a header for this reason.
info.h

-
1
2
3
4
5
6
7
8
9
10
11
#ifndef GUARD_INFO_H
#define GUARD_INFO_H

#include <list>
#include <vector>
#include "Student_info.h"

//typedef std::vector<Student_info> info;
typedef std::list<Student_info> info;

#endif /* GUARD_INFO_H */
-

All files that want to use the name info include the header where declares the name info.

-

Step 2

This step adds two functions: one is to extract the records for failing students; another one is a predicate on failing grades.
It can be observed that the header “info.h” is included for the purpose of defining the container fail to holding information of the failing students.
fails.cpp

-
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
#include "Student_info.h"
#include "grade.h"
#include "fails.h"
#include "info.h"

// the predicate for students who failed
bool fgrade(const Student_info &s)
{
return grade(s) < 60;
}

// function to extract the failed student records
info extract_fails(info &students)
{
info fail;
info::iterator iter = students.begin();

while(iter != students.end())
{
if(fgrade(*iter))
{
fail.push_back(*iter);
iter = students.erase(iter);
}
else
{
++iter;
}
}
return fail;
}
- -

fails.h

-
1
2
3
4
5
6
7
8
9
10
#ifndef GUARD_FAILS_H
#define GUARD_FAILS_H

#include "Student_info.h"
#include "info.h"

bool fgrade(const Student_info &s);
info extract_fails(info &students);

#endif /* GUARD_FAILS_H*/
- -

Step 3

The last change needs to be done is using iterators instead of indices. To avoid messy, I rewrite the output section as a function named print.

-

print.cpp

-
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>
#include <iomanip>
#include <string>
#include "grade.h"
#include "print.h"
#include "info.h"

using std::cout; using std::setprecision;
using std::endl; using std::streamsize;
using std::string;

void print(const info &records, const string::size_type &maxlen)
{
for (info::const_iterator iter = records.begin(); iter != records.end(); ++iter)
{
// write the name, blanks
cout << (*iter).name << string(maxlen + 1 - (*iter).name.size(), ' ');

// compute and write the final grade
double final_grade = grade(*iter);
streamsize prec = cout.precision();
cout << setprecision(3) << final_grade << setprecision(prec) << endl;
}
}
-

print.h

-
1
2
3
4
5
6
7
8
9
#ifndef GUARD_PRINT_H
#define GUARD_PRINT_H

#include <string>
#include "info.h"

void print(const info &records, const std::string::size_type &maxlen);

#endif /* GUARD_PRINT_H */
- -

Note that I didn’t add try block here as the exceptions may be thrown early in the process of extracting the failing records.

-

Step 4

To measure the performance of the vector based program and list based program, I uses members of the time library . Specifically:

-
1
2
3
4
5
6
7
8
typedef std::chrono::high_resolution_clock Clock;
Clock::time_point startTime = Clock::now(); // get current time
info fails = extract_fails(students); // extract records for failing students
Clock::time_point endTime = Clock::now(); // get current time

cout << "It took me "
<< std::chrono::duration_cast<std::chrono::seconds>(endTime - startTime).count()
<< " seconds" << endl;
-

The usage of std::chrono::high_resolution_clock refers to
high_resolution_clock.

-

A complete program

By now, I have introduced the main changes relative to the original program. Therefore, the complete program includes following files

- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
header filessource files
info.h
fails.hfails.cpp
print.hprint.cpp
Student_info.hStudent_info.cpp
grade.hgrade.cpp
mainfunction.cpp
-

I present the rest files in below. Noting that there are two major differences between the vector-based version and the list-based version. First is the declaration metioned above. The second is that the usage of sort function for two types of containers are different, for vectors, the statement is

-
1
sort(students.begin(), students.end(), compare);
-

while for lists,

-
1
students.sort(compare);
-

Strictly speaking, my solution doesn’t meet the requirements of 5-4. But I haven’t find a better one.

-

mainfunction.cpp

-
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
65
66
67
68
69
70
71
72
73
74
75
76
#include <algorithm>		// to get declaration of max, sort
#include <iostream> // to get declaration of cin, cout, endl
#include <stdexcept> // to get declaration of domain_error
#include <string> // to get declaration of string
#include <chrono>
#include "Student_info.h"
#include "grade.h"
#include "fails.h"
#include "print.h"
#include "info.h"


using std::cin; using std::string;
using std::cout; using std::max;
using std::endl; using std::sort;
using std::domain_error;

int main()
{
info students;
Student_info record;
string::size_type maxlen = 0;

// read and store all the records, and find the length of the longest name
while(read(cin, record))
{
maxlen = max(maxlen, record.name.size());
students.push_back(record);
}

try{
// measure the performance
typedef std::chrono::high_resolution_clock Clock;
Clock::time_point startTime = Clock::now(); // get current time
info fails = extract_fails(students); // extract records for failing students
Clock::time_point endTime = Clock::now(); // get current time

cout << "It took me "
<< std::chrono::duration_cast<std::chrono::seconds>(endTime - startTime).count()
<< " seconds" << endl;

// write each line of outputs for passing students
if (!students.empty())
{
//alphabetize the records

//sort(students.begin(), students.end(), compare);
students.sort(compare);
cout << "Students who passed: " << endl;
print(students, maxlen);
}
else
cout << "What a pity! all students failed.";

// write a blank line
cout << endl;

// // write each line of outputs for failing students
if(!fails.empty())
{
//alphabetize the records

//sort(fails.begin(), fails.end(), compare);
fails.sort(compare);
cout << "Students who failed: " << endl;
print(fails, maxlen);
}
else
cout << "Congratulations! all students passed.";

}catch(domain_error e){
cout << e.what();
}

return 0;
}
- -

Student_info.cpp

-
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 <vector>
#include <iostream>
#include "Student_info.h"

using std::vector; using std::istream;

bool compare(const Student_info &x, const Student_info &y)
{
return x.name < y.name;
}

istream & read(istream &is, Student_info &s)
{
// reads and store the student's name, midterm and final exam grades
is >> s.name >> s.midterm >> s.final;

// reads and store all homework grades
read_hw(is, s.homework);
return is;
}

istream & read_hw(istream &in, vector<double> &hw)
{
if (in){
//get rid of previous contents
hw.clear();

// read homework grades
double x;
while(in >> x)
hw.push_back(x);

// clear the stream so that input will work for the next student
in.clear();
}
return in;
}
- -

Student_info.h

-
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
#ifndef GUARD_Student_info
#define GUARD_Student_info

// Student_info.header file
#include<iostream>
#include<string>
#include<vector>

struct Student_info{
std::string name;
double midterm, final;
std::vector<double> homework;
};

bool compare(const Student_info &, const Student_info &);
std::istream & read(std::istream &, Student_info &);
std::istream & read_hw(std::istream &, std::vector<double> &);
#endif
- -

grade.cpp

-
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 <algorithm>
#include <stdexcept>
#include <vector>
#include "grade.h"
#include "Student_info.h"

using std::domain_error; using std::istream;
using std::vector; using std::sort;

// grade function 1
double grade(const Student_info &s)
{
return grade(s.midterm, s.final, s.homework);
}

// grade function 2
double grade(double midterm, double final, const vector<double> &hw)
{
if (hw.size() == 0)
throw domain_error("student has done no homework");
return grade(midterm, final, median(hw));
}

// grade function 3
double grade(double midterm, double final, double homework)
{
return 0.2*midterm + 0.4*final + 0.4*homework;
}

// compute the median of vector<double>
double median(vector<double> vec)
{
// get the size of the vector
typedef vector<double>::size_type vec_size;
vec_size size = vec.size();

// check whether the empty is empty
if (size == 0)
throw domain_error("median of an empty vector");

// sort the grades
sort(vec.begin(), vec.end());

// compute the median homework grade
vec_size mid = size/2;
return size % 2 == 0 ? (vec[mid] + vec[mid - 1])/2 : vec[mid];
}
- -

grade.h

-
1
2
3
4
5
6
7
8
9
10
11
12
#ifndef GUARD_grade_h
#define GUARD_grade_h

// grade.h
#include<vector>
#include "Student_info.h"

double grade(const Student_info &);
double grade(double, double, const std::vector<double> &);
double grade(double, double, double);
double median(std::vector<double>);
#endif
- -

Results

I also wrote a naive program that “randomly” generates thousands of names and grades.

-
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
#include <cstdlib>
#include <iostream>
#include <string>

using std::cout; using std::endl;
using std::string;

int main()
{
string initials{"ABCDEFGHIJKLMNOPQRSTUVWXYZ"};
string letters{"abcdefghijklmnopqrstuvwxyz"};

for (int i = 0; i != 10000; ++i)
{
int x = rand() % 26;
int y = rand() % 26;
int z = rand() % 26;
int p = rand() % 26;
int q = rand() % 26;
double midterm = rand() % 100 + (rand() % 100)/99.0;
double final = rand() % 100 + (rand() % 100)/99.0;
double homework = rand() % 100 + (rand() % 100)/99.0;

string name{initials[x], letters[y], letters[z], letters[p], letters[q]};
cout << name << ' ' << midterm << ' ' << final << ' ' << homework << endl;
}

return 0;
}
-

The table below gives the performance difference of two version pograms on input files of ten lines, 1,000 lines, and 10,000 lines.

- - - - - - - - - - - - - - - - - - - - - - - -
Number of linesvectorlist
100.00107 seconds0.00007 seconds
10000.136711 seconds0.003036 seconds
100000.826406 seconds0.015598 seconds
-

Apparently, the list based program has much better performance than the vector based program, with using much less time in extracting the failing students’ records regardless of the file size. In addition, along with the increase of file size, the time taken by the list version program increases much slower than that taken by the vector version.

-
-

To be continued.

- -
- - - - - - -
- -
- - - - -
- - - - -
- - -
- - - - -
- - - - - - - - -
-
- - -
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/2018/03/19/Accelerated-C-Solutions-to-Exercises-Chapter-5-Part-3/index.html b/2018/03/19/Accelerated-C-Solutions-to-Exercises-Chapter-5-Part-3/index.html deleted file mode 100644 index 0cfd1222..00000000 --- a/2018/03/19/Accelerated-C-Solutions-to-Exercises-Chapter-5-Part-3/index.html +++ /dev/null @@ -1,657 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - Accelerated C++ Solutions to Exercises(Chapter 5 Part 3) | Liam's Blog - - - - - - - - - - - - -
-
- -
-
- - -
- - -

If history repeats itself, and the unexpected always happens, how incapable must Man be of learning from experience?—George Bernard Shaw.

-
- - -
- - - - -
-
- - -
- - 0% -
- - - - -
-
-
- - -
- - -
- - - -
- - - - - -
-

- Accelerated C++ Solutions to Exercises(Chapter 5 Part 3) -

- - -
- - - - -
- - -

Exercise 5-5

Write a function named center(const vector&) that returns a picture in which all the lines of the original picture are padded out to their full width, and the padding is as evenly divided as possible between the left and right sides of the picture. What are the properties of pictures for which such a function is useful? How can you tell whether a given picture has those properties?

-

Solution & Results

The full width is the size of the longest string and can be obtained via a for loop.

-
1
2
3
4
5
for (vector<string>::const_iterator iter = p.begin();
iter != p.end(); ++iter)
{
maxlen = max(maxlen, (*iter).size());
}
-

The key is to compute the number of spaces (denoted by paddingLeft) needed for the left side padding. If we want that the padding is as evenly divided as possible, paddingLeft can be set as half of maxlen - (*iter).size(), which is the total number of spaces needed to pad out to the full width of a line.

-

If (maxlen - (*iter).size()) is even, the numbers of spaces on both sides of the original string are the same (i.e. the case of that padding is evenly divided).

-

If (maxlen - (*iter).size()) is odd, paddingLeft is in fact has the value of ((maxlen - (*iter).size()) - 1)/2, and hence is one space less that the right side padding.

-

In summary, the program logic is

-
    -
  1. find maxlen.
  2. -
  3. compute paddingLeft.
  4. -
  5. construct a new line based on the string in the original picture and paddingLeft spaces.
  6. -
  7. store each new line into a vector and return it once finishes.
  8. -
-

The complete program and tests can be found below.

-

mainfunction.cpp

-
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 <string>
#include <vector>
#include <algorithm>

using std::cout; using std::string;
using std::endl; using std::vector;
using std::max;

// function declaration
vector<string> center(const vector<string> &p);

int main()
{
// create an original picture
vector<string> p;
p.push_back("this is an");
p.push_back("example");
p.push_back("to");
p.push_back("illustrate");
p.push_back("framing");

// generate the centered picture
vector<string> np = center(p);
for(vector<string>::const_iterator iter = np.begin();
iter != np.end(); ++iter)
cout << *iter << endl;

return 0;
}

// define the function that returns a centered picture
vector<string> center(const vector<string> &p)
{
vector<string> centeredPicture;
string::size_type maxlen = 0;

// get the length of the longest string
for (vector<string>::const_iterator iter = p.begin();
iter != p.end(); ++iter)
{
maxlen = max(maxlen, (*iter).size());
}

// pad out the left side
for (vector<string>::const_iterator iter = p.begin();
iter != p.end(); ++iter)
{
string::size_type paddingLeft = (maxlen - (*iter).size())/2;
string s = string(paddingLeft, ' ') + (*iter);
centeredPicture.push_back(s);
}

return centeredPicture;
}
- -

Test Results

-
1
2
3
4
5
this is an
example
to
illustrate
framing
- -
-

Exercise 5-6

Rewrite the extract_fails function from §5.1.1/77 so that instead of erasing each failing student from the input vector v, it copies the records for the passing students to the beginning of v, and then uses the resize function to remove the extra elements from the end of v. How does the performance of this version compare with the one in §5.1.1/77?

-

Solution & Results

The original function is

-

Method 1

-
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
vector<Student_info> extract_fails_method1(vector<Student_info>& students)
{
vector<Student_info> fail;
vector<Student_info>::size_type i = 0;
// invariant:elements [0, i) of students represent passing grades
while (i != students.size())
{
if (fgrade(students[i]))
{
fail.push_back(students[i]);
students.erase(students.begin() + i);
}
else
++i;
}
return fail;
}
- -

The revised version is

-

Method 2

-
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
vector<Student_info> extract_fails_method2(vector<Student_info>& students)
{

vector<Student_info> fail;
vector<Student_info>::size_type i = 0, j = students.size();
// invariant:elements [0, i) of students represent passing grades
while (i != students.size())
{
if (fgrade(students[i]))
{
fail.push_back(students[i]};
}
else
{
// the size increases by 1
students.insert(students.begin(), students[i]);
// the indice move forward by 1
++i;
}
++i;
}
students.resize(j - fail.size());
return fail;
}
-

The next step is to measure the performance of these two function, using the same methodology as that applied in Exercise 5-4. I add above two methods into the file fails.cpp and fails.h as alternatives. To avoid redundancy, I only present the file that contains the main function in below.

-
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
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
// Accelerated C++ Solutions Exercises 5-2. 5-3, 5-4
#include <algorithm> // to get declaration of max, sort
#include <iostream> // to get declaration of cin, cout, endl
#include <stdexcept> // to get declaration of domain_error
#include <string> // to get declaration of string
#include <chrono>
#include "Student_info.h"
#include "grade.h"
#include "fails.h"
#include "print.h"
#include "info.h"


using std::cin; using std::string;
using std::cout; using std::max;
using std::endl; using std::sort;
using std::domain_error;

int main()
{
info students;
Student_info record;
string::size_type maxlen = 0;

// read and store all the records, and find the length of the longest name
while(read(cin, record))
{
maxlen = max(maxlen, record.name.size());
students.push_back(record);
}

try{
info students_copy = students;

// measure the performance for method1
typedef std::chrono::high_resolution_clock Clock;
Clock::time_point startTime = Clock::now(); // get current time
info fails = extract_fails_method1(students); // extract records for failing students
Clock::time_point endTime = Clock::now(); // get current time

cout << "Method 1 took me "
<< std::chrono::duration_cast<std::chrono::duration<double>>(endTime - startTime).count()
<< " seconds" << endl;

// measure the performance for method2
startTime = Clock::now(); // get current time
fails = extract_fails_method2(students_copy); // extract records for failing students
endTime = Clock::now(); // get current time

cout << "Method 2 took me "
<< std::chrono::duration_cast<std::chrono::duration<double>>(endTime - startTime).count()
<< " seconds" << endl;

// write each line of outputs for passing students
if (!students.empty())
{
//alphabetize the records

sort(students.begin(), students.end(), compare);
// students.sort(compare);
cout << "Students who passed: " << endl;
print(students, maxlen);
}
else
cout << "What a pity! all students failed.";

// write a blank line
cout << endl;

// write each line of outputs for failing students
if(!fails.empty())
{
//alphabetize the records

sort(fails.begin(), fails.end(), compare);
//fails.sort(compare);
cout << "Students who failed: " << endl;
print(fails, maxlen);
}
else
cout << "Congratulations! all students passed.";

}catch(domain_error e){
cout << e.what();
}

return 0;
}
- -

The table below gives the comparison of these two methods, showing that method 2 performs slightly better than method 1. The result is in fact not reliable as it strongly depends on the number of passing students and failing students contained in the raw data. More experiments need to be done for more robust results.
|Number of lines|Method 1-erase| Method 2-insert&resize|
| :— | :— | :— |
|10|0.000 seconds|0.000 seconds|
|1000|0.147114 seconds|0.117083 seconds|
|10000|0.848616 seconds|0.735503 seconds|

-
-

Exercise 5-7

Given the implementation of frame in §5.8.1/93, and the following code fragment

-
1
2
vector<string> v;
frame(v);
-

describe what happens in this call. In particular, trace through how both the width functiona nd the frame function operate. Now, run this code. If the results differ from your expectations, first understand why your expectations and the program differ, and then change one to match the other.

-

Solution & Results

Let’t recall the frame function

-
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
vector<string> frame(const vector<string> &v)
{
// to hold each rows of framed picture to be written
vector<string> ret;
// get the length of the longest string
string::size_type maxlen = width(v);

// first line of outputs
string border(maxlen + 4, '*');
ret.push_back(border);

for (vector<string>::const_iterator iter = v.begin(); iter != v.end(); ++iter)
{
// new rows except two border lines
string temp = "* " + (*iter) + string(maxlen - (*iter).size(), ' ') + " *";
ret.push_back(temp);
}

// bottom line of outputs
ret.push_back(border);
return ret;
}
-

Once calling this function with passing an empty vector v, I expected following procedures happen one after another.

-
    -
  1. the argument is passed by const reference. v refers to the empty vector v.

    -
  2. -
  3. an empty vector ret is created.

    -
  4. -
  5. an object of type string::size_type is created and named as maxlen. maxlen is initialized with a value returned by width function.

    -
  6. -
  7. the computer enters into width function (as shown below). The arguments is also passed by const reference and hence parameter v refers to the empty vector v defined in the main function.

    -

    4.1. object maxlen is created and initialized with value of 0.

    -

    4.2. the computer enters into a for loop. The init-statement is evaluated and iter is initialized as v.begin(). Then then condition is evaluated and the result is false as v.begin() == v.end() due to the fact that no any elements in the container.

    -

    4.3 for loop quits and maxlen that equals to 0 is returned.

    -
    1
    2
    3
    4
    5
    6
    7
    8
    9
    string::size_type width(const vector<string> &v)
    {
    string::size_type maxlen = 0;
    for (vector<string>::const_iterator iter = v.begin(); iter != v.end(); ++iter)
    {
    maxlen = max(maxlen, (*iter).size());
    }
    return maxlen;
    }
  8. -
  9. the computer goes back to the frame function and maxlen is initialized with value 0 (step 3 finishes).

    -
  10. -
  11. object border is created and initialized with 4 asterisks. Then, it is stored into vector ret.

    -
  12. -
  13. the next for loop is similar to the for loop inside the width function. It quits and has no any effects.

    -
  14. -
  15. object border is stored into vector ret again. By then, ret has two elements, both of which are strings filled by 4 asterisks.

    -
  16. -
  17. ret is returned. The computer goes back to the function caller.

    -
  18. -
-

According to my expection, the picture returned by frame only consist of two lines of strings each formed by 4 asterisks. Let’s verify this the program displayed below.

-

A complete 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
#include <string>
#include <vector>
#include <iostream>
#include <algorithm>

using std::string; using std::cout;
using std::endl; using std::max;
using std::vector;

// function declarations
string::size_type width(const vector<string> &v);
vector<string> frame(const vector<string> &v);

int main()
{
vector<string> v;
vector<string> p = frame(v);
for (vector<string>::iterator iter = p.begin(); iter != p.end(); ++iter)
cout << *iter <<endl;
return 0;
}

// please fill this part with the width function described above
// please fill this part with the frame function described above
-

As expected, the program produces following outputs

-
1
2
****
****
- -
-

Exercise 5-8

In the hcat function from §5.8.3/95, what would happen if we defined s outside the scope of the while? Rewrite and execute the program to confirm your hypothesis.

-

Solution & Results

Code analysis

Recalling the hcat function:

-
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
vector<string> hcat(const vector<string> &left, const vector<string> &right)
{
// to hold the each line of outputs
vector<string> ret;

// add one space column between two pictures
string::size_type width1 = width(left) + 1;

// iterators to look at elements from left and right respectively
vector<string>::const_iterator iter_i = left.begin(), iter_j = right.begin();

// continue until we've see all rows from two pictures
while (iter_i != left.end() || iter_j != right.end())
{
// construct new string to hold characters from two pictures
string s;

// copy a row from left side picture
if (iter_i != left.end())
{
s = (*iter_i);
++iter_i;
}
// pad to full width
s += string(width1 - s.size(), ' ');

// copy a row from right picture
if (iter_j != left.end())
{
s += (*iter_j);
++iter_j;
}

// store s into vector to form a new picture
ret.push_back(s);
}
return ret;
}
-

Let’s analyse what happens if we define s outside the scope of the while loop:

-
    -
  1. s is an empty string before entering into the while loop.

    -
  2. -
  3. the first iteration:

    -

    2.1. s is assigned with the copy of the first element from the left picture, and then is padded out to the full width (left side).

    -

    2.2. s is concatenated with the first element from the right picture.

    -

    2.3. s is stored into vector ret to formatted the first line of the new picture.

    -
  4. -
  5. the nth interation:

    -

    case 1: the numbers of rows of both pictures are the same. The process of the rest iterations are similar to the first iteration until the while condition is evaluated to false. As a result, all rows of both pictures are copied into the new picture. The function has the same effect as the original one, where s is defined inside the while loop.

    -

    case 2: the left side picture has less rows than the right side picture. When iter_i == left.end() but iter_j != right.end(), the first if statements are ignored but the next statement (shown below) will result in compilation errors.

    -
    1
    s += string(width1 - s.size(), ' ');
    -

    remembering that s.size() is the summation of widths of both sides, i.e. width1 plus the width of the right side picture. Therefore, width1 - s.size() is negative.

    -

    case 3: if left side picture has more rows than the right side picture. The process of the rest iterations are similar to case 1. The function has same effect as the original function, where s is defined inside of the while loop.

    -
  6. -
-

Rewrite the original program

Now, let’s verify above expectations using following program, covering three cases and two functions(i.e. the original one and the modified one). The files of the program includes mainfunction.cpp, pics.cpp, pics.h, width.cpp. width.h, print.cpp and print.h. More details about the original program can be found in putting strings together.

-

mainfunction.cpp

-
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
#include <iostream>
#include <string>
#include <vector>
#include "pics.h"
#include "print.h"

using std::cout; using std::endl;
using std::vector; using std::string;

int main()
{
vector<string> pic1;
pic1.push_back("aaaaaa");
pic1.push_back("bbbbbbbbbbbbb");
pic1.push_back("ccc");

vector<string> pic2;
pic2.push_back("ddddddddddddd");
pic2.push_back("eeeeeee");
pic2.push_back("fffffffffff");
pic2.push_back("ggggg");

vector<string> left, right;
// Test case 1
left = right = pic1;

// Test case 2
// left = pic1; right = pic2;

// Test case 3
// left = pic2; right = pic1;

vector<string> hcatPicture1 = hcat_function1(left, right);
vector<string> hcatPicture2 = hcat_function2(left, right);

cout << "The original function produces a picture shown as follows: " << endl;
print(hcatPicture1);
cout << endl;
cout << "The modified function produces a picture shown as follows: " << endl;
print(hcatPicture2);

return 0;
}
- -

pics.cpp

-
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
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
// functions that generate different pictures
#include <string> // to get declaration of string
#include <vector> // to get declaration of vector
#include "width.h"
#include "pics.h"

using std::string; using std::vector;

// to horizontally concatenate two pictures: original function
vector<string> hcat_function1(const vector<string> &left, const vector<string> &right)
{
// to hold the each line of outputs
vector<string> ret;

// add one space column between two pictures
string::size_type width1 = width(left) + 1;

// iterators to look at elements from left and right respectively
vector<string>::const_iterator iter_i = left.begin(), iter_j = right.begin();

// continue until we've see all rows from two pictures
while (iter_i != left.end() || iter_j != right.end())
{
// construct new string to hold characters from two pictures
string s;

// copy a row from left side picture
if (iter_i != left.end())
{
s = (*iter_i);
++iter_i;
}
// pad to full width
s += string(width1 - s.size(), ' ');

// copy a row from right picture
if (iter_j != left.end())
{
s += (*iter_j);
++iter_j;
}

// store s into vector to form a new picture
ret.push_back(s);
}
return ret;
}

// to horizontally concatenate two pictures: modified function
vector<string> hcat_function2(const vector<string> &left, const vector<string> &right)
{
// to hold the each line of outputs
vector<string> ret;

// add one space column between two pictures
string::size_type width1 = width(left) + 1;

// iterators to look at elements from left and right respectively
vector<string>::const_iterator iter_i = left.begin(), iter_j = right.begin();

// construct new string to hold characters from two pictures
string s;
// continue until we've see all rows from two pictures
while (iter_i != left.end() || iter_j != right.end())
{
// copy a row from left side picture
if (iter_i != left.end())
{
s = (*iter_i);
++iter_i;
}
// pad to full width
s += string(width1 - s.size(), ' ');

// copy a row from right picture
if (iter_j != right.end())
{
s += (*iter_j);
++iter_j;
}

// store s into vector to form a new picture
ret.push_back(s);
}
return ret;
}
- -

pics.h

-
1
2
3
4
5
6
7
8
9
10
11
12
13
#ifndef GUARD_PICS_H
#define GUARD_PICS_H

#include <string>
#include <vector>

std::vector<std::string> hcat_function1(const std::vector<std::string> &left,
const std::vector<std::string> &right);

std::vector<std::string> hcat_function2(const std::vector<std::string> &left,
const std::vector<std::string> &right);

#endif /* GUARD_PICS_H */
- -

width.cpp

-
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
// function returns the size of the longest string in a vector<string>
#include <string> // to get declaration of string
#include <vector> // to get declaration of vector
#include <algorithm> // to get declaration of max
#include "width.h" // to get declaration of the function itself

using std::string; using std::vector; using std::max;


string::size_type width(const vector<string> &v)
{
string::size_type maxlen = 0;
for (vector<string>::const_iterator iter = v.begin(); iter != v.end(); ++iter)
{
maxlen = max(maxlen, (*iter).size());
}
return maxlen;
}
- -

width.h

-
1
2
3
4
5
6
7
8
9
#ifndef GUARD_WIDTH_H
#define GUARD_WIDTH_H

#include <string>
#include <vector>

std::string::size_type width(const std::vector<std::string> &v);

#endif /* GUARD_WIDTH_H */
- -

print.cpp

-
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
// function to write each elements from a vector<string>
#include "print.h"
#include <iostream> // to get declaration of cout and endl
#include <string> // to get declaration of string
#include <vector> // to get declaration of vector

using std::cout; using std::string;
using std::endl; using std::vector;

void print(const vector<string> &pics)
{
// loop thru the vector and write elements one by one
for (vector<string>::const_iterator iter = pics.begin(); iter != pics.end(); ++iter)
{
cout << (*iter) << endl;
}
}
- -

print.h

-
1
2
3
4
5
6
7
8
9
#ifndef GUARD_OUTPUT_H
#define GUARD_OUTPUT_H

#include <string>
#include <vector>

void print(const std::vector<std::string> &pics);

#endif /* GUARD_OUTPUT_H */
-

Test results

Case 1

-
1
2
3
4
5
6
7
8
9
The original function produces a picture shown as follows: 
aaaaaa aaaaaa
bbbbbbbbbbbbb bbbbbbbbbbbbb
ccc ccc

The modified function produces a picture shown as follows:
aaaaaa aaaaaa
bbbbbbbbbbbbb bbbbbbbbbbbbb
ccc ccc
- -

Case 2

-
1
2
3
4
This application has requested the Runtime to terminate it in an unusual way. 
Please contact the application's support team for more information.
terminate called after throwing an instance of 'std::length_error'
what(): basic_string::_M_create
- -

Case 3

-
1
2
3
4
5
6
7
8
9
10
11
The original function produces a picture shown as follows: 
ddddddddddddd aaaaaa
eeeeeee bbbbbbbbbbbbb
fffffffffff ccc
ggggg

The modified function produces a picture shown as follows:
ddddddddddddd aaaaaa
eeeeeee bbbbbbbbbbbbb
fffffffffff ccc
ggggg
- -

The results of all three cases are exactly as I expected and analysed in last section.

-
-

To be continued.

- -
- - - - - - -
- -
- - - - -
- - - - -
- - -
- - - - -
- - - - - - - - -
-
- - -
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/2018/03/20/Accelerated-C-Solutions-to-Exercise-Chapter-5-Part-4/index.html b/2018/03/20/Accelerated-C-Solutions-to-Exercise-Chapter-5-Part-4/index.html deleted file mode 100644 index 590bd216..00000000 --- a/2018/03/20/Accelerated-C-Solutions-to-Exercise-Chapter-5-Part-4/index.html +++ /dev/null @@ -1,584 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - Accelerated C++ Solutions to Exercises(Chapter 5 Part 4) | Liam's Blog - - - - - - - - - - - - -
-
- -
-
- - -
- - -

If history repeats itself, and the unexpected always happens, how incapable must Man be of learning from experience?—George Bernard Shaw.

-
- - -
- - - - -
-
- - -
- - 0% -
- - - - -
-
-
- - -
- - -
- - - -
- - - - - -
-

- Accelerated C++ Solutions to Exercises(Chapter 5 Part 4) -

- - -
- - - - -
- - -

Exercise 5-9

Write a program to write the lowercase words in the input followed by the uppercase words.

-

Solution & Results

The solution strategy is pretty straightforward: check each entered word to see whether it contains one or more uppercase letters, and store words into two containers according to the check results. I present the code and test performance in below.

-

A complete 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
#include <iostream>
#include <string>
#include <vector>

using std::cin; using std::cout;
using std::endl; using std::string;
using std::vector;

// function declaration
bool pureLowercaseWords(const string &word); // a predicate on words
void print(const vector<string> &Words); // print elements in a vector one by one

int main()
{
vector<string> lowercase_Words; // for holding words that contains only lower case letters
vector<string> uppercase_Words; // for holding words that contains at lease one upper case letters
string word;
while(cin >> word)
{
if(!word.empty())
{
if(pureLowercaseWords(word))
lowercase_Words.push_back(word);
else
uppercase_Words.push_back(word);
}
}
cout << "Words that contain only lowercase letters: " << endl;
print(lowercase_Words);
cout << "Words that contain at lease one uppercase letters: " << endl;
print(uppercase_Words);
return 0;
}

bool pureLowercaseWords(const string &word)
{
for (string::const_iterator iter = word.begin(); iter != word.end(); ++iter)
{
if (isupper(*iter))
return false;
}
return true;
}

void print(const vector<string> &Words);
{
for (vector<string>::const_iterator iter = Words.begin(); iter != Words.end(); ++iter)
cout << *iter << endl;
}
- -

Test
with inputs: University of Oxford is one of best universities all over the world according to QS Ranking

-

The program works as expected:

-
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
Words that contain only lowercase letters: 
of
is
one
of
best
universities
all
over
the
world
according
to
Words that contain at lease one uppercase letters:
University
Oxford
QS
Ranking
- -
-

Exercise 5-10

Palindromes are words that are spelled the same right to left as left to right. Write aprogram to find all the palindromes in a dictionary. Next, find the longest palindrome.

-

Solution & Results

This project has two goals:

-
    -
  1. telling whether a word is a palindrome.
  2. -
  3. find the longest palindrome.
  4. -
-

If a word is not a palindromes, we dicard it directly. On the contrary, if a word is a palindromes, we need to store it for generating a final report. To find the longest palindrome, we can compare the size of two words and keep the longer word, finally obtaining the longest palindrome(or the first observed longest one).

-

To determine whether a word is a palindrome, one key point is that whether the palindromes are case sensitive. I treat the palindromes as case insensitive in this program and hence the predicate on a word is based on the lowercase(or uppercase) version of a word. Another point is that whether a single character is a palindrome. I regard it as a palindrome and treat a single character as a one-letter word.

-

Apparently, the key is to find an algorithm that can identify a palindrome. According to the definition, palindromes are words that are spelled the same right to left as left to right. A possible solution is to divide the characters in a word into two groups, which are symmetrically located at two side of a mid point, and compare them one pair by one pair. If all pairs are same, the word is a palindromes.

-

Intuitively, if the number of characters in a word is odd, then, there exists one unique mid point, which splits two groups of characters. The graph below shows this case and illustrates the relationship of one pair of characters regarding to their corresponding iterators.

-

A word contains odd number characters

-

Clearly, this process can be translated to a while loop and the stopping point is the mid point (i.e. the position that iterator word.begin() + size/2 refers to).

-

Similarly, if the number of characters in a word is even, characters can be exactly splitted into two groups, i.e. word.size()/2 pairs.

-

A word contains even number characters

-

It can be seen from above graph, the stopping point is also the position that word.begin() + size/2 refers to. Before this point, the last pair of characters(i.e. two mid elements) are compared.

-

Now the predicate can be write as:

-
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
bool isPalindromes(const string &word)
{
typedef string::const_iterator iter;
iter startPosition = word.begin();
iter endPosition = word.end() - 1;
iter midPosition = word.begin() + word.size()/2;
while (startPosition != midPosition)
{
if(tolower(*startPosition) != tolower(*endPosition))
return false;
++startPosition;
--endPosition;
}
return true;
}
-

Firstly, I choose to compare two endpoints and then move both points 1 position forward to the middle in the following iteration. Finally, the while loop stops when all pairs of characters have been compared.

-

Once finishes this function, we can write the main function directly. The code below shows the complete program. Note that I seperate the condition word.size() == 1 from isPalindromes(word) simply for the purpose of computational efficiency.

-
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 <algorithm>
#include <iostream>
#include <string>
#include <vector>

using std::cin; using std::cout;
using std::endl; using std::string;
using std::vector; using std::max;

// function declarations
bool isPalindromes(const string &word);
void print(const vector<string> &palindromes);

int main()
{
vector<string> palindromes; // hold all identified palindromes
string longest; // hold the first observed longest palindrome

// read words
string word;
while(cin >> word)
{
if(word.size() == 1 || isPalindromes(word))
{
if(longest.size() < word.size())
longest = word;
palindromes.push_back(word);
}
}

// print all palindromes
cout << "Following words are palindromes:" << endl;
print(palindromes);

// print the longest
cout << "The longest palindrome(first observed) is:\n" << longest << endl;
return 0;
}

void print(const vector<string> &palindromes)
{
for(vector<string>::const_iterator iter = palindromes.begin();
iter != palindromes.end(); ++iter)
cout << *iter << endl;
}

// please add the predicate here
-

Now let’s test how the program performs. I entered following words successively:

-
1
Rotator Anna Oxford Civic Sampling Kayak Level Madam Mom Noon shape racecar radar Redder Refer Repaper Please Rotor again Sagas impossible Solos distribution Stats Tenet Wow
- -

The results are displayed below:

-
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
Following words are palindromes:
Rotator
Anna
Civic
Kayak
Level
Madam
Mom
Noon
racecar
radar
Redder
Refer
Repaper
Rotor
Sagas
Solos
Stats
Tenet
Wow
The longest palindrome(first observed) is:
Rotator
-

Yeah, the program works perfectly.

-

Exercise 5-11

In text processing it is sometimes useful to know whether a word has any ascenders or descenders. Ascenders are the parts of lowercase letters that extend above the text line;in the English alphabet, the letters b, d, f, h, k, l, and t have ascenders. Similarly, the descenders are the parts of lowercase letters that descend below the line; In English,theletters g, j, p, q, and y have descenders. Write a program to determine whether a word has any ascenders or descenders. Extend that program to find the longest word in the dictionary that has neither ascenders nor descenders.

-

Solution & Results

The idea to solve this exercise is similar to that described in above exercise. The first step is to check whether a word contains any ascenders or descenders, by means of comparing each character in the word to ascenders or descenders. The second step is to store the word into corresponding vector according to the check results in step 1. The longest word that has neither ascenders nor descenders can be found using the same method as above.

-

I present the complete program below followed by a performance test. Noting that I defined two global variables which can be accessed anywhere inside of the file. But they cannot be modified as I add const in defining them. The purpose is to avoid that these two objects will be created repeatedly if they are put into the precidate. This problem can be easily solved with specifier static, which will be introduced in chapter 6.

-
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
65
66
67
68
69
70
71
72
// Accelerated C++ Solutions Exercises 5-11
#include <algorithm>
#include <iostream>
#include <string>
#include <vector>

using std::cin; using std::cout;
using std::endl; using std::string;
using std::vector; using std::max;

// global variables
const string ascenders = "bdfhlt";
const string descenders = "gjpqy";

// function declarations
bool noAscDes(const string &word);
void print(const vector<string> &words);

int main()
{
vector<string> asc_des; // hold words that contain any ascenders or descenders
vector<string> no_asc_des; // hold words that has neither ascenders nor descenders
string longest_no; // find the longest word that has neither ascenders nor descenders

// read words
string word;
while(cin >> word)
{
if(noAscDes(word))
{
if (longest_no.size() < word.size())
longest_no = word;
no_asc_des.push_back(word);
}
else
asc_des.push_back(word);
}

cout << "Following words contain either ascenders or descenders:" << endl;
print(asc_des);
cout << "Following words contain neither ascenders nor descenders:" << endl;
print(no_asc_des);
cout << "The longest word that has neither ascenders nor descenders is:\n" << longest_no << endl;

return 0;
}

bool noAscDes(const string &word)
{
typedef string::const_iterator iter;
for (iter i = word.begin(); i != word.end(); ++i)
{
for(iter j = ascenders.begin(); j != ascenders.end(); ++j)
{
if (*i == *j)
return false;
}
for (iter k = descenders.begin(); k != descenders.end(); ++k)
{
if (*i == *k)
return false;
}
}
return true;
}

void print(const vector<string> &words)
{
for(vector<string>::const_iterator iter = words.begin();
iter != words.end(); ++iter)
cout << *iter << endl;
}
- -

Test results

-
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
Inputs:
throughout the course of history there have been many famous speeches that changed the world from on the mount to the inaugural speeches of modern leaders their words have become an inspiration to millions of people

Outputs:
Following words contain either ascenders or descenders:
throughout
the
of
history
there
have
been
many
famous
speeches
that
changed
the
world
from
the
mount
to
the
inaugural
speeches
of
modern
leaders
their
words
have
become
inspiration
to
millions
of
people
Following words contain neither ascenders nor descenders:
course
on
an
The longest word that has neither ascenders nor descenders is:
course
-

As above results show, It works fine.

-
-

References

Koenig, A. and Moo, B.E., 2000. Accelerated C++.

-

Lippman, S.B., Lajoie, J. and Moo, B.E., 2013. C++ Primer, 5th edt.

- -
- - - - - - -
- -
- - - - -
- - - - -
- - -
- - - - -
- - - - - - - - -
-
- - -
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/2018/03/21/C-Using-library-algorithms/index.html b/2018/03/21/C-Using-library-algorithms/index.html deleted file mode 100644 index 84ab88c8..00000000 --- a/2018/03/21/C-Using-library-algorithms/index.html +++ /dev/null @@ -1,618 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - C++ - Using library algorithms (Part 1) | Liam's Blog - - - - - - - - - - - - -
-
- -
-
- - -
- - -

If history repeats itself, and the unexpected always happens, how incapable must Man be of learning from experience?—George Bernard Shaw.

-
- - -
- - - - -
-
- - -
- - 0% -
- - - - -
-
-
- - -
- - -
- - - -
- - - - - -
-

- C++ - Using library algorithms (Part 1) -

- - -
- - - - -
- - -

Generic algorithms for operations on strings

copy

Recalling the vcat function described in Vertical concatenation:

-
1
2
3
4
for (vector<string>::const_iterator i = bottom.begin(); i != bottom.end(); ++i)
{
ret.push_back((*i));
}
-

Using a for loop, all elements of vector bottom are copied and appended to the end of the vector ret.
An alternative method is to use the insert function:

-
1
ret.insert(ret.end(), bottom.begin(), bottom.end());
-

These two methods rely on the member functions of a specified container. A more general solution, using generic algorithm copy, to solve the same question,

-
1
copy(bottom.begin(), bottom.end(), back_inserter(ret));
-

The generic algorithms provided in the standard library implement classic algorithms via iterator operations. They do not depend on any specific type of container. The copy algorithm is an algorithm that writes elements to a container. It takes three iterators, of which, the first two iterators indicates the input range while the third iterator denotes the starting point of the destination sequence.

-

back_inserter() is a iterator adaptor which is a function that returns an appropriate iterator (has type of back_insert_iterator) according to the argument. All intertor adaptors are defined in header . In this case, back_inserter() takes a container as its arguments and yields an iterator that, when used as a destination, appends values to the container. But noting that

-
1
copy(bottom.begin(), bottom.end(), ret.end())
-

is not allowed as there is no element at ret.end.

-

After this function completes, the size of ret increases by bottom.size(). The copy function returns an iterator ((has type of back_insert_iterator) that refers to the next postion of the last element of ret.

-

find_if algorithm

Now, using another generic algorithm find_if, we can simplify the split function described in Taking strings apart.

-
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
// true if the argument is whitespace, false otherwise
bool space(char c)
{
return isspace(c);
}

// false is the argument is whitespace, true otherwise
book not_space(char c)
{
return !isspace(c);
}

vector<string> split(const_string &str)
{
typedef string::const_iterator iter;
vector<string> ret;

iter i = str.begin();
while(i != str.end())
{
// ignore leading blanks
i = find_if(i, str.end(), not_space)

// find end of next word
iter j = find_if(i, str.end(), space);

// copy the characters in [i, j)
if(i != str.end())
ret.push_back(string(i, j));
i = j;
}
return ret;
}
-

First, in this function, we use iterators instead of indices. The core algorithm is that use find_if to firstly find a nonwhiltespace character and a whitespace that closely follows, which determines the range of a word.

-

find_if algorithm takes three arguments, of which, first two are iterators that denotes a sequence while the third one is a predicate on characters. The algorithm calls the predicate to test each element starting from *i, and stops when the predicate returns true, i.e. the character is not whitespace in this case. It returns an iterator that refers to the first element that satisfies the predicate. If find_if failed to find an element that satifies the predicate, it returns its second arguments, i.e. str.end() in this case.

-

It has been observed that we didn’t use isspace() directly instead we write our own functions. This is because that isspace() is overloaded depending on arguments and can’t be passed as an argument to a template function.

-

Another new usage is string(i,j), which constructs a new string that copies the value from the range [i, j).

-

equal algorithm

Now we introduce another algorithm equal, which can simplify the isPalindromes function described in Exercise 5-10.

-
1
2
3
4
bool is_palindrome(const string &s)
{
return equal(s.begin(), s.end(), s.rbegin());
}
-

It is consise enough compared with the one I wrote before. The equal algorithm takes three iterators, of which first two indicate the range of the first sequence while the third iterator denotes the inital position of the second sequence. It compares these two sequence and returns true otherwise returns false.

-

It is known that begin() returns an iterator that refers to the first element in a container. On the contrary, rbegin() returns an reverse iterator that refers to the last element in a container. Similarly, rend() returns to an iterator that refers to the position that before the first element.

-

Finding URLs

Considering that one or more URLs are embedded in a string, we are requested to write a function that finds each URL. A URL is a sequence of characters of the form:

-
1
protocol-name://resource-name
-

where protocol-name contains only letters, and resource-name may consist of letters, digits, and certain punctuation characters(Koenig and Moo 2000).

-

To some extent, this exercise is similar to the split function. Though determining the range of such a string is much complex than finding the range for a word, the idea is the same. The strategy can be divided into three steps:

-
    -
  1. looking for the characters :// that might be a part of a URL.
  2. -
  3. if we find these characters, then it looks backward to find the protocol-name and determines the begining position of the URL; then, it looks forward to find the resource-name and determines the ending position of the URL.
  4. -
  5. finally, we store the URL according the two positions and continue searching for next URL until we finishes the whole document held in the single string.
  6. -
-

We starts from the last step, providing the first two steps have been completed.

-

function to find URLs

-
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
vector<string> find_urls(const string &s)
{
vector<string> ret;
typedef string::const_iterator iter;
iter b = s.begin(), e = s.end();

// look through the entire input
while (b != e)
{
// look for one or more letters followed by ://
b = url_beg(b, e);

// if we found it
if(b != e)
{
// get the rest of the URL
iter after = url_end(b, e);

// remember the URL
ret.push_back(string(b, after));

// advance b and check for more URLs on this line
b = after;
}
}
return ret;
}
-

Function url_beg responsibles for finding the :// and then finding the begining position of the URL accordingly. url_end responsibles for finding the end position of the URL based on the results of url_beg.

-

As mentioned earlier, string(b, after) constructs the URL with two iterators which denotes a range [b, after) of a sequence, i.e. the URL. In other words, b is the interator that denotes the first element of the URL while after denotes the position that one past the last element in the URL. After stores the URL into vector ret, we set the initial position for finding next URL as the end of the previous URL, with setting b = after.

-

Now we consider url_end function first, providing that the begining position of a URL has been found.

-
1
2
3
4
string::const_iterator  url_end(string::const_iterator b, string::const_iterator e)
{
return find_if(b, e, not_url_char);
}
-

We have explained the find_if algorithm in above. It calls the precidate not_url_char and test each element starting from the position where b denotes, and stops until finds the element that makes the predicate returns true. It returns an iterator that refers to the first element that satifies the predicate, and returns its second arguments e if can’t find an element that satisfies the predicate. Now let’s write the predicate:

-
1
2
3
4
5
6
7
8
bool not_url_char(char c)
{
// characters, in addition to alphanumerics, that can appear in a URL
static const string url_ch = "~;/?:@=&$-_.+!*'(),";

// see whether c can appear in a URL and return the negative
return !(isalnum(c) || find(url_ch.begin(), url_ch.end(), c) != url_ch.end());
}
-

Yeah, the statements in the function body seems to be both compact and informative. The first statement defines a const string that contains all punctuation characters that can appear in a URL. What’s new here is the storage class specifier static. Local variables that declaraed with specifier static have static storage duration and are initialized only the first time. On all other calls, the declarations are skipped. In other words, the variable exists starting from the first time declaration to when the program finishes. By doing so, the const string url_ch avoids being declared each time when the predicate is called.

-

The return statement contains three expressions:

-
    -
  • expression 1: isalnum(c). It returns true is c is a letter or digits, otherwise returns false.
  • -
  • expression 2: find(url_ch.begin(), url_ch.end(), c) != url_ch.end(). find algorithm takes three arguments, of which the first two are interators that denote the input sequence while the third one is a value to search for in the range. It returns an iterator to the first element in the range that compares equal to the value. If no such element is found, it returns the second arguments. In this case, if any character that equals to c, the expression is evaluated to true. If no character that matches with c, the expression is evaluated to false.
  • -
  • expression 3: !(expression 1 || expression 2). If and only if both expressions are evaluated to false, the expression 3 is evaluated to true.
  • -
-

In brief, if the scanned character is not a letter, not a digit, and not any punctuation character that can appear in a URL, it is regarded as not a URL character and it is the element that one past the last element of the URL.

-

Finally, we return to the first step, writing the function url_beg to find the initial position of the URL. One concern we have is that :// may not guaranntee a URL, for example, in the case that these characters appear at the end of a string. Therefore, we also need to make sure that there at least one or more letters before :// and at lease one character follows it.

-
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
string::const_iterator url_beg(string::const_iterator b, string::const_iterator e)
{
static const string sep = "://";
typedef string::const_iterator iter;

// i marks where the separator was found
iter i = b;
while((i = search(i, e, sep.begin(), sep.end())) != e)
{
// make sure the seperator isn't at the begining of the proticol-name
if(i != b && i + sep.size() != e)
{
// beg marks the begining of the protocol-name
iter beg = i;
while(beg != b && isalpha(beg[-1]))
--beg;

// is there at least one appropriate character before and after the sep
if (beg != i && !not_url_char(i[sep.size()]))
return beg;
}
// the seperator we found wasn't part of a URL advance i past this separator
i += sep.size();
}
return e;
}
-

Let’s analyse from the while loop. There appears a new algorithm search in the condition:

-
1
(i == search(i, e, sep.begin(), sep.end())) != e
-

The search algorithm takes four iterators, of which the first two indicate the sequence while the last two denotes the initial and final positions of the sequence to be searched for. It returns an iterator the refers to the first element of [sep.begin(), sep.end()) if such sequence can be found in [i, e), otherwise it returns e. Now we know the condition is evaluated to true if :// can be found in the range of [i, e).

-

Assuming that :// is found in the first iteration, i is assigned the iterator that denotes :. To make sure :// reveals a valid URL, we need to make sure it is neither the start of s nor the ending of s. This is managed by the first if statement inside of the while loop. The next is to find the begining position of the URL using following statements.

-
1
2
3
iter beg = i;
while(beg != b && isalpha(beg[-1]))
--beg;
-

beg[-1] accesses the element that before the one denoted by beg, it has the same effect as

-
1
*(beg - 1)
-

If the element denoted by beg is a letter within the range of [b, i), the loop continues with taking one position back each iteration. After the loop finishes, we got the initial position of the URL. However, the while body may fail to be executed even once, if the condition is evaluated to false the first time. Therefore, we need to verify another condition to make sure there exists at least one appropriate character before and after the sep.

-
1
2
if (beg != i && !not_url_char(i[sep.size]))
return beg;
-

The first expression beg != i ensures that there is at least one letter before :. In other words, the while loop above is executed at least once.

-

The sencond expression !not_url_char(i[sep.size]) ensures that there is at least one appropriate character follows ://. i[sep.size()] has the same effect as

-
1
*(i + sep.size())
-

which denotes the first character after sep (i.e. “://“).

-

At this phase, if the condition is evaluated to true, then the function returns the “qualified” iterator to the function caller, otherwise, the function keeps looking untill the last character.

-

A complete program

The files below show the complete program. A simple test can also be found after the program.

-

mainfunction.cpp

-
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
#include <iostream>
#include <vector>
#include <string>
#include "find_urls.h"

using std::cout; using std::endl;
using std::string; using std::vector;

int main()
{
vector<string> urls;
string doc{"A typical URL could have the form https://en.wikipedia.org/wiki/URL, "
"which indicates a protocol (http), a hostname (www.example.com), "
"and a file name (index.html). http://www.cplusplus.com/reference/algorithm/search/?kw=search"};
urls = find_urls(doc);

for (vector<string>::const_iterator iter = urls.begin(); iter != urls.end(); ++iter)
cout << *iter << endl;
}
- -

find_urls.cpp

-
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
// function that finds and returns an URL
#include "find_urls.h"
#include <vector>
#include <string>
#include "delimit.h"

using std::vector;
using std::string;

vector<string> find_urls(const string &s)
{
vector<string> ret;
typedef string::const_iterator iter;
iter b = s.begin(), e = s.end();

// look through the entire input
while (b != e)
{
// look for one or more letters followed by ://
b = url_beg(b, e);

// if we found it
if(b != e)
{
// get the rest of the URL
iter after = url_end(b, e);

// remember the URL
ret.push_back(string(b, after));

// advance b and check for more URLs on this line
b = after;
}
}
return ret;
}
-

find_urls.h

-
1
2
3
4
5
6
7
8
9
#ifndef GUARD_FINDINGURLS_H
#define GUARD_FINDINGURLS_H

#include <vector>
#include <string>

std::vector<std::string> find_urls(const std::string &);

#endif /* GUARD_FINDINGURLS_H */
- -

delimit.cpp

-
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
// contains three functions: not_url_char, url_beg, url_end
#include <string>
#include <algorithm>
#include "delimit.h"

using std::string; using std::find;
using std::find_if; using std::search;

// predicate on a char, check whether it is a char that can appear in a URL
bool not_url_char(char c)
{
// characters, in addition to alphanumerics, that can appear in a URL
static const string url_ch = "~;/?:@=&$-_.+!*'(),";

// see whether c can appear in a URL and return the negative
return !(isalnum(c) || find(url_ch.begin(), url_ch.end(), c) != url_ch.end());
}

// function that returns an iterator that refers to the first element of a URL
string::const_iterator url_beg(string::const_iterator b, string::const_iterator e)
{
static const string sep = "://";
typedef string::const_iterator iter;

// i marks where the separator was found
iter i = b;
while((i = search(i, e, sep.begin(), sep.end())) != e)
{
// make sure the seperator isn't at the begining of the proticol-name
if(i != b && i + sep.size() != e)
{
// beg marks the begining of the protocol-name
iter beg = i;
while(beg != b && isalpha(beg[-1]))
--beg;

// is there at least one appropriate character before and after the sep
if (beg != i && !not_url_char(i[sep.size()]))
return beg;
}
// the seperator we found wasn't part of a URL advance i past this separator
i += sep.size();
}
return e;
}

// function that returns an iterator that denotes the postion one past the last element
string::const_iterator url_end(string::const_iterator b, string::const_iterator e)
{
return find_if(b, e, not_url_char);
}
-

delimit.h

-
1
2
3
4
5
6
7
8
9
10
#ifndef GUARD_DELIMIT_H
#define GUARD_DELIMIT_H

#include <string>

bool not_url_char(char);
std::string::const_iterator url_beg(std::string::const_iterator, std::string::const_iterator);
std::string::const_iterator url_end(std::string::const_iterator, std::string::const_iterator);

#endif /* GUARD_DELIMIT_H */
- -

Test results

-
1
2
https://en.wikipedia.org/wiki/URL,
http://www.cplusplus.com/reference/algorithm/search/?kw=search
-

The program just has function to roughly grab the URLs, but works as expected.

-
-

To be continued.

- -
- - - - - - -
- -
- - - - -
- - - - -
- - -
- - - - -
- - - - - - - - -
-
- - -
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/2018/03/22/C-Using-library-algorithms-Part-2/index.html b/2018/03/22/C-Using-library-algorithms-Part-2/index.html deleted file mode 100644 index a467c10b..00000000 --- a/2018/03/22/C-Using-library-algorithms-Part-2/index.html +++ /dev/null @@ -1,659 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - C++ - Using library algorithms (Part 2) | Liam's Blog - - - - - - - - - - - - -
-
- -
-
- - -
- - -

If history repeats itself, and the unexpected always happens, how incapable must Man be of learning from experience?—George Bernard Shaw.

-
- - -
- - - - -
-
- - -
- - 0% -
- - - - -
-
-
- - -
- - -
- - - -
- - - - - -
-

- C++ - Using library algorithms (Part 2) -

- - -
- - - - -
- - -

Revisit the grading program

This section redesigns the grading program described in Organizing programs with data structures. The new program is required to include extra two grading schemes(Koenig and Moo 2000):

-

1. using the average instead of the median, and treating those assignments that the student failed to turn in as zero.
2. using the median of only the assignments that the student actually submitted.

-

Further, the program should solve following problems based on these grading schemes:

-

1. reading all the student records, separating the students who did all the homework from the others.
2. apply each of the grading schemes to all the students in each group, and report the median grade of each group.

-

Now, let’s follow the instructions and finish this program step by step.

-

Reading and Separating the students records

According to the first problem, we need to read and separate the students records into two groups, one group of which includes students who did all homeworks, and another group includes students who didn’t submit all homeworks. The read function has been introduced before and hence no further analysis. What is the next is to use a predict on students’ records and store the records separately based on the check results.

-
1
2
3
4
bool did_all_hw(const Student_info &s)
{
return ((find(s.homework.begin(), s.homework.end(), 0)) == s.homework.end());
}
-

The return statements in the function body means that if none of the homework grades equal to 0, the function returns true. The idea behind it is that overdue homeworks are given 0 grades.

-

Now this part can be accomplished with following 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
// to hold two groups of students records
vector<Student_info> did, didnt;
Student_info student;

// read all the records and separating them based on whether all homework was done
while(read(cin, student))
{
if(did_all_hw(student))
did.push_back(student);
else
didnt.push_back(student);
}

// check that both groups contain dada
if (did.empty())
{
cout << "No student did all the homework!" << endl;
return 1;
}
if (didnt.empty())
{
cout << "Every student did all the homework!" << endl;
return 1;
}
-

Three grading schemes

Before we go into the second problem, we need to solve three grading schemes first. All schemes compute the final grade as the weighted average of midterm exam grade, final exam grade, and homework grade. The grade function below returns the final grade if it is called.

-
1
2
3
4
double grade(double midterm, double final, double homework)
{
return 0.2*midterm + 0.4*final + 0.4*homework;
}
-

Midterm grade and final exam grade are fixed values once the information are read in. However, the homework grade can be computed using different methods depending on the grading schemes. Specifically:

-
    -
  1. scheme 1: computes the homework grade as the median value of homework grades.
  2. -
  3. scheme 2: computes the homework grade as the average value of homework grades.
  4. -
  5. scheme 3: computes the homework grade as the median value of homework grades excluding the zero grades (i.e. grades for overdue homeworks).
  6. -
-

In addition, if one did not do homework at all, his homework grade would be set to 0.

-

Fundamentally, there are two types of homework grade, one is the median value and another is the average value. Here are the two functions that return the median value and average value of a sequence of double values stored in a vector, respectively.

-
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
// fundermental functions 1: returns the median value of vector<double>
double median(vector<double> vec)
{
// get the size of the vector
typedef vector<double>::size_type vec_size;
vec_size size = vec.size();

// check whether the empty is empty
if (size == 0)
throw domain_error("median of an empty vector");

// sort the grades
sort(vec.begin(), vec.end());

// compute the median homework grade
vec_size mid = size/2;
return size % 2 == 0 ? (vec[mid] + vec[mid - 1])/2 : vec[mid];
}

// fundermental functions 2: returns the average value of vector<double>
double average(const vector<double> &v)
{
// check whether the empty is empty
if (v.empty())
throw domain_error("average of an empty vector");

return accumulate(v.begin(), v.end(), 0.0) / v.size();
}
-

What’s the new idea in above functions is that we compute the average using the accumulate algorithm. The accomulate is defined in the staandard header . It takes three arguments, the first two of which denotes a range of values to be summed while the third arguments gives the initial value for the summation. It is worth noting that we uses 0.0 rather than 0 due to the fact the type of the resulted sum is the type of the third argument.

-

Note that I slightly changes the average function provided in the book for the purpose of avoiding the case that v.size() == 0. As mentioned earlier, if one did not submit one or more homeworks, he would get 0 grades for the corresponding homeworks. But, the program may encounter the case that there is no any inputs for the homeworks, e.g. when someone did not do homeworks at all. To response this case, the homework grade is set to 0 directly. We need to catch the exception and handle the case in next step.

-

The median grading scheme

We are familar with the first grading scheme, that is, the original scheme we used to compute the final grade, in previous chapters. For convenience sake, I renamed the original functions grade as median_grade (as shown below).

-
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
/ grading scheme 1: final grade is based on the median homework grade
double median_grade(const Student_info &s)
{
return median_grade(s.midterm, s.final, s.homework);
}

// grading scheme 1: overloaded median_grade function
double median_grade(double midterm, double final, const vector<double> &hw)
{
if (hw.size() == 0)
throw domain_error("student has done no homework");
return grade(midterm, final, median(hw));
}

// grading scheme 1: auxiliary median_grade function
double median_grade_aux(const Student_info &s)
{
try{
return median_grade(s);
}catch (domain_error){
// students who did no homework at all, get 0 homework grade
return grade(s.midterm, s.final, 0);
}
}
-

It can be observed that a new function, median_grade_aux ,is included for the median grading scheme. The reason is that we’ll pass the median_gade as an argument, however, we cann’t pass an overloaded function easily. Therefore, we define an auxiliary function and handle the exception in this function. As analysed above, the function return a 0 homework grade once catches an exception.

-

The average grading scheme

The average based final grade is computed exactly the same as the median version as long as we replace the median value as the average value. The function handles the exception in the same manner.

-
1
2
3
4
5
6
7
8
9
10
// grading scheme 2: final grade is based on average homework grades
double average_grade(const Student_info &s)
{
try{
return grade(s.midterm, s.final, average(s.homework));
}catch (domain_error){
// students who did no homework at all, get 0 homework grade
return grade(s.midterm, s.final, 0);
}
}
-

The optimistic_median grading scheme

The third grading scheme is named as optimistic median as it ignores the zero homework grades and consequently improves students’ overall performances. Following code shows how this function works.

-
1
2
3
4
5
6
7
8
9
10
11
12
// grading scheme 3: final grade is based on median of the completed homework grades,
// and students who did no homework at all will get 0 homework grade
double optimistic_median(const Student_info &s)
{
vector<double> nonzero;
remove_copy(s.homework.begin(), s.homework.end(), back_inserter(nonzero), 0);

if(nonzero.empty())
return grade(s.midterm, s.final, 0);
else
return grade(s.midterm, s.final, median(nonzero));
}
-

remove_copy algorithm takes four arguments. It searches from the range denoted by first two iterators, and finds all values that match a value given by its fourth argument. Then, these values are “removed” and the remaining elements are copied into a new vector nonzero. It doesn’t change the input sequence. In addition, back_inserter is applied to append the copied values to the new vector, which ensures that the space is enough for holding all the elements.

-

Analyze the grades

The next step is to apply each of above schemes to each group, that is, to compute the median of each group. Let’s have a look at the function that returns the median grade of a vector students based on the median homework grade.

-
1
2
3
4
5
6
7
double median_analysis(const vector<Student_info> &students)
{
vector<double> grades;

transform(students.begin(), students.end(), back_inserter(grades), median_grade_aux);
return median(grades);
}
-

The function introduces a new algorithm transform which calls median_grade_aux, namely the transform function, and stores the result into a new vector. The first two iterators specify a range of elements to transform. back_inserter provides the destination and ensures that there is enough for the elements. Finally, the function returns the median value of the final grades.

-

Analogously, we can define another two functions that return average homework grade based median grade and optimistic median homework grade based median grade.

-
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
// average
double average_analysis(const vector<Student_info> &students)
{
vector<double> grades;

transform(students.begin(), students.end(), back_inserter(grades), average_grade);
return median(grades);
}

// median of the completed homework
double optimistic_median_analysis(const vector<Student_info> &students)
{
vector<double> grades;
transform(students.begin(), students.end(), back_inserter(grades), optimistic_median);
return median(grades);
}
-

Report the analysis

As described above, we need to compute three types of median grade for both two groups. In addition, we would better to generate a report such that we can compare two groups regarding to one specific type of median grade. The code below shows how we incorporate these driven factors into a analysis function:

-
1
2
3
4
5
6
7
8
void write_analysis(ostream &out, const string &name,
double analysis(const vector<Student_info> &),
const vector<Student_info> &did,
const vector<Student_info> &didnt)
{
out << name << ": median(did) = " << analysis(did)
<< ": median(didnt) = " << analysis(didnt) << endl;
}
-

This function takes four arguments, of which: the first is an object of the outstream; the second is a function; the third and fourth arguments are two objects of vector. What’ new here is that when passing a function as a parameter, we declare the parameter exactly as the same as ddeclare the function itself. Apparently, we’ll pass three analysis function defined above to this write analysis function.

-

A complete program

Now, we can complete the main function:

-

mainfunction.cpp

-
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
#include <iostream>	    // to get the declaration of cin, cout, endl
#include <vector> // to get the declaration of vector
#include "Student_info.h" // to get the declaration of Student_info
#include "did_all_hw.h" // to get the declatation of the predicate on students' records
#include "grades_analysis.h"// to get the declaration of three analysis function
#include "write_analysis.h" // to get the declaration of write_analysis function

using std::vector; using std::cout;
using std::cin; using std::endl;

int main()
{
// students who did and didn't do all their homework
vector<Student_info> did, didnt;

// read the student records and partition time
Student_info student;
while(read(cin, student))
{
if(did_all_hw(student))
did.push_back(student);
else
didnt.push_back(student);
}
// verify thatthe analyses will show us something
if(did.empty())
{
cout << "No student did all the homework!" << endl;
return 1;
}
if(didnt.empty())
{
cout << "Every student did all the homework!" << endl;
return 1;
}

// do the analysis
write_analysis(cout, "median", median_analysis, did, didnt);
write_analysis(cout, "average", average_analysis, did, didnt);
write_analysis(cout, "median of homework turned in", optimistic_median_analysis, did, didnt);

return 0;
}
-

I had seperate other functions into different files according to my own preference:

-
    -
  1. mainfunction.cpp (as shown above).

    -
  2. -
  3. did_all_hw.cpp, did_all_hw.h: including the function did_all_hw.

    -
  4. -
  5. gradingSchemes.cpp, gradingSchemes.h: covering following functions

    -
      -
    • grade, median, average, median_grade, median_grade_aux, average_grade, optimistic_median.
    • -
    -
  6. -
  7. grades_analysis.cpp, grades_analysis.h: covering following functions

    -
      -
    • median_analysis, average_analysis, optimistic_grade_median.
    • -
    -
  8. -
  9. write_analysis.cpp, write_analysis.h: including the function write_analysis.

    -
  10. -
  11. Student_info.cpp, Stuent_info.h: covering following functions

    -
      -
    • compare, read, read_hw.
    • -
    -
  12. -
-

All profiles are presented below in order.

-

did_all_hw.cpp

-
1
2
3
4
5
6
7
8
9
10
#include <algorithm>		// to get the declaration of find
#include "Student_info.h" // to get the declaration of Student_info
#include "did_all_hw.h" // to get the declaration of did_all_hw itself

using std::find;

bool did_all_hw(const Student_info &s)
{
return ((find(s.homework.begin(), s.homework.end(), 0)) == s.homework.end());
}
-

did_all_hw.h

-
1
2
3
4
5
6
7
8
#ifndef GUARD_DID_ALL_HW_H
#define GUARD_DID_ALL_HW_H

#include "Student_info.h"

bool did_all_hw(const Student_info &s);

#endif /* GUARD_DID_ALL_HW_H */
- -

gradingSchemes.cpp

-
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
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
#include <algorithm>	    // to get the declaration of remove_copy
#include <numeric> // to get the declaration of accumulate
#include <stdexcept> // to get the declaration of domain_error
#include <vector> // to get the declaration of vector
#include "Student_info.h" // to get the declaration of Student_info
#include "gradingSchemes.h" // to get the declaration of all functions here to keep consistent

using std::domain_error; using std::istream;
using std::vector; using std::sort;
using std::accumulate;

// final grade function returns weighted average of midterm exam grade,
// final exam grade, and homework grade which will be computed
// using different methods depending on grading schemes
double grade(double midterm, double final, double homework)
{
return 0.2*midterm + 0.4*final + 0.4*homework;
}

// fundermental functions 1: returns the median value of vector<double>
double median(vector<double> vec)
{
// get the size of the vector
typedef vector<double>::size_type vec_size;
vec_size size = vec.size();

// check whether the empty is empty
if (size == 0)
throw domain_error("median of an empty vector");

// sort the grades
sort(vec.begin(), vec.end());

// compute the median homework grade
vec_size mid = size/2;
return size % 2 == 0 ? (vec[mid] + vec[mid - 1])/2 : vec[mid];
}

// fundermental functions 2: returns the average value of vector<double>
double average(const vector<double> &v)
{
// check whether the empty is empty
if (v.empty())
{ throw domain_error("average of an empty vector");}

return accumulate(v.begin(), v.end(), 0.0) / v.size();
}


// grading scheme 1: final grade is based on the median homework grade
double median_grade(const Student_info &s)
{
return median_grade(s.midterm, s.final, s.homework);
}

// grading scheme 1: overloaded median_grade function
double median_grade(double midterm, double final, const vector<double> &hw)
{
if (hw.size() == 0)
throw domain_error("student has done no homework");
return grade(midterm, final, median(hw));
}

// grading scheme 1: auxiliary median_grade function
double median_grade_aux(const Student_info &s)
{
try{
return median_grade(s);
}catch (domain_error){
// students who did no homework at all, get 0 homework grade
return grade(s.midterm, s.final, 0);
}
}

// grading scheme 2: final grade is based on average homework grades
double average_grade(const Student_info &s)
{
try{
return grade(s.midterm, s.final, average(s.homework));
}catch (domain_error){
// students who did no homework at all, get 0 homework grade
return grade(s.midterm, s.final, 0);
}
}

// grading scheme 3: final grade is based on median of the completed homework grades,
// and students who did no homework at all will get 0 homework grade
double optimistic_median(const Student_info &s)
{
vector<double> nonzero;
remove_copy(s.homework.begin(), s.homework.end(), back_inserter(nonzero), 0);

if(nonzero.empty())
return grade(s.midterm, s.final, 0);
else
return grade(s.midterm, s.final, median(nonzero));
}
- -

gradingScheme.h

-
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
#ifndef GUARD_GRADING_SCHEMES_H
#define GUARD_GRADING_SCHEMES_H

#include<vector>
#include "Student_info.h"

double grade(double midterm, double final, double homework);
double median(std::vector<double> vec);
double average(const std::vector<double> &v);
double median_grade(const Student_info &s);
double median_grade(double midterm, double final, const std::vector<double> &hw);
double median_grade_aux(const Student_info &s);
double average_grade(const Student_info &s);
double optimistic_median(const Student_info &s);

#endif /* GUARD_GRADING_SCHEMES_H */
- -

grades_analysis.cpp

-
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
#include <vector>		// to get the declaration of vector
#include <iterator> // to get the declaration of back_inserter
#include <stdexcept> // to get the declaration of domain_error
#include <algorithm> // to get the declaration of transform
#include "Student_info.h" // to get the declaration of Student_info
#include "grades_analysis.h" // to get the declaration of all functions here to keep consistent
#include "gradingSchemes.h" // to get the declaration of median_grade_aux, average_grade, optimistic_median

using std::vector; using std::transform;
using std::domain_error; using std::back_inserter;

// median
double median_analysis(const vector<Student_info> &students)
{
vector<double> grades;

transform(students.begin(), students.end(), back_inserter(grades), median_grade_aux);
return median(grades);
}

// average
double average_analysis(const vector<Student_info> &students)
{
vector<double> grades;

transform(students.begin(), students.end(), back_inserter(grades), average_grade);
return median(grades);
}

// median of the completed homework
double optimistic_median_analysis(const vector<Student_info> &students)
{
vector<double> grades;
transform(students.begin(), students.end(), back_inserter(grades), optimistic_median);
return median(grades);
}
- -

grades_analysis.h

-
1
2
3
4
5
6
7
8
9
10
11
#ifndef GUARD_GRADES_ANALYSIS_H
#define GUARD_GRADES_ANALYSIS_H

#include <vector>
#include "Student_info.h"

double median_analysis(const std::vector<Student_info> &students);
double average_analysis(const std::vector<Student_info> &students);
double optimistic_median_analysis(const std::vector<Student_info> &students);

#endif /* GUARD_GRADES_ANALYSIS_H */
- -

write_analysis.cpp

-
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
#include <iostream>		// to get the declaration of cout, endl, ostream
#include <string> // to get the declaration of string
#include <vector> // to get the declaration of vector
#include "Student_info.h" // to get the declatation of Student_info
#include "write_analysis.h" // to get the declatation of write_analysis itself

using std::cout; using std::endl;
using std::string; using std::vector;
using std::ostream;

void write_analysis(ostream &out, const string &name,
double analysis(const vector<Student_info> &),
const vector<Student_info> &did,
const vector<Student_info> &didnt)
{
out << name << ": median(did) = " << analysis(did)
<< ": median(didnt) = " << analysis(didnt) << endl;
}
-

write_analysis.h

-
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
#ifndef GUARD_WRITE_ANALYSIS_H
#define GUARD_WRITE_ANALYSIS_H

#include <iostream>
#include <string>
#include <vector>
#include "Student_info.h"

void write_analysis(std::ostream &out, const std::string &name,
double analysis(const std::vector<Student_info> &),
const std::vector<Student_info> &did,
const std::vector<Student_info> &didnt);


#endif /* GUARD_WRITE_ANALYSIS_H */
- -

Student_info.cpp

-
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
#include "Student_info.h"
using std::vector; using std::istream;

// argument to the function sort
bool compare(const Student_info &x, const Student_info &y)
{
return x.name < y.name;
}

// read the info
istream & read(istream &is, Student_info &s)
{
// reads and store the student's name, midterm and final exam grades
is >> s.name >> s.midterm >> s.final;

// reads and store all homework grades
read_hw(is, s.homework);
return is;
}

// read all homework grades
istream & read_hw(istream &in, vector<double> &hw)
{
if (in)
{
//get rid of previous contents
hw.clear();

// read homework grades
double x;
while(in >> x)
hw.push_back(x);

// clear the stream so that input will work for the next student
in.clear();
}
return in;
}
- -

Student_info.h

-
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
#ifndef GUARD_Student_info
#define GUARD_Student_info

#include<iostream>
#include<string>
#include<vector>

struct Student_info{
std::string name;
double midterm, final;
std::vector<double> homework;
};

bool compare(const Student_info &, const Student_info &);
std::istream & read(std::istream &, Student_info &);
std::istream & read_hw(std::istream &, std::vector<double> &);
#endif
-

A simple Test

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
Inputs:

Xxzrz 87.1414 3.48485 18 58 0
Oketl 98.0909 57.7273 92 38 22
Gzqrc 91.1515 88.5657 34 2 11
Ojway 86.7576 33.697 16 44 42
Psajl 99.5758 76.9293 12 75 89
Aovlz 88.0101 89.5556 85 2 23
Cpwsr 57.3232 32.697 89 21 54
Izcob 55.3434 49.4141 60 45 12
Ijtvd 96.8788 29.4949 49 66 37
Ldvgy 5.88889 82.5556 1 14 34
Mborx 55.8586 53.1212 45 32 8
Xohgm 82.8182 44.9697 61 29 22
Xotog 59.9293 39.5354 10 54 24
Nfetc 22.6869 18.8788 91 58 5
Kljug 24.3434 74.7273 70 33 59
Zjenp 70.6364 68.9293 80 2 85
Pjsrd 25.4343 24.2323 81 61 72
Lchhb 31.9293 42.2222 0 64 86
Zobiw 70.3535 33.1111 67 96 60
Fsksr 24.1919 25.7677 2 58 94
Dcyzj 84.1818 64.1919 87 0 52
Qcozi 15.7677 27.4343 9 64 58
Eeddp 74.2525 27.2929 20 23 28
Mmtat 61.9596 25.6465 16 2 60
Muvnp 47.5354 20.9091 63 88 24
Azuxm 13.5859 78.6566 0 77 7

Outputs:
median: median(did) = 46.1475: median(didnt) = 42.9273
average: median(did) = 45.4202: median(didnt) = 44.3273
median of homework turned in: median(did) = 46.1475: median(didnt) = 52.1273
- -

Merging three analysis functions into one single function

It has been observed that three analysis functions are in fact defined in the same manner. The only difference between them is the statements:

-
1
transform(students.begin(), students.end(), back_inserter(grades), //transform function to be passed);
-

Therefore, we can redefine the analysis function such that the transform function can be passed to the analysis function as an argument. We have learned how to do this from the write_analysis function. Now let’s define it:

-
1
2
3
4
5
6
7
double analysis(const vector<Student_info> &students, double grading_scheme(const Student_info &s))
{
vector<double> grades;

transform(students.begin(), students.end(), back_inserter(grades), grading_scheme);
return median(grades);
}
-

For example, if we want to compute the average based median grade, we would call this function like:

-
1
analysis(did, average_grade);
-

Correspondingly, we need to change the write_analysis function as well.

-
1
2
3
4
5
6
7
8
void write_analysis(ostream &out, const string &name,
double grading_scheme(const Student_info &s),
const vector<Student_info> &did,
const vector<Student_info> &didnt)
{
out << name << ": median(did) = " << analysis(did, grading_scheme)
<< ": median(didnt) = " << analysis(didnt, grading_scheme) << endl;
}
-

I define the new write_analysis function such that it takes the grading_scheme functions as its second arguments. To qualify the use of the analysis function, I add #include “grade_analysis.h” into write_analysis.cpp. In addition, I add #include “gradingSchemes.h” into mainfunction.cpp for qualifing the use of three grading_scheme functions. Please find the revised files shown in below.

-

modified file 1: grades_analysis.cpp

-
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
#include <vector>		// to get the declaration of vector
#include <iterator> // to get the declaration of back_inserter
#include <stdexcept> // to get the declaration of domain_error
#include <algorithm> // to get the declaration of transform
#include "Student_info.h" // to get the declaration of Student_info
#include "grades_analysis.h" // to get the declaration of all functions here to keep consistent
#include "gradingSchemes.h" // to get the declaration of the median function

using std::vector; using std::transform;
using std::domain_error; using std::back_inserter;

double analysis(const vector<Student_info> &students, double grading_scheme(const Student_info &s))
{
vector<double> grades;

transform(students.begin(), students.end(), back_inserter(grades), grading_scheme);
return median(grades);
}
- -

modified file 2: grades_analysis.h

-
1
2
3
4
5
6
7
8
9
10
#ifndef GUARD_GRADES_ANALYSIS_H
#define GUARD_GRADES_ANALYSIS_H

#include <vector>
#include "Student_info.h"

double analysis(const std::vector<Student_info> &students,
double grading_scheme(const Student_info &s));

#endif /* GUARD_GRADES_ANALYSIS_H */
- -

modified file 3: write_analysis.cpp

-
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
#include <iostream>		// to get the declaration of cout, endl, ostream
#include <string> // to get the declaration of string
#include <vector> // to get the declaration of vector
#include "Student_info.h" // to get the declatation of Student_info
#include "write_analysis.h" // to get the declatation of write_analysis itself
#include "grades_analysis.h" // to get the declaration of analysis function

using std::cout; using std::endl;
using std::string; using std::vector;
using std::ostream;

void write_analysis(ostream &out, const string &name,
double grading_scheme(const Student_info &s),
const vector<Student_info> &did,
const vector<Student_info> &didnt)
{
out << name << ": median(did) = " << analysis(did, grading_scheme)
<< ": median(didnt) = " << analysis(didnt, grading_scheme) << endl;
}
- -

modified file 4: write_analysis.h

-
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
#ifndef GUARD_WRITE_ANALYSIS_H
#define GUARD_WRITE_ANALYSIS_H

#include <iostream>
#include <string>
#include <vector>
#include "Student_info.h"

void write_analysis(std::ostream &out, const std::string &name,
double grading_scheme(const Student_info &s),
const std::vector<Student_info> &did,
const std::vector<Student_info> &didnt);


#endif /* GUARD_WRITE_ANALYSIS_H */
- -

modified file 5: mainfunction.cpp

-
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
#include <iostream>		// to get the declaration of cin, cout, endl
#include <vector> // to get the declaration of vector
#include "Student_info.h" // to get the declaration of Student_info
#include "did_all_hw.h" // to get the declatation of the predicate on students' records
#include "write_analysis.h" // to get the declaration of write_analysis function
#include "gradingSchemes.h" // to get the declaration of three grading functions

using std::vector; using std::cout;
using std::cin; using std::endl;

int main()
{
// students who did and didn't do all their homework
vector<Student_info> did, didnt;

// read the student records and partition time
Student_info student;
while(read(cin, student))
{
if(did_all_hw(student))
did.push_back(student);
else
didnt.push_back(student);
}
// verify thatthe analyses will show us something
if(did.empty())
{
cout << "No student did all the homework!" << endl;
return 1;
}
if(didnt.empty())
{
cout << "Every student did all the homework!" << endl;
return 1;
}

// do the analysis
write_analysis(cout, "median", median_grade_aux, did, didnt);
write_analysis(cout, "average", average_grade, did, didnt);
write_analysis(cout, "median of homework turned in", optimistic_median, did, didnt);

return 0;
}
- -

I tested this program using the same inputs as I used in the original program. They work exactly the same and yield same outputs.

-
-

To be continued.

- -
- - - - - - -
- -
- - - - -
- - - - -
- - -
- - - - -
- - - - - - - - -
-
- - -
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/2018/03/25/Accelerated-C-Solutions-to-Exercises-Chapter-6/index.html b/2018/03/25/Accelerated-C-Solutions-to-Exercises-Chapter-6/index.html deleted file mode 100644 index 4c4d64dd..00000000 --- a/2018/03/25/Accelerated-C-Solutions-to-Exercises-Chapter-6/index.html +++ /dev/null @@ -1,660 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - Accelerated C++ Solutions to Exercises(Chapter 6) | Liam's Blog - - - - - - - - - - - - -
-
- -
-
- - -
- - -

If history repeats itself, and the unexpected always happens, how incapable must Man be of learning from experience?—George Bernard Shaw.

-
- - -
- - - - -
-
- - -
- - 0% -
- - - - -
-
-
- - -
- - -
- - - -
- - - - - -
-

- Accelerated C++ Solutions to Exercises(Chapter 6) -

- - -
- - - - -
- - -

Exercise 6-0

Compile, execute, and test the programs in this chapter.

-

Solution & Results

Please find all programs and detailed analysis on Using library algorithms (Part 1), Using library algorithms (Part 2) and Using library algorithms (Part 3).

-

Exercise 6-1

Reimplement the frame and hcat operations from §5.8.1/93 and §5.8.3/94 to use iterators.

-

Solution & Results

This exercise has been completed here Putting strings together.

-

Exercise 6-2

Write a program to test the find_urls function.

-

Solution & Results

Please find the program and analysis on Using library algorithms (Part 1).

-

Exercise 6-3, 6-4

6-3: What does this program fragment do?

-
1
2
3
vector<int> u(10, 100);
vector<int> v;
copy(u.begin(), u.end(), v.begin());
-

Write a program that contains this fragment, and compile and execute it.

-

6-4: Correct the program you wrote in the previous exercise to copy from u into v. Thereare at least two possible ways to correct the program. Implement both, and describe the relative advantages and disadvantages of each approach.

-

Soltion & Results

The first statement creates an object of vector and initializes it with 10 elements that all equals to 100. The second statement creates an empty (i.e. due to default initialization) vector v . The third statement calls an algorithm copy to copy values in the range [u.begin(), u.end()) into the destination denoted by the third argument v.begin(). However, the destination sequence should be at least as large as the input range. Therefore, the fragment will lead to compilation errors.

-

Method 1

To correct this, we can initialize the v as an vector that has the same size as u. For example

-
1
vector<int> v(10);
-

This statement means that v contains 10 elements that all equals to 0. Then, the program should work fine.

-

Method 2

Alternatively, we can use back_inserter to append the copied elements to v. It naturelly increases the size of v and hence ensures enough space for holding those elements. The third statement is replaced by:

-
1
copy(u.begin(), u.end(), back_inserter(v));
- -

Method 3

There is also an iterator adaptor inserter that creates an insert iterator for successive insertion into a container. Therefore, we can replace the back_inserter with inserter shown as follows

-
1
copy(u.begin(), u.end(), inserter(v, v.begin()));
-

This statement means that the elements of u are copied and inserted into v starting from the begining position of v.

-

comparison

All three methods can correct the original program. The first method is not practical as in most cases we probably don’t know the number of elements to copy. Contrarily, method 2 and method 3 ensures enough space for holding elements to copy. However, two iterator adaptors works differently:

-
    -
  1. back_inserter constructs a back-insert iterator that inserts new elements at the end of a container. The container should have member function push_back.

    -
  2. -
  3. inserter constructs an insert iterator that inserts new elements into a container successively starting from a specified position. The container should have member function insert.

    -
  4. -
-

Apparently, inserter provides more flexibility in inserting elements while back_inserter has more limitations. But more research should be done on their performances when dealing with massive data. I present a simple program to show that all three methods work fine and lead to same results.

-

A complete test program

Test 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
#include <iostream>
#include <vector>
#include <iterator>
#include <algorithm>

using std::cout; using std::endl;
using std::copy; using std::vector;
using std::inserter;using std::back_inserter;

void print(vector<int> &vec)
{
for(vector<int>::const_iterator iter = vec.begin(); iter != vec.end(); ++iter)
cout << *iter << endl;
}

int main()
{
vector<int> u(10, 100);

// method 1: let v has the same size as u
vector<int> v1(10);
copy(u.begin(), u.end(), v1.begin());
cout << "The results of method 1:" << endl;
print(v1);

cout << endl;

// method 2: back_inserter
vector<int> v2;
copy(u.begin(), u.end(), back_inserter(v2));
cout << "The results of method 2:" << endl;
print(v1);

cout << endl;

// method 3: inserter
vector<int> v3;
copy(u.begin(), u.end(), inserter(v3, v3.begin()));
cout << "The results of method 3:" << endl;
print(v3);

return 0;
}
- -

Test Results

-
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
The results of method 1:
100
100
100
100
100
100
100
100
100
100

The results of method 2:
100
100
100
100
100
100
100
100
100
100

The results of method 3:
100
100
100
100
100
100
100
100
100
100
- -

Exercise 6-5

Write an analysis function to call optimistic_median.

-

Solution & Results

Please find the analysis function here Using library algorithms (Part 2).

-

Exercise 6-6

Note that the function from the previous exercise and the functions from §6.2.2/113and §6.2.3/115 do the same task. Merge these three analysis functions into a singlefunction.

-

Solution & Results

Please find the solution strategy and analysis here Using library algorithms (Part 2).

-

Exercise 6-7

The portion of the grading analysis program from §6.2.1/110 that read and classified student records depending on whether they did (or did not) do all the homework is similar to the problem we solved in extract_fails. Write a function to handle this subproblem.

-

Solution & Results

This exercise requires us to seperate the students’ records into two groups: one group contains records that did all homeworks while the other one contains records that did not complete all homeworks. Specifically, we rewrite the main function such that all records will be stored into a vector did first, and then we extract and store the other group records into didnt via a function named extract_didnt. This function can be written exactly the same as the extract_fails function developed using the single-pass solution (see Using library algorithms (Part 3)).

-

I revised the original program described on Using library algorithms (Part 2) by creating a file contains extract_didnt function and the predicate did_all_hw:

-

extract_didnt.cpp

-
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
#include <vector>		// to get the declaration of vector
#include <algorithm> // to get the declaration of stable_partition
#include "Student_info.h" // to get the declaration of Student_info
#include "extract_didnt.h" // to get the declaration of extract_didnt

using std::vector;
using std::stable_partition;


bool did_all_hw(const Student_info &s)
{
return ((find(s.homework.begin(), s.homework.end(), 0)) == s.homework.end());
}

vector<Student_info> extract_didnt(vector<Student_info> &did)
{
vector<Student_info>::iterator iter = stable_partition(did.begin(), did.end(), did_all_hw);
vector<Student_info> didnt(iter, did.end());
did.erase(iter, did.end());
return didnt;
}
-

extract_didnt.h

-
1
2
3
4
5
6
7
8
9
10
#ifndef GUARD_EXTRACT_DIDNT_H
#define GUARD_EXTRACT_DIDNT_H

#include <vector>
#include "Student_info.h"

bool did_all_hw(const Student_info &);
std::vector<Student_info> extract_didnt(std::vector<Student_info> &);

#endif /* GUARD_EXTRACT_DIDNT_H */
- -

Correspondingly, the main function becomes

-

mainfunction.cpp

-
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
#include <iostream>			// to get the declaration of cin, cout, endl
#include <vector> // to get the declaration of vector
#include "Student_info.h" // to get the declaration of Student_inf"
#include "grades_analysis.h" // to get the declaration of three analysis function
#include "write_analysis.h" // to get the declaration of write_analysis function
#include "extract_didnt.h"

using std::vector; using std::cout;
using std::cin; using std::endl;

int main()
{
vector<Student_info> did;

// read the student records
Student_info student;
while(read(cin, student))
{
did.push_back(student);
}

// extract records that don't complete all the homeworks
vector<Student_info> didnt = extract_didnt(did);

// verify thatthe analyses will show us something
if(did.empty())
{
cout << "No student did all the homework!" << endl;
return 1;
}
if(didnt.empty())
{
cout << "Every student did all the homework!" << endl;
return 1;
}

// do the analysis
write_analysis(cout, "median", median_analysis, did, didnt);
write_analysis(cout, "average", average_analysis, did, didnt);
write_analysis(cout, "median of homework turned in", optimistic_median_analysis, did, didnt);

return 0;
}
- -

Exercise 6-8

Write a single function that can be used to classify students based on criteria of your choice. Test this function by using it in place of the extract_fails program, and use it in the program to analyze student grades.

-

Solution & Results

This exercise asks us to generalize the program in exercise 6-7 such that the program can deal with various kinds of criteria. A possible solution is to pass the criteria as arguments to the more generalized function classify which will classifies an input sequence according to the criteria. By doing so, we can pass different criteria to classify the students’ records based on our own preference.
Let’s define such classify function and put it in a sparate file.

-

classify.cpp

-
1
2
3
4
5
6
7
8
9
10
11
12
13
#include <vector>
#include <algorithm>
#include "Student_info.h"

using std::vector; using std::stable_partition;

vector<Student_info> classify(vector<Student_info> &v, bool criteria(const Student_info &s))
{
vector<Student_info>::iterator iter = stable_partition(v.begin(), v.end(), criteria);
vector<Student_info> criteria_false(iter, v.end());
v.erase(iter, v.end());
return criteria_false;
}
- -

classify.h

-
1
2
3
4
5
6
7
8
9
10
#ifndef GUARD_CLASSIFY_H
#define GUARD_CLASSIFY_H

#include <vector>
#include "Student_info.h"

std::vector<Student_info> classify(std::vector<Student_info> &,
bool criteria(const Student_info &));

#endif /* GUARD_CLASSIFY_H */
- -

Simialr to the extract_didnt function defined in exercise 6-7, this function returns a vector that contains all records that do not satisfy the criteria. The input sequence has also been modified and consequently contains only the records that statisfy the criteria.

-

To test the new function, the test program is designed to classify the students’ records as three groups (if available):

-
    -
  1. students who fail the final grade
  2. -
  3. students who pass the grade, and are awarded the first class honours
  4. -
  5. students who pass the grade, and are awarded the second class honours
  6. -
-

Firstly, we classify students according to criteria pgrade, as a result, the records are divided into two groups. By then, students contains only the passing records. Secondly, we further classify students according to another criterion which allowing us to extract students who are awarded the first class honours.

-

The test program includes following files:

-
    -
  1. classify.cpp, classify.h(as shown above)
  2. -
  3. mainfunction.cpp
  4. -
  5. criteria.cpp, criteria.h
  6. -
  7. grade.cpp, grade.h
  8. -
  9. Student_info.cpp, Student_info.h
  10. -
  11. print.cpp, print.h
  12. -
-

I present ecah file according to above order in below followed by the test results.

-

A complete program

mainfunction.cpp

-
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
65
66
67
68
69
#include <iostream>			// to get the declaration of cin, cout, endl
#include <algorithm> // to get the declaration of max
#include <vector> // to get the declaration of vector
#include <string> // to get the declaration of string
#include "Student_info.h" // to get the declaration of Student_info
#include "criteria.h" // to get the declaration of criteria
#include "classify.h" // to get the declaration of classify
#include "print.h" // to get the declaration of print

using std::vector; using std::cout;
using std::cin; using std::endl;
using std::string; using std::max;

int main()
{
vector<Student_info> students;
Student_info record;
string::size_type maxlen = 0;

// read and store all the records, and find the length of the longest name
while(read(cin, record))
{
maxlen = max(maxlen, record.name.size());
students.push_back(record);
}

// classfiy students according to the criteria: fail and pass
vector<Student_info> fails = classify(students, pgrade);

// verify that the analyses will show us something
if(fails.empty())
{
cout << "All students pass the grade!" << endl;
return 1;
}
if(students.empty())
{
cout << "All students fail the grade!" << endl;
return 1;
}

// classfiy students according to the criteria: first class and second class
vector<Student_info> first_class_honours = classify(students, second_class_honours);

// write lines of outputs
cout << "Students who fails the grade include:" << endl;
print(fails, maxlen);

cout << endl;
if(!first_class_honours.empty())
{
cout << "Students who are awarded first class honours include:" << endl;
print(first_class_honours, maxlen);
}
else
cout << "None of students is awarded first class honours." << endl;

cout << endl;
if(!students.empty())
{
cout << "Students who are awarded second class honours include:" << endl;
print(students, maxlen);
}
else
cout << "congratulations, all the students who "
"pass the grade are awarded first class honours." << endl;

return 0;
}
- -

criteria.cpp

-
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
#include "grade.h"
#include "Student_info.h"

bool fgrade(const Student_info &s)
{
return grade(s) < 60;
}

bool pgrade(const Student_info &s)
{
return !fgrade(s);
}

bool first_class_honours(const Student_info &s)
{
return grade(s) >= 85;
}

bool second_class_honours(const Student_info &s)
{
return (!fgrade(s) && !first_class_honours(s));
}
- -

criteria.h

-
1
2
3
4
5
6
7
8
9
10
#ifndef GUARD_CRITERIA_H
#define GUARD_CRITERIA_H

#include "Student_info.h"

bool fgrade(const Student_info &);
bool pgrade(const Student_info &);
bool first_class_honours(const Student_info &);
bool second_class_honours(const Student_info &s);
#endif /* GUARD_CRITERIA_H */
- -

grade.cpp

-
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 <algorithm>
#include <stdexcept>
#include <vector>
#include "grade.h"
#include "Student_info.h"

using std::domain_error; using std::istream;
using std::vector; using std::sort;

// grade function 1
double grade(const Student_info &s)
{
return grade(s.midterm, s.final, s.homework);
}

// grade function 2
double grade(double midterm, double final, const vector<double> &hw)
{
if (hw.size() == 0)
throw domain_error("student has done no homework");
return grade(midterm, final, median(hw));
}

// grade function 3
double grade(double midterm, double final, double homework)
{
return 0.2*midterm + 0.4*final + 0.4*homework;
}

// compute the median of vector<double>
double median(vector<double> vec)
{
// get the size of the vector
typedef vector<double>::size_type vec_size;
vec_size size = vec.size();

// check whether the empty is empty
if (size == 0)
throw domain_error("median of an empty vector");

// sort the grades
sort(vec.begin(), vec.end());

// compute the median homework grade
vec_size mid = size/2;
return size % 2 == 0 ? (vec[mid] + vec[mid - 1])/2 : vec[mid];
}
-

grade.h

-
1
2
3
4
5
6
7
8
9
10
11
12
#ifndef GUARD_grade_h
#define GUARD_grade_h

// grade.h
#include<vector>
#include "Student_info.h"

double grade(const Student_info &);
double grade(double, double, const std::vector<double> &);
double grade(double, double, double);
double median(std::vector<double>);
#endif
- -

Student_info.cpp

-
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 "Student_info.h"
using std::vector; using std::istream;

// argument to the function sort
bool compare(const Student_info &x, const Student_info &y)
{
return x.name < y.name;
}

// read the info
istream & read(istream &is, Student_info &s)
{
// reads and store the student's name, midterm and final exam grades
is >> s.name >> s.midterm >> s.final;

// reads and store all homework grades
read_hw(is, s.homework);
return is;
}

// read all homework grades
istream & read_hw(istream &in, vector<double> &hw)
{
if (in){
//get rid of previous contents
hw.clear();

// read homework grades
double x;
while(in >> x)
hw.push_back(x);

// clear the stream so that input will work for the next student
in.clear();
}
return in;
}
- -

Student_info.h

-
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
#ifndef GUARD_Student_info
#define GUARD_Student_info

#include<iostream>
#include<string>
#include<vector>

struct Student_info{
std::string name;
double midterm, final;
std::vector<double> homework;
};

bool compare(const Student_info &, const Student_info &);
std::istream & read(std::istream &, Student_info &);
std::istream & read_hw(std::istream &, std::vector<double> &);
#endif
- -

print.cpp

-
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
#include <iostream>
#include <vector>
#include <iomanip>
#include <string>
#include "grade.h"
#include "print.h"
#include "Student_info.h"

using std::cout; using std::setprecision;
using std::endl; using std::streamsize;
using std::string; using std::vector;

void print(const vector<Student_info> &records, const string::size_type &maxlen)
{
for (vector<Student_info>::const_iterator iter = records.begin(); iter != records.end(); ++iter)
{
// write the name, blanks
cout << (*iter).name << string(maxlen + 1 - (*iter).name.size(), ' ');

// compute and write the final grade
double final_grade = grade(*iter);
streamsize prec = cout.precision();
cout << setprecision(3) << final_grade << setprecision(prec) << endl;
}
}
- -

print.h

-
1
2
3
4
5
6
7
8
9
10
#ifndef GUARD_PRINT_H
#define GUARD_PRINT_H

#include <string>
#include <vector>
#include "Student_info.h"

void print(const std::vector<Student_info> &records, const std::string::size_type &maxlen);

#endif /* GUARD_PRINT_H */
- -

Test results

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
Inputs:

Phqgh 24.7879 58.6263 64.0505
Nlfdx 95.4242 27.3636 91.0404
Cxggb 16.1818 95.4747 26.7172
Uxwfn 35.9495 3.11111 22.3333
Tkjpr 68.4747 44.6263 57.3737
Pnrvy 16.3535 90.4242 88.0606
Syycq 5.90909 29.7071 50.0606
Ffmzn 84.5455 56.404 66.7677
Vwsre 23.3737 38.1818 82.2929
Fxtls 4.30303 77.0606 73.8687
Dpooe 29.7778 73.9798 12.8687
Ejuvp 55.7475 31.5253 50.5051
Poeyl 91.0707 37.5758 87.5354
Jvrvi 21.8889 22.4646 6.30303
Hwqnq 55.101 59.2424 37.4848
Jjloo 91.3636 74.202 96.2121
Whmsn 34.5354 99.1818 38
Sfzkv 48.8384 7.21212 10.1717
Lyjyh 51 49.1919 56.9899
Nkkuf 89.0202 95.8586 93.4343

Outputs:

Students who fails the grade include:
Phqgh 54
Cxggb 52.1
Uxwfn 17.4
Tkjpr 54.5
Syycq 33.1
Vwsre 52.9
Dpooe 40.7
Ejuvp 44
Jvrvi 15.9
Hwqnq 49.7
Sfzkv 16.7
Lyjyh 52.7

Students who are awarded first class honours include:
Jjloo 86.4
Nkkuf 93.5

Students who are awarded second class honours include:
Nlfdx 66.4
Pnrvy 74.7
Ffmzn 66.2
Fxtls 61.2
Poeyl 68.3
Whmsn 61.8
- -

The test program shows our function works perfectly.

-
-

Exercise 6-9

Use a library algorithm to concatenate all the elements of a vector.

-

Solution & Results

The complete program below gives two possible solutions.

-

The logic of the first solution is that

-
    -
  1. take each element in vector vec as an independent container (i.e. a string).
  2. -
  3. apply copy to each independent string and copy all characters from it into a new string to hold the final result.
  4. -
  5. loop thru the vector vec and repeat step 2.
  6. -
-

Finally, each character contained in each element of the vector vec is copied and stored into the new string, which has the same effect as concatenating all the elements of vec.

-

The first solution should works fine but is not a better solution. Because if we don’t use copy, we can simply concatenate each element of vec without accessing each character.

-

The second solution applies accumulate algorithm to concatenate each element from vec directly. accumulate returns the sum of all elements of a sequence denoted by its first two arguments. The third argument provides the initial value for the summation and determines the type of the returned value. In this case, the initial value is set to an empty string.

-

A complete 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
#include <iostream>	// to get the declaration of cout
#include <algorithm> // to get the declaration of copy
#include <iterator> // to get the declaration of back_inserter
#include <vector> // to get the declaration of vector
#include <string> // to get the declaration of string
#include <numeric> // to get the declaration of accumulate

using std::vector; using std::string;
using std::cout; using std::copy;
using std::endl; using std::back_inserter;
using std::accumulate;

int main()
{
vector<string> vec{"Please", "write", "an", "analysis", "function"};

// method 1
string vecCopy1;
for(vector<string>::const_iterator iter = vec.begin(); iter != vec.end(); ++iter)
{
copy((*iter).begin(), (*iter).end(), back_inserter(vecCopy1));
}
cout << vecCopy1 << endl;

// method 2
string vecCopy2;
vecCopy2 = accumulate(vec.begin(), vec.end(), vecCopy2);
cout << vecCopy2 << endl;

return 0;
}
-

The results below show that both two methods work fine and give correct results.

-

Results

-
1
2
Pleasewriteananalysisfunction
Pleasewriteananalysisfunction
- -
-

Reference

Koenig, A. and Moo, B.E., 2000. Accelerated C++.

-

Lippman, S.B., Lajoie, J. and Moo, B.E., 2013. C++ Primer, 5th edt.

- -
- - - - - - -
- -
- - - - -
- - - - -
- - -
- - - - -
- - - - - - - - -
-
- - -
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/2018/03/25/C-Using-library-algorithms-Part-3/index.html b/2018/03/25/C-Using-library-algorithms-Part-3/index.html deleted file mode 100644 index ae840fab..00000000 --- a/2018/03/25/C-Using-library-algorithms-Part-3/index.html +++ /dev/null @@ -1,610 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - C++ - Using library algorithms (Part 3) | Liam's Blog - - - - - - - - - - - - -
-
- -
-
- - -
- - -

If history repeats itself, and the unexpected always happens, how incapable must Man be of learning from experience?—George Bernard Shaw.

-
- - -
- - - - -
-
- - -
- - 0% -
- - - - -
-
-
- - -
- - -
- - - -
- - - - - -
-

- C++ - Using library algorithms (Part 3) -

- - -
- - - - -
- - -

Revisit the classifying program

Recalling the extract_fails function developed in last chapter:

-
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
vector<Student_info> extract_fails(vector<Student_info>& students)
{
vector<Student_info> fail;
vector<Student_info>::size_type i = 0;
// invariant:elements [0, i) of students represent passing grades
while (i != students.size())
{
if (fgrade(students[i]))
{
fail.push_back(students[i]);
students.erase(students.begin() + i);
}
else
++i;
}
return fail;
}
-

Instead of using member functions like push_back and erase, we can also apply library algorithms to accomplish the task that extracts failing records. Following parts introduce two algorithmic solutions.

-

A two-pass solution

1
2
3
4
5
6
7
8
vector<Student_info> extract_fails(vector<Student_info> &students)
{
vector<Student_info> fails;
remove_copy_if(students.begin(), students.end(), back_inserter(fails), pgrade);

students.erase(remove_if(students.begin(), students.end(), fgrade), students.end());
return fails;
}
-

This function uses remove_copy_if algorithm to copy all failing records from students into a new vector fails. remove_copy_if finds and “removes”(i.e. not copy) all values that satisfy the predicate pgrade. Apparently, pgrade is a predicate on grades and returns true if the final grade is equal or greater than 60. In other words, it is a predicate that reverts the results of calling fgrade.

-
1
2
3
4
bool pgrade(const Student_info &s)
{
return !fgrade(s);
}
-

However, remove_copy_if works similar to remove_copy. Both two algorithms won’t change the input sequence instead they copy the remaining values into a new vector. Therefore, we need one step more to modify students:

-
1
students.erase(remove_if(students.begin(), students.end(), fgrade), students.end());
-

In this statements, remove_if finds and “removes” all values that satisfies the predicate fgrade. However, the tricky point is that how does it remove those faling records?

-

The removal is done by replacing the elements for which pred returns true by the next element for which it does not, and signaling the new size of the shortened range by returning an iterator to the element that should be considered its new past-the-end element. The relative order of the elements not removed is preserved, while the elements between the returned iterator and last are left in a valid but unspecified state (see remove_if).

-

For example, we have a sequence of final grades:

-
1
s0(pass), s1(fail), s2(fail), s3(pass), s4(pass), s5(fail)
-

After the execution of remove_if function like above, the sequence becomes

-
1
s0(pass), ns1(pass), ns2(pass), s3(pass), s4(pass), s5(fail)
-

It copies passing records, s3 and s4, and replacing the faling records, s1 and s2. Then, it returns an iterator that refers to s3. The elements beteen s3 and s.end() (i.e. one past s5) are still there. Therefore, we need to erase them using erase function, resulting that only passing grades are left. The erase function here takes two arguments which denote a range of elements to be erased. It returns the new students.end().

-

A single pass solution

The two pass solution seems not very ideal as it computes each final grade twice. Another algorithm stable_partition solves this by seperating a sequence into two pieces using a predicate. For example,

-
1
stable_partition(students.begin(), students.end(), pgrade);
-

This algorithm rearranges the sequence and put all elements that satisfy pgrade ahead of the elements that do not satisfy pgrade. Then, it returns an iterator that denotes the first element of the second group (i.e. one past the last element of the first group). If all values do not satisfy the pgrade, the returned iterator refers to the first element of the sequence.

-

Now we can rewrite the extract_fail function applying stable_partition algorithm.

-
1
2
3
4
5
6
7
vector<Student_info> extract_fails(vector<Student_info> &students)
{
vector<Student_info>::iterator iter = stable_partition(students.begin(), students.end(), pgrade);
vector<Student_info> fails(iter, students.end());
students.erase(iter, students.end());
return fails;
}
-

A complete program

I revised the program that developed in Exercise 5-6 by replacing the original extract_fails function with above two functions. The new program accomplishes a simple comparison of the two alternatives. All files and the test results can be found in the remainder of this post.

-

file list

-
    -
  1. mainfunction.cpp.
  2. -
  3. extract_fails.cpp, extract_fails.h: declares and defines functions including fgrade, pgrade, extract_fails_m1, extract_fails_m2.
  4. -
  5. grade.cpp, grade.h: declares and defines functions including grade, median.
  6. -
  7. Student_info.cpp, Student_info.h: declares and defines functions including compare, read, read_hw, and Student_info type struct.
  8. -
  9. print.cpp, print.h: declares and defines functions including print.
  10. -
-
-

mainfunction.cpp

-
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
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
// Accelerated C++ Solutions Exercises 6-0: the revised extract_fails
#include <algorithm>
#include <stdexcept>
#include <iostream>
#include <vector>
#include <string>
#include <iterator>
#include <chrono>
#include "Student_info.h"
#include "grade.h"
#include "print.h"
#include "extract_fails.h"

using std::cin; using std::sort; using std::max;
using std::cout; using std::endl; using std::domain_error;
using std::string; using std::max; using std::stable_partition;
using std::vector; using std::remove_copy;
using std::remove_copy_if; using std::back_inserter;

int main()
{
vector<Student_info> students;
Student_info record;
string::size_type maxlen = 0;

// read and store all the records, and find the length of the longest name
while(read(cin, record))
{
maxlen = max(maxlen, record.name.size());
students.push_back(record);
}
try{
// measure the performance for two pass solution
typedef std::chrono::high_resolution_clock Clock;
Clock::time_point startTime = Clock::now(); // get current time
vector<Student_info> fails = extract_fails_m1(students); // extract records for failing students
Clock::time_point endTime = Clock::now(); // get current time

cout << "The two pass solution took me "
<< std::chrono::duration_cast<std::chrono::duration<double>>(endTime - startTime).count() << " seconds" << endl;


// measure the performance for one pass solution
startTime = Clock::now(); // get current time
fails = extract_fails_m2(students); // extract records for failing students
endTime = Clock::now(); // get current time

cout << "The one pass solution took me "
<< std::chrono::duration_cast<std::chrono::duration<double>>(endTime - startTime).count() << " seconds" << endl;

// write each line of outputs for passing students
if (!students.empty())
{
//alphabetize the records

sort(students.begin(), students.end(), compare);
cout << "Students who passed: " << endl;
print(students, maxlen);
}
else
cout << "What a pity! all students failed.";

// write a blank line
cout << endl;

// // write each line of outputs for failing students
if(!fails.empty())
{
//alphabetize the records

sort(fails.begin(), fails.end(), compare);
cout << "Students who failed: " << endl;
print(fails, maxlen);
}
else
cout << "Congratulations! all students passed.";

}catch(domain_error e){
cout << e.what();
}

return 0;
}
- -

extract_fails.cpp

-
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
#include <vector>			
#include <algorithm>
#include <iterator>
#include "Student_info.h"
#include "grade.h"
#include "extract_fails.h"

using std::vector; using std::stable_partition;
using std::remove_if; using std::remove_copy_if;
using std::back_inserter;

// predicate
bool fgrade(const Student_info &s)
{
return grade(s) < 60;
}
// predicate
bool pgrade(const Student_info &s)
{
return !fgrade(s);
}

// two-pass solution
vector<Student_info> extract_fails_m1(vector<Student_info> &students)
{
vector<Student_info> fails;
remove_copy_if(students.begin(), students.end(), back_inserter(fails), pgrade);

students.erase(remove_if(students.begin(), students.end(), fgrade), students.end());
return fails;
}

// single-pass solution
vector<Student_info> extract_fails_m2(vector<Student_info> &students)
{
vector<Student_info>::iterator iter = stable_partition(students.begin(), students.end(), pgrade);
vector<Student_info> fails(iter, students.end());
students.erase(iter, students.end());
return fails;
}
- -

extract_fails.h

-
1
2
3
4
5
6
7
8
9
10
11
12
#ifndef GUARD_EXTRACT_FAILS_H
#define GUARD_EXTRACT_FAILS_H

#include <vector>
#include "Student_info.h"

bool fgrade(const Student_info &);
bool pgrade(const Student_info &);
std::vector<Student_info> extract_fails_m1(std::vector<Student_info> &);
std::vector<Student_info> extract_fails_m2(std::vector<Student_info> &);

#endif /* GUARD_EXTRACT_FAILS_H */
- -

grade.cpp

-
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
#include <algorithm>
#include <stdexcept>
#include <vector>
#include "grade.h"
#include "Student_info.h"

using std::domain_error; using std::istream;
using std::vector; using std::sort;

// grade function 2
double grade(const Student_info &s)
{
return grade(s.midterm, s.final, s.homework);
}

// grade function 2
double grade(double midterm, double final, const vector<double> &hw)
{
if (hw.size() == 0)
throw domain_error("student has done no homework");
return grade(midterm, final, median(hw));
}

// grade function 3
double grade(double midterm, double final, double homework)
{
return 0.2*midterm + 0.4*final + 0.4*homework;
}

// compute the median of vector<double>
double median(vector<double> vec)
{
// get the size of the vector
typedef vector<double>::size_type vec_size;
vec_size size = vec.size();

// check whether the empty is empty
if (size == 0)
throw domain_error("median of an empty vector");

// sort the grades
sort(vec.begin(), vec.end());

// compute the median homework grade
vec_size mid = size/2;
return size % 2 == 0 ? (vec[mid] + vec[mid - 1])/2 : vec[mid];
}
···

**grade.h**
```c++
#ifndef GUARD_grade_h
#define GUARD_grade_h

// grade.h
#include<vector>
#include "Student_info.h"

double grade(const Student_info &);
double grade(double, double, const std::vector<double> &);
double grade(double, double, double);
double median(std::vector<double>);
#endif
- -

Student_info.cpp

-
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 <vector>
#include <iostream>
#include "Student_info.h"

using std::vector; using std::istream;

bool compare(const Student_info &x, const Student_info &y)
{
return x.name < y.name;
}

istream & read(istream &is, Student_info &s)
{
// reads and store the student's name, midterm and final exam grades
is >> s.name >> s.midterm >> s.final;

// reads and store all homework grades
read_hw(is, s.homework);
return is;
}

istream & read_hw(istream &in, vector<double> &hw)
{
if (in){
//get rid of previous contents
hw.clear();

// read homework grades
double x;
while(in >> x)
hw.push_back(x);

// clear the stream so that input will work for the next student
in.clear();
}
return in;
}
- -

Student_info.h

-
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
#ifndef GUARD_Student_info
#define GUARD_Student_info

#include<iostream>
#include<string>
#include<vector>

struct Student_info{
std::string name;
double midterm, final;
std::vector<double> homework;
};

bool compare(const Student_info &, const Student_info &);
std::istream & read(std::istream &, Student_info &);
std::istream & read_hw(std::istream &, std::vector<double> &);
#endif
- -

print.cpp

-
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
#include <iostream>
#include <vector>
#include <iomanip>
#include <string>
#include "grade.h"
#include "print.h"
#include "Student_info.h"

using std::cout; using std::setprecision;
using std::endl; using std::streamsize;
using std::string; using std::vector;

void print(const vector<Student_info> &records, const string::size_type &maxlen)
{
for (vector<Student_info>::const_iterator iter = records.begin(); iter != records.end(); ++iter)
{
// write the name, blanks
cout << (*iter).name << string(maxlen + 1 - (*iter).name.size(), ' ');

// compute and write the final grade
double final_grade = grade(*iter);
streamsize prec = cout.precision();
cout << setprecision(3) << final_grade << setprecision(prec) << endl;
}
}
- -

print.h

-
1
2
3
4
5
6
7
8
9
10
#ifndef GUARD_PRINT_H
#define GUARD_PRINT_H

#include <string>
#include <vector>
#include "Student_info.h"

void print(const std::vector<Student_info> &, const std::string::size_type &);

#endif /* GUARD_PRINT_H */
- -

Performance comparison

- - - - - - - - - - - - - - - - - - - - - - -
Number of linesTwo-pass solutionSingle-pass solution
100.001035 seconds0.000000 seconds
10000.006006 seconds0.000993 seconds
100000.017035 seconds0.003023 seconds
-

The results shows as expected that the single-pass solution has much better performance that the two-pass solution.

-

A simple summary

Algorithm act on container elements but do not act on containers and do not change the size of a container.

- -
- - - - - - -
- -
- - - - -
- - - - -
- - -
- - - - -
- - - - - - - - -
-
- - -
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/2018/03/27/C-Using-associative-containers/index.html b/2018/03/27/C-Using-associative-containers/index.html deleted file mode 100644 index 36635515..00000000 --- a/2018/03/27/C-Using-associative-containers/index.html +++ /dev/null @@ -1,619 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - C++ - Using associative containers (Part 1) | Liam's Blog - - - - - - - - - - - - -
-
- -
-
- - -
- - -

If history repeats itself, and the unexpected always happens, how incapable must Man be of learning from experience?—George Bernard Shaw.

-
- - -
- - - - -
-
- - -
- - 0% -
- - - - -
-
-
- - -
- - -
- - - -
- - - - - -
-

- C++ - Using associative containers (Part 1) -

- - -
- - - - -
- - -

Introduction

Unlike sequential containers, associative containers store data into a sequence depending on the values of the elements themselves rather than the sequence where we inserted them. An associative container supports efficient lookup and retrieval by a key which is the part of each element. The most common kind of assiciative data structure, namely the associative array, is one that stores key-value pairs, associating a value with each each key. In C++, such an associative array is called a map.

-

The keys in fact plays a role as an index for the values, which are similar to the index of a vector. But the difference is that the keys can be int* type or string type or any other types that allows ordering.

-

Example 1 - counting words

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
int main()
{
string s;
map<string, int> counters; // store each word and an associated counter

// read the input, keeping track of each word and how often we see it
while (cin >> s)
{
++counters[s];
}
// write the words and associated counts
for (map<string, int>::const_iterator it = counters.begin(); it != counters.end(); ++it)
{
cout << it->first << "\t" << it->second << endl;
}
return 0;
}
-

Key points

-
    -
  1. when we define a map, we need to specify the types for both key and value, i.e. a key-value pair. In this case, the key has type of string while the value has type of int. Such a container can be described as a map from string to int.

    -
  2. -
  3. from above, we know that each element in a map has type pair which is a data structure that holds two elements named first and second. For a map that has a key of type K, and a value of type V, the asociated pair type is pair<const K, V>. In this case, the elements of counters have type of pair<const string, int>.

    -
  4. -
  5. the map counters is constructed with default initialization, leading to an empty map.

    -
  6. -
  7. when we start to store words, the element pair<string, int> is value-initialized, that is, the key is initialized as an empty string and the value is initialized as 0.

    -
  8. -
  9. map supports operations via subscripting.

    -
    c[k] returns the **k**-associated value; if **k** doesn't exist, it adds **k** to the container, initialize and returns its associated value.

    In this case, when a word appears for the first time, counters[s] returns the associated value which is initialized with value 0. Then we increment the value via ++counters[s].

    -
  10. -
  11. the for loop shows how to access elements in a map using iterators. The operations are similar to those on a vactor. When we deference a map<string, int> iterator, we get a pair<const string, int>. Therefore, it->first extracts the value of the first element in the pair, that is, the key, while it->second gives the value of the second element in the pair, that is, the value associated with the key.

    -
  12. -
-

We can also access the value via subscripting as described at key point 5. For example,

-
1
cout << counters[it->first];
-

has the same effect as

-
1
cout << it->second;
- -

Example 2 - Generating a cross-reference table

This example extends above program such that the new program can generate a cross-reference table that indicates where each word occurs in the input. It requires:

-
    -
  1. read a line at a time for the purpose of obtaining the associated line number.
  2. -
  3. To separate each word from a line, we need a function like split described in Chpter 6. But rather than calling the function independently, we will pass it as an argument to the cross-reference function (denoted by xref).
  4. -
-
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
// find all the lines that refer to each word in the input
map<string, vector<int> > xref(istream & in,
vector<string> find_words(const string &) = split)
{
string line;
int line_number = 0;
map<string, vector<int> > ret;

// read the next line
while(getline(in, line))
{
++line_number;

// break the input line into words
vector<string> words = find_words(line);

// remember that each word occurs on the current line
for (vector<string>::const_iterator it = words.begin(); it != words.end(); ++it)
{
ret[*it].push_back(line_number);
}
return ret;
}
}
-

Let’s analyse this function:

-
    -
  1. what xref should return is a map<string, vector > as in fact we aims to build a map from the word to its line number. The key, representing each distinct word, has type string. The value, representing line numbers associated with each key, has type vector due to the fact that one word may appears in different lines.

    -
  2. -
  3. xref takes two arguments, one is an input stream object; another one is a function to extract words from a line. We are familar with how to define a function parameter. But what’ new here is to use ** = split** in defining such a function parameter.This indicates that this parameter has a default argument, i.e. split. It means that if xref is called without passing this argument, it uses the default argument. If xref is called with passing a new argument, it uses the new argument. For example

    -
    1
    2
    xref(cin); // uses split to find words in the input stream
    xref(cin, find_urls); // uses the function named find_urls to find words
  4. -
  5. the function begins with defining three varaibles: the first is a string named line to hold each line of input; the second is a integer that denotes the line number; the third is a map<string, vector > to hold each pair of word and line numbers.

    -
  6. -
  7. every iteration of the while statement, one line of input is read and broken into words. Then, each word is accessed via iterator and stored into the map together its line number. The core statement is:

    -
    1
    ret[*it].push_back(line_number);
    -

    As mentioned earlier, if *it, a word, doesn’t appear before, it will be stored and ret[it] returns an default initialized associated value, i.e. an empty vector. Then, we use *push_back** to append the line number to the end of the vector. If *it has already appeared before, ret[*it] returned the associated value (i.e. vector) and the new line number will be appended to the end of the vector.

    -
  8. -
-

A complete program

Now, I add #include directives and present all files including: mainfunction.cpp, xref.cpp, xref.h, split.cpp, split.h. This program uses the default split function.

-

mainfunction.cpp

-
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
int main()
{
// call xref using split by default
map<string, vector<int> > ret = xref(cin);

// write the results
for(map<string, vector<int> >::const_iterator it = ret.begin();
it != ret.end(); ++it)
{
// write the word
cout << it->first << " occurs on line(s): ";

// followed by one or more line_numbers
vector<int>::const_iterator line_it = it->second.begin();
cout << *line_it; // write the first line number

++line_it;
while (line_it != it->second.end())
{
cout << ", " << *line_it;
++line_it;
}
// write a new line to separate each word from the next
cout << endl;
}
return 0;
}
-

There is nothing new in above code. The program writes the first line number and the rest(if there exist) separately for the purpose of separating line numbers with a comma followed by a space.

-

xref.cpp

-
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
#include <iostream>	// to get the decalration of istream
#include <map> // to get the declaration of map
#include <vector> // to get the declaration of vector
#include <string> // to get the declaration of string
#include "split.h" // to get the declaration of split
#include "xref.h" // to get the declatation of xref

using std::map; using std::vector;
using std::string; using std::istream;

map<string, vector<int> > xref(istream &in,
vector<string> find_words(const string &))
{
string line;
int line_number = 0;
map<string, vector<int> > ret;

// read the next line
while(getline(in, line))
{
++line_number;

// break the input line into words
vector<string> words = find_words(line);

// remember that each word occurs on the current line
for(vector<string>::const_iterator it = words.begin(); it != words.end();
++it)
{
ret[*it].push_back(line_number);
}
}
return ret;
}
-

xref.h

-
1
2
3
4
5
6
7
8
9
10
11
12
13
#ifndef GUARD_XREF_H
#define GUARD_XREF_H

#include <map>
#include <vector>
#include <string>
#include <iostream>
#include "split.h"

std::map<std::string, std::vector<int> > xref(std::istream &,
std::vector<std::string> find_words(const std::string &) = split);

#endif /*GUARD_XREF_H */
-

It is worth noting that if xref is separated from the main function file, the default argument for the parameter (i.e. split in this case) should be put into its header file only. This is becasue that the default argument for a given parameter can only be specified once. (more discussion can be found on this page Default value of function parameter -).

-

split.cpp

-
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
#include <vector>	// to get the declarartion of vector
#include <string> // to get the declaration of string
#include <algorithm> // to get the declaration of find_if
#include "split.h" // to get the declaration of split

using std::vector; using std::string;
using std::find_if;

// true if the argument is whitespace, false otherwise
bool space(char c)
{
return isspace(c);
}

// false if the argument is whitesapce, true otherwise
bool not_space(char c)
{
return !isspace(c);
}

// function extracts words from a line of input
vector<string> split(const string &str)
{
typedef string::const_iterator iter;
vector<string> ret;

iter i = str.begin();
while (i != str.end()){
// ignore leading blanks
i = find_if(i, str.end(), not_space);

// find end of next word
iter j = find_if(i, str.end(), space);

// copy the characters in [i,j)
if(i != str.end())
ret.push_back(string(i, j));
i = j;
}
return ret;
}
- -

split.h

-
1
2
3
4
5
6
7
8
9
10
11
#ifndef GUARD_SPLIT_H
#define GUARD_SPLIT_H

#include <vector>
#include <string>

bool space(char);
bool not_space(char);
std::vector<std::string> split(const std::string &);

#endif /* GUARD_SPLIT_H */
- -

Test 1

From a simple test shown below, it can be seen that the program works as expected.

-
1
2
3
4
5
6
7
8
9
10
11
12
13
14
Inputs:

do you like me
no I do not like you

Outputs:

I occurs on line(s): 2
do occurs on line(s): 1 2
like occurs on line(s): 1 2
me occurs on line(s): 1
no occurs on line(s): 2
not occurs on line(s): 2
you occurs on line(s): 1 2
- -

Test 2

Sometimes we may want to use another strategy to extract words, for example, when extracting URLs like we did in Finding URLs. I add revelent files first.

-

find_urls.cpp

-
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
// function that finds and returns an URL
#include "find_urls.h"
#include <vector>
#include <string>
#include "delimit.h"

using std::vector;
using std::string;

vector<string> find_urls(const string &s)
{
vector<string> ret;
typedef string::const_iterator iter;
iter b = s.begin(), e = s.end();

// look through the entire input
while (b != e)
{
// look for one or more letters followed by ://
b = url_beg(b, e);

// if we found it
if(b != e)
{
// get the rest of the URL
iter after = url_end(b, e);

// remember the URL
ret.push_back(string(b, after));

// advance b and check for more URLs on this line
b = after;
}
}
return ret;
}
- -

find_urls.h

-
1
2
3
4
5
6
7
8
9
#ifndef GUARD_FINDINGURLS_H
#define GUARD_FINDINGURLS_H

#include <vector>
#include <string>

std::vector<std::string> find_urls(const std::string &);

#endif /* GUARD_FINDINGURLS_H */
- -

dilimit.cpp

-
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
// contains three functions: not_url_char, url_beg, url_end
#include <string>
#include <algorithm>
#include "delimit.h"

using std::string; using std::find;
using std::find_if; using std::search;

// predicate on a char, check whether it is a char that can appear in a URL
bool not_url_char(char c)
{
// characters, in addition to alphanumerics, that can appear in a URL
static const string url_ch = "~;/?:@=&$-_.+!*'(),";

// see whether c can appear in a URL and return the negative
return !(isalnum(c) || find(url_ch.begin(), url_ch.end(), c) != url_ch.end());
}

// function that returns an iterator that refers to the first element of a URL
string::const_iterator url_beg(string::const_iterator b, string::const_iterator e)
{
static const string sep = "://";
typedef string::const_iterator iter;

// i marks where the separator was found
iter i = b;
while((i = search(i, e, sep.begin(), sep.end())) != e)
{
// make sure the seperator isn't at the begining of the proticol-name
if(i != b && i + sep.size() != e)
{
// beg marks the begining of the protocol-name
iter beg = i;
while(beg != b && isalpha(beg[-1]))
--beg;

// is there at least one appropriate character before and after the sep
if (beg != i && !not_url_char(i[sep.size()]))
return beg;
}
// the seperator we found wasn't part of a URL advance i past this separator
i += sep.size();
}
return e;
}

// function that returns an iterator that denotes the postion one past the last element
string::const_iterator url_end(string::const_iterator b, string::const_iterator e)
{
return find_if(b, e, not_url_char);
}
- -

delimit.h

-
1
2
3
4
5
6
7
8
9
10
#ifndef GUARD_DELIMIT_H
#define GUARD_DELIMIT_H

#include <string>

bool not_url_char(char);
std::string::const_iterator url_beg(std::string::const_iterator, std::string::const_iterator);
std::string::const_iterator url_end(std::string::const_iterator, std::string::const_iterator);

#endif /* GUARD_DELIMIT_H */
- -

Now, let’s call xref with passing argument find_urls:

-
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
int main()
{
// call xref using split by default
map<string, vector<int> > ret = xref(cin, find_urls);

// write the results
for(map<string, vector<int> >::const_iterator it = ret.begin();
it != ret.end(); ++it)
{
// write the word
cout << it->first << " occurs on line(s): ";

// followed by one or more line_numbers
vector<int>::const_iterator line_it = it->second.begin();
cout << *line_it; // write the first line number

++line_it;
while (line_it != it->second.end())
{
cout << ", " << *line_it;
++line_it;
}
// write a new line to separate each word from the next
cout << endl;
}
return 0;
}
-

Then, we type following inputs:

-
1
2
3
A typical URL could have the form https://en.wikipedia.org/wiki/URL, 
which indicates a protocol (http), a hostname (www.example.com),
and a file name (index.html). http://www.cplusplus.com/reference/algorithm/search/?kw=search
- -

The program give results as expected:

-
1
2
http://www.cplusplus.com/reference/algorithm/search/?kw=search occurs on line(s): 3
https://en.wikipedia.org/wiki/URL, occurs on line(s): 1
-
- -
- - - - - - -
- -
- - - - -
- - - - -
- - -
- - - - -
- - - - - - - - -
-
- - -
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/2018/03/28/C-Using-associative-containers-Part-2/index.html b/2018/03/28/C-Using-associative-containers-Part-2/index.html deleted file mode 100644 index 1d9ff960..00000000 --- a/2018/03/28/C-Using-associative-containers-Part-2/index.html +++ /dev/null @@ -1,625 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - C++ - Using associative containers (Part 2) | Liam's Blog - - - - - - - - - - - - -
-
- -
-
- - -
- - -

If history repeats itself, and the unexpected always happens, how incapable must Man be of learning from experience?—George Bernard Shaw.

-
- - -
- - - - -
-
- - -
- - 0% -
- - - - -
-
-
- - -
- - -
- - - -
- - - - - -
-

- C++ - Using associative containers (Part 2) -

- - -
- - - - -
- - -

Example 3 - Generating sentences

This section introduces how to write a program that can randomly generate a sentence given certain grammar rules. For example, given following input

-
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
Categories        Rules

<noun> cat
<noun> dog
<noun> table
<noun-phrase> <noun>
<noun-phrase> <adjective> <noun-phrase>
<adjective> large
<adjective> brown
<adjective> absurd
<verb> jumps
<verb> sits
<location> on the stairs
<location> under the sky
<location> wherever it wants
<sentence> the <noun-phrase> <verb> <location>
- -

The program might generate such a sentence:

-
1
the cat sits on the stairs
-

Some stylized facts can be observed:

-
    -
  1. There are two types of element in the inputs, one type contains a string enclosed by a pair of angle brackets and the other type contains one or more strings. We can always find direct or indirect mapping relations from each first type element to the second type elements.

    -
  2. -
  3. The first type represents categories of the components that constitute a sentence. One or more categories can construct compound categories. The second type represents the smallest building blocks of a sentence.

    -
  4. -
  5. The sentence structure is determined by the Rule associated with the category . A Rule may contain either categories or most basic building blocks or mixed.

    -
  6. -
-

Therefore, to construct a sentence, we need to

-
    -
  1. find , and then find the associated Rule.
  2. -
  3. start to find each element of a sentence follow the instructions of the Rule.
  4. -
  5. if the element is already the most basic building block, then we just store it into a vector for the final output. If the element is a category (i.e. the first type of element), we’ll find the associated Rule recursively, until that we find any of the most basic building blocks (i.e. the second type of element).
  6. -
-

Now, the soluction strategy can be logically divided into three parts:

-
    -
  • part 1: read and store the grammar including categories and rules into a map
  • -
  • part 2: applying above steps to find all needed elements for a sentence
  • -
  • part 3: write the sentence on the output device.
  • -
-

Read the grammar

Seen from above example, we can built a map from the first colunm to the second column, two elements of which in each row construct a key-value pair. The elements in the first column have data type string. But what’s the data type for the elements of the second column? We know that each rule may contains one or more strings and hence each rule can be stored into a vector:

-
1
vector<string> Rule;
-

Also, the mapping from the first column to the second column has one-to-many relations,for example, one maps to several rules such as cat, dog and table. Therefore, we can store each rule into a high-level vector:

-
1
vector<vector<string>> Rule_collection;
-

For the sake of brevity, we can use type alias in declaring such a map:

-
1
2
3
typedef vector<string> Rule;
typedef vector<Rule> Rule_collection;
typedef map<string, Rule_collection> Grammar;
-

Let’s see how to read the grammar into such a map:

-
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
Grammar read_grammar(istream &in)
Grammar ret;
string line;

// read the input
while (getline(in, line))
{
// split the input into words
vector<string> entry = split(line);
if(!entry.empty())
// use the category to store the associated rule
ret[entry[0]].push_back(Rule(entry.begin() + 1, entry.end()));
}
return ret;
}
-

The function returns a map Grammar that contains all grammar rules to be applied to generate a sentence in the next step. There is only one argument, an input stream object, to be passed.

-

Inside of the function body, the first statement defines an empty map ret for holding the grammar, and the second statement defines an empty string for holding each row of input containing the key (i.e. one category) and the value (i.e. one rule). The next is a while loop to read the input repeatedly and read one line once. When the first line is read in, we need to extract all words contained in the line. Then, for the first word (i.e. the category represented by a string enclosed by a pair of angle brackets), we store it into the map as the key, while for the following words, we store them as the value that associates with the key. The core statement is

-
1
ret[entry[0]].push_back(Rule(entry.begin() + 1, entry.end()));
-

It seems complex but actually there is noting new in it. entry is the vector returned by the split function and hence contains all words extracted from the line of inputs. ret[entry[0]] stores the first word (if the word is new for the key), i.e. the category, and returns its associated value, i.e. Rule_collection. Then, we uses push_back to store the associated Rule which is a vector . The Rule is filled with values from the range [entry.begin() + 1, entry.end()) using

-
1
vector<string>(iterator_first, iterator_last);
-

The last statement is to return the Grammar ret.

-

Generate a sentence

Let’s consider the function that generate a sentence.

-
1
2
3
4
5
6
7
// generating the sentence
vector<string> gen_sentence(const Grammar &g)
{
vector<string> ret;
gen_aux(g, "<sentence>", ret);
return ret;
}
-

Apparently, we need a vector to hold the generated sentence. The only argument to be passed is the value returned by the function read_grammar.

-

The function that really deals with generating a sentence is named as gen_aux. It has three parameters, the first one is the grammar produced by read_grammar, the second one is a keyword ““ to be searched, the third one is a vector to hold the final results.

-

The function is defined below:

-
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
void gen_aux(const Grammar &g, const string &word, vector<string> &ret)
{
if(!bracketed(word)){
ret.push_back(word);
}
else
{
// locate the rule that corresponds to word
Grammar::const_iterator it = g.find(word);
if(it == g.end())
throw logic_error("empty rule");

// fetch the set of possible rules
const Rule_collection &c = it->second;

// from which we select one at random
const Rule &r = c[nrand(c.size())];

// recursively expand teh selected rule
for (Rule::const_iterator i = r.begin(); i != r.end(); ++i)
gen_aux(g, *i, ret);
}
}
-

The logic of this function is exactly as same as we described above. Ignoring the if-else statement first, the first step is to find the category :

-
1
Grammar::const_iterator it = g.find(word);
-

The member function find finds and returns an iterator that refers to the element with the key equivalent to the given k. If such element doesn’t exist in the map, the find function returns iterator g.end(). Therefore, if there exists such an iterator, getting the associated Rule_collection:

-
1
2
3
4
5
if(it == g.end())
throw logic_error("empty rule");

// fetch the set of possible rules
const Rule_collection &c = it->second;
-

By now, there exists two problems to be solved:

-
    -
  1. how to randomly pick one rule from Rule_collection
  2. -
  3. if one rule is picked, how to deal with the case that its elements are still categories.
  4. -
-

Let’s put question 1 last and solve question 2 first. Assuming we have picked one rule from the Rule_collection, we then scan each element of it and store the element if the element is the most basic building block. But if the element is still one of the categories, what we need to do is to find the lower level rule (i.e. its associated value). Therefore, we just repeat above processes until the element is not a category.
The if-else statement controls the recursive process while the condition that ceases the recursion is a predicate which returns true if the element is not a category:

-
1
2
3
4
bool bracketed(const string &s)
{
return s.size() > 1 && s[0] == '<' && s[s.size() - 1] == '>';
}
- -

The function gen_sentence is recursively called in the for loop:

-
1
2
3
// recursively expand teh selected rule
for (Rule::const_iterator i = r.begin(); i != r.end(); ++i)
gen_aux(g, *i, ret);
- -

Random drawing

Now the last piece is to randomly pick a rule from Rule_collection.
One possible solution is using rand() % n. rand() is an algorithm defined in standard header and gives a random integer in the range [0, RAND_MAX]. The upper bound is a large number defined in . rand() % n, computes the remainder when deviding the random number by n and hence gives a random integer in the range [0, n). If we set n = c.size(), we can then obtain an random index that yields a rule via c[rand() % c.size]. However, this solution is not a good choice due to(Koenig and Moo 2000):

-
    -
  1. rand() returns pseudo-random numbers. Many C++ implementations’ pseudo-random numbers give remainders that aren’t very random when the quotients are small integers.

    -
  2. -
  3. if n is large and RAND_MAX is not evenly divisible by n,some remainder will appear more often than others.

    -
  4. -
-

To circumvent these issues, we can divide the range of available numbers into buckets of exactly equal size:

-
1
const int bucket_size = RAND_MAX /n;
-

Then, bucket 0 has values from [0, bucket_size), bucket 1 has values from [bucket_size, bucket_size*2)…..

-

Then we can random draw a number and get the bucket number where the random number is located in via:

-
1
int r = rand() / bucket_size;
-

But there exist situations that the random number does not fall into any bucket due to the fact that RAND_MAX may be not evenly divisible by n. Therefore, we uses a do while statement to repeat above statement until it finds a random number that locates in the range of one of buckets.

-

The function nrand is shown below:

-
1
2
3
4
5
6
7
8
9
10
11
12
13
14
// return a random integer in the range [0, n)
int nrand(int n)
{
if(n <= 0 || n > RAND_MAX)
throw domain_error("Argument to nrand is out of range");

const int bucket_size = RAND_MAX /n;
int r;

do r = rand() / bucket_size;
while(r >= n);

return r;
}
-

Noting that rand() uses a seed to generate the sequence, which should be initialized to some distinctive value using void srand(unsigned int seed). A common practice is to use distinctive runtime value, like the value returned by function time. For example

-
1
srand (time(NULL));
-

In addition, srand() is in fact has global effect on rand() and hence can be stated at the very begining of the main function.

-

A complete program

Now I files all functions and code discussed above and present the compete program below. The main function easy to understand and hence no further analysis here.

-

mainfunction.cpp

-
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 <cstdlib>		// to get the declaration of srand
#include <ctime> // to get the declaration of time
#include <vector> // to get the declaration of vector
#include <string> // to get the declaration of string
#include "read_grammar.h" // to get the declaration of read_grammar
#include "gen_sentence.h" // to get the declaration of gen_sentence

using std::cin; using std::cout;
using std::vector; using std::endl;
using std::string; using std::srand;
using std::time;

int main()
{
// initialize random number generator with distinctive runtime value
srand (time(NULL));

// generate the sentence
vector<string> sentence = gen_sentence(read_grammar(cin));

// write the first word, if any
vector<string>::const_iterator it = sentence.begin();
if(!sentence.empty())
{
cout << *it;
++it;
}

// write the rest of the words, each preceded by a space
while(it != sentence.end())
{
cout << " " << *it;
++it;
}
cout << endl;
return 0;
}
- -

type_alias.h

-
1
2
3
4
5
6
7
8
9
10
11
12
#ifndef GUARD_TYPE_ALIAS_H
#define GUARD_TYPE_ALIAS_H

#include <vector>
#include <string>
#include <map>

typedef std::vector<std::string> Rule;
typedef std::vector<Rule> Rule_collection;
typedef std::map<std::string, Rule_collection> Grammar;

#endif /* GUARD_TYPE_ALIAS_H */
- -

read_grammar.cpp

-
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
#include <iostream>		// to get the declaration of istream
#include <vector> // to get the declaration of vector
#include <string> // to get the declaration of string
#include "split.h" // to get the declaration of split
#include "type_alias.h" // to get the declaration of type alias
#include "read_grammar.h" // to get the declaration of read_grammar

using std::istream; using std::vector;
using std::string;

// read a grammar from a given input stream
Grammar read_grammar(istream &in)
{
Grammar ret;
string line;

// read the input
while (getline(in, line))
{
// split the input into words
vector<string> entry = split(line);
if(!entry.empty())
// use the category to store the associated rule
ret[entry[0]].push_back(Rule(entry.begin() + 1, entry.end()));
}
return ret;
}
- -

read_grammar.h

-
1
2
3
4
5
6
7
8
9
#ifndef GUARD_READ_GRAMMAR_H
#define GUARD_READ_GRAMMAR_H

#include <iostream>
#include "type_alias.h"

Grammar read_grammar(std::istream &);

#endif /* GUARD_READ_GRAMMAR_H */
- -

gen_sentence.cpp

-
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 <vector>		// to get the declaration of vector
#include <string> // to get the declaration of string
#include <stdexcept> // to get the declaration of domain_error, logic_error
#include <cstdlib> // to get the declaration of rand
#include "type_alias.h" // to get the declaration of type_alias
#include "gen_sentence.h" // to get the declatation of gen_sentence

using std::vector; using std::string;
using std::domain_error; using std::logic_error;
using std::rand;

// generating the sentence
vector<string> gen_sentence(const Grammar &g)
{
vector<string> ret;
gen_aux(g, "<sentence>", ret);
return ret;
}

// auxillary gen function
void gen_aux(const Grammar &g, const string &word, vector<string> &ret)
{
if(!bracketed(word)){
ret.push_back(word);
}
else
{
// locate the rule that corresponds to word
Grammar::const_iterator it = g.find(word);
if(it == g.end())
throw logic_error("empty rule");

// fetch the set of possible rules
const Rule_collection &c = it->second;

// from which we select one at random
const Rule &r = c[nrand(c.size())];

// recursively expand teh selected rule
for (Rule::const_iterator i = r.begin(); i != r.end(); ++i)
gen_aux(g, *i, ret);
}
}

// the predicate
bool bracketed(const string &s)
{
return s.size() > 1 && s[0] == '<' && s[s.size() - 1] == '>';
}

// return a random integer in the range [0, n)
int nrand(int n)
{
if(n <= 0 || n > RAND_MAX)
throw domain_error("Argument to nrand is out of range");

const int bucket_size = RAND_MAX /n;
int r;

do r = rand() / bucket_size;
while(r >= n);

return r;
}
- -

split.cpp

-
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
#include <vector>	// to get the declarartion of vector
#include <string> // to get the declaration of string
#include <algorithm> // to get the declaration of find_if
#include "split.h" // to get the declaration of split

using std::vector; using std::string;
using std::find_if;

// true if the argument is whitespace, false otherwise
bool space(char c)
{
return isspace(c);
}

// false if the argument is whitesapce, true otherwise
bool not_space(char c)
{
return !isspace(c);
}

// function extracts words from a line of input
vector<string> split(const string &str)
{
typedef string::const_iterator iter;
vector<string> ret;

iter i = str.begin();
while (i != str.end()){
// ignore leading blanks
i = find_if(i, str.end(), not_space);

// find end of next word
iter j = find_if(i, str.end(), space);

// copy the characters in [i,j)
if(i != str.end())
ret.push_back(string(i, j));
i = j;
}
return ret;
}
-

split.h

-
1
2
3
4
5
6
7
8
9
10
11
#ifndef GUARD_SPLIT_H
#define GUARD_SPLIT_H

#include <vector>
#include <string>

bool space(char);
bool not_space(char);
std::vector<std::string> split(const std::string &);

#endif /* GUARD_SPLIT_H */
- -

Test performance

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
Inputs:

<noun> cat
<noun> dog
<noun> table
<noun-phrase> <noun>
<noun-phrase> <adjective> <noun-phrase>
<adjective> large
<adjective> brown
<adjective> absurd
<verb> jumps
<verb> sits
<location> on the stairs
<location> under the sky
<location> wherever it wants
<sentence> the <noun-phrase> <verb> <location>

Outputs:

the large brown dog sits wherever it wants
-

The program works as expected.

-
- -
- - - - - - -
- -
- - - - -
- - - - -
- - -
- - - - -
- - - - - - - - -
-
- - -
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/2018/03/30/Accelerated-C-Solutions-to-Exercises-Chapter-7/index.html b/2018/03/30/Accelerated-C-Solutions-to-Exercises-Chapter-7/index.html deleted file mode 100644 index e15676a0..00000000 --- a/2018/03/30/Accelerated-C-Solutions-to-Exercises-Chapter-7/index.html +++ /dev/null @@ -1,629 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - Accelerated C++ Solutions to Exercises(Chapter 7 Part 1) | Liam's Blog - - - - - - - - - - - - -
-
- -
-
- - -
- - -

If history repeats itself, and the unexpected always happens, how incapable must Man be of learning from experience?—George Bernard Shaw.

-
- - -
- - - - -
-
- - -
- - 0% -
- - - - -
-
-
- - -
- - -
- - - -
- - - - - -
-

- Accelerated C++ Solutions to Exercises(Chapter 7 Part 1) -

- - -
- - - - -
- - -

Exercise 7-0

Compile, execute, and test the programs in this chapter.

-

Solution & Results

Please find the programs and detailed analysis in Example 1, 2 and Example 3.

-

Exercise 7-1

Extend the program from §7.2/124 to produce its output sorted by occurrence count.That is, the output should group all the words that occur once, followed by those that occur twice, and so on.

-

Solution & Results

The key to the solution is building a map from occurrence numbers to corresponding words. The original program builds a map from each distinct word to its occurrence numbers. Therefore, we can simply inverse the original map. But noting there may be more than one words have the same occurrence numbers. The revised program is shown below:

-
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
#include <iostream>		// to get the declaration of cin, cout, endl
#include <vector> // to get the declaration of vector
#include <string> // to get the declaration of string
#include <map> // to get the declaration of map

using std::cin; using std::cout;
using std::endl; using std::string;
using std::map; using std::vector;

int main()
{
// store each word and an associated counter
string s;
map<string, int> counters;

// read the input, keeping track of each word and how often we see it
while (cin >> s)
{
++counters[s];
}

// sort words stored in counters according to the occurence count
map<int, vector<string> > sorted_counters;
for (map<string, int>::const_iterator it = counters.begin(); it != counters.end(); ++it)
{
sorted_counters[it->second].push_back(it->first);
}

// write the words and associated counts
cout << "Words and their associated counts:" << endl;
for (map<string, int>::const_iterator it = counters.begin(); it != counters.end(); ++it)
{
cout << it->first << "\t" << it->second << endl;
}

// write a blank line to separate the outputs of two maps
cout << endl;

// write occurrence count followed by the corresponding words
cout << "Occurrence count and the corresponding words:" << endl;
for (map<int, vector<string> >::const_iterator it = sorted_counters.begin(); it != sorted_counters.end(); ++it)
{
cout << it->first;
for (vector<string>::const_iterator i = (it->second).begin(); i != (it->second).end(); ++i)
cout << ' ' << *i;
cout << endl;
}

return 0;
}
-

It can be observed that the values of the original map are stored into the new map as keys while the keys are stored as values in the new map. In addition, we specify vector to hold more words that have same occurrence numbers. Now let’s type some words and check the results.

-
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
Inputs:

a dog and a cat
cat is good dog is bad
human is ugly

Outputs:

Words and their associated counts:
a 2
and 1
bad 1
cat 2
dog 2
good 1
human 1
is 3
ugly 1

Occurrence count and the corresponding words:
1 and bad good human ugly
2 a cat dog
3 is
-

Yeah, it correctly sorts the words according to their occurrence numbers.

-
-

Exercise 7-2

Extend the program in §4.2.3/64 to assign letter grades by ranges:

-
1
2
3
4
5
A   90-100
B 80-89.99...
C 70-79.99...
D 60-69.99...
F < 60
-

The output should list how many students fall into each category.

-

Solution & Results

The key to solution is building a map from the letter grades, A, B, C, D, F, to the number of students who have the corresponding final grades. Therefore, the map can be defined as:

-
1
map<string, int> grades_count;
-

The strategy can be divided into four steps:

-
    -
  1. read students’ information
  2. -
  3. calculate final grade for each student
  4. -
  5. check the range of each final grade and get a letter grade (i.e. the key)
  6. -
  7. increment the value associated with the key returned in step 3
  8. -
-

Step 1 and step 2 are familar.

-

Step 3 needs a function on the final grade. I uses a simple if-else statement to complete it:

-
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
string letter_grade(double &grade)
{
if(grade < 0 || grade > 100)
throw domain_error("grade is outside of[0, 100]");
else if (grade >= 90)
return "A";
else if(grade >= 80 && grade < 90)
return "B";
else if(grade >= 70 && grade < 80)
return "C";
else if(grade >= 60 && grade < 70)
return "D";
else
return "F";
}
-

Step 4 is accomplished by the statement:

-
1
++grades_count[letter_grade(final_grade)];
-

letter_grade(final_grade) returns a letter grade based on the function above. Then, the map grades_count stores (if it is new ) the letter as the key and returns the associated value. Finally, applies ++ operator to increment the associated value, showing the counting process.

-

The complete program

The complete program is displayed below including files: mainfunction.cpp, grade.cpp, grade.h, Student_info.cpp and Student_info.h.

-

mainfunction.cpp

-
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
#include <iostream>		// to get the declaration of cin, cout, endl
#include <stdexcept> // to get the declaration of domain_error
#include <string> // to get the declaration of string
#include <vector> // to get the declaration of vector
#include <map> // to get the declaration of map
#include "Student_info.h" // to get the declaration of Student_info
#include "grade.h" // to get the declaration of grade

using std::cin;
using std::cout; using std::string;
using std::endl; using std::vector;
using std::domain_error; using std::map;

string letter_grade(double &grade)
{
if(grade < 0 || grade > 100)
throw domain_error("grade is outside of[0, 100]");
else if (grade >= 90)
return "A";
else if(grade >= 80 && grade < 90)
return "B";
else if(grade >= 70 && grade < 80)
return "C";
else if(grade >= 60 && grade < 70)
return "D";
else
return "F";
}

int main()
{
// read and store all the records
vector<Student_info> students;
Student_info record;
while(read(cin, record))
{
students.push_back(record);
}

map<string, int> grades_count;
for (vector<Student_info>::size_type i = 0; i != students.size(); ++i)
{
// compute each final grade letter grades and counting the letter grades
try{
double final_grade = grade(students[i]);
++grades_count[letter_grade(final_grade)];
} catch(domain_error e){
cout << e.what();
}
}

for(map<string, int>::const_iterator it = grades_count.begin();
it != grades_count.end(); ++it)
cout << it->first << '\t' << it->second << endl;

return 0;
}
- -

grade.cpp

-
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 <algorithm>
#include <stdexcept>
#include <vector>
#include "grade.h"
#include "Student_info.h"

using std::domain_error; using std::istream;
using std::vector; using std::sort;

// grade function 1
double grade(const Student_info &s)
{
return grade(s.midterm, s.final, s.homework);
}

// grade function 2
double grade(double midterm, double final, const vector<double> &hw)
{
if (hw.size() == 0)
throw domain_error("student has done no homework");
return grade(midterm, final, median(hw));
}

// grade function 3
double grade(double midterm, double final, double homework)
{
return 0.2*midterm + 0.4*final + 0.4*homework;
}

// compute the median of vector<double>
double median(vector<double> vec)
{
// get the size of the vector
typedef vector<double>::size_type vec_size;
vec_size size = vec.size();

// check whether the empty is empty
if (size == 0)
throw domain_error("median of an empty vector");

// sort the grades
sort(vec.begin(), vec.end());

// compute the median homework grade
vec_size mid = size/2;
return size % 2 == 0 ? (vec[mid] + vec[mid - 1])/2 : vec[mid];
}
- -

grade.h

-
1
2
3
4
5
6
7
8
9
10
11
12
#ifndef GUARD_grade_h
#define GUARD_grade_h

// grade.h
#include<vector>
#include "Student_info.h"

double grade(const Student_info &);
double grade(double, double, const std::vector<double> &);
double grade(double, double, double);
double median(std::vector<double>);
#endif
- -

Student.info

-
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
#include "Student_info.h"
using std::vector; using std::istream;

bool compare(const Student_info &x, const Student_info &y)
{
return x.name < y.name;
}

istream & read(istream &is, Student_info &s)
{
// reads and store the student's name, midterm and final exam grades
is >> s.name >> s.midterm >> s.final;

// reads and store all homework grades
read_hw(is, s.homework);
return is;
}

istream & read_hw(istream &in, vector<double> &hw)
{
if (in){
//get rid of previous contents
hw.clear();

// read homework grades
double x;
while(in >> x)
hw.push_back(x);

// clear the stream so that input will work for the next student
in.clear();
}
return in;
}
-

Student_info.h

-
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
#ifndef GUARD_Student_info
#define GUARD_Student_info

#include<iostream>
#include<string>
#include<vector>

struct Student_info{
std::string name;
double midterm, final;
std::vector<double> homework;
};

bool compare(const Student_info &, const Student_info &);
std::istream & read(std::istream &, Student_info &);
std::istream & read_hw(std::istream &, std::vector<double> &);
#endif
- -

Performance Test

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
Inputs:

Phqgh 24.7879 58.6263 64.0505
Nlfdx 95.4242 27.3636 91.0404
Cxggb 16.1818 95.4747 26.7172
Uxwfn 35.9495 3.11111 22.3333
Tkjpr 68.4747 44.6263 57.3737
Pnrvy 16.3535 90.4242 88.0606
Syycq 5.90909 29.7071 50.0606
Ffmzn 84.5455 56.404 66.7677
Vwsre 23.3737 38.1818 82.2929
Fxtls 4.30303 77.0606 73.8687
Dpooe 29.7778 73.9798 12.8687
Ejuvp 55.7475 31.5253 50.5051
Poeyl 91.0707 37.5758 87.5354
Jvrvi 21.8889 22.4646 6.30303
Hwqnq 55.101 59.2424 37.4848
Jjloo 91.3636 74.202 96.2121
Whmsn 34.5354 99.1818 38
Sfzkv 48.8384 7.21212 10.1717
Lyjyh 51 49.1919 56.9899
Nkkuf 89.0202 95.8586 93.4343

Outputs:

A 1
B 1
C 1
D 5
F 12
- -
-

Exercise 7-3

The cross-reference program from §7.3/126 could be improved: As it stands, if a word occurs more than once on the same input line, the program will report that line multiple times. Change the code so that it detects multiple occurrences of the same line number and inserts the line number only once.

-

Solution & Results

In the original program, we built a map from each distinct word to the line numbers in which the word appears.

-
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
map<string, vector<int> > xref(istream &in,
vector<string> find_words(const string &))
{
string line;
int line_number = 0;
map<string, vector<int> > ret;

// read the next line
while(getline(in, line))
{
++line_number;

// break the input line into words
vector<string> words = find_words(line);

// remember that each word occurs on the current line
for(vector<string>::const_iterator it = words.begin(); it != words.end();
++it)
{
ret[*it].push_back(line_number);
}
}
return ret;
}
- -

However, the line numbers for each word may repeatedly recorded. To avoid this problem, one solution is to check whether the line number has already been recorded. If the line number has been recorded, we ignore it, otherwise, we store it into the vector. We do not need to check all elements in the vector instead we only check the last stored line number. This is because that if a line number is repeatedly recorded, two elements (i.e. same line numbers) must be adjacent. Therefore, I add if statement as follows:

-
1
2
if(ret[*it].empty() || *(ret[*it].end() - 1) != line_number)
ret[*it].push_back(line_number);
-

I’ll give the complete program as well as test results in next exercise.

-

Exercise 7-4

The output produced by the cross-reference program will be ungainly if the input file is large. Rewrite the program to break up the output if the lines get too long.

-

Solution & Results

The key to the solution is managing the length of each line of outputs. Theoretically, the strategy can be divided into three steps:

-
    -
  1. convert all line numbers (except the first one) associated with a word into strings.
  2. -
  3. count the number of characters that have been written.
  4. -
  5. when the predetermined length of a line is reached, write a new line.
  6. -
-

The first two steps seems tedious to us. Fortunately, we can use stringstream objects to accomplish these easily.

-

stringstream is a stream class defined standard library. It provides IO facilities that operate on strings. For example, ostringstream object uses a string buffer to hold a sequences of characters for printing all outputs together. In addition, the sequence of characters can be accessed directly as a string object, using member function str. In this case, I define such an object os to hold all line numbers as well as additional spaces between two line numbers.

-
1
os << ", " << *line_it;
-

line_it is an iterator that refers to one of line numbers stored in a vector.

-

Once all line numbers have been stored into os, we can access all contents to be written as a string object.

-
1
string line_numbers = os.str();
- -

Now, the next is to print all line numbers in one or more lines depending on the predetermined length of one line.

-
1
2
3
4
5
6
for(string::size_type i = 0; i != line_numbers.size(); ++i)
{
cout << line_numbers[i];
if((i + 1) % line_length == 0)
cout << endl;
}
-

When i+1th element (i.e. line_number[i])
is written, the if statement check that if the number of characters that have been written equals to the length (or multiple lengths) of a line, a newline character is inserted into the output stream.

-

A complete program

I integerated the changes described in exercise 7-3 and this exercise into a new program including files: mainfunction.cpp, xref.cpp, xref.h, split.cpp, split.h.

-

mainfunction.cpp

-
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
#include <map>		// to get the declaration of map
#include <string> // to get the declaration of string
#include <vector> // to get the declaration of vector
#include <iostream> // to get the declaration of cin, cout, endl;
#include <sstream> // to get the declaration of ostringstream
#include <cctype> // to get the declaration of isspace
#include "split.h" // to get the declaration of function split
#include "xref.h" // to get the declaration of xref

using std::map; using std::cout;
using std::cin; using std::endl;
using std::string; using std::vector;
using std:: ostringstream; using std::isspace;

int main()
{
// call xret using split by default
map<string, vector<int> > ret = xref(cin);

// set the length for each line of outputs
string::size_type line_length = 20;

// write the result
for(map<string, vector<int> >::const_iterator it = ret.begin(); it != ret.end(); ++it)
{
// write the word
cout << it->first << " occurs on line(s):" << endl;

// followed by one or more line numbers
vector<int>::const_iterator line_it = it->second.begin();
cout << *line_it; // write the first line number

// scan the rest line numbers
++line_it;
ostringstream os;
while(line_it != it->second.end())
{
// store line numbers into ostringstream object
os << ", " << *line_it;
++line_it;
}

// get the contents from line_numbers
string line_numbers = os.str();

// write each line of outputs
for(string::size_type i = 0; i != line_numbers.size(); ++i)
{
cout << line_numbers[i];
if((i + 1) % line_length == 0)
cout << endl;
}
// write a blank line to separate each words
cout << endl;
}
return 0;
}
- -

xref.cpp

-
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
#include <iostream>	// to get the decalration of istream
#include <map> // to get the declaration of map
#include <vector> // to get the declaration of vector
#include <string> // to get the declaration of string
#include "split.h" // to get the declaration of split
#include "xref.h" // to get the declatation of xref

using std::map; using std::vector;
using std::string; using std::istream;

map<string, vector<int> > xref(istream &in,
vector<string> find_words(const string &))
{
string line;
int line_number = 0;
map<string, vector<int> > ret;

// read the next line
while(getline(in, line))
{
++line_number;

// break the input line into words
vector<string> words = find_words(line);

// remember that each word occurs on the current line
for(vector<string>::const_iterator it = words.begin(); it != words.end();
++it)
{
if(ret[*it].empty() || *(ret[*it].end() - 1) != line_number)
ret[*it].push_back(line_number);
}
}
return ret;
}
- -

xref.h

-
1
2
3
4
5
6
7
8
9
10
11
12
#ifndef GUARD_XREF_H
#define GUARD_XREF_H

#include <map>
#include <vector>
#include <string>
#include <iostream>
#include "split.h"

std::map<std::string, std::vector<int> > xref(std::istream &,
std::vector<std::string> find_words(const std::string &) = split);
#endif /*GUARD_XREF_H */
- -

split.cpp

-
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
#include <vector>		// to get the declarartion of vector
#include <string> // to get the declaration of string
#include <algorithm> // to get the declaration of find_if
#include "split.h" // to get the declaration of split

using std::vector; using std::string;
using std::find_if;

// true if the argument is whitespace, false otherwise
bool space(char c)
{
return isspace(c);
}

// false if the argument is whitesapce, true otherwise
bool not_space(char c)
{
return !isspace(c);
}

// function extracts words from a line of input
vector<string> split(const string &str)
{
typedef string::const_iterator iter;
vector<string> ret;

iter i = str.begin();
while (i != str.end()){
// ignore leading blanks
i = find_if(i, str.end(), not_space);

// find end of next word
iter j = find_if(i, str.end(), space);

// copy the characters in [i,j)
if(i != str.end())
ret.push_back(string(i, j));
i = j;
}
return ret;
}
- -

split.h

-
1
2
3
4
5
6
7
8
9
10
11
#ifndef GUARD_SPLIT_H
#define GUARD_SPLIT_H

#include <vector>
#include <string>

bool space(char);
bool not_space(char);
std::vector<std::string> split(const std::string &);

#endif /* GUARD_SPLIT_H */
- -

Performance test

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
Inputs:

ABC
ABC DEF DEF
ABC
ABC ABC
ABC
ABC
ABC
ABC
ABC
ABC
ABC
ABC
ABC
ABC
ABC
ABC
ABC
ABC
ABC
ABC
ABC

Outputs:

ABC occurs on line(s):
1, 2, 3, 4, 5, 6, 7,
8, 9, 10, 11, 12, 13
, 14, 15, 16, 17, 18
, 19, 20, 21
DEF occurs on line(s):
2
- -

From this test, we observe that

-
    -
  1. if one word appears more than one times in the same line, the line number is only recorded once. This shows the change described in exercise 7-3 works well.
  2. -
  3. once a line of outputs exceeds 20 characters, it wraps. This verifies the solution given above.
  4. -
-

Noting that I also change the program such that it always writes line numbers starting from a new line.

-
-

To be continued.

- -
- - - - - - -
- -
- - - - -
- - - - -
- - -
- - - - -
- - - - - - - - -
-
- - -
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/2018/03/31/Accelerated-C-Solutions-to-Exercises-Chapter-7-Part-2/index.html b/2018/03/31/Accelerated-C-Solutions-to-Exercises-Chapter-7-Part-2/index.html deleted file mode 100644 index 2101f564..00000000 --- a/2018/03/31/Accelerated-C-Solutions-to-Exercises-Chapter-7-Part-2/index.html +++ /dev/null @@ -1,555 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - Accelerated C++ Solutions to Exercises(Chapter 7 Part 2) | Liam's Blog - - - - - - - - - - - - -
-
- -
-
- - -
- - -

If history repeats itself, and the unexpected always happens, how incapable must Man be of learning from experience?—George Bernard Shaw.

-
- - -
- - - - -
-
- - -
- - 0% -
- - - - -
-
-
- - -
- - -
- - - -
- - - - - -
-

- Accelerated C++ Solutions to Exercises(Chapter 7 Part 2) -

- - -
- - - - -
- - -

Exercise 7-5

Reimplement the grammar program using a list as the data structure in which webuild the sentence.

-

Solutions & Results

There is no any other differences between the list-based version and the vector-based version except that we replace vectors with lists literally, and hence No further discussion about this exercise. The original program can be found here Example 3.

-

Exercise 7-6

Reimplement the gen_sentence program using two vectors: One will hold the fullyunwound, generated sentence, and the other will hold the rules and will be used as a stack.Do not use any recursive calls.

-

Solution & Results

To be updated.

-

Exercise 7-7

Change the driver for the cross-reference program so that it writes line if there is only one line and lines otherwise.

-

Solution & Results

The solution is to add and check a condition that whether the vector where holds line numbers only contain one line number. If there is only one line number, we use line else use lines.

-
1
2
3
4
if ((it->second).size() == 1)
cout << "line:" << endl;
else
cout << "lines:" << endl;
-

I only present the revised file here and please find other file in Exercise 7-4.

-

mainfunction.cpp

-
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
#include <map>		// to get the declaration of map
#include <string> // to get the declaration of string
#include <vector> // to get the declaration of vector
#include <iostream> // to get the declaration of cin, cout, endl;
#include <sstream> // to get the declaration of ostringstream
#include <cctype> // to get the declaration of isspace
#include "split.h" // to get the declaration of function split
#include "xref.h" // to get the declaration of xref

using std::map; using std::cout;
using std::cin; using std::endl;
using std::string; using std::vector;
using std:: ostringstream; using std::isspace;

int main()
{
// call xret using split by default
map<string, vector<int> > ret = xref(cin);

// set the length for each line of outputs
string::size_type line_length = 20;

// write the result
for(map<string, vector<int> >::const_iterator it = ret.begin(); it != ret.end(); ++it)
{
// write the word
cout << it->first << " occurs on " << endl;

if ((it->second).size() == 1)
cout << "line:" << endl;
else
cout << "lines:" << endl;

// followed by one or more line numbers
vector<int>::const_iterator line_it = it->second.begin();
cout << *line_it; // write the first line number

// scan the rest line numbers
++line_it;
ostringstream os;
while(line_it != it->second.end())
{
// store line numbers into ostringstream object
os << ", " << *line_it;
++line_it;
}

// get the contents from line_numbers
string line_numbers = os.str();

// write each line of outputs
for(string::size_type i = 0; i != line_numbers.size(); ++i)
{
cout << line_numbers[i];
if((i + 1) % line_length == 0)
cout << endl;
}
// write a blank line to separate each words
cout << endl;
}
return 0;
}
-

I use the inputs as same as inputs used in exercise 7-4 and get following results, showing the effect of above changes.

-
1
2
3
4
5
6
7
ABC occurs on lines:
1, 2, 3, 4, 5, 6, 7,
8, 9, 10, 11, 12, 13
, 14, 15, 16, 17, 18
, 19, 20, 21
DEF occurs on line:
2
- -

Exercise 7-8

Change the cross-reference program to find all the URLs in a file, and write all the lines
on which each distinct URL occurs.

-

Solution & Results

Please find the program and analysis in Example 2-Test 2.

-

Exercise 7-9

(difficult) The implementation of nrand in §7.4.4/135 will not work for arguments greater than RAND_MAX. Usually, this restriction is no problem, because RAND_MAX is often the largest possible integer anyway. Nevertheless, there are implementations under which RAND_MAX is much smaller than the largest possible integer. For example, it is not uncommon for RAND_MAX to be 32767 (2^15 -1) and the largest possible integer to be 2147483647 (2^31 -1). Reimplement nrand so that it works well for all values of n.

-

Solution & Results

Recalling nrand function

-
1
2
3
4
5
6
7
8
9
10
11
12
13
int nrand(int n)
{
if(n <= 0 || n > RAND_MAX)
throw domain_error("Argument to nrand is out of range");

const int bucket_size = RAND_MAX /n;
int r;

do r = rand() / bucket_size;
while(r >= n);

return r;
}
-

nrand generates a random numbers in the range [0, n). The idea behind this function is that we divide the range[0, n(RAND_MAX/n)) into *n** pieces of equal size. Assuming RAND_MAX = 32767, n = 1000, random numbers r and random numbers generated from rand() have following relationships:

-
1
2
3
4
5
6
7
r (values)                   rand() (values of the range)

0 [0, 32)
1 [32, 64)
2 [64, 96)
... ...
999 [31968, 32000)
-

To be continued.

- -
- - - - - - -
- -
- - - - -
- - - - -
- - -
- - - - -
- - - - - - - - -
-
- - -
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/2018/04/02/C-Writing-generic-functions/index.html b/2018/04/02/C-Writing-generic-functions/index.html deleted file mode 100644 index 17d2d799..00000000 --- a/2018/04/02/C-Writing-generic-functions/index.html +++ /dev/null @@ -1,625 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - C++ - Writing generic functions | Liam's Blog - - - - - - - - - - - - -
-
- -
-
- - -
- - -

If history repeats itself, and the unexpected always happens, how incapable must Man be of learning from experience?—George Bernard Shaw.

-
- - -
- - - - -
-
- - -
- - 0% -
- - - - -
-
-
- - -
- - -
- - - -
- - - - - -
-

- C++ - Writing generic functions -

- - -
- - - - -
- - -

Generic function - an example

Generic functions are functions written in a way that is independent of any particular type. When we use a generic program, we supply the type(s) or value(s) on which that instance of the program will operate(Lippman etc. 2012). In C++, we can create generic functions by defining template functions, allowing writing a single definition for a family of functions or types that behave similarly but have different types of paratmters. The only difference can be summarized as parameters, namely, template parameters. Take the median function as an example, its template can be defined as follows.

-
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
template<class T>
T median(vector<T> v)
{
typedef typename vector<T>::size_type vec_sz;

vec_sz size = v.size();
if(size == 0)
throw domain_error("median of an empty vector");

sort(v.begin(), v.end());

vec_sz mid = size/2;

return size % 2 ? (v[mid] + v[mid - 1])/2 : v[mid];
}
-

From the definition, we observe

-
    -
  1. A template starts with keyword template followed by type parameter(s) enclosed by a pair of angle brackets.
  2. -
  3. type parameter(s) define the names that can be used within the scope of the function. They refer to types, not variables. In other words, the types of template parameters can be regarded as variables that have types specified by type parameters(s).
  4. -
  5. type parameter(s) are specified following the keyword class or typename.
  6. -
  7. the template tells implementation that vector::size_type is the name of a type by adding keyword typename before.
  8. -
-

When we call median and pass a template argument vector, the implementation will create and compile an instance of the function as if the function median(vector). This is so called template instantiation. But how is the template compiled?

-

When the compiler encounters the definition of a template, it doesn’t generate code. It generates only when the template is instantiated. Remembering that when we call a function, we need to supply only its declaration. But if we want to call the template, we need to supply not only its declaration but also its definition in order to instantiate it. Therefore, the header file of a template generally includes the source file also via a #include or directly. I found an article How To Organize Template Source Code, where gives detailed explinations on how to organize template source code and three solutions.

-

Due to the feature of generating code during instantiation, compilation errors may occur during three stages:

-
    -
  1. the first stage is when we compile the template itself. But the compiler can only detect some syntax errors like forgetting a semicolon, misspelling a variable name etc..
  2. -
  3. the second stage is when we use a template, before it is instantiated. The compiler typically will check that whether the number of the arguments is appropriate, whether two arguments that are supposed to have the same type do so.
  4. -
  5. the type-related errors mostly occurs during the third stage when the template is instantiated. Though implementations manage instantiation on their own ways, the type-related errors may be reported at link time due to that implementations assume the type is possibly defined in other unit and hence leave it to linker to resolve.
  6. -
-

In fact, we have applied many templates in previous programs, such as vector and list, and all standard library algorithms.

-

Algorithms and iterators

As shown in above example, the template is type independent. However, it is also clear that the function to instantiate requires its argument supporting all operations included in the function. In above case, the types stored in the vectors that are passed to the median function must support addition and division.

-

The standard algorithms are not limited by the type of containers and obviously are data-structure independent function templates. It is known that the parameters taken by algorithms includes iterators and others. This implies that iterators to pass should support certain operations used inside of the algorithms. But we also know that some containers may support operations that others do not. For example, vector support random access elements via iterators while list doesn’t. Therefore, it is important to restrict the right of iterators depending on operations that an algorithm include and operations different containers support. For this reason, the library defines five iterator categories with specifying the collection of iterator operations for each category. By doing so, we know exactly what effect of an algorithm can have on an container and which container can use which algorithms.

-

Sequential read-only access

Some algorithms only require iterators that can access elements sequentially. For example, the find algorithm:

-
1
2
3
4
5
6
7
template <class In, class X>
In find(In beg, In end, const X &x)
{
while (begin != end && *begin != x)
++begin;
return begin;
}
-

The iterators of type In access elements sequentially via
operations *, ++. Also, they can be compared using !=. Alternatively,

-
1
2
3
4
5
6
7
8
template <class In, class X>
In find(In begin, In end, const X &x)
{
if (begin == end || *begin == x)
return begin;
begin++;
return find(begin, end, x);
}
-

This version of find uses another strategy, recursively calling itself. We have observed that the iterators may also need to support operations ++(postfix) and ==.

-

Furthermore, the iterators ought to support member access via ->, e.g. it->first. It has the same effect as (*it).first.

-

In summary, iterators that offers sequential read-only access to elements of a container should supports ++(both prefix and postfix), == and !=, * and ->. Such iterators are named as input iterators. All standard container meet the requirements of input iterator.

-

Sequential write-only access

Some algorithms may require write elements of a sequence via iterators, for example

-
1
2
3
4
5
6
7
template <class In, class Out>
Out copy(In begin, In end, Out dest)
{
while(begin != end)
*dest++ = *begin++;
return dest;
}
-

The copy algorithm takes three iterators, the first two of which denote the range of a sequence to copy while the third iterator denotes the initial position of the destination. The iterators of type In offer sequential read-only access to elements and hence are input iterators. The type Out gives the third iterator the ability to write elements via *dest = and dest++. As with the find algorithm above, such iterators should also support ++dest.

-

The fact that iterators of type Out are used for output only also implies that

-
    -
  1. each element pointed by the iterator is written a value only once and then the iterator is incremented.

    -
  2. -
  3. the iterator can not be incremented twice without assignments to the elements that it refers to.

    -
  4. -
-

In summary, such iterators are output iterators. All standard containers as well as back_inserter meet the requirements of output iterator. Noting that in this function, the left side deference operator is used to write to the underlying elements while the right side deference operator is used to read the underlying elements only.

-

Sequential read-write access

There is another situation that we want to both read and write the elements of a sequence, but only sequentially. For example

-
1
2
3
4
5
6
7
8
9
10
template<class For, class X>
void replace(For beg, For end, const X &x, const X &y)
{
while (beg != end)
{
if (*beg == x)
*beg = y;
++beg;
}
}
-

The replace algorithm examines each element in the range [beg, end) and assigns value y to those elements that are equal to x. Apparently, iterators of type For should support all operations supported by an input operator as well as an output operator. Moreover, they can read and write the same elements multiple times. Such a type is a forward iterator. Some operations that such a type need to support are

-
    -
  1. *it (for both reading and writing)
  2. -
  3. ++ (both prefix and postfix)
  4. -
  5. == and !=
  6. -
  7. ->
  8. -
-

All standard containers meet the requirements of forward iterator.

-

Reversible access

All above iterators access elements in a container in a forward sequence. But some functions may require get elements in reverse order, for example

-
1
2
3
4
5
6
7
8
9
template <class Bi> void reverse(Bi begin, Bi end)
{
while (begin != end)
{
--end;
if (begin != end)
swap (*begin++, *end);
}
}
-

The iterators of type Bi moves backward from end to begin via . Then, the swap algorithm exchanges values of two elements. Such iterators meet all requirements of forward iterator and support (both prefix and postfix). They are bidirectional iterators. All standard containers meet the requirements of bidirectional iterators.

-

Random access

All above iterators access elements in a forward or backward sequence, however, some functions need to access elements starting from arbitrary positions. For example

-
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
template<class Ran, class X>
bool binary_search(Ran begin, Ran end, const X &x)
{
while (begin < end)
{
// find the midpoint of the range
Ran mid = begin + (end - begin)/2;

// see which part of the range contains x; keep looking only in that part
if (x < *mid)
end = mid;
else if (*mid <x)
begin = mid + 1;

// if we got here, then *mid == x so we are done
else
return true;
}
return false;
}
-

The binary search algorithm looks for a particular element in a sorted container. It always starts to search from the middle point of a sequence, which relies on the ability to do arithmetic on iterators. Such an iterator is called a random access iterator. Specifically, if p and q are random access iterators, they should support all operations that a bidirectional iterator supports, as well as following arithmetic operations

-
    -
  1. p + n, p - n, n + p
  2. -
  3. p-q
  4. -
  5. p[n] (equivalent to *(p + n))
  6. -
  7. p > q, p < q, p <= q, p >= q
  8. -
-

Standard sort algorithm requires random-access iterators. Therefore, vector and string can use standard sort function as their iterators are random-access iterators. list iterators are not random-access iterators and hence list defines its own member sort instead of using the standard sort.

-

Summary

From above analysis, we know that forward, bidirectional and random-access iterators are also valid input iterators as they all meet the requirements of input iterator. In addition, all forward, bidirectional and random-access iterators that are not constant iterators are also valid output iterators.

-

From the perspective of containers, different category of iterator makes some containers distinct, for example, list iterators are bidirectional iterators, forward_list iterators are forward_iterators, vector and string iterators are random access iterators.

-

But why we need input/output iterator? One reason is that not all iterators are assicoated with containers. For example, back_inserter() is an iterator that meet and only meet the requirements of output iterator.

-

Another typical example is that the standard library provides iterators that can be bound to input and output streams, namely, istream_iterator and ostream_iterator.
Apparently, istream_iterator is input iterator that allows us to read successive elements from an input stream.
ostream_iterator is output iterator that allows us to write sequentially to an output stream.

-
1
2
3
vector<int> v;
// read ints from the standard input and append them to v
copy(istream_iterator<int>(cin), istream_iterator<int>(), back_insert(v));
-

This example shows that the first istream_iterator is bound to cin and expects to read values of type int. But the second istream_iterator is not bound to any file.
This is because istream_iterator type has a default value with a sepcial property such that any istream_iterator that has reached end-of-file or is an error state will appear to be equal to the default value. Therefore, we can use the default value of a istream_iterator together with the first input iterator to denote the sequence in the input stream.

-

Similarly, we can use ostream_iterator to write elements to an output stream.

-
1
2
// write the elements of v each separated from the other by a space
copy(v.begin(), v.end(), ostream_iterator<int> (cout, " "));
-

This statement uses copy algorithm to copy the vector v onto the standard output. ostream_iterator is bound to cout with an additional argument, that is, a space in this case. This additional argument specifies a value to be written after each element and typically is a string literal.

-

Rewrite the split function

Now we apply the new knowledge learned above to rewrite the split function as a template such that it is data-structure independent.

-
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
template <class Out>
void split(const string &str, Out os)
{
typedef string::const_iterator iter;

iter i = str.begin();
while(i != str.end())
{
// ignore leading spaces
i = find_if(i, str.end(), not_space);

// find end of next word
iter j = find_if(i, str.end(), space);

// copy the characters in [i,j)
if(i != str.end())
*os++ = string(i, j);
}
}
-

This function has return type void, i.e. nothing to return. If we want to store each word contained in a line of inputs into a vector, we just need to pass an output iterator. This is also true for a list. For example

-
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
int main()
{
// list<string> words;
vector<string> words;

string line;
while(getline(cin, line))
{
split(line, back_inserter(words));
}

for(vector<string>::const_iterator it = words.begin(); it != words.end(); ++it)
cout << *it << endl;
return 0;
}
-

Alternatively, we may write all words onto the standard output directly. I take this as an exercise and present a complete program below.

-

mainfunction.cpp

-
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
#include <iostream>
#include <iterator>
#include <string>
#include "split.h"

using std::cin; using std::ostream_iterator;
using std::cout; using std::string;
using std::endl; using std::back_inserter;

int main(){

string line;
while (getline(cin, line))
{
split(line, ostream_iterator<string>(cout, "\n"));
}

return 0;
}
- -

split.h

-
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
#ifndef GUARD_SPLIT_H
#define GUARD_SPLIT_H

#include <string>
#include <algorithm>

// true if the argument is whitespace, false otherwise
bool space(char c)
{
return isspace(c);
}

// false is the argument is whitespace, true otherwise
bool not_space(char c)
{
return !isspace(c);
}

// template declaration and definition
template <class Out>
void split(const std::string &str, Out os)
{
typedef std::string::const_iterator iter;

iter i = str.begin();
while(i != str.end())
{
// ignore leading spaces
i = std::find_if(i, str.end(), not_space);

// find end of next word
iter j = std::find_if(i, str.end(), space);

// copy the characters in [i,j)
if(i != str.end())
*os++ = std::string(i, j);
i = j;
}
}
#endif /* GUARD_SPLIT_H */
- -

Noting that I didn’t separate the definition and declaration of the split template to make them be visible to compiler in the point of instantiation.

-

Let’s type some words

-
1
2
3
4
what 
a
beautiful
name
-

The results are as expected

-
1
2
3
4
5
6
7
8
what
what
a
a
beautiful
beautiful
name
name
-

Again type

-
1
what a beautiful name
-

The program gives

-
1
2
3
4
what
a
beautiful
name
-
- - - - - - -
- -
- - - - -
- - - - -
- - -
- - - - -
- - - - - - - - -
-
- - -
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/2018/04/03/Accelerated-C-Solutions-to-Exercises-Chapter-8/index.html b/2018/04/03/Accelerated-C-Solutions-to-Exercises-Chapter-8/index.html deleted file mode 100644 index 9b5e8a04..00000000 --- a/2018/04/03/Accelerated-C-Solutions-to-Exercises-Chapter-8/index.html +++ /dev/null @@ -1,573 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - Accelerated C++ Solutions to Exercises(Chapter 8 Part 1) | Liam's Blog - - - - - - - - - - - - -
-
- -
-
- - -
- - -

If history repeats itself, and the unexpected always happens, how incapable must Man be of learning from experience?—George Bernard Shaw.

-
- - -
- - - - -
-
- - -
- - 0% -
- - - - -
-
-
- - -
- - -
- - - -
- - - - - -
-

- Accelerated C++ Solutions to Exercises(Chapter 8 Part 1) -

- - -
- - - - -
- - -

Exercise 8-0

Compile, execute, and test the programs in this chapter.

-

Solution & Results

Please find the programs and analysis on Writing generic functions.

-

Exercise 8-1

Note that the various analysis functions we wrote in §6.2/110 share the same behavior; they differ only in terms of the functions they call to calculate the final grade. Write a template function, parameterized by the type of the grading function, and use that function to evaluate the grading schemes.

-

Solution & Results

To parameterize the type of the grading function, I define the type parameter of the analysis function template as Fun. Then what we need to do is replacing all revelant functions with f which is a variable that has type Fun. See the function template below:

-

New grades_analysis.h

-
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
#ifndef GUARD_GRADES_ANALYSIS_H
#define GUARD_GRADES_ANALYSIS_H

#include <vector>
#include <algorithm>
#include "Student_info.h"
#include "gradingSchemes.h"

template <class Fun>
double analysis(const std::vector<Student_info> &students, Fun &f)
{
std::vector<double> grades;

std::transform(students.begin(), students.end(), std::back_inserter(grades), f);
return median(grades);
}
#endif /* GUARD_GRADES_ANALYSIS_H */
-

The next step is to rewrite the write_analysis function as the original one defines a parameter to take various analysis functions.

-
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
#ifndef GUARD_WRITE_ANALYSIS_H
#define GUARD_WRITE_ANALYSIS_H

#include <iostream>
#include <string>
#include <vector>
#include "Student_info.h"
#include "grades_analysis.h"

template <class Fun>
void write_analysis(std::ostream &out, const std::string &name, Fun &f,
const std::vector<Student_info> &did,
const std::vector<Student_info> &didnt)
{
out << name << ": median(did) = " << analysis(did, f)
<< ": median(didnt) = " << analysis(didnt, f) << std::endl;
}
#endif /* GUARD_WRITE_ANALYSIS_H */
- -

Its necessary to put both declaration and definition into the header file for both two function templates due to the fact that they are required to be visible to the compiler in the point of instantiation.

-

Now we can pass different grading functions directly to the write_analysis function to generate analysis reports.

-

mainfunction.cpp

-
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
#include <iostream>			// to get the declaration of cin, cout, endl
#include <vector> // to get the declaration of vector
#include "Student_info.h" // to get the declaration of Student_info
#include "did_all_hw.h" // to get the declatation of the predicate on students' records
#include "write_analysis.h" // to get the declaration of write_analysis function
#include "gradingSchemes.h" // to get the declarations of various grading functions

using std::vector; using std::cout;
using std::cin; using std::endl;

int main()
{
// students who did and didn't do all their homework
vector<Student_info> did, didnt;

// read the student records and partition time
Student_info student;
while(read(cin, student))
{
if(did_all_hw(student))
did.push_back(student);
else
didnt.push_back(student);
}
// verify thatthe analyses will show us something
if(did.empty())
{
cout << "No student did all the homework!" << endl;
return 1;
}
if(didnt.empty())
{
cout << "Every student did all the homework!" << endl;
return 1;
}

// do the analysis
write_analysis(cout, "median", median_grade_aux, did, didnt);
write_analysis(cout, "average", average_grade, did, didnt);
write_analysis(cout, "median of homework turned in", optimistic_median, did, didnt);

return 0;
}
-

Other files are keep unchanged and can be found below.

-

did_all_hw.cpp

-
1
2
3
4
5
6
7
8
9
10
#include <algorithm>		// to get the declaration of find
#include "Student_info.h" // to get the declaration of Student_info
#include "did_all_hw.h" // to get the declaration of did_all_hw itself

using std::find;

bool did_all_hw(const Student_info &s)
{
return ((find(s.homework.begin(), s.homework.end(), 0)) == s.homework.end());
}
- -

did_all_hw.h

-
1
2
3
4
5
6
7
8
#ifndef GUARD_DID_ALL_HW_H
#define GUARD_DID_ALL_HW_H

#include "Student_info.h"

bool did_all_hw(const Student_info &s);

#endif /* GUARD_DID_ALL_HW_H */
-

gradingSchemes.cpp

-
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
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
#include <algorithm>		// to get the declaration of remove_copy
#include <numeric> // to get the declaration of accumulate
#include <stdexcept> // to get the declaration of domain_error
#include <vector> // to get the declaration of vector
#include "Student_info.h" // to get the declaration of Student_info
#include "gradingSchemes.h" // to get the declaration of all functions here to keep consistent

using std::domain_error; using std::istream;
using std::vector; using std::sort;
using std::accumulate;

// final grade function returns weighted average of midterm exam grade,
// final exam grade, and homework grade which will be computed
// using different methods depending on grading schemes
double grade(double midterm, double final, double homework)
{
return 0.2*midterm + 0.4*final + 0.4*homework;
}

// fundermental functions 1: returns the median value of vector<double>
double median(vector<double> vec)
{
// get the size of the vector
typedef vector<double>::size_type vec_size;
vec_size size = vec.size();

// check whether the empty is empty
if (size == 0)
throw domain_error("median of an empty vector");

// sort the grades
sort(vec.begin(), vec.end());

// compute the median homework grade
vec_size mid = size/2;
return size % 2 == 0 ? (vec[mid] + vec[mid - 1])/2 : vec[mid];
}

// fundermental functions 2: returns the average value of vector<double>
double average(const vector<double> &v)
{
// check whether the empty is empty
if (v.empty())
{ throw domain_error("average of an empty vector");}

return accumulate(v.begin(), v.end(), 0.0) / v.size();
}


// grading scheme 1: final grade is based on the median homework grade
double median_grade(const Student_info &s)
{
return median_grade(s.midterm, s.final, s.homework);
}

// grading scheme 1: overloaded median_grade function
double median_grade(double midterm, double final, const vector<double> &hw)
{
if (hw.size() == 0)
throw domain_error("student has done no homework");
return grade(midterm, final, median(hw));
}

// grading scheme 1: auxiliary median_grade function
double median_grade_aux(const Student_info &s)
{
try{
return median_grade(s);
}catch (domain_error){
// students who did no homework at all, get 0 homework grade
return grade(s.midterm, s.final, 0);
}
}

// grading scheme 2: final grade is based on average homework grades
double average_grade(const Student_info &s)
{
try{
return grade(s.midterm, s.final, average(s.homework));
}catch (domain_error){
// students who did no homework at all, get 0 homework grade
return grade(s.midterm, s.final, 0);
}
}

// grading scheme 3: final grade is based on median of the completed homework grades,
// and students who did no homework at all will get 0 homework grade
double optimistic_median(const Student_info &s)
{
vector<double> nonzero;
remove_copy(s.homework.begin(), s.homework.end(), back_inserter(nonzero), 0);

if(nonzero.empty())
return grade(s.midterm, s.final, 0);
else
return grade(s.midterm, s.final, median(nonzero));
}
- -

gradingSchemes.h

-
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
#ifndef GUARD_GRADING_SCHEMES_H
#define GUARD_GRADING_SCHEMES_H

#include<vector>
#include "Student_info.h"

double grade(double midterm, double final, double homework);
double median(std::vector<double> vec);
double average(const std::vector<double> &v);
double median_grade(const Student_info &s);
double median_grade(double midterm, double final, const std::vector<double> &hw);
double median_grade_aux(const Student_info &s);
double average_grade(const Student_info &s);
double optimistic_median(const Student_info &s);

#endif /* GUARD_GRADING_SCHEMES_H */
- -

Student_info.cpp

-
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 "Student_info.h"
using std::vector; using std::istream;

// argument to the function sort
bool compare(const Student_info &x, const Student_info &y)
{
return x.name < y.name;
}

// read the info
istream & read(istream &is, Student_info &s)
{
// reads and store the student's name, midterm and final exam grades
is >> s.name >> s.midterm >> s.final;

// reads and store all homework grades
read_hw(is, s.homework);
return is;
}

// read all homework grades
istream & read_hw(istream &in, vector<double> &hw)
{
if (in){
//get rid of previous contents
hw.clear();

// read homework grades
double x;
while(in >> x)
hw.push_back(x);

// clear the stream so that input will work for the next student
in.clear();
}
return in;
}
- -

Student_info.h

-
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
#ifndef GUARD_Student_info
#define GUARD_Student_info

#include<iostream>
#include<string>
#include<vector>

struct Student_info{
std::string name;
double midterm, final;
std::vector<double> homework;
};

bool compare(const Student_info &, const Student_info &);
std::istream & read(std::istream &, Student_info &);
std::istream & read_hw(std::istream &, std::vector<double> &);
#endif
- -

I tested this program using the same inputs as we did in A simple test. It gives the same results as the original progam

-
1
2
3
4
5
6
7
median: median(did) = 46.1475: median(didnt) = 42.9273
average: median(did) = 45.4202: median(didnt) = 44.3273
median of homework turned in: median(did) = 46.1475: median(didnt) = 52.1273
```

# Exercise 8-2
Implement the following library algorithms, which we used in Chapter 6 and describedin §6.5/121. Specify what kinds of iterators they require. Try to minimize the number of distinct iterator operations that each function requires. After you have finished your implementation, see §B.3/321 to see how well you did.
-

equal(b, e, d) search(b, e, b2, e2)
find(b, e, t) find_if(b, e, p)
copy(b, e, d) remove_copy(b, e, d, t)
remove_copy_if(b, e, d, p) remove(b, e, t)
transform(b, e, d, f) partition(b, e, p)
accumulate(b, e, t)

-

-## Solution & Results
-To be updated. 
-
-
-
-
- - - - - - -
- -
- - - - -
- - - - -
- - -
- - - - -
- - - - - - - - -
-
- - -
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/2018/04/04/Accelerated-C-Solutions-to-Exercises-Chapter-8-Part-2/index.html b/2018/04/04/Accelerated-C-Solutions-to-Exercises-Chapter-8-Part-2/index.html deleted file mode 100644 index 1e383b00..00000000 --- a/2018/04/04/Accelerated-C-Solutions-to-Exercises-Chapter-8-Part-2/index.html +++ /dev/null @@ -1,606 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - Accelerated C++ Solutions to Exercises(Chapter 8 Part 2) | Liam's Blog - - - - - - - - - - - - -
-
- -
-
- - -
- - -

If history repeats itself, and the unexpected always happens, how incapable must Man be of learning from experience?—George Bernard Shaw.

-
- - -
- - - - -
-
- - -
- - 0% -
- - - - -
-
-
- - -
- - -
- - - -
- - - - - -
-

- Accelerated C++ Solutions to Exercises(Chapter 8 Part 2) -

- - -
- - - - -
- - -

Exercise 8-3

As we learned in §4.1.4/58, it can be expensive to return (or pass) a container by value. Yet the median function that we wrote in §8.1.1/140 passes the vector by value. Could we rewrite the median function to operate on iterators instead of passing the vector? If we did so, what would you expect the performance impact to be?

-

Solution & Results

Yes, we can rewrite the median function without passing the vector. See the median funtion template below

-
1
2
3
4
5
6
7
8
9
10
11
12
13
// median function template
template<class In>
typename In::value_type median(In begin, In end)
{
if(begin == end)
throw domain_error("median of an empty vector");

sort(begin, end);

typename In::difference_type size = end - begin;
In mid = begin + size/2;
return size % 2 == 0 ? (*mid + *(mid - 1))/2 : *mid;
}
-

The type parameter of the function template is In which will be infered from two arguments, i.e. two iterators that denote the range of a sequence. In addition, The Iterator::value_type gives the type of the element the iterator can point to. Therefore, we can set the return type using

-
1
typename In::value_type
-

If the container is vector, this expression is equivalent to

-
1
vector<double>::iterator::value_type
-

In the original version, all computations are based on values of type size_type. But now we don’t have such information anymore. Instead we can do computations with iterators. Firstly, we checks whether the container is empty using begin == end. The sort function is applied as same as that in the original version. Then we get the “size” of the container using end - begin, which gives the distance between the first element and one past the last elements. The result of such computation has type difference_type which is in fact a signed integer value.

-

Once we have the “size”, we can get the position of the mid point. Finally we can compute the median by accessing elements via dereference operation on iterators. One might wonder that why we get the position of the mid point using begin + size/2 rather than (begin + end)/2? This is because iterators doesn’t support additive operation between two iterators, but they (random access iterators) do support additive operation between an iterator and an integer value.

-

Now let’s perform following test and check what happens:

-

test 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
#include <iostream>	// to get the declaration of cout, endl
#include <vector> // to get the declaration of vector
#include <stdexcept> // to get the declaration of domain_error
#include <algorithm> // to get the declaration of sort

using std::cout; using std::vector;
using std::endl; using std::domain_error;
using std::sort;

// print function template
template<class T>
void print(T &contianer)
{
cout << *(contianer.begin());
for(typename T::const_iterator i = contianer.begin() + 1; i != contianer.end(); ++i)
cout << ' ' << *i;
cout << endl;
}

// median function template
template<class In>
typename In::value_type median(In begin, In end)
{
if(begin == end)
throw domain_error("median of an empty vector");

sort(begin, end);
typename In::difference_type size = end - begin;
In mid = begin + size/2;

return size % 2 == 0 ? (*mid + *(mid - 1))/2 : *mid;
}

int main()
{
// print a sequence
vector<double> vec{9, 6, 8, 3, 5, 7, 1, 2};

cout << "Original sequence: ";
print(vec);

// get the median of the original sequence
cout << "The median value is: " << median(vec.begin(), vec.end()) << endl;

// print the new sequence
cout << "Now the sequence becomes: ";
print(vec);
return 0;
}
- -

results

-
1
2
3
Original sequence: 9 6 8 3 5 7 1 2
The median value is: 5.5
Now the sequence becomes: 1 2 3 5 6 7 8 9
-

The results show that we successfully compute the median value but change the original sequence due to operate on the original container directly. This is the cost that calling by reference instead of calling by value.

-

Exercise 8-4

Implement the swap function that we used in §8.2.5/148. Why did we call swap rather than exchange the values of *beg and *end directly? Hint: Try it and see.

-

Solution & Results

1
swap(*begin++, *end);
-

To swap two elements, generally we can write

-
1
2
3
4
T temp = *begin;
*begin = *end;
*end = x;
++begin;
-

where T is the type of the elements begin refers to.
Clearly, the key to solution is how to determine T. One way is to deduce the type from the varaible automatically using auto (or decltype) specifier. Another way is to use value_type to get the type of the element the iterator can point to. I tried both two methods and the test program is shown below

-
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
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
#include <iostream>		// to get the declaration of cout, endl
#include <algorithm> // to get the declaration of swap
#include <vector> // to get the declaration of vector

using std::cout; using std::endl;
using std::vector; using std::swap;

// print function template
template <class T>
void print(T &t)
{
for (typename T::const_iterator i = t.begin(); i != t.end(); ++i)
cout << *i << endl;
}

// original reverse function template
template<class Bi>
void reverse_ori(Bi begin, Bi end)
{
while (begin != end)
{
--end;
if (begin != end)
{
swap(*begin++, *end);
}

}
}

// revised reverse function version 1: using auto
template<class Bi>
void reverse_v1(Bi begin, Bi end)
{
while (begin != end)
{
--end;
if (begin != end)
{
auto temp = *begin;
*begin = *end;
*end = temp;
++begin;
}

}
}

// revised reverse function version 2: using value_type
template<class Bi>
void reverse_v2(Bi begin, Bi end)
{
typedef typename Bi::value_type valueType;
while (begin != end)
{
--end;
if (begin != end)
{
valueType temp = *begin;
*begin = *end;
*end = temp;
++begin;
}

}
}

int main()
{
vector<int> vec{1, 2, 3, 4, 5, 6, 7, 8, 9, 10};

// reverse
reverse_ori(vec.begin(), vec.end());
print(vec);

// reverse again
cout << endl;
reverse_v1(vec.begin(), vec.end());
print(vec);

// reverse again
cout << endl;
reverse_v2(vec.begin(), vec.end());
print(vec);

return 0;
}
-

This program gives following outputs

-
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
10
9
8
7
6
5
4
3
2
1

1
2
3
4
5
6
7
8
9
10

10
9
8
7
6
5
4
3
2
1
-

We have seen that both three method works fine, but why we use swap? The major limitation of other two methods is that they both copies objects to and from a variable, which may be not allowed for some highlevel objects. Also, it is not inefficient to do if we can avoid performing copies. The sandard swap algorithm is also an template and hence can determine the elements type automatically. As far as I know, it doesn’t rely on such temp variable as other two methods. I’ll give more explanations about swap once I know how exactly it works.

-

Exercise 8-5

Reimplement the gen_sentence and xref functions from Chapter 7 to use output iterators rather than writing their output directly to a vector . Test these new versions by writing programs that attach the output iterator directly to the standard output,and by storing the results in a list and a vector.

-

Solution & Results

To be updated.

-

Exercise 8-6

Suppose that m has type map<int, string> , and that we encounter a call to copy(m.begin(), m.end(), back_inserter(x)). What can we say about the type of x ? What if the call were copy(x.begin(), x.end(), back_inserter(m)) instead?

-

Solution & Results

1
copy(m.begin(), m.end(), back_inserter(x));
-

This statement copy all elements of m into the destination sequence x via back_inserter. It is known that x should support push_back member function. In addition, the elements contained in m have type pair<int, string>. Therefore, x should be a container that has type c<pair<int, string>>. Standard contaniers that meet the first condition include vector, list, deque, string. But string can only store string literals. In summary, I presume that x can be

-
    -
  1. vector<pair<int, string>>
  2. -
  3. list<pair<int, string>>
  4. -
  5. deque<pair<int, string>>
  6. -
-

To verify my expectation, I write a test program shown as below:

-
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
#include <iostream>		// to get the declaration of cout, endl
#include <vector> // to get the declaration of vector
#include <string> // to get the declaration of string
#include <list> // to get the declaration of list
#include <deque> // to get the declaration of deque
#include <map> // to get the declaration of map
#include <algorithm> // to get the declaration of copy
#include <iterator> // to get the declaration of back_inserter
#include <utility> // to get the declaration of pair

using std::cout; using std::copy;
using std::endl; using std::vector;
using std::map; using std::string;
using std::list; using std::pair;
using std::deque; using std::back_inserter;

template <class T>
void print(T &t)
{
for (typename T::const_iterator i = t.begin(); i != t.end(); ++i)
cout << i->first << ' ' << i->second << endl;
}

int main()
{
typedef pair<int, string> element;
vector<element> x;
list<element> y;
deque<element> z;
map<int, string> m{{1, "abc"}, {2, "bcd"}, {3, "def"}, {4, "hig"}, {5, "klm"}};
copy(m.begin(), m.end(), back_inserter(x));
copy(m.begin(), m.end(), back_inserter(y));
copy(m.begin(), m.end(), back_inserter(z));

print(x);
cout << endl;
print(y);
cout << endl;
print(z);

return 0;
}
-

The results show that all these three types of container work fine for the copy algorithm.

-
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
1 abc
2 bcd
3 def
4 hig
5 klm

1 abc
2 bcd
3 def
4 hig
5 klm

1 abc
2 bcd
3 def
4 hig
5 klm
- -

For the second function call

-
1
copy(x.begin(), x.end(), back_inserter(m));
-

I presume it doesn’t work because map doesn’t have member function push_back. We can correct this function call as

-
1
copy(x.begin(), x.end(), inserter(m, m.end());
-

If we add follow statements to above program

-
1
2
3
4
5

vector<element> n{{10, "abcd"}, {20, "bcde"}, {30, "defh"}};
map<int, string>::iterator it = m.end();
copy(n.begin(), n.end(), inserter(m, it));
print(m);
-

The modified program gives results as expected

-
1
2
3
4
5
6
7
8
1 abc
2 bcd
3 def
4 hig
5 klm
10 abcd
20 bcde
30 defh
-

Noting that, though we insert at the position where m.end() denotes, the new element may not appear starting from the end of the map. This is because that the map automatically sorts elements according to the values of keys.

-
-

Exercise 8-7

Why doesn’t the max function use two template parameters, one for each argument type?

-

Solution & Results

Let’s write the max function using two template parameters:

-
1
2
3
4
5
template<class T, class P>
P max(const T& left, const P& right)
{
return left > right ? left : right;
}
-

The biggest problem is how can we correctly set the return type. There are two types T and P. If we set return type as T, the value returned may has type P, and vice versa. Concequently, type coversion happens. However, the result of type conversion depends on the range of the values that the types permit. For example, we call the max function as follows

-
1
2
3
4
int main()
{
cout << max(3500.9, 'a');
}
-

If we set return type P, what happens of this program is unknown.

-

Therefore, such function template is non-practical and may lead to fatal errors.

-
-

Exercise 8-8

n the binary_search function in §8.2.6/148, why didn’t we write (begin + end)/ 2 instead of the more complicated begin + (end - begin) /2 ?

-

Solution & Results

This is because that iterators doesn’t support additive operation between two iterators. For random access iterators, they do support arithmetic operator + and - between an iterator and an ineger value, e.g.

-
1
2
3
iterator_i + integer_j
integer_j + iterator_i
iterator_i - integer_j
-

In addition, they support substracting an iterator from another, e.g.

-
1
iterator_i - iterator_j
-

Such operation yields an value of type difference_type, which in fact is an signed integer. Therefore, it is legal to do

-
1
begin + (end - begin) /2
-

as such expression is in fact the additive operation between an iterator and an iteger value.

-

But it makes no sence to do

-
1
(begin + end)/ 2
- -
-

Reference

Koenig, A. and Moo, B.E., 2000. Accelerated C++.

-

Lippman, S.B., Lajoie, J. and Moo, B.E., 2013. C++ Primer, 5th edt.

- -
- - - - - - -
- -
- - - - -
- - - - -
- - -
- - - - -
- - - - - - - - -
-
- - -
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/2018/04/06/C-Defining-new-types/index.html b/2018/04/06/C-Defining-new-types/index.html deleted file mode 100644 index b77bd081..00000000 --- a/2018/04/06/C-Defining-new-types/index.html +++ /dev/null @@ -1,641 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - C++ - Defining new types | Liam's Blog - - - - - - - - - - - - -
-
- -
-
- - -
- - -

If history repeats itself, and the unexpected always happens, how incapable must Man be of learning from experience?—George Bernard Shaw.

-
- - -
- - - - -
-
- - -
- - 0% -
- - - - -
-
-
- - -
- - -
- - - -
- - - - - -
-

- C++ - Defining new types -

- - -
- - - - -
- - -

Rewrite Student_info in class type

In chapter 4, we learned how to organize data with a single data structure:

-
1
2
3
4
5
struct Student_info{
std::string name;
double midterm, final;
std::vector<double> homework;
};
-

The type Stuent_info holds information including name, midterm, final exam grades and a bunch of homework grades for a student. It help us to easily access the information of one student. Then, we wrote several functions to compute final grades based on the objects of such type. However, our program that uses such data type has several limitations

-
    -
  1. users of our program have to follow some conventions. If we want to use a newly created object of Student_info, we need to ensure that we have read data into it. If one want to check whether the object contains information, he can only look at the actual data members in it. This requires users knowing well about the iternal structure of such type that the object belongs to. In other words, if I am a user but not a programmer, I need to learn each details about how to implement such type in a program.

    -
  2. -
  3. there is no data protection mechanism. Users might want to keep students’ information unchanged once read in. However, we don’t provide such mechanism in the original program.

    -
  4. -
  5. There is no universal interface for users. Function such as read** is closely connected with a Student_info object as they can change the state of an object. We might like to put them into a single header file for providing convenience for other users. However, we don’t have such structure that provids a universal interface for users.

    -
  6. -
-

Now we learn how to deal with these problems with a new data type-class. class type is a mechanism that combines related data values into a data structure. It is an abstract data type that similar to Student_info. But it also provides an interface that allows us to operate an object, e.g. an Student_info, while hiding all deatils of the object.

-

For example, we are familar with vector which is a class that provides a set of operations, such as push_back, erase for users. But we don’t know how exactly these functions are implemented.

-

Member function

As analysed above, we may don’t need some details of the students’ information as well as how read function deal with these information. What users need to know is how to use the relevant functions. Therefore, we need to design such interface.

-
1
2
3
4
5
6
7
8
9
struct Student_info
{
std::string name;
double midterm, final;
std::vector<double> homework;

std::istream & read(std::istream &);
double grade() const;
};
-

Let’s say we have such an Student_info object named record. It has four data members name, midterm, final and homework. In addition, there are two member functions named read and grade which let us to read a record from an input stream and calculate the final grade for the object. Now, though we didn’t define any such structure before, we can presume how to use it. By the analogy of other class such as vector, I can store information for one student by calling its member function read:

-
1
record.read(cin);
-

Again, we can calculate the final grade for record by calling member function grade():

-
1
record.grade();
-

Member functions can be defined inside or outside the class definition. Member functions defined inside are implicitly inline to avoid function call overhead. Apparently, member functions above are declared only and defined outside of the class. Let’s look at the read function:

-
1
2
3
4
5
6
istream & Student_info::read(istream &in)
{
in >> name >> midterm >> final;
read_hw(in, homework);
return in;
}
- -

Comparing with the original version:

-
    -
  1. the name of the function is Student_info::read because it is declared inside of and as a member of Student_info.
  2. -
  3. we don’t need to pass the Student_info object as an argument to the function as the function is a member of Student_info.
  4. -
  5. the member function can access data members directly using name, midterm etc. instead of record.name, record.midterm etc..
  6. -
-

Now let’s look at the grade member:

-
1
2
3
4
double Student_info::grade() const
{
return ::grade(midterm, final, homework);
}
-

It is similar to the read member in terms of putting :: in front of the name and accessing data members without any qualification. But there are two differences:

-

First, putting :: in front of grade when call it means that the grade function is a version that is not a member of class Student_info. Second, we put a qualifier const after the parameter list. What’s that mean? Recalling the original version

-
1
double grade(const Student_info &) {...}
-

We pass Student_info object by reference to const to avoid changing the argument to pass. In the new version, we don’t need to pass the Student_info* object any more as the function itself is a member function. Therefore, we add the **const after the parameter list for the same purpose, that is, to avoid changing the state of data members of the Student_info object. Noting that, the class object may not be created with const but is referenced to const in a function. Then, the function treat the object as if it were const.

-

Protection

Though we provide interface for our class, users still can access data members directly and might meddle the implementation unintentionally. Therefore it is probably sensible to restrict users’ rights such that they can only access data members through member functions. The idea behind this process (i.e. data hiding) is called encapsulation. C++ supports encapsulation with two access specifiers(aka. protection lables):

-
    -
  1. public specifier. Members defined after a keyword public are accessible to all parts of the program. This is typically used to specify members that define the interface to the class.
  2. -
  3. private specifier. Members defined after a keyword private are accessible to the member functions of the class only. This is typically used to specify members that involves the implementation.
  4. -
-

Therefore, we can change our class as follows:

-
1
2
3
4
5
6
7
8
9
10
11
12
13
class Student_info 
{
public:
// interface goes here
double grade() const;
std::istream & read(std::istream &);

private:
// implementation goes here
std::string name;
double midterm, final;
std::vector<double> homework;
};
-

As explained above, We have specified member functions as public and data memmers as private. It has been noted that we use keyword class instead struct to define the class type. Both two keywords are ok for defining a class type. The only difference is the default access level. A class may define members before its first protection lable. if we use class, those members are default private while if we use struct, those members are default public. Accordingly, the class defined above is equivalent to

-
1
2
3
4
5
6
7
8
9
10
class Student_info
{
std::string name;
double midterm, final;
std::vector<double> homework;

public:
double grade() const;
std::istream & read(std::istream &);
};
-

In general, if we don’t intend to restrict data access, we use struct. But if we intend to have private members, we use class.

-

Accessor functions

Now we have successfully hidden data memebers but the problem next is that We fail to access name as well. We can set member name to public, however, what we really need is only the read access rather than write access. Alternatively, we can write another member function that returns the name but is not allowed to rewrite the name object.

-
1
2
3
4
5
6
7
8
9
10
11
12
13
14
class Student_info 
{
public:
// interface goes here
double grade() const;
std::istream & read(std::istream &);
std::string name() const { return n; }

private:
// implementation goes here
std::string n;
double midterm, final;
std::vector<double> homework;
};
- -

As mentioned above, member functions defined inside are implicitly inline to avoid function call overhead. In this class, we define member name inside the class to imply compiler that this member function should be expanded inline at anywhere it is called.

-

Functions such as name are often called accessor functions. It seems that such function breaks the encapsulation that we were trying to achieve. Therefore, such function is privided only when accessors are part of the abtract interface of the class. In this case, the abtraction is that of a student and a corresponding final grade. Obviously, name is part of the abstract interface.

-

Accordingly, the compare function changes to:

-
1
2
3
4
bool compare(const Student_info & x, Student_info & y)
{
return x.name() < y.name();
}
- -

Testing for empty

There is another problem we may concern when using such class object. For example, if we call the member grade without calling read first, we would get an exception due to homework is empty. A traditional solution is to catch the exception and let users know what leads to the exception. But again, this may require users knowing about the iternal structure of the class object. Alternatively, we can provide a public member function named valid:

-
1
2
3
4
5
6
class Student_info()
{
public:
bool valid() const { return !homework.empty(); }
// as before
}
-

This member function tells the state of the object: if valid function returns true, it indicates that the object contains valid data, i.e. at least one homework grade; if valid returns false, it indicates the object is invalid for computing the final grades as there is no any homework grade. Users can check the state of the class object before the grade function call, thereby avoiding a potential exception.

-

Constructors

It is known that when we create an object, it should be default initialized or assigned with an appropriate value. For example, when we define a string without an initializer, we get an empty string. So, what happens when we create a class object?

-

The class type supports defining how to initialize an class onject through constructors which are special member functions. There is no way to explicitly call constructors. Instead creating an class object automatically calls an appropriate constructor as a side effect.

-

If we do not define any constructors, the compiler will synthesize one for us. The synthesized constructor initializes the data member to a value depending on how the object is created. Specifically, if a class object is created as a local variable, then the data members will be default initialized. If a class object is used to initialize a container element, either as a side effect of adding new element to a map, or as the elements of container defined to have a given size, then the member will be value-initialized determined by a class type itself.

-

The rules below summarize how a class type initializes its data members:

-
    -
  1. if an object is of a class type that defines one or more constructors, then the appropriate constructor determines how data memebers will be initialized.

    -
  2. -
  3. if an object is of built-in type, then value-initializing it sets it to zero, and default-initializing it gives it an undefined value.

    -
  4. -
  5. In the case that an object is of a class type that doesn’t define any constructor, value- or default-initializing the object value, or default-initializes each of its data members. This process will be recursive if any data member is of a class type with its own constructor.

    -
  6. -
-

In our example, the Student_info class type is the case 3. If we define a local variable that is of such class type, n and homework are default-initialized and concequently yields an empty string and vector respectively. However, default-initializing midterm and final leads to undefined values. To ensure all data members have sensible values at all times, we should define constructors for our class type. Let’s look at two constructors:

-
1
2
3
4
5
6
7
class Student_info 
{
public:
Student_info (); // construct an empty Student_info object
Student_info (std::istream); // construct one by reading from input stream
// as before
}
-

We add two public members functions that are both named Student_info , that is, the name of the class type itself, and both have no return type. These two features distinguish constructors from other member functions. It can also be observed that constructors has two different versions, the first constructor of which takes no argument while the second takes input stream object as argument. Corresponding, we can write our code like

-
1
2
Student_info s;          // an empty Student_info
Student_info s2(cin); // initialize s2 by reading from cin
- -

The default constructor

The first constructor

-
1
Student_info s;
-

is known as default constructor which takes no arguments and ensures that all data members are normally initialized through:

-
1
Student_info::Student_info(): midterm(0), final(0) {}
-

This definition uses new syntax: the contents between : and { are a sequence of initializers which initializes each given data member with the value that appears inside of the corresponding parenthese. This default constructor doesn’t explictly initialize n and homework members as they are initialized implicitly. In specific, n is initialized by the string default constructor and homework is initialized by the vector default constructor.

-

When we create a new class object, the implementation proceeds following steps:

-
    -
  1. allocates memory to hold the object
  2. -
  3. initializes the object as the constructor defines
  4. -
  5. executes the constructor body
  6. -
-

The implementation initializes every data member of every object even if some members are not explicitly initialized with the constructor initializer list.

-

Constructors with arguments

1
Student_info::Student_info(istream &is) { read(is); }
-

There is no initializer provided for each data member and hence n and homework will be initialized by the default constructors for string and vector, respectively. But midterm and fianl are undefined if the object is default-initialized otherwise are value-initialized to 0. Nevertheless, the function body gives new values to these data members by calling read function.

-

Class-based grading program

Now we have successfully defined a class-based Student_info type. Apparently, using the class is different from using the original structure. Therefore, the last step is to rewrite the main function and organsize files.

-

mainfunction.cpp

-
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
#include <algorithm>		// to get the declaration of max, sort
#include <iomanip> // to get the declaration of setprecision
#include <iostream> // to get the declaration of streamsize
#include <stdexcept> // to get the declatation of domain_error
#include <string> // to get the declaration of string
#include <vector> // to get the declaration of vector
#include "Student_info.h" // to get the declaration of Student_info
#include "grade.h" // to get the declatation of grading functions

using std::cin; using std::setprecision;
using std::cout; using std::sort;
using std::endl; using std::streamsize;
using std::domain_error; using std::string;
using std::max; using std::vector;

int main()
{
vector<Student_info> students;
Student_info record;
string::size_type maxlen = 0;

// read and store all the records, and find the length of the longest name
while(record.read(cin))
{
maxlen = max(maxlen, record.name().size());
students.push_back(record);
}

// alphabetize the records
sort(students.begin(), students.end(), compare);

// write each line of outpurs
for (vector<Student_info>::const_iterator it = students.begin();
it != students.end(); ++it)
{
// write the name, blanks
cout << (*it).name() << string(maxlen + 1 - (*it).name().size(), ' ');

// compute and write the final grade
try{
double final_grade = (*it).grade();
streamsize prec = cout.precision();
cout << setprecision(3) << final_grade << setprecision(prec);
} catch(domain_error e){
cout << e.what();
}
cout << endl;
}
return 0;
}
- -

Student_info.h

-
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
#ifndef GUARD_STUDENT_INFO
#define GUARD_STUDENT_INFO

#include <string>
#include <iostream>
#include <vector>

class Student_info
{
public:
Student_info (); // default constructor
Student_info (std::istream &); // constructor with argument
std::string name() const { return n; } // inline member function return name
bool valid() const { return !homework.empty(); } // inline member function check state
std::istream & read(std::istream &); // member function read in data
double grade() const; // member function calculate final grade

private:
std::string n;
double midterm, final;
std::vector<double> homework;
};

std::istream & read_hw(std::istream &, std::vector<double> &); // nonmember function read data into a vector
bool compare(const Student_info &, const Student_info &); // nonmember function compare two string

#endif
- -

Student_info.cpp

-
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
#include "Student_info.h"
#include "grade.h"

using std::vector; using std::istream;

// construct an empty Student_info object
Student_info::Student_info (): midterm(0), final(0) { }

// construct one by reading from input stream
Student_info::Student_info (std::istream & in) { read(in); }

// member function read data from input stream
std::istream & Student_info::read(std::istream &in)
{
// reads and store the student's name, midterm and final exam grades
in >> n >> midterm >> final;

// reads and store all homework grades
read_hw(in, homework);
return in;
}

// member function grade
double Student_info::grade() const
{
return ::grade(midterm, final, homework);
}

// nonmember function compare
bool compare(const Student_info &x, const Student_info &y)
{
return x.name() < y.name();
}

// nonmember function read_hw
istream & read_hw(istream &in, vector<double> &hw)
{
if (in){
//get rid of previous contents
hw.clear();

// read homework grades
double x;
while(in >> x)
hw.push_back(x);

// clear the stream so that input will work for the next student
in.clear();
}
return in;
}
- -

grade.h

-
1
2
3
4
5
6
7
8
9
#ifndef GUARD_GRADE_H
#define GUARD_GRADE_H

#include<vector>

double grade(double, double, const std::vector<double> &);
double grade(double, double, double);
double median(std::vector<double>);
#endif
- -

grade.cpp

-
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 <algorithm>
#include <stdexcept>
#include <vector>
#include "grade.h"

using std::domain_error; using std::istream;
using std::vector; using std::sort;

// grade function 1
double grade(double midterm, double final, const vector<double> &hw)
{
if (hw.empty())
throw domain_error("student has done no homework");
return grade(midterm, final, median(hw));
}

// grade function 2
double grade(double midterm, double final, double homework)
{
return 0.2*midterm + 0.4*final + 0.4*homework;
}

// compute the median of vector<double>
double median(vector<double> vec)
{
// check whether the vec is empty
if (vec.begin() == vec.end())
throw domain_error("median of an empty vector");

// sort the grades
sort(vec.begin(), vec.end());

// compute the median homework grade
vector<double>::difference_type size = vec.end() - vec.begin();
vector<double>::const_iterator mid = vec.begin() + size/2;
return size % 2 == 0 ? (*mid + *(mid - 1))/2 : *mid;
}
- -

Test

1
2
3
4
5
6
7
8
9
10
11
12
13
Inputs

Robin 90 87 79 88 81 73 45
Brendan 70 69 88 100 91 75 66
Arsenii 99 87 89 88 74 90 70
Liam 83 66 100 76 87 91 78

Outputs

Arsenii 89.8
Brendan 76.8
Liam 77.8
Robin 84.4
- -

The program works as same as the original program and delivers same results.

- -
- - - - - - -
- -
- - - - -
- - - - -
- - -
- - - - -
- - - - - - - - -
-
- - -
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/2018/04/08/Accelerated-C-Solutions-to-Exercises-Chapter-9/index.html b/2018/04/08/Accelerated-C-Solutions-to-Exercises-Chapter-9/index.html deleted file mode 100644 index 42f6f39e..00000000 --- a/2018/04/08/Accelerated-C-Solutions-to-Exercises-Chapter-9/index.html +++ /dev/null @@ -1,641 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - Accelerated C++ Solutions to Exercises(Chapter 9) | Liam's Blog - - - - - - - - - - - - -
-
- -
-
- - -
- - -

If history repeats itself, and the unexpected always happens, how incapable must Man be of learning from experience?—George Bernard Shaw.

-
- - -
- - - - -
-
- - -
- - 0% -
- - - - -
-
-
- - -
- - -
- - - -
- - - - - -
-

- Accelerated C++ Solutions to Exercises(Chapter 9) -

- - -
- - - - -
- - -

Exercise 9-0

Compile, execute, and test the programs in this chapter.

-

Solution & Results

Please find the solution and analysis in Defining new types.

-

Exercise 9-1

Reimplement the Student_info class so that it calculates the final grade when reading the student’s record, and stores that grade in the object. Reimplement the grade function to use this precomputed value.

-

Solution & Results

A similar program has been completed in exercise 4-6. Now I’ll write a new grading program based on class type. The strategy can be logically divided into three parts:

-
    -
  1. abstract data members
  2. -
  3. design interface and write member functions
  4. -
  5. access Control
  6. -
-

data members and constructors

The original program uses a class type that has four data members: name, midterm, final and homework. This exercise requires us to store the final grade in the class object, which implies that we need another data member grade representing the final grade. There seems no need to store midterm, fina and homework anymore due to that the final grade is computed when reading the student’s record. I didn’t remove them merely as theoretically these information should be kept for a student. Now, we have part of our new class type

-
1
2
3
4
5
6
7
class Student_info{
std::string n;
double midterm, final, g;
std::vector<double> homework;

// to be filled by defining/declaring member functions
};
-

The first I considered was to define constructors when desigining an proper interface. The purpose is to allow us to initialize the class object through:

-
1
2
Student_info record;        // create an empty object
Student_info record(cin); // create and initialize an object by reading from input stream
-

The first one needs a default constructor that is responsible for initializing data members. Following code shows their declarations and definitions that are put outside the class.

-

declaring constructors

-
1
2
Student_info ();        // construct an empty object 
Student_info (std::istream &); // construct an object by reading from istream
-

defining constructors

-
1
2
3
4
5
6
7
8
9
Student_info::Student_info(): midterm(0), final(0), g(0) {}
Student_info::Student_info (std::istream & in) { read(in); }
```
Nothing that all three **double** type variables are value-initialized to 0 while **homework** is initialized by default constructor of **vector<double>** type, leading to an empty **homework**.

### other member functions and protection
The next is to define a member function to read data into the class object. The declaration is as same as the previous one:
```c++
std::istream & read(std::istream &);
-

When we define the read member, we should meet the requirement that computing the final grade in the process of reading.

-
1
2
3
4
5
6
7
8
9
10
11
12
istream & Student_info::read(istream &is)
{
// reads and store the student's name, midterm and final exam grades
is >> n >> midterm >> final;
if (is)
{
// reads and store all homework grades
read_hw(is, homework);
g = !homework.empty() ? ::grade(midterm, final, homework) : 0;
}
return is;
}
-

Noting that I deal with the case of empty homework with an if statement which computes the final grade if the homwork is not empty and otherwise sets the final grade to 0. The purpose is to avoid the exception when reading data. Alternatively, I define extra two functions to check the validity of final grade and “catch” the state of the object. These two functions are defined inside the class

-
1
2
bool valid() const { return !homework.empty(); }
std::string state() const { return valid() ? "valid" : "invalid: student has done no homework"; }
-

This allows users to seperate valid records and invalid records.

-

Finally, we define members to get the name and grade for the purpose that prevents users access data members directly.

-
1
2
std::string name() const { return n; }
double grade() const { return g; }
-

All member functions except read and constructors are not allowed to change data members by adding qualifier const after the parameter list. To seperate the data abstraction and interface, we control access using specifier public and private.

-

Organize files

Now let’s complete the header file Student_info.h and the corresponding source file.

-
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
#ifndef GUARD_STUDENT_INFO
#define GUARD_STUDENT_INFO

#include<iostream>
#include<string>
#include<vector>

class Student_info{
public:
// constructors
Student_info ();
Student_info (std::istream &);

// member functions to check state
bool valid() const { return !homework.empty(); }
std::string state() const { return valid() ? "valid" : "invalid: student has done no homework"; }

// member functions read and functions to get name, grade and
std::string name() const { return n; }
double grade() const { return g; }
std::istream & read(std::istream &);

private:
std::string n;
double midterm, final, g;
std::vector<double> homework;
};

// nonmember functions
bool compare(const Student_info &, const Student_info &);
std::istream & read_hw(std::istream &, std::vector<double> &);
#endif
- -

Student_info.cpp

-
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
#include <stdexcept>
#include "Student_info.h"
#include "grade.h"

using std::vector; using std::cin;
using std::istream; using std::cout;
using std::domain_error;

// construct one by reading from input stream
Student_info::Student_info(): midterm(0), final(0), g(0) {}
Student_info::Student_info (std::istream & in) { read(in); }

// member function read
istream & Student_info::read(istream &is)
{
// reads and store the student's name, midterm and final exam grades
is >> n >> midterm >> final;
if (is)
{
// reads and store all homework grades
read_hw(is, homework);
g = !homework.empty() ? ::grade(midterm, final, homework) : 0;
}
return is;
}

// nonmember function compare two strings
bool compare(const Student_info &x, const Student_info &y)
{
return x.name() < y.name();
}

// nonmember function read data into a vector
istream & read_hw(istream &in, vector<double> &hw)
{
// read homework grades
hw.clear();
double x;
while(in >> x)
hw.push_back(x);

// clear the stream so that input will work for the next student
in.clear();

return in;
}
-

Now we can write the main function.

-

mainfunction.cpp

-
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
#include <algorithm>		// to get the declaration of max, sort
#include <iomanip> // to get the declaration of setprecision
#include <iostream> // to get the declaration of cin, cout, endl
#include <stdexcept> // to get the declaration of domain_error
#include <string> // to get the declaration of string
#include <vector> // to get the declaration of vector
#include "Student_info.h" // to get the declaration of Student_info

using std::cin; using std::setprecision;
using std::cout; using std::sort;
using std::endl; using std::streamsize;
using std::domain_error; using std::string;
using std::max; using std::vector;

int main()
{
vector<Student_info> students;
Student_info record;
string::size_type maxlen = 0;

// read and store all the records, and find the length of the longest name
while(record.read(cin))
{
maxlen = max(maxlen, record.name().size());
students.push_back(record);
}


// alphabetize the records
sort(students.begin(), students.end(), compare);

// write each line of outpurs
for (vector<Student_info>::const_iterator it = students.begin();
it != students.end(); ++it)
{
// write the name, blanks
cout << (*it).name() << string(maxlen + 1 - (*it).name().size(), ' ');
// compute and write the final grade
double final_grade = (*it).grade();
streamsize prec = cout.precision();
cout << setprecision(3) << final_grade << setprecision(prec);

if(!(*it).valid())
cout << string(4,' ') << (*it).state() << endl;
else
cout << endl;
}
return 0;
}
-

There is nothing new except that I deal with exception with member functions valid() and state() instead the try block.

-

Finally, I present the header file and source file that contains overloaded grade function and median function.

-

grade.h

-
1
2
3
4
5
6
7
8
9
#ifndef GUARD_GRADE_H
#define GUARD_GRADE_H

#include<vector>

double grade(double, double, const std::vector<double> &);
double grade(double, double, double);
double median(std::vector<double>);
#endif
- -

grade.cpp

-
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 <algorithm>
#include <stdexcept>
#include <vector>
#include "grade.h"

using std::domain_error; using std::istream;
using std::vector; using std::sort;

// grade function 1
double grade(double midterm, double final, const vector<double> &hw)
{
if (hw.empty())
throw domain_error("student has done no homework");
return grade(midterm, final, median(hw));
}

// grade function 2
double grade(double midterm, double final, double homework)
{
return 0.2*midterm + 0.4*final + 0.4*homework;
}

// compute the median of vector<double>
double median(vector<double> vec)
{
// check whether the vec is empty
if (vec.begin() == vec.end())
throw domain_error("median of an empty vector");

// sort the grades
sort(vec.begin(), vec.end());

// compute the median homework grade
vector<double>::difference_type size = vec.end() - vec.begin();
vector<double>::const_iterator mid = vec.begin() + size/2;
return size % 2 == 0 ? (*mid + *(mid - 1))/2 : *mid;
}
- -

Test

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
Inputs

Nqacg 32.4444 16.3838 43
Kmgsk 89.2525 14.7374 32
Awhof 73.7071 73.8485
Thyyp 92.7172 47.5556
Zvxxc 66.9091 69.6162 0
Asezo 67.8182 32.6364 10
Evawh 77.798 54.9596 13
Qhwir 75.4242 93.5758
Nbcpz 71.6263 75.8182 47
Dbevs 67.4949 75.3434 31

Outputs

Asezo 30.6
Awhof 0 invalid: student has done no homework
Dbevs 56
Evawh 42.7
Kmgsk 36.5
Nbcpz 63.5
Nqacg 30.2
Qhwir 0 invalid: student has done no homework
Thyyp 0 invalid: student has done no homework
Zvxxc 41.2
- -

Users can rewrite the main function to generate a better formatted report according to their own preference, using member functions valid() and state().

-
-

Exercise 9-2

If we define the name function as a plain, nonconst member function, what other functions in our system must change and why?

-

Solution & Results

If we define the name function as a nonconst member function, we need to change the compare function as well.

-
1
2
3
4
bool compare(const Student_info &x, const Student_info &y)
{
return x.name() < y.name();
}
-

See from the compare function above, the arguments are passed by reference to const objects. In other words, the compare function treats Student_info object as const. If it calls non-const member functions (i.e. name()), there is no guarantee that it doesn’t modify the object, which is a potential conflict with its definition. THerefore, this is not allowed by the compiler. To correct it, we can remove the qualifer const to release its restriction on arguments as shown below.

-
1
2
3
4
bool compare(Student_info &x, Student_info &y)
{
return x.name() < y.name();
}
- -
-

Exercise 9-3, 9-4

9-3: Our grade function was written to throw an exception if a user tried to calculate agrade for a Student_info object whose values had not yet been read. Users who care are expected to catch this exception. Write a program that triggers the exception but does not catch it. Write a program that catches the exception.

-

9-4: Rewrite your program from the previous exercise to use the valid function, thereby avoiding the exception altogether.

-

Solution & Results

The key is to use member function valid() to avoid the exception. This exercise is simple and hence no further analysis. I only write the main function here and please find other files in Defining new types.

-

Trigger the exception

-
1
2
3
4
5
6
7
8
9
10
11
12
13
#include "Student_info.h"
#include <stdexcept>
#include <iostream>

using std::domain_error;
using std::cout;

int main()
{
Student_info record;
record.grade();
return 0;
}
- -

catch the exception

-
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
#include "Student_info.h"
#include <stdexcept>
#include <iostream>

using std::domain_error;
using std::cout;

int main()
{
Student_info record;
try{
record.grade();
}catch(domain_error e){
cout << e.what();
}
return 0;
}
-

avoid the exception

-
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
#include "Student_info.h"
#include <stdexcept>
#include <iostream>

using std::domain_error;
using std::cout;
using std::cin;

int main()
{
Student_info record (cin);
if(record.valid())
cout << record.grade();
return 0;
}
- -
-

Exercise 9-5

Write a class and associated functions to generate grades for students who take thecourse for pass/fail credit. Assume that only the midterm and final grades matter, and that astudent passes with an average exam score greater than 60. The report should list the students in alphabetical order, and indicate P or F as the grade.

-

Solution & Results

My strategy is to define a new grade function:

-

declaration

-
1
std::string grade() const;
- -

definition

-
1
2
3
4
string Student_info::grade() const
{
return (midterm + final)/2 > 60 ? "P" : "F";
}
-

So, when calling this member, it gives the letter grade P or F. The revised class type is defined as follows

-

Student_info.h

-
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
#ifndef GUARD_STUDENT_INFO
#define GUARD_STUDENT_INFO

#include <string>
#include <iostream>
#include <vector>

class Student_info
{
public:
Student_info (); // default constructor
Student_info (std::istream &); // constructor with argument
std::string name() const { return n; } // inline member function return name
std::istream & read(std::istream &); // member function read in data
std::string grade() const;

private:
std::string n;
double midterm, final;
};

std::istream & read_hw(std::istream &, std::vector<double> &); // nonmember function read data into a vector
bool compare(const Student_info &, const Student_info &); // nonmember function compare two string

#endif

**Student_info.cpp**
```c++
#include "Student_info.h"

using std::vector; using std::string;
using std::istream;

// construct an empty Student_info object
Student_info::Student_info (): midterm(0), final(0) { }

// construct one by reading from input stream
Student_info::Student_info (std::istream & in) { read(in); }

// member function read data from input stream
std::istream & Student_info::read(std::istream &in)
{
// reads and store the student's name, midterm and final exam grades
in >> n >> midterm >> final;
return in;
}

// member function grade
string Student_info::grade() const
{
return (midterm + final)/2 > 60 ? "P" : "F";
}

// nonmember function compare
bool compare(const Student_info &x, const Student_info &y)
{
return x.name() < y.name();
}
- -

Noting that there is no computations as previous version. What we need to do is merely to remove the needless code.

-

Finally, I present the test function followed by the test results

-

mainfunction.cpp

-
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
#include <algorithm>		// to get the declaration of max, sort
#include <string> // to get the declaration of string
#include <vector> // to get the declaration of vector
#include "Student_info.h" // to get the declaration of Student_info

using std::cin;
using std::cout; using std::sort;
using std::endl; using std::string;
using std::max; using std::vector;

int main()
{
vector<Student_info> students;
Student_info record;
string::size_type maxlen = 0;

// read and store all the records, and find the length of the longest name
while(record.read(cin))
{
maxlen = max(maxlen, record.name().size());
students.push_back(record);
}

// alphabetize the records
sort(students.begin(), students.end(), compare);

// write each line of outpurs
for (vector<Student_info>::const_iterator it = students.begin();
it != students.end(); ++it)
{
// write the name, blanks
cout << (*it).name() << string(maxlen + 1 - (*it).name().size(), ' ');

// compute and write the final grade
cout << (*it).grade() << endl;
}
return 0;
}
- -

Test

-
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
Inputs

Xdvdr 55.404 28.7778
Qlyys 91.9192 60.0404
Iutlc 12.9697 61.202
Jygsc 58.2424 99.5657
Wxilm 85.0606 57.2424
Lshfy 34.8687 65.9697
Ujruj 41.8182 89.6364
Orbac 3.58586 56.8788
Fyhub 99 65.2828

Outputs

Fyhub P
Iutlc F
Jygsc P
Lshfy F
Orbac F
Qlyys P
Qzaen P
Ujruj P
Wxilm P
Xdvdr F
- -
-

Exercise 9-6

Rewrite the grading program for the pass/fail students so that the report shows all the students who passed, followed by all the students who failed.

-

Solution & Results

One possible solution is that using ths standard library algorithm stable_partition to rearrange the elements (i.e. student records) such that all passing grades preceds all failing grades.

-
1
stable_partition(students.begin(), students.end(), pgrade);
-

The pgrade is defined as follows

-
1
2
3
4
5
6
7
8
9
bool fgrade(const Student_info &s)
{
return s.grade() < 60;
}

bool pgrade(const Student_info &s)
{
return !fgrade(s);
}
-

These two function can be declared in the file Student_info.h as nonmember function( see Defining new types). I’ll present the main function and a simple test in below part.

-

mainfunction.cpp

-
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 <algorithm>		// to get the declaration of max, sort
#include <iomanip> // to get the declaration of setprecision
#include <iostream> // to get the declaration of streamsize
#include <algorithm> // to get the declaration of stable_partition
#include <string> // to get the declaration of string
#include <vector> // to get the declaration of vector
#include "Student_info.h" // to get the declaration of Student_info

using std::cin;
using std::cout; using std::setprecision;
using std::endl; using std::streamsize;
using std::max; using std::vector;
using std::stable_partition; using std::string;

int main()
{
vector<Student_info> students;
Student_info record;
string::size_type maxlen = 0;

// read and store all the records, and find the length of the longest name
while(record.read(cin))
{
maxlen = max(maxlen, record.name().size());
students.push_back(record);
}

// students who passed followed by all student who failed
stable_partition(students.begin(), students.end(), pgrade);

// write each line of outpurs
for (vector<Student_info>::const_iterator it = students.begin();
it != students.end() ; ++it)
{
if((*it).valid())
{
// write the name, blanks
cout << (*it).name() << string(maxlen + 1 - (*it).name().size(), ' ');

// compute and write the final grade
double final_grade = (*it).grade();
streamsize prec = cout.precision();
cout << setprecision(3) << final_grade << setprecision(prec) << endl;
}
}
return 0;
}
-

Test

-
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
Inputs

Nmlox 42.3434 61.0808 66
Mljwc 39.2727 80.3636 62
Omzml 78.9596 39.8283 42
Buvdm 20.7273 26.8384 35
Lczfw 14.6162 72.1717 75
Bloic 50.2525 60.798 44
Hewvl 59.4646 98.4141 73
Nunsg 95.1414 95.9596 8
Gkaqw 97.7071 85.6263 93
Isohi 49.3434 21.9293 63
Tduzm 92.1111 18.4343 31
Koede 82.404 36.0101 75
Igfab 57.6061 90.9899 15
Ejtaa 93.0404 27.8586 28
Iwhgb 97.6364 44.3333 4
Frbhw 40.7677 68.4747 19
Hsskh 9.44444 90.2121 25
Yvyel 2 20.6566 67
Ktnaa 95.596 19.8586 84
Pdjjb 37.5455 68.303 57

Outputs

Mljwc 64.8
Lczfw 61.8
Hewvl 80.5
Nunsg 60.6
Gkaqw 91
Koede 60.9
Ktnaa 60.7
Nmlox 59.3
Omzml 48.5
Buvdm 28.9
Bloic 52
Isohi 43.8
Tduzm 38.2
Igfab 53.9
Ejtaa 41
Iwhgb 38.9
Frbhw 43.1
Hsskh 48
Yvyel 35.5
Pdjjb 57.6
- -
-

Exercise 9-7

The read_hw function §4.1.3/57 solves a general problem (reading a sequence ofvalues into a vector) even though its name suggests that it should be part of theimplementation of Student_info. Of course, we could change its name—but suppose,instead, that you wanted to integrate it with the rest of the Student_info code, in order to clarify that it was not intended for public access despite its apparent generality? How would you do so?

-

Solution & Results

My solution is to add the read_hw as the member function. To control the access, I specify the member as private. Accordingly, the revised Student_info.h is

-
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
#ifndef GUARD_STUDENT_INFO
#define GUARD_STUDENT_INFO

#include <string>
#include <iostream>
#include <vector>

class Student_info
{
public:
Student_info (); // default constructor
Student_info (std::istream &); // constructor with argument
std::string name() const { return n; } // inline member function return name
bool valid() const { return !homework.empty(); }// inline member function check state
std::istream & read(std::istream &); // member function read in data
double grade() const; // member function calculate final grade

private:
std::string n;
double midterm, final;
std::vector<double> homework;

// private member function read data into a vector
std::istream & read_hw(std::istream &, std::vector<double> &);
};
// nonmember function compare two string
bool compare(const Student_info &, const Student_info &);
#endif
- -

Correspondingly, when we define the read_hw outside the class, we need to specify the name scope using Student_info::read_hw. The new read_hw is given below

-
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
istream & Student_info::read_hw(istream &in, vector<double> &hw)
{
if (in){
//get rid of previous contents
hw.clear();

// read homework grades
double x;
while(in >> x)
hw.push_back(x);

// clear the stream so that input will work for the next student
in.clear();
}
return in;
}
-

All other files keep unchanged and can be found in Defining new types.

-
-

Reference

Koenig, A. and Moo, B.E., 2000. Accelerated C++.

-

Lippman, S.B., Lajoie, J. and Moo, B.E., 2013. C++ Primer, 5th edt.

- -
- - - - - - -
- -
- - - - -
- - - - -
- - -
- - - - -
- - - - - - - - -
-
- - -
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/2018/04/10/C-Managing-memory-and-low-level-data-structures/index.html b/2018/04/10/C-Managing-memory-and-low-level-data-structures/index.html deleted file mode 100644 index 7e450db5..00000000 --- a/2018/04/10/C-Managing-memory-and-low-level-data-structures/index.html +++ /dev/null @@ -1,722 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - C++ - Managing memory and low-level data structures | Liam's Blog - - - - - - - - - - - - -
-
- -
-
- - -
- - -

If history repeats itself, and the unexpected always happens, how incapable must Man be of learning from experience?—George Bernard Shaw.

-
- - -
- - - - -
-
- - -
- - 0% -
- - - - -
-
-
- - -
- - -
- - - -
- - - - - -
-

- C++ - Managing memory and low-level data structures -

- - -
- - - - -
- - -

Pointers

Introduction

pointer is a compound type that points to another base type object. A pointer is a value that represents the address of the object that it points to. What is the address of an object? The address of an object denotes the part of the computer’s memory that contains the object. For example, if x is an object, then &x is the address of x and & is the address operator. Further, if p is a pointer, then *p is the value of the object that p points to and * is the deference operator.

-

We are familar with these two operators& and *, however, may feel comfused about their meanings. Generally, these rules can be summarized as follows:

-
    -
  1. when the & is used as part of a declaration, e.g.

    -
    int i = 10;
    -int &x = i;

    & follows a type and x is a reference.

    -
  2. -
  3. when the & is used as in an expression, e.g.

    -
    int *p = &i;

    & acts as an address operator.

    -
  4. -
  5. when the * is used as part of a declaration, e.g.

    -
    int *p = &i;

    * follows a type and p is a pointer.

    -
  6. -
  7. when the * is used in an expression, e.g.

    -
    int i = *p;
    -*p = i;

    it acts as an deference operator and yields the value of the object that p points to.

    -
  8. -
-

It can be observed that a pointer can indirect access the value an object like a reference. However, a reference itself is not an object while a pointer itself is an object. From the third rule above, we know how to define a pointer. For example

-
1
int *p;
-

p is a pointer that points to int type object. In other words, p has type int*. As other built-in types, a pointer might point to an unknown object unless we initialize it. Typically,we can initialize a pointer as a null pointer which means that the pointer does’t point to any object. There are several ways to do this:

-
1
2
3
int *p = 0;         // the constant 0 can be converted to a pointer type
int *p = nullptr; // c++11 supports
int *p = NULL; // must include header <cstdlib>
-

These three statements lead to an equivalent result. If we want to assign other values to a pointer, typically we uses the address-of operator:

-
1
2
3
4
5
int i = 10;
int j = 20;
int *p = &i; // p points to i
int *q = &j; // q points to j
p = q; // p points to j now
- -

Pointers to functions

It is known that functions are not objects and hence there is no way to copy or assign them, or to pass them as arguments directly. But we do “pass” functions as arguments in previous chapter, for example, the write_analysis function (see declaration below) takes function as its second parameter.

-
1
2
3
4
void write_analysis(std::ostream &out, const std::string &name,
double analysis(const std::vector<Student_info> &),
const std::vector<Student_info> &did,
const std::vector<Student_info> &didnt);
-

In fact this is achieved by taking the address of a function, that is, the pointer to the function. Let’s go into some details about the pointers to functions.

-

The syntax to define a pointer to function is, for example

-
1
int (*fp)(int);
-

Then, if we deference fp and call it with int argument, the result is an int type value.
Another fact is that what we can do with a function is to take its address or call it. Therefore, when we use a function but is not to call it, we are assumed to be take its address, either using & or not. For example,

-
1
2
3
4
int next (int n)
{
return n + 1;
}
-
1
2
3
// these two statements are equivalent
fp = next;
fp = &next;
-

Further, when we call the next function with an int variable i,

-
1
2
3
// these two statements are equivalent
int x = fp(i);
int x = (*fp)(i);
-

Finally, when we have a function that takes another function as a parameter, the compiler will translate the parameter to be a pointer. Taking the write_analysis as an example, the parameter

-
1
double analysis(const std::vector<Student_info> &)
-

is in fact equivalent to

-
1
double (*analysis)(const std::vector<Student_info> &)
-

As shown below, when we “pass” another function as an argument, analysis points to a function named median_analysis.

-
1
analysis = median_analysis; // == analysis = &median_analysis
- -

What if we want to write a function that returns a function pointer?
The simplest way is to use type alias

-
1
typedef double (*analysis_fp) (const vector<Student_info>&);
-

analysis_fp is the name of the type of a pointer to function. Then

-
1
2
// get_analysis_ptr returns a pointer to an analysis function
analysis_fp get_analysis_ptr();
-

declares a function get_analysis_ptr() that returns a pointer to an analysis function. This statement is equivalent to

-
1
double (*get_analysis_ptr()) (const vector<Student_info> &);
-

From this, we can see that get_analysis_ptr has a parameter list and hence it is a function. In addition, there is an * before it, which indicates that the function returns a pointer. Furthermore, the returned pointer also has a parameter list and hence the returned pointer points to a function that takes parameter const vector & and returns a double type value.

-

Now let’s look at an example

-
1
2
3
4
5
6
7
template <class In, class Pred>
In find_if(In begin, In end, Pred f)
{
while (begin != end && !f(*begin))
++begin;
return begin;
}
-

Suppose we have a predicate function

-
1
2
3
4
bool is_negative(int n)
{
return n < 0;
}
-

Let’s instanitialize the template with a vector named v

-
1
vector<int>::iterator i = find_if(v.begin(), v.end(), is_negative)
-

We use is_negative instead of &is_negative due to that the name of the function turns into a pointer to the function autimatially.

-
1
2
3
4
5

# Arrays
**Array** is part of the core language rather than part of the standard library. An **array** is a kind of container that similar to a **vector**, but has fixed length. When we create an **array** object, we should specify the length of the array. For example, we can create an array that contains three elements
```c++
double coords[3];
-

Alternatively, we can use a const object to denote the size of the array

-
1
2
const size_t NDim = 3;
double coords[NDim];
-

size_t is a fundamental unsigned integer type that can be used to represent the size of an array. It is defined in the header . The reason to use size_t is that array is not a class type and has no member function like size_type. It is worth noting that we use const to ensure Ndim is fixed and known at compilation time.

-

coords is the name of the array, and in fact it is a pointer that points to the first element of the array. Hence, we can assign a value to the first element through

-
1
*coords = 1.5; // set the initial element of coords to 1.5
- -

Pointer arithmetic

Another fact is that a pointer is a random access iterator. Therefore, we can access the mth element (if available) through

-
1
*(coords + m)
-

The pointer that points to one past the last element is coords + NDim. In other words, [coords, coords + NDim) denotes the range of address of the array.

-

Now, if we want to copy all elements of the array into a vector named v, we can use the copy algorithm as shown as follows

-
1
2
vector<double> v;
copy(coords, coords + NDim, back_inserter(v));
-

Alternatively, we can construct v directly as a copy of the elements in coords using

-
1
vector<double> v(coords, coords + NDim);
-

C++ 2011 library provides begin and end functions to get the initial pointer and the off-the-end pointer:

-
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
int *beg = begin(coords);   // points to the initial elements
int *last = end(coords); // points to one past the last element
···

Similar to **difference_type**, **ptrdiff_t** is an signed itegeral type that represents the distance between two **pointers**. **ptrdiff_t** is also defined in the header <cstddef>.

## Indexing
It is known now that **array** supports random access iterators and naturally supports indexing. Therefore, the **n**th emelent (if available) is **coords[n]** and ***coords = coords[0]**.

## Array initialization
Array supports list initialization, for example
```c++
// these two statements have same effect
int a[10] = {1, 2, 3, 4, 5, 6, 7, 8, 9, 0};
int a[] = {1, 2, 3, 4, 5, 6, 7, 8, 9, 0};
-

If we don’t specify the number of elements contained in the array, compiler will infer the number from the number of the supplied the initializers. But if we specify the number exactly, the number of initializers must not exceed the specified size. So, what if we specifies the size but provides initializers less than the specified number?

-
1
int a[10] = {1, 2, 3};
-

Then, the compiler will value initialize the rest elements. In this case, the elements have built-in type int and hence are set to 0.

-

String literals revisited

As we mentioned earlier, String literals are not strings. In fact, a string literal is an array of const char with one more element, i.e. ‘\0’ ,than the number of characters in the literal. ‘\0’ is a null character. Therefore, when we create a string literal, we should specify the size one larger than it should be for the purpose of holding the extral null character. For example, a string literal Hello is created through

-
1
2
3
// two ways to initialize a string literal
const char hello[] = {'H', 'e', 'l', 'l', 'o', '\0'};
const char hello[6] = "Hello";
-

The null character marks the end of the literal. When we want to get the number of characters excluding the null character, we can use the function strlen defined in . The strlen might has following implementation

-
1
2
3
4
5
6
7
8
// Exanple implementation of standard-library function
size_t strlen(const char *p)
{
size_t size = 0;
while (*p++ != '\0')
++size;
return size;
}
-

Now, if we want to copy the string literal Hello into a string type object, we can use

-
1
2
3
4
// three equivalent ways to copy the string literal into a string
string s(hello); // variable hello represents "Hello"
string s("Hello");
string s(hello, hello + strlen(hello)); // treats hello as the begin iterator
- -

Initializing arrays of character pointer

Essentially, a string literal is just a convenient way of writing the address of the initial character of a null-terminated sequence of characters. Now let’s look at an example that shows how to generate an appropriate letter grade according to a numeric grade. The letter grades and numeric grades have following mapping relations:

-
1
2
If the grade is at least  97  94  90  87  84  80  77  74  70  60  0
then the letter grade is A+ A A- B+ B B- C+ C C- D F
-

The program is shown below and let’s analyse it step by step.

-
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
string letter_grade(double grade)
{
// range posts for numberic grades
static const double numbers[] = {97, 94, 90, 87, 84, 80, 77, 74, 70, 60, 0};

// name for the letter grades
static const char* const letters[] = {"A+", "A", "A-", "B+"
"B", "B-", "C+", "C", "C-", "D", "F"};

// compute the number of grades given the size of the array
// and the size of a single element
static const size_t ngrades = sizeof(numbers)/sizeof(*numbers);

// given a numberic grade, find the associated letter grade
for (size_t i = 0; i < ngrades; ++i)
{
if (grade >= numbers[i])
return letters[i];
}
return "?\?\?";
}
-

The function itself takes a double type value (i.e. a numeric grade), and returns a string (i.e. a letter grade). The first step is to construct two objects that hold the numeric grades and the letter grades, respectively. It is simple if we use vector or list or map as we know the numeric grade int type and the letter grade is string type. What if we use array? There is nothing new if we use string type, for example,

-
1
static const string letters[] = {"A+", "A", "A-", "B+", "B", "B-", "C+", "C", "C-", "D", "F"};
-

But if we treat each letter grade as a a sequence of characters, we can store them as the program shows

-
1
static const char* const letters[] = {"A+", "A", "A-", "B+", "B", "B-", "C+", "C", "C-", "D", "F"};
- -

It can be observed that:

-
    -
  1. the static means that the object is initialized only once and exists in the whole process till the program terminates.
  2. -
  3. the letters is an array of const pointers to const char. Each element is a string literal, i.e. an alternative way of writing the address of the initial character. Therefore, each element is in fact can be regarded as a pointer that points to the initial letter of the string literal. For example, letters[0] gives the first letter grade A+ while *(letters[0]) gives the initial letter of the first letter grade as letters[0] is a pointer as well.
  4. -
  5. The first const means that the array elements are constant. The second const means that the address are constant.
  6. -
  7. the next statement introduces sizeof which is a function that returns a size_t type value indicating how much memory an object occupied. Therefore, sizeof(numbers) gives the number of bytes that the object numbers while sizeof(*numbers) yields the number of bytes that each element consumes. The result of the division yields a value that is the number of elements contained in the object (i.e. array numbers).
  8. -
  9. finally, using a for statement a corresponding letter grade according to the input. If there exists such a grade, return it, otherwise, return ???.
  10. -
-

Arguments to main

As mentioned in chapter 0, the main function can take arguments like other functions if it defines parameters. A conventional way is to pass a sequence of stings to main as an argument. For example, when we pass say Hello, world as an argument, the program give outputs

-
1
Hello, world
-

How to achieve this? This is done by giving two parameters:

-
    -
  1. an int type parameter named argc which is a value that denotes the number of pointers that pointed by argv.
  2. -
  3. another one is named as argv which is a pointer to a pointer to char. For example, the pointer letters described above. letters points to the initial element, that is, the pointer to char A as the pointer to a string literal points to the initial char of the sequence. Therefore, we can define this parameter as
  4. -
-
1
char* argv[]
-

or

-
1
char** argv
-

These two expression are equivalent. Now, let’s look at the main with arguments argc and argv:

-
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
int main(int argc, char** argv)
{
// if there are arguments, write them
if(argv > 1)
{
// declare i outside the for because we need it after the loop
int i;

// write all but the last entry and argv[i] is a char*
for (i = 1; i < argc - 1; ++i)
cout << argv[i] << " ";

// write the last entry but not a space
cout << argv[i] << endl;
}
}
-

Assuming we pass Hello, world as an argument, argc will be 3 (why ?) and argv points to the first element, that is, another pointer to “Hello,” that is, character ‘H’. argv[0] can be regarded as the name (i.e. pointer again) of the first string literal, and hence we can access each string literal through argv[i], where i < argc.

-

You might wonder that why argc is 3 and why we use range [1, argv) rather than [0, argv) as we did before? This is because when we pass arguments, the program name will be automatically passed as one element. Specifically, if we pass sequence please give me a number, there will be 5 string literal in total, but argc = 6 because:

-
1
2
3
4
5
6
argv points to pointer argv[0] points to "program name"
argv[1] points to "please"
argv[2] points to "give"
argv[3] points to "me"
argv[4] points to "a"
argv[5] points to "number"
-

This shows why we use the range [1, argv) to access each element.

-

Reading and writing files

By now, we are familar with four iostream class objects cin, cout, clog, and cerr. This part introduces another IO facilitiy ifstream and ofstream class to deal with multiple input and output files. Specifically, the ofstream class object allows us to write data into a file, while iftream allows us to read data from a file. We can use these objects like what we operate on cin and cout such as that they also support << and >> operators as well as getline.

-

When defining an ifstream or oftream object, we can associate the object with a file that we intend to write or read. Naturally we supply the file name which can be a string (C++11) or a string literal. This can be done either on construction or by calling member function open. For example,

-
1
2
3
// two ways to open a file named s
ifstream in(s);
ifstream.open(s);
- -

The below program shows how to copy the contents of a file named in to a file named out.

-
1
2
3
4
5
6
7
8
9
10
int main()
{
ifstream infile("in");
ofstream outfile("out");

string s;
while (getline(infile, s))
outfile << s << endl;
return 0;
}
- -

The first statement creates an ifstream object infile and associates it with a file named in. Similarly, the second statement creates an ofstream object outfile and associates it with a file named out. Both file names are string literals, i.e. pointers to the initial character of null-terminated array. If file in doesn’t exist, there won’t be a file in being created. However, if out doesn’t exist, it will be create to hold the outputs.

-

If we don’t want to use string literal as name, one solution is to store the name in a string and use member function c_str to get the pointer to the array that contains a null-terminated sequence of characters (i.e., a C-string) representing the current value of the string object_ Reference to std::string::c_str. For example, file is a string variable that contains the name of a file to be read, we then associate the file with a ifstream object through

-
1
ifstream infile(file.c_str());
- -

Finally, let’s look at another program that produces a copy of all files whose names are given as arguments to main.

-
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
int main(int argc, char** argv)
{
int fail_count = 0;
// for each file in the input list
for (int i = 1; i < argc; ++i)
{
ifstream in(argv[i]);

// if it exits, write its contents, otherwise generate an error message
if(in)
{
string s;
while(getline(in, s))
cout << s << endl;
}
else
{
cerr << "cannot open file " << argv[i] << endl;
++fail_count;
}
}
return fail_count;
}
-

The program logic is simple: first get a name from the arguments argv, then open a same name file; if the file exists, the contents of it will be written on the output device, but if there is no such file, count and record such case with a variable fail_count; finally, returns fail_count, by then, the value of fail_count represents the number of non-existent files; if fail_count is not 0, it indicates that the program terminates abnormally.

-

It is worth noting that when a ifstream object fails to associate the corresponding file, the state of the input stream is set to fail. Therefore, the if condition if(in) is very useful and helpful for us to check whether our operations on the file are valid.

-

Three kinds of memory management

The first kind is called automatic memory management: the system allocates memory for a local variable when it executes the variable’s definition, and deallocates that memory automatically at the end of the block that contains the definition. Therefore, we should note that once a variable has been dealloated, any pointers to it are invalid. For example,

-
1
2
3
4
5
6
7
// this function deliberately yields an invalid pointer
// it is intended as a negative example-don't do this!
int* invalid_pointer()
{
int x;
return &x; // instant disaster
}
-

This function intends to return the address of local variable x. However, the return statement ends the execution of the block and hence deallocates the memory of x, resulting that &x is invalid. To solve this problem, we can use another kine memory management, i.e. statically allocated.

-
1
2
3
4
5
6
// This function is completely legitimate
int* pointer_to_static()
{
static int x;
return &x;
}
-

When a local object is specified as static, it is created only the first time when its definition is executed and won’t be destroied until meet the end of the program. Therefore, the function returns a valid pointer of object x.

-

The shortcoming of this kind memory management is that each call of such function obtains the same pointer. If we want to get a different pointer each time, we can choose an alternative memory management, the dynamic allocation.

-

If T is an object type, then new T is an expression that

-
    -
  1. allocates an T type object
  2. -
  3. default-initializes the object
  4. -
  5. yields a pointer to this newly allocated object
  6. -
  7. the object exists since it is created till either the end of the program or the execution of delete p, where p is a copy of the pointer returned by the expression.
  8. -
-

For example,

-
1
2
// allocate an unnamed object of type int, and initialize it to 42
int *p = new int(42);
-

We can change the value of the object by manipulating the pointer p

-
1
2
// change the object to 43
++*p;
-

If we want to delete it, we do

-
1
2
// after this execution, the occupied memory is freed and p becomes an invalid pointer
delete p;
- -

Now let’s revisit the problem we met above and write a function that can return different pointer each time

-
1
2
3
4
int *pointer_to_dynamic()
{
return new int(0);
}
-

This function returns a brand new pointer each time. But do not forget to release the memory through delete p.

-

Allocating and deallocating an array

By analogy, we can allocate an array that contains T type values.

-
1
T* p = new T[n];
-

n is a non-negative integral value and new T[n] allocates an array of n objects of type T. There is also a returned pointer that points to the initial element of the array. Each object of the array is default-initialized. This means that if T is a built-in type, then the elements are undefined while if T is a class type, then the elements are initialized by the default constructor defined in that class type.

-

If n equals to 0, then the new return a valid off-the-end pointer as there is no element contained in the array. To deallocate the memory, we use delete[] p. Here is an example

-
1
2
3
T* p = new T[n];
vector<T> v(p, p + n); // copy elements in the array to vector
delete[] p;
- -

The new allocates an array and stay around until that the program terminates or executes the delete[]. Before deallocating the array, the system destroys each element in reverse order. Let’s look at a function

-
1
2
3
4
5
6
7
8
9
10
char* duplicate_share(const char* p)
{
// allocate enough space; remember to add one for the null
size_t length = strlen(p) + 1;
char* result = new char[length];

// copy into our newly allocated space and return pointer to first element
copy(p, p + length, result);
return result;
}
-

This function takes a pointer to char as an argument and returns a point to char. It actually copy all chars from one string literal into a new string literal. Firstly, it allocates memory for the new array of chars, then apply copy algorithm to copy each char in the original array into the newly created array. Noting that strlen gives the number of non-null characters contained in a string literal. However, when we allocate the memory for a new string literal, we need to add one position more for holding the null-character. As for the deallocation of dynamic allocation, we will discuss more in next chapter.

-
-

Test examples

Now I test severl programs analysed above. The first program tests arguments to main and letter_grade function.

-

Test_1.cpp

-
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>		// to get the declaration of cout, endl
#include <string> // to get the declaration of string
#include <cstddef> // to get the declaration of size_t

using std::cout; using std::endl;
using std::string; using std::size_t;

// function declaration
string letter_grade(double);

// main function with non-empty parameter list
int main(int argc, char** argv)
{
// if there are arguments, write them
if(argc > 1)
{
// declare i outside the for because we need it after the loop
int i;

// write all but the last entry and argv[i] is a char*
for (i = 1; i < argc - 1; ++i)
cout << argv[i] << " ";

// write the last entry but not a space
cout << argv[i] << endl;
}

cout << "my letter grade is: " << letter_grade(75) << endl;

return 0;
}

string letter_grade(double grade)
{
// range posts for numberic grades
static const double numbers[] = {97, 94, 90, 87, 84, 80, 77, 74, 70, 60, 0};

// name for the letter grades
static const char* const letters[] = {"A+", "A", "A-", "B+",
"B", "B-", "C+", "C", "C-", "D", "F"};

// compute the number of grades given the size of the array
// and the size of a single element
static const size_t ngrades = sizeof(numbers)/sizeof(*numbers);

// given a numberic grade, find the associated letter grade
for (size_t i = 0; i < ngrades; ++i)
{
if (grade >= numbers[i])
return letters[i];
}
return "?\?\?";
}
- -

Test Results

-
1
2
3
4
5
6
7
8
9
Inputs

command line arguments: please tell me the true
numeric grade: 75

Outputs

please tell me the truth
my letter grade is: C-
- -

The second program produces a copy of all files whose names are given as arguments to main.

-

test_2.cpp

-
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
#include <iostream>		// to get the declaration of cout, endl, cerr
#include <string> // to get the declaration string getline
#include <fstream> // to get the declaration of ifstream

using std::cout; using std::endl;
using std::cerr; using std::string;
using std::getline; using std::ifstream;

int main(int argc, char** argv)
{
int fail_count = 0;
// for each file in the input list
for (int i = 1; i < argc; ++i)
{
ifstream in(argv[i]);

// if it exits, write its contents, otherwise generate an error message
if(in)
{
string s;
while(getline(in, s))
cout << s << endl;
}
else
{
cerr << "cannot open file " << argv[i] << endl;
++fail_count;
}
}
return fail_count;
}
-

I firstly create a file named in and write following sequences into it

-
1
2
3
what are you going to do
to be or not to be
that is a question
- -

Then I set the commond line arguments as: please tell me the truth in
Once I click the run button, the program below gives following results

-
1
2
3
4
5
6
7
8
cannot open file please
cannot open file tell
cannot open file me
cannot open file the
cannot open file truth
what are you going to do
to be or not to be
that is a question
- -

The last program tests that we can use the duplicate_share function to produce a copy of a sequence of characters.

-
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
#include <iostream>	// to get the declaration of cout,
#include <cstring> // to get the declaration of strlen
#include <cstddef> // to get the declaration of size_t
#include <algorithm>// to get the declaration of copy

using std::cout; using std::endl;
using std::strlen; using std::size_t;
using std::copy;

char* duplicate_share(const char* p)
{
// allocate enough space; remember to add one for the null
size_t length = strlen(p) + 1;
char* result = new char[length];

// copy into our newly allocated space and return pointer to first element
copy(p, p + length, result);
return result;
}

int main()
{
// constructe an array
char s[] = "computational";

// get a copy
char* s_copy = duplicate_share(s);

// verify the copy
cout << s_copy << endl;
}
- -

The program works as we expected and gives results

-
1
computational
- -
- -
- - - - - - -
- -
- - - - -
- - - - -
- - -
- - - - -
- - - - - - - - -
-
- - -
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/2018/04/12/Accelerated-C-Solutions-to-Exercises-Chapter-10/index.html b/2018/04/12/Accelerated-C-Solutions-to-Exercises-Chapter-10/index.html deleted file mode 100644 index 1e5ae3d6..00000000 --- a/2018/04/12/Accelerated-C-Solutions-to-Exercises-Chapter-10/index.html +++ /dev/null @@ -1,606 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - Accelerated C++ Solutions to Exercises(Chapter 10) | Liam's Blog - - - - - - - - - - - - -
-
- -
-
- - -
- - -

If history repeats itself, and the unexpected always happens, how incapable must Man be of learning from experience?—George Bernard Shaw.

-
- - -
- - - - -
-
- - -
- - 0% -
- - - - -
-
-
- - -
- - -
- - - -
- - - - - -
-

- Accelerated C++ Solutions to Exercises(Chapter 10) -

- - -
- - - - -
- - -

Exercise 10-0

Compile, execute, and test the programs in this chapter.

-

Solution & Results

Please click here Managing memory and low-level data structures for codes and analysis.

-
-

Exercise 10-1

Rewrite the student-grading program from §9.6/166 to generate letter grades.

-

Solution & Results

Since the letter grades function computes a letter grade based on a numerical grade, I would like to add it as a non-member function of the Student_info class to avoid repetitively computing the final grade. When need calculate the letter grades, we simply call the function as follows:

-
1
2
double final_grade = it->grade(); // it is a pointer to the Student_info object
cout << letter_grade(final_grade); // print the letter grade
-

or

-
1
cout << letter_grade(it->grade());
- -

All files are presented at below including mainfunction.cpp, Student_info.h, Student_info.cpp, grade.h, grade.cpp.

-

mainfunction.cpp

-
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
// Accelerated C++ Solutions Exercises 10-1
#include <algorithm> // to get the declaration of max, sort
#include <iomanip> // to get the declaration of setprecision
#include <iostream> // to get the declaration of streamsize
#include <stdexcept> // to get the declatation of domain_error
#include <string> // to get the declaration of string
#include <vector> // to get the declaration of vector
#include "Student_info.h" // to get the declaration of Student_info

using std::cin; using std::setprecision;
using std::cout; using std::sort;
using std::endl; using std::streamsize;
using std::domain_error; using std::string;
using std::max; using std::vector;
using std::fixed;

int main()
{
vector<Student_info> students;
Student_info record;
string::size_type maxlen = 0;

// read and store all the records, and find the length of the longest name
while(record.read(cin))
{
maxlen = max(maxlen, record.name().size());
students.push_back(record);
}

// alphabetize the records
sort(students.begin(), students.end(), compare);

// write each line of outpurs
for (vector<Student_info>::const_iterator it = students.begin();
it != students.end(); ++it)
{
// write the name, blanks
cout << (*it).name() << string(maxlen + 1 - (*it).name().size(), ' ');

// compute and write the final grade
try{
double final_grade = (*it).grade();
streamsize prec = cout.precision();
cout << fixed << setprecision(1) << final_grade << setprecision(prec);
cout << '\t' << letter_grade(final_grade); // new added
} catch(domain_error e){
cout << e.what();
}
cout << endl;
}
return 0;
}
- -

Student_info.h

-
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
#ifndef GUARD_STUDENT_INFO
#define GUARD_STUDENT_INFO

#include <string>
#include <iostream>
#include <vector>

class Student_info
{
public:
Student_info (); // default constructor
Student_info (std::istream &); // constructor with argument
std::string name() const { return n; } // inline member function return name
bool valid() const { return !homework.empty(); } // inline member function check state
std::istream & read(std::istream &); // member function read in data
double grade() const; // member function calculate final grade


private:
std::string n;
double midterm, final;
std::vector<double> homework;
};

std::istream & read_hw(std::istream &, std::vector<double> &); // nonmember function read data into a vector
bool compare(const Student_info &, const Student_info &); // nonmember function compare two string
std::string letter_grade(double); // new added: nonmember function gives a letter grade

#endif
- -

Student_info.cpp

-
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
65
66
67
68
69
70
71
72
73
74
75
76
77
#include "Student_info.h"
#include "grade.h"
#include <string>

using std::vector;
using std::istream;
using std::string;

// construct an empty Student_info object
Student_info::Student_info (): midterm(0), final(0) { }

// construct one by reading from input stream
Student_info::Student_info (std::istream & in) { read(in); }

// member function read data from input stream
std::istream & Student_info::read(std::istream &in)
{
// reads and store the student's name, midterm and final exam grades
in >> n >> midterm >> final;

// reads and store all homework grades
read_hw(in, homework);
return in;
}

// member function grade
double Student_info::grade() const
{
return ::grade(midterm, final, homework);
}

// nonmember function compare
bool compare(const Student_info &x, const Student_info &y)
{
return x.name() < y.name();
}

// nonmember function read_hw
istream & read_hw(istream &in, vector<double> &hw)
{
if (in){
//get rid of previous contents
hw.clear();

// read homework grades
double x;
while(in >> x)
hw.push_back(x);

// clear the stream so that input will work for the next student
in.clear();
}
return in;
}

// new added: non-member function to calculate the letter grade
string letter_grade(double grade)
{
// range posts for numberic grades
static const double numbers[] = {97, 94, 90, 87, 84, 80, 77, 74, 70, 60, 0};

// name for the letter grades
static const char* const letters[] = {"A+", "A", "A-", "B+",
"B", "B-", "C+", "C", "C-", "D", "F"};

// compute the number of grades given the size of the array
// and the size of a single element
static const std::size_t ngrades = sizeof(numbers)/sizeof(*numbers);

// given a numberic grade, find the associated letter grade
for (std::size_t i = 0; i < ngrades; ++i)
{
if (grade >= numbers[i])
return letters[i];
}
return "?\?\?";
}
- -

grade.h

-
1
2
3
4
5
6
7
8
9
#ifndef GUARD_GRADE_H
#define GUARD_GRADE_H

#include<vector>

double grade(double, double, const std::vector<double> &);
double grade(double, double, double);
double median(std::vector<double>);
#endif
- -

grade.cpp

-
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 <algorithm>
#include <stdexcept>
#include <vector>
#include "grade.h"

using std::domain_error; using std::istream;
using std::vector; using std::sort;

// grade function 1
double grade(double midterm, double final, const vector<double> &hw)
{
if (hw.empty())
throw domain_error("student has done no homework");
return grade(midterm, final, median(hw));
}

// grade function 2
double grade(double midterm, double final, double homework)
{
return 0.2*midterm + 0.4*final + 0.4*homework;
}

// compute the median of vector<double>
double median(vector<double> vec)
{
// check whether the vec is empty
if (vec.begin() == vec.end())
throw domain_error("median of an empty vector");

// sort the grades
sort(vec.begin(), vec.end());

// compute the median homework grade
vector<double>::difference_type size = vec.end() - vec.begin();
vector<double>::const_iterator mid = vec.begin() + size/2;
return size % 2 == 0 ? (*mid + *(mid - 1))/2 : *mid;
}
- -

Test

-
1
2
3
4
5
6
7
8
9
10
11
12
13
Inputs:

Robin 90 87 79 88 81 73 45
Brendan 70 69 88 100 91 75 66
Arsenii 99 87 89 88 74 90 70
Liam 83 66 100 76 87 91 78

Outputs:

Arsenii 89.8 B+
Brendan 76.8 C
Liam 77.8 C+
Robin 84.4 B
- -
-

Exercise 10-2, 10-3

10-2: Rewrite the median function from §8.1.1/140 so that we can call it with either a vector or a built-in array. The function should allow containers of any arithmetic type.

-

10-3: Write a test program to verify that the median function operates correctly. Ensure that calling median does not change the order of the elements in the container.

-

Solution & Results

There are two requirements: first is that, the median function can calculate the median value by taking either a vector or built-in array; second is that, the container can be container of any arithmetic type.

-

Apparently, our median function should be a function template. The first condition implies that the parameters should be two pointers that denote the range of inputs. Unlike the standard vector, built-in array doesn’t provide member functions like begin() and end(). We cannot simply pass the name of the built-in array to the median function as the name of the array is mere the pointer to the initial element. Alternatively, we can denote its range with [arr, arr+n), where arr is the name of the array and n is the size of the array. The first type parameter of our tempalte represents the type of supplied pointers and will be inferred from the supplied pointers in the process of instantiation.

-

The second condition implies that the type of the elements contained in the container should be supplied as we cannot infer the value type from pointers. Therefore, another type parameter of the function template represents the value type of the container and will be infered from the third function parameter, which is defined as any element of the container. As the following declaration shows, Pointer and T represents the pointer type and value type. The function defines three parameters, first two of which denotes the range of inputs while the third is a const reference to the first element contained in the container.

-
1
2
template<class Pointer, class T>
T median(Pointer begin, Pointer end, const T& initialElement);
- -

Once we have the pointers, we can implement the algorithm as with the previous version. But to avoid changing the original sequence, we’d better construct a new vector to hold the input sequence. The code below gives the full program. I test it by calling the function template with a standard vector and a built-in array that contains the same elements as the vector. As expected, they yield same median value. If changing the value type to int, the function template works as well.

-
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
#include <vector>	// std::vector
#include <iostream> // std::cout, std::endl
#include <stdexcept> // std::domain_error
#include <algorithm> // copy
#include <cstddef> // size_t

using std::vector; using std::size_t;
using std::cout; using std::copy;
using std::domain_error; using std::endl;

template<class Pointer, class T>
T median(Pointer begin, Pointer end, const T& initialElement)
{
if(begin == end)
throw domain_error("median of an empty container");
vector<T> v(begin, end);
typename vector<T>::difference_type size = v.end() - v.begin();
sort(v.begin(), v.end());
typename vector<T>::const_iterator mid = v.begin() + size/2;
return size % 2 == 0 ? (*mid + *(mid-1)) / 2 : *mid;
}

int main()
{
vector<double> vec{53, 56, 23, 78, 90, 89, 34, 12, 41, 48};
size_t n = vec.size();
double arr[n];
copy(vec.begin(), vec.end(), arr);
cout << median(vec.begin(), vec.end(), vec[0]) << endl;
cout << median(arr, arr + n, arr[0]) << endl;
return 0;
}
- -

Test Results

-
1
2
50.5
50.5
- - -
-

Exercise 10-4, 10-5, 10-6

10-4: Write a class that implements a list that holds strings.

-

10-5: Write a bidirectional iterator for your String_list class.

-

10-6: Test the class by rewriting the split function to put its output into a String_list.

-

Solution & Results

define the String_list class

The standard list provides all the possible operations and what we need to do is merely to encapsulate our data, a list, by hiding the data and instead providing an appropriate interface. I intend to implement a subset of the list class. Specifically, we can use the String_list class as follows:

-

Interface

-
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
// construct an empty container
String_list s;

// construct a container with a size and a value
String_list s1(10, "Hello");

// return iterator to begining and end
s1.begin();
s1.end();

// construct a container with a range
String_list s2(s1.begin(), s1.end());

// copy construct a container
String_list s3(s2);

// assignment
s = s1;

// check the status of the container
s1.empty();

// check the size
s1.size();

// clear
s1.chear();

// add one element to end
s1.push_back("Hello");
-

All these operations are obvious and we can directly work with list in our implementations. You can add more operations to this String_list class as long as the operations are supported by the standard list class. Let’s see how our String_list class is implemented:

-

String_list.h

-
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
#ifndef STRING_LIST_H_
#define STRING_LIST_H_

#include <list>
#include <string>
#include <cstddef>

class String_list{
private:
typedef std::list<std::string> container;
container SL;

public:
typedef container::iterator iterator;
typedef container::const_iterator const_iterator;
typedef container::value_type value_type;
typedef container::size_type size_type;

// default constructor: construct an empty String_list
String_list() = default;

// constructor that takes a size and a value
explicit String_list(size_type n, const value_type& val = value_type()): SL(n, val) {}
String_list(const_iterator beg, const_iterator end): SL(beg, end) {}

// bidirectional iterator and const bidirectional iterator
iterator begin() { return SL.begin(); }
const_iterator begin() const { return SL.cbegin(); }
iterator end() { return SL.end(); }
const_iterator end() const {return SL.cend(); }

// empty
bool empty() const { return SL.empty(); }

// size
size_type size() const { return SL.size(); }

// clear
void clear() { SL.clear(); }

// push_back
void push_back(const value_type& v) { SL.push_back(v); }
};

#endif /* STRING_LIST_H_ */
-

It is worth noting that the class defines three constructors but ignores the copy constructor, assignment operator and the destructor. As a result, the compiler will synthesize copy constructor, assignment operator and destructor for us. The synthesized operations depend on the definition of the data member. For example, the compiler will synthesize the copy constructor by calling the default copy constructor of the list class. Therefore, we do not need to worry about that. The synthesized operations also work on the default constructor. I explicitly define the default constructor because that we need other constructors. If we didn’t explicitly define the default constructor, the compiler won’t synthesize for us due to the existence of other constructors.

-

Another point is that the class implicitly supports the conversion from a string literal to a string type. This property is inherent in the standard string class and is further explained in chapter 12.

-

The last point is that the iterator returned by begin() and end() are all bidirectional iterator. This has been defined in the standard list class. We can traverse forward or backward the container and access or rewrite the elements using the returned iterators.

-

rewrite the split function

The original split function copies its output into the output stream directly. We can replace the outstream object with our String_list. Rather than passing an ostream_iterator as the argument to the split function, we can pass a reference to the container as the argument.
Therefore, the declaration of the revisied split function is:

-
1
2
template <class container>
void split(const std::string &, container&);
-

Then we can store each seperated word into the container by calling its push_back function. Please see the code below:

-

Split.h

-
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
#ifndef GUARD_SPLIT_H
#define GUARD_SPLIT_H

#include <string>
#include <algorithm>
#include "String_list.h"

// true if the argument is whitespace, false otherwise
bool space(char c)
{
return isspace(c);
}

// false is the argument is whitespace, true otherwise
bool not_space(char c)
{
return !isspace(c);
}

// template declaration and definition
template <class container>
void split(const std::string &str, container& c)
{
typedef std::string::const_iterator iter;

iter i = str.begin();
while(i != str.end())
{
// ignore leading spaces
i = std::find_if(i, str.end(), not_space);

// find end of next word
iter j = std::find_if(i, str.end(), space);

// copy the characters in [i,j) and store into container
if(i != str.end())
c.push_back(std::string(i, j));
i = j;
}
}
#endif /* GUARD_SPLIT_H */
- -

Test

I wrote a test program to test each members of our String_list class and the revised split function.

-

main.cpp

-
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
65
66
67
68
69
70
#include <iostream>		// std::cin, endl, cout
#include <string> // std::string
#include "String_list.h" // String_list
#include "split.h" // split

using std::cout; using std::cin;
using std::endl; using std::string;

int main(){
// construct an empty container
String_list words;

// test the empty function
if(words.empty())
cout << "This is an empty container\n"
"Please enter a sentence: ";

// test the split function, and the push_back member of the String_list
string line;
while (getline(cin, line))
{
split(line, words);
}

cout << "There are " << words.size() << " word(s) in total: ";
for(auto i: words) {
cout << i << " ";
}

// test bidirectional iterators
cout << "\nprint all words in reverse order: ";
String_list::iterator rbeg = --words.end();
String_list::iterator rend = --words.begin();
while(rbeg != rend)
{
cout << *rbeg-- << " ";
}

// test the constructor that takes two input iterators
String_list words_copy(words.begin(), words.end());
if(!words_copy.empty()){
cout << "\nThe size of the container is: " << words_copy.size();
cout << "\nThe elements contained in the String_list are: ";
for(auto i: words_copy)
cout << i << " ";
}

// test the clear function
words.clear();
if(words.empty())
cout << "\nThe container now is empty again";

// test the constructor that takes a size and a value
String_list words_new(10, "Hello");

// test the default copy constructor
String_list words_new_copy(words_new);
cout << "\nThe container contains: ";
for(auto i: words_new_copy) {
cout << i << " ";
}

// test the default assignment operator
words = words_new;
cout << "\nNow the container words contains: ";
for(auto i: words) {
cout << i << " ";
}
return 0;
}
- -

The program above is pretty straightforward and gives following results as expected:

-
1
2
3
4
5
6
7
8
9
This is an empty container
Please enter a sentence: Stack is one of the rudimentary data structures that use pointers
There are 11 word(s) in total: Stack is one of the rudimentary data structures that use pointers
print all words in reverse order: pointers use that structures data rudimentary the of one is Stack
The size of the container is: 11
The elements contained in the String_list are: Stack is one of the rudimentary data structures that use pointers
The container now is empty again
The container contains: Hello Hello Hello Hello Hello Hello Hello
Now the container words contains: Hello Hello Hello Hello Hello Hello Hello
- -
-

Reference

Koenig, A. and Moo, B.E., 2000. Accelerated C++.

-

Lippman, S.B., Lajoie, J. and Moo, B.E., 2013. C++ Primer, 5th edt.

- -
- - - - - - -
- -
- - - - -
- - - - -
- - -
- - - - -
- - - - - - - - -
-
- - -
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/2018/04/14/C-Defining-abstract-data-types/index.html b/2018/04/14/C-Defining-abstract-data-types/index.html deleted file mode 100644 index d8453379..00000000 --- a/2018/04/14/C-Defining-abstract-data-types/index.html +++ /dev/null @@ -1,687 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - C++ - Defining abstract data types (Part 1) | Liam's Blog - - - - - - - - - - - - -
-
- -
-
- - -
- - -

If history repeats itself, and the unexpected always happens, how incapable must Man be of learning from experience?—George Bernard Shaw.

-
- - -
- - - - -
-
- - -
- - 0% -
- - - - -
-
-
- - -
- - -
- - - -
- - - - - -
-

- C++ - Defining abstract data types (Part 1) -

- - -
- - - - -
- - -

The vector class

This chapter mainly teaches us about how to define our own “vec” class follwing the standard library vector class template. Specifically, our vec class will provide an interface that allows following operations:

-
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
// construct a vector of T type
vector<T> v; // empty vector
vector<T> v(100); // vector with 100 elements

// obtain the names of the types used by the vector
vector<T>::const_iterator b, e;
vector<T>::size_type i = 0;

// use size and the index operator to look at each element in the vector
for (i = 0; i != v.size(); ++i)
cout << v[i].name(); // if T has a member name

// return iterators positioned on the first and one past the last element
b = v.begin();
e = v.end();
-

Implementing the Vec class

The standard library vector is a class template. Similarly, we define a class template to represent our vector to hold various types. We are familar with how to define a function template, now let’s see how to define a class template.

-
1
2
3
4
5
6
template <class T> class Vec{
public:
// interface
private:
// implementation
};
-

Similar to the definition of a function template, a class template begins with the keyword template follwed by the template parameters list. In this case, there is one type parameter named T. Then we define the class as we did before, assuming that there will be public and private parts to write our interface and implementation respectively.

-

Now we consider the data members for our vector class. Vector is a container that can hold multiple elements. A natural solution goes to a dynamically allocated array. So what information we need for the implementation of our Vec class? The functions begin, end and size imply that we might need to store the address of the initial element, one past the address of the last element and the number of elements. But once we know the address of the first element and one past the last element, we could compute the size easily. Let’s add two data members:

-
1
2
3
4
5
6
7
template <class T> class Vec{
public:
// interface
private:
T* data; // first element in the Vec
T* limit; // one past the last element in the Vec
};
- -

Constructors

From the interface we intend to provide, we know we need to define at least two constructors,

-
1
2
3
// construct a vector of T type
Vec<T> v; // using default constructor
Vec<T> v(100); // using constructor that takes a size
-

The default constructor leads to an empty Vec and hence there is no need to allocate space to hold the elements. Two data members can be initialized to null pointers. For the constructor that takes a size, we should allocate certain amount of storage for holding the elements. Two data members will be initialized to the corresponding addresses of that space. Each element will be initialized to a value given by the default constructor of Type T. There is also a case that users provide the initial values for the elements, for example

-
1
Vec<T> v(100, 1);   // using constructor that takes a size and an initial value
-

If so, we would initialize each element with the provided value. The constructor that takes a size and an initial value can be regarded as the special case of the constructor that only takes a size. The code below shows the definition of the constructors:

-
1
2
3
4
5
6
7
8
9
10
template <class T> class Vec{
public:
Vec() { create() };
explicit Vec(size_type n, const T& val = T()) { create(n, val); }
// remaining interface

private:
T* data
T* limit;
};
- -

As we haven’t talk about how to dynamically allocate space for our object, the details of implementations of each constructor will be discussed later. What we need to know here is that the constructors call another function create to initialize our data members and the elements. For the default constructor, create() initializes all data members to null pointers. For the second constructor, create(n, val) allocates enough space, and initializes all data members as well as each element with size n and value val. The second constructor takes two arguments, one is the size n and another is the value that to use in initializing the elements.

-

If there is no user-supplied value, val is assigned with an default value given by the default constructor of type T. One may speculate that if T is built-in type and the vector is allocated at local scope, then the elements are uninitialized as default-initializing an built-in object gives it an undefined value. However, we also know that when we create a standard vector with size only, the compiler initializes each element to 0. So, where there might be problems? Let’s do a simple experiment first.

-
1
2
3
4
5
6
7
8
9
10
11
12
13
#include <iostream>
using std::cout; using std::endl;

int main()
{
int x;
int y = int();

double m;
double n = double();

cout << x << '\n' << y << '\n' << m << '\n' << n << endl;
};
- -

Outputs

-
1
2
3
4
1954310794
0
-2.2854e+251
0
- -

From above example, we observe that a built-in type local variable is undefined in the case of default initialization. In contrast, int() and double() doesn’t default initialize the corresponding objects, but performs value-initialization. This means that T() only invokes default constructor if it is user-declared and otherwise it performs value-initialization. If T is built-in type, objects are zero initialized.

-

It also has been observed that we use a keyword explicit as the begining of the definition of the second constructor. This keyword only makes sence when the constructor takes a single argument, that is, the size. It indicates that the compiler will use the constructor only in the case that the user expressly invokes the constructor.

-
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
Vec<int> v(100); // ok, explicitly construct the Vec from an int
Vec<int> v = 100; // error: implicitly comstruct the Vec and copy
```

More about the **explicit** will be discussed in chapter 12.

## Type definitions
This section defines types for our **Vec** class including **const_iterator, iterator, size_type** and **value_type**. It is known that our **Vec** class is build upon the dynamic allocated array. In addition, pointers supports the random-access-iterator operations. Therefore, we can define the types **iterator** and **const_iterator** based on pointers. For **size_type**, we can define based on **size_t**. Apparently, The **value_type** is **T**. Now let's see the code

```c++
template <class T> class Vec{
public:
typedef T* iterator;
typedef const T* const_iterator;
typedef size_t size_type;
typedef T value_type;

Vec() { create(); }
explicit Vec(size_type n, const T& val = T()) { create(n, val); }
// remaining interface

private:
iterator data;
iterator limit;
};
-

Index and size

1
2
for (i = 0; i != v.size(); ++i)
cout << v[i].name(); // if T has a member name
-

The size function returns a value that represents the number of elements in a Vec.

-

The indexing operation is supported through the subscript operator [] and hence we should define an overloaded operator as we define other function: it has a name, takes arguments, and specifies a return type.

-

The name of such operator is obtained by appending the symbol [] to the word operator, that is, operator[].

-

If the operator is a function that is not a member function, then the function has as many arguments as the operator has operands. The first argument is bound to the left bound and the second is bound to the right operand. If the operator is defined as a member function, its left operand is implicitly bound to the object on which the operator invoked. In this case, the subscript operator is typically a member function. We can call it with v[i], meaning that v is the object on which it operates and i is an argument that should has type Vec::size_type.

-

As for the return type, the operator function ought to return a reference to the element in the Vec.

-
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
template <class T> class Vec{
public:
typedef T* iterator;
typedef const T* const_iterator;
typedef size_t size_type;
typedef T value_type;

Vec() { create(); }
explicit Vec(size_type n, const T& val = T()) { create(n, val); }

// operations: size and index
size_type size() const { return limit - data; }

T& operator[](size_type i) { return data[i]; }
const T& operator[](size_type i) const { return data[i] }

private:
iterator data;
iterator limit;
};
-

There are sevral key points here:

-
    -
  1. the result of (limit - data) has type ptrdiff_T, which is converted to size_type.
  2. -
  3. taking the size of a Vec doesn’t change the Vec and hence we define it as a const member.
  4. -
  5. we define two version of the operator function: one for const Vec objects and the other for nonconst Vec. It seems impossible to overload the operator function as both version have same parameter list. However, as mentioned above, the object itself is also an implicit argument to the function. Therefore, one function takes the const Vec object as an argument while the other one takes the nonconst Vec object as an argument.
  6. -
-

Operations that return iterators

Next is to define member functions begin() and end(). Similar to the operator function, we need to define two versions for both functions, one version returns const_iterator so that users cannot modify the Vec by operating on the iterator; another one returns an iterator that is not restricted by qualifier const, so that users can write elements into the Vec through the iterator if they want to. The improved code is shown below.

-
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
template <class T> class Vec{
public:
typedef T* iterator;
typedef const T* const_iterator;
typedef size_t size_type;
typedef T value_type;

Vec() { create(); }
explicit Vec(size_type n, const T& val = T()) { create(n, val); }

// operations: size and index
size_type size() const { return limit - data; }

T& operator[](size_type i) { return data[i]; }
const T& operator[](size_type i) const { return data[i] }

// function to return iterators
iterator begin() { return data; }
const_iterator begin() const { return data; }

iterator end() { return limit; }
const_iterator end() const { return limit; }

private:
iterator data;
iterator limit;
};
```

---
# Copy control
In chapter 9, we have learned how to initialize a class object when it is created. But we haven't talked about what happens when a class object is copied, assigned and destroyed. When we define the **Student_info** class, we didn't define these operations as well. We can presume that the compiler will synthesize definitions for us. Now this section focus on how can we define these operations and how the synthesized operations exactly work.

## Copy constructor
Two ways to implicitly copy a class object: one is that passing an object by value to a function; the other way is that returning an object by value. For example
```c++
vector<int> v;
double d;
d = median(v); // copy v into the parameter in median

string line;
vector<string> words = split(line); // copy the return from split into words
-

Sometimes we also explicitly copy an object, for example using it to initialize another object.

-
1
2
vector<Student_info> vs;
vector<Student_info> vs_copy = vs; // copy vs into vs_copy
- -

Both above copy behaviors are controlled by a special constructor called the copy constructor.

-

copy constructor is also a member function that has the same name as the name of class. It takes a single argument that has the same type as the class itself. In addition, the parameter is a const reference to the object to pass due to that the copy constructor should not change the object being copied from. Therefore, we can declare the copy constructor as shown below

-
1
2
3
4
5
template <class T> class Vec {
public:
Vec (const Vec& v); // copy constructor
// as before
};
-

When we copy a class object, we’ll need to allocate new space and then copy the contents from the source into the newly allocated storage. This is because we do not intend to change the object being copied from. For example, if we simple copy two data members, we may change the value of the elements due to the fact that the copied pointers still points to the elements contained in the object being copied from. As with the constructors, we will ask the overloaded create function to manage the memory and the details of the copy operations.

-
1
2
3
4
5
template <class T> class Vec {
public:
Vec (const Vec& v) { create(v.begin(), v.end()); };
// as before
};
- -

Assignment

Like the subscript operator, the assignment operator = needs to be defined for providing us the assignment operations. The name of the assignment operator function is operator=. The argument taken by such operator function is as same as the argument taken by copy constructor above. What about the return type? We return a reference to the left operand.

-
1
2
3
4
5
template <class T> class Vec{
public:
Vec& operator= (const Vec&);
// as before
};
- -

It is worth noting the difference between assignment and the copy constructor. assignment always involves obliterating an existing value of the left-hand side, and then replacing it with a new value, i.e. the right-side hand. What they have in common is that both of them need to assign each of the data values. As mentioned above, we cannot assign the value of pointers to the left-hand side because that doing so would bring potential change for the right-hand side.

-

There might be another problem when using the assignment operator, that is how to handle self-assignment. For example:

-
1
2
vector<int> x(100, 10);
x = x;
-

The assignment operator function will firstly obliterate the value of left-hand side then assign the value of right-hand side to the left-hand side. However, once we destroy the elements and free the space, we cannot create a new object that has the same value as the right-hand side due to both sides operands refer to the same space. To avoid this case, we add a if statement before implemeting the assignment. The code below gives the implementation of the assignment operator function:

-
1
2
3
4
5
6
7
8
9
10
11
12
13
14
template <class T>
Vec<T>& Vec<T>::operator= (const Vec& rhs)
{
// check for self-assignment
if(&rhs != this)
{
// free the array in the left-hand side
uncreate();

// copy elements from the right-hand to the left-hand side
create(rhs.begin(), rhs.end());
}
return *this;
}
- -

The code above introduces several new ideas:

-

First, the operator= is defined as a function template and the type parameter infers from the type parameter of the class template Vec.

-

Second, the return type as well as the function name are defined explicitly due to that this member function is defined outside the class. The declaration uses Vec& rather than Vec& is due to that the type parameter is implicit when we are within the scope of the template. This also explains why we use the function name Vec::operator=. Once we specifies that the function is a member of class Vec, we can omit the type parameter when defines its parameter const Vec& rhs.

-

Third, the if condition uses a keyword this to test whether the assigment happens between two same objects. this is a pointer that points to the object of which operator= is a member. It is valid only inside a member function. Hence, the condition means that if the address of the object (left-hand side) is as same as the address (denoted by &rhs) of the right-hand object, the assignment behavior won’t be executed.

-

Forth, if the leff-hand operand and the right-hand operand are not the same object, we destroy the elements and free the space first through uncreate() and then allocate new space and copy values from rhs like what the copy constructor does.

-

Finally, it is necessary to explain why we intend to return a reference to the left-side object. Why not return void directly? Why don’t we return a value? Move to see more discussion.

-

One reason is that to keep consistent with the default setting of the C++ compiler in regarding to the built-assignment operator. Another reason is that setting the return type to void doesn’t allow continues assignment. For example,

-
1
2
3
vector<int> x(100, 10);
vector<int> y, z;
y = z = x; // continues assignment
- -

Apparently, we don’t have to return a reference, instead we can return a value. Let’s take an example,

-
1
2
3
Vec<int> x(100, 10);
vector<int> y;
y = x; // calls assignment operator once, calls copy constructor once, calls destructor once
-

It can be presumed that returning an object involves calling three functions: first, assignment operator function is called and a temporary object is created, then, the return statement calls copy constructor to create a new object, finally, the destructor is called to destroy the temporary value and free the space. We’ll introduce the destructor later and will pose an experiment to verify these expectations.

-

Assignment is not initialization

Now we can summarize the difference between initialization and assignment. It can be observed that the operator = has different effect in various contexts. The default setting of = invokes copy constructor and then creating a new object,which is another form of initialization. The operator= described above invokes assignment that always obliterates the privious value first.

-

Initialization happens

-
    -
  1. In variable declaration
    1
    string y;  // default initialization
  2. -
  3. For function parameters on entry to a function
    1
    2
    vector<int> v;
    median(v); // the parameter is copy-initialized
  4. -
  5. For the return value of a function on return from the function
    1
    2
    string line;
    vector<string> words = split(line); // the return value is copy-initialized, the variable is then copy-initialized
  6. -
  7. In constructor initializers
    1
    2
    string url_ch = "@#$%^&**((";   // copy initialization
    string spaces(url_ch.size(), ' '); // direct initialization
    - -
  8. -
-

Let’s see another example

-
1
2
3
4
vector<string> split(const string&); // function declaration
vector<string> v; // default initialization

v = split(line); // on entry, initialization of split's parameter from line; on exit, both initialization of the return value and assignment to v
- -

The split function returns an object of type vector. As analysed above, it involves calling both copy constructor (at the call site) and the assignment operator function.

-

Destructor

It is known that when we allocate a space with new, we should destroy the values and free the space with delete. Therefore, it is necessary to define a member function to do the same job. In general, the destructor will be called automatically when:

-
    -
  1. a local variable go out of scope.
  2. -
  3. members of an object are destryoed when the object of which they are a part is destroyed.
  4. -
  5. elements in a container are destoryed when the container is destroyed.
  6. -
  7. the delete operator applied to an object.
  8. -
  9. temporary objects are destroyed.
  10. -
-

Taking an example,

-
1
2
3
4
5
vector<string> split(const string& str){
vector<string> ret;
// split str into words and store in ret
return ret;
}
-

The variable ret is destroyed when the implementation encounters the return statement because it goes out of the scope. Now let’s see how to define a destructor:

-
1
2
3
4
5
template <class T> class Vec{
public:
~Vec() { uncreate() };
// as before
}
-

The name of the destructor is as same as the name of the class itself, but prefixed by a tilde(~). There is no arguments taken by the destructor. To destroy the object and free the space, the destructor calls the uncreate() function, which is similar to the behavior of the assignment operator in obliterating the previous value.

-

Default operations

What happens if we do not explicitly define a copy constructor, assignment operator, or destructor? In such case, the compiler will synthesizes default versions of the unspecified operation. Some general rules(koening and Moo 2000):

-

1. the default version are defined to operate recursively-copying, assigning or destroying each data element according to the appropriate rules for the type of that data element.
2. Members that are of class type are copied, assigned, or destoryed by calling the constructor, assignment operator, and destructor for the data element.
3. Members that are of built-in type are copied and assigned by copying or assigning their value. The destructor for built-in types has no work to do-even if the type is a pointer. Destoring a pointer through the default constructor doesn’t free the space at which the pointer points, resulting a memory leak as the occupied space is impossible to free.

-

Recalling the Student_info class defined in chapter 9:

-
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
class Student_info
{
public:
Student_info (); // default constructor
Student_info (std::istream &); // constructor with argument
std::string name() const { return n; } // inline member function return name
bool valid() const { return !homework.empty(); } // inline member function check state
std::istream & read(std::istream &); // member function read in data
double grade() const; // member function calculate final grade

private:
std::string n;
double midterm, final;
std::vector<double> homework;
};
-

If we copy an object of Student_info, the synthesized copy constructor copies four data members. It invokes the string, vector copy constructors to copy the member name and homeworks respectively. It copies the two double values, midterm, final, directly. Similar procedures happen when we do assignment.

-

Noting that if a class defines any constructor explicitly, either a constructor or a copy constructor, the compiler will not synthesize a default constructor for that class. It is wise to provide a default constructor for the data type that to be used as a data member of a class that relies on the synthsized default constructor. We explicitly provide the default constructor in above class Student_info.

-

If a class needs a destructor, it almost surely needs a copy constructor as well as assignment operator. To control every copy of object of class T, we should define:

-
1
2
3
4
T::T(); // one or more constructors, perhaps with arguments
T::~T(); // the destructor
T::T(const T&); // the copy constructor
T::operator= (const T&); // the assignment operator
- -

Dynamic Vecs

This section focus on designing a dynamic Vec class through providing the push_back function which we are familiar with when using the standard vector. Theoretically, the push_back function can allocate new space to hold one more element and then we copy all elements into the new space while constructing a new last element from the argument to push_back. However, doing so would be inefficient when we call the push_back many times. One strategy is to allocate more storage than we need when necessary, that is, when we exhaust the preallocate storage. Specifically, each time the push_back allocate new space, it allocate twice as much as the current space.

-

For example, if we create a Vec with 100 elements, then call the push_back function for the first time, it will allocate a new space that can hold 200 elements. It then copies the original 100 elements into the new space with constructing the last element from the argument. There are still more space left for holding 99 elements more and hence the function do not need to allocate more space in next 99 calls. Moreover, the extral space keep uninitialized.

-

What we need to track is the address of the first element, the one past of the last constructed element, and the end of the new allocate storage(i.e. one past the available element). We’ll denote these address with three pointers, data, avail, limit respectively. The range [data, avail) contains all elements while the range [avail, limit) is the uninitialized storage. Now let’s write the push_back function:

-
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
template <class T> class Vec{
public:
void push_back(const T& val){
if(avail == limit) // get space if needed
grow();
unchecked_append(val); // append the new element
}

private:
iterator data; // as before, the pointer to the first element in the Vec
iterator avail; // pointer to one past the last constructed element
iterator limit; // now points to one past the available element

// rest of the class interface and implementation as before
};
-

grow() will double the space for us. unchecked_append(val) constructs the last element from the argument to push_back function. Correspondingly, we refresh the data members.

-

Flexible memory management

We have basically completed the design of our Vec class template. However, we haven’t talked about the real implementation, that is, how exactly allocate new space. As memtioned in chapter 10, we can dynamically manage memory through built-in operators new and delete (or new[] and delete[]). However, there are several shortcomings if we use such operators to manage memory for our Vec class.

-
    -
  1. if we use new[], it always initialize every element of a T array by using T::T(). If we want to initialize ourselves, we would have to initialize each element twice.
  2. -
  3. if push_back allocates new space, we want to keep the range [avail, limit) uninitialized. However, if we use new[], we cannot control this anymore.
  4. -
-

The standard header provides a class named allocator, that allocates a block of uninitialized memory that intended to contain objects of type T and returns a pointer to the initial element of that memory. In addition, allocator also defines members including functions to construct objects, destroy obejcts and deallocate the memory. Therefore, programmers can manage the allocated space directly and determine the unitialized space. Here introduces four member functions and two non-member functions of the allocator class:

-
1
2
3
4
5
6
7
8
9
10
11
template <class T> class allocator{
public:
T* allocate(size_t);
void deallocator(T*, size_t);
void construct(T*, const T&);
void destroy(T*);

// ...
};
template<class Out, class T> void uninitialized_fill(Out, Out, const T&);
template<class In, class Out> Out uninitialized_copy(In, In, Out);
-
    -
  1. the allocate member allocates typed but uninitialized storage to hold the requested number of elements. It returns a pointer that has type T and denotes the initial address of the storage.

    -
  2. -
  3. the deallocator frees this uninitialized storage with taking the pointer given by allocate and the size.

    -
  4. -
  5. construct and destroy construct or destroy a single object in the uninitialized space.

    -
  6. -
  7. the first algorithm uninitialized_fill fills this uninitialized space with value from the third argument. The first two arguments denote the range of the space that to be filled.

    -
  8. -
  9. the second algorithm uninitialized_copy copies values from a sequence specified by the first two arguments into a target sequence denoted by the third argument. The range pointed by the third argument should large enough to hold all elements contained in the range specified by the first two arguments. It finally returns a pointer to one past the last constructed element.

    -
  10. -
  11. both two algorithms assumes that the target range contains raw storage rather than elements that already hold values.

    -
  12. -
-

To obtain an allocator of the right type at the compiler time, we’ll add to our Vec class an allocator member. By doing so, we can use above member functions to provide efficient and flexible memory management for our Vec class.

-

The final Vec class

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
template <class T> class Vec{
public:
// member types
typedef T* iterator;
typedef const T* const_iterator;
typedef size_t size_type;
typedef T value_type;

// constructors
Vec() { create(); }
explicit Vec(size_type n, const T& t = T()) { create(n, t); }

// copy constructor, assignment operator, destructor
Vec(const Vec& v) { create(v.begin(), v.end()); }
Vec& operator=(const Vec&);
~Vec() { uncreate(); }

// indexing operator
const T& operator[](size_type i) const { return data[i]; }

// push_back function
void push_back(const T& t){
if(avail == limit)
grow();
unchecked_append(t);
}

// size function
size_type size() const { return avail - data; }

// begin(), end() function
iterator begin() { return data; }
const_iterator begin() const { return data; }
iterator end() { return avail; }
const_iterator end() const { return avail; }

private:
iterator data; // first element in the Vec
iterator avail; // (one past) the last element in the Vec
iterator limit; // (one past) the allocated memory

// facilities for memory allocation
allocator<T> alloc; // object to handle memory allocation

// allocate and initialize the underlying array
void create();
void create(size_type, const T&);
void create(const_iterator, const iterator);

// destroy the elements in the array and free the memory
void uncreate();

// support functions for push_back
void grow();
void unchecked_append(const T&);
};
-

We should note that there are four conditions (aka. class invariants) that guarantees a valid Vec:

-
    -
  1. data points at our initial element, if we have any, and is zero otherwise.
  2. -
  3. data <= avail <= limit.
  4. -
  5. Elements have been constructed in the range[data, avail).
  6. -
  7. Elements have not been constructed in the range[avail, limit).
  8. -
-

Now the next is to write the implementation of different version of create functions while maintaining above class invariants.

-
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
template <class T> void Vec<T>::create()
{
data = avail = limit;
}

template <class T> void Vec<T>::create(size_type n, const T& val)
{
data = alloc.allocate(n);
limit = avail = data + n;
uninitialized_fill(data, limit, val);
}

template <class T> void Vec<T>::create(const_iterator i, const_iterator, j)
{
data = alloc.allocate(j - i);
limit = avail = uninitialized_copy(i, j, data);
}
-

The first version of create is used for initializing an empty Vec. The second one that takes a size and a value creates a Vec by allocating enough memory to hold n elements through alloc.allocate(n), and initializes all elements with val by applying the algorithm uninitialized_fill. The third version is used for copy-initialization, which takes two iterators that denote the sequence from which to copy. It calls uninitialized_copy algorithm to copy all values of the elements in [i, j) into [data, avail).

-

The destructor calls the uncreate member to destroy the elements and free the space that allocated by create.

-
1
2
3
4
5
6
7
8
9
10
11
12
template <class T> void Vec<T>::uncreate()
{
if(!data){
// destroy the elements in reverse order
iterator it = avail;
while(it != data)
alloc.destroy(--it);
alloc.deallocate(data, limit - data);
}
// reset pointers to indicate that Vec is empty again
data = limit = avail = 0;
}
-

The uncreate function first checks whether the data is 0. This is because that alloc.deallocate requires a non-zero pointer. There are two steps to destruct the Vec: the first step is that calling the destroy function to destroy each object contained in the Vec; the second step is that calling deallocate function to free the previous allocated storage. As deallocate doesn’t destroy elements in an array, it is crucial to call destroy function first which calls the destructor of the target element to release resource that might be occupied by the target element. It is known that there is no destructor for built-in types, so how does the destroy function work? It is presumed that the destroy function treats built-in objects and other objects in different manner. It still needs further research.

-

Finally, we write functions to support our push_back member.

-
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
template <class T> void Vec<T>::grow()
{
// when growing, allocate twice as much as space as currently in use
size_type new_size = max(2*(limit-data), ptrdiff_t(1));

// allocate new space and copy existing elements to the new space
iterator new_data = alloc.allocate(new_size);
iterator new_avail = uninitialized_copy(data, avail, new_data);

// return the old space
uncreate();

// reset pointers to point to the newly allocated space
data = new_data;
avail = new_avail;
limit = data + new_size;
}

// assumes avail points at allocated, but uninitialized space
template <class T> void Vec<T>::unchecked_append(const T& val)
{
alloc.construct(avail++, val);
}
- -

Now we have really completed our Vec class. The next post presents some tests on our Vec type from different perspectives.

- -
- - - - - - -
- -
- - - - -
- - - - -
- - -
- - - - -
- - - - - - - - -
-
- - -
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/2018/04/15/Implementing-the-C-STL-Algorithms-Part-1-Simple-Find-Algorithms/index.html b/2018/04/15/Implementing-the-C-STL-Algorithms-Part-1-Simple-Find-Algorithms/index.html deleted file mode 100644 index eb860cf7..00000000 --- a/2018/04/15/Implementing-the-C-STL-Algorithms-Part-1-Simple-Find-Algorithms/index.html +++ /dev/null @@ -1,572 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - Implementing the C++ STL Algorithms-Part 1: Simple Find Algorithms | Liam's Blog - - - - - - - - - - - - -
-
- -
-
- - -
- - -

If history repeats itself, and the unexpected always happens, how incapable must Man be of learning from experience?—George Bernard Shaw.

-
- - -
- - - - -
-
- - -
- - 0% -
- - - - -
-
-
- - -
- - -
- - - -
- - - - - -
-

- Implementing the C++ STL Algorithms-Part 1: Simple Find Algorithms -

- - -
- - - - -
- - -

find(beg, end, val)

Possible implementation

1
2
3
4
5
6
7
template <class InputIterator, class T>
InputIterator find(InputIterator beg, InputIterator end, const T& val)
{
while(beg != end && *beg != val)
++beg;
return beg;
}
-

Key points

    -
  1. parameters beg and end are two Input iterators,denoting that the range searched is [beg, end). val is the value to search for in the range.
  2. -
  3. the algorithm returns an iterator to the first element in the range [beg, end) equal to val. If no such element is found, the function returns end.
  4. -
  5. pointers are random access iterators and hence are also valid input iterators. Therefore, the algorithm can also be applied to the built-in array.
  6. -
  7. complexity: linear
  8. -
-

Test 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
// test my find algorithm
#include <iostream> // cout, endl
#include <vector> // vector
#include <cstring> // strlen
#include "my_algorithms.h" // my_find

using std::cout; using std::vector;
using std::endl; using std::find;
using std::strlen;

int main()
{
// to find an int type element in a vector
vector<int> vec{2, 4, 87, 9, 35, 77, 60};
vector<int>::iterator it = my_find(vec.begin(), vec.end(), 60);
if(it != vec.end())
cout << "Element is found in vec: " << *it << endl;
else
cout << "Element is not found in vec" << endl;

// to find an char type element in an array
char arr[] = "computational";
char* p = my_find(arr, arr+strlen(arr), 'u');
if(p != arr+strlen(arr))
cout << "Element is found in arr: " << *p << endl;
else
cout << "Element is not found in arr" << endl;
return 0;
}
- -

Outputs

-
1
2
Element is found in vec: 60
Element is found in arr: u
- -
-

find_if(beg, end, UnaryPred)

Possible implementation

1
2
3
4
5
6
7
template <class InputIterator, class UnaryPred>
InputIterator my_find_if(InputIterator beg, InputIterator end, UnaryPred pred)
{
while(beg != end && !pred(*beg))
++beg;
return beg;
}
- -

Key points

    -
  1. beg and end are two Input iterators denoting that the range searched is [beg, end). UnaryPred is a predicate on elements in the range. Each time it takes one of the elements, and then returns a value convertible to bool.
  2. -
  3. the algorithm returns an iterator to the first element in the range for which the pred returns true. If there is no such element, the function returns end.
  4. -
  5. there is no way to copy, assign, or pass a function as an argument directly due to a function is not an object. In fact, when we pass a function, the compiler uses the pointer to function instead of using the function directly. In addition, we can call a pointer to a function with or withour deferencing the pointer. Therefore, in this function template, the argument can either be a function “object”, that is, UnaryPred pred; or a function pointer, that is, UnaryPred* pred; or a function reference, that is, UnaryPred& pred. All these three cases allows us to call the function through pred(*beg).
  6. -
  7. complexity: linear
  8. -
-

Test 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
#include <iostream>			// cout, endl
#include <vector> // vector
#include <cstring> // strlen
#include <cctype> // isupper
#include "my_algorithms.h" // my_find_if

using std::cout; using std::vector;
using std::endl; using std::find;
using std::strlen; using std::isupper;

// the predication 1
bool IsEven(const int &i)
{
return i % 2 == 0;
}

// the predication 2
bool Isupper(const char &c)
{
return isupper(c);
}

int main()
{
// find the first even number in vec
vector<int> vec{2, 4, 87, 9, 35, 77, 60};
vector<int>::iterator it = my_find_if(vec.begin(), vec.end(), IsEven);
if(it != vec.end())
cout << "The first even number in vec is: " << *it << endl;
else
cout << "There is no even number in vec" << endl;

// find the first upper-case letter in arr
char arr[] = "abceFghI";
char* p = my_find_if(arr, arr + strlen(arr), Isupper);
if(p != arr + strlen(arr))
cout << "The first upper-case letter in arr is: " << *p << endl;
else
cout << "There is no upper-case letter in arr" << endl;

return 0;
}
- -

Outputs

-
1
2
The first even number in vec is: 2
The first upper-case letter in arr is: F
- -

find_if_not(beg, end, UnaryPred)

Possible implementation

1
2
3
4
5
6
7
template <class InputIterator, class UnaryPred>
InputIterator my_find_if_not(InputIterator beg, InputIterator end, UnaryPred pred)
{
while(beg != end && pred(*beg))
++beg;
return beg;
}
- -

In contrary to the find_if algorithm, this function returns an iterator to the first element in the range for which pred returns false. If pred returns true for all elements, the function returns end.

-

Test program

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
#include <iostream>			// cout, endl
#include <vector> // vector
#include "my_algorithms.h" // my_find_if_not
bool IsEven(const int &i)
{
return i % 2 == 0;
}

int main()
{
// find the first even number in vec
vector<int> vec{2, 4, 87, 9, 35, 77, 60};
vector<int>::iterator it = my_find_if_not(vec.begin(), vec.end(), IsEven);
if(it != vec.end())
cout << "The first odd number in vec is: " << *it << endl;
else
cout << "There is no odd number in vec" << endl;
return 0;
}
- -

Outputs

-
1
The first odd number in vec is: 87
-
-

count(beg, end, UnaryPred)

-
- - - - - - -
- -
- - - - -
- - - - -
- - -
- - - - -
- - - - - - - - -
-
- - -
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/2018/04/22/C-Making-class-objects-act-like-values/index.html b/2018/04/22/C-Making-class-objects-act-like-values/index.html deleted file mode 100644 index 84152702..00000000 --- a/2018/04/22/C-Making-class-objects-act-like-values/index.html +++ /dev/null @@ -1,635 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - C++ - Making class objects act like values | Liam's Blog - - - - - - - - - - - - -
-
- -
-
- - -
- - -

If history repeats itself, and the unexpected always happens, how incapable must Man be of learning from experience?—George Bernard Shaw.

-
- - -
- - - - -
-
- - -
- - 0% -
- - - - -
-
-
- - -
- - -
- - - -
- - - - - -
-

- C++ - Making class objects act like values -

- - -
- - - - -
- - -

As last chapter introduces, we can control what happens when objects are created, copied, assigned, and destroyed by defining special members. Now we intend to make class objects act like objects of built-in types through controlling more operations such as type conversion. A typical example is that the standard library class string provides rich set of operators and supports automatic conversions. Following the standard string, we’ll write our own Str class.

-

A simple string class

Analogous to the Vec class built in last chapter, we could write our Str based on dynamiclly allocated array. But it is also known that the standard string share many operations with the standard vector while the major difference is that a string is a container that only contains char elements. Therefore, we can design Str based on Vec rather than the lower level data structure.

-
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
class Str{
public:
typedef Vec<char>::size_type size_type;

// default constructor; create an empty Str
Str() { }

// create a Str containing n copies of c
Str(size_type n, char c): data(n, c) { }

// create a Str from a null-terminated array of char
Str(const char* cp){
std::copy(cp, cp + std::strlen(cp), std::back_inserter(data));
}

// create a Str from the range denoted by iterators b and e
template <class In> Str(In b, In e){
std::copy(b, e, std::back_inserter(data));
}

private:
Vec<char> data;
};
- -

It can be observed that our Str is implemented through a hidden Vec. There are four constructors defined in above class. The first functon is a default constructor that creates an empty Str through invoking the Vec default constructor. It is worth noting that we have to explicitly define a default constructor thought it does exactly . If we don’t define a default constructor,the compiler won’t synthesize one for us as there exist other constructors. The second constructor takes a size and a character and initializes the only data member data by invoking another Vec constructor that takes a size and a value. The third constructor allows us to create a string with passing an argument that is a pointer to char, that is, a null-terminated array of char. It uses the standard algorithm copy to copy the elements from the array of char, covering the range [cp, cp+std::strlen(cp)) into data. cp points to the first character of the array and cp + std::strlen(cp), where strlen(cp) returns the length of the array excluding the ‘\0’, points to one past the last character in the array. Similarly, the last constructor creates a string by taking two input iterators that denotes a sequence of characters. But it is worth nothing that it is not a function but a function template. It accepts different kinds of iterators, which implies that it can construct a string object from various containers like the array of char, standard vector, standard list etc..

-

We also observed that the Str doesn’t define a copy constructor, assignment operator and default destructor. The synthesized operations call the corresponding members of Vec when we copy or assign or destruct the Str object. In fact, the Str class does no memory allocation and hence doesn’t require a destructor. According to the rule of three, a class that needs no destructor doesn’t need an explicit copy constructor or assignment operator either.

-

Automatic conversions

In the case of the Str class, the conversions may happen when we assign a string literal to a string type object. For example:

-
1
2
Str t;    // default initialize t
t = "hello"; // assign a new value to t
-

The first statement creates an empty string and the second statement assigns the value of the right-hand side to the left-hand side. However, the left-hand side has type Str while the right-hand side has type const char*. In addition, we didn’t define the assignment operator. How does the compiler evaluates this expression? It turns out that the compiler will call the constructor that takes the a const char*. In other words, the statement invokes the same constructor as the following statement:

-
1
Str t("hello");
-

This example indicates that constructors also acts a user-defined conversion which determines how to transform to and from objects of class type. In generally, we define conversions by defining a constructor with a single argument. The above statement t = “hello”; involves two steps: firstly, calling the Str(const char*) to construct an unnamed local temporary of type Str from the string literal; then calls the synthesized assignment operator to assign this temporary to t.

-

Str operations

Now we further extent the operations of our Str class such that a Str type string s supports following operations:

-
1
2
3
4
cin >> s;    // use the input operator to read a string
cout << s; // use the output operator to write a string
s[i]; // use the index operator to access a character
s1 + s2; // use the addition operator to concatenate two strings
- -

indexing operator

We have learned how to define a operator, such as operator=, in defining the Vec class. We can define these operators in a similar manner. All above operators are binary operators and hence each operator function takes two parameters, one of which may be implicit if the function is a member. We are familar with the indexing operator. Let’s define it first:

-
1
2
3
4
5
6
7
8
9
class Str{
public:
// constructors as before
char& operator[](size_type i) { return data[i]; }
const char& operator[](size_type i) const { return data[i]; }

private:
Vec<char> data;
};
-

We define two operators to take the case that access elements of a const string into consideration. Details of the implementation go to the indexing operator defined in the Vec.

-

input and output operator

Now let’s think about how to implement the input operator >> and the output operator <<. The first problem is should these operators be members of a class? Due to the operator (e.g. >>) changes the state of a string, we might think it should be a member of the Str. But it is also known that the left operand is bound to the first parameter while the right operand is bound to the second parameter. Thus,

-
1
cin >> s;
-

is equivalent to

-
1
cin.operator >> (s);
-

which calls the overloaded >> operator defined for the object cin. This implies that the operator should be a member of the istream class. However, we cannot define such operation as we don’t have the definition of the istream class. If we define the operator in Str, it should invoke the input operation through

-
1
s.operation>> (cin);
-

or equivalently,

-
1
s >> cin;
-

which obviously would flout the conventions used throughout the library.

-

Know then that both the input and output operators should be non-member functions. Let’s declare two non-member functions:

-
1
2
std::istream& operator>>(std::istream&, Str&);
std::ostream& operator<<(std::ostream&, const Str&);
-

To write the output operator, we need to access each character stored in the Str. Therefore, the implementation could be

-
1
2
3
4
5
6
ostream& operator<<(ostream& os, const Str& s)
{
for(Str::size_type i = 0; i != s.size(); ++i)
os << s[i];
return os;
}
-

To use this function, we have to define the size member first

-
1
2
3
4
5
class Str{
public:
size_type size() const { return data.size(); }
// as before
};
-

Friends

Unlike the output operator, the input operator is a little bit complex. The logic is that each time read one character from the input stream and then add the character to our Str. The experience of using the standard string tells us that when reading data from the input stream, it discards the leading whitespace. Beyond this, we should also take into consider the case that there exist old values in the Str. Let’s see how following code deal with these problems.

-
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
// this code won't compile quite yet
istream& operator>>(istream& is, Str& s)
{
// obliterate existing value(s)
s.data.clear();

// read and discard leading whitespace
char c;
while(is.get(c) && isspace(c))
; // nothing to do except testing the condition

// if still something to read, do so until next whitespace character
if(is) {
do s.data.push_back(c);
while(is.get(c) && !isspace(c));

// if we read whitespace, then put it back on the stream
if(is)
is.unget();
}
return is;
}
-
    -
  1. the first step is to obliterate the old values.
  2. -
  3. the second step is to discard the leading whitespace. Two conditions control the while loop, one is that whether characters are available and another one is that whether the character read from the input stream is a space. The member function get extracts one character each time, if there exists characters, it returns the character and will be evaluated to true. If there no available character, it returns eof and will be evaluated to false. In summary, if the while loop ceases, there would be two situations, no character is available or the character is not a whitespace anymore.
  4. -
  5. then we perform reading process if there still available character in the input stream after step 2. The reading process calls the member function push_back to append one character one time. It stops if it reads nothing from the stream or encounters a whitespace. In the case that it encounters a whitespace, there might be other characters following the extracted whitespace. Therefore, we should put the extracted whitespace back on the stream. This is done by calling another member function of the istream class, that is, unget which decreases the current location by one character such that the extracted character can be extracted again next.
  6. -
-

The logic is perfect and we do solve the problems mentioned earlier. However, above code fails to compile due to that operator>> is not allowed to access the private data member data defined in the Str. We could add public member functions clear and push_back to our Str class like we did for our Vec class. But in this case, well solve this problem with an alternative method, using keyword friend.

-
1
2
3
class Str{
friend std::istream& operator>>(std::istream&, Str&);
};
-

A friend gives the function operator>> access and write rights to the private data members defined in the Str class. In other words, if making one function a friend of a class, we are saying that the function will be treated as a member (either public or private) by the class. Above code shows that we add the declaration of operator>> into the Str class and specify it is a friend of the class.

-

Other binary operators

We also consider that define the addition operator as a non-member function. The reason is that the addition operation doesn’t change values of the left-hand operand as well as the right-hand operand. The result of the addition operation between two strings is a string that concatenates two strings. Thus, the return type shoule be Str. Therefore, the operator= may be declared as follows:

-
1
Str operator+(const Str&, const Str&);
-

After we complete writing the implementation, our program would supports the concatenation operation between two Strs through

-
1
2
3
Str s1 = "xxx";
Str s2 = "yyy";
s1 = s1 + s2;
-

Our experience tells us that we can concatenate two standard strings in an alternative form:

-
1
s1 += s2;
-

Both two statements involve two processes: the right-hand side creates a new temporary object that is the concatenation of two strings, then the value of the constructed object is assigned to the left-hand side. The difference is that operator+= changes the value of left-hand operand. Therefore, we will define the operator+= as a public member of the Str class. Let’s see how to define operator+= first

-
1
2
3
4
5
6
7
8
9
10
11
12
13
14
class Str{
public:
Str& operator+=(const Str& s){
std::copy(s.data.begin(), s.data.end(), std::back_inserter(data));
return *this;
}

// as before

private:
Vec<char> data;
};
// as before
Str operator+(const Str&, const Str&);
-

There is nothing new in above implementation of the operator+=. Now let’s define the operator+:

-
1
2
3
4
5
Str operator+(const Str& s, const Str& t){
Str r = s;
r += t;
return r;
}
- -

In the definition, we use the operator+= and the synthesized copy constructor to achieve the concatenation of two strings.

-

Mixed-type expressions

The standard library string class also allows us to concatenate a string literal and a string regardless there order. As a result, we get a new string type object. For our Str class, we have defined the concatenation operator that takes operands of type const Str&. So, What would happen if following statement is evaluated:

-
1
const Str greeting = "Hello, " + name + "!"; // where **name** is a **Str** type object.
-

an alternative and equivalent statement

-
1
const Str greeting = ("Hello, " + name) + "!";
-

We can observe that there are two forms of +. The first + takes a string literal as its first operand and a Str as its seconnd operand, while in the other, the left operand is a Str and the right operand is a string literal. We may think that we should define two additional operator+ to handle these two case as the operator+ defined above only takes two arguments that are both const Str&. In fact, our Str class handles these expressions already, by means of calling the constructor that takes a const char*. This is because that the constructor is also a conversion operator that can convert a const char* to a Str. Let’s see how exactly the Str deal with this statement:

-
1
2
3
4
1. Str temp1("Hello, ");        // call Str::Str(const char*)
2. Str temp2 = temp1 + name; // call operator+(const Str&, const Str&)
3. Str temp3("!"); // call Str::Str(const char*)
4. Str greeting = temp2 + temp3;// call operator+(const Str&, const Str&)
-

The implied conversion operations may be expensive due to multiple temporaries. But certainly, we still can explicitly define two additional versions o the operator+ to deal with this case.

-

Designing binary operators

There are some rules in defining binary operators(Koening and Moo 2000):

-

1. If a class supports type conversions, then it is usually good practice to define binary operators as nonmember functions. By doing so, we preserve symmetry between the operands.

-

2. If an operator is a member of a class, then that operator’s left operand cannot be the result of an automatic conversion.

-

3. The left operand of a nonmember operator, and the right operand of any operator, follow the same rules as any ordinary function argument: the operand can be any type that can be converted to the parameter type.

-

4. like the assignment operator itself, all the compound-assignment operators (e.g. +=) should be members of the class.

-

Some conversions are hazardous

Recalling the Vec class designed in last chapter, it contains a constructor that takes a size (and a value if supplied) (as shown below).

-
1
explicit Vec(size_type n, const T& t = T()) { create(n, t); }
-

The explicit specifies that the constructor can only construct an object explicitly. If we don’t declare the Vec constructor as explicit, then we could implicitly create a Vec of a given size. To illustrate how useful the explicit is, let’s see an example:
. It is crucial that consider about the type conversion when defining a single parameter constructor for a class.

-

Conversion operators

We have known that we can implicitly define conversion operations through defining constructors. Those cases typically involve that a class defines how to convert an object from a different type to the type of the class itself. In fact, class authors can also explicitly define conversion operators, which determines how to convert an object from its type to a target type.

-

A conversion operator must be defined as a member of a class, begining with the keyword operator followed by the target type name. For example,

-
1
2
3
4
5
class Student_info(){
public:
operator double();
// ...
};
-

The conversion operator above defines that a Student_info can be converted to a double type object. The definition of operator would say how exactly create a double from a Student_info. For example, we can convert the class object to its corresponding final grade, and then use this property in calculating an average grade for a class.

-
1
2
3
4
5
6
7
vector<Student_info> vs;
// fill up vs

double d = 0;
for (int = 0; i != vs.size(); ++i)
d += vs[i]; // vs[i] is automatically converted to double
cout << "Average grade: " << d/vs.size() << endl;
- -

In fact, we use this kind conversion operator everytime when we write a loop that implicitly tests the value of an istream. See the example

-
1
if(cin >> x) { /*...*/ }
-

is equivalent to

-
1
2
cin >> x;
if(cin) { /*...*/ }
-

It is known that the condition should be an expression that yields a value that is convertible to type bool. Using a value of any arithmetic or pointer type automatically converts the value to type bool, thus we can uses values of these type in the expression. But a iostream object neither an arithmetic type object nor a pointer type object. To makes the if condition works in above case, the standard library defines a conversion from type istream to void*, i.e. a pointer to void.

-
1
2
3
istream::operator void* {
/*...*/
}
-

The operator tests various status flags to to determine whether the istream is valid and return either 0 or an implementation-defined non-zero void* value to indicate the state of the stream.

-

It is necessary to explain the use of void*. A pointer to void is known as a universal pointer which can point to any type of object. We cannot deference such pointer because the type of the object to yield is unknown. But we can convert a void* to bool.

-

One might wonder why don’t the istream define conversion operator to bool directly. The reason is that doing so allows the compiler to detect the following erroneous usage:

-
1
2
int x;
cin << x; // we should have to written cin >> x
-

If the conversion operator converts a istream object to bool, this expression would convert cin to a bool, and thereby converts the bool to int again. As a result, the converted value is shifted left by a number of bits equal to the value of x.

-

Conversions and memory management

In this section, we think about the conversion that from a string type to a null-terminated arrays of characters. If we can successfully convert a string to an array of characters, we then can pass the string to a functions that requires and operates on null-terminated arrays.

-
1
2
3
4
5
6
7
8
9
10
class Str{
public:
// plausible, but problematic conversion operations
operator char*();
operator const char*() const;

// as before
private:
Vec<char> data;
};
-

If above code works, we then can write code such as

-
1
2
3
Str S;
//...
ifstream in(s); // wishful thinking: converts s and then open the stream named s
-

(Noting that since c++11, ifstream allows us to open a file using either a string type name or a c-stype(i.e. null-termintated array) name.)

-

There are several difficulties in defining such operator:

-
    -
  1. we can’t simply return data as data is a Vec while we need an array of char.
  2. -
  3. if we design our Vec based on an array of char, we could return it as the converted result. However, doing so exposes the private data member, which violates the class Str‘s encapsulation. If users obtained a pointer to data, they could change the value of the string. In addition, if the string is destroyed, then the pointer becomes invalid and any related operations would be dengerous.
  4. -
-

To solve the encapsulation problem, we may provide only one conversion to const char*. To solve the dangling pointer problem, we may allocate a new space for a copy of the characters from data, and returning a pointer to this newly allocated space. By doing so, users can manage the allocated storage properly. However, this design probably doesn’t work either because the conversion happens implicitly and hence no pointer is provided explicitly.

-

The standard string class takes a different approach that allows us to get a copy of the string in a character array but also makes them do explicitly. It defines three member functions to get a character array from a string. The first is c_str() which copies the contents of the string into a null-terminated char array. The string owns the array and users are expected not to delete the pointer. The data in the array are ephemeral and is only valid until the next call of a member function that might change the string. The second data() is like c_str except that it returns an array that is not null-terminated (c++11 releases this condition and hence data() and c_str() are synonym and return the same value). Finally, the copy function takes a char* and an integer as arguments, and copies as many characters as indicated by the integer into space pointed by the char*, which soace the user must allocate and free. These functions work as we expected. However, this type of coversions seems explicitly rather than implicitly.

-
- -
- - - - - - -
- -
- - - - -
- - - - -
- - -
- - - - -
- - - - - - - - -
-
- - -
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/2018/04/24/C-Implementations-Dynamic-Array-based-Stack/index.html b/2018/04/24/C-Implementations-Dynamic-Array-based-Stack/index.html deleted file mode 100644 index d5e099c6..00000000 --- a/2018/04/24/C-Implementations-Dynamic-Array-based-Stack/index.html +++ /dev/null @@ -1,551 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - C++ Implementation: Dynamic Array-based Stack | Liam's Blog - - - - - - - - - - - - -
-
- -
-
- - -
- - -

If history repeats itself, and the unexpected always happens, how incapable must Man be of learning from experience?—George Bernard Shaw.

-
- - -
- - - - -
-
- - -
- - 0% -
- - - - -
-
-
- - -
- - -
- - - -
- - - - - -
-

- C++ Implementation: Dynamic Array-based Stack -

- - -
- - - - -
- - -

Stack is one of the rudimentary data structures that use pointers, with a main feature that it implements the Delete operation following last in, first out (i.e. LIFO). More specific, a stack is a dynamic set that allows Insert and Delete operations, which are typically named push and pop respectively.

-

The program given below illustrates an ADT named my_stack, which implements the stack based on a dynamic allocated array.
my_stack is a class template and provides an interface that allows following operations:

-
1
2
3
4
5
6
7
8
my_stack ms;        // create a stack with fixed capacity 1000
my_stack s(100); // create a stack with a user-supplied capacity
s.get_capacity(); // get the current capacity of s
s.size(); // get the number of elements contained in s
s.empty(); // check whether the stack is empty
s.push(); // insert an new element into the stack at the end of it
s.pop(); // delete the last inserted element from the stack, and return the deleted element
s.top_element(); // return the top element only
-

Noting that the capacity of a stack means how many elements the stack can contain while the size means how many elements have the stack stored.

-

stack implementation

-
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
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
#ifndef MYSTACK_H_
#define MYSTACK_H_

#include <iostream> // std::cout, std::endl
#include <cstddef> // std::size_t
#include <stdexcept>// std::domain_error

template <class T> class MyStack{
public:
typedef std::size_t size_type;

// default constructor
MyStack(): top(0), capacity(1000) {
std::cout << "default constructor" << std::endl;
p = new T[capacity];
}

// constructor with user-defined capacity
explicit MyStack(size_type t): top(0), capacity(t) {
std::cout << "constructor with user-defined size" << std::endl;
p = new T[capacity];
}

// copy constructor
MyStack(const MyStack& s): top(0), capacity(s.capacity){
std::cout << "copy constructor" << std::endl;
p = new T[capacity];
T* temp = s.p;
while(top != s.top){
p[top] = *temp;
++top;
++temp;
}
}

// assignment operator
MyStack& operator=(const MyStack& s){
std::cout << "assignment operator" << std::endl;
if(&s != this){
clear();
capacity = s.capacity;
p = new T[capacity];
T* temp = s.p;
while(top != s.top){
p[top] = *temp;
++top;
++temp;
}
}
return *this;
}

// destructor
~MyStack() {
delete[] p;
p = nullptr;
}

void clear(){
top = 0;
}

// capacity: O(1)
size_type get_capacity() const { return capacity; }

// size: O(1)
size_type size() const { return top; }

// empty
bool empty() const { return top == 0; }

// top_element: O(1)
T top_element() const { return p[top - 1]; }

// push element: O(1)
void push(const T& t){
if(top == capacity)
throw std::domain_error("stack overflow");
p[top] = t;
++top;
}

// pop element and return the deleted element: O(1)
T pop() {
if(top == 0)
throw std::domain_error("stack underflow");
--top;
return p[top];
}

private:
size_type top; // count the number of elements
size_type capacity; // capacity of the stack
T* p; // a hidden pointer to head
};

#endif /* MYSTACK_H_ */
- -

The shortcoming of above stack is that it cannot grow automatically. Except constructors, each of member functions has constant complexity. The follwing program tests each operation listed above and shows that the my_stack works as expected.

-

stack test

-
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
65
66
67
68
69
70
/*
* this program tests all operations that provided by the MyStack<int> class
* created by Liam on: 27 Apr 2018
*/

#include <iostream> // std::cout, std::endl
#include <stdexcept> // std::domain_error
#include "MyStack.h" // MyStack

using std::cout;
using std::endl;
using std::domain_error;

int main(){

{ // test default constructor
MyStack<int> s;

// test member capacity()
cout << "The capacity of the stack is: " << s.get_capacity() << "\n";

// test member empty()
if(s.empty())
cout << "The stack is empty\n";

// test member push(const T& t)
for(int i = 0; i != 10; ++i)
s.push(i);

// test member pop
while(s.size() != 0)
cout << s.pop() << " ";
}

cout << "\n\n";

{ // test constructor with size
MyStack<int> s(10);

for(unsigned int i = 0; i != s.get_capacity(); ++i)
s.push(i);

// test the case of overflow
try{
s.push(10);
}catch(std::domain_error e){
cout << e.what() << "\n";
}

// test copy constructor
MyStack<int> s_copy(s);
cout << "The top element in MyStack is: ";
cout << s_copy.top_element() << "\n";

// test assignment operator
s.pop();
s_copy = s;

while(s_copy.size() != 0)
cout << s_copy.pop() << " ";

try{
s_copy.pop();
}catch(domain_error e){
cout << e.what() << "\n";
}

}
return 0;
}
- -

Outputs

-
1
2
3
4
5
6
7
8
9
10
11
default constructor
The capacity of the stack is: 1000
The stack is empty
9 8 7 6 5 4 3 2 1 0

constructor with user-defined size
stack overflow
copy constructor
The top element in MyStack is: 9
assignment operator
8 7 6 5 4 3 2 1 0 stack underflow
- - -
- - - - - - -
- -
- - - - -
- - - - -
- - -
- - - - -
- - - - - - - - -
-
- - -
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/2018/05/06/Accelerated-C-Solutions-to-Exercises-Chapter-11/index.html b/2018/05/06/Accelerated-C-Solutions-to-Exercises-Chapter-11/index.html deleted file mode 100644 index 084d799c..00000000 --- a/2018/05/06/Accelerated-C-Solutions-to-Exercises-Chapter-11/index.html +++ /dev/null @@ -1,566 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - Accelerated C++ Solutions to Exercises (Chapter 11) | Liam's Blog - - - - - - - - - - - - -
-
- -
-
- - -
- - -

If history repeats itself, and the unexpected always happens, how incapable must Man be of learning from experience?—George Bernard Shaw.

-
- - -
- - - - -
-
- - -
- - 0% -
- - - - -
-
-
- - -
- - -
- - - -
- - - - - -
-

- Accelerated C++ Solutions to Exercises (Chapter 11) -

- - -
- - - - -
- - -

Exercise 11-0

Compile, execute, and test the programs in this chapter.

-

Solution & Results

Please find the programs and analysis in Defining abstract data types(Part 2).

-

Exercise 11-1, 11-2, 11-3, 11-4

11-1: The Student_info structure that we defined in Chapter 9 did not define a copy constructor, assignment operator, or destructor. Why not?

-

11-2: That structure did define a default constructor. Why?

-

11-3: What does the synthesized assignment operator for Student_info objects do?

-

11-4: How many members does the synthesized Student_info destructor destroy?

-

Solution & Results

Recalling the Student_info class:

-
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
#ifndef GUARD_STUDENT_INFO
#define GUARD_STUDENT_INFO

#include <string>
#include <iostream>
#include <vector>

class Student_info
{
public:
Student_info (); // default constructor
Student_info (std::istream &); // constructor with argument
std::string name() const { return n; } // inline member function return name
bool valid() const { return !homework.empty(); } // inline member function check state
std::istream & read(std::istream &); // member function read in data
double grade() const; // member function calculate final grade

private:
std::string n;
double midterm, final;
std::vector<double> homework;
};

std::istream & read_hw(std::istream &, std::vector<double> &); // nonmember function read data into a vector
bool compare(const Student_info &, const Student_info &); // nonmember function compare two string

#endif
- -

If we don’t explicitly define a copy constructor, assignment operator and destructor, the compiler will synthesizes default versions of the unspecified operation. In this case, members midterm, final are built-in type variables and hence are copied and assigned by copying or assigning their value. But the destructors for built-in types do nothing. Members string and vector are class type variables and hence are copied, assigned, or destoryed by calling the constructor, assignment operator, and destructor for the data element. It is known that both these two standard classes define the corresponding behaviours in their headers. Therefore, it is unnecessary to define these operations in our class again. When the computer evaluates an assignment, for example

-
1
2
3
4
5
6
7
8
9
10
Student_info record(cin);   // construct from input stream
Student_info record_copy; // construct an empty object
record_copy = record; // assignment
```
it calls the default assignment operators for each data member as if:
```c++
n = record.n; // call assignment operator defined in the string class
midterm = record.m; // assign values
final = record.final; // assign values
homework = record.homework; // call assignment operator defined in the vector class
-

These operations typically involves obliterating the values of the left-hand side operand and then copying values from right-hand side operand into the left-hand side operand. By analogy, we know how the synthesized copy constructor work. When a Student_info class object is destructed, the synthesized destructor detroyes its data members by calling their destructors respectively. For midterm and final, their destructors have no work to do. Therefore, the synthesized Student_info destructor destroyes two data members.

-

The compiler will synthesize a default constructor for us if and only if we don’t explicitly define any constructors, even a copy constructor. In this case, we explicitly define a contructor with argument and hence no synthesized version for us. In addition, we do need a user-defined default constructor as the built-in types in local scope are undefined following the synthesized operation.

-

Exercise 11-5

Instrument the Student_info class to count how often objects are created, copied,assigned, and destroyed. Use this instrumented class to execute the student record programs from Chapter 6. Using the instrumented Student_info class will let you see how many copies the library algorithms are doing. Comparing the number of copies will let you estimate what proportion of the cost differences we saw are accounted for by the use of each library class. Do this instrumentation and analysis.

-

Solution & Results

To be filled.

-
-

Exercise 11-6, 11-7

Add an operation to remove an element from a Vec and another to empty the entire Vec. These should behave analogously to the erase and clear operations on vectors.

-

Once you’ve added erase and clear to Vec, you can use that class instead of vector in most of the earlier programs in this book. Rewrite the Student_info programs from Chapter 9 and the programs that work with character pictures from Chapter 5 to use Vecs instead of vectors.

-

Solution & Results

The original version can be found in C++ - Defining abstract data types(Part 2). The program below only shows the new contents including the erase functions and the clear function.

-

Vec.h

-
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
#ifndef GUARD_VEC_H
#define GUARD_VEC_H

#include <cstddef>
#include <algorithm>
#include <memory>

template <class T> class Vec{
public:
// as before

// erase function
iterator erase(iterator iter);

// overloaded erase function
iterator erase(iterator beg, iterator end);

// clear function
void clear() { erase(begin(), end()); }

private:
// as before
};

template <class T>
typename Vec<T>::iterator Vec<T>::erase(iterator iter){
if (iter + 1 != avail)
std::uninitialized_copy(iter + 1, avail, iter);
--avail;
alloc.destroy(avail);
return iter;
}

template <class T>
typename Vec<T>::iterator Vec<T>::erase(iterator first, iterator last){
if(last != avail)
std::uninitialized_copy(last, avail, first);
iterator new_avail = avail - (last - first);
iterator it = new_avail;
while (it != avail)
alloc.destroy(it++);
avail = new_avail;
return first;
}
-

The first erase function takes one parameter, an iterator, and removes the element pointed by the iterator. The second erase function takes two iterators, denoting a range [first, last), and removes all elements in this range. Both erase functions return an iterator pointing to the new location of the element that followed the last element erased by the function call. Noting that the position of limit keeps unchanged and hence the capacity of this vector remains the same. I only destroy these elements but do not free the space because the destructor will free the space occupied by the range [data, limit). The clear function calls the erase function and erase all elements in the range [first(), end()). The test program below shows that all three members work as expected.

-

mainfunction.cpp

-
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 "Vec.h"

using std::cout; using std::cin;
using std::endl;

int main()
{
Vec<double> v;

// stores 0-9 into the Vec
for(int i = 0; i != 10; ++i)
v.push_back(i);

// traverse
cout << "The original list is: ";
for(auto i: v)
cout << i << " ";
cout <<"\n";

// erase one by one starting from begin()
Vec<double>::iterator i = v.begin();
while(i != v.end())
{
v.erase(i);
for(auto i: v)
cout << i << " ";
cout << "\n";
}

Vec<double> v1(10, 10);
cout << "The size of v1 is: " << v1.size() << "\n";

// erase first 5 elements
v1.erase(v1.begin(), v1.begin() + 5);
cout << "The size of v1 is: " << v1.size() << "\n";
cout << "The rest elements are: ";
for(auto i: v1)
cout << i << " ";
cout << "\n";

// clear the Vec
v1.clear();
cout << "The size of v1 is: " << v1.size() << "\n";

return 0;
}
- -

Outputs

-
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
The original list is: 0 1 2 3 4 5 6 7 8 9 
1 2 3 4 5 6 7 8 9
2 3 4 5 6 7 8 9
3 4 5 6 7 8 9
4 5 6 7 8 9
5 6 7 8 9
6 7 8 9
7 8 9
8 9
9

The size of v1 is: 10
The size of v1 is: 5
The rest elements are: 10 10 10 10 10
The size of v1 is: 0
-

It is easy to rewrite the Student_info programs from Chapter 9 and the programs that work with character pictures from Chapter 5. No more discussion here.

-
-

Exercise 11-8

Write a simplified version of the standard list class and its associated iterator.

-

Solution & Results

To be filled.

Exercise 11-9

The grow function in §11.5.1/208 doubles the amount of memory each time it needsmore. Estimate the efficiency gains of this strategy. Once you’ve predicted how much of a difference it makes, change the grow function appropriately and measure the difference.

-

Solution & Results

There is an article written by the authors Andrew Koenig and Barbara E. Mooon about this topic C++ Made Easier: How Vectors Grow.

- -
- - - - - - -
- -
- - - - -
- - - - -
- - -
- - - - -
- - - - - - - - -
-
- - -
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/2018/05/06/Defining-abstract-data-types-Part-2/index.html b/2018/05/06/Defining-abstract-data-types-Part-2/index.html deleted file mode 100644 index e625940d..00000000 --- a/2018/05/06/Defining-abstract-data-types-Part-2/index.html +++ /dev/null @@ -1,546 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - C++ - Defining abstract data types(Part 2) | Liam's Blog - - - - - - - - - - - - -
-
- -
-
- - -
- - -

If history repeats itself, and the unexpected always happens, how incapable must Man be of learning from experience?—George Bernard Shaw.

-
- - -
- - - - -
-
- - -
- - 0% -
- - - - -
-
-
- - -
- - -
- - - -
- - - - - -
-

- C++ - Defining abstract data types(Part 2) -

- - -
- - - - -
- - -

The full version of Vec class template described in C++ - Defining abstract data types is presented below.

-

Vec.h

-
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
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
#ifndef GUARD_VEC_H
#define GUARD_VEC_H

#include <iostream> // std::cout, std::endl
#include <cstddef> // std::size_t
#include <algorithm> // std::max
#include <memory> // std::allocator, std::uninitialized_fill, std::uninitialized_copy

template <class T> class Vec{
public:
// member types
typedef T* iterator;
typedef const T* const_iterator;
typedef std::size_t size_type;
typedef T value_type;

// constructors
Vec() {
std::cout << "calling default constructor" << std::endl;
create();
}

explicit Vec(size_type n, const T& t = T()) {
std::cout << "calling the explicit constructor" << std::endl;
create(n, t);
}

// copy constructor,
Vec(const Vec& v) {
std::cout << "calling copy constructor" << std::endl;
create(v.begin(), v.end());
}

//assignment operator
Vec& operator=(const Vec&);

// destructor
~Vec() {
std::cout << "calling destructor" << std::endl;
uncreate();
}

// indexing operator
const T& operator[](size_type i) const {
std::cout << "calling operation[]" << endl;
return data[i];
}

// push_back function
void push_back(const T& t){
if(avail == limit)
grow();
unchecked_append(t);
}

// size function
size_type size() const { return avail - data; }

// begin(), end() function
iterator begin() { return data; }
const_iterator begin() const { return data; }
iterator end() { return avail; }
const_iterator end() const { return avail; }

private:
iterator data; // first element in the Vec
iterator avail; // (one past) the last element in the Vec
iterator limit; // (one past) the allocated memory

// facilities for memory allocation
std::allocator<T> alloc; // object to handle memory allocation

// allocate and initialize the underlying array
void create();
void create(size_type, const T&);
void create(const_iterator, const_iterator);

// destroy the elements in the array and free the memory
void uncreate();

// support functions for push_back
void grow();
void unchecked_append(const T&);
};

// initialize data members to nullptr
template <class T> void Vec<T>::create()
{
data = avail = limit = nullptr;
}

// create and initialize data members with a size and a value
template <class T> void Vec<T>::create(size_type n, const T& val)
{
data = alloc.allocate(n);
limit = avail = data + n;
std::uninitialized_fill(data, limit, val);
}

// create and initialize data members by copying values from an input sequence
template <class T> void Vec<T>::create(const_iterator i, const_iterator j)
{
data = alloc.allocate(j - i);
limit = avail = std::uninitialized_copy(i, j, data);
}

// destruct the class object using destroy and deallocate functions
template <class T> void Vec<T>::uncreate()
{
if(!data){
// destroy the elements in reverse order
iterator it = avail;
while(it != data)
alloc.destroy(--it);
alloc.deallocate(data, limit - data);
}
// reset pointers to indicate that Vec is empty again
data = limit = avail = 0;
}

// assign values from right-hand operand to the left-hand operand
template <class T>
Vec<T>& Vec<T>::operator= (const Vec& rhs)
{
std::cout << "calling operator= function" << std::endl;

// check for self-assignment
if(&rhs != this)
{
// free the array in the left-hand side
uncreate();

// copy elements from the right-hand to the left-hand side
create(rhs.begin(), rhs.end());
}
return *this;
}

// reallocate storage to hold more elements
template <class T> void Vec<T>::grow()
{
// when growing, allocate twice as much as space as currently in use
size_type new_size = std::max(2*(limit-data), ptrdiff_t(1));

// allocate new space and copy existing elements to the new space
iterator new_data = alloc.allocate(new_size);
iterator new_avail = std::uninitialized_copy(data, avail, new_data);

// return the old space
uncreate();

// reset pointers to point to the newly allocated space
data = new_data;
avail = new_avail;
limit = data + new_size;
}

// add new element at the end of the vector
template <class T> void Vec<T>::unchecked_append(const T& val)
{
alloc.construct(avail++, val);
}
#endif /* GUARD_VEC_H */
- -

Test

-

Now, let’s test our Vec class to see how does it work.

-

main.cpp

-
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
#include <iostream>
#include "Vec.h"

using std::cout; using std::cin;
using std::endl;

int main()
{
{
Vec<int> v; // call default constructor
Vec<int> v1(10, 100); // call explicit constructor
Vec<int> v2(v1); // call copy constructor
v = v1; // call assignment operator

// the destructor is expected to be called three times
}

cout << endl;
{
Vec<int> v; // call default constructor
if(v.size() == 0){ // test size
v.push_back(10); // test push_back function
}

// test indexing operator
cout << "The first element is: " << v[0] << "\n";

// call the destructor
}

cout << endl;
{
Vec<int> v(5, 100); // call explicit constructor

// test iterator
for(Vec<int>::iterator it = v.begin(); it != v.end(); ++it)
cout << *(it) << " ";

cout << "\n";
// call the destructor
}
return 0;
}
- -

Outputs

-
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
calling default constructor
calling the explicit constructor
calling copy constructor
calling operator= function
calling destructor
calling destructor
calling destructor

calling default constructor
calling operation[]
The first element is: 10
calling destructor

calling the explicit constructor
100 100 100 100 100
calling destructor
-

The test program generates outputs as expected. As mentioned in last post, the operator= function returns a reference to the new constructed class object can be more efficient than returning the value directly. The reason behind this is that returning a value unnecessarily calls the copy constructor and destructor. Let’s verify this by evaluating following statements within the setting of returning by value:

-
1
2
3
Vec<int> v;			// call default constructor
Vec<int> v1(10, 100); // call explicit constructor
v = v1; // call assignment operator
- -

Outputs

-
1
2
3
4
5
6
7
calling default constructor
calling the explicit constructor
calling operator= function
calling copy constructor
calling destructor
calling destructor
calling destructor
-

Comparing with the original version, the outputs are the same. However, the previous program evalutes four statements including a copy construction as well. This program only creates two objects: v and v1, but additionally calls the copy constructor and destructor once for each after calling the assignment operator. The results confirms our expectation.

- -
- - - - - - -
- -
- - - - -
- - - - -
- - -
- - - - -
- - - - - - - - -
-
- - -
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/2018/05/08/C-Using-inheritance-and-dynamic-binding/index.html b/2018/05/08/C-Using-inheritance-and-dynamic-binding/index.html deleted file mode 100644 index d2ef26a6..00000000 --- a/2018/05/08/C-Using-inheritance-and-dynamic-binding/index.html +++ /dev/null @@ -1,611 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - C++ - Using inheritance and dynamic binding | Liam's Blog - - - - - - - - - - - - -
-
- -
-
- - -
- - -

If history repeats itself, and the unexpected always happens, how incapable must Man be of learning from experience?—George Bernard Shaw.

-
- - -
- - - - -
-
- - -
- - 0% -
- - - - -
-
-
- - -
- - -
- - - -
- - - - - -
-

- C++ - Using inheritance and dynamic binding -

- - -
- - - - -
- - -

Inheritance

In this chapter, we intend to extend our grading program such that it meets the new requirements: students can take undergraduate or graduate credit while graduate students have to write a thesis in addition to the homework and exams. In other words, a record for graduate credit is the same as for undergraduate credit except that it has extra properties related to the thesis. This problem can be abstracted and solved by a mechanism called inheritance, which is one of the cornerstones of OOP.

-

Specifically, we’ll write two classes, the first class is the abstraction of the core requirements and is named Core while the second class represents the requirements for graduate credit and hence named Grad. The Grad class captures extra requirements but has same core requirements as the Core class. Therefore, We write two classes such that the Grad class can inherit the properties from the Core class. Typically, we say that the Grad class is derived from or inherits from the base class, i.e. the Core class here. let’s see how to define these two classes:

-
1
2
3
4
5
6
7
8
9
10
11
12
13
14
class Core{
public:
Core();
Core(std::istream&);
std::string name() const;
std::istream& read(std::istream&);
double grade() const;

private:
std::istream& read_common(std::istream&);
std::string n;
double midterm, final;
std::vector<double> homework;
};
- -
1
2
3
4
5
6
7
8
9
class Grad: public Core{
public:
Grad();
Grad(std::istream&);
double grade() const;
std::istream& read(std::istream&);
private:
double thesis;
};
-

Since Grad class inherits from Core class, every member of Core is also a member of Grad, except for the constructor, assignment operator, and destructor. The Grad class also has its own members, such as the thesis and its own constructors. It can also redefine members from the base class, such as the grade and read function.

-

The keyword public in public Core means that Grad inherits from Core is part of its interface rather than its implementation. In other words, the public interface to Core becomes part of the public interface to Grad. For example, if we have a Grad object, we can call its name member thought Grad doesn’t define its own name function.

-

Beyond the four data members from the Core class, Grad has a member thesis and calculates grade() using different algorithm. It have two constructors, and four member functions, two of which redefine the corresponding members of Core, and name and read_common functions.

-

Protection revisited

As it stands, four data members as well as the read_common function are inaccessible to member functions in Grad as they are private and only available to the Core members and its friends. But we do need these data members and read_common for defining the grade and read functions in the Grad. To achieve this goal, we rewrite the Core class using a protection lable:

-
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
class Core{
// public members are available for users of the derived class
public:
Core();
Core(std::istream&);
std::string name() const;
std::istream& read(std::istream&);
double grade() const;

// protected members are available for member functions of the derived class but not available for users
protected:
std::istream& read_common(std::istream&);
double midterm, final;
std::vector<double> homework;

// only available for members of the class itself and its friends.
private:
std::string n;
};
-

The protected members are available for derived classes but still inaccssible users of the classes. n is still private but Grad can access the name by calling its member function name.

-

Operations

The next is to implement four constructors(each class has one default constructor and one constructor with arguments) and six operations including common functions name, read_common, and read and grade for two class respectively. We’ll read the thesis grade closely after the final exam grade but precede the homework grades.

-
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
// name function
string Core::name() const { return n; }

// grade function for Core
double Core::grade() const {
return ::grade(midterm, final, homework);
}

// read_common function
istream& Core::read_common(istream& in){
in >> n >> midterm >> final;
return in;
}

// read function for Core
istream& Core::read(istream& in){
read_common(in);
read_hw(in, homework);
return in;
}
-
1
2
3
4
5
6
7
8
9
10
11
12
// read for Grad
istream& Grad::read(istream& in){
Core::read_common(in);
in >> thesis;
read_hw(in, Core::homework);
return in;
}

// grade for Grad
double Grad::grade() const{
return min(Core::grade(), thesis);
}
-

The Grad::grade function shows that we calculate the final grade as the lesser between the grade excluding thesis, and thesis. Though we can call members of Core directly, we’d better explicitly call some functions for avoiding ambiguity. For example, if we don’t explicitly call Core::grade(), the compiler may use the Grad::grade dirctly.

-

Inheritance and constructors

Derived objects are constructed by(Koenig and Moo 2000):
1. Allocating space for the entire object (base-class members as well as derived members)
2. Calling the base-class constructor to initialize the base-class part(s) of the object
3. Initializing the members of the derived class as directed by the constructor initializer
4. Executing the body of the derived-class constructor, if any

-

Clearly, the constructor of a derived class not only constructs its own members but also constructs data members of the base class.

-
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
class Core{
public:
// default constructor for Core
Core(): midterm(0), final(0) {}

// build a Core from an istream
Core(istream& is){ read(is); }

// ...
};

class Grad: public Core{
public:
// default constructor for Grad: first implicitly calls the default constructor Core::Core()
Grad(): thesis(0) {}

// build a Grad from istream: first implicitly calls Core::Core()
Grad(std::istream& is) { read(is); }

//...
};
-

For example, when execute

-
1
Grad g; // create an empty object
-

The computer allocates enough space to hold five data members for the Grad object, run the Core default constructor to initialize the data members in the Core part of g, and then run the default constructor of Grad. Again, when execute

-
1
Grad g(cin);
-

the computer will run the Core default constructor, followed by the Grad::Grad(istream&) constructor to read values into five data memners.

-

Polymorphism and virtual functions

There is also a support function for the Student_info program, that is, the compare function that acts as the predicate of the std::sort algorithm.

-
1
2
3
bool compare(const Core& c1, const Core& c2){
return c1.name() < c2.name();
}
-

How does it work on the Grad class objects? For example

-
1
2
3
4
5
6
7
8
9
Grad g(cin);    // read a Grad record
Grad g2(cin); // read a Grad record

Core c(cin); // read a Core record
Core c2(cin); // read a Core record

compare(g, g2); // compare two Grad records
compare(c, c2); // compare two Core records
compare(g, c); // compare Grad record with a Core record
-

The compare function can take two Core objects as well as two Grad objects, even one Core and one Grad. For the function body, it makes sence as any Grad object has a member name, which it inherits from the base. But why we can pass a Grad object to a function expecting a Core&? The reason is that Grad is inherited from Core and hence has a Core part. Then, we can bind compare‘s reference parameters to the Core portions of Grad objects, as if we bind them to plain Core objects.

-

Obtaining a value without knowing the object’s type

The compare function described above works properly. However, if we intend to compare the grade rather than the name, the function seems inappropriate for Grad objects as two classes have different grade function. A right logical manner is that the compare function can invoke the right grade function according to the type of the object that we pass, only at the stage of run time. To support this kind of run time selection, C++ provides virtual functions:

-
1
2
3
4
class Core{
public:
virtual double grade() const; // virtual added
};
-

When we call compare(grade-version), the implementation will determine which version of the grade should execute by looking at the actual type of the objects to which the reference c1 and c2 are bound. If the argument is Grad,then it calls Grad::grade and calls Core::grade otherwise.

-

Noting that the keyword virtual may be used only inside the class definition. If we seperate the declaration and definition, we do not need to repeatedly use it in the definition.

-

Dynamic binding

Another point about the virtual is that it is relevant only when the function is called through a reference or a pointer. If we call the function on behalf of the object, then we know the exact type of the object at compile time. In contrast, a reference or a pointer to a base class object may refer or point to a base-class object or to an object of a type derived from the base class. Assuming we write compare_grades:

-
1
2
3
4
// incorrect implementation
bool compare_grades(Core c1, Core c2){
return c1.grade() < c2.grade();
}
-

In this case, we know exactly that both two objects are Core type. Even we call the function with Grad objects, the Grad objects will be cut down to its Core part and a copy of that part will be passed to the compare_grades function. This case is known as statically bound, that is, the calls to Grad are bound at compile to Core::grade. Obviously, the dynamic binding is that the function is dynamically bound at run time. If we call a virtual function through a pointer or a reference, the version of virtual function to use depends on the type of the object which the reference or pointer is bound.

-
1
2
3
4
5
6
7
8
9
Core c;
Grad g;
Core* p;
Core& r = q;

c.grade(); // statically bound to Core::grade()
g.grade(); // statically bound to Grad::grade()
p->grade(); // dynamically bound, depending on the type of the object to which p points
r.grade(); // dynamically bound, depending on the type of the object to which r refers
- -

The fact that we can use a derived type where a pointer or reference to the base is expected is an example of polymorphism, meaning of many form. When we call the virtual function by a pointer or reference, we make a polymorphic call. We’ll make the read function virtual as well and then the version of the read function to be called depends on the type of the object on which it is invoked.

-

Recap

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
class Core{
public:
Core(): midterm(0), final(0) {}
Core(std::istream& is) { read(is); }

std::string name() const;

virtual std::istream& read(std::istream&);
virtual double grade() const;

protected:
// accessible to derived classes
std::istream& read_common(std::istream&);
double midterm, final;
std::vector<double> homework;

private:
// accessible only to Core
std::string n;
};

class Grad:: public Core{
public:
Grad(): thesis(0) {}
Grad(std::istream& is) { read(is); }

double grade() const;
std::istream& read(std::istream);

private:
double thesis;
};
bool compare(const Core&, const Core&);
- -

Using inheritance to solve our problem

Now we can write our student grading prorgam described in chapter 9. The problem is how can we write a program that can handle with both Core objects and Grad objects. To achieve our goal, we need to eliminate these type dependencies(Koenig and Moo 2000):

-

1. The definition of the vector in which we store the elements as we read them
2. The definition of the local temporary into which we read the records
3. The read function
4. The grade function

-

Now we’ll see how to solve these problems.

-

Containers of virtually unkown type

Consider if we define a vector as follows:

-
1
2
vector<Core> students;      // must hold Core objects, not polymorphic types
Core record; // Core object, not a type derived from Core
-

It is impossible to hold the Grad objects as we explicitly declare that a vector hold objects of type Core. Then, if we call the read function or grade function we indeed call Core::read or Core::grade. However, we have mentioned in above section, if we call those functions through pointers or references, these functions are dynamically bound at run time, depending on the type of the object which the reference or pointer is bound. Therefore, a natural solution is that define a vector that stores the pointer to each element rather than the element itself.

-
1
2
vector<Core*> students;
Core* record;
-

Howvever, this doesn’t work as no one knows where the record points initially. If the computer executes data reading, the program would crash.

-
1
while(record->read(cin)) { // crash! }
-

Now we provide a verbose solution to this problem: let users manually control the type of the object. We use lable U to represent that the type is a Core object, and use G to represent that the type is a Grad object. Before we implement this strategy, we should rewrite our compare function such that it can sort two pointers.

-
1
2
3
bool compare_Core_ptrs(const Core* cp1, const Core* cp2){
return compare(*cp1, *cp2);
}
-

Noting that we can’t name this predicate as compare as we cannot pass an overloaded function as a template argument. Now, let’s see the whole 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
// this work almost work
int main(){
vector<Core*> students; // store pointers, not objects
Core* record;
char ch;
string::size_type maxlen = 0;

// read and store the data
while(cin >> ch){
if(ch == 'U')
record = new Core; // allocate a Core object
else
record = new Grad; // allocate a Grad object
record->read(cin); // virtual call
maxlen = max(maxlen, record->name().size()); // dereference
students.push_back(record);
}

// pass the version of compare that works on pointers
sort(student.begin(), student.end(), compare_Core_ptrs);

// write the names and grades
for (vector<Core*>::size_type i = 0; i != student.size(); ++i){
cout << students[i]->name()
<< string(maxlen + 1 - students[i]->name.size(), ' ');
try{
double final_grade = students[i]->grade();
streamsize prec = cout.precision();
cout << setprecision(3) << final_grade
<< setprecision(prec) << endl;
}catch(domain_error e){
cout << e.what() << endl;
}
delete student[i]; // free the object allocating when reading
}
return 0;
}
-

Virtual destructors

Above program almost works. The only problem occurs when we delete the object. When we store each pointer, we store each as Core* though they may point to a Grad. Therefore, the delete operation can only delete pointers to Core. To solve this problem, we define a virtual destructor:

-
1
2
3
4
5
class Core{
public:
virtual ~Core() {}
// as before
};
-

Now, when we execute delete students[i], the destructor that will be run depends on the type of the object to which student[i] actually points. A virtual destructor is needed any time it is possible that an object of derived type is destroyed through a pointer to base.

-

A simple handle class

The above approach does solve the problem but seems complex. Users have to manage the pointers and memory properly to avoid potential bugs. we can use the technique handle class to encapsulate the pointer to Core:

-
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
class Student_info{
public:
// constructors and copy control
Student_info(): cp(0) {}
Student_info(std::istream& is): cp(0) { read(is); }
Student_info(const Student_info&);
Student_info& operator=(const Student_info&);
~Student_info() { delete cp; }

// operations
std::istream& read(std::istream&);
std::string name() const {
if(cp) return cp->name();
else throw std::runtime_error("unitialized Student");
}
double grade() const{
if(cp) return cp->grade();
else throw std::runtime_error("unitialized Student");
}
static bool compare (const Student_info& s1, const Student_info& s2){
return s1.name() < s2.name(); }
private
Core* cp;
};
-

Now the Student_info object represents either a Core or Grad. This handle class hids the details of implementations related to pointers as used in above program, and provides an interface that is consistent with the Core and Grad. Users do not need to worry about memory management any more as all has been done by the handle class. The novelty is that we define the compare function as a static member which is associated with a class rather than a particular object.
We can call it through Student_info::compare() directly even without creating any object first. Therefore, static function member cannot access nonstatic data members of objects of the class as there is no object associated with the function and hence no members to use.

-

Reading the handle

The first constructor construct a nullptr. The second constructor constructs an object from the input stream, relying on the read function:

-
1
2
3
4
5
6
7
8
9
10
11
12
13
14
istream& Student_info::read(istream& is){
delete cp; // delete previous object, if any

char ch; // get record type
is >> ch;

if(ch == 'U'){
cp = new Core(is); // construct Core from istream
}
else{
cp = new Grad(is); // construct Grad from istream
}
return is;
}
-

The read function allocate the space and construct the right type object according to the information from input stream. It starts by freeing the existing object (if any) to which the handle object was previously bound. It is worth noting that if cp is a nullptr, we still can use delete without causing any error.

-

Copying the handle objects

It also defines the copy constuctor and assignment operator. These two operations typically need to allocate new objects and then initialize or assign values from the object from which we are copying. But the problem is how can we know the type of the object from which we are copying? The object, i.e. cp, may point to a Core or a Grad. The solution is to define a new virtual function:

-
1
2
3
4
5
6
7
class Core{
friend class Student_info;

protected:
virtual Core* clone() const { return new Core(*this); }
// as before
};
-

The clone() function creates a new object that holds copies of the values in the original. The Core doesn’t have a user-defined copy constructor but have a synthesized copy constructor which copies each member from the existing Core object into the newly created object. The member is inaccessible to users and non-derived classes. Therefore, we declare the Student_info as a friend. Then all members of the Student_info are friends of Core. The Grad class inherits this member, but will return a new Grad:

-
1
2
3
4
5
class Grad: public Core{
protected:
Grad* clone() const { return new Grad(*this); }
// as before
};
-

In general, when we redefine a member function from the base class, we keep the parameter list and the return type unchanged. However, if the base-class function returns a pointer (or reference) to a base class, then the derived-class function can return a pointer or reference to a corresponding derived class.

-

In addition to above, the derived class doesn’t inherit the friend class from the base class. In this case, it is unnecessary to declare the Student_info as the friend class of Grad due to the fact that the Student_info class never refers to Grad::clone directly instead through the virtual function defined in Core.

-

The copy constructor and assignment operator are defined as follows:

-
1
2
3
4
5
6
7
8
9
10
11
12
13
14
Student_info::Student_info(const Student_info& s): cp(0) {
if (s.cp) cp = s.cp->clone();
}

Student_info& Student_info:: operator=(const Student_info& s){
if(&s != this){
delete cp;
if(s.cp)
cp = s.cp->clone();
else
cp = 0;
}
return *this;
}
-

One may wonder that why we can access the private member cp of object s. It is because that private only restricts data access from other classes. In other words, if both objects are instances of the same class, they are allowed to access private members with each other.

-

Using the handle class

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
int main(){
vector<Student_info> students;
Student_info record;
string::size_type maxlen = 0;

// read and store the data
while (record.read(cin)){
maxlen = max(maxlen, record.name().size());
students.push_back(record);
}

// alphabetize the student records
sort(students.begin(), students.end(), Student_info::compare);

// write the names and grades
for (vector<Student_info>::size_type i = 0;
i != students.size(); ++i){
cout << students[i].name()
<< string(maxlen + 1 - students[i].name.size(), ' ');
try{
double final_grade = students[i].grade();
streamsize prec = cout.precision();
cout << setprecision(3) << final_grade
<< setprecision(prec) << endl;
}catch (domain_error e){
cout << e.what() << endl;
}
}
return 0;
}
-

Now the program takes either a undergraduate record or a graduate record. It first reads the character that says what kind of record we are about to read, then creates a new object and initializes it from input stream. Then it stores objects and deal with sorting, printing as same as the previous program. When it exits from the main, all created objects are deleted automatically through the destructor defined in the Student_info class.

-

Subtleties

We are allowed to store Core or Grad objects into a vector due to the fact that push_back function takes a reference to the vector‘s value type. But the result is that the vector only stores the Core part of a Grad object.

-

If we want to declare a virtual function, we must give it the same interface in the base and the derived classes.

- -
- - - - - - -
- -
- - - - -
- - - - -
- - -
- - - - -
- - - - - - - - -
-
- - -
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/2018/05/09/C-Managing-memory-almost-automatically/index.html b/2018/05/09/C-Managing-memory-almost-automatically/index.html deleted file mode 100644 index ba454e02..00000000 --- a/2018/05/09/C-Managing-memory-almost-automatically/index.html +++ /dev/null @@ -1,614 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - C++ - Managing memory (almost) automatically | Liam's Blog - - - - - - - - - - - - -
-
- -
-
- - -
- - -

If history repeats itself, and the unexpected always happens, how incapable must Man be of learning from experience?—George Bernard Shaw.

-
- - -
- - - - -
-
- - -
- - 0% -
- - - - -
-
-
- - -
- - -
- - - -
- - - - - -
-

- C++ - Managing memory (almost) automatically -

- - -
- - - - -
- - -

In last chapter, we write a new class named Student_info to encapsulate the pointer to Core so that we do not need to concern about the memory management. Now we’ll further improve our class by seperating the class into two classes: one is a pure interface class and the other is a single pointerlike class which manages the underlying memory. The purpose to do so is that we then can use the pointerlike class with mutiple interface classes. In addition, by doing so, we can avoid copying objects unnecessarily. So what do we mean by saying copy an object? If an object x refers to an object y, does copying x cause y to be copied too ?

-
    -
  1. if y is a memer of x, the answer must be yes
  2. -
  3. if x is nothing but a pointer to y, the answer is no.
  4. -
-

This chapter defines three versions of our pointerlike class, each of which differs from the others in how it defines copying.

-

Handles that copy their objects

It is known that pointer is a primitive, low-level data structure. Working with pointers directly may leads to severe mistakes due to the fact that pointers are independent of the objects to which they point(Koenig and Moo 2000):

-

1. Copying a pointer doesn’t copy the corresponding object, leading to surprises if two pointers inadvertently point to the same object.

-
1
2
3
4
5
int* p = new int(10);   // p: pointer to an int object that has value 10
int* q = p; // q: points to the same int object
*q = 100; // if we modify the object pointed by q
cout << *p; // we inevitably changes the object pointed by p
// the output is 100
-

2. Destroying a pointer doesn’t destroy its object, leading to memory leaks.

-
1
2
3
void nameless(size_t n){
int* p = new int[n];// local variable p is destroyed when this function
} // finishes, however, the dynamically allocated // array still exists on the heap.
-

3. Deleting an object without destroying a pointer to it leads to a dangling pointer, which causes undefined behavior if the program uses the pointer.

-
1
2
3
4
5
int* p = new int(10);   // p: pointer to an int object that has value 10
int* q = p; // q: points to the same int object
delete p; // destroy the object pointed by p
p = nullptr; // p points to nowhere now
*q = 100; // undefined behavior as the object pointed by q has been destroyed.
- -

4. Creating a pointer without initializing it leaves the pointer unbound, which also causes undefined behavior if the program uses it.

-
1
2
int* p;                 // unnitialized variable p, which is unbound to any object
*p = 100; // undefined behavior
-

The Student_info class allows us to use pointers without worrying about above problems. Now we still let Student_info to provide the interface, but makes the handle class be independent of the type of the object that it manages. The properties that our class will provide are :

-

1. A Handle is a value that refers to an object.

-

2. We can copy a Handle object.

-

3. We can test a Handle object to determine whether it is bound to another object.

-

4. We can use a Handle to trigger polymorphic behavior when it points to an object of a class that belongs to an inheritance hierarchy. That is, if we call a virtual function through our class, we want the implementation to choose the function to run dynamically, just as if we’d called the function through a real pointer.

-

Our Handle class will take over the memory management and therefore, we should attach only one Handle to any object, and we should not access the object directly through a built-in pointer. To tackle problems when using a built-in pointer,

-
    -
  1. When we copy a Handle object, we’ll make a new copy of the object so that each Handle points to its own copy, such as what the copy constructor does in the Student_info class, calling clone() to create a new object.
  2. -
  3. When we destroy a Handle, it will destroy the associated object, such as what the destructor does in the Student_info.
  4. -
  5. We allows users to create unbound Handles but we will throw an exception if the user attempts to access the object to which an unbound Handle refers. Users who want to avoid the exception can test to see whether the Handle is bound, for example, the operations defined in the Student_info class check whether the handle object was bound to a real object.
  6. -
-

A generic Handle class

Now, let’s write the Handle class:

-
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
template <class T> class Handle{
public:
Handle(): p(0) {}
Handle(const Handle& s): p(0) {
if (s.p) p = s.p->clone();
}

Handle& operator=(const Handle& rhs){
if(&rhs != this){
delete p;
p = rhs.p ? rhs.p->clone() : 0;
}
return *this;
}
~Handle() { delete p; }

Handle(T* t): p(t) { }
operator bool() const { return p; } // type conversion
T& operator*() const;
T& operator->() const;
private:
T* p;
};
-

Firstly, we observe that the Handle is a class template and can accommodate to any type. For example, Handle holds a pointer to an object of Core type.

-

The default constructor initializes the pointer to a nullptr. The copy constructor lets the Handle object refers to a newly created object that has the same value as the object pointed by the passed argument. The operator= is samilar to the copy constructor except that it destroyes the original object pointed by the Handle object. The destructor is obvious. All these four members are defined exactly the same as those defined in the Student_info class.

-

The other constructor that takes an argument lets us to bind the pointer to an actual object:

-
1
Handle<Core> student(new Grad);
-

Finally, we define three operator functions: the first one operator bool() tests the value of a Handle, and returns true if the Handle is bound to an object, and false otherwise(in fact converts the Handle type to a bool type value); The other two deine operator* and operator-> which give access to the object bound to the Handle:

-
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
template <class T>
T& Handle<T>::operator* () const{
if(p)
return *p;
else
throw runtime_error("unbound Handle");
}

template <class T>
T* Handle<T>::operator->() const {
if(p)
return p;
else
throw runtime_error("unbound Handle");
}
-

The operator* allows us to access *student.p by using *student. It yields a reference to the bound object.
The -> operator is used to access a member whose name appears in its right operand from an object named by its left operand. It returns a value that can be treated as a pointer. Therefore, if x is a value that defines operator->, then

-
1
x->y;
-

is equivalent to

-
1
(x.operator->())->y;
-

is equivalent to

-
1
x.p->y; // p is pointer data member, for example, p is Core*
-

By doing so, we can use the Handle object as if we are using a pointer to the associated object. Both operator* and operator-> yield either a reference or a pointer, through which we obtain dynamic binding. For example, if we execute student->grade(), we’re calling grade() through student.p, that is, a pointer. The particular version of grade to be run depends on the type of the object to which student.p points to. Similarly, if we execute (*student).grade(), we’re calling grade() through a reference to the object and so the implementation will decide which particular version of the function to call.

-

Using a generic Handle

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
int main(){
vector< Handle<Core> > students; // changed type
Handle<Core> record;
char ch;
string::size_type maxlen = 0;

// read and store the data
while(cin >> ch){
if (ch == 'U')
record = new Core; ( // allocate a Core object
else
record = new Grad; // allocate a Grad object
record->read(cin); // Handle<T>::->, then virtual call to read
maxlen = max(maxlen, record->name().size());
students.push_back(record);
}

// write the names and grades
for (vector< Handle<Core> >::size_type i = 0; i != students.size(); ++i){
// students[i] is a Handle, which we deference to call the function
cout << students[i]->name()
<< string(maxlen + 1 - students[i]->name().size(), ' ');
try{
double final_grade = students[i]->grade();
streamsize prec = cout.precision();
cout << setprecision(3) << final_grade
<< setprecision(prec) << endl;
}catch (domain_error e){
cout << e.what() << endl;
}
}
return 0;
}
- -

We can rewrite our Student_info class to a pure interface class:

-
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
class Student_info{
Student_info () { } // calls default constructor of Handle<Core>
Student_info (std::istream& is) { read(is); }
// no copy, assign, or destructor: they're no longer needed

std::istream& read(std::istream);

std::string name() const {
if(cp)
return cp->name();
else
throw runtime_error("uninitialized Student");
}

double grade() const {
if(cp)
return cp->grade()
else
throw runtime_error("uninitialized Student");
}
static bool compare(const Student_info& s1, const Student_info& s2){
return s1.name() < s2.name();
}
private:
Handle<Core> cp;
};

Since the **Handle** class defines constructor, copy constructor, assignment operator, as well as destructor, we do not need to define these members for our **Student_info** again. Here we need one step more, that is, to redefine the **read** function:
```c++
istream& Student_info::read(istream& is){
char ch;
is >> ch;
if(ch == 'U')
cp = new Core(is); // implicitly converts from a Core* to a Handle<Core> through Handle::Handle(T*)
else // then assigns the value from the temporary Handle object to cp
cp = new Grad(is);

return is;
}
-

when we execute the cp = new Core(is), the right-hand side creates a new Core object from input stream, which we implicitly convert to a Handle using Handle(T*) constructor. That Handle value is then assigned to cp by calling the assignment operator. The assignment constructs and destroys an extra copy of the Core object that we created. The reason behind this is that copying or assigning a Handle object always makes a new copy of the object that the Handle points to. Doing so can avoid the dangling pointer as each Handle only points to its own object, however, may also make uncessary copies like above assignment operation.

-

Reference-counted handles

This section solves above problem by providing a Handle class that does not copy the underlying object when the Handle itself is copied. To avoid danglling pointer problem, we’ll need to free that object at the point when the last Handle that points to it goes away. We’ll use a reference count to keep track of how many objects refer to another object. Each time we create a new handle that refers to our target object, we increment the reference count object, while each time a referring object goes away we decrement the reference count. Finally, when the last referring object goes away, we know that it is safe to destroy the target object.

-
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
template <class T> class Ref_handle{
public:
// manage reference count as well as pointer
Ref_handle(): refer(new size_t(1)), p(0) {}
Ref_handle(T* t): refptr(new size_t(1)), p(t) {}
Ref_handle(const Ref_handle& h): refptr(h.refptr), p(h.p){
++*refptr;
}

Ref_handle& operator=(const Ref_handle&);
~Ref_handle();

// as before
operator bool() const { return p; }
T& operator*() const {
if(p)
return *p;
throw std::runtime_error("unbound Ref_handle");
}

T* operator->() const{
if(p)
return p;
throw std::runtime_error("unbound Ref_handle");
}

private:
T* p;
size_t* refptr; // newly added
};
-

Above code shows our Ref_handle class:

-
    -
  1. we add a new pointer, refptr, to to the new handle class to track the reference count

    -
  2. -
  3. if we default construct a Ref_handle object or construct from an existed T, we initialized *refptr** to 1

    -
  4. -
  5. if we construct a Ref_handle object from another Ref_handle, we do not copy the underlying object but instead only copy the value of the pointer from the passed argument. Then, our Ref_handle object points to the same object as the passed argument. In addition, we let the refptr points to the counter object pointed by h.refptr, then increment the counter value by 1 as there is a new pointer Ref_handle.p points to the object pointed by h.p now.

    -
  6. -
  7. the assignment operator also modifies the counter object instead of copying the underlying object:

    -
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    template <class T>
    Ref_handle<T>& Ref_handle<T>::operator=(const Ref_handle& rhs){
    ++*rhs.refptr;
    // free the left-hand side, destroy pointers if appropriate
    if(--*refptr == 0){
    delete refptr;
    delete p;
    }

    // copy in values from the right-hand side
    refptr = rhs.refptr;
    p = rhs.p;
    return *this;
    }
    -

    the assignment operation typically involves obliterating the value of the left-hand side operand. If the operand is a pointer, we execute delete p to free the space occupied by the object that pointed by p, if there is no other pointers points to the same object. Therefore, we executes delete p as well as delete refptr conditional on –*refptr == 0. If –*refptr == 0 is false, we do not execute delete operation, but we still need to decrement the counter object pointed by refptr, which has been done by executing –*refptr ==0. However, we also need to avoid self-assignment and hence we increment *refptr first.

    -

    The next step is to bind our Ref_handle to the object that pointed by the passed argument. Like what the copy constructor does, we copy pointers but don’t copy the underlying object.

    -
  8. -
  9. the destructor checks whether the Ref_handle object being destroyed is the last one bound to its T object:

    -
    1
    2
    3
    4
    5
    6
    template <class T> Ref_handle<T>::~Ref_handle(){
    if (--*refptr == 0){
    delete refptr;
    delete p;
    }
    }
    - -
  10. -
-

This version of *Ref_handle class works well for classes that can share state between copies of different objects, however, cannot provide valuelike behavior like the Handle class described in last section. It does avoid needless copying, however, avoid all copying even we want to copy the underlying data. Next, we discuss how to write a Handle that let us decide when to share data.

-

Handles that let you decide when to share data

Now we write our last version of generic handle class, which not only preserves the performance of Ref_handle but also provides the valuelike behavior of Handles. In general, the new handle class, named as Ptr, will copy the object if we are about to change the contents, but only if there is another handle attached to the same object.

-
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
template <class T> class Ptr{
public:
// new member to copy the object conditionally when needed
void make_unique(){
if(*refptr != 1){
--*refptr;
refptr = new size_t(1);
p = p ? p->clone() : 0;
}
}

// the rest of the class looks like Ref_handle except for its name
Ptr(): refptr(new size_t(1)), p(0) {}
Ptr(T* t): refptr(new size_t(1)), p(t) {}
Ptr(const Ptr& h): refptr(h.refptr), p(h.p) {
++*refptr;
}

Ptr& operator=(const Ptr&);
~Ptr();
operator bool() const { return p; }
T& operator*() const;
T* operator->() const;

private:
T* p;
size_t* refptr;
};
-

This new Ptr class has the mostly same members and implementations as the Ref_handle class, except that it defines a new make_unique function. The make_unique function calls the clone() function to copy the underlying object only in the condition that the reference count is not 1. More specific, if *refptr == 1, then it means that there is no any other Ptr objects are bound to the underlying object, and hence there is no need to do a underlying copy again;but if *refptr != 1, it means that there still other Ptr(s) are bound to the underlying object, and hence it is necessary to make our Ptr points to its own object to avoid bring changes to the object pointed by other Ptr(s). If we intend to add a function that can change the contents of the underlying object, we should call make_unique to make a copy of the underlying object.

-

An improvement on controllable handles

There is one problem when we use above Ptr to deal with some classes that do not have a member function clone(). In such case, we will define an intermediary global function that we can both call and create:

-
1
2
3
4
template <class T> 
T* clone(const T* tp){
return tp->clone();
}
-

Then we can change our make_unique member to call it

-
1
2
3
4
5
6
7
8
template <class T>
void Ptr<T>::make_unique(){
if(*refptr != 1){
--*refptr;
refptr = new size_t(1);
p = p ? clone(p) : 0; // call global version of clone
}
}
-

The new clone function does’t make any change to the make_unique function and hence works well for our Student_info class. Another example, the Vec doesn’t provide a clone function, how can we define an intermediary function to let the Ptr< Vec > work?

-
1
2
3
4
template <>
Vec<char>* clone(const Vec<char>* vp){
return new Vec<char>(*vp);
}
-

The novelty here is template<>, which indicates a function is a template specialization. The template specialization means that the template is a particular version of a template function for the argument type. If we pass clone a Vec*, the compiler will use this specialized version of clone. If we pass other types of pointers, it will instantiate the general template form of clone.

- -
- - - - - - -
- -
- - - - -
- - - - -
- - -
- - - - -
- - - - - - - - -
-
- - -
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/2018/05/09/Sorting-Algorithms-C-Implementations/index.html b/2018/05/09/Sorting-Algorithms-C-Implementations/index.html deleted file mode 100644 index 52e2542f..00000000 --- a/2018/05/09/Sorting-Algorithms-C-Implementations/index.html +++ /dev/null @@ -1,543 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - Sorting Algorithms C++ Implementations-Selection Sort | Liam's Blog - - - - - - - - - - - - -
-
- -
-
- - -
- - -

If history repeats itself, and the unexpected always happens, how incapable must Man be of learning from experience?—George Bernard Shaw.

-
- - -
- - - - -
-
- - -
- - 0% -
- - - - -
-
-
- - -
- - -
- - - -
- - - - - -
-

- Sorting Algorithms C++ Implementations-Selection Sort -

- - -
- - - - -
- - -

Selection Sort

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
/*-----------------------------------------------------------------------------
* Created on: 9 May 2018 || Author: Liam
*-----------------------------------------------------------------------------
* This file contains three generic functions that implement
* the selection sort algorithm, and a test program
*
* Logical steps:
* 1. select the minimum value from the sequence
* 2. put the minimun value in the first position
* 3. ignore the first position and sort the remaining
* sequence by repetitively executing step 1 and 2
* till the last number
*
* Complexity analysis:
* time complexity: Best, Average and Worst-case = O(n^2)
* auxiliary space: worst-case = O(1)
*
*-----------------------------------------------------------------------------
*/
-

Implementations

-
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
#ifndef SORTINGALGORITHMS_H_
#define SORTINGALGORITHMS_H_

#include <cstddef>
#include <iterator>
#include <algorithm>

// built-in array-based version
template <typename T>
void SelectionSort(T* p, std::size_t n){
if(n > 0){
for(std::size_t i = 0; i != n-1; ++i){
std::size_t imin = i;
for(std::size_t j = imin+1; j != n; ++j){
if(p[i] > p[j])
imin = j;
}
T temp = p[i];
p[i] = p[imin];
p[imin] = temp;
}
}
}

// iterator-based version STL style (C++11)
template <typename ForwardIterator>
void SelectionSort(ForwardIterator begin, ForwardIterator end){
for (; begin != end; ++begin){
auto imin = std::min_element(begin, end);
if(imin != begin)
std::iter_swap(begin, imin);
}
}

// iterator-based version with comparator
template <typename ForwardIterator, typename Comparator>
void SelectionSort(ForwardIterator begin, ForwardIterator end,
Comparator comp){
for (; begin != end; ++begin){
auto imin = std::min_element(begin, end, comp);
if(imin != begin)
std::iter_swap(begin, imin);
}
}
#endif /* SORTINGALGORITHMS_H_ */
- -

Test Program-main.cpp

-
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
65
66
67
68
69
70
71
#include <iostream>	// std::cout, endl
#include <string> // std::string
#include <list> // std::list
#include <vector> // std::vector
#include "SortingAlgorithms.h"

using std::cout; using std::cin;
using std::endl; using std::list;
using std::vector; using std::string;

// struct defined for testing
struct student{
string name;
int age;
};

// comparator 1
template<class T>
bool compare_age(const T& x, const T& y){
return x.age < y.age;
}

// comparator 2
template<class T>
bool compare_name(const T& x, const T& y){
return x.name < y.name;
}

int main(){
// test 1: array-based version
double arr[10] = {10, 9, 8, 7, 6, 5, 4, 3, 2, 1};
SelectionSort(arr, arr+ 10);
cout << "Sorted array: ";
for(int i = 0; i != 10; ++i)
cout << arr[i] << " ";
cout << "\n";

// test 2: iterator-based version
string str("eclipseworkspace");
SelectionSort(str.begin(), str.end());
cout << "Sorted string " << str << "\n";

list<int> l{10, 9, 8, 7, 6, 5, 4, 3, 2, 1};
SelectionSort(l.begin(), l.end());
cout << "Sorted list: ";
for (auto i: l)
cout << i << " ";

cout << endl;

// test 3: user-defined comparator
vector<student> students;
while(cin){
student temp;
cin >> temp.name >> temp.age;
if(cin)
students.push_back(temp);
}

SelectionSort(students.begin(), students.end(), compare_age<student>);
cout << "Sorted vector according to age:\n";
for (auto i: students)
cout << i.name << " " << i.age << "\n";

SelectionSort(students.begin(), students.end(), compare_name<student>);
cout << "Sorted vector according to name:\n";
for (auto i: students)
cout << i.name << " " << i.age << "\n";

return 0;
}
-

Outputs:

-
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
Sorted array: 1 2 3 4 5 6 7 8 9 10 
Sorted string acceeeiklopprssw
Sorted list: 1 2 3 4 5 6 7 8 9 10
John 18
Mike 16
Liam 23
Anna 26
Bobo 21
Sorted vector according to age:
Mike 16
John 18
Bobo 21
Liam 23
Anna 26
Sorted vector according to name:
Anna 26
Bobo 21
John 18
Liam 23
Mike 16
-
- - - - - - -
- -
- - - - -
- - - - -
- - -
- - - - -
- - - - - - - - -
-
- - -
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/2018/05/14/C-Revisiting-character-pictures/index.html b/2018/05/14/C-Revisiting-character-pictures/index.html deleted file mode 100644 index 97da392b..00000000 --- a/2018/05/14/C-Revisiting-character-pictures/index.html +++ /dev/null @@ -1,535 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - C++ - Revisiting character pictures | Liam's Blog - - - - - - - - - - - - -
-
- -
-
- - -
- - -

If history repeats itself, and the unexpected always happens, how incapable must Man be of learning from experience?—George Bernard Shaw.

-
- - -
- - - - -
-
- - -
- - 0% -
- - - - -
-
-
- - -
- - -
- - - -
- - - - - -
-

- C++ - Revisiting character pictures -

- - -
- - - - -
- - - -
- - - - - - -
- -
- - - - -
- - - - -
- - -
- - - - -
- - - - - - - - -
-
- - -
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/2018/05/14/Sorting-Algorithms-C-Implementations-Bubble-Sort/index.html b/2018/05/14/Sorting-Algorithms-C-Implementations-Bubble-Sort/index.html deleted file mode 100644 index 75ada6fe..00000000 --- a/2018/05/14/Sorting-Algorithms-C-Implementations-Bubble-Sort/index.html +++ /dev/null @@ -1,537 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - Sorting Algorithms C++ Implementations - Bubble Sort | Liam's Blog - - - - - - - - - - - - -
-
- -
-
- - -
- - -

If history repeats itself, and the unexpected always happens, how incapable must Man be of learning from experience?—George Bernard Shaw.

-
- - -
- - - - -
-
- - -
- - 0% -
- - - - -
-
-
- - -
- - -
- - - -
- - - - - -
-

- Sorting Algorithms C++ Implementations - Bubble Sort -

- - -
- - - - -
- - -

Bubble Sort

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
/*-----------------------------------------------------------------------------
* main.cpp || Created on: 13 May 2018 || Author: Liam
*-----------------------------------------------------------------------------
* this program tests the bubble sort algorithm implemented in three ways
*
* Logic:
* 1. each time find the largest number of the unsorted sequence and put it at
* the last position of the sequence. Then the unsorted sequence becomes the
* sequence, which is the original sequence excludes the last element.
* * compare adjacent numbers one pair by one pair from the begining
* position to the second last position, and exchange the position of
* two numbers if left-side number is larger than the right-side number
* * after each iteration, right-most position of the unsorted sequence
* holds the largest element, and hence the unsorted sequence becomes
* the sequence, which is the original sequence excludes the last
* element.
* 2. repectively perform step 1. There are two cases that the iteration stops:
* * the unsorted sequence only has one number left
* * there is no any exchange happens in last iteration, which means that
* all elements are in order already. Therefore, we do not need to
* perform step 1 again. In this case, we get the best time complexity
* if the sequence is completely sorted after the first iteration.
*
* Complexity analysis:
* time complexity: Best = O(n), Average, Worst = O(n^2)
* auxiliary space: worst-case = O(1)
*-----------------------------------------------------------------------------
*/
- -

Implementations

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
 #ifndef SORTINGALGORITHMS_H_
#define SORTINGALGORITHMS_H_

#include <cstddef>
#include <algorithm>
#include <iterator>

// array based version
template <typename T>
void BubbleSort(T* p, std::size_t n){
if (n == 0) return;
for(std::size_t i = n-1; i != 0; --i){
bool flag = true;
for(std::size_t j = 0; j != i; ++j){
if(p[j] > p[j+1]){
T temp = p[j];
p[j] = p[j+1];
p[j+1] = temp;
flag = false;
}
}
if(flag == true) break;
}
}

// iterator based version STL style (c++11)
template <typename BidirectionalIterator>
void BubbleSort(BidirectionalIterator begin, BidirectionalIterator end){
if(begin == end) return;
for(auto endIter = std::prev(end); endIter != begin; --endIter){
bool flag = true;
for(auto iter = begin; iter != endIter; ++iter){
auto iterNext = std::next(iter);
if(*iter > *iterNext){
std::iter_swap(iter, iterNext);
flag = false;
}
}
if(flag == true) break;
}
}

// iterator based version with user-defined comparator
template <typename BidirectionalIterator, typename Comparator>
void BubbleSort(BidirectionalIterator begin, BidirectionalIterator end,
Comparator comp){
if(begin == end) return;
for(auto endIter = std::prev(end); endIter != begin; --endIter){
bool flag = true;
for(auto iter = begin; iter != endIter; ++iter){
auto iterNext = std::next(iter);
if(comp(*iterNext, *iter)){
std::iter_swap(iter, iterNext);
flag = false;
}
}
if(flag == true) break;
}
}

#endif /* SORTINGALGORITHMS_H_ */

-

Test Program-main.cpp

-
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
65
66
67
68
69
70
71
#include <iostream>
#include <vector>
#include <string>
#include <list>
#include "SortingAlgorithms.h"

using std::cout; using std::vector;
using std::endl; using std::string;
using std::list; using std::cin;

// struct defined for testing
struct student{
string name;
int age;
};

// comparator 1
template<class T>
bool compare_age(const T& x, const T& y){
return x.age < y.age;
}

// comparator 2
template<class T>
bool compare_name(const T& x, const T& y){
return x.name < y.name;
}

int main(){
// test 1: array-based version
double arr[10] = {10, 9, 8, 7, 6, 5, 4, 3, 2, 1};
BubbleSort(arr, arr+ 10);
cout << "Sorted array: ";
for(int i = 0; i != 10; ++i)
cout << arr[i] << " ";
cout << "\n";

// test 2: iterator-based version
string str("eclipseworkspace");
BubbleSort(str.begin(), str.end());
cout << "Sorted string " << str << "\n";

list<int> l{10, 9, 8, 7, 6, 5, 4, 3, 2, 1};
BubbleSort(l.begin(), l.end());
cout << "Sorted list: ";
for (auto i: l)
cout << i << " ";

cout << endl;

// test 3: user-defined comparator
vector<student> students;
while(cin){
student temp;
cin >> temp.name >> temp.age;
if(cin)
students.push_back(temp);
}

BubbleSort(students.begin(), students.end(), compare_age<student>);
cout << "Sorted vector according to age:\n";
for (auto i: students)
cout << i.name << " " << i.age << "\n";

BubbleSort(students.begin(), students.end(), compare_name<student>);
cout << "Sorted vector according to name:\n";
for (auto i: students)
cout << i.name << " " << i.age << "\n";

return 0;
}
- -

Outputs:

-
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
Sorted array: 1 2 3 4 5 6 7 8 9 10 
Sorted string acceeeiklopprssw
Sorted list: 1 2 3 4 5 6 7 8 9 10
John 18
Mike 16
Liam 23
Anna 26
Bobo 21
Sorted vector according to age:
Mike 16
John 18
Bobo 21
Liam 23
Anna 26
Sorted vector according to name:
Anna 26
Bobo 21
John 18
Liam 23
Mike 16
-
- - - - - - -
- -
- - - - -
- - - - -
- - -
- - - - -
- - - - - - - - -
-
- - -
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/2018/05/15/Sorting-Algorithms-C-Implementations-Insertion-Sort/index.html b/2018/05/15/Sorting-Algorithms-C-Implementations-Insertion-Sort/index.html deleted file mode 100644 index 563aaf3d..00000000 --- a/2018/05/15/Sorting-Algorithms-C-Implementations-Insertion-Sort/index.html +++ /dev/null @@ -1,546 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - Sorting Algorithms C++ Implementations - Insertion Sort | Liam's Blog - - - - - - - - - - - - -
-
- -
-
- - -
- - -

If history repeats itself, and the unexpected always happens, how incapable must Man be of learning from experience?—George Bernard Shaw.

-
- - -
- - - - -
-
- - -
- - 0% -
- - - - -
-
-
- - -
- - -
- - - -
- - - - - -
-

- Sorting Algorithms C++ Implementations - Insertion Sort -

- - -
- - - - -
- - -

Insertion Sort

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
/*----------------------------------------------------------------------------- 
* main.cpp || Created on: 15 May 2018 || Author: Liam
*-----------------------------------------------------------------------------
* this program tests three versions of insertion sort implementations.
*
* Logic:
* 1. the sequence can always be divided into two parts: sorted and unsorted.
* We loop thru from the second position to the last position, before each
* loop, the elements on the left-side of the position are sorted. we can
* call this position as sortedIndex. After each loop, [0, sortedIndex] is
* sorted, and then we forward the sortedIndex 1 position.
* 2. in each loop, an embeded iteration starts from the initial position to
* the prev of the sortedIndex, or in reverse order, from the prev of the
* sortedIndex to the begining, comparing each value denoted in the range
* with the value denoted by sortedIndex. When found the first element that
* is greater than the value denoted by sortedIndex, we insert here
* the element denoted by sortedIndex.
*
* Complexity analysis:
* time complexity: Best = O(n), Average, Worst O(n^2)
* auxiliary space: worst-case = O(1)
*
*-----------------------------------------------------------------------------
*/
- -

Implementations

-
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
#ifndef SORTINGALGORITHMS_H_
#define SORTINGALGORITHMS_H_

#include <cstddef>
#include <algorithm>
#include <iterator>

// array-based version
template <class T>
void InsertionSort(T* p, std::size_t n){
if (n == 0) return;
for(std::size_t i = 1; i != n; ++i){
T value = p[i];
std::size_t sortedIndex = i;
// the left-side of the sortedIndex is sorted
while(sortedIndex >0 && p[sortedIndex - 1] > value){
p[sortedIndex] = p[sortedIndex - 1];
sortedIndex = sortedIndex - 1;
}
p[sortedIndex] = value;
}
}

// iterator-based version STL-stype (C++11)
template <typename ForwardIterator>
void InsertionSort(ForwardIterator begin, ForwardIterator end){
if(begin == end) return;
for (auto iter = std::next(begin); iter != end; ++iter){
auto insertPoint = std::upper_bound(begin, iter, *iter); // logN
std::rotate(insertPoint, iter, std::next(iter)); // N
}
}

// iterator-based version with comparator
template <typename ForwardIterator, typename Comparator>
void InsertionSort(ForwardIterator begin, ForwardIterator end,
Comparator comp){
if(begin == end) return;
for (auto iter = std::next(begin); iter != end; ++iter){
auto insertPoint = std::upper_bound(begin, iter, *iter, comp);
std::rotate(insertPoint, iter, std::next(iter));
}
}

#endif /* SORTINGALGORITHMS_H_ */
- -

Test program-main.cpp

-
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
65
66
67
68
69
70
71
#include <iostream> // std::cin, cout, endl
#include <vector> // std::vector
#include <string> // std::string
#include <list> // std::list
#include "SortingAlgorithms.h"

using std::cout; using std::vector;
using std::endl; using std::string;
using std::list; using std::cin;

// struct defined for testing
struct student{
string name;
int age;
};

// comparator 1
template<class T>
bool compare_age(const T& x, const T& y){
return x.age < y.age;
}

// comparator 2
template<class T>
bool compare_name(const T& x, const T& y){
return x.name < y.name;
}

int main(){
// test 1: array-based version
double arr[10] = {10, 9, 8, 7, 6, 5, 4, 3, 2, 1};
InsertionSort(arr, arr+ 10);
cout << "Sorted array: ";
for(int i = 0; i != 10; ++i)
cout << arr[i] << " ";
cout << "\n";

// test 2: iterator-based version
string str("eclipseworkspace");
InsertionSort(str.begin(), str.end());
cout << "Sorted string " << str << "\n";

list<int> l{10, 9, 8, 7, 6, 5, 4, 3, 2, 1};
InsertionSort(l.begin(), l.end());
cout << "Sorted list: ";
for (auto i: l)
cout << i << " ";

cout << endl;

// test 3: user-defined comparator
vector<student> students;
while(cin){
student temp;
cin >> temp.name >> temp.age;
if(cin)
students.push_back(temp);
}

InsertionSort(students.begin(), students.end(), compare_age<student>);
cout << "Sorted vector according to age:\n";
for (auto i: students)
cout << i.name << " " << i.age << "\n";

InsertionSort(students.begin(), students.end(), compare_name<student>);
cout << "Sorted vector according to name:\n";
for (auto i: students)
cout << i.name << " " << i.age << "\n";

return 0;
}
- - -

Outputs

-
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
Sorted array: 1 2 3 4 5 6 7 8 9 10 
Sorted string acceeeiklopprssw
Sorted list: 1 2 3 4 5 6 7 8 9 10
John 18
Mike 16
Liam 23
Anna 26
Bobo 21
Sorted vector according to age:
Mike 16
John 18
Bobo 21
Liam 23
Anna 26
Sorted vector according to name:
Anna 26
Bobo 21
John 18
Liam 23
Mike 16
-
- - - - - - -
- -
- - - - -
- - - - -
- - -
- - - - -
- - - - - - - - -
-
- - -
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/2018/05/17/Sorting-Algorithms-C-Implementations-Merge-Sort/index.html b/2018/05/17/Sorting-Algorithms-C-Implementations-Merge-Sort/index.html deleted file mode 100644 index cbc37215..00000000 --- a/2018/05/17/Sorting-Algorithms-C-Implementations-Merge-Sort/index.html +++ /dev/null @@ -1,539 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - Sorting Algorithms C++ Implementations - Merge Sort | Liam's Blog - - - - - - - - - - - - -
-
- -
-
- - -
- - -

If history repeats itself, and the unexpected always happens, how incapable must Man be of learning from experience?—George Bernard Shaw.

-
- - -
- - - - -
-
- - -
- - 0% -
- - - - -
-
-
- - -
- - -
- - - -
- - - - - -
-

- Sorting Algorithms C++ Implementations - Merge Sort -

- - -
- - - - -
- - -

Merge Sort

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
/*----------------------------------------------------------------------------- 
* main.cpp || Created on: 16 May 2018 || Author: Liam
*-----------------------------------------------------------------------------
* this program tests three implementation of the merge sort algorithm.
*
* Logic:
* 1. recursively partition the sequence a mid point, until that there is only
* one element left, that is when it cannot be partitioned further.
* 2. each pair of partitioned parts will be rearranged in non-decreasing order.
*
* Complexity analysis:
* time complexity: Best, Average, Worst = O(nlogn)
* auxiliary space: worst-case = O(n)
*
*-----------------------------------------------------------------------------
*/
- -

Implementations:

-
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
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
#ifndef SORTINGALGORITHMS_H_
#define SORTINGALGORITHMS_H_

#include <cstddef>
#include <vector>
#include <iterator>
#include <algorithm>

// array-based version
template <typename T>
void Merge(T* left, std::size_t left_size, T* right, std::size_t right_size){
T left_copy[left_size];
T right_copy[right_size];

/* O(n): n = left_size + right_size */
for(std::size_t i = 0; i != left_size; ++i)
left_copy[i] = left[i];
for(std::size_t i = 0; i != right_size; ++i)
right_copy[i] = right[i];

/* O(n): n = left_size + right_size */
std::size_t i, j, k;
i = j = k = 0;
while(i != left_size && j != right_size){
if(left_copy[i] <= right_copy[j]){
left[k] = left_copy[i];
++i;
}else{
left[k] = right_copy[j];
++j;
}
++k;
}

while(i != left_size){
left[k] = left_copy[i];
++i;
++k;
}

while(j != right_size){
left[k] = right_copy[j];
++j;
++k;
}
}

template <typename T>
void MergeSort(T* p, std::size_t n){
std::size_t mid = n/2;
if(mid == 0) return;

MergeSort(p, mid);
MergeSort(p + mid, n - mid);

/* O(n): n = n*/
Merge(p, mid, p + mid, n - mid);
}

// iterator-based version STL style (C++11)
template <typename ForwardIterator>
void Merge(ForwardIterator begin, ForwardIterator midIterator,
ForwardIterator end){
typedef typename std::iterator_traits<ForwardIterator>::value_type Type;

/* O(n), n = end - begin*/
std::vector<Type> left(begin, midIterator);
std::vector<Type> right(midIterator, end);

auto iter_l = left.begin();
auto iter_r = right.begin();

/* O(n), n = end - begin*/
while(iter_l != left.end() && iter_r != right.end()){
*begin++ = *iter_l <= *iter_r ? *iter_l++ : *iter_r++;
}

std::copy(iter_l, left.end(), begin);
std::copy(iter_r, right.end(), begin);
}

template <typename ForwardIterator>
void MergeSort(ForwardIterator begin, ForwardIterator end){

// O(1) for random access iterator
// O(n) for others: n = end - begin
auto mid = std::distance(begin, end)/2;
if(mid == 0) return;

// O(1) for random access iterator
// O(n) for others: n = end - begin
auto midIterator = std::next(begin, mid);
MergeSort(begin, midIterator);
MergeSort(midIterator, end);

// O(n): n = end - begin
Merge(begin, midIterator, end);
}

// iterator-based version with user-defined comparator
template <typename ForwardIterator, typename Comparator>
void Merge(ForwardIterator begin, ForwardIterator midIterator,
ForwardIterator end, Comparator comp){
typedef typename std::iterator_traits<ForwardIterator>::value_type Type;

/* O(n), n = end - begin*/
std::vector<Type> left(begin, midIterator);
std::vector<Type> right(midIterator, end);

auto iter_l = left.begin();
auto iter_r = right.begin();

/* O(n), n = end - begin*/
while(iter_l != left.end() && iter_r != right.end()){
*begin++ = comp(*iter_l, *iter_r) ? *iter_l++ : *iter_r++;
}

std::copy(iter_l, left.end(), begin);
std::copy(iter_r, right.end(), begin);

}

template <typename ForwardIterator, typename Comparator>
void MergeSort(ForwardIterator begin, ForwardIterator end, Comparator comp){

// O(1) for random access iterator
// O(n) for others: n = end - begin
auto mid = std::distance(begin, end)/2;
if(mid == 0) return;

// O(1) for random access iterator
// O(n) for others: n = end - begin
auto midIterator = std::next(begin, mid);
MergeSort(begin, midIterator, comp);
MergeSort(midIterator, end, comp);

// O(n): n = end - begin
Merge(begin, midIterator, end, comp);
}

#endif /* SORTINGALGORITHMS_H_ */
- -

Test program-main.cpp

-
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
65
66
67
68
69
70
71
#include <iostream>	// std::cout, endl
#include <string> // std::string
#include <list> // std::list
#include <vector> // std::vector
#include "SortingAlgorithms.h"

using std::cout; using std::cin;
using std::endl; using std::list;
using std::vector; using std::string;

// struct defined for testing
struct student{
string name;
int age;
};

// comparator 1
template<class T>
bool compare_age(const T& x, const T& y){
return x.age <= y.age;
}

// comparator 2
template<class T>
bool compare_name(const T& x, const T& y){
return x.name <= y.name;
}

int main(){
// test 1: array-based version
double arr[10] = {10, 9, 8, 7, 6, 5, 4, 3, 2, 1};
MergeSort(arr, 10);
cout << "Sorted array: ";
for(int i = 0; i != 10; ++i)
cout << arr[i] << " ";
cout << "\n";

// test 2: iterator-based version
string str("eclipseworkspace");
MergeSort(str.begin(), str.end());
cout << "Sorted string " << str << "\n";

list<int> l{10, 9, 8, 7, 6, 5, 4, 3, 2, 1};
MergeSort(l.begin(), l.end());
cout << "Sorted list: ";
for (auto i: l)
cout << i << " ";

cout << endl;

// test 3: user-defined comparator
vector<student> students;
while(cin){
student temp;
cin >> temp.name >> temp.age;
if(cin)
students.push_back(temp);
}

MergeSort(students.begin(), students.end(), compare_age<student>);
cout << "Sorted vector according to age:\n";
for (auto i: students)
cout << i.name << " " << i.age << "\n";

MergeSort(students.begin(), students.end(), compare_name<student>);
cout << "Sorted vector according to name:\n";
for (auto i: students)
cout << i.name << " " << i.age << "\n";

return 0;
}
- -

Outputs:

-
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
Sorted array: 1 2 3 4 5 6 7 8 9 10 
Sorted string acceeeiklopprssw
Sorted list: 1 2 3 4 5 6 7 8 9 10
John 18
Mike 16
Liam 23
Anna 26
Bobo 21
Sorted vector according to age:
Mike 16
John 18
Bobo 21
Liam 23
Anna 26
Sorted vector according to name:
Anna 26
Bobo 21
John 18
Liam 23
Mike 16
-
- - - - - - -
- -
- - - - -
- - - - -
- - -
- - - - -
- - - - - - - - -
-
- - -
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/2018/05/18/Sorting-Algorithms-C-Implementations-Quick-Sort/index.html b/2018/05/18/Sorting-Algorithms-C-Implementations-Quick-Sort/index.html deleted file mode 100644 index f02f400d..00000000 --- a/2018/05/18/Sorting-Algorithms-C-Implementations-Quick-Sort/index.html +++ /dev/null @@ -1,537 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - Sorting Algorithms C++ Implementations - Quick Sort | Liam's Blog - - - - - - - - - - - - -
-
- -
-
- - -
- - -

If history repeats itself, and the unexpected always happens, how incapable must Man be of learning from experience?—George Bernard Shaw.

-
- - -
- - - - -
-
- - -
- - 0% -
- - - - -
-
-
- - -
- - -
- - - -
- - - - - -
-

- Sorting Algorithms C++ Implementations - Quick Sort -

- - -
- - - - -
- - -

Quick Sort

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
/*----------------------------------------------------------------------------- 
* main.cpp || Created on: 18 May 2018 || Author: Liam
*-----------------------------------------------------------------------------
* this program tests three implementations of the quick sort algorithm
*
* Logic:
* 1. select an element from the sequence as the pivot and rearrange the
* sequence such that all elements less than the pivot are towards the left
* of it and all elements greater than the pivot are towards the right of it.
* 2. above process is called partitioning of the sequence. we recursively
* partition the sequence till that there is only one element left in each
* segment.
* 3. for convenience, we can always select the last element as the pivot. If
* so, we may encounter the case that the position of the pivot
* is still at the end (or begining) of the rearranged sequence. Such case is
* the worst case and the time complexity is O(n^). To achieve an average
* case, in which the time complexity is O(nlogn), we can randomly select
* the pivot and then put the pivot at the end of the sequence for the
* rearangement.
*
* Complexity analysis:
* time complexity:
*
* Best case: always be balanced at the midpoint in each partition
* T(n) = 2T(n/2) + cn
* = 2^k T(n/2^k) + kcn
* where k = logn
* therefore = O(nlogn)
*
* Average case: when randomly selected as any one of the position,
* the partition index or iterator is an average index
* T(n) = T(n - i) + T(i - 1) + cn
* = 1/n * summation(T(n - i) + T(i - 1)) + cn
* = O(nlogn)
*
* Worst case: unblanced in each partition
* T(n) = T(n - 1) + cn
* = T(n - 2) + c(n - 1) + cn
* = T(n - k) + c(n - k + 1 + ... + n)
* where k = n-1
* therefore
* T(n) = T(1) + c*(2 + 3 + ... + n)
* = O(n^2)
*
* auxiliary space: (non-stable) worst-case = O(1)
*-----------------------------------------------------------------------------
*/
- -

Implementations

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
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
 #ifndef SORTINGALGORITHMS_H_
#define SORTINGALGORITHMS_H_

#include <ctime>
#include <cstddef>
#include <cstdlib>
#include <iterator>
#include <stdexcept>
#include <algorithm>
#include <iostream>

// array-based version, first always is the index to indicate the
// first position of a sequence, last is the index to indicate the
// last position of a sequence. partitionIndex is the split point
template <typename T>
std::size_t Partition(T*p, const std::size_t first, const std::size_t last){
T pivot = p[last];
std::size_t partitionIndex = first;
for(std::size_t i = first; i != last; ++i){
if(p[i] <= pivot){
T temp = p[i];
p[i] = p[partitionIndex];
p[partitionIndex] = temp;
++partitionIndex;
}
}
T temp = p[partitionIndex];
p[partitionIndex] = p[last];
p[last] = temp;
return partitionIndex;
}

template <typename T>
void QuickSort(T*p, const std::size_t first, const std::size_t last){
if(first >= last) return;
std::size_t partitionIndex = Partition(p, first, last);
if(partitionIndex != 0){
QuickSort(p, first, partitionIndex - 1);
}
QuickSort(p, partitionIndex + 1, last);
}

// iterator-based version STL style (C++11)
// partitionIter denotes the position of the
// split point
template<typename BidirrectionalIterator>
BidirrectionalIterator Partition(BidirrectionalIterator begin,
BidirrectionalIterator end){
auto pivot = std::prev(end);
auto partitionIter = begin;

for(auto iter = begin; iter != pivot; ++iter){
if(*iter <= *pivot){
std::iter_swap(iter, partitionIter);
++partitionIter;
}
}
std::iter_swap(partitionIter, pivot);
return partitionIter;
}

template <typename BidirrectionalIterator>
void QuickSort(BidirrectionalIterator begin, BidirrectionalIterator end){
if(std::distance(begin, end) <= 1) return;
auto partitionIter = Partition(begin, end);
if(std::prev(partitionIter) != begin){
QuickSort(begin, partitionIter);
}
QuickSort(std::next(partitionIter), end);
}

// user-defined comparator + improved using random selected pivot
// return a random integer in the range [0, n)
int nrand(int n)
{
if(n <= 0 || n > RAND_MAX)
throw std::domain_error("Argument to nrand is out of range");

const int bucket_size = RAND_MAX /n;
int r;

do r = rand() / bucket_size;
while(r >= n);

return r;
}

template<typename BidirrectionalIterator, typename Comparator>
BidirrectionalIterator Partition(BidirrectionalIterator begin,
BidirrectionalIterator end, Comparator comp){
std::srand (std::time(nullptr));
auto randomIndex = nrand(std::distance(begin, end));
std::cout << randomIndex << "nihao" << std::endl;
auto pivot = std::prev(end);
std::iter_swap(pivot, std::next(begin, randomIndex));

auto partitionIter = begin;
for(auto iter = begin; iter != pivot; ++iter){
if(comp(*iter, *pivot)){
std::iter_swap(iter, partitionIter);
++partitionIter;
}
}
std::iter_swap(partitionIter, pivot);
return partitionIter;
}

template <typename BidirrectionalIterator, typename Comparator>
void QuickSort(BidirrectionalIterator begin, BidirrectionalIterator end,
Comparator comp){
if(std::distance(begin, end) <= 1) return;
auto partitionIter = Partition(begin, end, comp);
if(std::prev(partitionIter) != begin){
QuickSort(begin, partitionIter, comp);
}
QuickSort(std::next(partitionIter), end, comp);
}

#endif /* SORTINGALGORITHMS_H_ */

-

Test program-main.cpp

-
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
65
66
67
68
69
70
71
#include <iostream>	// std::cout, endl
#include <string> // std::string
#include <list> // std::list
#include <vector> // std::vector
#include "SortingAlgorithms.h"

using std::cout; using std::cin;
using std::endl; using std::list;
using std::vector; using std::string;

// struct defined for testing
struct student{
string name;
int age;
};

// comparator 1
template<class T>
bool compare_age(const T& x, const T& y){
return x.age < y.age;
}

// comparator 2
template<class T>
bool compare_name(const T& x, const T& y){
return x.name < y.name;
}

int main(){
// test 1: array-based version
double arr[10] = {10, 9, 8, 7, 6, 5, 4, 3, 2, 1};
QuickSort(arr, 0, 9);
cout << "Sorted array: ";
for(int i = 0; i != 10; ++i)
cout << arr[i] << " ";
cout << "\n";

// test 2: iterator-based version
string str("eclipseworkspace");
QuickSort(str.begin(), str.end());
cout << "Sorted string " << str << "\n";

list<int> l{10, 9, 8, 7, 6, 5, 4, 3, 2, 1};
QuickSort(l.begin(), l.end());
cout << "Sorted list: ";
for (auto i: l)
cout << i << " ";

cout << endl;

// test 3: user-defined comparator
vector<student> students;
while(cin){
student temp;
cin >> temp.name >> temp.age;
if(cin)
students.push_back(temp);
}

QuickSort(students.begin(), students.end(), compare_age<student>);
cout << "Sorted vector according to age:\n";
for (auto i: students)
cout << i.name << " " << i.age << "\n";

QuickSort(students.begin(), students.end(), compare_name<student>);
cout << "Sorted vector according to name:\n";
for (auto i: students)
cout << i.name << " " << i.age << "\n";

return 0;
}
- -

Outputs:

-
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
Sorted array: 1 2 3 4 5 6 7 8 9 10 
Sorted string acceeeiklopprssw
Sorted list: 1 2 3 4 5 6 7 8 9 10
John 18
Mike 16
Liam 23
Anna 26
Bobo 21
2nihao
1nihao
1nihao
Sorted vector according to age:
Mike 16
John 18
Bobo 21
Liam 23
Anna 26
2nihao
1nihao
Sorted vector according to name:
Anna 26
Bobo 21
John 18
Liam 23
Mike 16
-
- - - - - - -
- -
- - - - -
- - - - -
- - -
- - - - -
- - - - - - - - -
-
- - -
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/2018/05/19/C-Implementations-Singly-Linked-List/index.html b/2018/05/19/C-Implementations-Singly-Linked-List/index.html deleted file mode 100644 index 60dc8a10..00000000 --- a/2018/05/19/C-Implementations-Singly-Linked-List/index.html +++ /dev/null @@ -1,645 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - C++ Implementations: Singly Linked List | Liam's Blog - - - - - - - - - - - - -
-
- -
-
- - -
- - -

If history repeats itself, and the unexpected always happens, how incapable must Man be of learning from experience?—George Bernard Shaw.

-
- - -
- - - - -
-
- - -
- - 0% -
- - - - -
-
-
- - -
- - -
- - - -
- - - - - -
-

- C++ Implementations: Singly Linked List -

- - -
- - - - -
- - -

Logical View

How to efficiently store a sequence of values (aka. a List) of a given type is crucial for any non-trivial program due to the limited memory. The way we store or organsize data introduces an important concept, that is, data structures. A data structure is proposed to manage data in a specific way so that we can use it efficiently according to specific needs.

-

In C++/C, the most common facility we use is so called built-in array, which stores a given number of elements in a contiguous memory block. All elements are stored in certain order indicated by their addresses. When we want to access any one of the elements, we pass the order of that element to the computer. Then the computer will calculate the address of that element based on the initial address of the array, and then return us the associated value. Since the addresses are contiguous and the size of memory occupied by the given type is fixed, we can always get any element in constant time: there is only one arithmetic operation needed. Naturally, we can modify any one of elements in constant time as well. However, the shortingcoming is obvious: an array has fixed length. There are two ways to solve this problem, one is that we can preset a large enough size for the array, however, doing so is very inefficient in terms of memory usage. Another way is to dynamically allocated an array when it is needed. However, doing so possibly invalidates all pointers/iterators as all elements are moved to a new allocated storage, which is also very inefficient in terms of time cost(O(n)). Even if the allocated storage is sufficient, any modifications of the array, such as, insert or delete element, also invalidates parts or all of the pointers/iterators, and hence these operations have high time cost: O(n).

-

An alternative data structure is named Linked List, which stores data in a noncontinuous memory space through a manner that each element are connected and ordered by pointers. More specific, an element of a singly linked list is an single object (aka. Linked Node) that contains two members: one is the data stored in the object, and the other is the address (i.e. a pointer variable) of the next element.

-

Basic Operations

The entrance of a singly linked list is a pointer to the Head Node, i.e. the first Linked Node. If the linked list is empty, the pointer to the Head Node is a nullptr. To access one element, we have to start from the Head Node and move one Node by one Node. Therefore, unlike the array, the time to access elements of the linked list is proportational to the size of the linked list, that is, the time complexity is O(n). Naturally if we want to insert or delete one element into/from the list, we have to find the position or the specific element first. The time complexity is also O(n). One advantage compared to the array is that it doesn’t worry about the size of the storage. We do not need to preset the length of the list and hence no extra memory is wasted. But it does need extra memory for storing the pointer variable in each Linked Node. Another advantage is that the operations such as insert or delete doesn’t change other elements, which is crucial for us when we manipulate an object by pointers or iterators. Now let’s briefly summarize these two elementary data structures: Array and Linked List.

- - - - - - - - - - - - - - - - - - - - - - - - - - - - -
ArrayLinked List
Cost of accessing an element of an elementO(1)O(n)
Memory requirementFixed size: used memory and unused memoryNo unused memory but need extra memory for pointer variables
Cost of Inserting or deleting an elementInserting at beging: O(n); Insering at the end: O(1) if there exists unused memory but O(n) is the array is full; Inserting at middle: O(n)Inserting at begining:O(1); Inserting at the end: O(n); Inserting at middle: O(n)
Source:mycodeschool
-

From above table, we can see that whether to use an array or a linked list depends on that what is the most frequent operation the program performs and what is the size of the data structure.

-

Implementations

To define a abstract data type based on the singly linked list, the first step is to write the Linked Node:

-
1
2
3
4
5
template <typename T>
struct Node{
T data;
Node* next;
};
- -

The Node struct is templated for satisfying different underlying types. It contains two items: one is named data which for storing values of T type and ther other named next which is a pointer to a Node object. Next we define the class type based on the singly linked list model described above. In fact, we can incorporate the Node type into our singly linked list class as a private member for the purpose of hidding the implementation details. As mentioned above, the only information we know is the pointer to the Head Node, therefore, I declare a private member ptrToHead to indicate the address of the Head Node. Also, I define a unsigned integer to count the number of elements stored in a linked list. For the sake of convenience, I write a private function create to create a Node with a particular value. Following code shows the full view of the SinglyLinkedList class template. Noting that this implementation is not a STL style implementation.

-
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
template <typename T>
class SinglyLinkedList{
struct Node; // forward declaration
template<typename X>
friend void reverse(SinglyLinkedList<X>&,
typename SinglyLinkedList<X>::Node*)
public:

SinglyLinkedList(): ptrToHead(nullptr), count(0) { // create an empty linked_list
std::cout << "default constructor" << std::endl;
}
explicit SinglyLinkedList(const size_type, const T& val = T()); // create an linked list with size
SinglyLinkedList(const SinglyLinkedList&); // copy constructor
SinglyLinkedList& operator= (const SinglyLinkedList&); // assignment operator
~SinglyLinkedList(); // destructor
void clear(); // clear
Node* begin() { return ptrToHead; } // get pointer to Head Node
const Node* begin() const { return ptrToHead; }
bool empty() const { return ptrToHead == nullptr; } // check whether is empty
size_type size() const { return count; } // get the size of the list
void push_front(const T&); // insert at begining
void push_back(const T&); // insert at the end
void insert(size_type, const T&); // insert at the nth position
void pop_front(); // delete at the begining
void pop_back(); // delete the last element
void erase(size_type); // delete at nth position
void reverse(); // reverse the order iteratively
void remove(const T&); // remove elements with specific values

private:
// nested Node type
struct Node{
T data;
Node* next;
};

// data members
Node* ptrToHead;
size_type count;

// create a new Node
Node* create(const T& val){
Node* new_node = new Node;
new_node->data = val;
new_node->next = nullptr;
return new_node;
}
};
- -

Let’s define the special members first:

-

constructors

The default constructor initializes the ptrToHead to nullptr and count to 0, hence constructs an empty linked list. The second constructor constructs a linked list with a size and a value of T type. If no value supplied, the data member in each Node will be default-initialized or value-initialized depending on whether there exists a user-defined default constructor in the definition of T type. Also I specify this constructor as explicit to avoid potential type conversion. To validate my implementation, I instrument these special members by adding outputs when these members are called.

-
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
// constructor that takes a size and a value: O(n)
template <class T>
SinglyLinkedList<T>::SinglyLinkedList(size_type n, const T& val){
std::cout << "constructor with parameters" << std::endl;
if(n > 0){
ptrToHead = create(val);
count = 1;
Node* temp = ptrToHead;
while(count != n){
temp->next = create(val);
temp = temp->next;
++count;
}
}
}
-

The logic is simple: first create the Head Node and store its address into ptrToHead, at the same time, update the count to 1 representing that currently we have one element in our linked list; Then, create the rest Nodes and link one by one. We use a temporary pointer to move forward as we don’t want to modify the ptrToHead. The time cost is proportional to the length of the linked list and therefore the time complexity is big oh of n, where n represents the number of elements stored in the linked list.

-

Next is the copy constructor which controls the copy operation. The definition is similar to above constructor except that, the copy constructor constructs an object using values stored in the argument. If the argument is an empty linked list, we construct an empty linked list as well through the initialization list.

-
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
// copy constructor: O(n)
template <class T>
SinglyLinkedList<T>::SinglyLinkedList(const SinglyLinkedList& l): ptrToHead(nullptr), count(0) {
std::cout << "copy constructor" << std::endl;
if(!l.empty()){
ptrToHead = create(l.begin()->data);
count = 1;
Node* temp1 = ptrToHead;
const Node* temp2 = l.begin();
while(count != l.size())
{
temp1->next = create(temp2->next->data);
temp1 = temp1->next;
temp2 = temp2->next;
++count;
}
}
}
- -

The assignment operator is similar to our copy costructor except that it needs to obliterate the old values stored in the left-hand side operand. If the argument is an empty linked list, we only need to execute the clear() function to delete all nodes (if exist) to get an empty linked list.

-
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
// assignment operator: O(n)
template <class T>
SinglyLinkedList<T>& SinglyLinkedList<T>::operator= (const SinglyLinkedList& l){
std::cout << "assignment operator" << std::endl;
if(&l != this){
clear();
if(!l.empty()){
ptrToHead = create(l.begin()->data);
count = 1;
Node* temp1 = ptrToHead;
const Node* temp2 = l.begin();
while(count != l.size())
{
temp1->next = create(temp2->next->data);
temp1 = temp1->next;
temp2 = temp2->next;
++count;
}
}
}
return *this;
}
- -

destructor & clear member

The destructor is implemented by executing the clear() function.

-
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
// destructor
template <class T>
SinglyLinkedList<T>::~SinglyLinkedList() {
std::cout << "destructor" << std::endl;
clear();
}

// clear function: the time complexity O(n)
template <class T>
void SinglyLinkedList<T>::clear(){
Node* current = ptrToHead;
while(current != nullptr)
{
ptrToHead = ptrToHead->next;
delete current;
current = ptrToHead;
--count;
}
}
-

Due to the ptrToHead is the only entrance for us, we cannot delete it directly. Therefore, we do it by virtue of a temporary pointer, which points to the Node to be deleted. For example, let it points to the Head Node, then we can release the ptrToHead and let the ptrToHead points to our next Node, and then we clear the Head Node by delete the temporary pointer.

-

push_front, push_back, insert

The push_front means that we can add a new element at the very begining of our linked list. It is simple to do this, making the ptrToHead points to our new Node. But noting that when the linked list is not empty, we should stores the address of the original Head Node into the next member of our new Node.

-
1
2
3
4
5
6
7
8
9
// insert at begining: O(1)
template <class T>
void SinglyLinkedList<T>::push_front(const T& val) {
Node* new_node = create(val);
if(ptrToHead != nullptr)
new_node->next = ptrToHead;
ptrToHead = new_node;
++count;
}
- -

The case of appending a new Node at the end of the linked list is a little bit complex. The function is shown below.

-
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
// insert at the end: O(n)
template <class T>
void SinglyLinkedList<T>::push_back(const T& val) {
Node* new_node = create(val);
if(ptrToHead == nullptr){
ptrToHead = new_node;
++count;
pointToHead->data << std::endl;
return;
}

Node* temp = ptrToHead;
while(temp->next != nullptr)
temp = temp->next;
temp->next = new_node;
++count;
}
-

If the linked list is empty, we simply create a new Node and let ptrToHead points to the new Node. If the linked list is not empty, we need to let the next member of the last Node points to the newly created Node. To find the address of the last Node, we use the condition:

-
1
temp->next == nullptr
-

This condition stops the while loop and by then, temp is the address of the last Node.

-

Now we consider the case that inserting a new Node at the nth position, where n is in the range [1, size()]. The case that we insert a new Node at the first position can be tanckled by the push_front function.

-

The graph below illustrate a 5-Node linked list and the case that we intend to insert the new Node at a non-Head position. For example, we insert at the third position, that is, when n == 3:

-

Singly Linked List Example

-

We can observe that:

-
    -
  1. the newly created Node becomes the third Node and the original third Node becomes the fourth Node.
  2. -
  3. before inserting
    1
    add2->next == add2
    - after inserting
    1
    add2->next == addx
  4. -
  5. to link following Nodes, we let
    1
    addx->next == add3
    -Now everything is clear: if we want to insert at the nth position, we have to find the n-1 position and link the n-1th Node to the newly created Node. Let’s starting from the ptrToHead and show the relations between Nodes, Addresses and Iteration times:
    1
    2
    3
    4
    5
    6
    7
            counter i       Address         Node
    when i = 0 ptrToHead 1st Node
    i = 1 add2 2ed Node
    i = 2 add3 3ed Node
    ... ... ...
    i = n - 2 add(n - 1) n - 1 Node
    i = n - 1 addn n Node
    - -
  6. -
-

Obviously, we will stop our loop when i == n - 2 and by then we can manipulate the n - 1 Node. Following code shows a full view of the insert function.

-
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
// insert at the nth position, n belongs to [1, size()]: O(n)
// the positions means that we add the new node after (n-1)th position
template <class T>
void SinglyLinkedList<T>::insert(size_type position, const T& val){
if(position < 1 || position > size())
throw std::domain_error("Invalid Position");
else if(position == 1)
push_front(val);
else{
Node* new_node = create(val);
Node* temp = ptrToHead;
for(size_type i = 0; i != position - 2; ++i)
temp = temp->next;
new_node->next = temp->next;
temp->next = new_node;
++count;
}
}
- -

pop_front, pop_back, erase

The idea behind these three functions are similar to the inserting operations described above. Hence no further discussion here.

-
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
// delete at the begining: O(1)
template <class T>
void SinglyLinkedList<T>::pop_front(){
if(ptrToHead == nullptr){
throw std::domain_error("Empty Linked List");
}

Node* temp = ptrToHead;
ptrToHead = ptrToHead->next;
delete temp;
--count;
}

// delete the last element: O(n)
template <class T>
void SinglyLinkedList<T>::pop_back(){
if(ptrToHead == nullptr){
throw std::domain_error("Empty Linked List");
}
erase(size());
}

// delete at nth position: O(n)
// n belongs to [1, size()], the position after n - 1
template <class T>
void SinglyLinkedList<T>::erase(size_type position){
if(position < 1 || position > size())
throw std::domain_error("Invalid Position");
else if(position == 1)
pop_front();
else{
Node* current = ptrToHead;
for (size_type i = 0; i != position-2; ++i)
current = current->next;
Node* temp = current->next;
current-> next = temp->next;
delete temp;
--count;
}
}

## remove
// remove elements with specific values: O(n^2)
template <class T>
void SinglyLinkedList<T>::remove(const T& val){
Node* current = ptrToHead;
size_type i = 0;
while(current != nullptr){
if(current->data == val){
current = current->next;
erase(i + 1);
}
else{
current = current->next;
++i;
}
}
}

The remove function allows us to remove all elements that contains data values equal to a supplied value. My solution is to find the positions of the Nodes that should be removed and then call the **erase** function by passing the position. Taking an example(see below graph), suppose one want to remove any element that has value **v3**, we should find the previous position, that is, **2ed Node**, and then break the link and rebuilt the link between **2ed Node** and **4th Node** through:
-

temp = add2->next
temp = temp->next

-
1
2
3
4

![Remove operation](/images/remove.PNG)

If we loop through the linked list starting from the **ptrToHead**, the mapping relations are as follows:
-
counter i       values           position

when i = 0 v1 1
i = 1 v2 2
i = 2 v3 3
… … …
i = n - 2 v(n - 1) n - 1
i = n - 1 vn n

-
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
When i == 2, the position that should be deleted is positioned at **i+1**. Therefore, we pass **i+1** to the **erase** function. But noting that before we delete the Node, we should let **current** points to the Node that closely followed the Node to be deleted. In addition, we should not advance the counter **i** as now the Node pointed by **current** hasn't be checked. But if a Node doesn't satisfy the condition, we move forward the **current** as well as the counter **i**. 

## reverse
In our **SinglyLinkedList**, we define two **reverse** functions, one of which is a member defined based on iteration while the other one is a non-member function defined based on recursion.
​```c++
// reverse the order: iteration version O(n)
template <class T>
void SinglyLinkedList<T>::reverse(){
Node* prev = nullptr;
Node* current = ptrToHead;
Node* next;
while(current != nullptr){
next = current->next;
current->next = prev;
prev = current;
current = next;
}
ptrToHead = prev;
}
-

The basic idea behind the reverse function is that let each Node stores the address of the previous Node.
The initial information is that:

-
    -
  1. the previous address for the Head Node is nullptr
  2. -
  3. the current address for the Head Node is ptrToHead
  4. -
-

There are four steps in each iteration:

-
    -
  1. temporarily stores the address of next Node as next = current->next will be rewrite
  2. -
  3. rewrite the current->next with the previous address
  4. -
  5. in next iteration, the previous address is the current address in this iteration, hence let prev = current
  6. -
  7. in next iteration, the current address is the next address in this iteration, hence let current = next
  8. -
-

The recursive version starts from the last Node but still uses the same idea. Don’t forget to add this function template as the friend of our SinglyLinkedList class.

-
1
2
3
4
5
6
7
8
9
10
11
12
13
// reverse the order: recursion version O(n)
// space complexity: O(n)
template<typename X>
void reverse(SinglyLinkedList<X>& l, typename SinglyLinkedList<X>::Node* p){
if(p->next == nullptr || l.empty()){
l.ptrToHead = p;
return;
}
reverse(l, p->next);
typename SinglyLinkedList<X>::Node* q = p->next;
q->next = p;
p->next = nullptr;
}
- -

Test

I have tested every member in this class template and the results show that this SinglyLinkedList works perfectly. Please find the test program and results below:

-
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
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
/*
* this program tests all operations that provided by the
* SinglyLinkedList<T> class
* created by Liam on: 28 Apr 2018
*/

#include <iostream>
#include "SinglyLinkedList.h"

using std::endl; using std::cout;

template <class T>
void print(T& l){
auto it = l.begin();
for(; it != nullptr; it = it->next){
cout << it->data << " ";
}
cout << endl;
}

int main(){

{ // construct an empty linked list
SinglyLinkedList<int> s;
if(s.empty())
cout << "s is an empty linked list\n"
"the size of s1 is: " << s.size() << endl;

// call destructor once reaches the end of this block
}
cout << endl;

{ // construct a linked list that contains 10 elements, all values are 100
SinglyLinkedList<int> s(10, 100);

// construct a linked list by copying from s
SinglyLinkedList<int> s_copy(s);
if(!s.empty() && !s_copy.empty()){
cout << "the size of s is: " << s.size() << endl;
cout << "the size of s_copy is: " << s_copy.size() << endl;
}

// print the contents of s
cout << "all elements in s: ";
print(s);

// call destructor twice
}
cout << endl;

{ // assignment
SinglyLinkedList<int> s(10, 100);
SinglyLinkedList<int> s_copy;
s_copy = s;

// print the contents of s
}
cout << endl;

{ // push front
SinglyLinkedList<double> s;
for(int i = 5; i != 0; --i)
s.push_front(i);

cout << "after adding elements at front, s becomes: ";
print(s);

// push back
for(int i = 5; i != 0; --i)
s.push_back(i);

cout << "after adding elements at the end, s becomes: ";
print(s);


// insert at position 5
for(int i = 5; i != 0; --i)
s.insert(i, 0);

cout << "after inserting elements in-between, s becomes: ";
print(s);

// delete from the begining
for(int i = 5; i != 0; --i)
s.pop_front();

cout << "after deleting from the begining, s becomes: ";
print(s);

// delete from the end
for(int i = 5; i != 0; --i)
s.pop_back();

cout << "after deleting from the end, s becomes: ";
print(s);

// erase at in-between positions
for(int i = 3; i != 0; --i)
s.erase(3);

cout << "after deleting from other positions, s becomes: ";
print(s);

}
cout << endl;

{ // remove
SinglyLinkedList<int> s(5, 5);
for(int i = 5; i != 0; --i)
s.insert(5, i);

cout << "at present, s contains following elements: ";
print(s);

s.remove(5);
cout << "after removing all elements equal 5, s becomes: ";
print(s);
}
cout << endl;

{ // test reverse function
SinglyLinkedList<int> s;
for(int i = 0; i != 10; ++i)
s.push_back(i);

cout << "at present, s contains following elements: ";
print(s);

s.reverse();
cout << "reverse: ";
print(s);

s.reverse();
cout << "reverse again: ";
print(s);
}

return 0;
}
- -

Outputs:

-
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
default constructor
s is an empty linked list
the size of s1 is: 0
destructor

constructor with parameters
copy constructor
the size of s is: 10
the size of s_copy is: 10
all elements in s: 100 100 100 100 100 100 100 100 100 100
destructor
destructor

constructor with parameters
default constructor
assignment operator
destructor
destructor

default constructor
after adding elements at front, s becomes: 1 2 3 4 5
after adding elements at the end, s becomes: 1 2 3 4 5 5 4 3 2 1
after inserting elements in-between, s becomes: 0 1 0 2 0 3 0 4 0 5 5 4 3 2 1
after deleting from the begining, s becomes: 3 0 4 0 5 5 4 3 2 1
after deleting from the end, s becomes: 3 0 4 0 5
after deleting from other positions, s becomes: 3 0
destructor

constructor with parameters
at present, s contains following elements: 5 5 5 5 1 2 3 4 5 5
after removing all elements equal 5, s becomes: 1 2 3 4
destructor

default constructor
at present, s contains following elements: 0 1 2 3 4 5 6 7 8 9
reverse: 9 8 7 6 5 4 3 2 1 0
reverse again: 0 1 2 3 4 5 6 7 8 9
destructor
- -
- -
- - - - - - -
- -
- - - - -
- - - - -
- - -
- - - - -
- - - - - - - - -
-
- - -
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/2018/05/26/C-Implementations-Circular-Doubly-Linked-List/index.html b/2018/05/26/C-Implementations-Circular-Doubly-Linked-List/index.html deleted file mode 100644 index 5f1e01a1..00000000 --- a/2018/05/26/C-Implementations-Circular-Doubly-Linked-List/index.html +++ /dev/null @@ -1,542 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - C++ Implementations: Singly Circular Linked List with a sentinel | Liam's Blog - - - - - - - - - - - - -
-
- -
-
- - -
- - -

If history repeats itself, and the unexpected always happens, how incapable must Man be of learning from experience?—George Bernard Shaw.

-
- - -
- - - - -
-
- - -
- - 0% -
- - - - -
-
-
- - -
- - -
- - - -
- - - - - -
-

- C++ Implementations: Singly Circular Linked List with a sentinel -

- - -
- - - - -
- - -

Header file

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
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
#ifndef CIRCULARLINKEDLIST_H_
#define CIRCULARLINKEDLIST_H_

#include <cstddef>
#include <iostream>

template <typename T>
class CircularLinkedList{
struct Node;

public:
typedef std::size_t size_type;
typedef T value_type;

// create an empty linked list
CircularLinkedList(): sentinel(create_sentinel()), count(0){
std::cout << "default constructor" << std::endl;
}

// create an linked list with user supplied size and value
explicit CircularLinkedList(size_type, const T& val = T());

// copy constructor
CircularLinkedList(const CircularLinkedList&);

// assignment operator
CircularLinkedList& operator=(const CircularLinkedList&);

// destructor
~CircularLinkedList() {
clear();
delete sentinel;
sentinel = nullptr;
std::cout << "destructor" << std::endl;
}

void clear();

bool empty() const { return sentinel == sentinel->next; }
size_type size() const { return count; }

Node* begin() { return sentinel->next; }
const Node* begin() const { return sentinel->next; }

Node* end() { return sentinel; }
const Node* end() const { return sentinel; }

// insert at begining
void push_front(const T&);

// insert at the end
void push_back(const T&);

// insert at the nth position, that is, after (n-1)the position
// the range of position is [1, size()]
void insert(size_type, const T&);

// delete at the begining
void pop_front();

// delete the last element
void pop_back();

// delete at nth position
void erase(size_type);

// reverse the order iteratively
void reverse();

// remove elements with specific values
void remove(const T&);

private:
struct Node{
T data;
Node* next;
};

Node* sentinel;
size_type count;


Node* create_sentinel(){
Node* nil = new Node;
nil->next = nil;
return nil;
}


Node* create(const T& val = T()){
Node* new_node = new Node;
new_node->data = val;
new_node->next = sentinel;
return new_node;
}
};

// O(n)
template <typename T>
CircularLinkedList<T>::CircularLinkedList(size_type n, const T& val):
sentinel(create_sentinel()), count(0){
std::cout << "constructor with parameters" << std::endl;

Node* current = sentinel;
while(count != n){
current->next = create(val);
current = current->next;
++count;
}
}

// O(n)
template <typename T>
CircularLinkedList<T>::CircularLinkedList(const CircularLinkedList& l):
sentinel(create_sentinel()), count(0){
Node* current = sentinel;
const Node* temp = l.begin();
while(temp != l.end()){
current->next = create(temp->data);
current = current->next;
temp = temp->next;
++count;
}
}

// O(n)
template <typename T>
CircularLinkedList<T>& CircularLinkedList<T>::operator=
(const CircularLinkedList& l){
if(&l != this){
clear();
Node* current = sentinel;
const Node* temp = l.begin();
while(temp != l.end()){
current->next = create(temp->data);
current = current->next;
temp = temp->next;
++count;
}
}
return *this;
}

// O(n)
template <typename T>
void CircularLinkedList<T>::clear(){
Node* current = sentinel;
if(current->next != sentinel){
Node* temp = current->next;
current->next = temp->next;
delete temp;
--count;
}
}

// O(1)
template <class T>
void CircularLinkedList<T>::push_front(const T& val) {
Node* current = sentinel;
Node* new_node = create(val);
new_node->next = current->next;
current->next = new_node;
++count;
}

// O(n)
template <class T>
void CircularLinkedList<T>::push_back(const T& val) {
Node* current = sentinel;
while(current->next != sentinel)
current = current->next;

current->next = create(val);
++count;
}

// O(n)
template <class T>
void CircularLinkedList<T>::insert(size_type position, const T& val){
if(position < 1 || position > size())
throw std::domain_error("Invalid Position");

Node* new_node = create(val);
Node* temp = sentinel;
for (size_type i = 0; i != position-1; ++i)
temp = temp->next;
new_node->next = temp->next;
temp->next = new_node;
++count;
}

// O(1)
template <class T>
void CircularLinkedList<T>::pop_front(){
erase(1);
}

// O(n)
template <class T>
void CircularLinkedList<T>::pop_back(){
erase(size());
}

// O(n)
template <class T>
void CircularLinkedList<T>::erase(size_type position){
if(position < 1 || position > size())
throw std::domain_error("Invalid Position");

Node* current = sentinel;
for(size_type i = 0; i != position - 1; ++i)
current = current->next;

Node* temp = current->next;
current->next = temp->next;
delete temp;
--count;
}

// O(n)
template <class T>
void CircularLinkedList<T>::remove(const T& val){
Node* current = sentinel;
while(current->next != sentinel){
if(current->next->data == val){
Node* temp = current->next;
current->next = temp->next;
delete temp;
--count;
}else current = current->next;
}
}

// O(n)
template <class T>
void CircularLinkedList<T>::reverse(){
if (size() < 2) return;
Node* current = sentinel->next;
Node* PREV = sentinel;
Node* NEXT;
while(current->next != sentinel){
NEXT = current->next;
current->next = PREV;
PREV = current;
current = NEXT;
}
current->next = PREV;
sentinel->next = current;
}

#endif /* CIRCULARLINKEDLIST_H_ */
-

Test program and results

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
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
/*
* this program tests all operations that provided by the
* CircularLinkedList<T> class
* created by Liam on: 28 May 2018
*/

#include <iostream>
#include "CircularLinkedList.h"

using std::endl; using std::cout;

template <class Pointer>
void print(Pointer begin, Pointer end){
while(begin != end){
cout << begin->data << " ";
begin = begin->next;
}
cout<< endl;
}

int main(){

{ // construct an empty linked list
CircularLinkedList<int> s;
if(s.empty())
cout << "s is an empty linked list\n"
"the size of s1 is: " << s.size() << endl;

// call destructor once reaches the end of this block
}
cout << endl;

{ // construct a linked list that contains 10 elements, all values are 100
CircularLinkedList<int> s(10, 100);

// construct a linked list by copying from s
CircularLinkedList<int> s_copy(s);
if(!s.empty() && !s_copy.empty()){
cout << "the size of s is: " << s.size() << endl;
cout << "the size of s_copy is: " << s_copy.size() << endl;
}

// print the contents of s_copy
cout << "all elements in s_copy: ";
print(s.begin(), s.end());

// call destructor twice
}
cout << endl;

{ // assignment
CircularLinkedList<int> s(10, 100);
CircularLinkedList<int> s_copy;
s_copy = s;

// print the contents of s_copy
cout << "all elements in s_copy: ";
print(s_copy.begin(), s_copy.end());
}
cout << endl;

{ // push front
CircularLinkedList<double> s;
for(int i = 5; i != 0; --i)
s.push_front(i);

cout << "after adding elements at front, s becomes: ";
print(s.begin(), s.end());

// push back
for(int i = 5; i != 0; --i)
s.push_back(i);

cout << "after adding elements at the end, s becomes: ";
print(s.begin(), s.end());


// insert at position 5
for(int i = 5; i != 0; --i)
s.insert(i, 0);

cout << "after inserting elements in-between, s becomes: ";
print(s.begin(), s.end());

// delete from the begining
for(int i = 5; i != 0; --i)
s.pop_front();

cout << "after deleting from the begining, s becomes: ";
print(s.begin(), s.end());

// delete from the end
for(int i = 5; i != 0; --i)
s.pop_back();

cout << "after deleting from the end, s becomes: ";
print(s.begin(), s.end());

// erase at in-between positions
for(int i = 3; i != 0; --i)
s.erase(3);

cout << "after deleting from other positions, s becomes: ";
print(s.begin(), s.end());

}
cout << endl;

{ // remove
CircularLinkedList<int> s(5, 5);
for(int i = 5; i != 0; --i)
s.insert(5, i);

cout << "at present, s contains following elements: ";
print(s.begin(), s.end());

s.remove(5);
cout << "after removing all elements equal 5, s becomes: ";
print(s.begin(), s.end());
}
cout << endl;

{ // test reverse function
CircularLinkedList<int> s;
for(int i = 0; i != 10; ++i)
s.push_back(i);

cout << "at present, s contains following elements: ";
print(s.begin(), s.end());

s.reverse();
cout << "reverse: ";
print(s.begin(), s.end());

s.reverse();
cout << "reverse again: ";
print(s.begin(), s.end());

}

return 0;
}
- -

Outputs:

-
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
default constructor
s is an empty linked list
the size of s1 is: 0
destructor

constructor with parameters
the size of s is: 10
the size of s_copy is: 10
all elements in s_copy: 100 100 100 100 100 100 100 100 100 100
destructor
destructor

constructor with parameters
default constructor
all elements in s_copy: 100 100 100 100 100 100 100 100 100 100
destructor
destructor

default constructor
after adding elements at front, s becomes: 1 2 3 4 5
after adding elements at the end, s becomes: 1 2 3 4 5 5 4 3 2 1
after inserting elements in-between, s becomes: 0 1 0 2 0 3 0 4 0 5 5 4 3 2 1
after deleting from the begining, s becomes: 3 0 4 0 5 5 4 3 2 1
after deleting from the end, s becomes: 3 0 4 0 5
after deleting from other positions, s becomes: 3 0
destructor

constructor with parameters
at present, s contains following elements: 5 5 5 5 1 2 3 4 5 5
after removing all elements equal 5, s becomes: 1 2 3 4
destructor

default constructor
at present, s contains following elements: 0 1 2 3 4 5 6 7 8 9
reverse: 9 8 7 6 5 4 3 2 1 0
reverse again: 0 1 2 3 4 5 6 7 8 9
destructor
-
- - - - - - -
- -
- - - - -
- - - - -
- - -
- - - - -
- - - - - - - - -
-
- - -
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/2018/05/26/C-Implementations-Doubly-Linked-List/index.html b/2018/05/26/C-Implementations-Doubly-Linked-List/index.html deleted file mode 100644 index 8a19b869..00000000 --- a/2018/05/26/C-Implementations-Doubly-Linked-List/index.html +++ /dev/null @@ -1,538 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - C++ Implementations: Doubly Linked List | Liam's Blog - - - - - - - - - - - - -
-
- -
-
- - -
- - -

If history repeats itself, and the unexpected always happens, how incapable must Man be of learning from experience?—George Bernard Shaw.

-
- - -
- - - - -
-
- - -
- - 0% -
- - - - -
-
-
- - -
- - -
- - - -
- - - - - -
-

- C++ Implementations: Doubly Linked List -

- - -
- - - - -
- - -

Head files

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
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
#ifndef DOUBLYLINKEDLIST_H_
#define DOUBLYLINKEDLIST_H_

#include <cstddef>
#include <stdexcept>
#include <iostream>

template <typename T>
class DoublyLinkedList{
struct Node;
public:
typedef std::size_t size_type;
typedef T value_type;

// default constructor
DoublyLinkedList(): ptrToHead(nullptr), count(0){
std::cout << "default constructor" << std::endl;
}

// constructor with parameters
explicit DoublyLinkedList(size_type, const T&val = T());

// copy constructor
DoublyLinkedList(const DoublyLinkedList&);

// assignment operator
DoublyLinkedList& operator=(const DoublyLinkedList&);

// destructor
~DoublyLinkedList() {
std::cout << "destructor" << std::endl;
clear();
}

void clear();
bool empty() const { return ptrToHead == nullptr; }
size_type size() const { return count; }
Node* begin() { return ptrToHead; }
const Node* begin() const { return ptrToHead; }

void push_front(const T&);
void push_back(const T&);
void insert(size_type, const T&);

void pop_front();
void pop_back();
void erase(size_type);

void remove(const T&);
void reverse();

private:
struct Node{
T data;
Node* prev;
Node* next;
};

Node* ptrToHead;
size_type count;

Node* create(const T& val){
Node* new_node = new Node;
new_node->data = val;
new_node->prev = nullptr;
new_node->next = nullptr;
return new_node;
}
};

// constructor with parameters:O(n)
template <typename T>
DoublyLinkedList<T>::DoublyLinkedList(size_type n, const T&val){
std::cout << "constructor with parameters" << std::endl;
if(n > 0){
ptrToHead = create(val);
count = 1;

Node* current = ptrToHead;
while(count != n){
current->next = create(val);
current->next->prev = current;
current = current->next;
++count;
}
}
}

// copy constructor: O(n)
template <typename T>
DoublyLinkedList<T>::DoublyLinkedList(const DoublyLinkedList& l)
: ptrToHead(nullptr), count(0){
std::cout << "copy constructor" << std::endl;
if(!l.empty()){
ptrToHead = create(l.begin()->data);
count = 1;

const Node* temp = l.begin();
Node* current = ptrToHead;
while(count != l.size()){
current->next = create(temp->next->data);
current->next->prev = current;
current = current->next;
temp = temp->next;
++count;
}
}
}

// assignment operator: O(n)
template <typename T>
DoublyLinkedList<T>& DoublyLinkedList<T>::operator= (const DoublyLinkedList& l)
{
std::cout << "assignment operator" << std::endl;
if(&l != this){
clear();
if(!l.empty()){
ptrToHead = create(l.begin()->data);
count = 1;

const Node* temp = l.begin();
Node* current = ptrToHead;
while(count != l.size()){
current->next = create(temp->next->data);
current->next->prev = current;
current = current->next;
temp = temp->next;
++count;
}
}
}
return *this;
}

// O(n)
template <typename T>
void DoublyLinkedList<T>::clear(){
Node* current = ptrToHead;
while(current != nullptr){
ptrToHead = ptrToHead->next;
delete current;
current = ptrToHead;
--count;
}
}

// O(1)
template <typename T>
void DoublyLinkedList<T>::push_front(const T& val){
Node* new_node = create(val);
if(ptrToHead != nullptr){
new_node->next = ptrToHead;
ptrToHead->prev = new_node;
}
ptrToHead = new_node;
++count;
}

// O(n)
template <typename T>
void DoublyLinkedList<T>::push_back(const T& val){
Node* new_node = create(val);
if(ptrToHead == nullptr){
ptrToHead = new_node;
++count;
return;
}

Node* current = ptrToHead;
while(current->next != nullptr)
current = current->next;

current->next = new_node;
new_node->prev = current;
++count;
}

// O(n)
template <typename T>
void DoublyLinkedList<T>::insert(size_type position, const T& val){
if(position < 1 || position > size())
throw std::domain_error("Invalid Position");
else if(position == 1)
push_front(val);
else{
Node* new_node = create(val);
Node* current = ptrToHead;

for(size_type i = 0; i != position - 1; ++i)
current = current->next;
current->prev->next = new_node;
new_node->next = current;
new_node->prev = current->prev;
current->prev = new_node;
++count;
}
}

// O(1)
template <typename T>
void DoublyLinkedList<T>::pop_front(){ erase(1); }

// O(n)
template <typename T>
void DoublyLinkedList<T>::pop_back(){ erase(size()); }

// O(n)
template <typename T>
void DoublyLinkedList<T>::erase(size_type position){
if(position < 1 || position > size())
throw std::domain_error("Invalid Position");

Node* current = ptrToHead;

if(size() == 1){
ptrToHead = nullptr;
}else if(position == 1){
ptrToHead = ptrToHead->next;
ptrToHead->prev = nullptr;
}else if(position == size()){
while(current->next != nullptr)
current = current->next;
current->prev->next = nullptr;
}else{
for (size_type i = 0; i != position - 1; ++i){
current = current->next;
}
current->next->prev = current->prev;
current->prev->next = current->next;
}

delete current;
--count;
}

// O(n)
template <typename T>
void DoublyLinkedList<T>::remove(const T& val){
Node* current = ptrToHead;
while(current != nullptr){
if(current->data == val){
Node* temp = current->next;
if(current->prev == nullptr){
ptrToHead = ptrToHead->next;
if(ptrToHead != nullptr){
ptrToHead->prev = nullptr;
}
}else if(current->next == nullptr){
current->prev->next = nullptr;
}else{
current->next->prev = current->prev;
current->prev->next = current->next;
}

delete current;
current = temp;
}else
current = current->next;
}
}

// O(n)
template <typename T>
void DoublyLinkedList<T>::reverse(){
Node* current = ptrToHead;
Node* PREV = nullptr;
while(current != nullptr){
current->prev = current->next;
current->next = PREV;
PREV = current;
current = current->prev;
}
ptrToHead = PREV;
}
#endif /* DOUBLYLINKEDLIST_H_ */
- - -

Test program and results

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
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
/*
* this program tests all operations that provided by the
* DoublyLinkedList<T> class
* created by Liam on: 28 Apr 2018
*/

#include <iostream>
#include "DoublyLinkedList.h"

using std::endl; using std::cout;

// print and reverse print
template <class T>
void print(T& l){
cout << "print in order: ";
for(auto it = l.begin(); it != nullptr; it = it->next){
cout << it->data << " ";
}

cout << "\n" << "print in reverse: ";
auto it = l.begin();
while(it->next != nullptr){
it = it->next;

}
while(it != nullptr){
cout << it->data << " ";
it = it->prev;
}

cout << endl;
}

int main(){

{ // construct an empty linked list
DoublyLinkedList<int> s;
if(s.empty())
cout << "s is an empty linked list\n"
"the size of s1 is: " << s.size() << endl;

// call destructor once reaches the end of this block
}
cout << endl;

{ // construct a linked list that contains 10 elements, all values are 100
DoublyLinkedList<int> s(10, 100);

// construct a linked list by copying from s
DoublyLinkedList<int> s_copy(s);
if(!s.empty() && !s_copy.empty()){
cout << "the size of s is: " << s.size() << endl;
cout << "the size of s_copy is: " << s_copy.size() << endl;
}

// print the contents of s_copy
print(s_copy);

// call destructor twice
}
cout << endl;

{ // assignment
DoublyLinkedList<int> s(10, 100);
DoublyLinkedList<int> s_copy;
s_copy = s;

// print the contents of s_copy
print(s_copy);
}
cout << endl;

{ // push front
DoublyLinkedList<double> s;
for(int i = 5; i != 0; --i)
s.push_front(i);

cout << "after adding elements at front:\n";
print(s);

// push back
for(int i = 5; i != 0; --i)
s.push_back(i);

cout << "after adding elements at the end:\n";
print(s);

// insert at position 5
for(int i = 5; i != 0; --i)
s.insert(i, 0);

cout << "after inserting elements in-between:\n";
print(s);

// delete from the begining
for(int i = 5; i != 0; --i)
s.pop_front();

cout << "after deleting from the begining:\n";
print(s);

// delete from the end
for(int i = 5; i != 0; --i){
s.pop_back();
}

cout << "after deleting from the end:\n";
print(s);

// erase at in-between positions
for(int i = 3; i != 0; --i)
s.erase(3);

cout << "after deleting from other positions:\n";
print(s);

}
cout << endl;

{ // remove
DoublyLinkedList<int> s(5, 5);
for(int i = 5; i != 0; --i)
s.insert(5, i);

cout << "at present:\n";
print(s);

s.remove(5);
cout << "after removing all elements:\n";
print(s);
}
cout << endl;

{ // test reverse function
DoublyLinkedList<int> s;
for(int i = 0; i != 10; ++i)
s.push_back(i);

cout << "at present, s contains following elements:\n";
print(s);

s.reverse();
cout << "after reverse:\n";
print(s);

}

return 0;
}
- -

Outputs:

-
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
default constructor
s is an empty linked list
the size of s1 is: 0
destructor

constructor with parameters
copy constructor
the size of s is: 10
the size of s_copy is: 10
print in order: 100 100 100 100 100 100 100 100 100 100
print in reverse: 100 100 100 100 100 100 100 100 100 100
destructor
destructor

constructor with parameters
default constructor
assignment operator
print in order: 100 100 100 100 100 100 100 100 100 100
print in reverse: 100 100 100 100 100 100 100 100 100 100
destructor
destructor

default constructor
after adding elements at front:
print in order: 1 2 3 4 5
print in reverse: 5 4 3 2 1
after adding elements at the end:
print in order: 1 2 3 4 5 5 4 3 2 1
print in reverse: 1 2 3 4 5 5 4 3 2 1
after inserting elements in-between:
print in order: 0 1 0 2 0 3 0 4 0 5 5 4 3 2 1
print in reverse: 1 2 3 4 5 5 0 4 0 3 0 2 0 1 0
after deleting from the begining:
print in order: 3 0 4 0 5 5 4 3 2 1
print in reverse: 1 2 3 4 5 5 0 4 0 3
after deleting from the end:
print in order: 3 0 4 0 5
print in reverse: 5 0 4 0 3
after deleting from other positions:
print in order: 3 0
print in reverse: 0 3
destructor

constructor with parameters
at present:
print in order: 5 5 5 5 1 2 3 4 5 5
print in reverse: 5 5 4 3 2 1 5 5 5 5
after removing all elements:
print in order: 1 2 3 4
print in reverse: 4 3 2 1
destructor

default constructor
at present, s contains following elements:
print in order: 0 1 2 3 4 5 6 7 8 9
print in reverse: 9 8 7 6 5 4 3 2 1 0
after reverse:
print in order: 9 8 7 6 5 4 3 2 1 0
print in reverse: 0 1 2 3 4 5 6 7 8 9
destructor
-
- - - - - - -
- -
- - - - -
- - - - -
- - -
- - - - -
- - - - - - - - -
-
- - -
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/2018/05/28/C-Implementations-Doubly-Circular-Linked-List/index.html b/2018/05/28/C-Implementations-Doubly-Circular-Linked-List/index.html deleted file mode 100644 index 4501198a..00000000 --- a/2018/05/28/C-Implementations-Doubly-Circular-Linked-List/index.html +++ /dev/null @@ -1,537 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - C++ Implementations - Doubly Circular Linked List | Liam's Blog - - - - - - - - - - - - -
-
- -
-
- - -
- - -

If history repeats itself, and the unexpected always happens, how incapable must Man be of learning from experience?—George Bernard Shaw.

-
- - -
- - - - -
-
- - -
- - 0% -
- - - - -
-
-
- - -
- - -
- - - -
- - - - - -
-

- C++ Implementations - Doubly Circular Linked List -

- - -
- - - - -
- - -

Header file

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
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
#ifndef CIRCULARLINKEDLIST_H_
#define CIRCULARLINKEDLIST_H_

#include <cstddef>
#include <iostream>

template <typename T>
class CircularLinkedList{
struct Node;
public:
typedef std::size_t size_type;
typedef T value_type;

// default constructor
CircularLinkedList(): sentinel(create_sentinel()), count(0) {
std::cout << "default constructor" << std::endl;
}
CircularLinkedList(size_type, const T&val = T());
CircularLinkedList(const CircularLinkedList&);
CircularLinkedList& operator= (const CircularLinkedList&);
~CircularLinkedList();

bool empty() const { return sentinel->next == sentinel; }
size_type size() const { return count; }
Node* begin() { return sentinel->next; }
const Node* begin() const { return sentinel->next; }
Node* end() { return sentinel; }
const Node* end() const { return sentinel; }

void clear();
void push_front(const T&);
void push_back(const T&);
void insert(size_type, const T&);
void pop_front();
void pop_back();
void erase(size_type);
void reverse();
void remove(const T&);

private:
struct Node{
T data;
Node* prev;
Node* next;
};

Node* sentinel;
size_type count;

Node* create_sentinel(){
Node* nil = new Node;
nil->prev = nil;
nil->next = nil;
return nil;
}

Node* create(const T&val){
Node* new_node = new Node;
new_node->data = val;
new_node->prev = sentinel;
new_node->next = sentinel;
return new_node;
}

};

// O(n)
template <typename T>
CircularLinkedList<T>::CircularLinkedList(size_type n, const T& val)
: sentinel(create_sentinel()), count(0){
std::cout << "construtor with parameters" << std::endl;
Node* current = sentinel;
while(count != n){
current->next = create(val);
current->next->prev = current;
current = current->next;
++count;
}
sentinel->prev = current;
}

// O(n)
template <typename T>
CircularLinkedList<T>::CircularLinkedList(const CircularLinkedList& l)
: sentinel(create_sentinel()), count(0){
std::cout << "copy constructor" << std::endl;
Node* current = sentinel;
const Node* temp = l.begin();
while(count != l.size()){
current->next = create(temp->data);
current->next->prev = current;
current = current->next;
temp = temp->next;
++count;
}
sentinel->prev = current;

}

// O(n)
template <typename T>
CircularLinkedList<T>& CircularLinkedList<T>::operator=
(const CircularLinkedList& l){
std::cout << "assignment operator" << std::endl;
if(&l != this){
clear();
if(!l.empty()){
Node* current = sentinel;
const Node* temp = l.begin();
while(count != l.size()){
current->next = create(temp->data);
current->next->prev = current;
current = current->next;
temp = temp->next;
++count;
}
sentinel->prev = current;
}
}
return *this;
}

// O(n)
template <typename T>
void CircularLinkedList<T>::clear(){
while(sentinel->next != sentinel){
Node* temp = sentinel->next;
sentinel->next = temp->next;
sentinel->next->prev = sentinel;
delete temp;
--count;
}
}

// O(n)
template <typename T>
CircularLinkedList<T>::~CircularLinkedList() {
clear();
delete sentinel;
sentinel = nullptr;
std::cout << "destructor" << std::endl;
}

// O(1)
template <typename T>
void CircularLinkedList<T>::push_front(const T& val){
Node* new_node = create(val);
new_node->next = sentinel->next;
new_node->next->prev = new_node;
sentinel->next = new_node;
new_node->prev = sentinel;
++count;
}

// O(n)
template <typename T>
void CircularLinkedList<T>::push_back(const T& val){
Node* current = sentinel;
while(current->next != sentinel)
current = current->next;

current->next = create(val);
current->next->prev = current;
sentinel->prev = current->next;
++count;
}

// O(n)
template <typename T>
void CircularLinkedList<T>::insert(size_type position, const T& val){
if(position < 1 || position > size())
throw std::domain_error("Invalid Position");
Node* current = sentinel;
Node* new_node = create(val);
for(size_type i = 0; i != position; ++i)
current = current->next;

current->prev->next = new_node;
new_node->prev = current->prev;
new_node->next = current;
current->prev = new_node;
++count;
}

// O(1)
template <typename T>
void CircularLinkedList<T>::pop_front(){
erase(1);
}

// O(n)
template <typename T>
void CircularLinkedList<T>::pop_back(){
erase(size());
}

// O(n)
template <typename T>
void CircularLinkedList<T>::erase(size_type position){
if(position < 1 || position > size())
throw std::domain_error("Invalid Position");

Node* current = sentinel;
for(size_type i = 0; i != position; ++i)
current = current->next;

current->next->prev = current->prev;
current->prev->next = current->next;
delete current;
--count;
}

// O(n)
template <class T>
void CircularLinkedList<T>::remove(const T& val){
Node* current = sentinel;
while(current->next != sentinel){
if(current->next->data == val){
Node* temp = current->next;
current->next = temp->next;
temp->next->prev = current;
delete temp;
--count;
}else current = current->next;
}
}

// O(n)
template <class T>
void CircularLinkedList<T>::reverse(){
if (size() < 2) return;
sentinel->prev = sentinel->next;
Node* current = sentinel;
Node* NEXT;
while(current->next != sentinel){
NEXT = current->next;
current->next = current->prev;
current->prev = NEXT;
current = NEXT;
}
current->next = current->prev;
current->prev = sentinel;
sentinel->next = current;
}

#endif /* CIRCULARLINKEDLIST_H_ */
- -

Test program and results

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
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
/*
* this program tests all operations that provided by the
* CircularLinkedList<T> class
* created by Liam on: 28 Apr 2018
*/

#include <iostream>
#include "CircularLinkedList.h"

using std::endl; using std::cout;

// print and reverse print
template <class T>
void print(T& l){
cout << "print in order: ";
for(auto i = l.begin(); i != l.end(); i = i->next)
cout << i->data << " ";
cout << endl;

cout << "print in reverse: ";

for(auto i = (l.end())->prev; i != l.end(); i = i->prev)
cout << i->data << " ";
cout << endl;
}

int main(){

{ // construct an empty linked list
CircularLinkedList<int> s;
if(s.empty())
cout << "s is an empty linked list\n"
"the size of s1 is: " << s.size() << endl;

// call destructor once reaches the end of this block
}
cout << endl;

{ // construct a linked list that contains 10 elements, all values are 100
CircularLinkedList<int> s(10, 100);

// construct a linked list by copying from s
CircularLinkedList<int> s_copy(s);
if(!s.empty() && !s_copy.empty()){
cout << "the size of s is: " << s.size() << endl;
cout << "the size of s_copy is: " << s_copy.size() << endl;
}

// print the contents of s_copy
print(s_copy);
// call destructor twice
}
cout << endl;

{ // assignment
CircularLinkedList<int> s(10, 100);
CircularLinkedList<int> s_copy;
s_copy = s;

// print the contents of s_copy
print(s_copy);
}
cout << endl;

{ // push front
CircularLinkedList<double> s;
for(int i = 5; i != 0; --i)
s.push_front(i);

cout << "after adding elements at front:\n";
print(s);

// push back
for(int i = 5; i != 0; --i)
s.push_back(i);

cout << "after adding elements at the end:\n";
print(s);

// insert at position 5
for(int i = 5; i != 0; --i)
s.insert(i, 0);

cout << "after inserting elements in-between:\n";
print(s);

// delete from the begining
for(int i = 5; i != 0; --i)
s.pop_front();

cout << "after deleting from the begining:\n";
print(s);

// delete from the end
for(int i = 5; i != 0; --i){
s.pop_back();
}

cout << "after deleting from the end:\n";
print(s);

// erase at in-between positions
for(int i = 3; i != 0; --i)
s.erase(3);

cout << "after deleting from other positions:\n";
print(s);

}
cout << endl;

{ // remove
CircularLinkedList<int> s(5, 5);
for(int i = 5; i != 0; --i)
s.insert(5, i);

cout << "at present:\n";
print(s);

s.remove(5);
cout << "after removing all elements:\n";
print(s);
}
cout << endl;

{ // test reverse function
CircularLinkedList<int> s;
for(int i = 0; i != 10; ++i)
s.push_back(i);

cout << "at present, s contains following elements:\n";
print(s);

s.reverse();
cout << "after reverse:\n";
print(s);

}

return 0;
}
- -

Outputs:

-
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
default constructor
s is an empty linked list
the size of s1 is: 0
destructor

construtor with parameters
copy constructor
the size of s is: 10
the size of s_copy is: 10
print in order: 100 100 100 100 100 100 100 100 100 100
print in reverse: 100 100 100 100 100 100 100 100 100 100
destructor
destructor

construtor with parameters
default constructor
assignment operator
print in order: 100 100 100 100 100 100 100 100 100 100
print in reverse: 100 100 100 100 100 100 100 100 100 100
destructor
destructor

default constructor
after adding elements at front:
print in order: 1 2 3 4 5
print in reverse: 5 4 3 2 1
after adding elements at the end:
print in order: 1 2 3 4 5 5 4 3 2 1
print in reverse: 1 2 3 4 5 5 4 3 2 1
after inserting elements in-between:
print in order: 0 1 0 2 0 3 0 4 0 5 5 4 3 2 1
print in reverse: 1 2 3 4 5 5 0 4 0 3 0 2 0 1 0
after deleting from the begining:
print in order: 3 0 4 0 5 5 4 3 2 1
print in reverse: 1 2 3 4 5 5 0 4 0 3
after deleting from the end:
print in order: 3 0 4 0 5
print in reverse: 5 0 4 0 3
after deleting from other positions:
print in order: 3 0
print in reverse: 0 3
destructor

construtor with parameters
at present:
print in order: 5 5 5 5 1 2 3 4 5 5
print in reverse: 5 5 4 3 2 1 5 5 5 5
after removing all elements:
print in order: 1 2 3 4
print in reverse: 4 3 2 1
destructor

default constructor
at present, s contains following elements:
print in order: 0 1 2 3 4 5 6 7 8 9
print in reverse: 9 8 7 6 5 4 3 2 1 0
after reverse:
print in order: 9 8 7 6 5 4 3 2 1 0
print in reverse: 0 1 2 3 4 5 6 7 8 9
destructor
-
- - - - - - -
- -
- - - - -
- - - - -
- - -
- - - - -
- - - - - - - - -
-
- - -
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/2018/05/28/C-notes-for-financial-mathematics/index.html b/2018/05/28/C-notes-for-financial-mathematics/index.html deleted file mode 100644 index 739d0a5d..00000000 --- a/2018/05/28/C-notes-for-financial-mathematics/index.html +++ /dev/null @@ -1,544 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - C++ notes for financial mathematics | Liam's Blog - - - - - - - - - - - - -
-
- -
-
- - -
- - -

If history repeats itself, and the unexpected always happens, how incapable must Man be of learning from experience?—George Bernard Shaw.

-
- - -
- - - - -
-
- - -
- - 0% -
- - - - -
-
-
- - -
- - -
- - - -
- - - - - -
-

- C++ notes for financial mathematics -

- - -
- - - - -
- - -
    -
  1. 1
    2
    3
    Inf: positive infinity
    -Inf: negative infinity
    NaN: not a number
    -
  2. -
  3. Noting that two decimals can only be approximately equal.

    -
  4. -
  5. Casting an int to a float is risky since the float data type uses binary scientific notation with only a handful of significant figures. So an int cannot be represented precisely using a float.

    -
  6. -
  7. -
- -
- - - - - - -
- -
- - - - -
- - - - -
- - -
- - - - -
- - - - - - - - -
-
- - -
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/2018/05/30/C-Implementations-Linked-List-based-Stack/index.html b/2018/05/30/C-Implementations-Linked-List-based-Stack/index.html deleted file mode 100644 index e48fb38d..00000000 --- a/2018/05/30/C-Implementations-Linked-List-based-Stack/index.html +++ /dev/null @@ -1,545 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - C++ Implementations: Linked List-Based Stack | Liam's Blog - - - - - - - - - - - - -
-
- -
-
- - -
- - -

If history repeats itself, and the unexpected always happens, how incapable must Man be of learning from experience?—George Bernard Shaw.

-
- - -
- - - - -
-
- - -
- - 0% -
- - - - -
-
-
- - -
- - -
- - - -
- - - - - -
-

- C++ Implementations: Linked List-Based Stack -

- - -
- - - - -
- - -

Implementation

- -
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
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
#ifndef MYSTACK_H_
#define MYSTACK_H_

#include <cstddef>
#include <iostream>
#include <stdexcept>

template <typename T>
class MyStack{
struct Node;

public:
typedef std::size_t size_type;
typedef T value_type;

MyStack(): ptrToHead(nullptr), count(0) {
std::cout << "default constructor" << std::endl;
}

// O(n)
MyStack(const MyStack& s): ptrToHead(nullptr), count(0){
std::cout << "copy constructor" << std::endl;
if(!s.empty()){
ptrToHead = create(s.ptrToHead->data);
++count;
Node* current = ptrToHead;
const Node* temp = s.ptrToHead;
while(count != s.count){
current->next = create(temp->next->data);
current = current->next;
temp = temp->next;
++count;
}
}
}

// O(n)
MyStack& operator=(const MyStack& s){
std::cout << "assignment operator" << std::endl;
if(&s != this){
clear();
if(!s.empty()){
ptrToHead = create(s.ptrToHead->data);
++count;
Node* current = ptrToHead;
const Node* temp = s.ptrToHead;
while(count != s.count){
current->next = create(temp->next->data);
current = current->next;
temp = temp->next;
++count;
}
}
}
return *this;
}

// O(n)
~MyStack() {
std::cout << "destructor" << std::endl;
clear();
}

// O(n)
void clear() {
Node* current = ptrToHead;
while(current != nullptr){
ptrToHead = ptrToHead->next;
delete current;
current = ptrToHead;
--count;
}
}

// O(1)
bool empty() const { return ptrToHead == nullptr; }
size_type size() const { return count; }

// O(1)
void push(const T& val){
Node* new_node = create(val);
if(ptrToHead != nullptr)
new_node->next = ptrToHead;
ptrToHead = new_node;
++count;
}

// O(1)
void pop(){
if(ptrToHead == nullptr)
throw std::domain_error("Stack underflow");

Node* temp = ptrToHead;
ptrToHead = ptrToHead->next;
delete temp;
--count;
}

// O(1)
T top() const {
if(ptrToHead == nullptr)
throw std::domain_error("stack underflow");
return ptrToHead->data;
}

private:
struct Node{
T data;
Node* next;
};

// data members
Node* ptrToHead;
size_type count;

// private function to create a new Node given a value
Node* create(const T& val = T()){
Node* new_node = new Node;
new_node->next = nullptr;
new_node->data = val;
return new_node;
}
};
#endif /* MYSTACK_H_ */
- -

Test and results

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
/*
* this program tests all operations that provided by the MyStack<int> class
* created by Liam on: 27 May 2018
*/

#include <iostream>
#include <stdexcept>
#include "MyStack.h"

using std::cout;
using std::endl;
using std::domain_error;

int main(){
{ // test default constructor
MyStack<int> s;
if(!s.empty())
cout << "s is an empty stack\n";

// test push
for (int i = 0; i != 10; ++i)
s.push(i);

// test top, pop
while(s.size() != 0){
cout << s.top() << " ";
s.pop();
}

cout << "\n";
// test stack underflow
try{
s.pop();
}catch(domain_error e){
cout << e.what() << "\n";
}
}

cout << "\n";

{ // test copy and assignment operation
MyStack<int> s;
for (int i = 0; i != 10; ++i)
s.push(i);

MyStack<int> s_copy(s);
while(s_copy.size() != 0){
cout << s_copy.top() << " ";
s_copy.pop();
}
cout << "\n";
s.pop();

s_copy = s;
while(s_copy.size() != 0){
cout << s_copy.top() << " ";
s_copy.pop();
}
cout << "\n";
}
}
- -

Outputs:

-
1
2
3
4
5
6
7
8
9
10
11
12
default constructor
9 8 7 6 5 4 3 2 1 0
Stack underflow
destructor

default constructor
copy constructor
9 8 7 6 5 4 3 2 1 0
assignment operator
8 7 6 5 4 3 2 1 0
destructor
destructor
-
- - - - - - -
- -
- - - - -
- - - - -
- - -
- - - - -
- - - - - - - - -
-
- - -
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/2018/06/13/Fundamentals-of-Python/index.html b/2018/06/13/Fundamentals-of-Python/index.html deleted file mode 100644 index 7f445aed..00000000 --- a/2018/06/13/Fundamentals-of-Python/index.html +++ /dev/null @@ -1,546 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - Fundamentals of Python | Liam's Blog - - - - - - - - - - - - -
-
- -
-
- - -
- - -

If history repeats itself, and the unexpected always happens, how incapable must Man be of learning from experience?—George Bernard Shaw.

-
- - -
- - - - -
-
- - -
- - 0% -
- - - - -
-
-
- - -
- - -
- - - -
- - - - - -
-

- Fundamentals of Python -

- - -
- - - - -
- - -

The Python Conceptual Hierarchy

    -
  1. Programs are composed of modules
  2. -
  3. Modules contain statements
  4. -
  5. Statements contain expressions
  6. -
  7. Expressions create and process objects
  8. -
- - -

Once create an object, you bind its operation set for all time, i.e. you can perform only string operations on a string and list operations on a list. This characteristic is typically known as dynamically typed - a model that keeps track of types for you automatically instead of requiring declaration code but it is also strongly typed, that means you can perform on an object only operations that are valid for its type.

-

Numbers

    -
  1. + performs addition
  2. -
  3. one asterisk () performs multiplication and two asterisks (*) are used for exponentiation, e.g.
    1
    2**2 # 2 to the power 2
  4. -
- -
- - - - - - -
- -
- - - - -
- - - - -
- - -
- - - - -
- - - - - - - - -
-
- - -
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/21/03/2018/C-Using-library-algorithms/index.html b/21/03/2018/C-Using-library-algorithms/index.html new file mode 100644 index 00000000..ce2feebf --- /dev/null +++ b/21/03/2018/C-Using-library-algorithms/index.html @@ -0,0 +1,852 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + C++ - Using library algorithms (Part 1) | Liam's Blog + + + + + + + + + + + + + + + + + + +
+
+ + + +
+
+
+
+ + +
+ + + + + + + + +
+ + + +
+ + + + + + + +
+ + + +

C++ - Using library algorithms (Part 1)

+ + + +
+ + + + + +
+ + + + + +

Generic algorithms for operations on strings

copy

Recalling the vcat function described in Vertical concatenation:

1
2
3
4
for (vector<string>::const_iterator i = bottom.begin(); i != bottom.end(); ++i)
{
ret.push_back((*i));
}

+

Using a for loop, all elements of vector bottom are copied and appended to the end of the vector ret.
An alternative method is to use the insert function:

1
ret.insert(ret.end(), bottom.begin(), bottom.end());

+

These two methods rely on the member functions of a specified container. A more general solution, using generic algorithm copy, to solve the same question,

1
copy(bottom.begin(), bottom.end(), back_inserter(ret));

+

The generic algorithms provided in the standard library implement classic algorithms via iterator operations. They do not depend on any specific type of container. The copy algorithm is an algorithm that writes elements to a container. It takes three iterators, of which, the first two iterators indicates the input range while the third iterator denotes the starting point of the destination sequence.

+

back_inserter() is a iterator adaptor which is a function that returns an appropriate iterator (has type of back_insert_iterator) according to the argument. All intertor adaptors are defined in header . In this case, back_inserter() takes a container as its arguments and yields an iterator that, when used as a destination, appends values to the container. But noting that

1
copy(bottom.begin(), bottom.end(), ret.end())

+

is not allowed as there is no element at ret.end.

+

After this function completes, the size of ret increases by bottom.size(). The copy function returns an iterator ((has type of back_insert_iterator) that refers to the next postion of the last element of ret.

+

find_if algorithm

Now, using another generic algorithm find_if, we can simplify the split function described in Taking strings apart.

+
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
// true if the argument is whitespace, false otherwise
bool space(char c)
{
return isspace(c);
}

// false is the argument is whitespace, true otherwise
book not_space(char c)
{
return !isspace(c);
}

vector<string> split(const_string &str)
{
typedef string::const_iterator iter;
vector<string> ret;

iter i = str.begin();
while(i != str.end())
{
// ignore leading blanks
i = find_if(i, str.end(), not_space)

// find end of next word
iter j = find_if(i, str.end(), space);

// copy the characters in [i, j)
if(i != str.end())
ret.push_back(string(i, j));
i = j;
}
return ret;
}
+

First, in this function, we use iterators instead of indices. The core algorithm is that use find_if to firstly find a nonwhiltespace character and a whitespace that closely follows, which determines the range of a word.

+

find_if algorithm takes three arguments, of which, first two are iterators that denotes a sequence while the third one is a predicate on characters. The algorithm calls the predicate to test each element starting from *i, and stops when the predicate returns true, i.e. the character is not whitespace in this case. It returns an iterator that refers to the first element that satisfies the predicate. If find_if failed to find an element that satifies the predicate, it returns its second arguments, i.e. str.end() in this case.

+

It has been observed that we didn’t use isspace() directly instead we write our own functions. This is because that isspace() is overloaded depending on arguments and can’t be passed as an argument to a template function.

+

Another new usage is string(i,j), which constructs a new string that copies the value from the range [i, j).

+

equal algorithm

Now we introduce another algorithm equal, which can simplify the isPalindromes function described in Exercise 5-10.

+
1
2
3
4
bool is_palindrome(const string &s)
{
return equal(s.begin(), s.end(), s.rbegin());
}
+

It is consise enough compared with the one I wrote before. The equal algorithm takes three iterators, of which first two indicate the range of the first sequence while the third iterator denotes the inital position of the second sequence. It compares these two sequence and returns true otherwise returns false.

+

It is known that begin() returns an iterator that refers to the first element in a container. On the contrary, rbegin() returns an reverse iterator that refers to the last element in a container. Similarly, rend() returns to an iterator that refers to the position that before the first element.

+

Finding URLs

Considering that one or more URLs are embedded in a string, we are requested to write a function that finds each URL. A URL is a sequence of characters of the form:

1
protocol-name://resource-name

+

where protocol-name contains only letters, and resource-name may consist of letters, digits, and certain punctuation characters(Koenig and Moo 2000).

+

To some extent, this exercise is similar to the split function. Though determining the range of such a string is much complex than finding the range for a word, the idea is the same. The strategy can be divided into three steps:

+
    +
  1. looking for the characters :// that might be a part of a URL.
  2. +
  3. if we find these characters, then it looks backward to find the protocol-name and determines the begining position of the URL; then, it looks forward to find the resource-name and determines the ending position of the URL.
  4. +
  5. finally, we store the URL according the two positions and continue searching for next URL until we finishes the whole document held in the single string.
  6. +
+

We starts from the last step, providing the first two steps have been completed.

+

function to find URLs

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
vector<string> find_urls(const string &s)
{
vector<string> ret;
typedef string::const_iterator iter;
iter b = s.begin(), e = s.end();

// look through the entire input
while (b != e)
{
// look for one or more letters followed by ://
b = url_beg(b, e);

// if we found it
if(b != e)
{
// get the rest of the URL
iter after = url_end(b, e);

// remember the URL
ret.push_back(string(b, after));

// advance b and check for more URLs on this line
b = after;
}
}
return ret;
}

+

Function url_beg responsibles for finding the :// and then finding the begining position of the URL accordingly. url_end responsibles for finding the end position of the URL based on the results of url_beg.

+

As mentioned earlier, string(b, after) constructs the URL with two iterators which denotes a range [b, after) of a sequence, i.e. the URL. In other words, b is the interator that denotes the first element of the URL while after denotes the position that one past the last element in the URL. After stores the URL into vector ret, we set the initial position for finding next URL as the end of the previous URL, with setting b = after.

+

Now we consider url_end function first, providing that the begining position of a URL has been found.

1
2
3
4
string::const_iterator  url_end(string::const_iterator b, string::const_iterator e)
{
return find_if(b, e, not_url_char);
}

+

We have explained the find_if algorithm in above. It calls the precidate not_url_char and test each element starting from the position where b denotes, and stops until finds the element that makes the predicate returns true. It returns an iterator that refers to the first element that satifies the predicate, and returns its second arguments e if can’t find an element that satisfies the predicate. Now let’s write the predicate:

1
2
3
4
5
6
7
8
bool not_url_char(char c)
{
// characters, in addition to alphanumerics, that can appear in a URL
static const string url_ch = "~;/?:@=&$-_.+!*'(),";

// see whether c can appear in a URL and return the negative
return !(isalnum(c) || find(url_ch.begin(), url_ch.end(), c) != url_ch.end());
}

+

Yeah, the statements in the function body seems to be both compact and informative. The first statement defines a const string that contains all punctuation characters that can appear in a URL. What’s new here is the storage class specifier static. Local variables that declaraed with specifier static have static storage duration and are initialized only the first time. On all other calls, the declarations are skipped. In other words, the variable exists starting from the first time declaration to when the program finishes. By doing so, the const string url_ch avoids being declared each time when the predicate is called.

+

The return statement contains three expressions:

+
    +
  • expression 1: isalnum(c). It returns true is c is a letter or digits, otherwise returns false.
  • +
  • expression 2: find(url_ch.begin(), url_ch.end(), c) != url_ch.end(). find algorithm takes three arguments, of which the first two are interators that denote the input sequence while the third one is a value to search for in the range. It returns an iterator to the first element in the range that compares equal to the value. If no such element is found, it returns the second arguments. In this case, if any character that equals to c, the expression is evaluated to true. If no character that matches with c, the expression is evaluated to false.
  • +
  • expression 3: !(expression 1 || expression 2). If and only if both expressions are evaluated to false, the expression 3 is evaluated to true.
  • +
+

In brief, if the scanned character is not a letter, not a digit, and not any punctuation character that can appear in a URL, it is regarded as not a URL character and it is the element that one past the last element of the URL.

+

Finally, we return to the first step, writing the function url_beg to find the initial position of the URL. One concern we have is that :// may not guaranntee a URL, for example, in the case that these characters appear at the end of a string. Therefore, we also need to make sure that there at least one or more letters before :// and at lease one character follows it.

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
string::const_iterator url_beg(string::const_iterator b, string::const_iterator e)
{
static const string sep = "://";
typedef string::const_iterator iter;

// i marks where the separator was found
iter i = b;
while((i = search(i, e, sep.begin(), sep.end())) != e)
{
// make sure the seperator isn't at the begining of the proticol-name
if(i != b && i + sep.size() != e)
{
// beg marks the begining of the protocol-name
iter beg = i;
while(beg != b && isalpha(beg[-1]))
--beg;

// is there at least one appropriate character before and after the sep
if (beg != i && !not_url_char(i[sep.size()]))
return beg;
}
// the seperator we found wasn't part of a URL advance i past this separator
i += sep.size();
}
return e;
}

+

Let’s analyse from the while loop. There appears a new algorithm search in the condition:

1
(i == search(i, e, sep.begin(), sep.end())) != e

+

The search algorithm takes four iterators, of which the first two indicate the sequence while the last two denotes the initial and final positions of the sequence to be searched for. It returns an iterator the refers to the first element of [sep.begin(), sep.end()) if such sequence can be found in [i, e), otherwise it returns e. Now we know the condition is evaluated to true if :// can be found in the range of [i, e).

+

Assuming that :// is found in the first iteration, i is assigned the iterator that denotes :. To make sure :// reveals a valid URL, we need to make sure it is neither the start of s nor the ending of s. This is managed by the first if statement inside of the while loop. The next is to find the begining position of the URL using following statements.

1
2
3
iter beg = i;
while(beg != b && isalpha(beg[-1]))
--beg;

+

beg[-1] accesses the element that before the one denoted by beg, it has the same effect as

1
*(beg - 1)

+

If the element denoted by beg is a letter within the range of [b, i), the loop continues with taking one position back each iteration. After the loop finishes, we got the initial position of the URL. However, the while body may fail to be executed even once, if the condition is evaluated to false the first time. Therefore, we need to verify another condition to make sure there exists at least one appropriate character before and after the sep.

1
2
if (beg != i && !not_url_char(i[sep.size]))
return beg;

+

The first expression beg != i ensures that there is at least one letter before :. In other words, the while loop above is executed at least once.

+

The sencond expression !not_url_char(i[sep.size]) ensures that there is at least one appropriate character follows ://. i[sep.size()] has the same effect as

1
*(i + sep.size())

+

which denotes the first character after sep (i.e. “://“).

+

At this phase, if the condition is evaluated to true, then the function returns the “qualified” iterator to the function caller, otherwise, the function keeps looking untill the last character.

+

A complete program

The files below show the complete program. A simple test can also be found after the program.

+

mainfunction.cpp

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
#include <iostream>
#include <vector>
#include <string>
#include "find_urls.h"

using std::cout; using std::endl;
using std::string; using std::vector;

int main()
{
vector<string> urls;
string doc{"A typical URL could have the form https://en.wikipedia.org/wiki/URL, "
"which indicates a protocol (http), a hostname (www.example.com), "
"and a file name (index.html). http://www.cplusplus.com/reference/algorithm/search/?kw=search"};
urls = find_urls(doc);

for (vector<string>::const_iterator iter = urls.begin(); iter != urls.end(); ++iter)
cout << *iter << endl;
}

+

find_urls.cpp

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
// function that finds and returns an URL
#include "find_urls.h"
#include <vector>
#include <string>
#include "delimit.h"

using std::vector;
using std::string;

vector<string> find_urls(const string &s)
{
vector<string> ret;
typedef string::const_iterator iter;
iter b = s.begin(), e = s.end();

// look through the entire input
while (b != e)
{
// look for one or more letters followed by ://
b = url_beg(b, e);

// if we found it
if(b != e)
{
// get the rest of the URL
iter after = url_end(b, e);

// remember the URL
ret.push_back(string(b, after));

// advance b and check for more URLs on this line
b = after;
}
}
return ret;
}

+

find_urls.h

1
2
3
4
5
6
7
8
9
#ifndef GUARD_FINDINGURLS_H
#define GUARD_FINDINGURLS_H

#include <vector>
#include <string>

std::vector<std::string> find_urls(const std::string &);

#endif /* GUARD_FINDINGURLS_H */

+

delimit.cpp

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
// contains three functions: not_url_char, url_beg, url_end
#include <string>
#include <algorithm>
#include "delimit.h"

using std::string; using std::find;
using std::find_if; using std::search;

// predicate on a char, check whether it is a char that can appear in a URL
bool not_url_char(char c)
{
// characters, in addition to alphanumerics, that can appear in a URL
static const string url_ch = "~;/?:@=&$-_.+!*'(),";

// see whether c can appear in a URL and return the negative
return !(isalnum(c) || find(url_ch.begin(), url_ch.end(), c) != url_ch.end());
}

// function that returns an iterator that refers to the first element of a URL
string::const_iterator url_beg(string::const_iterator b, string::const_iterator e)
{
static const string sep = "://";
typedef string::const_iterator iter;

// i marks where the separator was found
iter i = b;
while((i = search(i, e, sep.begin(), sep.end())) != e)
{
// make sure the seperator isn't at the begining of the proticol-name
if(i != b && i + sep.size() != e)
{
// beg marks the begining of the protocol-name
iter beg = i;
while(beg != b && isalpha(beg[-1]))
--beg;

// is there at least one appropriate character before and after the sep
if (beg != i && !not_url_char(i[sep.size()]))
return beg;
}
// the seperator we found wasn't part of a URL advance i past this separator
i += sep.size();
}
return e;
}

// function that returns an iterator that denotes the postion one past the last element
string::const_iterator url_end(string::const_iterator b, string::const_iterator e)
{
return find_if(b, e, not_url_char);
}

+

delimit.h

1
2
3
4
5
6
7
8
9
10
#ifndef GUARD_DELIMIT_H
#define GUARD_DELIMIT_H

#include <string>

bool not_url_char(char);
std::string::const_iterator url_beg(std::string::const_iterator, std::string::const_iterator);
std::string::const_iterator url_end(std::string::const_iterator, std::string::const_iterator);

#endif /* GUARD_DELIMIT_H */

+

Test results

1
2
https://en.wikipedia.org/wiki/URL,
http://www.cplusplus.com/reference/algorithm/search/?kw=search

+

The program just has function to roughly grab the URLs, but works as expected.

+
+

To be continued.

+ + +
+ + + + + + + + + + + +
+ + + +
+ + + +
+ +
+
+ + +
+ + + + + + + + + +
+ + + + + + + + + +
+
+ + + + +
+ + +
+ + + + +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/22/02/2018/Accelerated-C-Solutions-to-Exercises-Chapter-1/index.html b/22/02/2018/Accelerated-C-Solutions-to-Exercises-Chapter-1/index.html new file mode 100644 index 00000000..ec8ced6d --- /dev/null +++ b/22/02/2018/Accelerated-C-Solutions-to-Exercises-Chapter-1/index.html @@ -0,0 +1,874 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Accelerated C++ Solutions to Exercises(Chapter 1) | Liam's Blog + + + + + + + + + + + + + + + + + + +
+
+ + + +
+
+
+
+ + +
+ + + + + + + + +
+ + + +
+ + + + + + + +
+ + + +

Accelerated C++ Solutions to Exercises(Chapter 1)

+ + + +
+ + + + + +
+ + + + + +

Exercise 1-0

Compile, execute, and test the programs in this chapter.

+

Solution & Results

The first program:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
// ask for a person's name, and greet the person
#include <iostream>
#include <string>

int main()
{
// ask for the person's name
std::cout << "Please enter your first name: ";

// read the name
std::string name; // define name
std::cin >> name; // read into

// write a greeting
std::cout << "Hello, " << name << "!" << std:: endl;
return 0;
}

+

Test: type Liam according to the prompt and the console window displays as follows:

1
2
Please enter your first name: Liam
Hello, Liam!

+

The second 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
// ask for a person's name, and generate a framed greeting
#include <iostream>
#include <string>

int main()
{
std::cout << "Please enter your first name: ";
std::string name;
std::cin >> name;

// build the message that we intend to write
const std::string greeting = "Hello, " + name + "!";

// build the second and forth lines of the output
const std::string spaces(greeting.size(), ' ');
const std::string second = "* " + spaces + " *";

// build the first and fifth lines of the output
const std::string first(second.size(), '*');

// write it all
std::cout << std::endl;
std::cout << first << std::endl;
std::cout << second << std::endl;
std::cout << "* " << greeting << " *" << std::endl;
std::cout << second << std::endl;
std::cout << first << std::endl;

return 0;
}

+

Test: type Liam according to the prompt and the console window displays as follows:

1
2
3
4
5
6
7
Please enter your first name: Liam

****************
* *
* Hello, Liam! *
* *
****************

+

Analysis

See C++ - Working with strings.

+
+

Exercise 1-1

Are the following definitions valid? Why or why not?

1
2
const std::string hello = "Hello";
const std::string message = hello + ", world" + "!";

+

Solution & Results

Yes, above definitions are valid.

+

The first statement defines a string type variable named hello and initialize the variable with copying string literals Hello into it. The second statement defines a variable named message and copy the value of the right side of the = into it. The right side is an expression that the concatenation operator + operates on the object hello and two string literals. First, the operator is left-associative and hence:

1
2
const std::string message = hello + ", world" + "!";
= (hello + ", world") + "!";

+

Due to the fact that the result of (hello + “, world”) is also a string type object, the whole expression is simply to concatenate a string and a string literals, which is legal and valid. The test is shown below:

1
2
3
4
5
6
7
8
9
10
11
12
13
#include <iostream>
#include <string>
using std::cout; using std::endl; using std::string;

int main()
{
const std::string hello = "Hello";
const std::string message = hello + ", world" + "!";
cout << hello << endl;
cout << message << endl;
return 0;

}

+

The program runs ok and display results on the console window as below:

1
2
Hello
Hello, world!

+

Analysis

As stated by Lippman ect. (2012): “When we mix strings and string or character literals, at least one operand to each + operator must be of string type”. For more analysis, please see C++ - Working with strings.

+
+

Exercise 1-2

Are the following definitions valid? Why or why not?

1
2
const std::string exclam = "!";
const std::string message = "Hello" + ", world" + exclam;

+

Solution & Results

The definition of exclam is valid but the definition of message is illegal and hence invalid. This is because that the operator + is left-associative and cannot concatenate two string literals. Follow program shows my test:

1
2
3
4
5
6
7
8
9
10
11
12
13
#include <iostream>
#include <string>
using std::cout; using std::endl; using std::string;

int main()
{
const std::string exclam = "!";
const std::string message = "Hello" + ", world" + exclam;
cout << exclam << endl;
cout << message << endl;
return 0;

}

+

As expected, the compilation reports errors as below:

1
invalid operands of types 'const char [6]' and 'const char [8]' to binary 'operator+'.

+

Analysis

See Exercise 1-1 and
C++ - Working with strings.

+
+

Exercise 1-3

Is the following program valid? If so, what does it do? If not, why not?

1
2
3
4
5
6
7
8
9
10
11
#include <iostream>
#include <string>

int main()
{
{ const std::string s = "a string";
std::cout << s << std::endl; }
{ const std::string s = "another string";
std::cout << s << std::endl; }
return 0;
}

+

Solution & Results

Yes, it is a valid program.
The program intends to print two different strings, both of which have the same name s within the main function body.
I firstly present the result when running the program:

1
2
a string
another string

+

These two varibles are not conflict because their names have different scopes that formed by two pairs of curly braces. Specifically:

+
    +
  • Both variables are local variables as they are inside of the main function.
  • +
  • The first s is visible from its declaration until the end of its scope, that is, the scope formed by the first nested curly braces.
  • +
  • The second s is visible from its declaration until the end of the second nested curly braces.
  • +
  • Two names refer to different entities in different scope.
  • +
+

From the perspective of memory management, the first variable exists only when the part of the program within the first nested braces is executing, and disappears and returns the memory it occupied once the computer reaches the end of the braces. It has limited lifetime and so does the second variable.

+

Analysis

See C++ - Working with strings.

+
+

Exercise 1-4

Question 1

Is the following program valid?

1
2
3
4
5
6
7
8
9
10
11
#include <iostream>
#include <string>

int main()
{
{ const std::string s = "a string"; //outer scope
std::cout << s << std::endl;
{ const std::string s = "another string"; // inner scope
std::cout << s << std::endl; }}
return 0;
}

+

Solution & Results

Yes, the program is valid.
In constrast with the last program, the scopes of two names are not independent with eachother but are nested:

+
    +
  • The scope of the first s is the outer scope, containing the other scope, that is, the inner scope where the second s is in.
  • +
  • The name that declared in the outer scope can be can be accessed and reused in the nested scope, i.e. inner scope.
  • +
+

Therefore, the logic of this program can be described as:

+
    +
  1. the first variable is defined and initialized with string literals “a string” and is printed out in the following statement.
  2. +
  3. “the variable” is redefined in the inner scope and initialized with string literals another string. Then, it is printed out in the following step.
  4. +
  5. the second s is destroyed at the end of its scope, that is, the first right brace (}).
  6. +
  7. the first s is still available after the first right brace, but is destroyed once the computer reaches the second right brace.
  8. +
+

Note that two variables are different and the second variable doesn’t overwriting the first one though they both use the name s. To confirm this, I simply add one statement between the first right curly brace and second right curly brace:

1
std::cout << s <<std::endl;

+

Then, I run the program and it yields:

1
2
3
a string
another string
a string

+

Question 2

what if we change } } to };} in the third line from the end?

1
2
3
4
5
6
7
8
9
10
11
#include <iostream>
#include <string>

int main()
{
{ const std::string s = "a string";
std::cout << s << std::endl;
{ const std::string s = "another string";
std::cout << s << std::endl; };}
return 0;
}

+

Solution & Results

The program is still valid after adding an semicolon betwween the first and second right curly brace. The semicolon typically working as a statement terminator in C++. In this case, it does nothing except forming a null statement. This program leads to the same results as the program that without adding a semicolon.

1
2
a string
another string

+

Analysis

I didn’t found very good interpretations about the role of the semicolon in C++. For more analysis, please move to What is the semicolon in C++? and What is the function of semicolon in C++? , where some good answers have been provided by the forum users.

+
+

Exercise 1-5

Is this program valid? If so, what does it do? If not, say why not, and rewrite it to be valid.

1
2
3
4
5
6
7
8
9
10
11
12
13
#include <iostream>
#include <string>

int main()
{
{ std::string s = "a string";
{ std::string x = s + ", really";
std::cout << s << std::endl;
}
std::cout << x << std::endl;
}
return 0;
}

+

Solution & Results

No, this program is invalid. S
imilarly as last two exercises, this question tries to test my understanding on the scope of a name. The scope of name s is outter scope and can be accessed in the inner scope where the variable x is defined. Therefore, it is correct that initializing x with a expression which oncatenates a string and string literals. It is also ok to output s inside the nested scope. However, the statement std::cout << x << std::endl; would be invalid due to the fact that x doesn’t exist anymore once the computer reaches the end of its scope, that is, the first right curly brace. As expected, when running this program, error occurs:

1
'x' was not declared in this scope

+

To correct it, we can simply put the statement:std::cout << x << std::endl; into the inner scope, or directly remove the nested curly braces as long as the name is not redefined in a same scope. Both corrections work well and following results can be seen on the console window:

1
2
a string
a string, really

+

Analysis

See Exercise 1-3, Exercise 1-4 and
C++ - Working with strings.

+
+

Exercise 1-6

What does the following program do if, when it asks you for input, you type two names(for example, Samuel Beckett)? Predict the behavior before running the program, then try it.

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
#include <iostream>
#include <string>

int main()
{ // prompt that asks for input
std::cout << "What is your name? ";

// define an string type variable named **name** which is empty initially.
std::string name;

// read the contents into **name**
std::cin >> name;

// output greetings as well as asking for input
std::cout << "Hello, " << name
<< std::endl << "And what is yours?";

// read new contents into the same variable
std::cin >> name;

// output greeting
std::cout << "Hello, " << name
<< "; nice to meet you too!" << std::endl;
return 0;
}

+

Solution & Results

The program intends to create a greeting conversation between two people. For For clarity, I added comments for each statement first. Let’s analyse this program step by step:

+
    +
  1. once click the running button, the computer reads the first statement and stores the contents What is your name? into buffer.
  2. +
  3. the cin statement triggers the flush of cout, and user can type two names Samuel Beckett according to the prompt. I predict following contents would be written on the output device:

    +
    1
    What is your name? Samuel Beckett
    +
  4. +
  5. then, the cin reads from the first character untill it encounters the whitespace. Therefore, only Samuel is stored into name.

    +
  6. +
  7. the fouth statement would flush the cout because of the manipulator endl. Then, the console windows should have following outputs:

    +
    1
    Hello, Samuel
    +
  8. +
  9. the fifth statement cin flushes buffer and following sentence will be printed out straight after above contents.

    +
    1
    And what is yours?
    +
  10. +
  11. then, cin starts reading the rest content Beckett and stores into name. The new name Beckett rewrites this variable.

    +
  12. +
  13. finally, the last buffer flush happens once the computer reads std::endl. Following contents are expected to appear on the console window.

    +
    1
    Hello, Beckett; nice to meet you too!
    +
  14. +
  15. finished.

    +
  16. +
+

As ecpected, the final results of this program is shown as follows:

1
2
3
What is your name? Samuel Beckett
Hello, Samuel
And what is yours?Hello, Beckett; nice to meet you too!

+

Analysis

I am not entirly sure about my analysis, and may update this in the futuer if I get new ideas.

+
+

References

Koenig, A. and Moo, B.E., 2000. Accelerated C++.

+

Lippman, S.B., Lajoie, J. and Moo, B.E., 2013. C++ Primer, 5th edt.

+ + +
+ + + + + + + + + + + +
+ + + +
+ + + +
+ +
+
+ + +
+ + + + + + + + + +
+ + + + + + + + + +
+
+ + + + +
+ + +
+ + + + +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/22/02/2018/Accelerated-C-Solutions-to-Exercises-Chapter-2/index.html b/22/02/2018/Accelerated-C-Solutions-to-Exercises-Chapter-2/index.html new file mode 100644 index 00000000..6dfe2430 --- /dev/null +++ b/22/02/2018/Accelerated-C-Solutions-to-Exercises-Chapter-2/index.html @@ -0,0 +1,815 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Accelerated C++ Solutions to Exercises(Chapter 2 Part 1) | Liam's Blog + + + + + + + + + + + + + + + + + + +
+
+ + + +
+
+
+
+ + +
+ + + + + + + + +
+ + + +
+ + + + + + + +
+ + + +

Accelerated C++ Solutions to Exercises(Chapter 2 Part 1)

+ + + +
+ + + + + +
+ + + + + +

Exercise 2-0

Compile and run the program presented in this chapter

+

Solution & Results

This exercise has been accomplished in C++ - Looping and counting with detailed explination.

+
+

Exercise 2-1

Change the framing program so that it writes its greeting with no separation from the frame.

+

Solution & Results

This can be easily accomplished by changing the number of the blanks to 0.

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
#include <iostream>
#include <string>

// using namespace std and names
using std::cout; using std::cin;
using std::endl; using std::string;

int main()
{
// ask for the person's name
cout << "Please enter your first name: ";

// read the name
string name;
cin >> name;

// build the message that we intend to write
const string greeting = "Hello, " + name + "!";

// the number of blanks surrounding the greeting
// change to 0 to meet the requirement
const int pad = 0;

// the number of rows and columns
const int rows = pad * 2 + 3;
const string::size_type cols = greeting.size() + pad * 2 + 2;

// write a blank line to separate the output from the input
cout << endl;

// write rows rows of output
// invariant: we have written r rows so far
for (int r = 0; r != rows; ++r)
{
string::size_type c = 0;
// we have written c characters so far in the current row
while (c != cols)
{
// are we on the border?
if (r == 0 || r == rows - 1 ||
c == 0 || c == cols - 1)
{
cout << "*";
++c;
}
else
{ // is it time to write the greeting?
if (r == pad + 1 && c == pad + 1)
{
cout << greeting;
c += greeting.size();
}
else
{
cout <<" ";
++c;
}
}
}
cout << endl;
}
return 0;
}

+

Again, once we typed Bruce after the input prompt, the program writes below outputs on the console window

1
2
3
4
5
Please enter your first name: Bruce

***************
*Hello, Bruce!*
***************

+

Analysis

See deatiled analysis in C++ - Looping and counting.

+
+

Exercise 2-2, 2-3

2-2. Change the framing program so that it uses a different amount of space to seperate sides from the greeting than it uses to seperate the top and botton borders from the greeting.

+

2-3. Rewrite the framing program to ask the user to supply the amount of spacing to leave between the frame and the greeting.

+

These two exercises will be answered together.

+

Solution & Results

It has been seen from the program (as shown in Exercise 2-1), the variable pad controls the spaces. Therefore, it is necessary to replace the pad with two seperate variables for controling the spaces on the leftside and rightside, and the spaces on the upside and downside, respectively. For example, I define two variables with initializers 2 and 3 seperately

1
2
const int lr_sides_pad  = 2; // controls left and right blanks
const int tb_sides_pad = 3; // controls top and bottom blanks

+

Correspondingly, the size of rows becomes

1
const int rows = tb_sides_pad * 2 + 3

+

and the size of cols becomes

1
const string::size_type cols = greeting.size() + lr_sides_pad * 2 + 2;

+

In addition, the condition for determining the position of the greeting changes to

1
r == tb_sides_pad + 1 && c == lr_sides_pad + 1

+

Up to now, I have replaced all pad with above two new variables. However, the frame size is still predetermined in the program and not very flexible. We further change it so that the size can meet each user’s preference. We need to redifine the variables and assign user-defined values to them.

1
2
3
4
5
6
7
8
9
10
11
12
13
// ask for the number of blanks that on the left or right side
cout << "Please enter the number of spaces between the greeting and the left border (or the right boder): ";

// read the number of blanks that on the left or right side
int lr_sides_pad;
cin >> lr_sides_pad;

// ask for the number of blanks that on the upper or below side
cout << "Please enter the number of spaces between the greeting and the top border (or the bottom boder): ";

// read the number of blanks that on the left or right side
int tb_sides_pad;
cin >> tb_sides_pad;

+

Now the program becomes

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
65
66
67
68
69
70
71
72
73
74
#include <iostream>
#include <string>

// using namespace std and names
using std::cout; using std::cin;
using std::endl; using std::string;

int main()
{
// ask for the person's name
cout << "Please enter your first name: ";

// read the name
string name;
cin >> name;

// build the message that we intend to write
const string greeting = "Hello, " + name + "!";

// ask for the number of blanks that on the left or right side
cout << "Please enter the number of spaces between the greeting and the left border (or the right boder): ";

// read the number of blanks that on the left or right side
int lr_sides_pad;
cin >> lr_sides_pad;

// ask for the number of blanks that on the upper or below side
cout << "Please enter the number of spaces between the greeting and the top border (or the bottom boder): ";

// read the number of blanks that on the left or right side
int tb_sides_pad;
cin >> tb_sides_pad;

// the number of rows and columns
const int rows = tb_sides_pad * 2 + 3;
const string::size_type cols = greeting.size() + lr_sides_pad * 2 + 2;

// write a blank line to separate the output from the input
cout << endl;

// write rows rows of output
// invariant: we have written r rows so far
for (int r = 0; r != rows; ++r)
{
string::size_type c = 0;
// we have written c characters so far in the current row
while (c != cols)
{
// are we on the border?
if (r == 0 || r == rows - 1 ||
c == 0 || c == cols - 1)
{
cout << "*";
++c;
}
else
{ // is it time to write the greeting?
if (r == tb_sides_pad + 1 &&
c == lr_sides_pad + 1)
{
cout << greeting;
c += greeting.size();
}
else
{
cout <<" ";
++c;
}
}
}
cout << endl;
}
return 0;
}

+

I performed three tests and present the results here

+

Test 1: input: Bruce 2 3

1
2
3
4
5
6
7
8
9
10
11
12
13
Please enter your first name: Bruce
Please enter the number of spaces between the greeting and the left border (or the right border): 2
Please enter the number of spaces between the greeting and the top border (or the bottom border): 3

*******************
* *
* *
* *
* Hello, Bruce! *
* *
* *
* *
*******************
+

Test 2: input: Bruce 4 0

1
2
3
4
5
6
7
Please enter your first name: Bruce
Please enter the number of spaces between the greeting and the left border (or the right border): 4
Please enter the number of spaces between the greeting and the top border (or the bottom border): 0

***********************
* Hello, Bruce! *
***********************
+

Test 3: input: Bruce 5 5

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
Please enter your first name: Bruce
Please enter the number of spaces between the greeting and the left border (or the right border): 5
Please enter the number of spaces between the greeting and the top border (or the bottom border): 5

*************************
* *
* *
* *
* *
* *
* Hello, Bruce! *
* *
* *
* *
* *
* *
*************************
+

Analysis

See deatiled analysis in C++ - Looping and counting.

+
+

To be continued.

+ + +
+ + + + + + + + + + + +
+ + + +
+ + + +
+ +
+
+ + +
+ + + + + + + + + +
+ + + + + + + + + +
+
+ + + + +
+ + +
+ + + + +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/22/03/2018/C-Using-library-algorithms-Part-2/index.html b/22/03/2018/C-Using-library-algorithms-Part-2/index.html new file mode 100644 index 00000000..a59569c2 --- /dev/null +++ b/22/03/2018/C-Using-library-algorithms-Part-2/index.html @@ -0,0 +1,872 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + C++ - Using library algorithms (Part 2) | Liam's Blog + + + + + + + + + + + + + + + + + + +
+
+ + + +
+
+
+
+ + +
+ + + + + + + + +
+ + + +
+ + + + + + + +
+ + + +

C++ - Using library algorithms (Part 2)

+ + + +
+ + + + + +
+ + + + + +

Revisit the grading program

This section redesigns the grading program described in Organizing programs with data structures. The new program is required to include extra two grading schemes(Koenig and Moo 2000):

+

1. using the average instead of the median, and treating those assignments that the student failed to turn in as zero.
2. using the median of only the assignments that the student actually submitted.

+

Further, the program should solve following problems based on these grading schemes:

+

1. reading all the student records, separating the students who did all the homework from the others.
2. apply each of the grading schemes to all the students in each group, and report the median grade of each group.

+

Now, let’s follow the instructions and finish this program step by step.

+

Reading and Separating the students records

According to the first problem, we need to read and separate the students records into two groups, one group of which includes students who did all homeworks, and another group includes students who didn’t submit all homeworks. The read function has been introduced before and hence no further analysis. What is the next is to use a predict on students’ records and store the records separately based on the check results.

1
2
3
4
bool did_all_hw(const Student_info &s)
{
return ((find(s.homework.begin(), s.homework.end(), 0)) == s.homework.end());
}

+

The return statements in the function body means that if none of the homework grades equal to 0, the function returns true. The idea behind it is that overdue homeworks are given 0 grades.

+

Now this part can be accomplished with following 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
// to hold two groups of students records
vector<Student_info> did, didnt;
Student_info student;

// read all the records and separating them based on whether all homework was done
while(read(cin, student))
{
if(did_all_hw(student))
did.push_back(student);
else
didnt.push_back(student);
}

// check that both groups contain dada
if (did.empty())
{
cout << "No student did all the homework!" << endl;
return 1;
}
if (didnt.empty())
{
cout << "Every student did all the homework!" << endl;
return 1;
}

+

Three grading schemes

Before we go into the second problem, we need to solve three grading schemes first. All schemes compute the final grade as the weighted average of midterm exam grade, final exam grade, and homework grade. The grade function below returns the final grade if it is called.

1
2
3
4
double grade(double midterm, double final, double homework)
{
return 0.2*midterm + 0.4*final + 0.4*homework;
}

+

Midterm grade and final exam grade are fixed values once the information are read in. However, the homework grade can be computed using different methods depending on the grading schemes. Specifically:

+
    +
  1. scheme 1: computes the homework grade as the median value of homework grades.
  2. +
  3. scheme 2: computes the homework grade as the average value of homework grades.
  4. +
  5. scheme 3: computes the homework grade as the median value of homework grades excluding the zero grades (i.e. grades for overdue homeworks).
  6. +
+

In addition, if one did not do homework at all, his homework grade would be set to 0.

+

Fundamentally, there are two types of homework grade, one is the median value and another is the average value. Here are the two functions that return the median value and average value of a sequence of double values stored in a vector, respectively.

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
// fundermental functions 1: returns the median value of vector<double>
double median(vector<double> vec)
{
// get the size of the vector
typedef vector<double>::size_type vec_size;
vec_size size = vec.size();

// check whether the empty is empty
if (size == 0)
throw domain_error("median of an empty vector");

// sort the grades
sort(vec.begin(), vec.end());

// compute the median homework grade
vec_size mid = size/2;
return size % 2 == 0 ? (vec[mid] + vec[mid - 1])/2 : vec[mid];
}

// fundermental functions 2: returns the average value of vector<double>
double average(const vector<double> &v)
{
// check whether the empty is empty
if (v.empty())
throw domain_error("average of an empty vector");

return accumulate(v.begin(), v.end(), 0.0) / v.size();
}

+

What’s the new idea in above functions is that we compute the average using the accumulate algorithm. The accomulate is defined in the staandard header . It takes three arguments, the first two of which denotes a range of values to be summed while the third arguments gives the initial value for the summation. It is worth noting that we uses 0.0 rather than 0 due to the fact the type of the resulted sum is the type of the third argument.

+

Note that I slightly changes the average function provided in the book for the purpose of avoiding the case that v.size() == 0. As mentioned earlier, if one did not submit one or more homeworks, he would get 0 grades for the corresponding homeworks. But, the program may encounter the case that there is no any inputs for the homeworks, e.g. when someone did not do homeworks at all. To response this case, the homework grade is set to 0 directly. We need to catch the exception and handle the case in next step.

+

The median grading scheme

We are familar with the first grading scheme, that is, the original scheme we used to compute the final grade, in previous chapters. For convenience sake, I renamed the original functions grade as median_grade (as shown below).

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
/ grading scheme 1: final grade is based on the median homework grade
double median_grade(const Student_info &s)
{
return median_grade(s.midterm, s.final, s.homework);
}

// grading scheme 1: overloaded median_grade function
double median_grade(double midterm, double final, const vector<double> &hw)
{
if (hw.size() == 0)
throw domain_error("student has done no homework");
return grade(midterm, final, median(hw));
}

// grading scheme 1: auxiliary median_grade function
double median_grade_aux(const Student_info &s)
{
try{
return median_grade(s);
}catch (domain_error){
// students who did no homework at all, get 0 homework grade
return grade(s.midterm, s.final, 0);
}
}

+

It can be observed that a new function, median_grade_aux ,is included for the median grading scheme. The reason is that we’ll pass the median_gade as an argument, however, we cann’t pass an overloaded function easily. Therefore, we define an auxiliary function and handle the exception in this function. As analysed above, the function return a 0 homework grade once catches an exception.

+

The average grading scheme

The average based final grade is computed exactly the same as the median version as long as we replace the median value as the average value. The function handles the exception in the same manner.

1
2
3
4
5
6
7
8
9
10
// grading scheme 2: final grade is based on average homework grades
double average_grade(const Student_info &s)
{
try{
return grade(s.midterm, s.final, average(s.homework));
}catch (domain_error){
// students who did no homework at all, get 0 homework grade
return grade(s.midterm, s.final, 0);
}
}

+

The optimistic_median grading scheme

The third grading scheme is named as optimistic median as it ignores the zero homework grades and consequently improves students’ overall performances. Following code shows how this function works.

1
2
3
4
5
6
7
8
9
10
11
12
// grading scheme 3: final grade is based on median of the completed homework grades,
// and students who did no homework at all will get 0 homework grade
double optimistic_median(const Student_info &s)
{
vector<double> nonzero;
remove_copy(s.homework.begin(), s.homework.end(), back_inserter(nonzero), 0);

if(nonzero.empty())
return grade(s.midterm, s.final, 0);
else
return grade(s.midterm, s.final, median(nonzero));
}

+

remove_copy algorithm takes four arguments. It searches from the range denoted by first two iterators, and finds all values that match a value given by its fourth argument. Then, these values are “removed” and the remaining elements are copied into a new vector nonzero. It doesn’t change the input sequence. In addition, back_inserter is applied to append the copied values to the new vector, which ensures that the space is enough for holding all the elements.

+

Analyze the grades

The next step is to apply each of above schemes to each group, that is, to compute the median of each group. Let’s have a look at the function that returns the median grade of a vector students based on the median homework grade.

1
2
3
4
5
6
7
double median_analysis(const vector<Student_info> &students)
{
vector<double> grades;

transform(students.begin(), students.end(), back_inserter(grades), median_grade_aux);
return median(grades);
}

+

The function introduces a new algorithm transform which calls median_grade_aux, namely the transform function, and stores the result into a new vector. The first two iterators specify a range of elements to transform. back_inserter provides the destination and ensures that there is enough for the elements. Finally, the function returns the median value of the final grades.

+

Analogously, we can define another two functions that return average homework grade based median grade and optimistic median homework grade based median grade.

+
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
// average
double average_analysis(const vector<Student_info> &students)
{
vector<double> grades;

transform(students.begin(), students.end(), back_inserter(grades), average_grade);
return median(grades);
}

// median of the completed homework
double optimistic_median_analysis(const vector<Student_info> &students)
{
vector<double> grades;
transform(students.begin(), students.end(), back_inserter(grades), optimistic_median);
return median(grades);
}
+

Report the analysis

As described above, we need to compute three types of median grade for both two groups. In addition, we would better to generate a report such that we can compare two groups regarding to one specific type of median grade. The code below shows how we incorporate these driven factors into a analysis function:

1
2
3
4
5
6
7
8
void write_analysis(ostream &out, const string &name,
double analysis(const vector<Student_info> &),
const vector<Student_info> &did,
const vector<Student_info> &didnt)
{
out << name << ": median(did) = " << analysis(did)
<< ": median(didnt) = " << analysis(didnt) << endl;
}

+

This function takes four arguments, of which: the first is an object of the outstream; the second is a function; the third and fourth arguments are two objects of vector. What’ new here is that when passing a function as a parameter, we declare the parameter exactly as the same as ddeclare the function itself. Apparently, we’ll pass three analysis function defined above to this write analysis function.

+

A complete program

Now, we can complete the main function:

+

mainfunction.cpp

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
#include <iostream>	    // to get the declaration of cin, cout, endl
#include <vector> // to get the declaration of vector
#include "Student_info.h" // to get the declaration of Student_info
#include "did_all_hw.h" // to get the declatation of the predicate on students' records
#include "grades_analysis.h"// to get the declaration of three analysis function
#include "write_analysis.h" // to get the declaration of write_analysis function

using std::vector; using std::cout;
using std::cin; using std::endl;

int main()
{
// students who did and didn't do all their homework
vector<Student_info> did, didnt;

// read the student records and partition time
Student_info student;
while(read(cin, student))
{
if(did_all_hw(student))
did.push_back(student);
else
didnt.push_back(student);
}
// verify thatthe analyses will show us something
if(did.empty())
{
cout << "No student did all the homework!" << endl;
return 1;
}
if(didnt.empty())
{
cout << "Every student did all the homework!" << endl;
return 1;
}

// do the analysis
write_analysis(cout, "median", median_analysis, did, didnt);
write_analysis(cout, "average", average_analysis, did, didnt);
write_analysis(cout, "median of homework turned in", optimistic_median_analysis, did, didnt);

return 0;
}

+

I had seperate other functions into different files according to my own preference:

+
    +
  1. mainfunction.cpp (as shown above).
  2. +
  3. did_all_hw.cpp, did_all_hw.h: including the function did_all_hw.
  4. +
  5. gradingSchemes.cpp, gradingSchemes.h: covering following functions

    +
      +
    • grade, median, average, median_grade, median_grade_aux, average_grade, optimistic_median.
    • +
    +
  6. +
  7. grades_analysis.cpp, grades_analysis.h: covering following functions
      +
    • median_analysis, average_analysis, optimistic_grade_median.
    • +
    +
  8. +
  9. write_analysis.cpp, write_analysis.h: including the function write_analysis.
  10. +
  11. Student_info.cpp, Stuent_info.h: covering following functions
      +
    • compare, read, read_hw.
    • +
    +
  12. +
+

All profiles are presented below in order.

+

did_all_hw.cpp

1
2
3
4
5
6
7
8
9
10
#include <algorithm>		// to get the declaration of find
#include "Student_info.h" // to get the declaration of Student_info
#include "did_all_hw.h" // to get the declaration of did_all_hw itself

using std::find;

bool did_all_hw(const Student_info &s)
{
return ((find(s.homework.begin(), s.homework.end(), 0)) == s.homework.end());
}

+

did_all_hw.h

1
2
3
4
5
6
7
8
#ifndef GUARD_DID_ALL_HW_H
#define GUARD_DID_ALL_HW_H

#include "Student_info.h"

bool did_all_hw(const Student_info &s);

#endif /* GUARD_DID_ALL_HW_H */

+

gradingSchemes.cpp

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
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
#include <algorithm>	    // to get the declaration of remove_copy
#include <numeric> // to get the declaration of accumulate
#include <stdexcept> // to get the declaration of domain_error
#include <vector> // to get the declaration of vector
#include "Student_info.h" // to get the declaration of Student_info
#include "gradingSchemes.h" // to get the declaration of all functions here to keep consistent

using std::domain_error; using std::istream;
using std::vector; using std::sort;
using std::accumulate;

// final grade function returns weighted average of midterm exam grade,
// final exam grade, and homework grade which will be computed
// using different methods depending on grading schemes
double grade(double midterm, double final, double homework)
{
return 0.2*midterm + 0.4*final + 0.4*homework;
}

// fundermental functions 1: returns the median value of vector<double>
double median(vector<double> vec)
{
// get the size of the vector
typedef vector<double>::size_type vec_size;
vec_size size = vec.size();

// check whether the empty is empty
if (size == 0)
throw domain_error("median of an empty vector");

// sort the grades
sort(vec.begin(), vec.end());

// compute the median homework grade
vec_size mid = size/2;
return size % 2 == 0 ? (vec[mid] + vec[mid - 1])/2 : vec[mid];
}

// fundermental functions 2: returns the average value of vector<double>
double average(const vector<double> &v)
{
// check whether the empty is empty
if (v.empty())
{ throw domain_error("average of an empty vector");}

return accumulate(v.begin(), v.end(), 0.0) / v.size();
}


// grading scheme 1: final grade is based on the median homework grade
double median_grade(const Student_info &s)
{
return median_grade(s.midterm, s.final, s.homework);
}

// grading scheme 1: overloaded median_grade function
double median_grade(double midterm, double final, const vector<double> &hw)
{
if (hw.size() == 0)
throw domain_error("student has done no homework");
return grade(midterm, final, median(hw));
}

// grading scheme 1: auxiliary median_grade function
double median_grade_aux(const Student_info &s)
{
try{
return median_grade(s);
}catch (domain_error){
// students who did no homework at all, get 0 homework grade
return grade(s.midterm, s.final, 0);
}
}

// grading scheme 2: final grade is based on average homework grades
double average_grade(const Student_info &s)
{
try{
return grade(s.midterm, s.final, average(s.homework));
}catch (domain_error){
// students who did no homework at all, get 0 homework grade
return grade(s.midterm, s.final, 0);
}
}

// grading scheme 3: final grade is based on median of the completed homework grades,
// and students who did no homework at all will get 0 homework grade
double optimistic_median(const Student_info &s)
{
vector<double> nonzero;
remove_copy(s.homework.begin(), s.homework.end(), back_inserter(nonzero), 0);

if(nonzero.empty())
return grade(s.midterm, s.final, 0);
else
return grade(s.midterm, s.final, median(nonzero));
}

+

gradingScheme.h

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
#ifndef GUARD_GRADING_SCHEMES_H
#define GUARD_GRADING_SCHEMES_H

#include<vector>
#include "Student_info.h"

double grade(double midterm, double final, double homework);
double median(std::vector<double> vec);
double average(const std::vector<double> &v);
double median_grade(const Student_info &s);
double median_grade(double midterm, double final, const std::vector<double> &hw);
double median_grade_aux(const Student_info &s);
double average_grade(const Student_info &s);
double optimistic_median(const Student_info &s);

#endif /* GUARD_GRADING_SCHEMES_H */

+

grades_analysis.cpp

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
#include <vector>		// to get the declaration of vector
#include <iterator> // to get the declaration of back_inserter
#include <stdexcept> // to get the declaration of domain_error
#include <algorithm> // to get the declaration of transform
#include "Student_info.h" // to get the declaration of Student_info
#include "grades_analysis.h" // to get the declaration of all functions here to keep consistent
#include "gradingSchemes.h" // to get the declaration of median_grade_aux, average_grade, optimistic_median

using std::vector; using std::transform;
using std::domain_error; using std::back_inserter;

// median
double median_analysis(const vector<Student_info> &students)
{
vector<double> grades;

transform(students.begin(), students.end(), back_inserter(grades), median_grade_aux);
return median(grades);
}

// average
double average_analysis(const vector<Student_info> &students)
{
vector<double> grades;

transform(students.begin(), students.end(), back_inserter(grades), average_grade);
return median(grades);
}

// median of the completed homework
double optimistic_median_analysis(const vector<Student_info> &students)
{
vector<double> grades;
transform(students.begin(), students.end(), back_inserter(grades), optimistic_median);
return median(grades);
}

+

grades_analysis.h

1
2
3
4
5
6
7
8
9
10
11
#ifndef GUARD_GRADES_ANALYSIS_H
#define GUARD_GRADES_ANALYSIS_H

#include <vector>
#include "Student_info.h"

double median_analysis(const std::vector<Student_info> &students);
double average_analysis(const std::vector<Student_info> &students);
double optimistic_median_analysis(const std::vector<Student_info> &students);

#endif /* GUARD_GRADES_ANALYSIS_H */

+

write_analysis.cpp

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
#include <iostream>		// to get the declaration of cout, endl, ostream
#include <string> // to get the declaration of string
#include <vector> // to get the declaration of vector
#include "Student_info.h" // to get the declatation of Student_info
#include "write_analysis.h" // to get the declatation of write_analysis itself

using std::cout; using std::endl;
using std::string; using std::vector;
using std::ostream;

void write_analysis(ostream &out, const string &name,
double analysis(const vector<Student_info> &),
const vector<Student_info> &did,
const vector<Student_info> &didnt)
{
out << name << ": median(did) = " << analysis(did)
<< ": median(didnt) = " << analysis(didnt) << endl;
}

+

write_analysis.h

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
#ifndef GUARD_WRITE_ANALYSIS_H
#define GUARD_WRITE_ANALYSIS_H

#include <iostream>
#include <string>
#include <vector>
#include "Student_info.h"

void write_analysis(std::ostream &out, const std::string &name,
double analysis(const std::vector<Student_info> &),
const std::vector<Student_info> &did,
const std::vector<Student_info> &didnt);


#endif /* GUARD_WRITE_ANALYSIS_H */

+

Student_info.cpp

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
#include "Student_info.h"
using std::vector; using std::istream;

// argument to the function sort
bool compare(const Student_info &x, const Student_info &y)
{
return x.name < y.name;
}

// read the info
istream & read(istream &is, Student_info &s)
{
// reads and store the student's name, midterm and final exam grades
is >> s.name >> s.midterm >> s.final;

// reads and store all homework grades
read_hw(is, s.homework);
return is;
}

// read all homework grades
istream & read_hw(istream &in, vector<double> &hw)
{
if (in)
{
//get rid of previous contents
hw.clear();

// read homework grades
double x;
while(in >> x)
hw.push_back(x);

// clear the stream so that input will work for the next student
in.clear();
}
return in;
}

+

Student_info.h

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
#ifndef GUARD_Student_info
#define GUARD_Student_info

#include<iostream>
#include<string>
#include<vector>

struct Student_info{
std::string name;
double midterm, final;
std::vector<double> homework;
};

bool compare(const Student_info &, const Student_info &);
std::istream & read(std::istream &, Student_info &);
std::istream & read_hw(std::istream &, std::vector<double> &);
#endif

+

A simple Test

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
Inputs:

Xxzrz 87.1414 3.48485 18 58 0
Oketl 98.0909 57.7273 92 38 22
Gzqrc 91.1515 88.5657 34 2 11
Ojway 86.7576 33.697 16 44 42
Psajl 99.5758 76.9293 12 75 89
Aovlz 88.0101 89.5556 85 2 23
Cpwsr 57.3232 32.697 89 21 54
Izcob 55.3434 49.4141 60 45 12
Ijtvd 96.8788 29.4949 49 66 37
Ldvgy 5.88889 82.5556 1 14 34
Mborx 55.8586 53.1212 45 32 8
Xohgm 82.8182 44.9697 61 29 22
Xotog 59.9293 39.5354 10 54 24
Nfetc 22.6869 18.8788 91 58 5
Kljug 24.3434 74.7273 70 33 59
Zjenp 70.6364 68.9293 80 2 85
Pjsrd 25.4343 24.2323 81 61 72
Lchhb 31.9293 42.2222 0 64 86
Zobiw 70.3535 33.1111 67 96 60
Fsksr 24.1919 25.7677 2 58 94
Dcyzj 84.1818 64.1919 87 0 52
Qcozi 15.7677 27.4343 9 64 58
Eeddp 74.2525 27.2929 20 23 28
Mmtat 61.9596 25.6465 16 2 60
Muvnp 47.5354 20.9091 63 88 24
Azuxm 13.5859 78.6566 0 77 7

Outputs:
median: median(did) = 46.1475: median(didnt) = 42.9273
average: median(did) = 45.4202: median(didnt) = 44.3273
median of homework turned in: median(did) = 46.1475: median(didnt) = 52.1273
+

Merging three analysis functions into one single function

It has been observed that three analysis functions are in fact defined in the same manner. The only difference between them is the statements:

1
transform(students.begin(), students.end(), back_inserter(grades), //transform function to be passed);

+

Therefore, we can redefine the analysis function such that the transform function can be passed to the analysis function as an argument. We have learned how to do this from the write_analysis function. Now let’s define it:

+
1
2
3
4
5
6
7
double analysis(const vector<Student_info> &students, double grading_scheme(const Student_info &s))
{
vector<double> grades;

transform(students.begin(), students.end(), back_inserter(grades), grading_scheme);
return median(grades);
}
+

For example, if we want to compute the average based median grade, we would call this function like:

+
1
analysis(did, average_grade);
+

Correspondingly, we need to change the write_analysis function as well.

+
1
2
3
4
5
6
7
8
void write_analysis(ostream &out, const string &name,
double grading_scheme(const Student_info &s),
const vector<Student_info> &did,
const vector<Student_info> &didnt)
{
out << name << ": median(did) = " << analysis(did, grading_scheme)
<< ": median(didnt) = " << analysis(didnt, grading_scheme) << endl;
}
+

I define the new write_analysis function such that it takes the grading_scheme functions as its second arguments. To qualify the use of the analysis function, I add #include “grade_analysis.h” into write_analysis.cpp. In addition, I add #include “gradingSchemes.h” into mainfunction.cpp for qualifing the use of three grading_scheme functions. Please find the revised files shown in below.

+

modified file 1: grades_analysis.cpp

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
#include <vector>		// to get the declaration of vector
#include <iterator> // to get the declaration of back_inserter
#include <stdexcept> // to get the declaration of domain_error
#include <algorithm> // to get the declaration of transform
#include "Student_info.h" // to get the declaration of Student_info
#include "grades_analysis.h" // to get the declaration of all functions here to keep consistent
#include "gradingSchemes.h" // to get the declaration of the median function

using std::vector; using std::transform;
using std::domain_error; using std::back_inserter;

double analysis(const vector<Student_info> &students, double grading_scheme(const Student_info &s))
{
vector<double> grades;

transform(students.begin(), students.end(), back_inserter(grades), grading_scheme);
return median(grades);
}

+

modified file 2: grades_analysis.h

1
2
3
4
5
6
7
8
9
10
#ifndef GUARD_GRADES_ANALYSIS_H
#define GUARD_GRADES_ANALYSIS_H

#include <vector>
#include "Student_info.h"

double analysis(const std::vector<Student_info> &students,
double grading_scheme(const Student_info &s));

#endif /* GUARD_GRADES_ANALYSIS_H */

+

modified file 3: write_analysis.cpp

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
#include <iostream>		// to get the declaration of cout, endl, ostream
#include <string> // to get the declaration of string
#include <vector> // to get the declaration of vector
#include "Student_info.h" // to get the declatation of Student_info
#include "write_analysis.h" // to get the declatation of write_analysis itself
#include "grades_analysis.h" // to get the declaration of analysis function

using std::cout; using std::endl;
using std::string; using std::vector;
using std::ostream;

void write_analysis(ostream &out, const string &name,
double grading_scheme(const Student_info &s),
const vector<Student_info> &did,
const vector<Student_info> &didnt)
{
out << name << ": median(did) = " << analysis(did, grading_scheme)
<< ": median(didnt) = " << analysis(didnt, grading_scheme) << endl;
}

+

modified file 4: write_analysis.h

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
#ifndef GUARD_WRITE_ANALYSIS_H
#define GUARD_WRITE_ANALYSIS_H

#include <iostream>
#include <string>
#include <vector>
#include "Student_info.h"

void write_analysis(std::ostream &out, const std::string &name,
double grading_scheme(const Student_info &s),
const std::vector<Student_info> &did,
const std::vector<Student_info> &didnt);


#endif /* GUARD_WRITE_ANALYSIS_H */

+

modified file 5: mainfunction.cpp

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
#include <iostream>		// to get the declaration of cin, cout, endl
#include <vector> // to get the declaration of vector
#include "Student_info.h" // to get the declaration of Student_info
#include "did_all_hw.h" // to get the declatation of the predicate on students' records
#include "write_analysis.h" // to get the declaration of write_analysis function
#include "gradingSchemes.h" // to get the declaration of three grading functions

using std::vector; using std::cout;
using std::cin; using std::endl;

int main()
{
// students who did and didn't do all their homework
vector<Student_info> did, didnt;

// read the student records and partition time
Student_info student;
while(read(cin, student))
{
if(did_all_hw(student))
did.push_back(student);
else
didnt.push_back(student);
}
// verify thatthe analyses will show us something
if(did.empty())
{
cout << "No student did all the homework!" << endl;
return 1;
}
if(didnt.empty())
{
cout << "Every student did all the homework!" << endl;
return 1;
}

// do the analysis
write_analysis(cout, "median", median_grade_aux, did, didnt);
write_analysis(cout, "average", average_grade, did, didnt);
write_analysis(cout, "median of homework turned in", optimistic_median, did, didnt);

return 0;
}

+

I tested this program using the same inputs as I used in the original program. They work exactly the same and yield same outputs.

+
+

To be continued.

+ + +
+ + + + + + + + + + + +
+ + + +
+ + + +
+ +
+
+ + +
+ + + + + + + + + +
+ + + + + + + + + +
+
+ + + + +
+ + +
+ + + + +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/22/04/2018/C-Making-class-objects-act-like-values/index.html b/22/04/2018/C-Making-class-objects-act-like-values/index.html new file mode 100644 index 00000000..8293569f --- /dev/null +++ b/22/04/2018/C-Making-class-objects-act-like-values/index.html @@ -0,0 +1,857 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + C++ - Making class objects act like values | Liam's Blog + + + + + + + + + + + + + + + + + + +
+
+ + + +
+
+
+
+ + +
+ + + + + + + + +
+ + + +
+ + + + + + + +
+ + + +

C++ - Making class objects act like values

+ + + +
+ + + + + +
+ + + + + +

As last chapter introduces, we can control what happens when objects are created, copied, assigned, and destroyed by defining special members. Now we intend to make class objects act like objects of built-in types through controlling more operations such as type conversion. A typical example is that the standard library class string provides rich set of operators and supports automatic conversions. Following the standard string, we’ll write our own Str class.

+

A simple string class

Analogous to the Vec class built in last chapter, we could write our Str based on dynamiclly allocated array. But it is also known that the standard string share many operations with the standard vector while the major difference is that a string is a container that only contains char elements. Therefore, we can design Str based on Vec rather than the lower level data structure.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
class Str{
public:
typedef Vec<char>::size_type size_type;

// default constructor; create an empty Str
Str() { }

// create a Str containing n copies of c
Str(size_type n, char c): data(n, c) { }

// create a Str from a null-terminated array of char
Str(const char* cp){
std::copy(cp, cp + std::strlen(cp), std::back_inserter(data));
}

// create a Str from the range denoted by iterators b and e
template <class In> Str(In b, In e){
std::copy(b, e, std::back_inserter(data));
}

private:
Vec<char> data;
};

+

It can be observed that our Str is implemented through a hidden Vec. There are four constructors defined in above class. The first functon is a default constructor that creates an empty Str through invoking the Vec default constructor. It is worth noting that we have to explicitly define a default constructor thought it does exactly . If we don’t define a default constructor,the compiler won’t synthesize one for us as there exist other constructors. The second constructor takes a size and a character and initializes the only data member data by invoking another Vec constructor that takes a size and a value. The third constructor allows us to create a string with passing an argument that is a pointer to char, that is, a null-terminated array of char. It uses the standard algorithm copy to copy the elements from the array of char, covering the range [cp, cp+std::strlen(cp)) into data. cp points to the first character of the array and cp + std::strlen(cp), where strlen(cp) returns the length of the array excluding the ‘\0’, points to one past the last character in the array. Similarly, the last constructor creates a string by taking two input iterators that denotes a sequence of characters. But it is worth nothing that it is not a function but a function template. It accepts different kinds of iterators, which implies that it can construct a string object from various containers like the array of char, standard vector, standard list etc..

+

We also observed that the Str doesn’t define a copy constructor, assignment operator and default destructor. The synthesized operations call the corresponding members of Vec when we copy or assign or destruct the Str object. In fact, the Str class does no memory allocation and hence doesn’t require a destructor. According to the rule of three, a class that needs no destructor doesn’t need an explicit copy constructor or assignment operator either.

+

Automatic conversions

In the case of the Str class, the conversions may happen when we assign a string literal to a string type object. For example:

1
2
Str t;    // default initialize t
t = "hello"; // assign a new value to t

+

The first statement creates an empty string and the second statement assigns the value of the right-hand side to the left-hand side. However, the left-hand side has type Str while the right-hand side has type const char*. In addition, we didn’t define the assignment operator. How does the compiler evaluates this expression? It turns out that the compiler will call the constructor that takes the a const char*. In other words, the statement invokes the same constructor as the following statement:

1
Str t("hello");

+

This example indicates that constructors also acts a user-defined conversion which determines how to transform to and from objects of class type. In generally, we define conversions by defining a constructor with a single argument. The above statement t = “hello”; involves two steps: firstly, calling the Str(const char*) to construct an unnamed local temporary of type Str from the string literal; then calls the synthesized assignment operator to assign this temporary to t.

+

Str operations

Now we further extent the operations of our Str class such that a Str type string s supports following operations:

1
2
3
4
cin >> s;    // use the input operator to read a string
cout << s; // use the output operator to write a string
s[i]; // use the index operator to access a character
s1 + s2; // use the addition operator to concatenate two strings

+

indexing operator

We have learned how to define a operator, such as operator=, in defining the Vec class. We can define these operators in a similar manner. All above operators are binary operators and hence each operator function takes two parameters, one of which may be implicit if the function is a member. We are familar with the indexing operator. Let’s define it first:

1
2
3
4
5
6
7
8
9
class Str{
public:
// constructors as before
char& operator[](size_type i) { return data[i]; }
const char& operator[](size_type i) const { return data[i]; }

private:
Vec<char> data;
};

+

We define two operators to take the case that access elements of a const string into consideration. Details of the implementation go to the indexing operator defined in the Vec.

+

input and output operator

Now let’s think about how to implement the input operator >> and the output operator <<. The first problem is should these operators be members of a class? Due to the operator (e.g. >>) changes the state of a string, we might think it should be a member of the Str. But it is also known that the left operand is bound to the first parameter while the right operand is bound to the second parameter. Thus,

1
cin >> s;

+

is equivalent to

1
cin.operator >> (s);

+

which calls the overloaded >> operator defined for the object cin. This implies that the operator should be a member of the istream class. However, we cannot define such operation as we don’t have the definition of the istream class. If we define the operator in Str, it should invoke the input operation through

1
s.operation>> (cin);

+

or equivalently,

1
s >> cin;

+

which obviously would flout the conventions used throughout the library.

+

Know then that both the input and output operators should be non-member functions. Let’s declare two non-member functions:

1
2
std::istream& operator>>(std::istream&, Str&);
std::ostream& operator<<(std::ostream&, const Str&);

+

To write the output operator, we need to access each character stored in the Str. Therefore, the implementation could be

1
2
3
4
5
6
ostream& operator<<(ostream& os, const Str& s)
{
for(Str::size_type i = 0; i != s.size(); ++i)
os << s[i];
return os;
}

+

To use this function, we have to define the size member first

1
2
3
4
5
class Str{
public:
size_type size() const { return data.size(); }
// as before
};

+

Friends

Unlike the output operator, the input operator is a little bit complex. The logic is that each time read one character from the input stream and then add the character to our Str. The experience of using the standard string tells us that when reading data from the input stream, it discards the leading whitespace. Beyond this, we should also take into consider the case that there exist old values in the Str. Let’s see how following code deal with these problems.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
// this code won't compile quite yet
istream& operator>>(istream& is, Str& s)
{
// obliterate existing value(s)
s.data.clear();

// read and discard leading whitespace
char c;
while(is.get(c) && isspace(c))
; // nothing to do except testing the condition

// if still something to read, do so until next whitespace character
if(is) {
do s.data.push_back(c);
while(is.get(c) && !isspace(c));

// if we read whitespace, then put it back on the stream
if(is)
is.unget();
}
return is;
}

+
    +
  1. the first step is to obliterate the old values.
  2. +
  3. the second step is to discard the leading whitespace. Two conditions control the while loop, one is that whether characters are available and another one is that whether the character read from the input stream is a space. The member function get extracts one character each time, if there exists characters, it returns the character and will be evaluated to true. If there no available character, it returns eof and will be evaluated to false. In summary, if the while loop ceases, there would be two situations, no character is available or the character is not a whitespace anymore.
  4. +
  5. then we perform reading process if there still available character in the input stream after step 2. The reading process calls the member function push_back to append one character one time. It stops if it reads nothing from the stream or encounters a whitespace. In the case that it encounters a whitespace, there might be other characters following the extracted whitespace. Therefore, we should put the extracted whitespace back on the stream. This is done by calling another member function of the istream class, that is, unget which decreases the current location by one character such that the extracted character can be extracted again next.
  6. +
+

The logic is perfect and we do solve the problems mentioned earlier. However, above code fails to compile due to that operator>> is not allowed to access the private data member data defined in the Str. We could add public member functions clear and push_back to our Str class like we did for our Vec class. But in this case, well solve this problem with an alternative method, using keyword friend.

1
2
3
class Str{
friend std::istream& operator>>(std::istream&, Str&);
};

+

A friend gives the function operator>> access and write rights to the private data members defined in the Str class. In other words, if making one function a friend of a class, we are saying that the function will be treated as a member (either public or private) by the class. Above code shows that we add the declaration of operator>> into the Str class and specify it is a friend of the class.

+

Other binary operators

We also consider that define the addition operator as a non-member function. The reason is that the addition operation doesn’t change values of the left-hand operand as well as the right-hand operand. The result of the addition operation between two strings is a string that concatenates two strings. Thus, the return type shoule be Str. Therefore, the operator= may be declared as follows:

1
Str operator+(const Str&, const Str&);

+

After we complete writing the implementation, our program would supports the concatenation operation between two Strs through

1
2
3
Str s1 = "xxx";
Str s2 = "yyy";
s1 = s1 + s2;

+

Our experience tells us that we can concatenate two standard strings in an alternative form:

1
s1 += s2;

+

Both two statements involve two processes: the right-hand side creates a new temporary object that is the concatenation of two strings, then the value of the constructed object is assigned to the left-hand side. The difference is that operator+= changes the value of left-hand operand. Therefore, we will define the operator+= as a public member of the Str class. Let’s see how to define operator+= first

1
2
3
4
5
6
7
8
9
10
11
12
13
14
class Str{
public:
Str& operator+=(const Str& s){
std::copy(s.data.begin(), s.data.end(), std::back_inserter(data));
return *this;
}

// as before

private:
Vec<char> data;
};
// as before
Str operator+(const Str&, const Str&);

+

There is nothing new in above implementation of the operator+=. Now let’s define the operator+:

1
2
3
4
5
Str operator+(const Str& s, const Str& t){
Str r = s;
r += t;
return r;
}

+

In the definition, we use the operator+= and the synthesized copy constructor to achieve the concatenation of two strings.

+

Mixed-type expressions

The standard library string class also allows us to concatenate a string literal and a string regardless there order. As a result, we get a new string type object. For our Str class, we have defined the concatenation operator that takes operands of type const Str&. So, What would happen if following statement is evaluated:

1
const Str greeting = "Hello, " + name + "!"; // where **name** is a **Str** type object.

+

an alternative and equivalent statement

1
const Str greeting = ("Hello, " + name) + "!";

+

We can observe that there are two forms of +. The first + takes a string literal as its first operand and a Str as its seconnd operand, while in the other, the left operand is a Str and the right operand is a string literal. We may think that we should define two additional operator+ to handle these two case as the operator+ defined above only takes two arguments that are both const Str&. In fact, our Str class handles these expressions already, by means of calling the constructor that takes a const char*. This is because that the constructor is also a conversion operator that can convert a const char* to a Str. Let’s see how exactly the Str deal with this statement:

1
2
3
4
1. Str temp1("Hello, ");        // call Str::Str(const char*)
2. Str temp2 = temp1 + name; // call operator+(const Str&, const Str&)
3. Str temp3("!"); // call Str::Str(const char*)
4. Str greeting = temp2 + temp3;// call operator+(const Str&, const Str&)

+

The implied conversion operations may be expensive due to multiple temporaries. But certainly, we still can explicitly define two additional versions o the operator+ to deal with this case.

+

Designing binary operators

There are some rules in defining binary operators(Koening and Moo 2000):

+

1. If a class supports type conversions, then it is usually good practice to define binary operators as nonmember functions. By doing so, we preserve symmetry between the operands.

+

2. If an operator is a member of a class, then that operator’s left operand cannot be the result of an automatic conversion.

+

3. The left operand of a nonmember operator, and the right operand of any operator, follow the same rules as any ordinary function argument: the operand can be any type that can be converted to the parameter type.

+

4. like the assignment operator itself, all the compound-assignment operators (e.g. +=) should be members of the class.

+

Some conversions are hazardous

Recalling the Vec class designed in last chapter, it contains a constructor that takes a size (and a value if supplied) (as shown below).

1
explicit Vec(size_type n, const T& t = T()) { create(n, t); }

+

The explicit specifies that the constructor can only construct an object explicitly. If we don’t declare the Vec constructor as explicit, then we could implicitly create a Vec of a given size. To illustrate how useful the explicit is, let’s see an example:
. It is crucial that consider about the type conversion when defining a single parameter constructor for a class.

+

Conversion operators

We have known that we can implicitly define conversion operations through defining constructors. Those cases typically involve that a class defines how to convert an object from a different type to the type of the class itself. In fact, class authors can also explicitly define conversion operators, which determines how to convert an object from its type to a target type.

+

A conversion operator must be defined as a member of a class, begining with the keyword operator followed by the target type name. For example,

1
2
3
4
5
class Student_info(){
public:
operator double();
// ...
};

+

The conversion operator above defines that a Student_info can be converted to a double type object. The definition of operator would say how exactly create a double from a Student_info. For example, we can convert the class object to its corresponding final grade, and then use this property in calculating an average grade for a class.

1
2
3
4
5
6
7
vector<Student_info> vs;
// fill up vs

double d = 0;
for (int = 0; i != vs.size(); ++i)
d += vs[i]; // vs[i] is automatically converted to double
cout << "Average grade: " << d/vs.size() << endl;

+

In fact, we use this kind conversion operator everytime when we write a loop that implicitly tests the value of an istream. See the example

1
if(cin >> x) { /*...*/ }

+

is equivalent to

1
2
cin >> x;
if(cin) { /*...*/ }

+

It is known that the condition should be an expression that yields a value that is convertible to type bool. Using a value of any arithmetic or pointer type automatically converts the value to type bool, thus we can uses values of these type in the expression. But a iostream object neither an arithmetic type object nor a pointer type object. To makes the if condition works in above case, the standard library defines a conversion from type istream to void*, i.e. a pointer to void.

1
2
3
istream::operator void* {
/*...*/
}

+

The operator tests various status flags to to determine whether the istream is valid and return either 0 or an implementation-defined non-zero void* value to indicate the state of the stream.

+

It is necessary to explain the use of void*. A pointer to void is known as a universal pointer which can point to any type of object. We cannot deference such pointer because the type of the object to yield is unknown. But we can convert a void* to bool.

+

One might wonder why don’t the istream define conversion operator to bool directly. The reason is that doing so allows the compiler to detect the following erroneous usage:

1
2
int x;
cin << x; // we should have to written cin >> x

+

If the conversion operator converts a istream object to bool, this expression would convert cin to a bool, and thereby converts the bool to int again. As a result, the converted value is shifted left by a number of bits equal to the value of x.

+

Conversions and memory management

In this section, we think about the conversion that from a string type to a null-terminated arrays of characters. If we can successfully convert a string to an array of characters, we then can pass the string to a functions that requires and operates on null-terminated arrays.

1
2
3
4
5
6
7
8
9
10
class Str{
public:
// plausible, but problematic conversion operations
operator char*();
operator const char*() const;

// as before
private:
Vec<char> data;
};

+

If above code works, we then can write code such as

1
2
3
Str S;
//...
ifstream in(s); // wishful thinking: converts s and then open the stream named s

+

(Noting that since c++11, ifstream allows us to open a file using either a string type name or a c-stype(i.e. null-termintated array) name.)

+

There are several difficulties in defining such operator:

+
    +
  1. we can’t simply return data as data is a Vec while we need an array of char.
  2. +
  3. if we design our Vec based on an array of char, we could return it as the converted result. However, doing so exposes the private data member, which violates the class Str‘s encapsulation. If users obtained a pointer to data, they could change the value of the string. In addition, if the string is destroyed, then the pointer becomes invalid and any related operations would be dengerous.
  4. +
+

To solve the encapsulation problem, we may provide only one conversion to const char*. To solve the dangling pointer problem, we may allocate a new space for a copy of the characters from data, and returning a pointer to this newly allocated space. By doing so, users can manage the allocated storage properly. However, this design probably doesn’t work either because the conversion happens implicitly and hence no pointer is provided explicitly.

+

The standard string class takes a different approach that allows us to get a copy of the string in a character array but also makes them do explicitly. It defines three member functions to get a character array from a string. The first is c_str() which copies the contents of the string into a null-terminated char array. The string owns the array and users are expected not to delete the pointer. The data in the array are ephemeral and is only valid until the next call of a member function that might change the string. The second data() is like c_str except that it returns an array that is not null-terminated (c++11 releases this condition and hence data() and c_str() are synonym and return the same value). Finally, the copy function takes a char* and an integer as arguments, and copies as many characters as indicated by the integer into space pointed by the char*, which soace the user must allocate and free. These functions work as we expected. However, this type of coversions seems explicitly rather than implicitly.

+
+ + +
+ + + + + + + + + + + +
+ + + +
+ + + +
+ +
+
+ + +
+ + + + + + + + + +
+ + + + + + + + + +
+
+ + + + +
+ + +
+ + + + +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/23/02/2018/C-Looping-and-counting/index.html b/23/02/2018/C-Looping-and-counting/index.html new file mode 100644 index 00000000..ae5ab4fe --- /dev/null +++ b/23/02/2018/C-Looping-and-counting/index.html @@ -0,0 +1,942 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + C++ - Looping and counting | Liam's Blog + + + + + + + + + + + + + + + + + + +
+
+ + + +
+
+
+
+ + +
+ + + + + + + + +
+ + + +
+ + + + + + + +
+ + + +

C++ - Looping and counting

+ + + +
+ + + + + +
+ + + + + +

In last chapter, the authors Koenig and Moo presents how to write a program that can produce a framed greeting. This chapter is to make the program more flexible so that we can change the size of the frame easily. Let’s have a look at the framed greeting again:

1
2
3
4
5
********************
* *
* Hello, Estragon! *
* *
********************

+

It has been observed that the greeting work has following characteristics:

+
    +
  • it consist of three elements: the greeting itself, asterisks and spaces.
  • +
  • the greeting itself will be fixed once we finished inputing.
  • +
  • the greeting itself is centrally located, meaning that it is surrounded by blank rows and the same number of black columns.
  • +
  • the outmost rows and columns are constituted by asterisks.
  • +
+

Clearly, the size of the frame is determined by the length of the greeting itself and the number of blank lines(i.e. spaces in this case). Now, we can define a variable named pad that represents the length of spaces (e.g. 1):

1
const int pad = 1;

+

Then, we can indicate the number of rows with defining another variable:

1
const int rows = 1 + 2 + 2 * pad;

+

The number 1 means the row of the greeting itself and the number 2 means two edges formed by asterisks. Once we know the number of rows, we can write the program similar as the the original one which defines several variables for each row and prints one by one. However, we need to change the code every time we change the frame size. This problem can be tackled with using iterative statements which will fully utilize the information listed above including the position of each type of elements.

+

While statement & for statement

A while statement repeatedly executes a given statement as long as a pre-dertimined condition is true. The syntax of a while statement is

1
2
while (condition)
statement

+

The processing of while statement starts from testing the condition and then executes the statement (aka, the while body) if the condition is true, and it starts all over again until the condition becomes false. Our purpose is to write each row, totalling rows times. Hence, what the program need here is a counting variable which is used to control the outputs.

1
2
3
4
5
6
7
int r = 0; // counting variable with initial value 0
while (r != rows)
{
// write a row of output
std::cout << std::endl;
++r;
}

+

The while loop requires a single statement but the logic of our program need more. Hence, we can use a compound statement which refers to a block formed by a pair of curly braces. As shown above, We can write a sequence of statements and declarations or empty statement in the block. Note that the block forms a scope and the right brace (}) indicates the end of the statement.

+

The condition is an expression that returns bool type value (true or false). When the condition yields an arithmetic type value, the value will be converted to a value of bool type:

+
    +
  1. zero values convert to false
  2. +
  3. non-zero values convert to true
  4. +
+

The inequality operator (!=) test the inequality between r and rows. The table below gives the logical and rational operators (all returns a value of bool type):

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
AssociativityOperatorFunction
Right!Logical NOT
Left<less than
Left<=less than or equal
Left>greater than
Left>=greater than or equal
Left==equality
Left!=inequality
Left&&Logical AND
Left||Logical OR
Source: C++ Primer 2012
+

The ++() is the increment (decrement) operator which adds (subtract) 1 to the variable r. The expression is equivalent to

1
r = r + 1;

+

If we want to add another variable with varing values each time, we can use compound assignment operators (refer to arithmetic operators)

+ + + + + + + + + + + + + + + + + + + + + + + + + + +
+=-=*=/=%/
+

For example, x += y; is equivalent to

1
x = x + y;

+

Comparing with the ordinary assignment, compound assignment only evaluate the left operand once while the ordinary assignment evaluate twice.

+

Let’s get back to the while statement. According to the condition, the counting process will starts from 0 till rows-1 and the statement is expected to be executed rows times. In this case, an alternative loop structure can also be considered, a for statement

1
2
for (init-statement; condition; expression) // for header
statement // for body

+

The for header controls the for body which contains the statement that needs to be executed. The init-statement can only be one of three statements: declaration statement, an expression statement and anull statement(using a semicolon).
The processing order of the for statement is

+
    +
  1. at the begining of the loop, init-statement will be executed.
  2. +
  3. then, condition will be evaluated and the for body would be executed if the condition returns true. Otherwise, the loop terminates.
  4. +
  5. if condition is true, the last step of the loop is to execute expression.
  6. +
+

In this case, the for statement can be write as

1
2
3
4
5
for (int r; r != rows; ++r)
{
// write a row of output
std::cout << std::endl;
}

+

Loop invariant

To verify the correctness of the loops, the book introduces two techniques. The first is that the condition must be false when the loop finishes. In this case, when the while statement finishes, r != rows is false. Another one is to use loop invariant which is a property that is true before and after each loop. In this example, the loop invariant is not the core language but a comment before the while statement:

1
// invariant: we have written r rows so far

+

The loop invariant shows that the while statement works as expected, and in turn the program should meet the requirements that invariant is true in each iteration. There are two specific points for verify this:

+
    +
  1. the first point is before the first time that the condition is evaluated. In this example, we know it is correct as there is no output yet.
  2. +
  3. the second point is before the end of the while body. Once the while body is executed, one row will be written. To meet the requirement, we increase the value of r by adding 1 each time.
  4. +
+

When the while statement finishes, there will be rows lines are written as r = rows. The below piece of code shows how the loop invariant plays its role.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
// invariant: we have written r rows so far
int r = 0;
// setting r to 0 makes the invariant true
while (r != rows)
{
// we can assume the invariant is true here

// writting a row of output makes the invariant false
std::cout << std::endl;

// incrementing r makes the invariant true again
++r;
}
// we can conclude that the invariant is true here

+

Up to now, we have figured how to control the loops for producing right number of rows. Similarly, we can use while loop or for loop to produce every character of each specific row. By the analogy, the first step is to define a variable to representing the number of columns. But the difference is that the number of columns is not only determined by blanks but also by the size of the greeting itself.

1
const std::string::size_type cols = greeting.size() + pad * 2 + 2;

+

The variable cols is defined as a std::string::size_type to keep consistent with the type of greeting.size(). This knowledge has been introduced in my last notes. We can also use type specifiers auto or decltype. For example

1
const auto cols = greeting.size() + pad*2 + 2;

+

What if we use int type directly? Then the variable might be insufficient if one input a name that is long enough. Now we can write the structure for writing a specific row.

1
2
3
4
5
6
7
8
std::string::size_type c = 0;

// invariant: we have written c characters so far in the current row
while (c != cols)
{
// write one or more characters
// adjust the value of c to maintain the invariant
}

+

if statements

Now We already have the whole structure of the program. What we need next is to determine what character is needed to be written for each row and column. As listed at the begining of this notes, we can utilize the characteristic of the target. I’ll further transform the information to plain english for clarify the logic of the program. There are three components in the outputs: asterisk, spaces and the greeting. The asterisks form the border of the frame and will be written when the loop is processing

+
    +
  • the first row or
  • +
  • the last row or
  • +
  • the first column or
  • +
  • the last column
  • +
+

These conditions can be expressed by means of the logical operators and relational operators

1
r == 0 || r == rows - 1 || c == 0 || cols - 1

+

The relational operators == have lower precedence than the arithmetic operators. Therefore, r == rows - 1 is equivalent to r == (rows - 1).

+

The logical OR operator has lower precedence than the relational operators. Therefore, above expression means

1
(r == 0) || (r == rows - 1) || (c == 0) || (cols - 1)

+

Moreover, the logical OR (as well as the logical AND) is left associative and the expression is evluated using the short-circuit strategy, that is, “the right side of an || is evaluated if and only if the left side is false (true for the logical AND)” (Lippman ect. 2012, p.154).

+

To evaluate above condition, we use the if statement

1
2
if (condition) 
statement

+

or if else statement

1
2
3
4
if (condition)
statement1
else
statement2

+

or nested if statement

1
2
3
4
if (condition1)
statement1
else if (condition2)
statement2

+

Now we have got a complete structure for printing out the border

1
2
3
4
5
6
7
8
9
10
11
if (r == 0 || r == rows - 1 || c == 0 || cols - 1)
{
std::cout << "*";
++c;
}
else
{
if (other condition)
// write one or more noborder characters
// adjust the value of c to maintain the invariant
}

+

By the analogy, we can determine the condition for outputing the greeting

1
r == pad + 1 && c == pad + 1

+

Note that we don’t need to write the condition of the each character of the greeting as once we find the row and the initial position we can print out the variable directly. Therefore, the value of the counting variable c will add a value of greeting.size() to maintain the invairant. Finally, it is not necessary to find the condition for the space characters as the remainder of the frame can only be spaces. Let’s combine three situations together

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
if (r == 0 || r == rows - 1 || c == 0 || cols - 1)
{
std::cout << "*";
++c;
}
else
{
if (r == pad + 1 && c == pad + 1)
{
std::cout << greeting;
c += greeting.size();
}
else
{
std::cout <<" ";
++c;
}
}

+

One common mistake in using if statement is that missing the curly braces after if or else when there are multiple statements. We should pay much attention on this in the case of nested if statements as there may exist more if branches than else branches. It is confusing how each else match with the if branches, which is commonly termed as dangling else. In the default seeting, C++ specifies that each else is matched with the closest preceding unmatched if. To avoid the ambiguity, it is recommended to control execution with curly braces.

+

The complete framing program

Now we can put all pieces together to get the complete framing program. Note there are several ways to organize the if statements and both while loop and for loop are available. Therefore, the program below is only one of the correct programs.

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
#include <iostream>
#include <string>

// using namespace std and names
using std::cout; using std::cin;
using std::endl; using std::string;

int main()
{
// ask for the person's name
cout << "Please enter your first name: ";

// read the name
string name;
cin >> name;

// build the message that we intend to write
const string greeting = "Hello, " + name + "!";

// the number of blanks surrounding the greeting
const int pad = 2;

// the number of rows and columns
const int rows = pad * 2 + 3;
const string::size_type cols = greeting.size() + pad * 2 + 2;

// write a blank line to separate the output from the input
cout << endl;

// write rows rows of output
// invariant: we have written r rows so far
for (int r = 0; r != rows; ++r)
{
// we have written c characters so far in the current row
string::size_type c = 0;
while (c != cols)
{
// are we on the border?
if (r == 0 || r == rows - 1 ||
c == 0 || c == cols - 1)
{
cout << "*";
++c;
}
else
{ // is it time to write the greeting?
if (r == pad + 1 && c == pad + 1)
{
cout << greeting;
c += greeting.size();
}
else
{
cout <<" ";
++c;
}
}
}
cout << endl;
}
return 0;
}

+

Now we can test the program with changing the number of blanks to 2, and type Bruce after the input prompt. It gives the outputs as we expected.

1
2
3
4
5
6
7
8
9
Please enter your first name: Bruce

*******************
* *
* *
* Hello, Bruce! *
* *
* *
*******************

+ + +
+ + + + + + + + + + + +
+ + + +
+ + + +
+ +
+
+ + +
+ + + + + + + + + +
+ + + + + + + + + +
+
+ + + + +
+ + +
+ + + + +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/24/04/2018/C-Implementations-Dynamic-Array-based-Stack/index.html b/24/04/2018/C-Implementations-Dynamic-Array-based-Stack/index.html new file mode 100644 index 00000000..cfbded35 --- /dev/null +++ b/24/04/2018/C-Implementations-Dynamic-Array-based-Stack/index.html @@ -0,0 +1,777 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + C++ Implementation: Dynamic Array-based Stack | Liam's Blog + + + + + + + + + + + + + + + + + + +
+
+ + + +
+
+
+
+ + +
+ + + + + + + + +
+ + + +
+ + + + + + + +
+ + + +

C++ Implementation: Dynamic Array-based Stack

+ + + +
+ + + + + +
+ + + + + +

Stack is one of the rudimentary data structures that use pointers, with a main feature that it implements the Delete operation following last in, first out (i.e. LIFO). More specific, a stack is a dynamic set that allows Insert and Delete operations, which are typically named push and pop respectively.

+

The program given below illustrates an ADT named my_stack, which implements the stack based on a dynamic allocated array.
my_stack is a class template and provides an interface that allows following operations:

1
2
3
4
5
6
7
8
my_stack ms;        // create a stack with fixed capacity 1000
my_stack s(100); // create a stack with a user-supplied capacity
s.get_capacity(); // get the current capacity of s
s.size(); // get the number of elements contained in s
s.empty(); // check whether the stack is empty
s.push(); // insert an new element into the stack at the end of it
s.pop(); // delete the last inserted element from the stack, and return the deleted element
s.top_element(); // return the top element only

+

Noting that the capacity of a stack means how many elements the stack can contain while the size means how many elements have the stack stored.

+

stack implementation

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
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
#ifndef MYSTACK_H_
#define MYSTACK_H_

#include <iostream> // std::cout, std::endl
#include <cstddef> // std::size_t
#include <stdexcept>// std::domain_error

template <class T> class MyStack{
public:
typedef std::size_t size_type;

// default constructor
MyStack(): top(0), capacity(1000) {
std::cout << "default constructor" << std::endl;
p = new T[capacity];
}

// constructor with user-defined capacity
explicit MyStack(size_type t): top(0), capacity(t) {
std::cout << "constructor with user-defined size" << std::endl;
p = new T[capacity];
}

// copy constructor
MyStack(const MyStack& s): top(0), capacity(s.capacity){
std::cout << "copy constructor" << std::endl;
p = new T[capacity];
T* temp = s.p;
while(top != s.top){
p[top] = *temp;
++top;
++temp;
}
}

// assignment operator
MyStack& operator=(const MyStack& s){
std::cout << "assignment operator" << std::endl;
if(&s != this){
clear();
capacity = s.capacity;
p = new T[capacity];
T* temp = s.p;
while(top != s.top){
p[top] = *temp;
++top;
++temp;
}
}
return *this;
}

// destructor
~MyStack() {
delete[] p;
p = nullptr;
}

void clear(){
top = 0;
}

// capacity: O(1)
size_type get_capacity() const { return capacity; }

// size: O(1)
size_type size() const { return top; }

// empty
bool empty() const { return top == 0; }

// top_element: O(1)
T top_element() const { return p[top - 1]; }

// push element: O(1)
void push(const T& t){
if(top == capacity)
throw std::domain_error("stack overflow");
p[top] = t;
++top;
}

// pop element and return the deleted element: O(1)
T pop() {
if(top == 0)
throw std::domain_error("stack underflow");
--top;
return p[top];
}

private:
size_type top; // count the number of elements
size_type capacity; // capacity of the stack
T* p; // a hidden pointer to head
};

#endif /* MYSTACK_H_ */

+

The shortcoming of above stack is that it cannot grow automatically. Except constructors, each of member functions has constant complexity. The follwing program tests each operation listed above and shows that the my_stack works as expected.

+

stack test

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
65
66
67
68
69
70
/*
* this program tests all operations that provided by the MyStack<int> class
* created by Liam on: 27 Apr 2018
*/

#include <iostream> // std::cout, std::endl
#include <stdexcept> // std::domain_error
#include "MyStack.h" // MyStack

using std::cout;
using std::endl;
using std::domain_error;

int main(){

{ // test default constructor
MyStack<int> s;

// test member capacity()
cout << "The capacity of the stack is: " << s.get_capacity() << "\n";

// test member empty()
if(s.empty())
cout << "The stack is empty\n";

// test member push(const T& t)
for(int i = 0; i != 10; ++i)
s.push(i);

// test member pop
while(s.size() != 0)
cout << s.pop() << " ";
}

cout << "\n\n";

{ // test constructor with size
MyStack<int> s(10);

for(unsigned int i = 0; i != s.get_capacity(); ++i)
s.push(i);

// test the case of overflow
try{
s.push(10);
}catch(std::domain_error e){
cout << e.what() << "\n";
}

// test copy constructor
MyStack<int> s_copy(s);
cout << "The top element in MyStack is: ";
cout << s_copy.top_element() << "\n";

// test assignment operator
s.pop();
s_copy = s;

while(s_copy.size() != 0)
cout << s_copy.pop() << " ";

try{
s_copy.pop();
}catch(domain_error e){
cout << e.what() << "\n";
}

}
return 0;
}

+

Outputs

1
2
3
4
5
6
7
8
9
10
11
default constructor
The capacity of the stack is: 1000
The stack is empty
9 8 7 6 5 4 3 2 1 0

constructor with user-defined size
stack overflow
copy constructor
The top element in MyStack is: 9
assignment operator
8 7 6 5 4 3 2 1 0 stack underflow

+ + +
+ + + + + + + + + + + +
+ + + +
+ + + +
+ +
+
+ + +
+ + + + + + + + + +
+ + + + + + + + + +
+
+ + + + +
+ + +
+ + + + +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/24/04/2018/Elementary-Data-Structures-Stack-C-Implementation/index.html b/24/04/2018/Elementary-Data-Structures-Stack-C-Implementation/index.html new file mode 100644 index 00000000..e3830c47 --- /dev/null +++ b/24/04/2018/Elementary-Data-Structures-Stack-C-Implementation/index.html @@ -0,0 +1,777 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + C++ Implementation: dynamic array-based stack | Liam's Blog + + + + + + + + + + + + + + + + + + +
+
+ + + +
+
+
+
+ + +
+ + + + + + + + +
+ + + +
+ + + + + + + +
+ + + +

C++ Implementation: dynamic array-based stack

+ + + +
+ + + + + +
+ + + + + +

Stack is one of the rudimentary data structures that use pointers, with a main feature that it implements the Delete operation following last in, first out (i.e. LIFO). More specific, a stack is a dynamic set that allows Insert and Delete operations, which are typically named push and pop respectively.

+

The program given below illustrates an ADT named my_stack, which implements the stack based on a dynamic allocated array.
my_stack is a class template and provides an interface that allows following operations:

1
2
3
4
5
6
7
my_stack ms;        // create a stack with fixed capacity 1000
my_stack s(100); // create a stack with a user-supplied capacity
s.get_capacity(); // get the current capacity of s
s.size(); // get the number of elements contained in s
s.empty(); // check whether the stack is empty
s.push(); // insert an new element into the stack at the end of it
s.pop(); // delete the last inserted element from the stack, and return the deleted element

+

Noting that the capacity of a stack means how many elements the stack can contain while the size means how many elements have the stack stored.

+

stack implementation

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
#ifndef MY_STACK_H_
#define MY_STACK_H_

#include <cstddef> // std::size_t
#include <stdexcept> // std::domain_error

template <class T> class my_stack{
public:
typedef size_t size_type;

// default constructor
my_stack(): top(0), capacity(1000) { p = new T[capacity]; }

// constructor with user-defined capacity
explicit my_stack(size_type t): top(0), capacity(t) { p = new T[capacity]; }

// destructor
~my_stack() { delete[] p; }

// member: capacity
size_type get_capacity() const { return capacity; }

// member: size
size_type size() const { return top; }

// member: empty
bool empty() const { return top == 0; }

// member: push
void push(const T& t){
if(top == capacity)
throw std::domain_error("stack overflow");
p[top] = t;
++top;
}

// member: pop
T pop() {
if(top == 0)
throw std::domain_error("stack underflow");
--top;
return p[top];
}

private:
size_type top;
size_type capacity;
T* p;
};

#endif /* MY_STACK_H_ */

+

The shortcoming of above stack is that it cannot grow automatically. Except constructors, each of member functions has constant complexity. The follwing program tests each operation listed above and shows that the my_stack works as expected.

+

stack test

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
65
66
67
68
69
70
71
72
73
74
75
76
/*
* this program tests all operations that provided by the my_stack<int> class
*/

#include "my_stack.h" // my_stack
#include <iostream> // std::cout, std::endl
#include <stdexcept> // std::domain_error

using std::cout;
using std::endl;
using std::domain_error;

int main(){
// test default constructor
my_stack<int> s;

// test member capacity()
cout << "The capacity of the stack is: " << s.get_capacity() << endl;

// test member empty()
if(s.empty())
cout << "The stack is empty" << endl;

// test member push(const T& t)
for(int i = 0; i != 10; ++i)
s.push(i);

// test member size()
if(!s.empty())
cout << "The size of the stack is: " << s.size() << endl;

// test member pop()
for(int i = 0; i != 10; ++i)
{
cout << "The top element is: " << s.pop() << "\t";
cout << "The size now is: " << s.size() << endl;
}

// test the constructor that takes a size
my_stack<int> s1(10);

// again: test member capacity()
cout << "The capacity of s1 is: " << s1.get_capacity() << endl;

// again: test member empty()
if(s1.empty())
cout << "The stack is empty" << endl;

// test exception overflow
for(int i = 0; i != 11; ++i)
{
try{
s1.push(i);
}catch(domain_error e){
cout << e.what() << endl;
}
}

// test exception underflow
for(int i = 0; i != 11; ++i)
{
try{
s1.pop();
}catch(domain_error e){
cout << e.what() << endl;
}
}

// the current status of s2 is expected to be empty and size() == 0
if(s1.empty() && s1.size() == 0)
cout << "The stack is empty now" << endl;

// at the end of the program, two destructors are expected to be called to free the space
// that occupied by stack<int> s and x
return 0;
}

+

Outputs

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
The capacity of the stack is: 1000
The stack is empty
The size of the stack is: 10
The top element is: 9 The size now is: 9
The top element is: 8 The size now is: 8
The top element is: 7 The size now is: 7
The top element is: 6 The size now is: 6
The top element is: 5 The size now is: 5
The top element is: 4 The size now is: 4
The top element is: 3 The size now is: 3
The top element is: 2 The size now is: 2
The top element is: 1 The size now is: 1
The top element is: 0 The size now is: 0
The capacity of s1 is: 10
The stack is empty
stack overflow
stack underflow
The stack is empty now

+ + +
+ + + + + + + + + + + +
+ + + +
+ + + +
+ +
+
+ + +
+ + + + + + + + + +
+ + + + + + + + + +
+
+ + + + +
+ + +
+ + + + +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/25/02/2018/Accelerated-C-Solutions-to-Exercises-Chapter-2-Part2/index.html b/25/02/2018/Accelerated-C-Solutions-to-Exercises-Chapter-2-Part2/index.html new file mode 100644 index 00000000..8c3e5740 --- /dev/null +++ b/25/02/2018/Accelerated-C-Solutions-to-Exercises-Chapter-2-Part2/index.html @@ -0,0 +1,958 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Accelerated C++ Solutions to Exercises (Chapter 2 Part2) | Liam's Blog + + + + + + + + + + + + + + + + + + +
+
+ + + +
+
+
+
+ + +
+ + + + + + + + +
+ + + +
+ + + + + + + +
+ + + +

Accelerated C++ Solutions to Exercises (Chapter 2 Part2)

+ + + +
+ + + + + +
+ + + + + +

Exercise 2-4

The framing program writes the mostly blank lines that seperate the borders from the greeting one character at a time. Change the program so that it writes all the spaces needed in a single output expression.

+

Solution & Results

Without considering the frame itself, there only exist two types of row. The first type is the rows that full of spaces. The second type is the row that contains the greeting and spaces symmetricly located on two sides of the greeting. Therefore, one of the possible solutions is to write one row directly each time instead of writing one character. First, I defines two variables representing two blank strings

1
2
3
   // two blank strings containing different number of spaces
const string blankStringTopBottom(cols - 2, ' ');
const string blankStringLeftRight(pad, ' ');

+

The first string is in fact the first type of row and can be written directly given certain conditions. The second row can be obtained by means of catenate operations

1
2
   // the string that contains both the greeting and blanks
const string greetingRow = blankStringLeftRight + greeting + blankStringLeftRight;

+

To maintain the loop invariant, the c need to be adjusted after each output. Due to one row is written each time (ignoring the borders), c increases cols - 1 every time.

1
c += cols - 2;

+

The modified program is

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
65
66
#include <iostream>
#include <string>

// using namespace std and names
using std::cout; using std::cin;
using std::endl; using std::string;

int main()
{
// ask for the person's name
cout << "Please enter your first name: ";

// read the name
string name;
cin >> name;

// build the message that we intend to write
const string greeting = "Hello, " + name + "!";

// the number of blanks surrounding the greeting
const int pad = 1;

// the number of rows and columns
const int rows = pad * 2 + 3;
const string::size_type cols = greeting.size() + pad * 2 + 2;

// two blank strings containing different number of spaces
const string blankStringTopBottom(cols - 2, ' ');
const string blankStringLeftRight(pad, ' ');

// the string that contains both the greeting and blanks
const string greetingRow = blankStringLeftRight + greeting + blankStringLeftRight;


// write a blank line to separate the output from the input
cout << endl;

// write rows rows of output
// invariant: we have written r rows so far
for (int r = 0; r != rows; ++r)
{
string::size_type c = 0;
// we have written c characters so far in the current row
while (c != cols)
{
// are we on the border?
if (r == 0 || r == rows - 1 ||
c == 0 || c == cols - 1)
{
cout << "*";
++c;
}
else
{ // is it time to write the greeting row?
if (r == pad + 1)
cout << greetingRow;
// is it time to write the non-greeting rows?
else
cout << blankStringTopBottom;
c += cols - 2;
}
}
cout << endl;
}
return 0;
}

+

A simple test has been done and the outputs are written on the console window as below

1
2
3
4
5
6
7
Please enter your first name: Bruce

*****************
* *
* Hello, Bruce! *
* *
*****************

+

Analysis

See deatiled analysis in C++ - Looping and counting.

+
+

Exercise 2-5

Write a set of “*” characters so that they form a square, a rectangle, and a triangle.

+

Solution & Results

The design ideas

The correct program for this exercise is non-unique as the requirements are loose. It doesn’t specify the details of each shape. I tried to design a program that is flexible enough to meet more needs and preferences. For example, users may ask the program to write various rectangles (or squares/triangles) with different lengths or different appearance such as solid or hollow shapes. In summary, I intend to design a program with following properties

+
    +
  1. provide choices for three types of shapes, i.e. square, rectangle and triangle.
  2. +
    • +
    • for a square, the length of the side is customizable
    • +
    • for a rectangle, the length and the width are customizable, and the base can be the long side or the short side.
    • +
    • for a triangle, the height is customizable and the triangle is a equilateral triangle.
    • +
    +
  3. +
  4. for all shapes, there are two types of appearance: solid and hollow.
  5. +
+

The Primary Structure

Due to there are three types of shape, we can use if else statement as one of the choices of the primary structure and provide three banches for the three shapes. First, flags are needed for indicating what shape type is to be written. For example:

1
2
3
4
5
/* flags for what shape to print
* shapeType == 1, print a square shape
* shapeType == 2, print a rectangle shape
* shapeType == 3, print a triangle shape
*/

+

In addition, each type of shape can be a solid shape or hollow shape. Therefore, a choice for the appearance will be provided for users before the computer enters into each branch. For other properties, such as lengths of sides and heights, are not the same for each types of shape and will be solved in the branches. Therefore, the whole structure of the program is

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
int main()
{
// select a shape
cout << "Select a shape type\n";
cout << "1. A Square\n2. A Rectangle\n3. A Triangle\n";
cout << "Please enter 1 or 2 or 3: ";

// read the shape type
int shapeType;
cin >> shapeType;

// write a blank line for clarity
cout << endl;

// select appearance
cout << "Do you want a solid shape?\n";
cout << "yes: a solid shape\nno: a hollow shape\n";
cout << "Please enter y or n: ";

// read the shape appearance
char shapeAppearance;
cin >> shapeAppearance;

// write a blank line for clarity
cout << endl;

if (shapeType == 1)
{
// statements for generating a square
}
else if (shapeType == 2)
{
// statements for generating a rectangle
}
else if (shapeType == 3)
{
// statements for generating a triangles
}
return 0;
}

+

Algorithms

A square shape

The core part of the program is how to impelment the algorithms that can generate different shapes. I starts with the implementation of writing a square. Assuming the edge length of the square is 10, the program aims to generate two different squares as follows

1
2
3
4
5
6
7
8
9
10
**********          **********
* * **********
* * **********
* * **********
* * and **********
* * **********
* * **********
* * **********
* * **********
********** **********

+

Note that for all the shapes, the length or height means the number of columns or rows formed by asterisks (and spaces).

+

The algorithm is similar as that in the framed greeting program except that it doesn’t need to write the greeting itself. Essentially, there are two types of string:one is full filled with asterisks and another one is filled by blanks and two asterisks located at both ends of the blanks. The structure could be a for loop or while loop, which is up to your preference. Firstly, I define the variables that are needed.

1
2
3
4
5
6
7
8
9
10
// read in edge length for a square
int edgeLength;
cin >> edgeLength;

// string variable type 1: full of asterisks
string asteriskString(edgeLength, '*');

// string variable type 2: blanks and asterisks
string blankString(edgeLength - 2, ' ');
string mixedString = "*" + blankString + "*";

+

The solid square shape is simple and only needs one type of the string type vairable. If we choose one of the loop statements, the loop invariant can be expressed as: we have written r rows of output, of which r is the counting variable.

1
2
3
4
5
6
// invariant: we have written r rows of output
for (int r = 0; r != edgeLength; ++r)
{
// write a row whose length is the edge length
cout << asteriskString << endl;
}

+

To maintaint the loop invariant, r is increased by 1 after each iteration. Once the for loop finishes, the condition r != edgeLength is false and r = edgeLength. Then, the loop invariant becomes: we have written edgeLength rows of outputs, which keeps the correctness of our loop statement.

+

For a hollow square shape, the output of each row is conditional on its position.

1
2
3
4
5
// is it the first row or last row of outputs?
if (r == 0 || r == edgeLength - 1)
cout << asteriskString << endl;
else
cout << mixedString << endl;

+

Now, we can combine two cases together

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
   if (shapeType == 1)
{
cout << "Please enter the edge length: ";

// read the edge length
int edgeLength;
cin >> edgeLength;

// write a blank line to separate inputs and outputs
cout << endl;

// string type 1: full of asterisks
string asteriskString(edgeLength, '*');

// string type 2: blanks and asterisks
string blankString(edgeLength - 2, ' ');
string mixedString = "*" + blankString + "*";

for (int r = 0; r != edgeLength; ++r)
{
// is it rows of a solid shape or
// the first/ last row of a hollow shape ?

if (shapeAppearance == 'y' || r == 0 || r == edgeLength - 1)
// write a row whose length is the edge length
cout << asteriskString << endl;
else
cout << mixedString << endl;
}
}

+

A rectangle shape

Square shapes can be regarded as a special case of rectangles. We can simply modify the above algorithm to generate a rectangle. I use base lenght and height instead of the length and width for convenience.

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
else if (shapeType == 2)
{
cout << "Please enter the base length and height\n";
cout << "The base lenght = ";

// read the base length
int baseLength;
cin >> baseLength;

cout << "The height = ";

// read the height
int height;
cin >> height;

// write a blank line to separate inputs and outputs
cout << endl;

// string type 1: full of asterisks
string asteriskString(baseLength, '*');

// string type 2: blanks and asterisks
string blankString(baseLength - 2, ' ');
string mixedString = "*" + blankString + "*";

for (int r = 0; r != height; ++r)
{
// is it rows of a solid shape or
// the first/ last row of a hollow shape ?

if (shapeAppearance == 'y' || r == 0 || r == height - 1)
cout << asteriskString << endl;
else
cout << mixedString<< endl;
}
}

+

A triangle shape

For a triangle, the ideal result is to print out follow two shapes, providing the height is 4.

1
2
3
4
   *                   *
*** and * *
***** * *
******* *******

+

The solid trangle shape is formed only by asterisks ignoring the outer blanks. However, the number of asterisks of each row is different with eachother. From the table below, we can see a pattern in the numbers of asterisks of the rows.

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
The rth RowThe number of columns of each row
01
13
25
37
49
r(r*2 + 1)
Heightloop finishes
+

The table shows that when the program writes the rth row, it needs to write asterisks in a total number of r*2 + 1.
It also shows that the number of columns is height*2 - 1.

+

For the hollow triangle shapes, there are two types of rows: the first type is rows filled only by asterisks such as the first row and the last row; another type is rows formed by blanks with two asterisks located at both ends of the blanks. The table gives the pattern in the numbers of blanks of the rows.

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
The rth RowThe number of blanks in each row
00
11
23
35
47
r((r-1)*2 + 1)
Heightloop finishes
+

Correspondingly, I define variables inside a for loop as below

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
for (int r = 0; r != height; ++r)
{
// is it the row of a solid shape, or the first or the last row of the hollow shape
if (shapeAppearance == 'y' || r == 0 || r == height - 1)
{
// string type 1: full of asterisks
string asteriskString(r*2 + 1, '*');
}
else
{
// string type 2: blanks and asterisks
string blankString(((r-1)*2 + 1), ' ');
string mixedString = "*" + blankString + "*";
}
}

+

What we need next is to find the condition for writing each row as each row has different initial position. Before the real start of each row, we only need to output spaces. The table below gives the pattern of the initial column position of each row.

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
The rth RowThe column number of the initial position
heightloop finishes
height - 10
height - 21
rheight - r - 1
0height - 1
+

The table shows that when the loop processes the column number height - r - 1, it starts to write the strings that I defined above. When we incorporate the column loops into the row loops, the program becomes

+
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
else if (shapeType == 3)
{
// read the height
cout << "Please enter the height: ";
int height;
cin >> height;

// write a blank line to separate inputs and outputs
cout << endl;

// loop invariant: we have written r rows now
for (int r = 0; r != height; ++r)
{
// loop invariant: we have written c characters so far in the current line
int c = 0;

while(c != (height*2 - 1))
{ // // is it the row of a solid shape, or the first or the last row of the hollow shape
if (shapeAppearance == 'y'
|| r == 0
|| r == height - 1)
{
// is it time to write the real row?
if (c == height - r - 1)
{
// string type 1: full of asterisks
string asteriskString(r*2 + 1, '*');
cout << asteriskString;

// maintain the loop invariant
c += (r*2 + 1);
}
else
{
cout << ' ';
// maintain the loop invariant
++c;
}
}
else
{
// is it time to write the real row?
if (c == height - r - 1)
{
// string type 2: blanks and asterisks
string blankString(((r-1)*2 + 1), ' ');
string mixedString = "*" + blankString + "*";

cout << mixedString;
// maintain the loop invariant
c += ((r-1)*2 + 1 + 2);
}
else
{
cout << ' ';
++c;
}
}
}
cout << endl;
}
}
+

A complete program and tests

Let’s put all pieces together

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
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
//Accelerated C++ Solutions Exercises 2-5
#include <iostream>
#include <string>

// using namespace std and names
using std::cout; using std::cin;
using std::endl; using std::string;

int main()
{
// select a shape
cout << "Select a shape type\n";
cout << "1. A Square\n2. A Rectangle\n3. A Triangle\n";
cout << "Please enter 1 or 2 or 3: ";

// read the shape type
int shapeType;
cin >> shapeType;

// write a blank line for clarity
cout << endl;

// select appearance
cout << "Do you want a solid shape?\n";
cout << "yes: a solid shape\nno: a hollow shape\n";
cout << "Please enter y or n: ";

// read the shape appearance
char shapeAppearance;
cin >> shapeAppearance;

// write a blank line for clarity
cout << endl;

if (shapeType == 1)
{
cout << "Please enter the edge length: ";
int edgeLength;
cin >> edgeLength;

// write a blank line to separate inputs and outputs
cout << endl;

// string type 1: full of asterisks
string asteriskString(edgeLength, '*');

// string type 2: blanks and asterisks
string blankString(edgeLength - 2, ' ');
string mixedString = "*" + blankString + "*";

for (int r = 0; r != edgeLength; ++r)
{
if (shapeAppearance == 'y' || r == 0 || r == edgeLength - 1)
// write a row whose length is the edge length
cout << asteriskString << endl;
else
cout << mixedString << endl;
}
}
else if (shapeType == 2)
{
cout << "Please enter the base length and height\n";
cout << "The base lenght = ";

// read the base length
int baseLength;
cin >> baseLength;

cout << "The height = ";

// read the height
int height;
cin >> height;

// write a blank line to separate inputs and outputs
cout << endl;

// string type 1: full of asterisks
string asteriskString(baseLength, '*');

// string type 2: blanks and asterisks
string blankString(baseLength - 2, ' ');
string mixedString = "*" + blankString + "*";

for (int r = 0; r != height; ++r)
{
if (shapeAppearance == 'y' || r == 0 || r == height - 1)
cout << asteriskString << endl;
else
cout << mixedString<< endl;
}
}
else if (shapeType == 3)
{
cout << "Please enter the height: ";
int height;
cin >> height;

// write a blank line to separate inputs and outputs
cout << endl;

// loop invariant: we have written r rows now
for (int r = 0; r != height; ++r)
{
// loop invariant: we have written c characters so far in the current line
int c = 0;

while(c != (height*2 - 1))
{
if (shapeAppearance == 'y' || r == 0 || r == height - 1)
{
if (c == height - r - 1)
{
// string type 1: full of asterisks
string asteriskString(r*2 + 1, '*');
cout << asteriskString;

// maintain the loop invariant
c += (r*2 + 1);
}
else
{
cout << ' ';
// maintain the loop invariant
++c;
}
}
else
{
if (c == height - r - 1)
{
// string type 2: blanks and asterisks
string blankString(((r-1)*2 + 1), ' ');
string mixedString = "*" + blankString + "*";

cout << mixedString;
// maintain the loop invariant
c += ((r-1)*2 + 1 + 2);
}
else
{
cout << ' ';
++c;
}
}
}
cout << endl;
}
}
return 0;
}

+

Note that I used the same variables names asteriskString, blankString and mixedString in all three branches, but it does not matter the correctness as each branch is an independent block. I did three tests and the program works as expected. Please see the results below

+

Test 1

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
Select a shape type
1. A Square
2. A Rectangle
3. A Triangle
Please enter 1 or 2 or 3: 1

Do you want a solid shape?
yes: a solid shape
no: a hollow shape
Please enter y or n: y

Please enter the edge length: 5

*****
*****
*****
*****
*****

+

Test 2

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
Select a shape type
1. A Square
2. A Rectangle
3. A Triangle
Please enter 1 or 2 or 3: 2

Do you want a solid shape?
yes: a solid shape
no: a hollow shape
Please enter y or n: n

Please enter the base length and height
The base lenght = 8
The height = 4

********
* *
* *
********

+

Test 3

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
Select a shape type
1. A Square
2. A Rectangle
3. A Triangle
Please enter 1 or 2 or 3: 3

Do you want a solid shape?
yes: a solid shape
no: a hollow shape
Please enter y or n: n

Please enter the height: 6

*
* *
* *
* *
* *
***********

+
+

To be continued.

+ + +
+ + + + + + + + + + + +
+ + + +
+ + + +
+ +
+
+ + +
+ + + + + + + + + +
+ + + + + + + + + +
+
+ + + + +
+ + +
+ + + + +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/25/03/2018/Accelerated-C-Solutions-to-Exercises-Chapter-6/index.html b/25/03/2018/Accelerated-C-Solutions-to-Exercises-Chapter-6/index.html new file mode 100644 index 00000000..42c0f4e2 --- /dev/null +++ b/25/03/2018/Accelerated-C-Solutions-to-Exercises-Chapter-6/index.html @@ -0,0 +1,878 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Accelerated C++ Solutions to Exercises(Chapter 6) | Liam's Blog + + + + + + + + + + + + + + + + + + +
+
+ + + +
+
+
+
+ + +
+ + + + + + + + +
+ + + +
+ + + + + + + +
+ + + +

Accelerated C++ Solutions to Exercises(Chapter 6)

+ + + +
+ + + + + +
+ + + + + +

Exercise 6-0

Compile, execute, and test the programs in this chapter.

+

Solution & Results

Please find all programs and detailed analysis on Using library algorithms (Part 1), Using library algorithms (Part 2) and Using library algorithms (Part 3).

+

Exercise 6-1

Reimplement the frame and hcat operations from §5.8.1/93 and §5.8.3/94 to use iterators.

+

Solution & Results

This exercise has been completed here Putting strings together.

+

Exercise 6-2

Write a program to test the find_urls function.

+

Solution & Results

Please find the program and analysis on Using library algorithms (Part 1).

+

Exercise 6-3, 6-4

6-3: What does this program fragment do?

1
2
3
vector<int> u(10, 100);
vector<int> v;
copy(u.begin(), u.end(), v.begin());

+

Write a program that contains this fragment, and compile and execute it.

+

6-4: Correct the program you wrote in the previous exercise to copy from u into v. Thereare at least two possible ways to correct the program. Implement both, and describe the relative advantages and disadvantages of each approach.

+

Soltion & Results

The first statement creates an object of vector and initializes it with 10 elements that all equals to 100. The second statement creates an empty (i.e. due to default initialization) vector v . The third statement calls an algorithm copy to copy values in the range [u.begin(), u.end()) into the destination denoted by the third argument v.begin(). However, the destination sequence should be at least as large as the input range. Therefore, the fragment will lead to compilation errors.

+

Method 1

To correct this, we can initialize the v as an vector that has the same size as u. For example

1
vector<int> v(10);

+

This statement means that v contains 10 elements that all equals to 0. Then, the program should work fine.

+

Method 2

Alternatively, we can use back_inserter to append the copied elements to v. It naturelly increases the size of v and hence ensures enough space for holding those elements. The third statement is replaced by:

1
copy(u.begin(), u.end(), back_inserter(v));

+

Method 3

There is also an iterator adaptor inserter that creates an insert iterator for successive insertion into a container. Therefore, we can replace the back_inserter with inserter shown as follows

1
copy(u.begin(), u.end(), inserter(v, v.begin()));

+

This statement means that the elements of u are copied and inserted into v starting from the begining position of v.

+

comparison

All three methods can correct the original program. The first method is not practical as in most cases we probably don’t know the number of elements to copy. Contrarily, method 2 and method 3 ensures enough space for holding elements to copy. However, two iterator adaptors works differently:

+
    +
  1. back_inserter constructs a back-insert iterator that inserts new elements at the end of a container. The container should have member function push_back.

    +
  2. +
  3. inserter constructs an insert iterator that inserts new elements into a container successively starting from a specified position. The container should have member function insert.

    +
  4. +
+

Apparently, inserter provides more flexibility in inserting elements while back_inserter has more limitations. But more research should be done on their performances when dealing with massive data. I present a simple program to show that all three methods work fine and lead to same results.

+

A complete test program

Test 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
#include <iostream>
#include <vector>
#include <iterator>
#include <algorithm>

using std::cout; using std::endl;
using std::copy; using std::vector;
using std::inserter;using std::back_inserter;

void print(vector<int> &vec)
{
for(vector<int>::const_iterator iter = vec.begin(); iter != vec.end(); ++iter)
cout << *iter << endl;
}

int main()
{
vector<int> u(10, 100);

// method 1: let v has the same size as u
vector<int> v1(10);
copy(u.begin(), u.end(), v1.begin());
cout << "The results of method 1:" << endl;
print(v1);

cout << endl;

// method 2: back_inserter
vector<int> v2;
copy(u.begin(), u.end(), back_inserter(v2));
cout << "The results of method 2:" << endl;
print(v1);

cout << endl;

// method 3: inserter
vector<int> v3;
copy(u.begin(), u.end(), inserter(v3, v3.begin()));
cout << "The results of method 3:" << endl;
print(v3);

return 0;
}

+

Test Results

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
The results of method 1:
100
100
100
100
100
100
100
100
100
100

The results of method 2:
100
100
100
100
100
100
100
100
100
100

The results of method 3:
100
100
100
100
100
100
100
100
100
100

+

Exercise 6-5

Write an analysis function to call optimistic_median.

+

Solution & Results

Please find the analysis function here Using library algorithms (Part 2).

+

Exercise 6-6

Note that the function from the previous exercise and the functions from §6.2.2/113and §6.2.3/115 do the same task. Merge these three analysis functions into a singlefunction.

+

Solution & Results

Please find the solution strategy and analysis here Using library algorithms (Part 2).

+

Exercise 6-7

The portion of the grading analysis program from §6.2.1/110 that read and classified student records depending on whether they did (or did not) do all the homework is similar to the problem we solved in extract_fails. Write a function to handle this subproblem.

+

Solution & Results

This exercise requires us to seperate the students’ records into two groups: one group contains records that did all homeworks while the other one contains records that did not complete all homeworks. Specifically, we rewrite the main function such that all records will be stored into a vector did first, and then we extract and store the other group records into didnt via a function named extract_didnt. This function can be written exactly the same as the extract_fails function developed using the single-pass solution (see Using library algorithms (Part 3)).

+

I revised the original program described on Using library algorithms (Part 2) by creating a file contains extract_didnt function and the predicate did_all_hw:

+

extract_didnt.cpp

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
#include <vector>		// to get the declaration of vector
#include <algorithm> // to get the declaration of stable_partition
#include "Student_info.h" // to get the declaration of Student_info
#include "extract_didnt.h" // to get the declaration of extract_didnt

using std::vector;
using std::stable_partition;


bool did_all_hw(const Student_info &s)
{
return ((find(s.homework.begin(), s.homework.end(), 0)) == s.homework.end());
}

vector<Student_info> extract_didnt(vector<Student_info> &did)
{
vector<Student_info>::iterator iter = stable_partition(did.begin(), did.end(), did_all_hw);
vector<Student_info> didnt(iter, did.end());
did.erase(iter, did.end());
return didnt;
}

+

extract_didnt.h

1
2
3
4
5
6
7
8
9
10
#ifndef GUARD_EXTRACT_DIDNT_H
#define GUARD_EXTRACT_DIDNT_H

#include <vector>
#include "Student_info.h"

bool did_all_hw(const Student_info &);
std::vector<Student_info> extract_didnt(std::vector<Student_info> &);

#endif /* GUARD_EXTRACT_DIDNT_H */

+

Correspondingly, the main function becomes

+

mainfunction.cpp

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
#include <iostream>			// to get the declaration of cin, cout, endl
#include <vector> // to get the declaration of vector
#include "Student_info.h" // to get the declaration of Student_inf"
#include "grades_analysis.h" // to get the declaration of three analysis function
#include "write_analysis.h" // to get the declaration of write_analysis function
#include "extract_didnt.h"

using std::vector; using std::cout;
using std::cin; using std::endl;

int main()
{
vector<Student_info> did;

// read the student records
Student_info student;
while(read(cin, student))
{
did.push_back(student);
}

// extract records that don't complete all the homeworks
vector<Student_info> didnt = extract_didnt(did);

// verify thatthe analyses will show us something
if(did.empty())
{
cout << "No student did all the homework!" << endl;
return 1;
}
if(didnt.empty())
{
cout << "Every student did all the homework!" << endl;
return 1;
}

// do the analysis
write_analysis(cout, "median", median_analysis, did, didnt);
write_analysis(cout, "average", average_analysis, did, didnt);
write_analysis(cout, "median of homework turned in", optimistic_median_analysis, did, didnt);

return 0;
}

+

Exercise 6-8

Write a single function that can be used to classify students based on criteria of your choice. Test this function by using it in place of the extract_fails program, and use it in the program to analyze student grades.

+

Solution & Results

This exercise asks us to generalize the program in exercise 6-7 such that the program can deal with various kinds of criteria. A possible solution is to pass the criteria as arguments to the more generalized function classify which will classifies an input sequence according to the criteria. By doing so, we can pass different criteria to classify the students’ records based on our own preference.
Let’s define such classify function and put it in a sparate file.

+

classify.cpp

1
2
3
4
5
6
7
8
9
10
11
12
13
#include <vector>
#include <algorithm>
#include "Student_info.h"

using std::vector; using std::stable_partition;

vector<Student_info> classify(vector<Student_info> &v, bool criteria(const Student_info &s))
{
vector<Student_info>::iterator iter = stable_partition(v.begin(), v.end(), criteria);
vector<Student_info> criteria_false(iter, v.end());
v.erase(iter, v.end());
return criteria_false;
}

+

classify.h

1
2
3
4
5
6
7
8
9
10
#ifndef GUARD_CLASSIFY_H
#define GUARD_CLASSIFY_H

#include <vector>
#include "Student_info.h"

std::vector<Student_info> classify(std::vector<Student_info> &,
bool criteria(const Student_info &));

#endif /* GUARD_CLASSIFY_H */

+

Simialr to the extract_didnt function defined in exercise 6-7, this function returns a vector that contains all records that do not satisfy the criteria. The input sequence has also been modified and consequently contains only the records that statisfy the criteria.

+

To test the new function, the test program is designed to classify the students’ records as three groups (if available):

+
    +
  1. students who fail the final grade
  2. +
  3. students who pass the grade, and are awarded the first class honours
  4. +
  5. students who pass the grade, and are awarded the second class honours
  6. +
+

Firstly, we classify students according to criteria pgrade, as a result, the records are divided into two groups. By then, students contains only the passing records. Secondly, we further classify students according to another criterion which allowing us to extract students who are awarded the first class honours.

+

The test program includes following files:

+
    +
  1. classify.cpp, classify.h(as shown above)
  2. +
  3. mainfunction.cpp
  4. +
  5. criteria.cpp, criteria.h
  6. +
  7. grade.cpp, grade.h
  8. +
  9. Student_info.cpp, Student_info.h
  10. +
  11. print.cpp, print.h
  12. +
+

I present ecah file according to above order in below followed by the test results.

+

A complete program

mainfunction.cpp

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
65
66
67
68
69
#include <iostream>			// to get the declaration of cin, cout, endl
#include <algorithm> // to get the declaration of max
#include <vector> // to get the declaration of vector
#include <string> // to get the declaration of string
#include "Student_info.h" // to get the declaration of Student_info
#include "criteria.h" // to get the declaration of criteria
#include "classify.h" // to get the declaration of classify
#include "print.h" // to get the declaration of print

using std::vector; using std::cout;
using std::cin; using std::endl;
using std::string; using std::max;

int main()
{
vector<Student_info> students;
Student_info record;
string::size_type maxlen = 0;

// read and store all the records, and find the length of the longest name
while(read(cin, record))
{
maxlen = max(maxlen, record.name.size());
students.push_back(record);
}

// classfiy students according to the criteria: fail and pass
vector<Student_info> fails = classify(students, pgrade);

// verify that the analyses will show us something
if(fails.empty())
{
cout << "All students pass the grade!" << endl;
return 1;
}
if(students.empty())
{
cout << "All students fail the grade!" << endl;
return 1;
}

// classfiy students according to the criteria: first class and second class
vector<Student_info> first_class_honours = classify(students, second_class_honours);

// write lines of outputs
cout << "Students who fails the grade include:" << endl;
print(fails, maxlen);

cout << endl;
if(!first_class_honours.empty())
{
cout << "Students who are awarded first class honours include:" << endl;
print(first_class_honours, maxlen);
}
else
cout << "None of students is awarded first class honours." << endl;

cout << endl;
if(!students.empty())
{
cout << "Students who are awarded second class honours include:" << endl;
print(students, maxlen);
}
else
cout << "congratulations, all the students who "
"pass the grade are awarded first class honours." << endl;

return 0;
}

+

criteria.cpp

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
#include "grade.h"
#include "Student_info.h"

bool fgrade(const Student_info &s)
{
return grade(s) < 60;
}

bool pgrade(const Student_info &s)
{
return !fgrade(s);
}

bool first_class_honours(const Student_info &s)
{
return grade(s) >= 85;
}

bool second_class_honours(const Student_info &s)
{
return (!fgrade(s) && !first_class_honours(s));
}

+

criteria.h

1
2
3
4
5
6
7
8
9
10
#ifndef GUARD_CRITERIA_H
#define GUARD_CRITERIA_H

#include "Student_info.h"

bool fgrade(const Student_info &);
bool pgrade(const Student_info &);
bool first_class_honours(const Student_info &);
bool second_class_honours(const Student_info &s);
#endif /* GUARD_CRITERIA_H */

+

grade.cpp

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 <algorithm>
#include <stdexcept>
#include <vector>
#include "grade.h"
#include "Student_info.h"

using std::domain_error; using std::istream;
using std::vector; using std::sort;

// grade function 1
double grade(const Student_info &s)
{
return grade(s.midterm, s.final, s.homework);
}

// grade function 2
double grade(double midterm, double final, const vector<double> &hw)
{
if (hw.size() == 0)
throw domain_error("student has done no homework");
return grade(midterm, final, median(hw));
}

// grade function 3
double grade(double midterm, double final, double homework)
{
return 0.2*midterm + 0.4*final + 0.4*homework;
}

// compute the median of vector<double>
double median(vector<double> vec)
{
// get the size of the vector
typedef vector<double>::size_type vec_size;
vec_size size = vec.size();

// check whether the empty is empty
if (size == 0)
throw domain_error("median of an empty vector");

// sort the grades
sort(vec.begin(), vec.end());

// compute the median homework grade
vec_size mid = size/2;
return size % 2 == 0 ? (vec[mid] + vec[mid - 1])/2 : vec[mid];
}

+

grade.h

1
2
3
4
5
6
7
8
9
10
11
12
#ifndef GUARD_grade_h
#define GUARD_grade_h

// grade.h
#include<vector>
#include "Student_info.h"

double grade(const Student_info &);
double grade(double, double, const std::vector<double> &);
double grade(double, double, double);
double median(std::vector<double>);
#endif

+

Student_info.cpp

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 "Student_info.h"
using std::vector; using std::istream;

// argument to the function sort
bool compare(const Student_info &x, const Student_info &y)
{
return x.name < y.name;
}

// read the info
istream & read(istream &is, Student_info &s)
{
// reads and store the student's name, midterm and final exam grades
is >> s.name >> s.midterm >> s.final;

// reads and store all homework grades
read_hw(is, s.homework);
return is;
}

// read all homework grades
istream & read_hw(istream &in, vector<double> &hw)
{
if (in){
//get rid of previous contents
hw.clear();

// read homework grades
double x;
while(in >> x)
hw.push_back(x);

// clear the stream so that input will work for the next student
in.clear();
}
return in;
}

+

Student_info.h

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
#ifndef GUARD_Student_info
#define GUARD_Student_info

#include<iostream>
#include<string>
#include<vector>

struct Student_info{
std::string name;
double midterm, final;
std::vector<double> homework;
};

bool compare(const Student_info &, const Student_info &);
std::istream & read(std::istream &, Student_info &);
std::istream & read_hw(std::istream &, std::vector<double> &);
#endif

+

print.cpp

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
#include <iostream>
#include <vector>
#include <iomanip>
#include <string>
#include "grade.h"
#include "print.h"
#include "Student_info.h"

using std::cout; using std::setprecision;
using std::endl; using std::streamsize;
using std::string; using std::vector;

void print(const vector<Student_info> &records, const string::size_type &maxlen)
{
for (vector<Student_info>::const_iterator iter = records.begin(); iter != records.end(); ++iter)
{
// write the name, blanks
cout << (*iter).name << string(maxlen + 1 - (*iter).name.size(), ' ');

// compute and write the final grade
double final_grade = grade(*iter);
streamsize prec = cout.precision();
cout << setprecision(3) << final_grade << setprecision(prec) << endl;
}
}

+

print.h

1
2
3
4
5
6
7
8
9
10
#ifndef GUARD_PRINT_H
#define GUARD_PRINT_H

#include <string>
#include <vector>
#include "Student_info.h"

void print(const std::vector<Student_info> &records, const std::string::size_type &maxlen);

#endif /* GUARD_PRINT_H */

+

Test results

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
Inputs:

Phqgh 24.7879 58.6263 64.0505
Nlfdx 95.4242 27.3636 91.0404
Cxggb 16.1818 95.4747 26.7172
Uxwfn 35.9495 3.11111 22.3333
Tkjpr 68.4747 44.6263 57.3737
Pnrvy 16.3535 90.4242 88.0606
Syycq 5.90909 29.7071 50.0606
Ffmzn 84.5455 56.404 66.7677
Vwsre 23.3737 38.1818 82.2929
Fxtls 4.30303 77.0606 73.8687
Dpooe 29.7778 73.9798 12.8687
Ejuvp 55.7475 31.5253 50.5051
Poeyl 91.0707 37.5758 87.5354
Jvrvi 21.8889 22.4646 6.30303
Hwqnq 55.101 59.2424 37.4848
Jjloo 91.3636 74.202 96.2121
Whmsn 34.5354 99.1818 38
Sfzkv 48.8384 7.21212 10.1717
Lyjyh 51 49.1919 56.9899
Nkkuf 89.0202 95.8586 93.4343

Outputs:

Students who fails the grade include:
Phqgh 54
Cxggb 52.1
Uxwfn 17.4
Tkjpr 54.5
Syycq 33.1
Vwsre 52.9
Dpooe 40.7
Ejuvp 44
Jvrvi 15.9
Hwqnq 49.7
Sfzkv 16.7
Lyjyh 52.7

Students who are awarded first class honours include:
Jjloo 86.4
Nkkuf 93.5

Students who are awarded second class honours include:
Nlfdx 66.4
Pnrvy 74.7
Ffmzn 66.2
Fxtls 61.2
Poeyl 68.3
Whmsn 61.8
+

The test program shows our function works perfectly.

+
+

Exercise 6-9

Use a library algorithm to concatenate all the elements of a vector.

+

Solution & Results

The complete program below gives two possible solutions.

+

The logic of the first solution is that

+
    +
  1. take each element in vector vec as an independent container (i.e. a string).
  2. +
  3. apply copy to each independent string and copy all characters from it into a new string to hold the final result.
  4. +
  5. loop thru the vector vec and repeat step 2.
  6. +
+

Finally, each character contained in each element of the vector vec is copied and stored into the new string, which has the same effect as concatenating all the elements of vec.

+

The first solution should works fine but is not a better solution. Because if we don’t use copy, we can simply concatenate each element of vec without accessing each character.

+

The second solution applies accumulate algorithm to concatenate each element from vec directly. accumulate returns the sum of all elements of a sequence denoted by its first two arguments. The third argument provides the initial value for the summation and determines the type of the returned value. In this case, the initial value is set to an empty string.

+

A complete 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
#include <iostream>	// to get the declaration of cout
#include <algorithm> // to get the declaration of copy
#include <iterator> // to get the declaration of back_inserter
#include <vector> // to get the declaration of vector
#include <string> // to get the declaration of string
#include <numeric> // to get the declaration of accumulate

using std::vector; using std::string;
using std::cout; using std::copy;
using std::endl; using std::back_inserter;
using std::accumulate;

int main()
{
vector<string> vec{"Please", "write", "an", "analysis", "function"};

// method 1
string vecCopy1;
for(vector<string>::const_iterator iter = vec.begin(); iter != vec.end(); ++iter)
{
copy((*iter).begin(), (*iter).end(), back_inserter(vecCopy1));
}
cout << vecCopy1 << endl;

// method 2
string vecCopy2;
vecCopy2 = accumulate(vec.begin(), vec.end(), vecCopy2);
cout << vecCopy2 << endl;

return 0;
}

+

The results below show that both two methods work fine and give correct results.

+

Results

1
2
Pleasewriteananalysisfunction
Pleasewriteananalysisfunction

+
+

Reference

Koenig, A. and Moo, B.E., 2000. Accelerated C++.

+

Lippman, S.B., Lajoie, J. and Moo, B.E., 2013. C++ Primer, 5th edt.

+ + +
+ + + + + + + + + + + +
+ + + +
+ + + +
+ +
+
+ + +
+ + + + + + + + + +
+ + + + + + + + + +
+
+ + + + +
+ + +
+ + + + +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/25/03/2018/C-Using-library-algorithms-Part-3/index.html b/25/03/2018/C-Using-library-algorithms-Part-3/index.html new file mode 100644 index 00000000..d98de3b6 --- /dev/null +++ b/25/03/2018/C-Using-library-algorithms-Part-3/index.html @@ -0,0 +1,852 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + C++ - Using library algorithms (Part 3) | Liam's Blog + + + + + + + + + + + + + + + + + + +
+
+ + + +
+
+
+
+ + +
+ + + + + + + + +
+ + + +
+ + + + + + + +
+ + + +

C++ - Using library algorithms (Part 3)

+ + + +
+ + + + + +
+ + + + + +

Revisit the classifying program

Recalling the extract_fails function developed in last chapter:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
vector<Student_info> extract_fails(vector<Student_info>& students)
{
vector<Student_info> fail;
vector<Student_info>::size_type i = 0;
// invariant:elements [0, i) of students represent passing grades
while (i != students.size())
{
if (fgrade(students[i]))
{
fail.push_back(students[i]);
students.erase(students.begin() + i);
}
else
++i;
}
return fail;
}

+

Instead of using member functions like push_back and erase, we can also apply library algorithms to accomplish the task that extracts failing records. Following parts introduce two algorithmic solutions.

+

A two-pass solution

1
2
3
4
5
6
7
8
vector<Student_info> extract_fails(vector<Student_info> &students)
{
vector<Student_info> fails;
remove_copy_if(students.begin(), students.end(), back_inserter(fails), pgrade);

students.erase(remove_if(students.begin(), students.end(), fgrade), students.end());
return fails;
}
+

This function uses remove_copy_if algorithm to copy all failing records from students into a new vector fails. remove_copy_if finds and “removes”(i.e. not copy) all values that satisfy the predicate pgrade. Apparently, pgrade is a predicate on grades and returns true if the final grade is equal or greater than 60. In other words, it is a predicate that reverts the results of calling fgrade.

1
2
3
4
bool pgrade(const Student_info &s)
{
return !fgrade(s);
}

+

However, remove_copy_if works similar to remove_copy. Both two algorithms won’t change the input sequence instead they copy the remaining values into a new vector. Therefore, we need one step more to modify students:

1
students.erase(remove_if(students.begin(), students.end(), fgrade), students.end());

+

In this statements, remove_if finds and “removes” all values that satisfies the predicate fgrade. However, the tricky point is that how does it remove those faling records?

+

The removal is done by replacing the elements for which pred returns true by the next element for which it does not, and signaling the new size of the shortened range by returning an iterator to the element that should be considered its new past-the-end element. The relative order of the elements not removed is preserved, while the elements between the returned iterator and last are left in a valid but unspecified state (see remove_if).

+

For example, we have a sequence of final grades:

1
s0(pass), s1(fail), s2(fail), s3(pass), s4(pass), s5(fail)

+

After the execution of remove_if function like above, the sequence becomes

1
s0(pass), ns1(pass), ns2(pass), s3(pass), s4(pass), s5(fail)

+

It copies passing records, s3 and s4, and replacing the faling records, s1 and s2. Then, it returns an iterator that refers to s3. The elements beteen s3 and s.end() (i.e. one past s5) are still there. Therefore, we need to erase them using erase function, resulting that only passing grades are left. The erase function here takes two arguments which denote a range of elements to be erased. It returns the new students.end().

+

A single pass solution

The two pass solution seems not very ideal as it computes each final grade twice. Another algorithm stable_partition solves this by seperating a sequence into two pieces using a predicate. For example,

1
stable_partition(students.begin(), students.end(), pgrade);

+

This algorithm rearranges the sequence and put all elements that satisfy pgrade ahead of the elements that do not satisfy pgrade. Then, it returns an iterator that denotes the first element of the second group (i.e. one past the last element of the first group). If all values do not satisfy the pgrade, the returned iterator refers to the first element of the sequence.

+

Now we can rewrite the extract_fail function applying stable_partition algorithm.

1
2
3
4
5
6
7
vector<Student_info> extract_fails(vector<Student_info> &students)
{
vector<Student_info>::iterator iter = stable_partition(students.begin(), students.end(), pgrade);
vector<Student_info> fails(iter, students.end());
students.erase(iter, students.end());
return fails;
}

+

A complete program

I revised the program that developed in Exercise 5-6 by replacing the original extract_fails function with above two functions. The new program accomplishes a simple comparison of the two alternatives. All files and the test results can be found in the remainder of this post.

+

file list

+
    +
  1. mainfunction.cpp.
  2. +
  3. extract_fails.cpp, extract_fails.h: declares and defines functions including fgrade, pgrade, extract_fails_m1, extract_fails_m2.
  4. +
  5. grade.cpp, grade.h: declares and defines functions including grade, median.
  6. +
  7. Student_info.cpp, Student_info.h: declares and defines functions including compare, read, read_hw, and Student_info type struct.
  8. +
  9. print.cpp, print.h: declares and defines functions including print.
  10. +
+
+

mainfunction.cpp

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
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
// Accelerated C++ Solutions Exercises 6-0: the revised extract_fails
#include <algorithm>
#include <stdexcept>
#include <iostream>
#include <vector>
#include <string>
#include <iterator>
#include <chrono>
#include "Student_info.h"
#include "grade.h"
#include "print.h"
#include "extract_fails.h"

using std::cin; using std::sort; using std::max;
using std::cout; using std::endl; using std::domain_error;
using std::string; using std::max; using std::stable_partition;
using std::vector; using std::remove_copy;
using std::remove_copy_if; using std::back_inserter;

int main()
{
vector<Student_info> students;
Student_info record;
string::size_type maxlen = 0;

// read and store all the records, and find the length of the longest name
while(read(cin, record))
{
maxlen = max(maxlen, record.name.size());
students.push_back(record);
}
try{
// measure the performance for two pass solution
typedef std::chrono::high_resolution_clock Clock;
Clock::time_point startTime = Clock::now(); // get current time
vector<Student_info> fails = extract_fails_m1(students); // extract records for failing students
Clock::time_point endTime = Clock::now(); // get current time

cout << "The two pass solution took me "
<< std::chrono::duration_cast<std::chrono::duration<double>>(endTime - startTime).count() << " seconds" << endl;


// measure the performance for one pass solution
startTime = Clock::now(); // get current time
fails = extract_fails_m2(students); // extract records for failing students
endTime = Clock::now(); // get current time

cout << "The one pass solution took me "
<< std::chrono::duration_cast<std::chrono::duration<double>>(endTime - startTime).count() << " seconds" << endl;

// write each line of outputs for passing students
if (!students.empty())
{
//alphabetize the records

sort(students.begin(), students.end(), compare);
cout << "Students who passed: " << endl;
print(students, maxlen);
}
else
cout << "What a pity! all students failed.";

// write a blank line
cout << endl;

// // write each line of outputs for failing students
if(!fails.empty())
{
//alphabetize the records

sort(fails.begin(), fails.end(), compare);
cout << "Students who failed: " << endl;
print(fails, maxlen);
}
else
cout << "Congratulations! all students passed.";

}catch(domain_error e){
cout << e.what();
}

return 0;
}

+

extract_fails.cpp

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
#include <vector>			
#include <algorithm>
#include <iterator>
#include "Student_info.h"
#include "grade.h"
#include "extract_fails.h"

using std::vector; using std::stable_partition;
using std::remove_if; using std::remove_copy_if;
using std::back_inserter;

// predicate
bool fgrade(const Student_info &s)
{
return grade(s) < 60;
}
// predicate
bool pgrade(const Student_info &s)
{
return !fgrade(s);
}

// two-pass solution
vector<Student_info> extract_fails_m1(vector<Student_info> &students)
{
vector<Student_info> fails;
remove_copy_if(students.begin(), students.end(), back_inserter(fails), pgrade);

students.erase(remove_if(students.begin(), students.end(), fgrade), students.end());
return fails;
}

// single-pass solution
vector<Student_info> extract_fails_m2(vector<Student_info> &students)
{
vector<Student_info>::iterator iter = stable_partition(students.begin(), students.end(), pgrade);
vector<Student_info> fails(iter, students.end());
students.erase(iter, students.end());
return fails;
}

+

extract_fails.h

1
2
3
4
5
6
7
8
9
10
11
12
#ifndef GUARD_EXTRACT_FAILS_H
#define GUARD_EXTRACT_FAILS_H

#include <vector>
#include "Student_info.h"

bool fgrade(const Student_info &);
bool pgrade(const Student_info &);
std::vector<Student_info> extract_fails_m1(std::vector<Student_info> &);
std::vector<Student_info> extract_fails_m2(std::vector<Student_info> &);

#endif /* GUARD_EXTRACT_FAILS_H */

+

grade.cpp

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
#include <algorithm>
#include <stdexcept>
#include <vector>
#include "grade.h"
#include "Student_info.h"

using std::domain_error; using std::istream;
using std::vector; using std::sort;

// grade function 2
double grade(const Student_info &s)
{
return grade(s.midterm, s.final, s.homework);
}

// grade function 2
double grade(double midterm, double final, const vector<double> &hw)
{
if (hw.size() == 0)
throw domain_error("student has done no homework");
return grade(midterm, final, median(hw));
}

// grade function 3
double grade(double midterm, double final, double homework)
{
return 0.2*midterm + 0.4*final + 0.4*homework;
}

// compute the median of vector<double>
double median(vector<double> vec)
{
// get the size of the vector
typedef vector<double>::size_type vec_size;
vec_size size = vec.size();

// check whether the empty is empty
if (size == 0)
throw domain_error("median of an empty vector");

// sort the grades
sort(vec.begin(), vec.end());

// compute the median homework grade
vec_size mid = size/2;
return size % 2 == 0 ? (vec[mid] + vec[mid - 1])/2 : vec[mid];
}
···

**grade.h**
```c++
#ifndef GUARD_grade_h
#define GUARD_grade_h

// grade.h
#include<vector>
#include "Student_info.h"

double grade(const Student_info &);
double grade(double, double, const std::vector<double> &);
double grade(double, double, double);
double median(std::vector<double>);
#endif

+

Student_info.cpp

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 <vector>
#include <iostream>
#include "Student_info.h"

using std::vector; using std::istream;

bool compare(const Student_info &x, const Student_info &y)
{
return x.name < y.name;
}

istream & read(istream &is, Student_info &s)
{
// reads and store the student's name, midterm and final exam grades
is >> s.name >> s.midterm >> s.final;

// reads and store all homework grades
read_hw(is, s.homework);
return is;
}

istream & read_hw(istream &in, vector<double> &hw)
{
if (in){
//get rid of previous contents
hw.clear();

// read homework grades
double x;
while(in >> x)
hw.push_back(x);

// clear the stream so that input will work for the next student
in.clear();
}
return in;
}

+

Student_info.h

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
#ifndef GUARD_Student_info
#define GUARD_Student_info

#include<iostream>
#include<string>
#include<vector>

struct Student_info{
std::string name;
double midterm, final;
std::vector<double> homework;
};

bool compare(const Student_info &, const Student_info &);
std::istream & read(std::istream &, Student_info &);
std::istream & read_hw(std::istream &, std::vector<double> &);
#endif

+

print.cpp

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
#include <iostream>
#include <vector>
#include <iomanip>
#include <string>
#include "grade.h"
#include "print.h"
#include "Student_info.h"

using std::cout; using std::setprecision;
using std::endl; using std::streamsize;
using std::string; using std::vector;

void print(const vector<Student_info> &records, const string::size_type &maxlen)
{
for (vector<Student_info>::const_iterator iter = records.begin(); iter != records.end(); ++iter)
{
// write the name, blanks
cout << (*iter).name << string(maxlen + 1 - (*iter).name.size(), ' ');

// compute and write the final grade
double final_grade = grade(*iter);
streamsize prec = cout.precision();
cout << setprecision(3) << final_grade << setprecision(prec) << endl;
}
}

+

print.h

1
2
3
4
5
6
7
8
9
10
#ifndef GUARD_PRINT_H
#define GUARD_PRINT_H

#include <string>
#include <vector>
#include "Student_info.h"

void print(const std::vector<Student_info> &, const std::string::size_type &);

#endif /* GUARD_PRINT_H */

+

Performance comparison

+ + + + + + + + + + + + + + + + + + + + + + + + +
Number of linesTwo-pass solutionSingle-pass solution
100.001035 seconds0.000000 seconds
10000.006006 seconds0.000993 seconds
100000.017035 seconds0.003023 seconds
+

The results shows as expected that the single-pass solution has much better performance that the two-pass solution.

+

A simple summary

Algorithm act on container elements but do not act on containers and do not change the size of a container.

+ + +
+ + + + + + + + + + + +
+ + + +
+ + + +
+ +
+
+ + +
+ + + + + + + + + +
+ + + + + + + + + +
+
+ + + + +
+ + +
+ + + + +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/26/05/2018/C-Implementations-Circular-Doubly-Linked-List/index.html b/26/05/2018/C-Implementations-Circular-Doubly-Linked-List/index.html new file mode 100644 index 00000000..966855d7 --- /dev/null +++ b/26/05/2018/C-Implementations-Circular-Doubly-Linked-List/index.html @@ -0,0 +1,798 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + C++ Implementations: Singly Circular Linked List with a sentinel | Liam's Blog + + + + + + + + + + + + + + + + + + +
+
+ + + +
+
+
+
+ + +
+ + + + + + + + +
+ + + +
+ + + + + + + +
+ + + +

C++ Implementations: Singly Circular Linked List with a sentinel

+ + + +
+ + + + + +
+ + + + + +

Header file

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
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
#ifndef CIRCULARLINKEDLIST_H_
#define CIRCULARLINKEDLIST_H_

#include <cstddef>
#include <iostream>

template <typename T>
class CircularLinkedList{
struct Node;

public:
typedef std::size_t size_type;
typedef T value_type;

// create an empty linked list
CircularLinkedList(): sentinel(create_sentinel()), count(0){
std::cout << "default constructor" << std::endl;
}

// create an linked list with user supplied size and value
explicit CircularLinkedList(size_type, const T& val = T());

// copy constructor
CircularLinkedList(const CircularLinkedList&);

// assignment operator
CircularLinkedList& operator=(const CircularLinkedList&);

// destructor
~CircularLinkedList() {
clear();
delete sentinel;
sentinel = nullptr;
std::cout << "destructor" << std::endl;
}

void clear();

bool empty() const { return sentinel == sentinel->next; }
size_type size() const { return count; }

Node* begin() { return sentinel->next; }
const Node* begin() const { return sentinel->next; }

Node* end() { return sentinel; }
const Node* end() const { return sentinel; }

// insert at begining
void push_front(const T&);

// insert at the end
void push_back(const T&);

// insert at the nth position, that is, after (n-1)the position
// the range of position is [1, size()]
void insert(size_type, const T&);

// delete at the begining
void pop_front();

// delete the last element
void pop_back();

// delete at nth position
void erase(size_type);

// reverse the order iteratively
void reverse();

// remove elements with specific values
void remove(const T&);

private:
struct Node{
T data;
Node* next;
};

Node* sentinel;
size_type count;


Node* create_sentinel(){
Node* nil = new Node;
nil->next = nil;
return nil;
}


Node* create(const T& val = T()){
Node* new_node = new Node;
new_node->data = val;
new_node->next = sentinel;
return new_node;
}
};

// O(n)
template <typename T>
CircularLinkedList<T>::CircularLinkedList(size_type n, const T& val):
sentinel(create_sentinel()), count(0){
std::cout << "constructor with parameters" << std::endl;

Node* current = sentinel;
while(count != n){
current->next = create(val);
current = current->next;
++count;
}
}

// O(n)
template <typename T>
CircularLinkedList<T>::CircularLinkedList(const CircularLinkedList& l):
sentinel(create_sentinel()), count(0){
Node* current = sentinel;
const Node* temp = l.begin();
while(temp != l.end()){
current->next = create(temp->data);
current = current->next;
temp = temp->next;
++count;
}
}

// O(n)
template <typename T>
CircularLinkedList<T>& CircularLinkedList<T>::operator=
(const CircularLinkedList& l){
if(&l != this){
clear();
Node* current = sentinel;
const Node* temp = l.begin();
while(temp != l.end()){
current->next = create(temp->data);
current = current->next;
temp = temp->next;
++count;
}
}
return *this;
}

// O(n)
template <typename T>
void CircularLinkedList<T>::clear(){
Node* current = sentinel;
if(current->next != sentinel){
Node* temp = current->next;
current->next = temp->next;
delete temp;
--count;
}
}

// O(1)
template <class T>
void CircularLinkedList<T>::push_front(const T& val) {
Node* current = sentinel;
Node* new_node = create(val);
new_node->next = current->next;
current->next = new_node;
++count;
}

// O(n)
template <class T>
void CircularLinkedList<T>::push_back(const T& val) {
Node* current = sentinel;
while(current->next != sentinel)
current = current->next;

current->next = create(val);
++count;
}

// O(n)
template <class T>
void CircularLinkedList<T>::insert(size_type position, const T& val){
if(position < 1 || position > size())
throw std::domain_error("Invalid Position");

Node* new_node = create(val);
Node* temp = sentinel;
for (size_type i = 0; i != position-1; ++i)
temp = temp->next;
new_node->next = temp->next;
temp->next = new_node;
++count;
}

// O(1)
template <class T>
void CircularLinkedList<T>::pop_front(){
erase(1);
}

// O(n)
template <class T>
void CircularLinkedList<T>::pop_back(){
erase(size());
}

// O(n)
template <class T>
void CircularLinkedList<T>::erase(size_type position){
if(position < 1 || position > size())
throw std::domain_error("Invalid Position");

Node* current = sentinel;
for(size_type i = 0; i != position - 1; ++i)
current = current->next;

Node* temp = current->next;
current->next = temp->next;
delete temp;
--count;
}

// O(n)
template <class T>
void CircularLinkedList<T>::remove(const T& val){
Node* current = sentinel;
while(current->next != sentinel){
if(current->next->data == val){
Node* temp = current->next;
current->next = temp->next;
delete temp;
--count;
}else current = current->next;
}
}

// O(n)
template <class T>
void CircularLinkedList<T>::reverse(){
if (size() < 2) return;
Node* current = sentinel->next;
Node* PREV = sentinel;
Node* NEXT;
while(current->next != sentinel){
NEXT = current->next;
current->next = PREV;
PREV = current;
current = NEXT;
}
current->next = PREV;
sentinel->next = current;
}

#endif /* CIRCULARLINKEDLIST_H_ */
+

Test program and results

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
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
/*
* this program tests all operations that provided by the
* CircularLinkedList<T> class
* created by Liam on: 28 May 2018
*/

#include <iostream>
#include "CircularLinkedList.h"

using std::endl; using std::cout;

template <class Pointer>
void print(Pointer begin, Pointer end){
while(begin != end){
cout << begin->data << " ";
begin = begin->next;
}
cout<< endl;
}

int main(){

{ // construct an empty linked list
CircularLinkedList<int> s;
if(s.empty())
cout << "s is an empty linked list\n"
"the size of s1 is: " << s.size() << endl;

// call destructor once reaches the end of this block
}
cout << endl;

{ // construct a linked list that contains 10 elements, all values are 100
CircularLinkedList<int> s(10, 100);

// construct a linked list by copying from s
CircularLinkedList<int> s_copy(s);
if(!s.empty() && !s_copy.empty()){
cout << "the size of s is: " << s.size() << endl;
cout << "the size of s_copy is: " << s_copy.size() << endl;
}

// print the contents of s_copy
cout << "all elements in s_copy: ";
print(s.begin(), s.end());

// call destructor twice
}
cout << endl;

{ // assignment
CircularLinkedList<int> s(10, 100);
CircularLinkedList<int> s_copy;
s_copy = s;

// print the contents of s_copy
cout << "all elements in s_copy: ";
print(s_copy.begin(), s_copy.end());
}
cout << endl;

{ // push front
CircularLinkedList<double> s;
for(int i = 5; i != 0; --i)
s.push_front(i);

cout << "after adding elements at front, s becomes: ";
print(s.begin(), s.end());

// push back
for(int i = 5; i != 0; --i)
s.push_back(i);

cout << "after adding elements at the end, s becomes: ";
print(s.begin(), s.end());


// insert at position 5
for(int i = 5; i != 0; --i)
s.insert(i, 0);

cout << "after inserting elements in-between, s becomes: ";
print(s.begin(), s.end());

// delete from the begining
for(int i = 5; i != 0; --i)
s.pop_front();

cout << "after deleting from the begining, s becomes: ";
print(s.begin(), s.end());

// delete from the end
for(int i = 5; i != 0; --i)
s.pop_back();

cout << "after deleting from the end, s becomes: ";
print(s.begin(), s.end());

// erase at in-between positions
for(int i = 3; i != 0; --i)
s.erase(3);

cout << "after deleting from other positions, s becomes: ";
print(s.begin(), s.end());

}
cout << endl;

{ // remove
CircularLinkedList<int> s(5, 5);
for(int i = 5; i != 0; --i)
s.insert(5, i);

cout << "at present, s contains following elements: ";
print(s.begin(), s.end());

s.remove(5);
cout << "after removing all elements equal 5, s becomes: ";
print(s.begin(), s.end());
}
cout << endl;

{ // test reverse function
CircularLinkedList<int> s;
for(int i = 0; i != 10; ++i)
s.push_back(i);

cout << "at present, s contains following elements: ";
print(s.begin(), s.end());

s.reverse();
cout << "reverse: ";
print(s.begin(), s.end());

s.reverse();
cout << "reverse again: ";
print(s.begin(), s.end());

}

return 0;
}
+

Outputs:

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
default constructor
s is an empty linked list
the size of s1 is: 0
destructor

constructor with parameters
the size of s is: 10
the size of s_copy is: 10
all elements in s_copy: 100 100 100 100 100 100 100 100 100 100
destructor
destructor

constructor with parameters
default constructor
all elements in s_copy: 100 100 100 100 100 100 100 100 100 100
destructor
destructor

default constructor
after adding elements at front, s becomes: 1 2 3 4 5
after adding elements at the end, s becomes: 1 2 3 4 5 5 4 3 2 1
after inserting elements in-between, s becomes: 0 1 0 2 0 3 0 4 0 5 5 4 3 2 1
after deleting from the begining, s becomes: 3 0 4 0 5 5 4 3 2 1
after deleting from the end, s becomes: 3 0 4 0 5
after deleting from other positions, s becomes: 3 0
destructor

constructor with parameters
at present, s contains following elements: 5 5 5 5 1 2 3 4 5 5
after removing all elements equal 5, s becomes: 1 2 3 4
destructor

default constructor
at present, s contains following elements: 0 1 2 3 4 5 6 7 8 9
reverse: 9 8 7 6 5 4 3 2 1 0
reverse again: 0 1 2 3 4 5 6 7 8 9
destructor

+ + +
+ + + + + + + + + + + +
+ + + +
+ + + +
+ +
+
+ + +
+ + + + + + + + + +
+ + + + + + + + + +
+
+ + + + +
+ + +
+ + + + +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/26/05/2018/C-Implementations-Doubly-Linked-List/index.html b/26/05/2018/C-Implementations-Doubly-Linked-List/index.html new file mode 100644 index 00000000..628088d9 --- /dev/null +++ b/26/05/2018/C-Implementations-Doubly-Linked-List/index.html @@ -0,0 +1,798 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + C++ Implementations: Doubly Linked List | Liam's Blog + + + + + + + + + + + + + + + + + + +
+
+ + + +
+
+
+
+ + +
+ + + + + + + + +
+ + + +
+ + + + + + + +
+ + + +

C++ Implementations: Doubly Linked List

+ + + +
+ + + + + +
+ + + + + +

Head files

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
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
#ifndef DOUBLYLINKEDLIST_H_
#define DOUBLYLINKEDLIST_H_

#include <cstddef>
#include <stdexcept>
#include <iostream>

template <typename T>
class DoublyLinkedList{
struct Node;
public:
typedef std::size_t size_type;
typedef T value_type;

// default constructor
DoublyLinkedList(): ptrToHead(nullptr), count(0){
std::cout << "default constructor" << std::endl;
}

// constructor with parameters
explicit DoublyLinkedList(size_type, const T&val = T());

// copy constructor
DoublyLinkedList(const DoublyLinkedList&);

// assignment operator
DoublyLinkedList& operator=(const DoublyLinkedList&);

// destructor
~DoublyLinkedList() {
std::cout << "destructor" << std::endl;
clear();
}

void clear();
bool empty() const { return ptrToHead == nullptr; }
size_type size() const { return count; }
Node* begin() { return ptrToHead; }
const Node* begin() const { return ptrToHead; }

void push_front(const T&);
void push_back(const T&);
void insert(size_type, const T&);

void pop_front();
void pop_back();
void erase(size_type);

void remove(const T&);
void reverse();

private:
struct Node{
T data;
Node* prev;
Node* next;
};

Node* ptrToHead;
size_type count;

Node* create(const T& val){
Node* new_node = new Node;
new_node->data = val;
new_node->prev = nullptr;
new_node->next = nullptr;
return new_node;
}
};

// constructor with parameters:O(n)
template <typename T>
DoublyLinkedList<T>::DoublyLinkedList(size_type n, const T&val){
std::cout << "constructor with parameters" << std::endl;
if(n > 0){
ptrToHead = create(val);
count = 1;

Node* current = ptrToHead;
while(count != n){
current->next = create(val);
current->next->prev = current;
current = current->next;
++count;
}
}
}

// copy constructor: O(n)
template <typename T>
DoublyLinkedList<T>::DoublyLinkedList(const DoublyLinkedList& l)
: ptrToHead(nullptr), count(0){
std::cout << "copy constructor" << std::endl;
if(!l.empty()){
ptrToHead = create(l.begin()->data);
count = 1;

const Node* temp = l.begin();
Node* current = ptrToHead;
while(count != l.size()){
current->next = create(temp->next->data);
current->next->prev = current;
current = current->next;
temp = temp->next;
++count;
}
}
}

// assignment operator: O(n)
template <typename T>
DoublyLinkedList<T>& DoublyLinkedList<T>::operator= (const DoublyLinkedList& l)
{
std::cout << "assignment operator" << std::endl;
if(&l != this){
clear();
if(!l.empty()){
ptrToHead = create(l.begin()->data);
count = 1;

const Node* temp = l.begin();
Node* current = ptrToHead;
while(count != l.size()){
current->next = create(temp->next->data);
current->next->prev = current;
current = current->next;
temp = temp->next;
++count;
}
}
}
return *this;
}

// O(n)
template <typename T>
void DoublyLinkedList<T>::clear(){
Node* current = ptrToHead;
while(current != nullptr){
ptrToHead = ptrToHead->next;
delete current;
current = ptrToHead;
--count;
}
}

// O(1)
template <typename T>
void DoublyLinkedList<T>::push_front(const T& val){
Node* new_node = create(val);
if(ptrToHead != nullptr){
new_node->next = ptrToHead;
ptrToHead->prev = new_node;
}
ptrToHead = new_node;
++count;
}

// O(n)
template <typename T>
void DoublyLinkedList<T>::push_back(const T& val){
Node* new_node = create(val);
if(ptrToHead == nullptr){
ptrToHead = new_node;
++count;
return;
}

Node* current = ptrToHead;
while(current->next != nullptr)
current = current->next;

current->next = new_node;
new_node->prev = current;
++count;
}

// O(n)
template <typename T>
void DoublyLinkedList<T>::insert(size_type position, const T& val){
if(position < 1 || position > size())
throw std::domain_error("Invalid Position");
else if(position == 1)
push_front(val);
else{
Node* new_node = create(val);
Node* current = ptrToHead;

for(size_type i = 0; i != position - 1; ++i)
current = current->next;
current->prev->next = new_node;
new_node->next = current;
new_node->prev = current->prev;
current->prev = new_node;
++count;
}
}

// O(1)
template <typename T>
void DoublyLinkedList<T>::pop_front(){ erase(1); }

// O(n)
template <typename T>
void DoublyLinkedList<T>::pop_back(){ erase(size()); }

// O(n)
template <typename T>
void DoublyLinkedList<T>::erase(size_type position){
if(position < 1 || position > size())
throw std::domain_error("Invalid Position");

Node* current = ptrToHead;

if(size() == 1){
ptrToHead = nullptr;
}else if(position == 1){
ptrToHead = ptrToHead->next;
ptrToHead->prev = nullptr;
}else if(position == size()){
while(current->next != nullptr)
current = current->next;
current->prev->next = nullptr;
}else{
for (size_type i = 0; i != position - 1; ++i){
current = current->next;
}
current->next->prev = current->prev;
current->prev->next = current->next;
}

delete current;
--count;
}

// O(n)
template <typename T>
void DoublyLinkedList<T>::remove(const T& val){
Node* current = ptrToHead;
while(current != nullptr){
if(current->data == val){
Node* temp = current->next;
if(current->prev == nullptr){
ptrToHead = ptrToHead->next;
if(ptrToHead != nullptr){
ptrToHead->prev = nullptr;
}
}else if(current->next == nullptr){
current->prev->next = nullptr;
}else{
current->next->prev = current->prev;
current->prev->next = current->next;
}

delete current;
current = temp;
}else
current = current->next;
}
}

// O(n)
template <typename T>
void DoublyLinkedList<T>::reverse(){
Node* current = ptrToHead;
Node* PREV = nullptr;
while(current != nullptr){
current->prev = current->next;
current->next = PREV;
PREV = current;
current = current->prev;
}
ptrToHead = PREV;
}
#endif /* DOUBLYLINKEDLIST_H_ */
+

Test program and results

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
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
/*
* this program tests all operations that provided by the
* DoublyLinkedList<T> class
* created by Liam on: 28 Apr 2018
*/

#include <iostream>
#include "DoublyLinkedList.h"

using std::endl; using std::cout;

// print and reverse print
template <class T>
void print(T& l){
cout << "print in order: ";
for(auto it = l.begin(); it != nullptr; it = it->next){
cout << it->data << " ";
}

cout << "\n" << "print in reverse: ";
auto it = l.begin();
while(it->next != nullptr){
it = it->next;

}
while(it != nullptr){
cout << it->data << " ";
it = it->prev;
}

cout << endl;
}

int main(){

{ // construct an empty linked list
DoublyLinkedList<int> s;
if(s.empty())
cout << "s is an empty linked list\n"
"the size of s1 is: " << s.size() << endl;

// call destructor once reaches the end of this block
}
cout << endl;

{ // construct a linked list that contains 10 elements, all values are 100
DoublyLinkedList<int> s(10, 100);

// construct a linked list by copying from s
DoublyLinkedList<int> s_copy(s);
if(!s.empty() && !s_copy.empty()){
cout << "the size of s is: " << s.size() << endl;
cout << "the size of s_copy is: " << s_copy.size() << endl;
}

// print the contents of s_copy
print(s_copy);

// call destructor twice
}
cout << endl;

{ // assignment
DoublyLinkedList<int> s(10, 100);
DoublyLinkedList<int> s_copy;
s_copy = s;

// print the contents of s_copy
print(s_copy);
}
cout << endl;

{ // push front
DoublyLinkedList<double> s;
for(int i = 5; i != 0; --i)
s.push_front(i);

cout << "after adding elements at front:\n";
print(s);

// push back
for(int i = 5; i != 0; --i)
s.push_back(i);

cout << "after adding elements at the end:\n";
print(s);

// insert at position 5
for(int i = 5; i != 0; --i)
s.insert(i, 0);

cout << "after inserting elements in-between:\n";
print(s);

// delete from the begining
for(int i = 5; i != 0; --i)
s.pop_front();

cout << "after deleting from the begining:\n";
print(s);

// delete from the end
for(int i = 5; i != 0; --i){
s.pop_back();
}

cout << "after deleting from the end:\n";
print(s);

// erase at in-between positions
for(int i = 3; i != 0; --i)
s.erase(3);

cout << "after deleting from other positions:\n";
print(s);

}
cout << endl;

{ // remove
DoublyLinkedList<int> s(5, 5);
for(int i = 5; i != 0; --i)
s.insert(5, i);

cout << "at present:\n";
print(s);

s.remove(5);
cout << "after removing all elements:\n";
print(s);
}
cout << endl;

{ // test reverse function
DoublyLinkedList<int> s;
for(int i = 0; i != 10; ++i)
s.push_back(i);

cout << "at present, s contains following elements:\n";
print(s);

s.reverse();
cout << "after reverse:\n";
print(s);

}

return 0;
}
+

Outputs:

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
default constructor
s is an empty linked list
the size of s1 is: 0
destructor

constructor with parameters
copy constructor
the size of s is: 10
the size of s_copy is: 10
print in order: 100 100 100 100 100 100 100 100 100 100
print in reverse: 100 100 100 100 100 100 100 100 100 100
destructor
destructor

constructor with parameters
default constructor
assignment operator
print in order: 100 100 100 100 100 100 100 100 100 100
print in reverse: 100 100 100 100 100 100 100 100 100 100
destructor
destructor

default constructor
after adding elements at front:
print in order: 1 2 3 4 5
print in reverse: 5 4 3 2 1
after adding elements at the end:
print in order: 1 2 3 4 5 5 4 3 2 1
print in reverse: 1 2 3 4 5 5 4 3 2 1
after inserting elements in-between:
print in order: 0 1 0 2 0 3 0 4 0 5 5 4 3 2 1
print in reverse: 1 2 3 4 5 5 0 4 0 3 0 2 0 1 0
after deleting from the begining:
print in order: 3 0 4 0 5 5 4 3 2 1
print in reverse: 1 2 3 4 5 5 0 4 0 3
after deleting from the end:
print in order: 3 0 4 0 5
print in reverse: 5 0 4 0 3
after deleting from other positions:
print in order: 3 0
print in reverse: 0 3
destructor

constructor with parameters
at present:
print in order: 5 5 5 5 1 2 3 4 5 5
print in reverse: 5 5 4 3 2 1 5 5 5 5
after removing all elements:
print in order: 1 2 3 4
print in reverse: 4 3 2 1
destructor

default constructor
at present, s contains following elements:
print in order: 0 1 2 3 4 5 6 7 8 9
print in reverse: 9 8 7 6 5 4 3 2 1 0
after reverse:
print in order: 9 8 7 6 5 4 3 2 1 0
print in reverse: 0 1 2 3 4 5 6 7 8 9
destructor

+ + +
+ + + + + + + + + + + +
+ + + +
+ + + +
+ +
+
+ + +
+ + + + + + + + + +
+ + + + + + + + + +
+
+ + + + +
+ + +
+ + + + +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/27/03/2018/C-Using-associative-containers/index.html b/27/03/2018/C-Using-associative-containers/index.html new file mode 100644 index 00000000..776fb2f8 --- /dev/null +++ b/27/03/2018/C-Using-associative-containers/index.html @@ -0,0 +1,850 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + C++ - Using associative containers (Part 1) | Liam's Blog + + + + + + + + + + + + + + + + + + +
+
+ + + +
+
+
+
+ + +
+ + + + + + + + +
+ + + +
+ + + + + + + +
+ + + +

C++ - Using associative containers (Part 1)

+ + + +
+ + + + + +
+ + + + + +

Introduction

Unlike sequential containers, associative containers store data into a sequence depending on the values of the elements themselves rather than the sequence where we inserted them. An associative container supports efficient lookup and retrieval by a key which is the part of each element. The most common kind of assiciative data structure, namely the associative array, is one that stores key-value pairs, associating a value with each each key. In C++, such an associative array is called a map.

+

The keys in fact plays a role as an index for the values, which are similar to the index of a vector. But the difference is that the keys can be int type or string* type or any other types that allows ordering.

+

Example 1 - counting words

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
int main()
{
string s;
map<string, int> counters; // store each word and an associated counter

// read the input, keeping track of each word and how often we see it
while (cin >> s)
{
++counters[s];
}
// write the words and associated counts
for (map<string, int>::const_iterator it = counters.begin(); it != counters.end(); ++it)
{
cout << it->first << "\t" << it->second << endl;
}
return 0;
}
+

Key points

+
    +
  1. when we define a map, we need to specify the types for both key and value, i.e. a key-value pair. In this case, the key has type of string while the value has type of int. Such a container can be described as a map from string to int.
  2. +
  3. from above, we know that each element in a map has type pair which is a data structure that holds two elements named first and second. For a map that has a key of type K, and a value of type V, the asociated pair type is pair. In this case, the elements of counters have type of pair.
  4. +
  5. the map counters is constructed with default initialization, leading to an empty map.
  6. +
  7. when we start to store words, the element pair is value-initialized, that is, the key is initialized as an empty string and the value is initialized as 0.
  8. +
  9. map supports operations via subscripting.

    +
    c[k] returns the **k**-associated value; if **k** doesn't exist, it adds **k** to the container, initialize and returns its associated value.
    +

    In this case, when a word appears for the first time, counters[s] returns the associated value which is initialized with value 0. Then we increment the value via ++counters[s].

    +
  10. +
  11. the for loop shows how to access elements in a map using iterators. The operations are similar to those on a vactor. When we deference a map iterator, we get a pair. Therefore, it->first extracts the value of the first element in the pair, that is, the key, while it->second gives the value of the second element in the pair, that is, the value associated with the key.
  12. +
+

We can also access the value via subscripting as described at key point 5. For example,

1
cout << counters[it->first];

+

has the same effect as

1
cout << it->second;

+

Example 2 - Generating a cross-reference table

This example extends above program such that the new program can generate a cross-reference table that indicates where each word occurs in the input. It requires:

+
    +
  1. read a line at a time for the purpose of obtaining the associated line number.
  2. +
  3. To separate each word from a line, we need a function like split described in Chpter 6. But rather than calling the function independently, we will pass it as an argument to the cross-reference function (denoted by xref).
  4. +
+
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
// find all the lines that refer to each word in the input
map<string, vector<int> > xref(istream & in,
vector<string> find_words(const string &) = split)
{
string line;
int line_number = 0;
map<string, vector<int> > ret;

// read the next line
while(getline(in, line))
{
++line_number;

// break the input line into words
vector<string> words = find_words(line);

// remember that each word occurs on the current line
for (vector<string>::const_iterator it = words.begin(); it != words.end(); ++it)
{
ret[*it].push_back(line_number);
}
return ret;
}
}
+

Let’s analyse this function:

+
    +
  1. what xref should return is a map<string, vector > as in fact we aims to build a map from the word to its line number. The key, representing each distinct word, has type string. The value, representing line numbers associated with each key, has type vector due to the fact that one word may appears in different lines.

    +
  2. +
  3. xref takes two arguments, one is an input stream object; another one is a function to extract words from a line. We are familar with how to define a function parameter. But what’ new here is to use = split in defining such a function parameter.This indicates that this parameter has a default argument, i.e. split. It means that if xref is called without passing this argument, it uses the default argument. If xref is called with passing a new argument, it uses the new argument. For example

    +
    1
    2
    xref(cin); // uses split to find words in the input stream
    xref(cin, find_urls); // uses the function named find_urls to find words
    +
  4. +
  5. the function begins with defining three varaibles: the first is a string named line to hold each line of input; the second is a integer that denotes the line number; the third is a map<string, vector > to hold each pair of word and line numbers.

    +
  6. +
  7. every iteration of the while statement, one line of input is read and broken into words. Then, each word is accessed via iterator and stored into the map together its line number. The core statement is:

    +
    1
    ret[*it].push_back(line_number);
    +

    As mentioned earlier, if *it, a word, doesn’t appear before, it will be stored and ret[it] returns an default initialized associated value, i.e. an empty vector. Then, we use push_back to append the line number to the end of the vector. If **it* has already appeared before, ret[it] returned the associated value (i.e. vector) and the new line number will be appended to the end of the vector.

    +
  8. +
+

A complete program

Now, I add #include directives and present all files including: mainfunction.cpp, xref.cpp, xref.h, split.cpp, split.h. This program uses the default split function.

+

mainfunction.cpp

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
int main()
{
// call xref using split by default
map<string, vector<int> > ret = xref(cin);

// write the results
for(map<string, vector<int> >::const_iterator it = ret.begin();
it != ret.end(); ++it)
{
// write the word
cout << it->first << " occurs on line(s): ";

// followed by one or more line_numbers
vector<int>::const_iterator line_it = it->second.begin();
cout << *line_it; // write the first line number

++line_it;
while (line_it != it->second.end())
{
cout << ", " << *line_it;
++line_it;
}
// write a new line to separate each word from the next
cout << endl;
}
return 0;
}

+

There is nothing new in above code. The program writes the first line number and the rest(if there exist) separately for the purpose of separating line numbers with a comma followed by a space.

+

xref.cpp

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
#include <iostream>	// to get the decalration of istream
#include <map> // to get the declaration of map
#include <vector> // to get the declaration of vector
#include <string> // to get the declaration of string
#include "split.h" // to get the declaration of split
#include "xref.h" // to get the declatation of xref

using std::map; using std::vector;
using std::string; using std::istream;

map<string, vector<int> > xref(istream &in,
vector<string> find_words(const string &))
{
string line;
int line_number = 0;
map<string, vector<int> > ret;

// read the next line
while(getline(in, line))
{
++line_number;

// break the input line into words
vector<string> words = find_words(line);

// remember that each word occurs on the current line
for(vector<string>::const_iterator it = words.begin(); it != words.end();
++it)
{
ret[*it].push_back(line_number);
}
}
return ret;
}

+

xref.h

1
2
3
4
5
6
7
8
9
10
11
12
13
#ifndef GUARD_XREF_H
#define GUARD_XREF_H

#include <map>
#include <vector>
#include <string>
#include <iostream>
#include "split.h"

std::map<std::string, std::vector<int> > xref(std::istream &,
std::vector<std::string> find_words(const std::string &) = split);

#endif /*GUARD_XREF_H */

+

It is worth noting that if xref is separated from the main function file, the default argument for the parameter (i.e. split in this case) should be put into its header file only. This is becasue that the default argument for a given parameter can only be specified once. (more discussion can be found on this page Default value of function parameter +).

+

split.cpp

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
#include <vector>	// to get the declarartion of vector
#include <string> // to get the declaration of string
#include <algorithm> // to get the declaration of find_if
#include "split.h" // to get the declaration of split

using std::vector; using std::string;
using std::find_if;

// true if the argument is whitespace, false otherwise
bool space(char c)
{
return isspace(c);
}

// false if the argument is whitesapce, true otherwise
bool not_space(char c)
{
return !isspace(c);
}

// function extracts words from a line of input
vector<string> split(const string &str)
{
typedef string::const_iterator iter;
vector<string> ret;

iter i = str.begin();
while (i != str.end()){
// ignore leading blanks
i = find_if(i, str.end(), not_space);

// find end of next word
iter j = find_if(i, str.end(), space);

// copy the characters in [i,j)
if(i != str.end())
ret.push_back(string(i, j));
i = j;
}
return ret;
}

+

split.h

1
2
3
4
5
6
7
8
9
10
11
#ifndef GUARD_SPLIT_H
#define GUARD_SPLIT_H

#include <vector>
#include <string>

bool space(char);
bool not_space(char);
std::vector<std::string> split(const std::string &);

#endif /* GUARD_SPLIT_H */

+

Test 1

From a simple test shown below, it can be seen that the program works as expected.

+
1
2
3
4
5
6
7
8
9
10
11
12
13
14
Inputs:

do you like me
no I do not like you

Outputs:

I occurs on line(s): 2
do occurs on line(s): 1 2
like occurs on line(s): 1 2
me occurs on line(s): 1
no occurs on line(s): 2
not occurs on line(s): 2
you occurs on line(s): 1 2
+

Test 2

Sometimes we may want to use another strategy to extract words, for example, when extracting URLs like we did in Finding URLs. I add revelent files first.

+

find_urls.cpp

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
// function that finds and returns an URL
#include "find_urls.h"
#include <vector>
#include <string>
#include "delimit.h"

using std::vector;
using std::string;

vector<string> find_urls(const string &s)
{
vector<string> ret;
typedef string::const_iterator iter;
iter b = s.begin(), e = s.end();

// look through the entire input
while (b != e)
{
// look for one or more letters followed by ://
b = url_beg(b, e);

// if we found it
if(b != e)
{
// get the rest of the URL
iter after = url_end(b, e);

// remember the URL
ret.push_back(string(b, after));

// advance b and check for more URLs on this line
b = after;
}
}
return ret;
}

+

find_urls.h

1
2
3
4
5
6
7
8
9
#ifndef GUARD_FINDINGURLS_H
#define GUARD_FINDINGURLS_H

#include <vector>
#include <string>

std::vector<std::string> find_urls(const std::string &);

#endif /* GUARD_FINDINGURLS_H */

+

dilimit.cpp

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
// contains three functions: not_url_char, url_beg, url_end
#include <string>
#include <algorithm>
#include "delimit.h"

using std::string; using std::find;
using std::find_if; using std::search;

// predicate on a char, check whether it is a char that can appear in a URL
bool not_url_char(char c)
{
// characters, in addition to alphanumerics, that can appear in a URL
static const string url_ch = "~;/?:@=&$-_.+!*'(),";

// see whether c can appear in a URL and return the negative
return !(isalnum(c) || find(url_ch.begin(), url_ch.end(), c) != url_ch.end());
}

// function that returns an iterator that refers to the first element of a URL
string::const_iterator url_beg(string::const_iterator b, string::const_iterator e)
{
static const string sep = "://";
typedef string::const_iterator iter;

// i marks where the separator was found
iter i = b;
while((i = search(i, e, sep.begin(), sep.end())) != e)
{
// make sure the seperator isn't at the begining of the proticol-name
if(i != b && i + sep.size() != e)
{
// beg marks the begining of the protocol-name
iter beg = i;
while(beg != b && isalpha(beg[-1]))
--beg;

// is there at least one appropriate character before and after the sep
if (beg != i && !not_url_char(i[sep.size()]))
return beg;
}
// the seperator we found wasn't part of a URL advance i past this separator
i += sep.size();
}
return e;
}

// function that returns an iterator that denotes the postion one past the last element
string::const_iterator url_end(string::const_iterator b, string::const_iterator e)
{
return find_if(b, e, not_url_char);
}

+

delimit.h

1
2
3
4
5
6
7
8
9
10
#ifndef GUARD_DELIMIT_H
#define GUARD_DELIMIT_H

#include <string>

bool not_url_char(char);
std::string::const_iterator url_beg(std::string::const_iterator, std::string::const_iterator);
std::string::const_iterator url_end(std::string::const_iterator, std::string::const_iterator);

#endif /* GUARD_DELIMIT_H */

+

Now, let’s call xref with passing argument find_urls:

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
int main()
{
// call xref using split by default
map<string, vector<int> > ret = xref(cin, find_urls);

// write the results
for(map<string, vector<int> >::const_iterator it = ret.begin();
it != ret.end(); ++it)
{
// write the word
cout << it->first << " occurs on line(s): ";

// followed by one or more line_numbers
vector<int>::const_iterator line_it = it->second.begin();
cout << *line_it; // write the first line number

++line_it;
while (line_it != it->second.end())
{
cout << ", " << *line_it;
++line_it;
}
// write a new line to separate each word from the next
cout << endl;
}
return 0;
}

+

Then, we type following inputs:

1
2
3
A typical URL could have the form https://en.wikipedia.org/wiki/URL, 
which indicates a protocol (http), a hostname (www.example.com),
and a file name (index.html). http://www.cplusplus.com/reference/algorithm/search/?kw=search

+

The program give results as expected:

1
2
http://www.cplusplus.com/reference/algorithm/search/?kw=search occurs on line(s): 3
https://en.wikipedia.org/wiki/URL, occurs on line(s): 1

+
+ + +
+ + + + + + + + + + + +
+ + + +
+ + + +
+ +
+
+ + +
+ + + + + + + + + +
+ + + + + + + + + +
+
+ + + + +
+ + +
+ + + + +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/28/03/2018/C-Using-associative-containers-Part-2/index.html b/28/03/2018/C-Using-associative-containers-Part-2/index.html new file mode 100644 index 00000000..9a70b4cb --- /dev/null +++ b/28/03/2018/C-Using-associative-containers-Part-2/index.html @@ -0,0 +1,833 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + C++ - Using associative containers (Part 2) | Liam's Blog + + + + + + + + + + + + + + + + + + +
+
+ + + +
+
+
+
+ + +
+ + + + + + + + +
+ + + +
+ + + + + + + +
+ + + +

C++ - Using associative containers (Part 2)

+ + + +
+ + + + + +
+ + + + + +

Example 3 - Generating sentences

This section introduces how to write a program that can randomly generate a sentence given certain grammar rules. For example, given following input

+
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
Categories        Rules

<noun> cat
<noun> dog
<noun> table
<noun-phrase> <noun>
<noun-phrase> <adjective> <noun-phrase>
<adjective> large
<adjective> brown
<adjective> absurd
<verb> jumps
<verb> sits
<location> on the stairs
<location> under the sky
<location> wherever it wants
<sentence> the <noun-phrase> <verb> <location>
+

The program might generate such a sentence:

1
the cat sits on the stairs

+

Some stylized facts can be observed:

+
    +
  1. There are two types of element in the inputs, one type contains a string enclosed by a pair of angle brackets and the other type contains one or more strings. We can always find direct or indirect mapping relations from each first type element to the second type elements.

    +
  2. +
  3. The first type represents categories of the components that constitute a sentence. One or more categories can construct compound categories. The second type represents the smallest building blocks of a sentence.

    +
  4. +
  5. The sentence structure is determined by the Rule associated with the category . A Rule may contain either categories or most basic building blocks or mixed.

    +
  6. +
+

Therefore, to construct a sentence, we need to

+
    +
  1. find , and then find the associated Rule.
  2. +
  3. start to find each element of a sentence follow the instructions of the Rule.
  4. +
  5. if the element is already the most basic building block, then we just store it into a vector for the final output. If the element is a category (i.e. the first type of element), we’ll find the associated Rule recursively, until that we find any of the most basic building blocks (i.e. the second type of element).
  6. +
+

Now, the soluction strategy can be logically divided into three parts:

+
    +
  • part 1: read and store the grammar including categories and rules into a map
  • +
  • part 2: applying above steps to find all needed elements for a sentence
  • +
  • part 3: write the sentence on the output device.
  • +
+

Read the grammar

Seen from above example, we can built a map from the first colunm to the second column, two elements of which in each row construct a key-value pair. The elements in the first column have data type string. But what’s the data type for the elements of the second column? We know that each rule may contains one or more strings and hence each rule can be stored into a vector:

1
vector<string> Rule;

+

Also, the mapping from the first column to the second column has one-to-many relations,for example, one maps to several rules such as cat, dog and table. Therefore, we can store each rule into a high-level vector:

1
vector<vector<string>> Rule_collection;

+

For the sake of brevity, we can use type alias in declaring such a map:

1
2
3
typedef vector<string> Rule;
typedef vector<Rule> Rule_collection;
typedef map<string, Rule_collection> Grammar;

+

Let’s see how to read the grammar into such a map:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
Grammar read_grammar(istream &in)
Grammar ret;
string line;

// read the input
while (getline(in, line))
{
// split the input into words
vector<string> entry = split(line);
if(!entry.empty())
// use the category to store the associated rule
ret[entry[0]].push_back(Rule(entry.begin() + 1, entry.end()));
}
return ret;
}

+

The function returns a map Grammar that contains all grammar rules to be applied to generate a sentence in the next step. There is only one argument, an input stream object, to be passed.

+

Inside of the function body, the first statement defines an empty map ret for holding the grammar, and the second statement defines an empty string for holding each row of input containing the key (i.e. one category) and the value (i.e. one rule). The next is a while loop to read the input repeatedly and read one line once. When the first line is read in, we need to extract all words contained in the line. Then, for the first word (i.e. the category represented by a string enclosed by a pair of angle brackets), we store it into the map as the key, while for the following words, we store them as the value that associates with the key. The core statement is

1
ret[entry[0]].push_back(Rule(entry.begin() + 1, entry.end()));

+

It seems complex but actually there is noting new in it. entry is the vector returned by the split function and hence contains all words extracted from the line of inputs. ret[entry[0]] stores the first word (if the word is new for the key), i.e. the category, and returns its associated value, i.e. Rule_collection. Then, we uses push_back to store the associated Rule which is a vector . The Rule is filled with values from the range [entry.begin() + 1, entry.end()) using

1
vector<string>(iterator_first, iterator_last);

+

The last statement is to return the Grammar ret.

+

Generate a sentence

Let’s consider the function that generate a sentence.

1
2
3
4
5
6
7
// generating the sentence
vector<string> gen_sentence(const Grammar &g)
{
vector<string> ret;
gen_aux(g, "<sentence>", ret);
return ret;
}

+

Apparently, we need a vector to hold the generated sentence. The only argument to be passed is the value returned by the function read_grammar.

+

The function that really deals with generating a sentence is named as gen_aux. It has three parameters, the first one is the grammar produced by read_grammar, the second one is a keyword ““ to be searched, the third one is a vector to hold the final results.

+

The function is defined below:

+
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
void gen_aux(const Grammar &g, const string &word, vector<string> &ret)
{
if(!bracketed(word)){
ret.push_back(word);
}
else
{
// locate the rule that corresponds to word
Grammar::const_iterator it = g.find(word);
if(it == g.end())
throw logic_error("empty rule");

// fetch the set of possible rules
const Rule_collection &c = it->second;

// from which we select one at random
const Rule &r = c[nrand(c.size())];

// recursively expand teh selected rule
for (Rule::const_iterator i = r.begin(); i != r.end(); ++i)
gen_aux(g, *i, ret);
}
}
+

The logic of this function is exactly as same as we described above. Ignoring the if-else statement first, the first step is to find the category :

1
Grammar::const_iterator it = g.find(word);

+

The member function find finds and returns an iterator that refers to the element with the key equivalent to the given k. If such element doesn’t exist in the map, the find function returns iterator g.end(). Therefore, if there exists such an iterator, getting the associated Rule_collection:

1
2
3
4
5
if(it == g.end())
throw logic_error("empty rule");

// fetch the set of possible rules
const Rule_collection &c = it->second;

+

By now, there exists two problems to be solved:

+
    +
  1. how to randomly pick one rule from Rule_collection
  2. +
  3. if one rule is picked, how to deal with the case that its elements are still categories.
  4. +
+

Let’s put question 1 last and solve question 2 first. Assuming we have picked one rule from the Rule_collection, we then scan each element of it and store the element if the element is the most basic building block. But if the element is still one of the categories, what we need to do is to find the lower level rule (i.e. its associated value). Therefore, we just repeat above processes until the element is not a category.
The if-else statement controls the recursive process while the condition that ceases the recursion is a predicate which returns true if the element is not a category:

1
2
3
4
bool bracketed(const string &s)
{
return s.size() > 1 && s[0] == '<' && s[s.size() - 1] == '>';
}

+

The function gen_sentence is recursively called in the for loop:

1
2
3
// recursively expand teh selected rule
for (Rule::const_iterator i = r.begin(); i != r.end(); ++i)
gen_aux(g, *i, ret);

+

Random drawing

Now the last piece is to randomly pick a rule from Rule_collection.
One possible solution is using rand() % n. rand() is an algorithm defined in standard header and gives a random integer in the range [0, RAND_MAX]. The upper bound is a large number defined in . rand() % n, computes the remainder when deviding the random number by n and hence gives a random integer in the range [0, n). If we set n = c.size(), we can then obtain an random index that yields a rule via c[rand() % c.size]. However, this solution is not a good choice due to(Koenig and Moo 2000):

+
    +
  1. rand() returns pseudo-random numbers. Many C++ implementations’ pseudo-random numbers give remainders that aren’t very random when the quotients are small integers.

    +
  2. +
  3. _if n is large and RAND_MAX is not evenly divisible by n,some remainder will appear more often than others_.

    +
  4. +
+

To circumvent these issues, we can divide the range of available numbers into buckets of exactly equal size:

1
const int bucket_size = RAND_MAX /n;

+

Then, bucket 0 has values from [0, bucket_size), bucket 1 has values from [bucket_size, bucket_size*2)…..

+

Then we can random draw a number and get the bucket number where the random number is located in via:

1
int r = rand() / bucket_size;

+

But there exist situations that the random number does not fall into any bucket due to the fact that RAND_MAX may be not evenly divisible by n. Therefore, we uses a do while statement to repeat above statement until it finds a random number that locates in the range of one of buckets.

+

The function nrand is shown below:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
// return a random integer in the range [0, n)
int nrand(int n)
{
if(n <= 0 || n > RAND_MAX)
throw domain_error("Argument to nrand is out of range");

const int bucket_size = RAND_MAX /n;
int r;

do r = rand() / bucket_size;
while(r >= n);

return r;
}

+

Noting that rand() uses a seed to generate the sequence, which should be initialized to some distinctive value using void srand(unsigned int seed). A common practice is to use distinctive runtime value, like the value returned by function time. For example

1
srand (time(NULL));

+

In addition, srand() is in fact has global effect on rand() and hence can be stated at the very begining of the main function.

+

A complete program

Now I files all functions and code discussed above and present the compete program below. The main function easy to understand and hence no further analysis here.

+

mainfunction.cpp

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 <cstdlib>		// to get the declaration of srand
#include <ctime> // to get the declaration of time
#include <vector> // to get the declaration of vector
#include <string> // to get the declaration of string
#include "read_grammar.h" // to get the declaration of read_grammar
#include "gen_sentence.h" // to get the declaration of gen_sentence

using std::cin; using std::cout;
using std::vector; using std::endl;
using std::string; using std::srand;
using std::time;

int main()
{
// initialize random number generator with distinctive runtime value
srand (time(NULL));

// generate the sentence
vector<string> sentence = gen_sentence(read_grammar(cin));

// write the first word, if any
vector<string>::const_iterator it = sentence.begin();
if(!sentence.empty())
{
cout << *it;
++it;
}

// write the rest of the words, each preceded by a space
while(it != sentence.end())
{
cout << " " << *it;
++it;
}
cout << endl;
return 0;
}

+

type_alias.h

1
2
3
4
5
6
7
8
9
10
11
12
#ifndef GUARD_TYPE_ALIAS_H
#define GUARD_TYPE_ALIAS_H

#include <vector>
#include <string>
#include <map>

typedef std::vector<std::string> Rule;
typedef std::vector<Rule> Rule_collection;
typedef std::map<std::string, Rule_collection> Grammar;

#endif /* GUARD_TYPE_ALIAS_H */

+

read_grammar.cpp

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
#include <iostream>		// to get the declaration of istream
#include <vector> // to get the declaration of vector
#include <string> // to get the declaration of string
#include "split.h" // to get the declaration of split
#include "type_alias.h" // to get the declaration of type alias
#include "read_grammar.h" // to get the declaration of read_grammar

using std::istream; using std::vector;
using std::string;

// read a grammar from a given input stream
Grammar read_grammar(istream &in)
{
Grammar ret;
string line;

// read the input
while (getline(in, line))
{
// split the input into words
vector<string> entry = split(line);
if(!entry.empty())
// use the category to store the associated rule
ret[entry[0]].push_back(Rule(entry.begin() + 1, entry.end()));
}
return ret;
}

+

read_grammar.h

1
2
3
4
5
6
7
8
9
#ifndef GUARD_READ_GRAMMAR_H
#define GUARD_READ_GRAMMAR_H

#include <iostream>
#include "type_alias.h"

Grammar read_grammar(std::istream &);

#endif /* GUARD_READ_GRAMMAR_H */

+

gen_sentence.cpp

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 <vector>		// to get the declaration of vector
#include <string> // to get the declaration of string
#include <stdexcept> // to get the declaration of domain_error, logic_error
#include <cstdlib> // to get the declaration of rand
#include "type_alias.h" // to get the declaration of type_alias
#include "gen_sentence.h" // to get the declatation of gen_sentence

using std::vector; using std::string;
using std::domain_error; using std::logic_error;
using std::rand;

// generating the sentence
vector<string> gen_sentence(const Grammar &g)
{
vector<string> ret;
gen_aux(g, "<sentence>", ret);
return ret;
}

// auxillary gen function
void gen_aux(const Grammar &g, const string &word, vector<string> &ret)
{
if(!bracketed(word)){
ret.push_back(word);
}
else
{
// locate the rule that corresponds to word
Grammar::const_iterator it = g.find(word);
if(it == g.end())
throw logic_error("empty rule");

// fetch the set of possible rules
const Rule_collection &c = it->second;

// from which we select one at random
const Rule &r = c[nrand(c.size())];

// recursively expand teh selected rule
for (Rule::const_iterator i = r.begin(); i != r.end(); ++i)
gen_aux(g, *i, ret);
}
}

// the predicate
bool bracketed(const string &s)
{
return s.size() > 1 && s[0] == '<' && s[s.size() - 1] == '>';
}

// return a random integer in the range [0, n)
int nrand(int n)
{
if(n <= 0 || n > RAND_MAX)
throw domain_error("Argument to nrand is out of range");

const int bucket_size = RAND_MAX /n;
int r;

do r = rand() / bucket_size;
while(r >= n);

return r;
}

+

split.cpp

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
#include <vector>	// to get the declarartion of vector
#include <string> // to get the declaration of string
#include <algorithm> // to get the declaration of find_if
#include "split.h" // to get the declaration of split

using std::vector; using std::string;
using std::find_if;

// true if the argument is whitespace, false otherwise
bool space(char c)
{
return isspace(c);
}

// false if the argument is whitesapce, true otherwise
bool not_space(char c)
{
return !isspace(c);
}

// function extracts words from a line of input
vector<string> split(const string &str)
{
typedef string::const_iterator iter;
vector<string> ret;

iter i = str.begin();
while (i != str.end()){
// ignore leading blanks
i = find_if(i, str.end(), not_space);

// find end of next word
iter j = find_if(i, str.end(), space);

// copy the characters in [i,j)
if(i != str.end())
ret.push_back(string(i, j));
i = j;
}
return ret;
}

+

split.h

1
2
3
4
5
6
7
8
9
10
11
#ifndef GUARD_SPLIT_H
#define GUARD_SPLIT_H

#include <vector>
#include <string>

bool space(char);
bool not_space(char);
std::vector<std::string> split(const std::string &);

#endif /* GUARD_SPLIT_H */

+

Test performance

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
Inputs:

<noun> cat
<noun> dog
<noun> table
<noun-phrase> <noun>
<noun-phrase> <adjective> <noun-phrase>
<adjective> large
<adjective> brown
<adjective> absurd
<verb> jumps
<verb> sits
<location> on the stairs
<location> under the sky
<location> wherever it wants
<sentence> the <noun-phrase> <verb> <location>

Outputs:

the large brown dog sits wherever it wants
+

The program works as expected.

+
+ + +
+ + + + + + + + + + + +
+ + + +
+ + + +
+ +
+
+ + +
+ + + + + + + + + +
+ + + + + + + + + +
+
+ + + + +
+ + +
+ + + + +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/28/05/2018/C-Implementations-Doubly-Circular-Linked-List/index.html b/28/05/2018/C-Implementations-Doubly-Circular-Linked-List/index.html new file mode 100644 index 00000000..704e234e --- /dev/null +++ b/28/05/2018/C-Implementations-Doubly-Circular-Linked-List/index.html @@ -0,0 +1,798 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + C++ Implementations - Doubly Circular Linked List | Liam's Blog + + + + + + + + + + + + + + + + + + +
+
+ + + +
+
+
+
+ + +
+ + + + + + + + +
+ + + +
+ + + + + + + +
+ + + +

C++ Implementations - Doubly Circular Linked List

+ + + +
+ + + + + +
+ + + + + +

Header file

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
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
#ifndef CIRCULARLINKEDLIST_H_
#define CIRCULARLINKEDLIST_H_

#include <cstddef>
#include <iostream>

template <typename T>
class CircularLinkedList{
struct Node;
public:
typedef std::size_t size_type;
typedef T value_type;

// default constructor
CircularLinkedList(): sentinel(create_sentinel()), count(0) {
std::cout << "default constructor" << std::endl;
}
CircularLinkedList(size_type, const T&val = T());
CircularLinkedList(const CircularLinkedList&);
CircularLinkedList& operator= (const CircularLinkedList&);
~CircularLinkedList();

bool empty() const { return sentinel->next == sentinel; }
size_type size() const { return count; }
Node* begin() { return sentinel->next; }
const Node* begin() const { return sentinel->next; }
Node* end() { return sentinel; }
const Node* end() const { return sentinel; }

void clear();
void push_front(const T&);
void push_back(const T&);
void insert(size_type, const T&);
void pop_front();
void pop_back();
void erase(size_type);
void reverse();
void remove(const T&);

private:
struct Node{
T data;
Node* prev;
Node* next;
};

Node* sentinel;
size_type count;

Node* create_sentinel(){
Node* nil = new Node;
nil->prev = nil;
nil->next = nil;
return nil;
}

Node* create(const T&val){
Node* new_node = new Node;
new_node->data = val;
new_node->prev = sentinel;
new_node->next = sentinel;
return new_node;
}

};

// O(n)
template <typename T>
CircularLinkedList<T>::CircularLinkedList(size_type n, const T& val)
: sentinel(create_sentinel()), count(0){
std::cout << "construtor with parameters" << std::endl;
Node* current = sentinel;
while(count != n){
current->next = create(val);
current->next->prev = current;
current = current->next;
++count;
}
sentinel->prev = current;
}

// O(n)
template <typename T>
CircularLinkedList<T>::CircularLinkedList(const CircularLinkedList& l)
: sentinel(create_sentinel()), count(0){
std::cout << "copy constructor" << std::endl;
Node* current = sentinel;
const Node* temp = l.begin();
while(count != l.size()){
current->next = create(temp->data);
current->next->prev = current;
current = current->next;
temp = temp->next;
++count;
}
sentinel->prev = current;

}

// O(n)
template <typename T>
CircularLinkedList<T>& CircularLinkedList<T>::operator=
(const CircularLinkedList& l){
std::cout << "assignment operator" << std::endl;
if(&l != this){
clear();
if(!l.empty()){
Node* current = sentinel;
const Node* temp = l.begin();
while(count != l.size()){
current->next = create(temp->data);
current->next->prev = current;
current = current->next;
temp = temp->next;
++count;
}
sentinel->prev = current;
}
}
return *this;
}

// O(n)
template <typename T>
void CircularLinkedList<T>::clear(){
while(sentinel->next != sentinel){
Node* temp = sentinel->next;
sentinel->next = temp->next;
sentinel->next->prev = sentinel;
delete temp;
--count;
}
}

// O(n)
template <typename T>
CircularLinkedList<T>::~CircularLinkedList() {
clear();
delete sentinel;
sentinel = nullptr;
std::cout << "destructor" << std::endl;
}

// O(1)
template <typename T>
void CircularLinkedList<T>::push_front(const T& val){
Node* new_node = create(val);
new_node->next = sentinel->next;
new_node->next->prev = new_node;
sentinel->next = new_node;
new_node->prev = sentinel;
++count;
}

// O(n)
template <typename T>
void CircularLinkedList<T>::push_back(const T& val){
Node* current = sentinel;
while(current->next != sentinel)
current = current->next;

current->next = create(val);
current->next->prev = current;
sentinel->prev = current->next;
++count;
}

// O(n)
template <typename T>
void CircularLinkedList<T>::insert(size_type position, const T& val){
if(position < 1 || position > size())
throw std::domain_error("Invalid Position");
Node* current = sentinel;
Node* new_node = create(val);
for(size_type i = 0; i != position; ++i)
current = current->next;

current->prev->next = new_node;
new_node->prev = current->prev;
new_node->next = current;
current->prev = new_node;
++count;
}

// O(1)
template <typename T>
void CircularLinkedList<T>::pop_front(){
erase(1);
}

// O(n)
template <typename T>
void CircularLinkedList<T>::pop_back(){
erase(size());
}

// O(n)
template <typename T>
void CircularLinkedList<T>::erase(size_type position){
if(position < 1 || position > size())
throw std::domain_error("Invalid Position");

Node* current = sentinel;
for(size_type i = 0; i != position; ++i)
current = current->next;

current->next->prev = current->prev;
current->prev->next = current->next;
delete current;
--count;
}

// O(n)
template <class T>
void CircularLinkedList<T>::remove(const T& val){
Node* current = sentinel;
while(current->next != sentinel){
if(current->next->data == val){
Node* temp = current->next;
current->next = temp->next;
temp->next->prev = current;
delete temp;
--count;
}else current = current->next;
}
}

// O(n)
template <class T>
void CircularLinkedList<T>::reverse(){
if (size() < 2) return;
sentinel->prev = sentinel->next;
Node* current = sentinel;
Node* NEXT;
while(current->next != sentinel){
NEXT = current->next;
current->next = current->prev;
current->prev = NEXT;
current = NEXT;
}
current->next = current->prev;
current->prev = sentinel;
sentinel->next = current;
}

#endif /* CIRCULARLINKEDLIST_H_ */
+

Test program and results

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
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
/*
* this program tests all operations that provided by the
* CircularLinkedList<T> class
* created by Liam on: 28 Apr 2018
*/

#include <iostream>
#include "CircularLinkedList.h"

using std::endl; using std::cout;

// print and reverse print
template <class T>
void print(T& l){
cout << "print in order: ";
for(auto i = l.begin(); i != l.end(); i = i->next)
cout << i->data << " ";
cout << endl;

cout << "print in reverse: ";

for(auto i = (l.end())->prev; i != l.end(); i = i->prev)
cout << i->data << " ";
cout << endl;
}

int main(){

{ // construct an empty linked list
CircularLinkedList<int> s;
if(s.empty())
cout << "s is an empty linked list\n"
"the size of s1 is: " << s.size() << endl;

// call destructor once reaches the end of this block
}
cout << endl;

{ // construct a linked list that contains 10 elements, all values are 100
CircularLinkedList<int> s(10, 100);

// construct a linked list by copying from s
CircularLinkedList<int> s_copy(s);
if(!s.empty() && !s_copy.empty()){
cout << "the size of s is: " << s.size() << endl;
cout << "the size of s_copy is: " << s_copy.size() << endl;
}

// print the contents of s_copy
print(s_copy);
// call destructor twice
}
cout << endl;

{ // assignment
CircularLinkedList<int> s(10, 100);
CircularLinkedList<int> s_copy;
s_copy = s;

// print the contents of s_copy
print(s_copy);
}
cout << endl;

{ // push front
CircularLinkedList<double> s;
for(int i = 5; i != 0; --i)
s.push_front(i);

cout << "after adding elements at front:\n";
print(s);

// push back
for(int i = 5; i != 0; --i)
s.push_back(i);

cout << "after adding elements at the end:\n";
print(s);

// insert at position 5
for(int i = 5; i != 0; --i)
s.insert(i, 0);

cout << "after inserting elements in-between:\n";
print(s);

// delete from the begining
for(int i = 5; i != 0; --i)
s.pop_front();

cout << "after deleting from the begining:\n";
print(s);

// delete from the end
for(int i = 5; i != 0; --i){
s.pop_back();
}

cout << "after deleting from the end:\n";
print(s);

// erase at in-between positions
for(int i = 3; i != 0; --i)
s.erase(3);

cout << "after deleting from other positions:\n";
print(s);

}
cout << endl;

{ // remove
CircularLinkedList<int> s(5, 5);
for(int i = 5; i != 0; --i)
s.insert(5, i);

cout << "at present:\n";
print(s);

s.remove(5);
cout << "after removing all elements:\n";
print(s);
}
cout << endl;

{ // test reverse function
CircularLinkedList<int> s;
for(int i = 0; i != 10; ++i)
s.push_back(i);

cout << "at present, s contains following elements:\n";
print(s);

s.reverse();
cout << "after reverse:\n";
print(s);

}

return 0;
}
+

Outputs:

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
default constructor
s is an empty linked list
the size of s1 is: 0
destructor

construtor with parameters
copy constructor
the size of s is: 10
the size of s_copy is: 10
print in order: 100 100 100 100 100 100 100 100 100 100
print in reverse: 100 100 100 100 100 100 100 100 100 100
destructor
destructor

construtor with parameters
default constructor
assignment operator
print in order: 100 100 100 100 100 100 100 100 100 100
print in reverse: 100 100 100 100 100 100 100 100 100 100
destructor
destructor

default constructor
after adding elements at front:
print in order: 1 2 3 4 5
print in reverse: 5 4 3 2 1
after adding elements at the end:
print in order: 1 2 3 4 5 5 4 3 2 1
print in reverse: 1 2 3 4 5 5 4 3 2 1
after inserting elements in-between:
print in order: 0 1 0 2 0 3 0 4 0 5 5 4 3 2 1
print in reverse: 1 2 3 4 5 5 0 4 0 3 0 2 0 1 0
after deleting from the begining:
print in order: 3 0 4 0 5 5 4 3 2 1
print in reverse: 1 2 3 4 5 5 0 4 0 3
after deleting from the end:
print in order: 3 0 4 0 5
print in reverse: 5 0 4 0 3
after deleting from other positions:
print in order: 3 0
print in reverse: 0 3
destructor

construtor with parameters
at present:
print in order: 5 5 5 5 1 2 3 4 5 5
print in reverse: 5 5 4 3 2 1 5 5 5 5
after removing all elements:
print in order: 1 2 3 4
print in reverse: 4 3 2 1
destructor

default constructor
at present, s contains following elements:
print in order: 0 1 2 3 4 5 6 7 8 9
print in reverse: 9 8 7 6 5 4 3 2 1 0
after reverse:
print in order: 9 8 7 6 5 4 3 2 1 0
print in reverse: 0 1 2 3 4 5 6 7 8 9
destructor

+ + +
+ + + + + + + + + + + +
+ + + +
+ + + +
+ +
+
+ + +
+ + + + + + + + + +
+ + + + + + + + + +
+
+ + + + +
+ + +
+ + + + +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/28/05/2018/C-notes-for-financial-mathematics/index.html b/28/05/2018/C-notes-for-financial-mathematics/index.html new file mode 100644 index 00000000..40a7355d --- /dev/null +++ b/28/05/2018/C-notes-for-financial-mathematics/index.html @@ -0,0 +1,776 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + C++ notes for financial mathematics | Liam's Blog + + + + + + + + + + + + + + + + + + +
+
+ + + +
+
+
+
+ + +
+ + + + + + + + +
+ + + +
+ + + + + + + +
+ + + +

C++ notes for financial mathematics

+ + + +
+ + + + + +
+ + + + + +
    +
  1. 1
    2
    3
    Inf: positive infinity
    -Inf: negative infinity
    NaN: not a number
    +
  2. +
  3. Noting that two decimals can only be approximately equal.

    +
  4. +
  5. Casting an int to a float is risky since the float data type uses binary scientific notation with only a handful of significant figures. So an int cannot be represented precisely using a float.
  6. +
  7. +
+ + +
+ + + + + + + + + + + +
+ + + +
+ + + +
+ +
+
+ + +
+ + + + + + + + + +
+ + + + + + + + + +
+
+ + + + +
+ + +
+ + + + +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/30/03/2018/Accelerated-C-Solutions-to-Exercises-Chapter-7/index.html b/30/03/2018/Accelerated-C-Solutions-to-Exercises-Chapter-7/index.html new file mode 100644 index 00000000..32f1326d --- /dev/null +++ b/30/03/2018/Accelerated-C-Solutions-to-Exercises-Chapter-7/index.html @@ -0,0 +1,855 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Accelerated C++ Solutions to Exercises(Chapter 7 Part 1) | Liam's Blog + + + + + + + + + + + + + + + + + + +
+
+ + + +
+
+
+
+ + +
+ + + + + + + + +
+ + + +
+ + + + + + + +
+ + + +

Accelerated C++ Solutions to Exercises(Chapter 7 Part 1)

+ + + +
+ + + + + +
+ + + + + +

Exercise 7-0

Compile, execute, and test the programs in this chapter.

+

Solution & Results

Please find the programs and detailed analysis in Example 1, 2 and Example 3.

+

Exercise 7-1

Extend the program from §7.2/124 to produce its output sorted by occurrence count.That is, the output should group all the words that occur once, followed by those that occur twice, and so on.

+

Solution & Results

The key to the solution is building a map from occurrence numbers to corresponding words. The original program builds a map from each distinct word to its occurrence numbers. Therefore, we can simply inverse the original map. But noting there may be more than one words have the same occurrence numbers. The revised program is shown below:

+
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
#include <iostream>		// to get the declaration of cin, cout, endl
#include <vector> // to get the declaration of vector
#include <string> // to get the declaration of string
#include <map> // to get the declaration of map

using std::cin; using std::cout;
using std::endl; using std::string;
using std::map; using std::vector;

int main()
{
// store each word and an associated counter
string s;
map<string, int> counters;

// read the input, keeping track of each word and how often we see it
while (cin >> s)
{
++counters[s];
}

// sort words stored in counters according to the occurence count
map<int, vector<string> > sorted_counters;
for (map<string, int>::const_iterator it = counters.begin(); it != counters.end(); ++it)
{
sorted_counters[it->second].push_back(it->first);
}

// write the words and associated counts
cout << "Words and their associated counts:" << endl;
for (map<string, int>::const_iterator it = counters.begin(); it != counters.end(); ++it)
{
cout << it->first << "\t" << it->second << endl;
}

// write a blank line to separate the outputs of two maps
cout << endl;

// write occurrence count followed by the corresponding words
cout << "Occurrence count and the corresponding words:" << endl;
for (map<int, vector<string> >::const_iterator it = sorted_counters.begin(); it != sorted_counters.end(); ++it)
{
cout << it->first;
for (vector<string>::const_iterator i = (it->second).begin(); i != (it->second).end(); ++i)
cout << ' ' << *i;
cout << endl;
}

return 0;
}
+

It can be observed that the values of the original map are stored into the new map as keys while the keys are stored as values in the new map. In addition, we specify vector to hold more words that have same occurrence numbers. Now let’s type some words and check the results.

+
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
Inputs:

a dog and a cat
cat is good dog is bad
human is ugly

Outputs:

Words and their associated counts:
a 2
and 1
bad 1
cat 2
dog 2
good 1
human 1
is 3
ugly 1

Occurrence count and the corresponding words:
1 and bad good human ugly
2 a cat dog
3 is
+

Yeah, it correctly sorts the words according to their occurrence numbers.

+
+

Exercise 7-2

Extend the program in §4.2.3/64 to assign letter grades by ranges:

1
2
3
4
5
A   90-100
B 80-89.99...
C 70-79.99...
D 60-69.99...
F < 60

+

The output should list how many students fall into each category.

+

Solution & Results

The key to solution is building a map from the letter grades, A, B, C, D, F, to the number of students who have the corresponding final grades. Therefore, the map can be defined as:

1
map<string, int> grades_count;

+

The strategy can be divided into four steps:

+
    +
  1. read students’ information
  2. +
  3. calculate final grade for each student
  4. +
  5. check the range of each final grade and get a letter grade (i.e. the key)
  6. +
  7. increment the value associated with the key returned in step 3
  8. +
+

Step 1 and step 2 are familar.

+

Step 3 needs a function on the final grade. I uses a simple if-else statement to complete it:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
string letter_grade(double &grade)
{
if(grade < 0 || grade > 100)
throw domain_error("grade is outside of[0, 100]");
else if (grade >= 90)
return "A";
else if(grade >= 80 && grade < 90)
return "B";
else if(grade >= 70 && grade < 80)
return "C";
else if(grade >= 60 && grade < 70)
return "D";
else
return "F";
}

+

Step 4 is accomplished by the statement:

1
++grades_count[letter_grade(final_grade)];

+

letter_grade(final_grade) returns a letter grade based on the function above. Then, the map grades_count stores (if it is new ) the letter as the key and returns the associated value. Finally, applies ++ operator to increment the associated value, showing the counting process.

+

The complete program

The complete program is displayed below including files: mainfunction.cpp, grade.cpp, grade.h, Student_info.cpp and Student_info.h.

+

mainfunction.cpp

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
#include <iostream>		// to get the declaration of cin, cout, endl
#include <stdexcept> // to get the declaration of domain_error
#include <string> // to get the declaration of string
#include <vector> // to get the declaration of vector
#include <map> // to get the declaration of map
#include "Student_info.h" // to get the declaration of Student_info
#include "grade.h" // to get the declaration of grade

using std::cin;
using std::cout; using std::string;
using std::endl; using std::vector;
using std::domain_error; using std::map;

string letter_grade(double &grade)
{
if(grade < 0 || grade > 100)
throw domain_error("grade is outside of[0, 100]");
else if (grade >= 90)
return "A";
else if(grade >= 80 && grade < 90)
return "B";
else if(grade >= 70 && grade < 80)
return "C";
else if(grade >= 60 && grade < 70)
return "D";
else
return "F";
}

int main()
{
// read and store all the records
vector<Student_info> students;
Student_info record;
while(read(cin, record))
{
students.push_back(record);
}

map<string, int> grades_count;
for (vector<Student_info>::size_type i = 0; i != students.size(); ++i)
{
// compute each final grade letter grades and counting the letter grades
try{
double final_grade = grade(students[i]);
++grades_count[letter_grade(final_grade)];
} catch(domain_error e){
cout << e.what();
}
}

for(map<string, int>::const_iterator it = grades_count.begin();
it != grades_count.end(); ++it)
cout << it->first << '\t' << it->second << endl;

return 0;
}

+

grade.cpp

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 <algorithm>
#include <stdexcept>
#include <vector>
#include "grade.h"
#include "Student_info.h"

using std::domain_error; using std::istream;
using std::vector; using std::sort;

// grade function 1
double grade(const Student_info &s)
{
return grade(s.midterm, s.final, s.homework);
}

// grade function 2
double grade(double midterm, double final, const vector<double> &hw)
{
if (hw.size() == 0)
throw domain_error("student has done no homework");
return grade(midterm, final, median(hw));
}

// grade function 3
double grade(double midterm, double final, double homework)
{
return 0.2*midterm + 0.4*final + 0.4*homework;
}

// compute the median of vector<double>
double median(vector<double> vec)
{
// get the size of the vector
typedef vector<double>::size_type vec_size;
vec_size size = vec.size();

// check whether the empty is empty
if (size == 0)
throw domain_error("median of an empty vector");

// sort the grades
sort(vec.begin(), vec.end());

// compute the median homework grade
vec_size mid = size/2;
return size % 2 == 0 ? (vec[mid] + vec[mid - 1])/2 : vec[mid];
}

+

grade.h

1
2
3
4
5
6
7
8
9
10
11
12
#ifndef GUARD_grade_h
#define GUARD_grade_h

// grade.h
#include<vector>
#include "Student_info.h"

double grade(const Student_info &);
double grade(double, double, const std::vector<double> &);
double grade(double, double, double);
double median(std::vector<double>);
#endif

+

Student.info

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
#include "Student_info.h"
using std::vector; using std::istream;

bool compare(const Student_info &x, const Student_info &y)
{
return x.name < y.name;
}

istream & read(istream &is, Student_info &s)
{
// reads and store the student's name, midterm and final exam grades
is >> s.name >> s.midterm >> s.final;

// reads and store all homework grades
read_hw(is, s.homework);
return is;
}

istream & read_hw(istream &in, vector<double> &hw)
{
if (in){
//get rid of previous contents
hw.clear();

// read homework grades
double x;
while(in >> x)
hw.push_back(x);

// clear the stream so that input will work for the next student
in.clear();
}
return in;
}

+

Student_info.h

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
#ifndef GUARD_Student_info
#define GUARD_Student_info

#include<iostream>
#include<string>
#include<vector>

struct Student_info{
std::string name;
double midterm, final;
std::vector<double> homework;
};

bool compare(const Student_info &, const Student_info &);
std::istream & read(std::istream &, Student_info &);
std::istream & read_hw(std::istream &, std::vector<double> &);
#endif

+

Performance Test

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
Inputs:

Phqgh 24.7879 58.6263 64.0505
Nlfdx 95.4242 27.3636 91.0404
Cxggb 16.1818 95.4747 26.7172
Uxwfn 35.9495 3.11111 22.3333
Tkjpr 68.4747 44.6263 57.3737
Pnrvy 16.3535 90.4242 88.0606
Syycq 5.90909 29.7071 50.0606
Ffmzn 84.5455 56.404 66.7677
Vwsre 23.3737 38.1818 82.2929
Fxtls 4.30303 77.0606 73.8687
Dpooe 29.7778 73.9798 12.8687
Ejuvp 55.7475 31.5253 50.5051
Poeyl 91.0707 37.5758 87.5354
Jvrvi 21.8889 22.4646 6.30303
Hwqnq 55.101 59.2424 37.4848
Jjloo 91.3636 74.202 96.2121
Whmsn 34.5354 99.1818 38
Sfzkv 48.8384 7.21212 10.1717
Lyjyh 51 49.1919 56.9899
Nkkuf 89.0202 95.8586 93.4343

Outputs:

A 1
B 1
C 1
D 5
F 12
+
+

Exercise 7-3

The cross-reference program from §7.3/126 could be improved: As it stands, if a word occurs more than once on the same input line, the program will report that line multiple times. Change the code so that it detects multiple occurrences of the same line number and inserts the line number only once.

+

Solution & Results

In the original program, we built a map from each distinct word to the line numbers in which the word appears.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
map<string, vector<int> > xref(istream &in,
vector<string> find_words(const string &))
{
string line;
int line_number = 0;
map<string, vector<int> > ret;

// read the next line
while(getline(in, line))
{
++line_number;

// break the input line into words
vector<string> words = find_words(line);

// remember that each word occurs on the current line
for(vector<string>::const_iterator it = words.begin(); it != words.end();
++it)
{
ret[*it].push_back(line_number);
}
}
return ret;
}

+

However, the line numbers for each word may repeatedly recorded. To avoid this problem, one solution is to check whether the line number has already been recorded. If the line number has been recorded, we ignore it, otherwise, we store it into the vector. We do not need to check all elements in the vector instead we only check the last stored line number. This is because that if a line number is repeatedly recorded, two elements (i.e. same line numbers) must be adjacent. Therefore, I add if statement as follows:

+
1
2
if(ret[*it].empty() || *(ret[*it].end() - 1) != line_number)
ret[*it].push_back(line_number);
+

I’ll give the complete program as well as test results in next exercise.

+

Exercise 7-4

The output produced by the cross-reference program will be ungainly if the input file is large. Rewrite the program to break up the output if the lines get too long.

+

Solution & Results

The key to the solution is managing the length of each line of outputs. Theoretically, the strategy can be divided into three steps:

+
    +
  1. convert all line numbers (except the first one) associated with a word into strings.
  2. +
  3. count the number of characters that have been written.
  4. +
  5. when the predetermined length of a line is reached, write a new line.
  6. +
+

The first two steps seems tedious to us. Fortunately, we can use stringstream objects to accomplish these easily.

+

stringstream is a stream class defined standard library. It provides IO facilities that operate on strings. For example, ostringstream object uses a string buffer to hold a sequences of characters for printing all outputs together. In addition, the sequence of characters can be accessed directly as a string object, using member function str. In this case, I define such an object os to hold all line numbers as well as additional spaces between two line numbers.

1
os << ", " << *line_it;

+

line_it is an iterator that refers to one of line numbers stored in a vector.

+

Once all line numbers have been stored into os, we can access all contents to be written as a string object.

1
string line_numbers = os.str();

+

Now, the next is to print all line numbers in one or more lines depending on the predetermined length of one line.

1
2
3
4
5
6
for(string::size_type i = 0; i != line_numbers.size(); ++i)
{
cout << line_numbers[i];
if((i + 1) % line_length == 0)
cout << endl;
}

+

When i+1th element (i.e. line_number[i])
is written, the if statement check that if the number of characters that have been written equals to the length (or multiple lengths) of a line, a newline character is inserted into the output stream.

+

A complete program

I integerated the changes described in exercise 7-3 and this exercise into a new program including files: mainfunction.cpp, xref.cpp, xref.h, split.cpp, split.h.

+

mainfunction.cpp

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
#include <map>		// to get the declaration of map
#include <string> // to get the declaration of string
#include <vector> // to get the declaration of vector
#include <iostream> // to get the declaration of cin, cout, endl;
#include <sstream> // to get the declaration of ostringstream
#include <cctype> // to get the declaration of isspace
#include "split.h" // to get the declaration of function split
#include "xref.h" // to get the declaration of xref

using std::map; using std::cout;
using std::cin; using std::endl;
using std::string; using std::vector;
using std:: ostringstream; using std::isspace;

int main()
{
// call xret using split by default
map<string, vector<int> > ret = xref(cin);

// set the length for each line of outputs
string::size_type line_length = 20;

// write the result
for(map<string, vector<int> >::const_iterator it = ret.begin(); it != ret.end(); ++it)
{
// write the word
cout << it->first << " occurs on line(s):" << endl;

// followed by one or more line numbers
vector<int>::const_iterator line_it = it->second.begin();
cout << *line_it; // write the first line number

// scan the rest line numbers
++line_it;
ostringstream os;
while(line_it != it->second.end())
{
// store line numbers into ostringstream object
os << ", " << *line_it;
++line_it;
}

// get the contents from line_numbers
string line_numbers = os.str();

// write each line of outputs
for(string::size_type i = 0; i != line_numbers.size(); ++i)
{
cout << line_numbers[i];
if((i + 1) % line_length == 0)
cout << endl;
}
// write a blank line to separate each words
cout << endl;
}
return 0;
}

+

xref.cpp

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
#include <iostream>	// to get the decalration of istream
#include <map> // to get the declaration of map
#include <vector> // to get the declaration of vector
#include <string> // to get the declaration of string
#include "split.h" // to get the declaration of split
#include "xref.h" // to get the declatation of xref

using std::map; using std::vector;
using std::string; using std::istream;

map<string, vector<int> > xref(istream &in,
vector<string> find_words(const string &))
{
string line;
int line_number = 0;
map<string, vector<int> > ret;

// read the next line
while(getline(in, line))
{
++line_number;

// break the input line into words
vector<string> words = find_words(line);

// remember that each word occurs on the current line
for(vector<string>::const_iterator it = words.begin(); it != words.end();
++it)
{
if(ret[*it].empty() || *(ret[*it].end() - 1) != line_number)
ret[*it].push_back(line_number);
}
}
return ret;
}

+

xref.h

1
2
3
4
5
6
7
8
9
10
11
12
#ifndef GUARD_XREF_H
#define GUARD_XREF_H

#include <map>
#include <vector>
#include <string>
#include <iostream>
#include "split.h"

std::map<std::string, std::vector<int> > xref(std::istream &,
std::vector<std::string> find_words(const std::string &) = split);
#endif /*GUARD_XREF_H */

+

split.cpp

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
#include <vector>		// to get the declarartion of vector
#include <string> // to get the declaration of string
#include <algorithm> // to get the declaration of find_if
#include "split.h" // to get the declaration of split

using std::vector; using std::string;
using std::find_if;

// true if the argument is whitespace, false otherwise
bool space(char c)
{
return isspace(c);
}

// false if the argument is whitesapce, true otherwise
bool not_space(char c)
{
return !isspace(c);
}

// function extracts words from a line of input
vector<string> split(const string &str)
{
typedef string::const_iterator iter;
vector<string> ret;

iter i = str.begin();
while (i != str.end()){
// ignore leading blanks
i = find_if(i, str.end(), not_space);

// find end of next word
iter j = find_if(i, str.end(), space);

// copy the characters in [i,j)
if(i != str.end())
ret.push_back(string(i, j));
i = j;
}
return ret;
}

+

split.h

1
2
3
4
5
6
7
8
9
10
11
#ifndef GUARD_SPLIT_H
#define GUARD_SPLIT_H

#include <vector>
#include <string>

bool space(char);
bool not_space(char);
std::vector<std::string> split(const std::string &);

#endif /* GUARD_SPLIT_H */

+

Performance test

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
Inputs:

ABC
ABC DEF DEF
ABC
ABC ABC
ABC
ABC
ABC
ABC
ABC
ABC
ABC
ABC
ABC
ABC
ABC
ABC
ABC
ABC
ABC
ABC
ABC

Outputs:

ABC occurs on line(s):
1, 2, 3, 4, 5, 6, 7,
8, 9, 10, 11, 12, 13
, 14, 15, 16, 17, 18
, 19, 20, 21
DEF occurs on line(s):
2
+

From this test, we observe that

+
    +
  1. if one word appears more than one times in the same line, the line number is only recorded once. This shows the change described in exercise 7-3 works well.
  2. +
  3. once a line of outputs exceeds 20 characters, it wraps. This verifies the solution given above.
  4. +
+

Noting that I also change the program such that it always writes line numbers starting from a new line.

+
+

To be continued.

+ + +
+ + + + + + + + + + + +
+ + + +
+ + + +
+ +
+
+ + +
+ + + + + + + + + +
+ + + + + + + + + +
+
+ + + + +
+ + +
+ + + + +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/30/05/2018/C-Implementations-Linked-List-based-Stack/index.html b/30/05/2018/C-Implementations-Linked-List-based-Stack/index.html new file mode 100644 index 00000000..ec75d6da --- /dev/null +++ b/30/05/2018/C-Implementations-Linked-List-based-Stack/index.html @@ -0,0 +1,794 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + C++ Implementations: Linked List-based Stack | Liam's Blog + + + + + + + + + + + + + + + + + + +
+
+ + + +
+
+
+
+ + +
+ + + + + + + + +
+ + + +
+ + + + + + + +
+ + + +

C++ Implementations: Linked List-based Stack

+ + + +
+ + + + + +
+ + + + + +

Implementation

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
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
#ifndef MYSTACK_H_
#define MYSTACK_H_

#include <cstddef>
#include <iostream>
#include <stdexcept>

template <typename T>
class MyStack{
struct Node;

public:
typedef std::size_t size_type;
typedef T value_type;

MyStack(): ptrToHead(nullptr), count(0) {
std::cout << "default constructor" << std::endl;
}

// O(n)
MyStack(const MyStack& s): ptrToHead(nullptr), count(0){
std::cout << "copy constructor" << std::endl;
if(!s.empty()){
ptrToHead = create(s.ptrToHead->data);
++count;
Node* current = ptrToHead;
const Node* temp = s.ptrToHead;
while(count != s.count){
current->next = create(temp->next->data);
current = current->next;
temp = temp->next;
++count;
}
}
}

// O(n)
MyStack& operator=(const MyStack& s){
std::cout << "assignment operator" << std::endl;
if(&s != this){
clear();
if(!s.empty()){
ptrToHead = create(s.ptrToHead->data);
++count;
Node* current = ptrToHead;
const Node* temp = s.ptrToHead;
while(count != s.count){
current->next = create(temp->next->data);
current = current->next;
temp = temp->next;
++count;
}
}
}
return *this;
}

// O(n)
~MyStack() {
std::cout << "destructor" << std::endl;
clear();
}

// O(n)
void clear() {
Node* current = ptrToHead;
while(current != nullptr){
ptrToHead = ptrToHead->next;
delete current;
current = ptrToHead;
--count;
}
}

// O(1)
bool empty() const { return ptrToHead == nullptr; }
size_type size() const { return count; }

// O(1)
void push(const T& val){
Node* new_node = create(val);
if(ptrToHead != nullptr)
new_node->next = ptrToHead;
ptrToHead = new_node;
++count;
}

// O(1)
void pop(){
if(ptrToHead == nullptr)
throw std::domain_error("Stack underflow");

Node* temp = ptrToHead;
ptrToHead = ptrToHead->next;
delete temp;
--count;
}

// O(1)
T top() const {
if(ptrToHead == nullptr)
throw std::domain_error("stack underflow");
return ptrToHead->data;
}

private:
struct Node{
T data;
Node* next;
};

// data members
Node* ptrToHead;
size_type count;

// private function to create a new Node given a value
Node* create(const T& val = T()){
Node* new_node = new Node;
new_node->next = nullptr;
new_node->data = val;
return new_node;
}
};
#endif /* MYSTACK_H_ */
+

Test and results

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
/*
* this program tests all operations that provided by the MyStack<int> class
* created by Liam on: 27 May 2018
*/

#include <iostream>
#include <stdexcept>
#include "MyStack.h"

using std::cout;
using std::endl;
using std::domain_error;

int main(){
{ // test default constructor
MyStack<int> s;
if(!s.empty())
cout << "s is an empty stack\n";

// test push
for (int i = 0; i != 10; ++i)
s.push(i);

// test top, pop
while(s.size() != 0){
cout << s.top() << " ";
s.pop();
}

cout << "\n";
// test stack underflow
try{
s.pop();
}catch(domain_error e){
cout << e.what() << "\n";
}
}

cout << "\n";

{ // test copy and assignment operation
MyStack<int> s;
for (int i = 0; i != 10; ++i)
s.push(i);

MyStack<int> s_copy(s);
while(s_copy.size() != 0){
cout << s_copy.top() << " ";
s_copy.pop();
}
cout << "\n";
s.pop();

s_copy = s;
while(s_copy.size() != 0){
cout << s_copy.top() << " ";
s_copy.pop();
}
cout << "\n";
}
}
+

Outputs:

1
2
3
4
5
6
7
8
9
10
11
12
default constructor
9 8 7 6 5 4 3 2 1 0
Stack underflow
destructor

default constructor
copy constructor
9 8 7 6 5 4 3 2 1 0
assignment operator
8 7 6 5 4 3 2 1 0
destructor
destructor

+ + +
+ + + + + + + + + + + +
+ + + +
+ + + +
+ +
+
+ + +
+ + + + + + + + + +
+ + + + + + + + + +
+
+ + + + +
+ + +
+ + + + +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/31/03/2018/Accelerated-C-Solutions-to-Exercises-Chapter-7-Part-2/index.html b/31/03/2018/Accelerated-C-Solutions-to-Exercises-Chapter-7-Part-2/index.html new file mode 100644 index 00000000..bfeb1d93 --- /dev/null +++ b/31/03/2018/Accelerated-C-Solutions-to-Exercises-Chapter-7-Part-2/index.html @@ -0,0 +1,808 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Accelerated C++ Solutions to Exercises(Chapter 7 Part 2) | Liam's Blog + + + + + + + + + + + + + + + + + + +
+
+ + + +
+
+
+
+ + +
+ + + + + + + + +
+ + + +
+ + + + + + + +
+ + + +

Accelerated C++ Solutions to Exercises(Chapter 7 Part 2)

+ + + +
+ + + + + +
+ + + + + +

Exercise 7-5

Reimplement the grammar program using a list as the data structure in which webuild the sentence.

+

Solutions & Results

There is no any other differences between the list-based version and the vector-based version except that we replace vectors with lists literally, and hence No further discussion about this exercise. The original program can be found here Example 3.

+

Exercise 7-6

Reimplement the gen_sentence program using two vectors: One will hold the fullyunwound, generated sentence, and the other will hold the rules and will be used as a stack.Do not use any recursive calls.

+

Solution & Results

To be updated.

+

Exercise 7-7

Change the driver for the cross-reference program so that it writes line if there is only one line and lines otherwise.

+

Solution & Results

The solution is to add and check a condition that whether the vector where holds line numbers only contain one line number. If there is only one line number, we use line else use lines.

1
2
3
4
if ((it->second).size() == 1)
cout << "line:" << endl;
else
cout << "lines:" << endl;

+

I only present the revised file here and please find other file in Exercise 7-4.

+

mainfunction.cpp

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
#include <map>		// to get the declaration of map
#include <string> // to get the declaration of string
#include <vector> // to get the declaration of vector
#include <iostream> // to get the declaration of cin, cout, endl;
#include <sstream> // to get the declaration of ostringstream
#include <cctype> // to get the declaration of isspace
#include "split.h" // to get the declaration of function split
#include "xref.h" // to get the declaration of xref

using std::map; using std::cout;
using std::cin; using std::endl;
using std::string; using std::vector;
using std:: ostringstream; using std::isspace;

int main()
{
// call xret using split by default
map<string, vector<int> > ret = xref(cin);

// set the length for each line of outputs
string::size_type line_length = 20;

// write the result
for(map<string, vector<int> >::const_iterator it = ret.begin(); it != ret.end(); ++it)
{
// write the word
cout << it->first << " occurs on " << endl;

if ((it->second).size() == 1)
cout << "line:" << endl;
else
cout << "lines:" << endl;

// followed by one or more line numbers
vector<int>::const_iterator line_it = it->second.begin();
cout << *line_it; // write the first line number

// scan the rest line numbers
++line_it;
ostringstream os;
while(line_it != it->second.end())
{
// store line numbers into ostringstream object
os << ", " << *line_it;
++line_it;
}

// get the contents from line_numbers
string line_numbers = os.str();

// write each line of outputs
for(string::size_type i = 0; i != line_numbers.size(); ++i)
{
cout << line_numbers[i];
if((i + 1) % line_length == 0)
cout << endl;
}
// write a blank line to separate each words
cout << endl;
}
return 0;
}

+

I use the inputs as same as inputs used in exercise 7-4 and get following results, showing the effect of above changes.

+
1
2
3
4
5
6
7
ABC occurs on lines:
1, 2, 3, 4, 5, 6, 7,
8, 9, 10, 11, 12, 13
, 14, 15, 16, 17, 18
, 19, 20, 21
DEF occurs on line:
2
+

Exercise 7-8

Change the cross-reference program to find all the URLs in a file, and write all the lines
on which each distinct URL occurs.

+

Solution & Results

Please find the program and analysis in Example 2-Test 2.

+

Exercise 7-9

(difficult) The implementation of nrand in §7.4.4/135 will not work for arguments greater than RAND_MAX. Usually, this restriction is no problem, because RAND_MAX is often the largest possible integer anyway. Nevertheless, there are implementations under which RAND_MAX is much smaller than the largest possible integer. For example, it is not uncommon for RAND_MAX to be 32767 (2^15 -1) and the largest possible integer to be 2147483647 (2^31 -1). Reimplement nrand so that it works well for all values of n.

+

Solution & Results

Recalling nrand function

1
2
3
4
5
6
7
8
9
10
11
12
13
int nrand(int n)
{
if(n <= 0 || n > RAND_MAX)
throw domain_error("Argument to nrand is out of range");

const int bucket_size = RAND_MAX /n;
int r;

do r = rand() / bucket_size;
while(r >= n);

return r;
}

+

nrand generates a random numbers in the range [0, n). The idea behind this function is that we divide the range[0, n(RAND_MAX/n)) into n pieces of equal size. Assuming RAND_MAX = 32767, n = 1000, random numbers r and random numbers generated from *rand() have following relationships:

+
1
2
3
4
5
6
7
r (values)                   rand() (values of the range)

0 [0, 32)
1 [32, 64)
2 [64, 96)
... ...
999 [31968, 32000)
+

To be continued.

+ + +
+ + + + + + + + + + + +
+ + + +
+ + + +
+ +
+
+ + +
+ + + + + + + + + +
+ + + + + + + + + +
+
+ + + + +
+ + +
+ + + + +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/archives/2018/02/index.html b/archives/2018/02/index.html index b50bd48a..836a1986 100644 --- a/archives/2018/02/index.html +++ b/archives/2018/02/index.html @@ -1,47 +1,128 @@ - + + + + + + + - - + + + - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + - + - - - + + + + + + + + + + + + - - Archive | Liam's Blog @@ -51,322 +132,439 @@ - - -
-
+ -
-
- + + + + + +
-
+
- - -
+
-
-
-
+
+
+ + + - Nice! 59 posts in total. Keep on posting. -
+ + + + Nice! 58 posts in total. Keep on posting. + -
-

2018

-
-
+ + + + + + +
+

2018

+
+ + + + + + -
+ + + + + + + + + + + + + + -
+ + + + + + + + + + + + + + -
+ + + + + + + + + + + + + + -
+ + + + + + + + + + + + + + -
@@ -380,179 +578,265 @@

- + +
+ -
-
- - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + - - + - @@ -562,8 +846,8 @@

+ - @@ -576,6 +860,22 @@

+ + + + + + + + + + + + + + + + diff --git a/archives/2018/03/index.html b/archives/2018/03/index.html index 2d13cd1d..281068a2 100644 --- a/archives/2018/03/index.html +++ b/archives/2018/03/index.html @@ -1,47 +1,128 @@ - + + + + + + + - - + + + - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + - + - - - + + + + + + + + + + + + - - Archive | Liam's Blog @@ -51,362 +132,513 @@ - - -
-
+ -
-
- + + + + -
+ +
-
+
- - -
+
-
-
-
+
+
+ + + - Nice! 59 posts in total. Keep on posting. -
+ + + + Nice! 58 posts in total. Keep on posting. + -
-

2018

-
-
+ + + + + + +
+

2018

+
+ + + + + + -
+ + + + + + + + + + + + + + -
+ + + + + + + + + + + + + + -
+ + + + + + + + + + + + + + -
+ + + + + + + + + + + + + + -
+ + + + + + + + + + + + + + -
+ + + + + + + + + + + + + + -
+ + + + + + + + + + + + + + -
@@ -415,187 +647,274 @@

+

- + +
+ -
-
+
+ + +
+ + +
+ + + +
- - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + - - + - @@ -605,8 +924,8 @@

+ - @@ -619,6 +938,22 @@

+ + + + + + + + + + + + + + + + diff --git a/archives/2018/03/page/2/index.html b/archives/2018/03/page/2/index.html index a8f76f44..3c4ea08a 100644 --- a/archives/2018/03/page/2/index.html +++ b/archives/2018/03/page/2/index.html @@ -1,47 +1,128 @@ - + + + + + + + - - + + + - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + - + - - - + + + + + + + + + + + + - - Archive | Liam's Blog @@ -51,362 +132,513 @@ - - -
-
+ -
-
- + + + + -
+ +
-
+
- - -
+
-
-
-
+
+
+ + + - Nice! 59 posts in total. Keep on posting. -
+ + + + Nice! 58 posts in total. Keep on posting. + -
-

2018

-
-
+ + + + + + +
+

2018

+
+ + + + + + -
+ + + + + + + + + + + + + + -
+ + + + + + + + + + + + + + -
+ + + + + + + + + + + + + + -
+ + + + + + + + + + + + + + -
+ + + + + + + + + + + + + + -
+ + + + + + + + + + + + + + -
+ + + + + + + + + + + + + + -
@@ -415,187 +647,274 @@

+

- + +
+ -
-
+
+ + +
+ + +
+ + + +
- - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + - - + - @@ -605,8 +924,8 @@

+ - @@ -619,6 +938,22 @@

+ + + + + + + + + + + + + + + + diff --git a/archives/2018/03/page/3/index.html b/archives/2018/03/page/3/index.html index ce19afee..5f0b1a13 100644 --- a/archives/2018/03/page/3/index.html +++ b/archives/2018/03/page/3/index.html @@ -1,47 +1,128 @@ - + + + + + + + - - + + + - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + - + - - - + + + + + + + + + + + + - - Archive | Liam's Blog @@ -51,222 +132,254 @@ - - -
-
+ -
-
- + + + + + +
-
+
- - -
+
-
-
-
+
+
+ + + - Nice! 59 posts in total. Keep on posting. -
+ + + + Nice! 58 posts in total. Keep on posting. + -
-

2018

-
-
+ + + + + + +
+

2018

+
+ + + + + + -
@@ -275,187 +388,195 @@

+

- + +
+ -
-
+
+ + +
+ + +
+ + + +
- - - - + - - - @@ -463,23 +584,118 @@

+ + + + + + + + - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/archives/2018/04/index.html b/archives/2018/04/index.html index e55e4610..1904076e 100644 --- a/archives/2018/04/index.html +++ b/archives/2018/04/index.html @@ -1,47 +1,128 @@ - + + + + + + + - - + + + - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + - + - - - + + + + + + + + + + + + - - Archive | Liam's Blog @@ -51,362 +132,513 @@ - - -
-
+ -
-
- + + + + -
+ +
-
+
- - -
+
-
-
-
+
+
+ + + - Nice! 59 posts in total. Keep on posting. -
+ + + + Nice! 58 posts in total. Keep on posting. + -
-

2018

-
-
+ + + + + + +
+

2018

+
+ + + + + + -
+ + + + + + + + + + + + + + -
+ + + + + + + + + + + + + + -
+ + + + + + + + + + + + + + -
+ + + + + + + + + + + + + + -
+ + + + + + + + + + + + + + -
+ + + + + + + + + + + + + + -
+ + + + + + + + + + + + + + -
@@ -415,187 +647,274 @@

+

- + +
+ -
-
+
+ + +
+ + +
+ + + +
- - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + - - + - @@ -605,8 +924,8 @@

+ - @@ -619,6 +938,22 @@

+ + + + + + + + + + + + + + + + diff --git a/archives/2018/04/page/2/index.html b/archives/2018/04/page/2/index.html index d53aabf8..edb1360d 100644 --- a/archives/2018/04/page/2/index.html +++ b/archives/2018/04/page/2/index.html @@ -1,47 +1,128 @@ - + + + + + + + - - + + + - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + - + - - - + + + + + + + + + + + + - - Archive | Liam's Blog @@ -51,182 +132,180 @@ - - -
-
+ -
-
- + + + + + +
-
+
- - -
+
-
-
-
+
+
+ + + - Nice! 59 posts in total. Keep on posting. -
+ + + + Nice! 58 posts in total. Keep on posting. + -
-

2018

-
-
@@ -235,210 +314,313 @@

+

- + +
+ -
-
+
+ + +
+ + +
+ + + +
- - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + - + - + + - + + + + + - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/archives/2018/05/index.html b/archives/2018/05/index.html index 13ee149a..b767968a 100644 --- a/archives/2018/05/index.html +++ b/archives/2018/05/index.html @@ -1,47 +1,128 @@ - + + + + + + + - - + + + - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + - + - - - + + + + + + + + + + + + - - Archive | Liam's Blog @@ -51,362 +132,513 @@ - - -
-
+ -
-
- + + + + -
+ +
-
+
- - -
+
-
-
-
+
+
+ + + - Nice! 59 posts in total. Keep on posting. -
+ + + + Nice! 58 posts in total. Keep on posting. + -
-

2018

-
-
+ + + + + + +
+

2018

+
+ + + + + + -
+ + + + + + + + + + + + + + -
+ + + + + + + + + + + + + + -
+ + + + + + + + + + + + + + -
+ + + + + + + + + + + + + + -
+ + + + + + + + + + + + + + -
+ + + + + + + + + + + + + + -
+ + + + + + + + + + + + + + -
@@ -415,187 +647,274 @@

+

- + +
+ -
-
+
+ + +
+ + +
+ + + +
- - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + - - + - @@ -605,8 +924,8 @@

+ - @@ -619,6 +938,22 @@

+ + + + + + + + + + + + + + + + diff --git a/archives/2018/05/page/2/index.html b/archives/2018/05/page/2/index.html index 3657a72e..be645f8b 100644 --- a/archives/2018/05/page/2/index.html +++ b/archives/2018/05/page/2/index.html @@ -1,47 +1,128 @@ - + + + + + + + - - + + + - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + - + - - - + + + + + + + + + + + + - - Archive | Liam's Blog @@ -51,282 +132,365 @@ - - -
-
+ -
-
- + + + + -
+ +
-
+
- - -
+
-
-
-
+
+
+ + + - Nice! 59 posts in total. Keep on posting. -
+ + + + Nice! 58 posts in total. Keep on posting. + -
-

2018

-
-
+ + + + + + +
+

2018

+
+ + + + + + -
+ + + + + + + + + + + + + + -
+ + + + + + + + + + + + + + -
+ + + + + + + + + + + + + + -
@@ -335,187 +499,195 @@

+

- + +
+ -
-
+
+ + +
+ + +
+ + + +
- - - - + - - - @@ -525,20 +697,115 @@

+ + + + + + + + + + + - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/archives/2018/06/index.html b/archives/2018/06/index.html deleted file mode 100644 index b065c919..00000000 --- a/archives/2018/06/index.html +++ /dev/null @@ -1,445 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - Archive | Liam's Blog - - - - - - - - - - - - -
-
- -
-
- - -
- - -

If history repeats itself, and the unexpected always happens, how incapable must Man be of learning from experience?—George Bernard Shaw.

-
- - -
- - - - -
-
- - -
- - 0% -
- - - - -
-
-
- - -
- - - - - -
-
-
- - Nice! 59 posts in total. Keep on posting. -
- - -
-

2018

-
- - - - -
-
- - - - - - - - -
- - - - -
- - - - - - - - -
-
- -
- -
-
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/archives/2018/index.html b/archives/2018/index.html index 62b1d2d3..2052c709 100644 --- a/archives/2018/index.html +++ b/archives/2018/index.html @@ -1,47 +1,128 @@ - + + + + + + + - - + + + - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + - + - - - + + + + + + + + + + + + - - Archive | Liam's Blog @@ -51,362 +132,513 @@ - - -
-
+ -
-
- + + + + -
+ +
-
+
- - -
+
-
-
-
+
+
+ + + - Nice! 59 posts in total. Keep on posting. -
+ + + + Nice! 58 posts in total. Keep on posting. + -
-

2018

-
- + -
+ -
+ + + + + + + + + + + + + + -
+ + + + + + + + + + + + + + -
+ + + + + + + + + + + + + + -
+ + + + + + + + + + + + + + -
+ + + + + + + + + + + + + + -
+ + + + + + + + + + + + + + -
+ + + + + + + + + + + + + + -
@@ -415,187 +647,274 @@

+

- + +
+ -
-
+
+ + +
+ + +
+ + + +
- - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + - - + - @@ -605,8 +924,8 @@

+ - @@ -619,6 +938,22 @@

+ + + + + + + + + + + + + + + + diff --git a/archives/2018/page/2/index.html b/archives/2018/page/2/index.html index b503d562..93e5e5f0 100644 --- a/archives/2018/page/2/index.html +++ b/archives/2018/page/2/index.html @@ -1,47 +1,128 @@ - + + + + + + + - - + + + - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + - + - - - + + + + + + + + + + + + - - Archive | Liam's Blog @@ -51,362 +132,513 @@ - - -
-
+ -
-
- + + + + -
+ +
-
+
- - -
+
-
-
-
+
+
+ + + - Nice! 59 posts in total. Keep on posting. -
+ + + + Nice! 58 posts in total. Keep on posting. + -
-

2018

-
- + -
+ -
+ + + + + + + + + + + + + + -
+ + + + + + + + + + + + + + -
+ + + + + + + + + + + + + + -
+ + + + + + + + + + + + + + -
+ + + + + + + + + + + + + + -
+ + + + + + + + + + + + + + -
+ + + + + + + + + + + + + + -
@@ -415,187 +647,274 @@

+

- + +
+ -
-
+
+ + +
+ + +
+ + + +
- - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + - - + - @@ -605,8 +924,8 @@

+ - @@ -619,6 +938,22 @@

+ + + + + + + + + + + + + + + + diff --git a/archives/2018/page/3/index.html b/archives/2018/page/3/index.html index 7b7b960f..96a1d8f4 100644 --- a/archives/2018/page/3/index.html +++ b/archives/2018/page/3/index.html @@ -1,47 +1,128 @@ - + + + + + + + - - + + + - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + - + - - - + + + + + + + + + + + + - - Archive | Liam's Blog @@ -51,362 +132,513 @@ - - -
-
+ -
-
- + + + + -
+ +
-
+
- - -
+
-
-
-
+
+
+ + + - Nice! 59 posts in total. Keep on posting. -
+ + + + Nice! 58 posts in total. Keep on posting. + -
-

2018

-
- + -
+ -
+ + + + + + + + + + + + + + -
+ + + + + + + + + + + + + + -
+ + + + + + + + + + + + + + -
+ + + + + + + + + + + + + + -
+ + + + + + + + + + + + + + -
+ + + + + + + + + + + + + + -
+ + + + + + + + + + + + + + -
@@ -415,187 +647,274 @@

+

- + +
+ -
-
+
+ + +
+ + +
+ + + +
- - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + - - + - @@ -605,8 +924,8 @@

+ - @@ -619,6 +938,22 @@

+ + + + + + + + + + + + + + + + diff --git a/archives/2018/page/4/index.html b/archives/2018/page/4/index.html index 56805766..2e16603f 100644 --- a/archives/2018/page/4/index.html +++ b/archives/2018/page/4/index.html @@ -1,47 +1,128 @@ - + + + + + + + - - + + + - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + - + - - - + + + + + + + + + + + + - - Archive | Liam's Blog @@ -51,362 +132,513 @@ - - -
-
+ -
-
- + + + + -
+ +
-
+
- - -
+
-
-
-
+
+
+ + + - Nice! 59 posts in total. Keep on posting. -
+ + + + Nice! 58 posts in total. Keep on posting. + -
-

2018

-
- + -
+ -
+ + + + + + + + + + + + + + -
+ + + + + + + + + + + + + + -
+ + + + + + + + + + + + + + -
+ + + + + + + + + + + + + + -
+ + + + + + + + + + + + + + -
+ + + + + + + + + + + + + + -
+ + + + + + + + + + + + + + -
@@ -415,187 +647,274 @@

+

- + +
+ -
-
+
+ + +
+ + +
+ + + +
- - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + - - + - @@ -605,8 +924,8 @@

+ - @@ -619,6 +938,22 @@

+ + + + + + + + + + + + + + + + diff --git a/archives/2018/page/5/index.html b/archives/2018/page/5/index.html index 6691c68b..0ae5c5f0 100644 --- a/archives/2018/page/5/index.html +++ b/archives/2018/page/5/index.html @@ -1,47 +1,128 @@ - + + + + + + + - - + + + - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + - + - - - + + + + + + + + + + + + - - Archive | Liam's Blog @@ -51,362 +132,513 @@ - - -
-
+ -
-
- + + + + -
+ +
-
+
- - -
+
-
-
-
+
+
+ + + - Nice! 59 posts in total. Keep on posting. -
+ + + + Nice! 58 posts in total. Keep on posting. + -
-

2018

-
- + -
+ -
+ + + + + + + + + + + + + + -
+ + + + + + + + + + + + + + -
+ + + + + + + + + + + + + + -
+ + + + + + + + + + + + + + -
+ + + + + + + + + + + + + + -
+ + + + + + + + + + + + + + -
@@ -415,187 +647,274 @@

+

- + +
+ -
-
+
+ + +
+ + +
+ + + +
- - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + - - + - @@ -605,8 +924,8 @@

+ - @@ -619,6 +938,22 @@

+ + + + + + + + + + + + + + + + diff --git a/archives/2018/page/6/index.html b/archives/2018/page/6/index.html index dabaeb78..c81b5e5b 100644 --- a/archives/2018/page/6/index.html +++ b/archives/2018/page/6/index.html @@ -1,47 +1,128 @@ - + + + + + + + - - + + + - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + - + - - - + + + + + + + + + + + + - - Archive | Liam's Blog @@ -51,342 +132,439 @@ - - -
-
+ -
-
- + + + + + +
-
+
- - -
+
-
-
-
+
+
+ + + - Nice! 59 posts in total. Keep on posting. -
+ + + + Nice! 58 posts in total. Keep on posting. + -
-

2018

-
- + -
+ -
+ + + + + + + + + + + + + + -
+ + + + + + + + + + + + + + -
+ + + + + + + + + + + + + + -
+ + + + + + + + + + + + + + -
@@ -395,187 +573,274 @@

+

- + +
+ -
-
+
+ + +
+ + +
+ + + +
- - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + - - + - @@ -585,8 +850,8 @@

+ - @@ -599,6 +864,22 @@

+ + + + + + + + + + + + + + + + diff --git a/archives/index.html b/archives/index.html index 4ec705b1..f0a636c4 100644 --- a/archives/index.html +++ b/archives/index.html @@ -1,47 +1,128 @@ - + + + + + + + - - + + + - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + - + - - - + + + + + + + + + + + + - - Archive | Liam's Blog @@ -51,362 +132,513 @@ - - -
-
+ -
-
- + + + + -
+ +
-
+
- - -
+
-
-
-
+
+
+ + + - Nice! 59 posts in total. Keep on posting. -
+ + + + Nice! 58 posts in total. Keep on posting. + -
-

2018

-
- + -
+ -
+ + + + + + + + + + + + + + -
+ + + + + + + + + + + + + + -
+ + + + + + + + + + + + + + -
+ + + + + + + + + + + + + + -
+ + + + + + + + + + + + + + -
+ + + + + + + + + + + + + + -
+ + + + + + + + + + + + + + -
@@ -415,187 +647,274 @@

+

- + +
+ -
-
+
+ + +
+ + +
+ + + +
- - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + - - + - @@ -605,8 +924,8 @@

+ - @@ -619,6 +938,22 @@

+ + + + + + + + + + + + + + + + diff --git a/archives/page/2/index.html b/archives/page/2/index.html index f4ff485b..068e99c1 100644 --- a/archives/page/2/index.html +++ b/archives/page/2/index.html @@ -1,47 +1,128 @@ - + + + + + + + - - + + + - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + - + - - - + + + + + + + + + + + + - - Archive | Liam's Blog @@ -51,362 +132,513 @@ - - -
-
+ -
-
- + + + + -
+ +
-
+
- - -
+
-
-
-
+
+
+ + + - Nice! 59 posts in total. Keep on posting. -
+ + + + Nice! 58 posts in total. Keep on posting. + -
-

2018

-
- + -
+ -
+ + + + + + + + + + + + + + -
+ + + + + + + + + + + + + + -
+ + + + + + + + + + + + + + -
+ + + + + + + + + + + + + + -
+ + + + + + + + + + + + + + -
+ + + + + + + + + + + + + + -
+ + + + + + + + + + + + + + -
@@ -415,187 +647,274 @@

+

- + +
+ -
-
+
+ + +
+ + +
+ + + +
- - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + - - + - @@ -605,8 +924,8 @@

+ - @@ -619,6 +938,22 @@

+ + + + + + + + + + + + + + + + diff --git a/archives/page/3/index.html b/archives/page/3/index.html index 538ed4e3..b2c26349 100644 --- a/archives/page/3/index.html +++ b/archives/page/3/index.html @@ -1,47 +1,128 @@ - + + + + + + + - - + + + - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + - + - - - + + + + + + + + + + + + - - Archive | Liam's Blog @@ -51,362 +132,513 @@ - - -
-
+ -
-
- + + + + -
+ +
-
+
- - -
+
-
-
-
+
+
+ + + - Nice! 59 posts in total. Keep on posting. -
+ + + + Nice! 58 posts in total. Keep on posting. + -
-

2018

-
- + -
+ -
+ + + + + + + + + + + + + + -
+ + + + + + + + + + + + + + -
+ + + + + + + + + + + + + + -
+ + + + + + + + + + + + + + -
+ + + + + + + + + + + + + + -
+ + + + + + + + + + + + + + -
+ + + + + + + + + + + + + + -
@@ -415,187 +647,274 @@

+

- + +
+ -
-
+
+ + +
+ + +
+ + + +
- - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + - - + - @@ -605,8 +924,8 @@

+ - @@ -619,6 +938,22 @@

+ + + + + + + + + + + + + + + + diff --git a/archives/page/4/index.html b/archives/page/4/index.html index 49af9e8b..6155c15b 100644 --- a/archives/page/4/index.html +++ b/archives/page/4/index.html @@ -1,47 +1,128 @@ - + + + + + + + - - + + + - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + - + - - - + + + + + + + + + + + + - - Archive | Liam's Blog @@ -51,362 +132,513 @@ - - -
-
+ -
-
- + + + + -
+ +
-
+
- - -
+
-
-
-
+
+
+ + + - Nice! 59 posts in total. Keep on posting. -
+ + + + Nice! 58 posts in total. Keep on posting. + -
-

2018

-
- + -
+ -
+ + + + + + + + + + + + + + -
+ + + + + + + + + + + + + + -
+ + + + + + + + + + + + + + -
+ + + + + + + + + + + + + + -
+ + + + + + + + + + + + + + -
+ + + + + + + + + + + + + + -
+ + + + + + + + + + + + + + -
@@ -415,187 +647,274 @@

+

- + +
+ -
-
+
+ + +
+ + +
+ + + +
- - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + - - + - @@ -605,8 +924,8 @@

+ - @@ -619,6 +938,22 @@

+ + + + + + + + + + + + + + + + diff --git a/archives/page/5/index.html b/archives/page/5/index.html index 5b908f71..41fb0a43 100644 --- a/archives/page/5/index.html +++ b/archives/page/5/index.html @@ -1,47 +1,128 @@ - + + + + + + + - - + + + - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + - + - - - + + + + + + + + + + + + - - Archive | Liam's Blog @@ -51,362 +132,513 @@ - - -
-
+ -
-
- + + + + -
+ +
-
+
- - -
+
-
-
-
+
+
+ + + - Nice! 59 posts in total. Keep on posting. -
+ + + + Nice! 58 posts in total. Keep on posting. + -
-

2018

-
- + -
+ -
+ + + + + + + + + + + + + + -
+ + + + + + + + + + + + + + -
+ + + + + + + + + + + + + + -
+ + + + + + + + + + + + + + -
+ + + + + + + + + + + + + + -
+ + + + + + + + + + + + + + -
@@ -415,187 +647,274 @@

+

- + +
+ -
-
+
+ + +
+ + +
+ + + +
- - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + - - + - @@ -605,8 +924,8 @@

+ - @@ -619,6 +938,22 @@

+ + + + + + + + + + + + + + + + diff --git a/archives/page/6/index.html b/archives/page/6/index.html index 0e7f0e82..7eaa2879 100644 --- a/archives/page/6/index.html +++ b/archives/page/6/index.html @@ -1,47 +1,128 @@ - + + + + + + + - - + + + - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + - + - - - + + + + + + + + + + + + - - Archive | Liam's Blog @@ -51,342 +132,439 @@ - - -
-
+ -
-
- + + + + + +
-
+
- - -
+
-
-
-
+
+
+ + + - Nice! 59 posts in total. Keep on posting. -
+ + + + Nice! 58 posts in total. Keep on posting. + -
-

2018

-
- + -
+ -
+ + + + + + + + + + + + + + -
+ + + + + + + + + + + + + + -
+ + + + + + + + + + + + + + -
+ + + + + + + + + + + + + + -
@@ -395,187 +573,274 @@

+

- + +
+ -
-
+
+ + +
+ + +
+ + + +
- - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + - - + - @@ -585,8 +850,8 @@

+ - @@ -599,6 +864,22 @@

+ + + + + + + + + + + + + + + + diff --git a/categories/Algorithms/index.html b/categories/Algorithms/index.html index 9c3a42a6..1eefa04d 100644 --- a/categories/Algorithms/index.html +++ b/categories/Algorithms/index.html @@ -1,47 +1,128 @@ - + + + + + + + - - + + + - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + - + - - - + + + + + + + + + + + + - - Category: Algorithms | Liam's Blog @@ -51,363 +132,415 @@ - - -
-
+ -
-
- + + + + + +
-
+
- - -
+
-
-
+
+ +
-

Algorithms - Category +

AlgorithmsCategory

-
-

2018

-
+ -
+ -
+ + + + + -
+ + + + + -
+ + + + + -
+ + + + + -
+ + + + + -
+ + + + + -
+ + + + + -
+
@@ -415,210 +548,313 @@

+

- + +
+ -
-
+
+ + +
+ + +
+ + + +
- - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + - + + + + + + + + - + - + + + + + + + + + + - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/categories/Algorithms/page/2/index.html b/categories/Algorithms/page/2/index.html index 6c16056c..325bedc4 100644 --- a/categories/Algorithms/page/2/index.html +++ b/categories/Algorithms/page/2/index.html @@ -1,47 +1,128 @@ - + + + + + + + - - + + + - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + - + - - - + + + + + + + + + + + + - - Category: Algorithms | Liam's Blog @@ -51,203 +132,191 @@ - - -
-
+ -
-
- + + + + -
+ +
-
+
- - -
+
-
-
+
+ +
-

Algorithms - Category +

AlgorithmsCategory

-
-

2018

-
+ -
+
@@ -255,187 +324,274 @@

+

- + +
+ -
-
+
+ + +
+ + +
+ + + +
- - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + - - + - @@ -445,8 +601,8 @@

+ - @@ -459,6 +615,22 @@

+ + + + + + + + + + + + + + + + diff --git a/categories/Programming/index.html b/categories/Programming/index.html index 1310d0b1..764f177a 100644 --- a/categories/Programming/index.html +++ b/categories/Programming/index.html @@ -1,47 +1,128 @@ - + + + + + + + - - + + + - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + - + - - - + + + + + + + + + + + + - - Category: Programming | Liam's Blog @@ -51,363 +132,415 @@ - - -
-
+ -
-
- + + + + + +
-
+
- - -
+
-
-
+
+ +
-

Programming - Category +

ProgrammingCategory

-
-

2018

-
+ -
+ - - -
+ + + + + -
+ + + + + -
+ + + + + -
+ + + + + -
+ + + + + -
+ + + + + -
+
@@ -415,187 +548,195 @@

+

- + +
+ -
-
+
+ + +
+ + +
+ + + +
- - - - + - - - @@ -603,22 +744,117 @@

+ + + + + + + + - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/categories/Programming/page/2/index.html b/categories/Programming/page/2/index.html index 5a7fca15..4d9c3ef9 100644 --- a/categories/Programming/page/2/index.html +++ b/categories/Programming/page/2/index.html @@ -1,47 +1,128 @@ - + + + + + + + - - + + + - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + - + - - - + + + + + + + + + + + + - - Category: Programming | Liam's Blog @@ -51,363 +132,415 @@ - - -
-
+ -
-
- + + + + + +
-
+
- - -
+
-
-
+
+ +
-

Programming - Category +

ProgrammingCategory

-
-

2018

-
+ -
+ - - -
+ + + + + -
+ + + + + -
+ + + + + -
+ + + + + -
+ + + + + -
+ + + + + -
+
@@ -415,187 +548,274 @@

+

- + +
+ -
-
+
+ + +
+ + +
+ + + +
- - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + - + - + + + + + + + + + - @@ -605,8 +825,8 @@

+ - @@ -619,6 +839,22 @@

+ + + + + + + + + + + + + + + + diff --git a/categories/Programming/page/3/index.html b/categories/Programming/page/3/index.html index c45dc9c8..17a9d8f6 100644 --- a/categories/Programming/page/3/index.html +++ b/categories/Programming/page/3/index.html @@ -1,47 +1,128 @@ - + + + + + + + - - + + + - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + - + - - - + + + + + + + + + + + + - - Category: Programming | Liam's Blog @@ -51,363 +132,415 @@ - - -
-
+ -
-
- + + + + + +
-
+
- - -
+
-
-
+
+ +
-

Programming - Category +

ProgrammingCategory

-
-

2018

-
+ -
+ - - -
+ + + + + -
+ + + + + -
+ + + + + -
+ + + + + -
+ + + + + -
+ + + + + -
+
@@ -415,187 +548,195 @@

+

- + +
+ -
-
+
+ + +
+ + +
+ + + +
- - - - + - - - @@ -603,22 +744,117 @@

+ + + + + + + + - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/categories/Programming/page/4/index.html b/categories/Programming/page/4/index.html index ce7dcab5..6258878f 100644 --- a/categories/Programming/page/4/index.html +++ b/categories/Programming/page/4/index.html @@ -1,47 +1,128 @@ - + + + + + + + - - + + + - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + - + - - - + + + + + + + + + + + + - - Category: Programming | Liam's Blog @@ -51,363 +132,415 @@ - - -
-
+ -
-
- + + + + + +
-
+
- - -
+
-
-
+
+ +
-

Programming - Category +

ProgrammingCategory

-
-

2018

-
+ -
+ - - -
+ + + + + -
+ + + + + -
+ + + + + -
+ + + + + -
+ + + + + -
+ + + + + -
+
@@ -415,187 +548,195 @@

+

- + +
+ -
-
+
+ + +
+ + +
+ + + +
- - - - + - - - @@ -603,22 +744,117 @@

+ + + + + + + + - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/categories/Programming/page/5/index.html b/categories/Programming/page/5/index.html index 42ec2b1a..3b623a08 100644 --- a/categories/Programming/page/5/index.html +++ b/categories/Programming/page/5/index.html @@ -1,47 +1,128 @@ - + + + + + + + - - + + + - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + - + - - - + + + + + + + + + + + + - - Category: Programming | Liam's Blog @@ -51,263 +132,247 @@ - - -
-
+ -
-
- + + + + + +
-
+
- - -
+
-
-
+
+ +
-

Programming - Category +

ProgrammingCategory

-
-

2018

-
+ -
+ - - -
+
@@ -315,162 +380,161 @@

+

- + +
+ - - +
-
+
+ + +
+ + +
+ + + +
- - - - + - - - @@ -503,22 +576,117 @@

+ + + + + + + + - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/categories/index.html b/categories/index.html index 8a49fca8..fb9edcda 100644 --- a/categories/index.html +++ b/categories/index.html @@ -1,215 +1,263 @@ - + + + + + + + - - + + + - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + - + - - - - - + + + + + + + + - - + + - categories | Liam's Blog - - + categories | Liam's Blog + + + - - -
-
+ -
-
- + + + + + +
+
-
+ - - -
- - 0% -
- - +
+
-
+
- - - - -
+
-
+
-
+
-

categories -

+

categories

-
@@ -217,12 +265,14 @@

categories
+ +
- 2 categories in total + 2 categories in total
@@ -232,8 +282,6 @@

categories

- -

@@ -242,179 +290,190 @@

categories

- + + + + + +
+ -
-
+
+ + +
+ + +
+ + + +
- - - - + - - - @@ -424,21 +483,120 @@

categories + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/css/main.css b/css/main.css index caabcf69..5e4f84a2 100644 --- a/css/main.css +++ b/css/main.css @@ -1,89 +1,66 @@ -:root { - --body-bg-color: #fff; - --content-bg-color: #fff; - --card-bg-color: #f5f5f5; - --text-color: #555; - --link-color: #555; - --link-hover-color: #222; - --brand-color: #fff; - --brand-hover-color: #fff; - --table-row-odd-bg-color: #f9f9f9; - --table-row-hover-bg-color: #f5f5f5; - --menu-item-bg-color: #f5f5f5; - --btn-default-bg: #222; - --btn-default-color: #fff; - --btn-default-border-color: #222; - --btn-default-hover-bg: #fff; - --btn-default-hover-color: #222; - --btn-default-hover-border-color: #222; -} -@media (prefers-color-scheme: dark) { - :root { - --body-bg-color: #121212; - --content-bg-color: #1d1d1d; - --card-bg-color: #282828; - --text-color: #e1e1e1; - --link-color: #e1e1e1; - --link-hover-color: #fff; - --brand-color: #fff; - --brand-hover-color: #fff; - --table-row-odd-bg-color: #282828; - --table-row-hover-bg-color: #363636; - --menu-item-bg-color: #333; - --btn-default-bg: #222; - --btn-default-color: #e1e1e1; - --btn-default-border-color: #333; - --btn-default-hover-bg: #555; - --btn-default-hover-color: #e1e1e1; - --btn-default-hover-border-color: #555; - } - img { - opacity: 0.75; - } - img:hover { - opacity: 1; - } -} +/* normalize.css v3.0.2 | MIT License | git.io/normalize */ html { - line-height: 1.15; /* 1 */ + font-family: sans-serif; /* 1 */ + -ms-text-size-adjust: 100%; /* 2 */ -webkit-text-size-adjust: 100%; /* 2 */ } body { margin: 0; } -main { +article, +aside, +details, +figcaption, +figure, +footer, +header, +hgroup, +main, +menu, +nav, +section, +summary { display: block; } -h1 { - font-size: 2em; - margin: 0.67em 0; +audio, +canvas, +progress, +video { + display: inline-block; /* 1 */ + vertical-align: baseline; /* 2 */ } -hr { - box-sizing: content-box; /* 1 */ - height: 0; /* 1 */ - overflow: visible; /* 2 */ +audio:not([controls]) { + display: none; + height: 0; } -pre { - font-family: monospace, monospace; /* 1 */ - font-size: 1em; /* 2 */ +[hidden], +template { + display: none; } a { - background: transparent; + background-color: transparent; +} +a:active, +a:hover { + outline: 0; } abbr[title] { - border-bottom: none; /* 1 */ - text-decoration: underline; /* 2 */ - text-decoration: underline dotted; /* 2 */ + border-bottom: 1px dotted; } b, strong { - font-weight: bolder; + font-weight: bold; } -code, -kbd, -samp { - font-family: monospace, monospace; /* 1 */ - font-size: 1em; /* 2 */ +dfn { + font-style: italic; +} +h1 { + font-size: 2em; + margin: 0.67em 0; +} +mark { + background: #ff0; + color: #000; } small { font-size: 80%; @@ -95,415 +72,520 @@ sup { position: relative; vertical-align: baseline; } -sub { - bottom: -0.25em; -} sup { top: -0.5em; } +sub { + bottom: -0.25em; +} img { - border-style: none; + border: 0; +} +svg:not(:root) { + overflow: hidden; +} +figure { + margin: 1em 40px; +} +hr { + -moz-box-sizing: content-box; + box-sizing: content-box; + height: 0; +} +pre { + overflow: auto; +} +code, +kbd, +pre, +samp { + font-family: monospace, monospace; + font-size: 1em; } button, input, optgroup, select, textarea { - font-family: inherit; /* 1 */ - font-size: 100%; /* 1 */ - line-height: 1.15; /* 1 */ - margin: 0; /* 2 */ + color: inherit; /* 1 */ + font: inherit; /* 2 */ + margin: 0; /* 3 */ } -button, -input { -/* 1 */ +button { overflow: visible; } button, select { -/* 1 */ text-transform: none; } button, -[type='button'], -[type='reset'], -[type='submit'] { - -webkit-appearance: button; +html input[type="button"], +input[type="reset"], +input[type="submit"] { + -webkit-appearance: button; /* 2 */ + cursor: pointer; /* 3 */ +} +button[disabled], +html input[disabled] { + cursor: default; } button::-moz-focus-inner, -[type='button']::-moz-focus-inner, -[type='reset']::-moz-focus-inner, -[type='submit']::-moz-focus-inner { - border-style: none; +input::-moz-focus-inner { + border: 0; padding: 0; } -button:-moz-focusring, -[type='button']:-moz-focusring, -[type='reset']:-moz-focusring, -[type='submit']:-moz-focusring { - outline: 1px dotted ButtonText; -} -fieldset { - padding: 0.35em 0.75em 0.625em; -} -legend { - box-sizing: border-box; /* 1 */ - color: inherit; /* 2 */ - display: table; /* 1 */ - max-width: 100%; /* 1 */ - padding: 0; /* 3 */ - white-space: normal; /* 1 */ -} -progress { - vertical-align: baseline; -} -textarea { - overflow: auto; +input { + line-height: normal; } -[type='checkbox'], -[type='radio'] { +input[type="checkbox"], +input[type="radio"] { box-sizing: border-box; /* 1 */ padding: 0; /* 2 */ } -[type='number']::-webkit-inner-spin-button, -[type='number']::-webkit-outer-spin-button { +input[type="number"]::-webkit-inner-spin-button, +input[type="number"]::-webkit-outer-spin-button { height: auto; } -[type='search'] { - outline-offset: -2px; /* 2 */ +input[type="search"] { -webkit-appearance: textfield; /* 1 */ + -moz-box-sizing: content-box; + -webkit-box-sizing: content-box; /* 2 */ + box-sizing: content-box; } -[type='search']::-webkit-search-decoration { +input[type="search"]::-webkit-search-cancel-button, +input[type="search"]::-webkit-search-decoration { -webkit-appearance: none; } -::-webkit-file-upload-button { - font: inherit; /* 2 */ - -webkit-appearance: button; /* 1 */ +fieldset { + border: 1px solid #c0c0c0; + margin: 0 2px; + padding: 0.35em 0.625em 0.75em; } -details { - display: block; +legend { + border: 0; /* 1 */ + padding: 0; /* 2 */ } -summary { - display: list-item; +textarea { + overflow: auto; } -template { - display: none; +optgroup { + font-weight: bold; } -[hidden] { - display: none; +table { + border-collapse: collapse; + border-spacing: 0; +} +td, +th { + padding: 0; } ::selection { background: #262a30; color: #fff; } -html, -body { - height: 100%; -} body { - background: var(--body-bg-color); - color: var(--text-color); - font-family: 'Times New Roman', "PingFang SC", "Microsoft YaHei", sans-serif; - font-size: 1em; + position: relative; + font-family: 'Lato', "PingFang SC", "Microsoft YaHei", sans-serif; + font-size: 14px; line-height: 2; + color: #555; + background: #fff; +} +@media (max-width: 767px) { + body { + padding-right: 0 !important; + } } -@media (max-width: 991px) { +@media (min-width: 768px) and (max-width: 991px) { body { - padding-left: 0 !important; padding-right: 0 !important; } } +@media (min-width: 1600px) { + body { + font-size: 16px; + } +} h1, h2, h3, h4, h5, h6 { - font-family: 'Times New Roman', 'Times New Roman', "PingFang SC", "Microsoft YaHei", sans-serif; + margin: 0; + padding: 0; font-weight: bold; line-height: 1.5; + font-family: 'Lato', "PingFang SC", "Microsoft YaHei", sans-serif; +} +h2, +h3, +h4, +h5, +h6 { margin: 20px 0 15px; } h1 { - font-size: 1.5em; + font-size: 22px; +} +@media (max-width: 767px) { + h1 { + font-size: 18px; + } } h2 { - font-size: 1.375em; + font-size: 20px; +} +@media (max-width: 767px) { + h2 { + font-size: 16px; + } } h3 { - font-size: 1.25em; + font-size: 18px; +} +@media (max-width: 767px) { + h3 { + font-size: 14px; + } } h4 { - font-size: 1.125em; + font-size: 16px; +} +@media (max-width: 767px) { + h4 { + font-size: 12px; + } } h5 { - font-size: 1em; + font-size: 14px; +} +@media (max-width: 767px) { + h5 { + font-size: 10px; + } } h6 { - font-size: 0.875em; + font-size: 12px; +} +@media (max-width: 767px) { + h6 { + font-size: 8px; + } } p { margin: 0 0 20px 0; } -a, -span.exturl { - border-bottom: 1px solid #999; - color: var(--link-color); - outline: 0; +a { + color: #555; text-decoration: none; - overflow-wrap: break-word; + outline: none; + border-bottom: 1px solid #999; word-wrap: break-word; - cursor: pointer; } -a:hover, -span.exturl:hover { - border-bottom-color: var(--link-hover-color); - color: var(--link-hover-color); +a:hover { + color: #222; + border-bottom-color: #222; } -iframe, -img, -video { +blockquote { + margin: 0; + padding: 0; +} +img { display: block; - margin-left: auto; - margin-right: auto; + margin: auto; max-width: 100%; + height: auto; } hr { - background-image: repeating-linear-gradient(-45deg, #ddd, #ddd 4px, transparent 4px, transparent 8px); - border: 0; - height: 3px; margin: 40px 0; + height: 3px; + border: none; + background-color: #ddd; + background-image: repeating-linear-gradient(-45deg, #fff, #fff 4px, transparent 4px, transparent 8px); } blockquote { - border-left: 4px solid #ddd; - color: #666; - margin: 0; padding: 0 15px; + color: #666; + border-left: 4px solid #ddd; } blockquote cite::before { - content: '-'; + content: "-"; padding: 0 5px; } dt { - font-weight: bold; + font-weight: 700; } dd { margin: 0; padding: 0; } kbd { - background-color: #f5f5f5; - background-image: linear-gradient(#eee, #fff, #eee); border: 1px solid #ccc; border-radius: 0.2em; box-shadow: 0.1em 0.1em 0.2em rgba(0,0,0,0.1); - color: #555; + background-color: #f9f9f9; font-family: inherit; + background-image: -webkit-linear-gradient(top, #eee, #fff, #eee); padding: 0.1em 0.3em; white-space: nowrap; } -.table-container { - overflow: auto; +.text-left { + text-align: left; +} +.text-center { + text-align: center; +} +.text-right { + text-align: right; +} +.text-justify { + text-align: justify; +} +.text-nowrap { + white-space: nowrap; +} +.text-lowercase { + text-transform: lowercase; +} +.text-uppercase { + text-transform: uppercase; +} +.text-capitalize { + text-transform: capitalize; +} +.center-block { + display: block; + margin-left: auto; + margin-right: auto; +} +.clearfix:before, +.clearfix:after { + content: " "; + display: table; +} +.clearfix:after { + clear: both; +} +.pullquote { + width: 45%; +} +.pullquote.left { + float: left; + margin-left: 5px; + margin-right: 10px; +} +.pullquote.right { + float: right; + margin-left: 10px; + margin-right: 5px; +} +.affix.affix.affix { + position: fixed; +} +.translation { + margin-top: -20px; + font-size: 14px; + color: #999; +} +.scrollbar-measure { + width: 100px; + height: 100px; + overflow: scroll; + position: absolute; + top: -9999px; +} +.use-motion .motion-element { + opacity: 0; } table { + margin: 20px 0; + width: 100%; border-collapse: collapse; border-spacing: 0; - font-size: 0.875em; - margin: 0 0 20px 0; - width: 100%; + border: 1px solid #ddd; + font-size: 14px; + table-layout: fixed; + word-wrap: break-all; } -tbody tr:nth-of-type(odd) { - background: var(--table-row-odd-bg-color); +table>tbody>tr:nth-of-type(odd) { + background-color: #f9f9f9; } -tbody tr:hover { - background: var(--table-row-hover-bg-color); +table>tbody>tr:hover { + background-color: #f5f5f5; } caption, th, td { - font-weight: normal; padding: 8px; text-align: left; vertical-align: middle; + font-weight: normal; } th, td { - border: 1px solid #ddd; border-bottom: 3px solid #ddd; + border-right: 1px solid #eee; } th { - font-weight: 700; padding-bottom: 10px; + font-weight: 700; } td { border-bottom-width: 1px; } -.btn { - background: var(--btn-default-bg); - border: 2px solid var(--btn-default-border-color); - border-radius: 0; - color: var(--btn-default-color); - display: inline-block; - font-size: 0.875em; - line-height: 2; - padding: 0 20px; - text-decoration: none; - transition-property: background-color; - transition-delay: 0s; - transition-duration: 0.2s; - transition-timing-function: ease-in-out; +html, +body { + height: 100%; } -.btn:hover { - background: var(--btn-default-hover-bg); - border-color: var(--btn-default-hover-border-color); - color: var(--btn-default-hover-color); +.container { + position: relative; + min-height: 100%; } -.btn + .btn { - margin: 0 0 8px 8px; +.header-inner { + margin: 0 auto; + padding: 100px 0 70px; + width: 700px; } -.btn .fa-fw { - text-align: left; - width: 1.285714285714286em; +@media (min-width: 1600px) { + .container .header-inner { + width: 900px; + } } -.toggle { - line-height: 0; +.main { + padding-bottom: 150px; } -.toggle .toggle-line { - background: #fff; - display: inline-block; - height: 2px; +.main-inner { + margin: 0 auto; + width: 700px; +} +@media (min-width: 1600px) { + .container .main-inner { + width: 900px; + } +} +.footer { + position: absolute; left: 0; - position: relative; - top: 0; - transition: all 0.4s; - vertical-align: top; + bottom: 0; width: 100%; + min-height: 50px; } -.toggle .toggle-line:not(:first-child) { - margin-top: 3px; +.footer-inner { + box-sizing: border-box; + margin: 20px auto; + width: 700px; } -.toggle.toggle-arrow .toggle-line-first { - top: 2px; - transform: rotate(-45deg); - width: 50%; -} -.toggle.toggle-arrow .toggle-line-middle { - width: 90%; -} -.toggle.toggle-arrow .toggle-line-last { - top: -2px; - transform: rotate(45deg); - width: 50%; -} -.toggle.toggle-close .toggle-line-first { - top: 5px; - transform: rotate(-45deg); -} -.toggle.toggle-close .toggle-line-middle { - opacity: 0; -} -.toggle.toggle-close .toggle-line-last { - top: -5px; - transform: rotate(45deg); +@media (min-width: 1600px) { + .container .footer-inner { + width: 900px; + } } -.highlight, -pre { - background: #1d1f21; +pre, +.highlight { + overflow: auto; + margin: 20px 0; + padding: 0; + font-size: 13px; color: #c5c8c6; + background: #1d1f21; line-height: 1.6; - margin: 0 auto 20px; } pre, code { - font-family: 'Courier New', consolas, Menlo, monospace, "PingFang SC", "Microsoft YaHei"; + font-family: consolas, Menlo, "PingFang SC", "Microsoft YaHei", monospace; } code { - background: #eee; - border-radius: 3px; - color: #555; padding: 2px 4px; - overflow-wrap: break-word; word-wrap: break-word; + color: #555; + background: #eee; + border-radius: 3px; + font-size: 13px; +} +pre { + padding: 10px; +} +pre code { + padding: 0; + color: #c5c8c6; + background: none; + text-shadow: none; } -.highlight *::selection { - background: #373b41; +.highlight { + border-radius: 1px; } .highlight pre { - border: 0; + border: none; margin: 0; padding: 10px 0; } .highlight table { - border: 0; margin: 0; width: auto; + border: none; } .highlight td { - border: 0; + border: none; padding: 0; } .highlight figcaption { - background: #eee; + font-size: 1em; color: #c5c8c6; - display: flex; - font-size: 0.875em; - justify-content: space-between; - line-height: 1.2; - padding: 0.5em; + line-height: 1em; + margin-bottom: 1em; +} +.highlight figcaption:before, +.highlight figcaption:after { + content: " "; + display: table; +} +.highlight figcaption:after { + clear: both; } .highlight figcaption a { + float: right; color: #c5c8c6; } .highlight figcaption a:hover { border-bottom-color: #c5c8c6; } -.highlight .gutter { - -moz-user-select: none; - -ms-user-select: none; - -webkit-user-select: none; - user-select: none; -} .highlight .gutter pre { - background: #000; - color: #888f96; padding-left: 10px; padding-right: 10px; + color: #888f96; text-align: right; + background-color: #000; } .highlight .code pre { - background: #1d1f21; - padding-left: 10px; width: 100%; + padding-left: 10px; + padding-right: 10px; + background-color: #1d1f21; +} +.highlight .line { + height: 20px; +} +.gutter { + -webkit-user-select: none; + -moz-user-select: none; + -ms-user-select: none; + user-select: none; } .gist table { width: auto; } .gist table td { - border: 0; -} -pre { - overflow: auto; - padding: 10px; -} -pre code { - background: none; - color: #c5c8c6; - font-size: 0.875em; - padding: 0; - text-shadow: none; + border: none; } pre .deletion { - background: #800000; + background: #008000; } pre .addition { - background: #008000; + background: #800000; } pre .meta { - color: #f0c674; - -moz-user-select: none; - -ms-user-select: none; - -webkit-user-select: none; - user-select: none; + color: #b294bb; } pre .comment { color: #969896; @@ -511,7 +593,6 @@ pre .comment { pre .variable, pre .attribute, pre .tag, -pre .name, pre .regexp, pre .ruby .constant, pre .xml .tag .title, @@ -526,7 +607,6 @@ pre .css .pseudo { pre .number, pre .preprocessor, pre .built_in, -pre .builtin-name, pre .literal, pre .params, pre .constant, @@ -536,13 +616,13 @@ pre .command { pre .ruby .class .title, pre .css .rules .attribute, pre .string, -pre .symbol, pre .value, pre .inheritance, pre .header, pre .ruby .symbol, pre .xml .cdata, pre .special, +pre .number, pre .formula { color: #b5bd68; } @@ -564,193 +644,110 @@ pre .keyword, pre .javascript .function { color: #b294bb; } -.blockquote-center { - border-left: none; +.full-image.full-image.full-image.full-image { + border: none; + max-width: 100%; + width: auto; + margin: 20px auto 25px; +} +@media (min-width: 992px) { + .full-image.full-image.full-image.full-image { + max-width: none; + width: 110%; + margin: 25px -5%; + } +} +.blockquote-center, +.page-home .post-type-quote blockquote, +.page-post-detail .post-type-quote blockquote { + position: relative; margin: 40px 0; padding: 0; - position: relative; + border-left: none; text-align: center; } .blockquote-center::before, -.blockquote-center::after { - background-repeat: no-repeat; - background-size: 22px 22px; +.page-home .post-type-quote blockquote::before, +.page-post-detail .post-type-quote blockquote::before, +.blockquote-center::after, +.page-home .post-type-quote blockquote::after, +.page-post-detail .post-type-quote blockquote::after { + position: absolute; content: ' '; display: block; + width: 100%; height: 24px; opacity: 0.2; - position: absolute; - width: 100%; + background-repeat: no-repeat; + background-position: 0 -6px; + background-size: 22px 22px; } -.blockquote-center::before { +.blockquote-center::before, +.page-home .post-type-quote blockquote::before, +.page-post-detail .post-type-quote blockquote::before { + top: -20px; background-image: url("../images/quote-l.svg"); - background-position: 0 -6px; border-top: 1px solid #ccc; - top: -20px; } -.blockquote-center::after { +.blockquote-center::after, +.page-home .post-type-quote blockquote::after, +.page-post-detail .post-type-quote blockquote::after { + bottom: -20px; background-image: url("../images/quote-r.svg"); - background-position: 100% 8px; border-bottom: 1px solid #ccc; - bottom: -20px; + background-position: 100% 8px; } .blockquote-center p, -.blockquote-center div { +.page-home .post-type-quote blockquote p, +.page-post-detail .post-type-quote blockquote p, +.blockquote-center div, +.page-home .post-type-quote blockquote div, +.page-post-detail .post-type-quote blockquote div { text-align: center; } -.post-body .group-picture img { - margin: 0 auto; +.post .post-body .group-picture img { + box-sizing: border-box; padding: 0 3px; + border: none; } -.group-picture-row { - margin-bottom: 6px; +.post .group-picture-row { overflow: hidden; + margin-top: 6px; } -.group-picture-column { - float: left; - margin-bottom: 10px; -} -.post-body .label { - color: #555; - display: inline; - padding: 0 2px; -} -.post-body .label.default { - background: #f0f0f0; -} -.post-body .label.primary { - background: #efe6f7; -} -.post-body .label.info { - background: #e5f2f8; -} -.post-body .label.success { - background: #e7f4e9; -} -.post-body .label.warning { - background: #fcf6e1; -} -.post-body .label.danger { - background: #fae8eb; -} -.post-body .tabs { - margin-bottom: 20px; -} -.post-body .tabs, -.tabs-comment { - display: block; - padding-top: 10px; - position: relative; -} -.post-body .tabs ul.nav-tabs, -.tabs-comment ul.nav-tabs { - display: flex; - flex-wrap: wrap; - margin: 0; - margin-bottom: -1px; - padding: 0; -} -@media (max-width: 413px) { - .post-body .tabs ul.nav-tabs, - .tabs-comment ul.nav-tabs { - display: block; - margin-bottom: 5px; - } -} -.post-body .tabs ul.nav-tabs li.tab, -.tabs-comment ul.nav-tabs li.tab { - border-bottom: 1px solid #ddd; - border-left: 1px solid transparent; - border-right: 1px solid transparent; - border-top: 3px solid transparent; - flex-grow: 1; - list-style-type: none; - border-radius: 0 0 0 0; -} -@media (max-width: 413px) { - .post-body .tabs ul.nav-tabs li.tab, - .tabs-comment ul.nav-tabs li.tab { - border-bottom: 1px solid transparent; - border-left: 3px solid transparent; - border-right: 1px solid transparent; - border-top: 1px solid transparent; - } -} -@media (max-width: 413px) { - .post-body .tabs ul.nav-tabs li.tab, - .tabs-comment ul.nav-tabs li.tab { - border-radius: 0; - } -} -.post-body .tabs ul.nav-tabs li.tab a, -.tabs-comment ul.nav-tabs li.tab a { - border-bottom: initial; - display: block; - line-height: 1.8; - outline: 0; - padding: 0.25em 0.75em; - text-align: center; - transition-delay: 0s; - transition-duration: 0.2s; - transition-timing-function: ease-out; -} -.post-body .tabs ul.nav-tabs li.tab a i, -.tabs-comment ul.nav-tabs li.tab a i { - width: 1.285714285714286em; -} -.post-body .tabs ul.nav-tabs li.tab.active, -.tabs-comment ul.nav-tabs li.tab.active { - border-bottom: 1px solid transparent; - border-left: 1px solid #ddd; - border-right: 1px solid #ddd; - border-top: 3px solid #fc6423; +.post .group-picture-row:first-child { + margin-top: 0; } -@media (max-width: 413px) { - .post-body .tabs ul.nav-tabs li.tab.active, - .tabs-comment ul.nav-tabs li.tab.active { - border-bottom: 1px solid #ddd; - border-left: 3px solid #fc6423; - border-right: 1px solid #ddd; - border-top: 1px solid #ddd; - } +.post .group-picture-column { + float: left; } -.post-body .tabs ul.nav-tabs li.tab.active a, -.tabs-comment ul.nav-tabs li.tab.active a { - color: var(--link-color); - cursor: default; +.page-post-detail .post-body .group-picture-column { + float: none; + margin-top: 10px; + width: auto !important; } -.post-body .tabs .tab-content .tab-pane, -.tabs-comment .tab-content .tab-pane { - border: 1px solid #ddd; - border-top: 0; - padding: 20px 20px 0 20px; - border-radius: 0; +.page-post-detail .post-body .group-picture-column img { + margin: 0 auto; } -.post-body .tabs .tab-content .tab-pane:not(.active), -.tabs-comment .tab-content .tab-pane:not(.active) { - display: none; +.page-archive .group-picture-container { + overflow: hidden; } -.post-body .tabs .tab-content .tab-pane.active, -.tabs-comment .tab-content .tab-pane.active { - display: block; +.page-archive .group-picture-row { + float: left; } -.post-body .tabs .tab-content .tab-pane.active:nth-of-type(1), -.tabs-comment .tab-content .tab-pane.active:nth-of-type(1) { - border-radius: 0 0 0 0; +.page-archive .group-picture-row:first-child { + margin-top: 6px; } -@media (max-width: 413px) { - .post-body .tabs .tab-content .tab-pane.active:nth-of-type(1), - .tabs-comment .tab-content .tab-pane.active:nth-of-type(1) { - border-radius: 0; - } +.page-archive .group-picture-column { + max-width: 150px; + max-height: 150px; } .post-body .note { - border-radius: 3px; - margin-bottom: 20px; - padding: 1em; position: relative; + padding: 15px; + margin-bottom: 20px; border: 1px solid #eee; border-left-width: 5px; + border-radius: 3px; } .post-body .note h2, .post-body .note h3, @@ -758,17 +755,16 @@ pre .javascript .function { .post-body .note h5, .post-body .note h6 { margin-top: 0; - border-bottom: initial; margin-bottom: 0; - padding-top: 0; + border-bottom: initial; + padding-top: 0 !important; } .post-body .note p:first-child, .post-body .note ul:first-child, .post-body .note ol:first-child, .post-body .note table:first-child, .post-body .note pre:first-child, -.post-body .note blockquote:first-child, -.post-body .note img:first-child { +.post-body .note blockquote:first-child { margin-top: 0; } .post-body .note p:last-child, @@ -776,8 +772,7 @@ pre .javascript .function { .post-body .note ol:last-child, .post-body .note table:last-child, .post-body .note pre:last-child, -.post-body .note blockquote:last-child, -.post-body .note img:last-child { +.post-body .note blockquote:last-child { margin-bottom: 0; } .post-body .note.default { @@ -840,58 +835,198 @@ pre .javascript .function { .post-body .note.danger h6 { color: #d9534f; } -.pagination .prev, -.pagination .next, -.pagination .page-number, -.pagination .space { - display: inline-block; - margin: 0 10px; - padding: 0 11px; - position: relative; - top: -1px; +.post-body .label { + display: inline; + padding: 0 2px; + white-space: nowrap; } -@media (max-width: 767px) { - .pagination .prev, - .pagination .next, - .pagination .page-number, - .pagination .space { - margin: 0 5px; - } +.post-body .label.default { + background-color: #f0f0f0; } -.pagination { - border-top: 1px solid #eee; - margin: 120px 0 0; - text-align: center; +.post-body .label.primary { + background-color: #efe6f7; } -.pagination .prev, -.pagination .next, -.pagination .page-number { - border-bottom: 0; - border-top: 1px solid #eee; - transition-property: border-color; - transition-delay: 0s; - transition-duration: 0.2s; - transition-timing-function: ease-in-out; +.post-body .label.info { + background-color: #e5f2f8; } -.pagination .prev:hover, -.pagination .next:hover, -.pagination .page-number:hover { - border-top-color: #222; +.post-body .label.success { + background-color: #e7f4e9; } -.pagination .space { - margin: 0; - padding: 0; +.post-body .label.warning { + background-color: #fcf6e1; } -.pagination .prev { - margin-left: 0; +.post-body .label.danger { + background-color: #fae8eb; } -.pagination .next { +.post-body .tabs { + position: relative; + display: block; + margin-bottom: 20px; + padding-top: 10px; +} +.post-body .tabs ul.nav-tabs { + margin: 0; + padding: 0; + display: flex; + margin-bottom: -1px; +} +@media (max-width: 413px) { + .post-body .tabs ul.nav-tabs { + display: block; + margin-bottom: 5px; + } +} +.post-body .tabs ul.nav-tabs li.tab { + list-style-type: none !important; + margin: 0 0.25em 0 0; + border-top: 3px solid transparent; + border-left: 1px solid transparent; + border-right: 1px solid transparent; +} +@media (max-width: 413px) { + .post-body .tabs ul.nav-tabs li.tab { + margin: initial; + border-top: 1px solid transparent; + border-left: 3px solid transparent; + border-right: 1px solid transparent; + border-bottom: 1px solid transparent; + } +} +.post-body .tabs ul.nav-tabs li.tab a { + outline: 0; + border-bottom: initial; + display: block; + line-height: 1.8em; + padding: 0.25em 0.75em; + transition-duration: 0.2s; + transition-timing-function: ease-out; + transition-delay: 0s; +} +.post-body .tabs ul.nav-tabs li.tab a i { + width: 1.285714285714286em; +} +.post-body .tabs ul.nav-tabs li.tab.active { + border-top: 3px solid #fc6423; + border-left: 1px solid #ddd; + border-right: 1px solid #ddd; + background-color: #fff; +} +@media (max-width: 413px) { + .post-body .tabs ul.nav-tabs li.tab.active { + border-top: 1px solid #ddd; + border-left: 3px solid #fc6423; + border-right: 1px solid #ddd; + border-bottom: 1px solid #ddd; + } +} +.post-body .tabs ul.nav-tabs li.tab.active a { + cursor: default; + color: #555; +} +.post-body .tabs .tab-content { + background-color: #fff; +} +.post-body .tabs .tab-content .tab-pane { + border: 1px solid #ddd; + padding: 20px 20px 0 20px; +} +.post-body .tabs .tab-content .tab-pane:not(.active) { + display: none !important; +} +.post-body .tabs .tab-content .tab-pane.active { + display: block !important; +} +.btn { + display: inline-block; + padding: 0 20px; + font-size: 14px; + color: #fff; + background: #222; + border: 2px solid #222; + text-decoration: none; + border-radius: 0; + transition-property: background-color; + transition-duration: 0.2s; + transition-timing-function: ease-in-out; + transition-delay: 0s; + line-height: 2; +} +.btn:hover { + border-color: #222; + color: #222; + background: #fff; +} +.btn +.btn { + margin: 0 0 8px 8px; +} +.btn .fa-fw { + width: 1.285714285714286em; + text-align: left; +} +.btn-bar { + display: block; + width: 22px; + height: 2px; + background: #555; + border-radius: 1px; +} +.btn-bar+.btn-bar { + margin-top: 4px; +} +.pagination { + margin: 120px 0 40px; + text-align: center; + border-top: 1px solid #eee; +} +.page-number-basic, +.pagination .prev, +.pagination .next, +.pagination .page-number, +.pagination .space { + display: inline-block; + position: relative; + top: -1px; + margin: 0 10px; + padding: 0 11px; +} +@media (max-width: 767px) { + .page-number-basic, + .pagination .prev, + .pagination .next, + .pagination .page-number, + .pagination .space { + margin: 0 5px; + } +} +.pagination .prev, +.pagination .next, +.pagination .page-number { + border-bottom: 0; + border-top: 1px solid #eee; + transition-property: border-color; + transition-duration: 0.2s; + transition-timing-function: ease-in-out; + transition-delay: 0s; +} +.pagination .prev:hover, +.pagination .next:hover, +.pagination .page-number:hover { + border-top-color: #222; +} +.pagination .space { + padding: 0; + margin: 0; +} +.pagination .prev { + margin-left: 0; +} +.pagination .next { margin-right: 0; } .pagination .page-number.current { + color: #fff; background: #ccc; border-top-color: #ccc; - color: #fff; } @media (max-width: 767px) { .pagination { @@ -900,9 +1035,9 @@ pre .javascript .function { .pagination .prev, .pagination .next, .pagination .page-number { - border-bottom: 1px solid #eee; - border-top: 0; margin-bottom: 10px; + border-top: 0; + border-bottom: 1px solid #eee; padding: 0 10px; } .pagination .prev:hover, @@ -913,85 +1048,59 @@ pre .javascript .function { } .comments { margin: 60px 20px 0; - overflow: hidden; -} -.comment-button-group { - display: flex; - flex-wrap: wrap-reverse; - justify-content: center; - margin: 1em 0; -} -.comment-button-group .comment-button { - margin: 0.1em 0.2em; -} -.comment-button-group .comment-button.active { - background: var(--btn-default-hover-bg); - border-color: var(--btn-default-hover-border-color); - color: var(--btn-default-hover-color); -} -.comment-position { - display: none; } -.comment-position.active { - display: block; -} -.tabs-comment { - background: var(--content-bg-color); - margin-top: 4em; - padding-top: 0; -} -.tabs-comment .comments { - border: 0; - box-shadow: none; - margin-top: 0; - padding-top: 0; +.tag-cloud { + text-align: center; } -.container { - min-height: 100%; - position: relative; +.tag-cloud a { + display: inline-block; + margin: 10px; } -.main-inner { - margin: 0 auto; - width: 700px; +.back-to-top { + box-sizing: border-box; + position: fixed; + bottom: -100px; + right: 30px; + z-index: 1050; + padding: 0 6px; + width: 24px; + background: #222; + font-size: 12px; + opacity: 1; + color: #fff; + cursor: pointer; + text-align: center; + -webkit-transform: translateZ(0); + transition-property: bottom; + transition-duration: 0.2s; + transition-timing-function: ease-in-out; + transition-delay: 0s; } -@media (min-width: 1200px) { - .main-inner { - width: 800px; +@media (min-width: 768px) and (max-width: 991px) { + .back-to-top { + display: none !important; } } -@media (min-width: 1600px) { - .main-inner { - width: 900px; +@media (max-width: 767px) { + .back-to-top { + display: none !important; } } +.back-to-top.back-to-top-on { + bottom: 19px; +} .header { background: transparent; } .header-inner { - margin: 0 auto; - width: 700px; -} -@media (min-width: 1200px) { - .header-inner { - width: 800px; - } -} -@media (min-width: 1600px) { - .header-inner { - width: 900px; - } -} -.site-brand-container { - display: flex; - flex-shrink: 0; - padding: 0 10px; + position: relative; } .headband { - background: #222; height: 3px; + background: #222; } .site-meta { - flex-grow: 1; + margin: 0; text-align: center; } @media (max-width: 767px) { @@ -1000,402 +1109,816 @@ pre .javascript .function { } } .brand { - border-bottom: none; - color: var(--brand-color); + position: relative; display: inline-block; - line-height: 1.4em; padding: 0 40px; - position: relative; + color: #fff; + background: #222; + border-bottom: none; } .brand:hover { - color: var(--brand-hover-color); + color: #fff; +} +.logo { + display: inline-block; + margin-right: 5px; + line-height: 36px; + vertical-align: top; } .site-title { display: inline-block; - font-family: 'Times New Roman', 'Times New Roman', "PingFang SC", "Microsoft YaHei", sans-serif; - font-size: 1.4em; - font-weight: normal; - line-height: 1.5; vertical-align: top; + line-height: 36px; + font-size: 20px; + font-weight: normal; + font-family: 'Lato', "PingFang SC", "Microsoft YaHei", sans-serif; } .site-subtitle { + margin-top: 10px; + font-size: 13px; color: #999; - font-size: 0.8125em; - margin: 10px 0; } .use-motion .brand { opacity: 0; } +.use-motion .logo, .use-motion .site-title, -.use-motion .site-subtitle, -.use-motion .custom-logo-image { +.use-motion .site-subtitle { opacity: 0; position: relative; top: -10px; } -.site-nav-toggle, -.site-nav-right { +.site-nav-toggle { display: none; + position: absolute; + top: 10px; + left: 10px; } @media (max-width: 767px) { - .site-nav-toggle, - .site-nav-right { - display: flex; - flex-direction: column; - justify-content: center; + .site-nav-toggle { + display: block; } } -.site-nav-toggle .toggle, -.site-nav-right .toggle { - color: var(--text-color); - padding: 10px; - width: 22px; -} -.site-nav-toggle .toggle .toggle-line, -.site-nav-right .toggle .toggle-line { - background: var(--text-color); - border-radius: 1px; -} -.site-nav { - display: block; +.site-nav-toggle button { + margin-top: 2px; + padding: 9px 10px; + background: transparent; + border: none; } @media (max-width: 767px) { .site-nav { - clear: both; display: none; + margin: 0 -10px; + padding: 0 10px; + clear: both; + border-top: 1px solid #ddd; } } -.site-nav.site-nav-on { - display: block; +@media (min-width: 768px) and (max-width: 991px) { + .site-nav { + display: block !important; + } +} +@media (min-width: 992px) { + .site-nav { + display: block !important; + } } .menu { margin-top: 20px; padding-left: 0; text-align: center; } -.menu-item { +.menu .menu-item { display: inline-block; - list-style: none; margin: 0 10px; + list-style: none; } -@media (max-width: 767px) { - .menu-item { - display: block; +@media screen and (max-width: 767px) { + .menu .menu-item { margin-top: 10px; } - .menu-item.menu-item-search { - display: none; - } } -.menu-item a, -.menu-item span.exturl { - border-bottom: 0; +.menu .menu-item a { display: block; - font-size: 0.8125em; + font-size: 13px; + line-height: inherit; + border-bottom: 1px solid transparent; transition-property: border-color; - transition-delay: 0s; transition-duration: 0.2s; transition-timing-function: ease-in-out; + transition-delay: 0s; } -@media (hover: none) { - .menu-item a:hover, - .menu-item span.exturl:hover { - border-bottom-color: transparent !important; - } +.menu .menu-item a:hover { + border-bottom-color: #222; } -.menu-item .fa { - margin-right: 8px; +.menu .menu-item .fa { + margin-right: 5px; } -.menu-item .badge { - display: inline-block; - font-weight: bold; - line-height: 1; - margin-left: 0.35em; - margin-top: 0.35em; - text-align: center; - white-space: nowrap; +.use-motion .menu-item { + opacity: 0; +} +.post-body { + font-family: 'Lato', "PingFang SC", "Microsoft YaHei", sans-serif; } @media (max-width: 767px) { - .menu-item .badge { - float: right; - margin-left: 0; + .post-body { + word-break: break-word; } } -.menu-item-active a, -.menu .menu-item a:hover, -.menu .menu-item span.exturl:hover { - background: var(--menu-item-bg-color); +.post-body .fancybox img { + display: block !important; + margin: 0 auto; + cursor: pointer; + cursor: zoom-in; + cursor: -webkit-zoom-in; } -.use-motion .menu-item { +.post-body .image-caption, +.post-body .figure .caption { + margin: -20px auto 15px; + text-align: center; + font-size: 14px; + color: #999; + font-weight: bold; + line-height: 1; +} +.post-sticky-flag { + display: inline-block; + font-size: 16px; + -ms-transform: rotate(30deg); + -webkit-transform: rotate(30deg); + -moz-transform: rotate(30deg); + -ms-transform: rotate(30deg); + -o-transform: rotate(30deg); + transform: rotate(30deg); +} +.use-motion .post-block, +.use-motion .pagination, +.use-motion .comments { opacity: 0; } -.github-corner :hover .octo-arm { - animation: octocat-wave 560ms ease-in-out; +.use-motion .post-header { + opacity: 0; } -.github-corner svg { - border: 0; - color: #fff; - fill: #222; - position: absolute; - right: 0; - top: 0; - z-index: 1000; +.use-motion .post-body { + opacity: 0; } -@media (max-width: 991px) { - .github-corner { - display: none; - } - .github-corner .github-corner:hover .octo-arm { - animation: none; - } - .github-corner .github-corner .octo-arm { - animation: octocat-wave 560ms ease-in-out; - } +.use-motion .collection-title { + opacity: 0; +} +.posts-expand { + padding-top: 40px; } -@-moz-keyframes octocat-wave { - 0%, 100% { - transform: rotate(0); +@media (max-width: 767px) { + .posts-expand { + margin: 0 20px; } - 20%, 60% { - transform: rotate(-25deg); + .post-body pre .gutter pre { + padding-right: 10px; } - 40%, 80% { - transform: rotate(10deg); + .post-body .highlight { + margin-left: 0px; + margin-right: 0px; + padding: 0; + } + .post-body .highlight .gutter pre { + padding-right: 10px; } } -@-webkit-keyframes octocat-wave { - 0%, 100% { - transform: rotate(0); +@media (min-width: 992px) { + .posts-expand .post-body { + text-align: justify; } - 20%, 60% { - transform: rotate(-25deg); +} +.posts-expand .post-body h2, +.posts-expand .post-body h3, +.posts-expand .post-body h4, +.posts-expand .post-body h5, +.posts-expand .post-body h6 { + padding-top: 10px; +} +.posts-expand .post-body h2 .header-anchor, +.posts-expand .post-body h3 .header-anchor, +.posts-expand .post-body h4 .header-anchor, +.posts-expand .post-body h5 .header-anchor, +.posts-expand .post-body h6 .header-anchor { + float: right; + margin-left: 10px; + color: #ccc; + border-bottom-style: none; + visibility: hidden; +} +.posts-expand .post-body h2 .header-anchor:hover, +.posts-expand .post-body h3 .header-anchor:hover, +.posts-expand .post-body h4 .header-anchor:hover, +.posts-expand .post-body h5 .header-anchor:hover, +.posts-expand .post-body h6 .header-anchor:hover { + color: inherit; +} +.posts-expand .post-body h2:hover .header-anchor, +.posts-expand .post-body h3:hover .header-anchor, +.posts-expand .post-body h4:hover .header-anchor, +.posts-expand .post-body h5:hover .header-anchor, +.posts-expand .post-body h6:hover .header-anchor { + visibility: visible; +} +.posts-expand .post-body ul li { + list-style: circle; +} +.posts-expand .post-body img { + box-sizing: border-box; + margin: auto; + padding: 3px; + border: 1px solid #ddd; +} +.posts-expand .post-body .fancybox img { + margin: 0 auto 25px; +} +@media (max-width: 767px) { + .posts-collapse { + margin: 0 20px; } - 40%, 80% { - transform: rotate(10deg); + .posts-collapse .post-title, + .posts-collapse .post-meta { + display: block; + width: auto; + text-align: left; } } -@-o-keyframes octocat-wave { - 0%, 100% { - transform: rotate(0); +.posts-collapse { + position: relative; + z-index: 1010; + margin-left: 55px; +} +.posts-collapse::after { + content: " "; + position: absolute; + top: 20px; + left: 0; + margin-left: -2px; + width: 4px; + height: 100%; + background: #f5f5f5; + z-index: -1; +} +@media (max-width: 767px) { + .posts-collapse { + margin: 0 20px; } - 20%, 60% { - transform: rotate(-25deg); +} +.posts-collapse .collection-title { + position: relative; + margin: 60px 0; +} +.posts-collapse .collection-title h1, +.posts-collapse .collection-title h2 { + margin-left: 20px; +} +.posts-collapse .collection-title small { + color: #bbb; + margin-left: 5px; +} +.posts-collapse .collection-title::before { + content: " "; + position: absolute; + left: 0; + top: 50%; + margin-left: -4px; + margin-top: -4px; + width: 8px; + height: 8px; + background: #bbb; + border-radius: 50%; +} +.posts-collapse .post { + margin: 30px 0; +} +.posts-collapse .post-header { + position: relative; + transition-duration: 0.2s; + transition-timing-function: ease-in-out; + transition-delay: 0s; + transition-property: border; + border-bottom: 1px dashed #ccc; +} +.posts-collapse .post-header::before { + content: " "; + position: absolute; + left: 0; + top: 12px; + width: 6px; + height: 6px; + margin-left: -4px; + background: #bbb; + border-radius: 50%; + border: 1px solid #fff; + transition-duration: 0.2s; + transition-timing-function: ease-in-out; + transition-delay: 0s; + transition-property: background; +} +.posts-collapse .post-header:hover { + border-bottom-color: #666; +} +.posts-collapse .post-header:hover::before { + background: #222; +} +.posts-collapse .post-meta { + position: absolute; + font-size: 12px; + left: 20px; + top: 5px; +} +.posts-collapse .post-comments-count { + display: none; +} +.posts-collapse .post-title { + margin-left: 60px; + font-size: 16px; + font-weight: normal; + line-height: inherit; +} +.posts-collapse .post-title::after { + margin-left: 3px; + opacity: 0.6; +} +.posts-collapse .post-title a { + color: #666; + border-bottom: none; +} +.page-home .post-type-quote .post-header, +.page-post-detail .post-type-quote .post-header, +.page-home .post-type-quote .post-tags, +.page-post-detail .post-type-quote .post-tags { + display: none; +} +.posts-expand .post-title { + text-align: center; + word-break: break-word; + font-weight: 400; +} +.posts-expand .post-title-link { + display: inline-block; + position: relative; + color: #555; + border-bottom: none; + line-height: 1.2; + vertical-align: top; +} +.posts-expand .post-title-link::before { + content: ""; + position: absolute; + width: 100%; + height: 2px; + bottom: 0; + left: 0; + background-color: #000; + visibility: hidden; + -webkit-transform: scaleX(0); + -moz-transform: scaleX(0); + -ms-transform: scaleX(0); + -o-transform: scaleX(0); + transform: scaleX(0); + transition-duration: 0.2s; + transition-timing-function: ease-in-out; + transition-delay: 0s; +} +.posts-expand .post-title-link:hover::before { + visibility: visible; + -webkit-transform: scaleX(1); + -moz-transform: scaleX(1); + -ms-transform: scaleX(1); + -o-transform: scaleX(1); + transform: scaleX(1); +} +.posts-expand .post-title-link .fa { + font-size: 16px; +} +.posts-expand .post-meta { + margin: 3px 0 60px 0; + color: #999; + font-family: 'Lato', "PingFang SC", "Microsoft YaHei", sans-serif; + font-size: 12px; + text-align: center; +} +.posts-expand .post-meta .post-category-list { + display: inline-block; + margin: 0; + padding: 3px; +} +.posts-expand .post-meta .post-category-list-link { + color: #999; +} +.posts-expand .post-meta .post-description { + font-size: 14px; + margin-top: 2px; +} +.post-meta-divider { + margin: 0 0.5em; +} +.post-meta-item-icon { + margin-right: 3px; +} +@media (min-width: 768px) and (max-width: 991px) { + .post-meta-item-icon { + display: inline-block; } - 40%, 80% { - transform: rotate(10deg); +} +@media (max-width: 767px) { + .post-meta-item-icon { + display: inline-block; } } -@keyframes octocat-wave { - 0%, 100% { - transform: rotate(0); +@media (min-width: 768px) and (max-width: 991px) { + .post-meta-item-text { + display: none; } - 20%, 60% { - transform: rotate(-25deg); +} +@media (max-width: 767px) { + .post-meta-item-text { + display: none; } - 40%, 80% { - transform: rotate(10deg); +} +@media (max-width: 767px) { + .posts-expand .post-comments-count { + display: none; } } +.post-button { + margin-top: 40px; +} +.posts-expand .post-tags { + margin-top: 40px; + text-align: center; +} +.posts-expand .post-tags a { + display: inline-block; + margin-right: 10px; + font-size: 13px; +} +.post-nav { + display: table; + margin-top: 15px; + width: 100%; + border-top: 1px solid #eee; +} +.post-nav-divider { + display: table-cell; + width: 10%; +} +.post-nav-item { + display: table-cell; + padding: 10px 0 0 0; + width: 45%; + vertical-align: top; +} +.post-nav-item a { + position: relative; + display: block; + line-height: 25px; + font-size: 14px; + color: #555; + border-bottom: none; +} +.post-nav-item a:hover { + color: #222; + border-bottom: none; +} +.post-nav-item a:active { + top: 2px; +} +.post-nav-item .fa { + position: absolute; + top: 8px; + left: 0; + font-size: 12px; +} +.post-nav-next a { + padding-left: 15px; +} +.post-nav-prev { + text-align: right; +} +.post-nav-prev a { + padding-right: 15px; +} +.post-nav-prev .fa { + right: 0; + left: auto; +} +.posts-expand .post-eof { + display: block; + margin: 80px auto 60px; + width: 8%; + height: 1px; + background: #ccc; + text-align: center; +} +.post:last-child .post-eof.post-eof.post-eof { + display: none; +} +.post-gallery { + display: table; + table-layout: fixed; + width: 100%; + border-collapse: separate; +} +.post-gallery-row { + display: table-row; +} +.post-gallery .post-gallery-img { + display: table-cell; + text-align: center; + vertical-align: middle; + border: none; +} +.post-gallery .post-gallery-img img { + max-width: 100%; + max-height: 100%; + border: none; +} +.fancybox-close, +.fancybox-close:hover { + border: none; +} +.rtl.post-body p, +.rtl.post-body a, +.rtl.post-body h1, +.rtl.post-body h2, +.rtl.post-body h3, +.rtl.post-body h4, +.rtl.post-body h5, +.rtl.post-body h6, +.rtl.post-body li, +.rtl.post-body ul, +.rtl.post-body ol { + direction: rtl; + font-family: UKIJ Ekran; +} +.rtl.post-title { + font-family: UKIJ Ekran; +} .sidebar { - background: #222; - bottom: 0; - box-shadow: inset 0 2px 6px #000; position: fixed; + right: 0; top: 0; + bottom: 0; + width: 0; + z-index: 1040; + box-shadow: inset 0 2px 6px #000; + background: #222; + -webkit-transform: translateZ(0); +} +.sidebar a { + color: #999; + border-bottom-color: #555; +} +.sidebar a:hover { + color: #eee; +} +@media (min-width: 768px) and (max-width: 991px) { + .sidebar { + display: none !important; + } +} +@media (max-width: 767px) { + .sidebar { + display: none !important; + } } .sidebar-inner { + position: relative; + padding: 20px 10px; color: #999; - padding: 60px 10px; text-align: center; } -.cc-license { - margin-top: 10px; - text-align: center; +.site-overview-wrap { + overflow: hidden; } -.cc-license .cc-opacity { - border-bottom: none; - opacity: 0.7; +.site-overview { + overflow-y: auto; + overflow-x: hidden; } -.cc-license .cc-opacity:hover { - opacity: 0.9; +.sidebar-toggle { + position: fixed; + right: 30px; + bottom: 45px; + width: 14px; + height: 14px; + padding: 5px; + background: #222; + line-height: 0; + z-index: 1050; + cursor: pointer; + -webkit-transform: translateZ(0); } -.cc-license img { +@media (min-width: 768px) and (max-width: 991px) { + .sidebar-toggle { + display: none !important; + } +} +@media (max-width: 767px) { + .sidebar-toggle { + display: none !important; + } +} +.sidebar-toggle-line { + position: relative; display: inline-block; + vertical-align: top; + height: 2px; + width: 100%; + background: #fff; + margin-top: 3px; +} +.sidebar-toggle-line:first-child { + margin-top: 0; } .site-author-image { - border: 2px solid #333; display: block; margin: 0 auto; - max-width: 96px; padding: 2px; + max-width: 96px; + height: auto; + border: 2px solid #333; } .site-author-name { - color: #f5f5f5; - font-weight: normal; margin: 5px 0 0; text-align: center; + color: #f5f5f5; + font-weight: normal; } .site-description { - color: #999; - font-size: 1em; margin-top: 5px; text-align: center; + font-size: 14px; + color: #999; } -.links-of-author { - margin-top: 15px; +.site-state { + overflow: hidden; + line-height: 1.4; + white-space: nowrap; + text-align: center; } -.links-of-author a, -.links-of-author span.exturl { - border-bottom-color: #555; +.site-state-item { display: inline-block; - font-size: 0.8125em; - margin-bottom: 10px; - margin-right: 10px; - vertical-align: middle; + padding: 0 15px; + border-left: 1px solid #333; } -.links-of-author a::before, -.links-of-author span.exturl::before { - background: #debd50; - border-radius: 50%; - content: ' '; - display: inline-block; - height: 4px; - margin-right: 3px; - vertical-align: middle; - width: 4px; +.site-state-item:first-child { + border-left: none; } -.sidebar-button { - margin-top: 15px; +.site-state-item a { + border-bottom: none; } -.sidebar-button a { - border: 1px solid #fc6423; - border-radius: 4px; - color: #fc6423; +.site-state-item-count { + display: block; + text-align: center; + color: inherit; + font-weight: 600; + font-size: 18px; +} +.site-state-item-name { + font-size: 13px; + color: inherit; +} +.feed-link { + margin-top: 20px; +} +.feed-link a { display: inline-block; padding: 0 15px; + color: #fc6423; + border: 1px solid #fc6423; + border-radius: 4px; } -.sidebar-button a .fa { - margin-right: 5px; +.feed-link a i { + color: #fc6423; + font-size: 14px; } -.sidebar-button a:hover { - background: #fc6423; - border: 1px solid #fc6423; +.feed-link a:hover { color: #fff; + background: #fc6423; } -.sidebar-button a:hover .fa { +.feed-link a:hover i { color: #fff; } +.links-of-author { + margin-top: 20px; +} +.links-of-author a { + display: inline-block; + vertical-align: middle; + margin-right: 10px; + margin-bottom: 10px; + border-bottom-color: #555; + font-size: 13px; +} +.links-of-author a:before { + display: inline-block; + vertical-align: middle; + margin-right: 3px; + content: " "; + width: 4px; + height: 4px; + border-radius: 50%; + background: #ffb0ff; +} .links-of-blogroll { - font-size: 0.8125em; - margin-top: 10px; + font-size: 13px; } .links-of-blogroll-title { - font-size: 0.875em; + margin-top: 20px; + font-size: 14px; font-weight: 600; - margin-top: 0; } .links-of-blogroll-list { - list-style: none; margin: 0; padding: 0; + list-style: none; } -#sidebar-dimmer { - display: none; +.links-of-blogroll-item { + padding: 2px 10px; } -@media (max-width: 767px) { - #sidebar-dimmer { - background: #000; - display: block; - height: 100%; - left: 100%; - opacity: 0; - position: fixed; - top: 0; - width: 100%; - z-index: 1100; - } - .sidebar-active + #sidebar-dimmer { - opacity: 0.7; - transform: translateX(-100%); - transition: opacity 0.5s; - } +.links-of-blogroll-item a { + max-width: 280px; + box-sizing: border-box; + display: inline-block; + overflow: hidden; + white-space: nowrap; + text-overflow: ellipsis; } .sidebar-nav { - margin: 0; - padding-bottom: 20px; + margin: 0 0 20px; padding-left: 0; } .sidebar-nav li { + display: inline-block; + cursor: pointer; border-bottom: 1px solid transparent; + font-size: 14px; color: #555; - cursor: pointer; - display: inline-block; - font-size: 0.875em; -} -.sidebar-nav li.sidebar-nav-overview { - margin-left: 10px; } .sidebar-nav li:hover { color: #f5f5f5; } +.page-post-detail .sidebar-nav-toc { + padding: 0 5px; +} +.page-post-detail .sidebar-nav-overview { + margin-left: 10px; +} .sidebar-nav .sidebar-nav-active { - border-bottom-color: #87daff; color: #87daff; + border-bottom-color: #87daff; } .sidebar-nav .sidebar-nav-active:hover { color: #87daff; } .sidebar-panel { display: none; - overflow-x: hidden; - overflow-y: auto; } .sidebar-panel-active { display: block; } -.sidebar-toggle { - background: #222; - bottom: 45px; - cursor: pointer; - height: 14px; - left: 30px; - padding: 5px; - position: fixed; - width: 14px; - z-index: 1300; -} -@media (max-width: 991px) { - .sidebar-toggle { - left: 20px; - opacity: 0.8; - } +.post-toc-empty { + font-size: 14px; + color: #666; } -.sidebar-toggle:hover .toggle-line { - background: #87daff; +.post-toc-wrap { + overflow: hidden; } .post-toc { - font-size: 0.875em; + overflow: auto; } .post-toc ol { - list-style: none; margin: 0; padding: 0 2px 5px 10px; text-align: left; + list-style: none; + font-size: 14px; } .post-toc ol > ol { padding-left: 0; } .post-toc ol a { - transition-property: all; - transition-delay: 0s; transition-duration: 0.2s; transition-timing-function: ease-in-out; + transition-delay: 0s; + transition-property: all; + color: #999; + border-bottom-color: #555; +} +.post-toc ol a:hover { + color: #ccc; + border-bottom-color: #ccc; } .post-toc .nav-item { - line-height: 1.8; overflow: hidden; text-overflow: ellipsis; + text-align: justify; white-space: nowrap; + line-height: 1.8; } .post-toc .nav .nav-child { display: none; @@ -1410,8 +1933,8 @@ pre .javascript .function { display: block; } .post-toc .nav .active > a { - border-bottom-color: #87daff; color: #87daff; + border-bottom-color: #87daff; } .post-toc .nav .active-current > a { color: #87daff; @@ -1419,63 +1942,17 @@ pre .javascript .function { .post-toc .nav .active-current > a:hover { color: #87daff; } -.site-state { - display: flex; - justify-content: center; - line-height: 1.4; - margin-top: 10px; - overflow: hidden; - text-align: center; - white-space: nowrap; -} -.site-state-item { - padding: 0 15px; -} -.site-state-item:not(:first-child) { - border-left: 1px solid #333; -} -.site-state-item a { - border-bottom: none; -} -.site-state-item-count { - display: block; - font-size: 1.25em; - font-weight: 600; - text-align: center; -} -.site-state-item-name { - color: inherit; - font-size: 0.875em; -} .footer { + font-size: 14px; color: #999; - font-size: 0.875em; - padding: 20px 0; } -.footer.footer-fixed { - bottom: 0; - left: 0; - position: absolute; - right: 0; +.footer img { + border: none; } .footer-inner { - box-sizing: border-box; - margin: 0 auto; text-align: center; - width: 700px; -} -@media (min-width: 1200px) { - .footer-inner { - width: 800px; - } -} -@media (min-width: 1600px) { - .footer-inner { - width: 900px; - } } .with-love { - color: #808080; display: inline-block; margin: 0 5px; } @@ -1483,564 +1960,429 @@ pre .javascript .function { .theme-info { display: inline-block; } -@-moz-keyframes iconAnimate { - 0%, 100% { - transform: scale(1); - } - 10%, 30% { - transform: scale(0.9); - } - 20%, 40%, 60%, 80% { - transform: scale(1.1); - } - 50%, 70% { - transform: scale(1.1); - } -} -@-webkit-keyframes iconAnimate { - 0%, 100% { - transform: scale(1); - } - 10%, 30% { - transform: scale(0.9); - } - 20%, 40%, 60%, 80% { - transform: scale(1.1); - } - 50%, 70% { - transform: scale(1.1); - } -} -@-o-keyframes iconAnimate { - 0%, 100% { - transform: scale(1); - } - 10%, 30% { - transform: scale(0.9); - } - 20%, 40%, 60%, 80% { - transform: scale(1.1); - } - 50%, 70% { - transform: scale(1.1); - } -} -@keyframes iconAnimate { - 0%, 100% { - transform: scale(1); - } - 10%, 30% { - transform: scale(0.9); - } - 20%, 40%, 60%, 80% { - transform: scale(1.1); - } - 50%, 70% { - transform: scale(1.1); - } -} -.back-to-top { - font-size: 12px; +.cc-license { + margin-top: 10px; text-align: center; - transition-delay: 0s; - transition-duration: 0.2s; - transition-timing-function: ease-in-out; -} -.back-to-top { - background: #222; - bottom: -100px; - box-sizing: border-box; - color: #fff; - cursor: pointer; - left: 30px; - opacity: 1; - padding: 0 6px; - position: fixed; - transition-property: bottom; - z-index: 1300; - width: 24px; -} -.back-to-top span { - display: none; -} -.back-to-top:hover { - color: #87daff; -} -.back-to-top.back-to-top-on { - bottom: 19px; -} -@media (max-width: 991px) { - .back-to-top { - left: 20px; - opacity: 0.8; - } -} -.post-body { - font-family: 'Times New Roman', "PingFang SC", "Microsoft YaHei", sans-serif; - overflow-wrap: break-word; - word-wrap: break-word; -} -@media (min-width: 1200px) { - .post-body { - font-size: 1.125em; - } } -.post-body .exturl .fa { - font-size: 0.875em; - margin-left: 4px; +.cc-license .cc-opacity { + opacity: 0.7; + border-bottom: none; } -.post-body .image-caption, -.post-body .figure .caption { - color: #999; - font-size: 0.875em; - font-weight: bold; - line-height: 1; - margin: -20px auto 15px; - text-align: center; +.cc-license .cc-opacity:hover { + opacity: 0.9; } -.post-sticky-flag { +.cc-license img { display: inline-block; - transform: rotate(30deg); -} -.post-button { - margin-top: 40px; - text-align: center; -} -.use-motion .post-block, -.use-motion .pagination, -.use-motion .comments { - opacity: 0; -} -.use-motion .post-header { - opacity: 0; -} -.use-motion .post-body { - opacity: 0; -} -.use-motion .collection-header { - opacity: 0; -} -.posts-collapse { - margin-left: 35px; - margin-top: 35px; - position: relative; } -@media (max-width: 767px) { - .posts-collapse { - margin-left: 20px; - margin-right: 20px; - } -} -.posts-collapse .collection-title { - font-size: 1.125em; - position: relative; +.theme-next #ds-thread #ds-reset { + color: #555; } -.posts-collapse .collection-title::before { - background: #999; - border: 1px solid #fff; - border-radius: 50%; - content: ' '; - height: 10px; - left: 0; - margin-left: -6px; - margin-top: -4px; - position: absolute; - top: 50%; - width: 10px; +.theme-next #ds-thread #ds-reset .ds-replybox { + margin-bottom: 30px; } -.posts-collapse .collection-year { - margin: 60px 0; - position: relative; +.theme-next #ds-thread #ds-reset .ds-replybox .ds-avatar, +.theme-next #ds-reset .ds-avatar img { + box-shadow: none; } -.posts-collapse .collection-year::before { - background: #bbb; - border-radius: 50%; - content: ' '; - height: 8px; - left: 0; - margin-left: -4px; - margin-top: -4px; - position: absolute; - top: 50%; - width: 8px; +.theme-next #ds-thread #ds-reset .ds-textarea-wrapper { + border-color: #c7d4e1; + background: none; + border-top-right-radius: 3px; + border-top-left-radius: 3px; } -.posts-collapse .collection-header { - display: inline-block; - margin: 0 0 0 20px; +.theme-next #ds-thread #ds-reset .ds-textarea-wrapper textarea { + height: 60px; } -.posts-collapse .collection-header small { - color: #bbb; - margin-left: 5px; +.theme-next #ds-reset .ds-rounded-top { + border-radius: 0; } -.posts-collapse .post-header { - border-bottom: 1px dashed #ccc; - margin: 30px 0; - padding-left: 15px; - position: relative; - transition-property: border; - transition-delay: 0s; - transition-duration: 0.2s; - transition-timing-function: ease-in-out; +.theme-next #ds-thread #ds-reset .ds-post-toolbar { + box-sizing: border-box; + border: 1px solid #c7d4e1; + background: #f6f8fa; } -.posts-collapse .post-header::before { - background: #bbb; - border: 1px solid #fff; - border-radius: 50%; - content: ' '; - height: 6px; - left: 0; - margin-left: -4px; - position: absolute; - top: 0.75em; - transition-property: background; - width: 6px; - transition-delay: 0s; - transition-duration: 0.2s; - transition-timing-function: ease-in-out; +.theme-next #ds-thread #ds-reset .ds-post-options { + height: 40px; + border: none; + background: none; } -.posts-collapse .post-header:hover { - border-bottom-color: #666; +.theme-next #ds-thread #ds-reset .ds-toolbar-buttons { + top: 11px; } -.posts-collapse .post-header:hover::before { - background: #222; +.theme-next #ds-thread #ds-reset .ds-sync { + top: 5px; } -.posts-collapse .post-meta { - display: inline; - font-size: 0.75em; - margin-right: 10px; +.theme-next #ds-thread #ds-reset .ds-post-button { + top: 4px; + right: 5px; + width: 90px; + height: 30px; + border: 1px solid #c5ced7; + border-radius: 3px; + background-image: linear-gradient(#fbfbfc, #f5f7f9); + color: #60676d; } -.posts-collapse .post-title { - display: inline; - font-size: 1em; - font-weight: normal; +.theme-next #ds-thread #ds-reset .ds-post-button:hover { + background-position: 0 -30px; + color: #60676d; } -.posts-collapse .post-title a, -.posts-collapse .post-title span.exturl { - border-bottom: none; - color: var(--link-color); +.theme-next #ds-thread #ds-reset .ds-comments-info { + padding: 10px 0; } -.posts-collapse::before { - background: #f5f5f5; - content: ' '; - height: 100%; - left: 0; - margin-left: -2px; - position: absolute; - top: 1.25em; - width: 4px; +.theme-next #ds-thread #ds-reset .ds-sort { + display: none; } -.posts-collapse .fa-external-link { - font-size: 0.875em; - margin-left: 5px; +.theme-next #ds-thread #ds-reset li.ds-tab a.ds-current { + border: none; + background: #f6f8fa; + color: #60676d; } -.post-eof { - background: #ccc; - height: 1px; - margin: 80px auto 60px; - text-align: center; - width: 8%; +.theme-next #ds-thread #ds-reset li.ds-tab a.ds-current:hover { + background-color: #e9f0f7; + color: #60676d; } -.post-block:last-child .post-eof { - display: none; +.theme-next #ds-thread #ds-reset li.ds-tab a { + border-radius: 2px; + padding: 5px; } -.posts-expand { - padding-top: 40px; +.theme-next #ds-thread #ds-reset .ds-login-buttons p { + color: #999; + line-height: 36px; } -@media (max-width: 767px) { - .posts-expand { - margin: 0 20px; - } +.theme-next #ds-thread #ds-reset .ds-login-buttons .ds-service-list li { + height: 28px; } -@media (min-width: 992px) { - .post-body { - text-align: justify; - } +.theme-next #ds-thread #ds-reset .ds-service-list a { + background: none; + padding: 5px; + border: 1px solid; + border-radius: 3px; + text-align: center; } -@media (max-width: 991px) { - .post-body { - text-align: justify; - } +.theme-next #ds-thread #ds-reset .ds-service-list a:hover { + color: #fff; + background: #666; } -.post-body h1, -.post-body h2, -.post-body h3, -.post-body h4, -.post-body h5, -.post-body h6 { - padding-top: 10px; +.theme-next #ds-thread #ds-reset .ds-service-list .ds-weibo { + color: #fc9b00; + border-color: #fc9b00; } -.post-body h1 .header-anchor, -.post-body h2 .header-anchor, -.post-body h3 .header-anchor, -.post-body h4 .header-anchor, -.post-body h5 .header-anchor, -.post-body h6 .header-anchor { - border-bottom-style: none; - color: #ccc; - float: right; - margin-left: 10px; - visibility: hidden; +.theme-next #ds-thread #ds-reset .ds-service-list .ds-weibo:hover { + background: #fc9b00; } -.post-body h1 .header-anchor:hover, -.post-body h2 .header-anchor:hover, -.post-body h3 .header-anchor:hover, -.post-body h4 .header-anchor:hover, -.post-body h5 .header-anchor:hover, -.post-body h6 .header-anchor:hover { - color: inherit; +.theme-next #ds-thread #ds-reset .ds-service-list .ds-qq { + color: #60a3ec; + border-color: #60a3ec; } -.post-body h1:hover .header-anchor, -.post-body h2:hover .header-anchor, -.post-body h3:hover .header-anchor, -.post-body h4:hover .header-anchor, -.post-body h5:hover .header-anchor, -.post-body h6:hover .header-anchor { - visibility: visible; +.theme-next #ds-thread #ds-reset .ds-service-list .ds-qq:hover { + background: #60a3ec; } -.post-body iframe, -.post-body img, -.post-body video { - margin-bottom: 20px; +.theme-next #ds-thread #ds-reset .ds-service-list .ds-renren { + color: #2e7ac4; + border-color: #2e7ac4; } -.post-body .video-container { - height: 0; - margin-bottom: 20px; - overflow: hidden; - padding-top: 75%; - position: relative; - width: 100%; +.theme-next #ds-thread #ds-reset .ds-service-list .ds-renren:hover { + background: #2e7ac4; } -.post-body .video-container iframe, -.post-body .video-container object, -.post-body .video-container embed { - height: 100%; - left: 0; - margin: 0; - position: absolute; - top: 0; - width: 100%; +.theme-next #ds-thread #ds-reset .ds-service-list .ds-douban { + color: #37994c; + border-color: #37994c; } -.post-gallery { - align-items: center; - display: grid; - grid-gap: 10px; - grid-template-columns: 1fr 1fr 1fr; - margin-bottom: 20px; +.theme-next #ds-thread #ds-reset .ds-service-list .ds-douban:hover { + background: #37994c; } -@media (max-width: 767px) { - .post-gallery { - grid-template-columns: 1fr 1fr; - } +.theme-next #ds-thread #ds-reset .ds-service-list .ds-kaixin { + color: #fef20d; + border-color: #fef20d; } -.post-gallery a { - border: 0; +.theme-next #ds-thread #ds-reset .ds-service-list .ds-kaixin:hover { + background: #fef20d; } -.post-gallery img { - margin: 0; +.theme-next #ds-thread #ds-reset .ds-service-list .ds-netease { + color: #f00; + border-color: #f00; } -.posts-expand .post-header { - font-size: 1.125em; +.theme-next #ds-thread #ds-reset .ds-service-list .ds-netease:hover { + background: #f00; } -.posts-expand .post-title { - font-weight: normal; - margin: initial; - text-align: center; - overflow-wrap: break-word; - word-wrap: break-word; +.theme-next #ds-thread #ds-reset .ds-service-list .ds-sohu { + color: #ffcb05; + border-color: #ffcb05; } -.posts-expand .post-title-link { - border-bottom: none; - color: var(--link-color); - display: inline-block; - position: relative; - vertical-align: top; +.theme-next #ds-thread #ds-reset .ds-service-list .ds-sohu:hover { + background: #ffcb05; } -.posts-expand .post-title-link::before { - background: var(--link-color); - bottom: 0; - content: ''; - height: 2px; - left: 0; - position: absolute; - transform: scaleX(0); - visibility: hidden; - width: 100%; - transition-delay: 0s; - transition-duration: 0.2s; - transition-timing-function: ease-in-out; +.theme-next #ds-thread #ds-reset .ds-service-list .ds-baidu { + color: #2831e0; + border-color: #2831e0; } -.posts-expand .post-title-link:hover::before { - transform: scaleX(1); - visibility: visible; +.theme-next #ds-thread #ds-reset .ds-service-list .ds-baidu:hover { + background: #2831e0; } -.posts-expand .post-title-link .fa { - font-size: 0.875em; - margin-left: 5px; +.theme-next #ds-thread #ds-reset .ds-service-list .ds-google { + color: #166bec; + border-color: #166bec; } -.posts-expand .post-meta { - color: #999; - font-family: 'Times New Roman', "PingFang SC", "Microsoft YaHei", sans-serif; - font-size: 0.75em; - margin: 3px 0 60px 0; - text-align: center; +.theme-next #ds-thread #ds-reset .ds-service-list .ds-google:hover { + background: #166bec; } -.posts-expand .post-meta .post-description { - font-size: 0.875em; - margin-top: 2px; +.theme-next #ds-thread #ds-reset .ds-service-list .ds-weixin { + color: #00ce0d; + border-color: #00ce0d; } -.posts-expand .post-meta time { - border-bottom: 1px dashed #999; - cursor: pointer; +.theme-next #ds-thread #ds-reset .ds-service-list .ds-weixin:hover { + background: #00ce0d; } -.post-meta .post-meta-item + .post-meta-item::before { - content: '|'; - margin: 0 0.5em; +.theme-next #ds-thread #ds-reset .ds-service-list .ds-more-services { + border: none; } -.post-meta-divider { - margin: 0 0.5em; +.theme-next #ds-thread #ds-reset .ds-service-list .ds-more-services:hover { + background: none; } -.post-meta-item-icon { +.theme-next #ds-reset .duoshuo-ua-admin { + display: inline-block; + color: #f00; +} +.theme-next #ds-reset .duoshuo-ua-platform, +.theme-next #ds-reset .duoshuo-ua-browser { + color: #ccc; +} +.theme-next #ds-reset .duoshuo-ua-platform .fa, +.theme-next #ds-reset .duoshuo-ua-browser .fa { + display: inline-block; margin-right: 3px; } -@media (max-width: 991px) { - .post-meta-item-icon { - display: inline-block; - } +.theme-next #ds-reset .duoshuo-ua-separator { + display: inline-block; + margin-left: 5px; } -@media (max-width: 991px) { - .post-meta-item-text { - display: none; - } +.theme-next .this_ua { + background-color: #ccc !important; + border-radius: 4px; + padding: 0 5px !important; + margin: 1px 1px !important; + border: 1px solid #bbb !important; + color: #fff; + display: inline-block !important; +} +.theme-next .this_ua.admin { + background-color: #d9534f !important; + border-color: #d9534f !important; +} +.theme-next .this_ua.platform.iOS, +.theme-next .this_ua.platform.Mac, +.theme-next .this_ua.platform.Windows { + background-color: #39b3d7 !important; + border-color: #46b8da !important; +} +.theme-next .this_ua.platform.Linux { + background-color: #3a3a3a !important; + border-color: #1f1f1f !important; +} +.theme-next .this_ua.platform.Android { + background-color: #00c47d !important; + border-color: #01b171 !important; +} +.theme-next .this_ua.browser.Mobile, +.theme-next .this_ua.browser.Chrome { + background-color: #5cb85c !important; + border-color: #4cae4c !important; +} +.theme-next .this_ua.browser.Firefox { + background-color: #f0ad4e !important; + border-color: #eea236 !important; +} +.theme-next .this_ua.browser.Maxthon, +.theme-next .this_ua.browser.IE { + background-color: #428bca !important; + border-color: #357ebd !important; +} +.theme-next .this_ua.browser.baidu, +.theme-next .this_ua.browser.UCBrowser, +.theme-next .this_ua.browser.Opera { + background-color: #d9534f !important; + border-color: #d43f3a !important; +} +.theme-next .this_ua.browser.Android, +.theme-next .this_ua.browser.QQBrowser { + background-color: #78ace9 !important; + border-color: #4cae4c !important; +} +.post-spread { + margin-top: 20px; + text-align: center; } -.post-nav { - border-top: 1px solid #eee; - display: flex; - justify-content: space-between; - margin-top: 15px; - padding-top: 10px; +.jiathis_style { + display: inline-block; } -.post-nav-item { - flex: 1; +.jiathis_style a { + border: none; } -.post-nav-item a { - border-bottom: none; - display: block; - font-size: 0.875em; - line-height: 1.6; - position: relative; +.fa { + font-family: FontAwesome !important; } -.post-nav-item a:active { - top: 2px; +.post-spread { + margin-top: 20px; + text-align: center; } -.post-nav-item .fa { - font-size: 0.75em; +.bdshare-slide-button-box a { + border: none; } -.post-nav-item:first-child { - margin-right: 15px; +.bdsharebuttonbox { + display: inline-block; } -.post-nav-item:first-child a { - padding-left: 5px; +.bdsharebuttonbox a { + border: none; } -.post-nav-item:first-child .fa { - margin-right: 5px; +.local-search-pop-overlay { + position: fixed; + width: 100%; + height: 100%; + top: 0; + left: 0; + z-index: 2080; + background-color: rgba(0,0,0,0.3); } -.post-nav-item:last-child { - margin-left: 15px; - text-align: right; +.local-search-popup { + display: none; + position: fixed; + top: 10%; + left: 50%; + margin-left: -350px; + width: 700px; + height: 80%; + padding: 0; + background: #fff; + color: #333; + z-index: 9999; + border-radius: 5px; } -.post-nav-item:last-child a { - padding-right: 5px; +@media (max-width: 767px) { + .local-search-popup { + padding: 0; + top: 0; + left: 0; + margin: 0; + width: 100%; + height: 100%; + border-radius: 0; + } } -.post-nav-item:last-child .fa { - margin-left: 5px; +.local-search-popup ul.search-result-list { + padding: 0; + margin: 0 5px; } -.rtl.post-body p, -.rtl.post-body a, -.rtl.post-body h1, -.rtl.post-body h2, -.rtl.post-body h3, -.rtl.post-body h4, -.rtl.post-body h5, -.rtl.post-body h6, -.rtl.post-body li, -.rtl.post-body ul, -.rtl.post-body ol { - direction: rtl; - font-family: UKIJ Ekran; +.local-search-popup p.search-result { + border-bottom: 1px dashed #ccc; + padding: 5px 0; } -.rtl.post-title { - font-family: UKIJ Ekran; +.local-search-popup a.search-result-title { + font-weight: bold; + font-size: 16px; } -.post-tags { - margin-top: 40px; - text-align: center; +.local-search-popup .search-keyword { + border-bottom: 1px dashed #f00; + font-weight: bold; + color: #f00; } -.post-tags a { - display: inline-block; - font-size: 0.8125em; +.local-search-popup .local-search-header { + padding: 5px; + height: 36px; + background: #f5f5f5; + border-top-left-radius: 5px; + border-top-right-radius: 5px; } -.post-tags a:not(:last-child) { - margin-right: 10px; +.local-search-popup #local-search-result { + overflow: auto; + position: relative; + padding: 5px 25px; + height: calc(100% - 55px); } -.post-widgets { - border-top: 1px solid #eee; - margin-top: 15px; - text-align: center; +.local-search-popup .local-search-input-wrapper { + display: inline-block; + width: calc(100% - 90px); + height: 36px; + line-height: 36px; + padding: 0 5px; } -.wp_rating { +.local-search-popup .local-search-input-wrapper input { + padding: 8px 0; height: 20px; - line-height: 20px; - margin-top: 10px; - padding-top: 6px; - text-align: center; + display: block; + width: 100%; + outline: none; + border: none; + background: transparent; + vertical-align: middle; } -.social-like { - display: flex; - font-size: 0.875em; - justify-content: center; - text-align: center; +.local-search-popup .search-icon, +.local-search-popup .popup-btn-close { + display: inline-block; + font-size: 18px; + color: #999; + height: 36px; + width: 18px; + padding-left: 10px; + padding-right: 10px; } -.reward-container { - margin: 20px auto; - padding: 10px 0; - text-align: center; - width: 90%; +.local-search-popup .search-icon { + float: left; } -.reward-container button { - background: #ff2a2a; - border: 0; - border-radius: 5px; - color: #fff; +.local-search-popup .popup-btn-close { + border-left: 1px solid #eee; + float: right; cursor: pointer; - line-height: 2; - outline: 0; - padding: 0 15px; - vertical-align: text-top; } -.reward-container button:hover { - background: #f55; +.local-search-popup #no-result { + position: absolute; + left: 50%; + top: 50%; + -webkit-transform: translate(-50%, -50%); + -webkit-transform: translate(-50%, -50%); + -moz-transform: translate(-50%, -50%); + -ms-transform: translate(-50%, -50%); + -o-transform: translate(-50%, -50%); + transform: translate(-50%, -50%); + color: #ccc; } -#qr { - padding-top: 20px; +.site-uv, +.site-pv, +.page-pv { + display: inline-block; } -#qr a { - border: 0; +.site-uv .busuanzi-value, +.site-pv .busuanzi-value, +.page-pv .busuanzi-value { + margin: 0 5px; } -#qr img { - display: inline-block; - margin: 0.8em 2em 0 2em; - max-width: 100%; - width: 180px; +.site-uv { + margin-right: 10px; } -#qr p { - text-align: center; +.site-uv::after { + content: "|"; + padding-left: 10px; } -.post-copyright { - background: var(--card-bg-color); - border-left: 3px solid #ff2a2a; - list-style: none; - margin: 2em 0 0; - padding: 0.5em 1em; +.page-archive .archive-page-counter { + position: relative; + top: 3px; + left: 20px; +} +@media (max-width: 767px) { + .page-archive .archive-page-counter { + top: 5px; + } +} +.page-archive .posts-collapse .archive-move-on { + position: absolute; + top: 11px; + left: 0; + margin-left: -6px; + width: 10px; + height: 10px; + opacity: 0.5; + background: #555; + border: 1px solid #fff; + border-radius: 50%; } .category-all-page .category-all-title { text-align: center; @@ -2049,9 +2391,9 @@ pre .javascript .function { margin-top: 20px; } .category-all-page .category-list { - list-style: none; margin: 0; padding: 0; + list-style: none; } .category-all-page .category-list-item { margin: 5px 10px; @@ -2059,313 +2401,232 @@ pre .javascript .function { .category-all-page .category-list-count { color: #bbb; } -.category-all-page .category-list-count::before { - content: ' ('; +.category-all-page .category-list-count:before { display: inline; + content: " ("; } -.category-all-page .category-list-count::after { - content: ') '; +.category-all-page .category-list-count:after { display: inline; + content: ") "; } .category-all-page .category-list-child { padding-left: 10px; } -.event-list { - padding: 0; +#schedule ul#event-list { + padding-left: 30px; } -.event-list hr { +#schedule ul#event-list hr { + margin: 20px 0 45px 0 !important; background: #222; - margin: 20px 0 45px 0; } -.event-list hr::after { +#schedule ul#event-list hr:after { + display: inline-block; + content: 'NOW'; background: #222; color: #fff; - content: 'NOW'; - display: inline-block; font-weight: bold; - padding: 0 5px; text-align: right; + padding: 0 5px; } -.event-list .event { - background: #222; - margin: 20px 0; +#schedule ul#event-list li.event { + margin: 20px 0px; + background: #f9f9f9; + padding-left: 10px; min-height: 40px; - padding: 15px 0 15px 10px; } -.event-list .event .event-summary { - color: #fff; +#schedule ul#event-list li.event h2.event-summary { margin: 0; padding-bottom: 3px; } -.event-list .event .event-summary::before { - animation: dot-flash 1s alternate infinite ease-in-out; - color: #fff; - content: '\f111'; +#schedule ul#event-list li.event h2.event-summary:before { display: inline-block; - font-family: 'FontAwesome'; - font-size: 10px; - margin-right: 25px; + font-family: FontAwesome; + font-size: 8px; + content: '\f111'; vertical-align: middle; -} -.event-list .event .event-relative-time { + margin-right: 25px; color: #bbb; +} +#schedule ul#event-list li.event span.event-relative-time { display: inline-block; font-size: 12px; - font-weight: normal; + font-weight: 400; padding-left: 12px; + color: #bbb; } -.event-list .event .event-details { - color: #fff; +#schedule ul#event-list li.event span.event-details { display: block; - line-height: 18px; + color: #bbb; margin-left: 56px; - padding-bottom: 6px; padding-top: 3px; + padding-bottom: 6px; text-indent: -24px; + line-height: 18px; } -.event-list .event .event-details::before { - color: #fff; - display: inline-block; - font-family: 'FontAwesome'; - margin-right: 9px; - text-align: center; +#schedule ul#event-list li.event span.event-details:before { text-indent: 0; + display: inline-block; width: 14px; + font-family: FontAwesome; + text-align: center; + margin-right: 9px; + color: #bbb; } -.event-list .event .event-details.event-location::before { +#schedule ul#event-list li.event span.event-details.event-location:before { content: '\f041'; } -.event-list .event .event-details.event-duration::before { +#schedule ul#event-list li.event span.event-details.event-duration:before { content: '\f017'; } -.event-list .event-past { - background: #f5f5f5; +#schedule ul#event-list li.event-past { + background: #fcfcfc; } -.event-list .event-past .event-summary, -.event-list .event-past .event-details { - color: #bbb; - opacity: 0.9; +#schedule ul#event-list li.event-past > * { + opacity: 0.6; } -.event-list .event-past .event-summary::before, -.event-list .event-past .event-details::before { - animation: none; +#schedule ul#event-list li.event-past h2.event-summary { color: #bbb; } +#schedule ul#event-list li.event-past h2.event-summary:before { + color: #dfdfdf; +} +#schedule ul#event-list li.event-now { + background: #222; + color: #fff; + padding: 15px 0 15px 10px; +} +#schedule ul#event-list li.event-now h2.event-summary:before { + -webkit-transform: scale(1.2); + -moz-transform: scale(1.2); + -ms-transform: scale(1.2); + -o-transform: scale(1.2); + transform: scale(1.2); + color: #fff; + animation: dot-flash 1s alternate infinite ease-in-out; +} +#schedule ul#event-list li.event-now * { + color: #fff !important; +} @-moz-keyframes dot-flash { from { opacity: 1; - transform: scale(1); + -webkit-transform: scale(1.1); + -moz-transform: scale(1.1); + -ms-transform: scale(1.1); + -o-transform: scale(1.1); + transform: scale(1.1); } to { opacity: 0; - transform: scale(0.8); + -webkit-transform: scale(1); + -moz-transform: scale(1); + -ms-transform: scale(1); + -o-transform: scale(1); + transform: scale(1); } } @-webkit-keyframes dot-flash { from { opacity: 1; - transform: scale(1); + -webkit-transform: scale(1.1); + -moz-transform: scale(1.1); + -ms-transform: scale(1.1); + -o-transform: scale(1.1); + transform: scale(1.1); } to { opacity: 0; - transform: scale(0.8); + -webkit-transform: scale(1); + -moz-transform: scale(1); + -ms-transform: scale(1); + -o-transform: scale(1); + transform: scale(1); } } @-o-keyframes dot-flash { from { opacity: 1; - transform: scale(1); + -webkit-transform: scale(1.1); + -moz-transform: scale(1.1); + -ms-transform: scale(1.1); + -o-transform: scale(1.1); + transform: scale(1.1); } to { opacity: 0; - transform: scale(0.8); + -webkit-transform: scale(1); + -moz-transform: scale(1); + -ms-transform: scale(1); + -o-transform: scale(1); + transform: scale(1); } } @keyframes dot-flash { from { opacity: 1; - transform: scale(1); + -webkit-transform: scale(1.1); + -moz-transform: scale(1.1); + -ms-transform: scale(1.1); + -o-transform: scale(1.1); + transform: scale(1.1); } to { opacity: 0; - transform: scale(0.8); - } -} -ul.breadcrumb { - font-size: 0.75em; - list-style: none; - margin: 1em 0; - padding: 0 2em; - text-align: center; -} -ul.breadcrumb li { - display: inline; -} -ul.breadcrumb li + li::before { - content: '/\00a0'; - font-weight: normal; - padding: 0.5em; -} -ul.breadcrumb li + li:last-child { - font-weight: bold; -} -.tag-cloud { - text-align: center; -} -.tag-cloud a { - display: inline-block; - margin: 10px; -} -.tag-cloud a:hover { - color: #222 !important; -} -.search-pop-overlay { - background: rgba(0,0,0,0.3); - display: none; - height: 100%; - left: 0; - position: fixed; - top: 0; - width: 100%; - z-index: 1400; -} -.search-popup { - background: var(--card-bg-color); - border-radius: 5px; - height: 80%; - left: calc(50% - 350px); - position: fixed; - top: 10%; - width: 700px; - z-index: 1500; -} -@media (max-width: 767px) { - .search-popup { - border-radius: 0; - height: 100%; - left: 0; - margin: 0; - top: 0; - width: 100%; + -webkit-transform: scale(1); + -moz-transform: scale(1); + -ms-transform: scale(1); + -o-transform: scale(1); + transform: scale(1); } } -.search-popup .search-icon, -.search-popup .popup-btn-close { - color: #999; - font-size: 18px; - padding: 0 10px; -} -.search-popup .popup-btn-close { - cursor: pointer; -} -.search-popup .popup-btn-close:hover .fa { - color: #222; -} -.search-popup .search-header { - background: #eee; - border-top-left-radius: 5px; - border-top-right-radius: 5px; - display: flex; - padding: 5px; -} -.site-search input.search-input { - background: transparent; - border: 0; - outline: 0; - width: 100%; -} -.site-search input.search-input::-webkit-search-cancel-button { - display: none; -} -.search-popup .search-input-container { - flex-grow: 1; - padding: 2px; -} -.search-popup ul.search-result-list { - margin: 0 5px; - padding: 0; - width: 100%; -} -.search-popup p.search-result { - border-bottom: 1px dashed #ccc; - padding: 5px 0; -} -.search-popup a.search-result-title { - font-weight: bold; -} -.search-popup .search-keyword { - border-bottom: 1px dashed #ff2a2a; - color: #ff2a2a; - font-weight: bold; -} -.search-popup #search-result { - display: flex; - height: calc(100% - 55px); - overflow: auto; - padding: 5px 25px; +.page-post-detail .sidebar-toggle-line { + background: #87daff; } -.search-popup #no-result { - color: #ccc; - margin: auto; +.page-post-detail .comments { + overflow: hidden; } @media (max-width: 767px) { .header-inner, - .main-inner, + .container .main-inner, .footer-inner { width: auto; } } -.header-inner { - padding-top: 100px; -} -@media (max-width: 767px) { - .header-inner { - padding-top: 50px; - } -} -.main-inner { - max-width: 45em; - padding-bottom: 60px; -} -.content { - padding-top: 70px; -} -@media (max-width: 767px) { - .content { - padding-top: 35px; - } -} embed { display: block; - margin: 0 auto 25px auto; + margin: 0px auto 25px auto; } .custom-logo .site-meta-headline { text-align: center; } +.custom-logo .brand { + background: none; +} .custom-logo .site-title { - color: #222; margin: 10px auto 0; + font-size: 24px; + color: #222; } .custom-logo .site-title a { - border: 0; + border: none; } .custom-logo-image { - background: #fff; margin: 0 auto; - max-width: 150px; padding: 5px; -} -.brand { - background: var(--btn-default-bg); + max-width: 150px; + background: #fff; } @media (max-width: 767px) { .site-nav { - border-bottom: 1px solid #ddd; - border-top: 1px solid #ddd; + position: absolute; left: 0; + top: 52px; margin: 0; - padding: 0; width: 100%; + padding: 0; + background: #fff; + border-bottom: 1px solid #ddd; + z-index: 1030; } } @media (max-width: 767px) { @@ -2373,106 +2634,29 @@ embed { text-align: left; } } -.menu-item-active a, -.menu .menu-item a:hover, -.menu .menu-item span.exturl:hover { - background: transparent; - border-bottom: 1px solid var(--link-hover-color) !important; -} -@media (max-width: 767px) { - .menu-item-active a, - .menu .menu-item a:hover, - .menu .menu-item span.exturl:hover { - border-bottom: 1px dotted #ddd !important; - } -} @media (max-width: 767px) { .menu .menu-item { + display: block; margin: 0 10px; + vertical-align: top; } } -.menu .menu-item a, -.menu .menu-item span.exturl { - border-bottom: 1px solid transparent; -} @media (max-width: 767px) { - .menu .menu-item a, - .menu .menu-item span.exturl { - padding: 5px 10px; - } -} -@media (min-width: 768px) and (max-width: 991px) { - .menu .menu-item .fa { - display: block; - line-height: 2; - margin-right: 0; - width: 100%; - } -} -@media (min-width: 992px) { - .menu .menu-item .fa { - display: block; - line-height: 2; - margin-right: 0; - width: 100%; + .menu .menu-item br { + display: none; } } -.menu .menu-item .badge { - background: #eee; - padding: 1px 4px; -} -.sub-menu { - margin: 10px 0; -} -.sub-menu .menu-item { - display: inline-block; -} -.sidebar { - right: -320px; -} -.sidebar.sidebar-active { - right: 0; -} -.sidebar-toggle, -.back-to-top { - left: auto; - right: 30px; -} -@media (max-width: 991px) { - .sidebar-toggle, - .back-to-top { - right: 20px; +@media (max-width: 767px) { + .menu .menu-item a { + padding: 5px 10px; } } -.book-mark-link { - left: 30px; -} -.sidebar { - width: 320px; - z-index: 1200; - transition-delay: 0s; - transition-duration: 0.2s; - transition-timing-function: ease-out; -} -.sidebar a, -.sidebar span.exturl { - border-bottom-color: #555; - color: #999; -} -.sidebar a:hover, -.sidebar span.exturl:hover { - border-bottom-color: #eee; - color: #eee; +.menu .menu-item .fa { + margin-right: 0; } -.links-of-blogroll-item { - padding: 2px 10px; +.site-search form { + display: none; } -.links-of-blogroll-item a, -.links-of-blogroll-item span.exturl { - box-sizing: border-box; +.links-of-blogroll-inline .links-of-blogroll-item { display: inline-block; - max-width: 280px; - overflow: hidden; - text-overflow: ellipsis; - white-space: nowrap; } diff --git a/images/avatar.gif b/images/avatar.gif index 28411fd0..98990257 100644 Binary files a/images/avatar.gif and b/images/avatar.gif differ diff --git a/index.html b/index.html index 2d200a9d..51f116e9 100644 --- a/index.html +++ b/index.html @@ -1,47 +1,128 @@ - + + + + + + + - - + + + - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + - + - - - + + + + + + + + + + + + - - Liam's Blog @@ -51,212 +132,248 @@ - - -
-
+ -
-
- + + + + + +
-
+
- - -
+
+
+ + -
- + + + + + + +
+ -
- +
+ + +
-

- - -

+ + + +

+ +

+
+ @@ -264,99 +381,178 @@

-

The Python Conceptual Hierarchy

    -
  1. Programs are composed of modules
  2. -
  3. Modules contain statements
  4. -
  5. Statements contain expressions
  6. -
  7. Expressions create and process objects
  8. -
+ + + + + + Implementation1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727 + ... -
- - Read more » - -
+
+ + Read more » + +
+ + + + + -
+ +
+ + + + + + + +
-
+ +
+

+ + +
+ + + + + + + - +
-
- + +
+ + +
-

- - -

+ + + +

+ +

+
+ @@ -364,94 +560,183 @@

-

Implementation

+ + + + + + +123Inf: positive infinity-Inf: negative infinityNaN: not a number + +Noting that two decimals can only be approximately equal. + +Casting an int to a flo + ... -
- - Read more » - -
+
+ + Read more » + +
+ + + + + -
+ +
+ + + + + + + +
-
+ +
+

+ + +
+ + + + + + + - +
-
- + +
+ + +
-

- - -

+ + + +

+ +

+
+ @@ -459,181 +744,178 @@

-
    -
  1. 1
    2
    3
    Inf: positive infinity
    -Inf: negative infinity
    NaN: not a number
    -
  2. -
  3. Noting that two decimals can only be approximately equal.

    -
  4. -
  5. Casting an int to a float is risky since the float data type uses binary scientific notation with only a handful of significant figures. So an int cannot be represented precisely using a float.

    -
  6. -
  7. -
+ + + + Header file1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374 + ... + +
+ + Read more » + +
+ + +
+ + + + + -
+ +
+ + + + + + + +
-
+ +
+

+ + +
+ + + + + + + - +
-
- + +
+ + +
-

- - -

+ + + +

+ +

+ -
+ + + - - - -
+ + - -

Header file

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
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
#ifndef CIRCULARLINKEDLIST_H_
#define CIRCULARLINKEDLIST_H_

#include <cstddef>
#include <iostream>

template <typename T>
class CircularLinkedList{
struct Node;
public:
typedef std::size_t size_type;
typedef T value_type;

// default constructor
CircularLinkedList(): sentinel(create_sentinel()), count(0) {
std::cout << "default constructor" << std::endl;
}
CircularLinkedList(size_type, const T&val = T());
CircularLinkedList(const CircularLinkedList&);
CircularLinkedList& operator= (const CircularLinkedList&);
~CircularLinkedList();

bool empty() const { return sentinel->next == sentinel; }
size_type size() const { return count; }
Node* begin() { return sentinel->next; }
const Node* begin() const { return sentinel->next; }
Node* end() { return sentinel; }
const Node* end() const { return sentinel; }

void clear();
void push_front(const T&);
void push_back(const T&);
void insert(size_type, const T&);
void pop_front();
void pop_back();
void erase(size_type);
void reverse();
void remove(const T&);

private:
struct Node{
T data;
Node* prev;
Node* next;
};

Node* sentinel;
size_type count;

Node* create_sentinel(){
Node* nil = new Node;
nil->prev = nil;
nil->next = nil;
return nil;
}

Node* create(const T&val){
Node* new_node = new Node;
new_node->data = val;
new_node->prev = sentinel;
new_node->next = sentinel;
return new_node;
}

};

// O(n)
template <typename T>
CircularLinkedList<T>::CircularLinkedList(size_type n, const T& val)
: sentinel(create_sentinel()), count(0){
std::cout << "construtor with parameters" << std::endl;
Node* current = sentinel;
while(count != n){
current->next = create(val);
current->next->prev = current;
current = current->next;
++count;
}
sentinel->prev = current;
}

// O(n)
template <typename T>
CircularLinkedList<T>::CircularLinkedList(const CircularLinkedList& l)
: sentinel(create_sentinel()), count(0){
std::cout << "copy constructor" << std::endl;
Node* current = sentinel;
const Node* temp = l.begin();
while(count != l.size()){
current->next = create(temp->data);
current->next->prev = current;
current = current->next;
temp = temp->next;
++count;
}
sentinel->prev = current;

}

// O(n)
template <typename T>
CircularLinkedList<T>& CircularLinkedList<T>::operator=
(const CircularLinkedList& l){
std::cout << "assignment operator" << std::endl;
if(&l != this){
clear();
if(!l.empty()){
Node* current = sentinel;
const Node* temp = l.begin();
while(count != l.size()){
current->next = create(temp->data);
current->next->prev = current;
current = current->next;
temp = temp->next;
++count;
}
sentinel->prev = current;
}
}
return *this;
}

// O(n)
template <typename T>
void CircularLinkedList<T>::clear(){
while(sentinel->next != sentinel){
Node* temp = sentinel->next;
sentinel->next = temp->next;
sentinel->next->prev = sentinel;
delete temp;
--count;
}
}

// O(n)
template <typename T>
CircularLinkedList<T>::~CircularLinkedList() {
clear();
delete sentinel;
sentinel = nullptr;
std::cout << "destructor" << std::endl;
}

// O(1)
template <typename T>
void CircularLinkedList<T>::push_front(const T& val){
Node* new_node = create(val);
new_node->next = sentinel->next;
new_node->next->prev = new_node;
sentinel->next = new_node;
new_node->prev = sentinel;
++count;
}

// O(n)
template <typename T>
void CircularLinkedList<T>::push_back(const T& val){
Node* current = sentinel;
while(current->next != sentinel)
current = current->next;

current->next = create(val);
current->next->prev = current;
sentinel->prev = current->next;
++count;
}

// O(n)
template <typename T>
void CircularLinkedList<T>::insert(size_type position, const T& val){
if(position < 1 || position > size())
throw std::domain_error("Invalid Position");
Node* current = sentinel;
Node* new_node = create(val);
for(size_type i = 0; i != position; ++i)
current = current->next;

current->prev->next = new_node;
new_node->prev = current->prev;
new_node->next = current;
current->prev = new_node;
++count;
}

// O(1)
template <typename T>
void CircularLinkedList<T>::pop_front(){
erase(1);
}

// O(n)
template <typename T>
void CircularLinkedList<T>::pop_back(){
erase(size());
}

// O(n)
template <typename T>
void CircularLinkedList<T>::erase(size_type position){
if(position < 1 || position > size())
throw std::domain_error("Invalid Position");

Node* current = sentinel;
for(size_type i = 0; i != position; ++i)
current = current->next;

current->next->prev = current->prev;
current->prev->next = current->next;
delete current;
--count;
}

// O(n)
template <class T>
void CircularLinkedList<T>::remove(const T& val){
Node* current = sentinel;
while(current->next != sentinel){
if(current->next->data == val){
Node* temp = current->next;
current->next = temp->next;
temp->next->prev = current;
delete temp;
--count;
}else current = current->next;
}
}

// O(n)
template <class T>
void CircularLinkedList<T>::reverse(){
if (size() < 2) return;
sentinel->prev = sentinel->next;
Node* current = sentinel;
Node* NEXT;
while(current->next != sentinel){
NEXT = current->next;
current->next = current->prev;
current->prev = NEXT;
current = NEXT;
}
current->next = current->prev;
current->prev = sentinel;
sentinel->next = current;
}

#endif /* CIRCULARLINKEDLIST_H_ */
+ -

Test program and results

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
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
/*
* this program tests all operations that provided by the
* CircularLinkedList<T> class
* created by Liam on: 28 Apr 2018
*/

#include <iostream>
#include "CircularLinkedList.h"

using std::endl; using std::cout;

// print and reverse print
template <class T>
void print(T& l){
cout << "print in order: ";
for(auto i = l.begin(); i != l.end(); i = i->next)
cout << i->data << " ";
cout << endl;

cout << "print in reverse: ";

for(auto i = (l.end())->prev; i != l.end(); i = i->prev)
cout << i->data << " ";
cout << endl;
}

int main(){

{ // construct an empty linked list
CircularLinkedList<int> s;
if(s.empty())
cout << "s is an empty linked list\n"
"the size of s1 is: " << s.size() << endl;

// call destructor once reaches the end of this block
}
cout << endl;

{ // construct a linked list that contains 10 elements, all values are 100
CircularLinkedList<int> s(10, 100);

// construct a linked list by copying from s
CircularLinkedList<int> s_copy(s);
if(!s.empty() && !s_copy.empty()){
cout << "the size of s is: " << s.size() << endl;
cout << "the size of s_copy is: " << s_copy.size() << endl;
}

// print the contents of s_copy
print(s_copy);
// call destructor twice
}
cout << endl;

{ // assignment
CircularLinkedList<int> s(10, 100);
CircularLinkedList<int> s_copy;
s_copy = s;

// print the contents of s_copy
print(s_copy);
}
cout << endl;

{ // push front
CircularLinkedList<double> s;
for(int i = 5; i != 0; --i)
s.push_front(i);

cout << "after adding elements at front:\n";
print(s);

// push back
for(int i = 5; i != 0; --i)
s.push_back(i);

cout << "after adding elements at the end:\n";
print(s);

// insert at position 5
for(int i = 5; i != 0; --i)
s.insert(i, 0);

cout << "after inserting elements in-between:\n";
print(s);

// delete from the begining
for(int i = 5; i != 0; --i)
s.pop_front();

cout << "after deleting from the begining:\n";
print(s);

// delete from the end
for(int i = 5; i != 0; --i){
s.pop_back();
}

cout << "after deleting from the end:\n";
print(s);

// erase at in-between positions
for(int i = 3; i != 0; --i)
s.erase(3);

cout << "after deleting from other positions:\n";
print(s);

}
cout << endl;

{ // remove
CircularLinkedList<int> s(5, 5);
for(int i = 5; i != 0; --i)
s.insert(5, i);

cout << "at present:\n";
print(s);

s.remove(5);
cout << "after removing all elements:\n";
print(s);
}
cout << endl;

{ // test reverse function
CircularLinkedList<int> s;
for(int i = 0; i != 10; ++i)
s.push_back(i);

cout << "at present, s contains following elements:\n";
print(s);

s.reverse();
cout << "after reverse:\n";
print(s);

}

return 0;
}
+ +
+ + + + + + + + 1,752 + + -

Outputs:

-
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
default constructor
s is an empty linked list
the size of s1 is: 0
destructor

construtor with parameters
copy constructor
the size of s is: 10
the size of s_copy is: 10
print in order: 100 100 100 100 100 100 100 100 100 100
print in reverse: 100 100 100 100 100 100 100 100 100 100
destructor
destructor

construtor with parameters
default constructor
assignment operator
print in order: 100 100 100 100 100 100 100 100 100 100
print in reverse: 100 100 100 100 100 100 100 100 100 100
destructor
destructor

default constructor
after adding elements at front:
print in order: 1 2 3 4 5
print in reverse: 5 4 3 2 1
after adding elements at the end:
print in order: 1 2 3 4 5 5 4 3 2 1
print in reverse: 1 2 3 4 5 5 4 3 2 1
after inserting elements in-between:
print in order: 0 1 0 2 0 3 0 4 0 5 5 4 3 2 1
print in reverse: 1 2 3 4 5 5 0 4 0 3 0 2 0 1 0
after deleting from the begining:
print in order: 3 0 4 0 5 5 4 3 2 1
print in reverse: 1 2 3 4 5 5 0 4 0 3
after deleting from the end:
print in order: 3 0 4 0 5
print in reverse: 5 0 4 0 3
after deleting from other positions:
print in order: 3 0
print in reverse: 0 3
destructor

construtor with parameters
at present:
print in order: 5 5 5 5 1 2 3 4 5 5
print in reverse: 5 5 4 3 2 1 5 5 5 5
after removing all elements:
print in order: 1 2 3 4
print in reverse: 4 3 2 1
destructor

default constructor
at present, s contains following elements:
print in order: 0 1 2 3 4 5 6 7 8 9
print in reverse: 9 8 7 6 5 4 3 2 1 0
after reverse:
print in order: 9 8 7 6 5 4 3 2 1 0
print in reverse: 0 1 2 3 4 5 6 7 8 9
destructor
- -
- - - - -
-
-
-
- - - - - - - -
+ @@ -641,177 +923,178 @@

-

Header file

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
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
#ifndef CIRCULARLINKEDLIST_H_
#define CIRCULARLINKEDLIST_H_

#include <cstddef>
#include <iostream>

template <typename T>
class CircularLinkedList{
struct Node;

public:
typedef std::size_t size_type;
typedef T value_type;

// create an empty linked list
CircularLinkedList(): sentinel(create_sentinel()), count(0){
std::cout << "default constructor" << std::endl;
}

// create an linked list with user supplied size and value
explicit CircularLinkedList(size_type, const T& val = T());

// copy constructor
CircularLinkedList(const CircularLinkedList&);

// assignment operator
CircularLinkedList& operator=(const CircularLinkedList&);

// destructor
~CircularLinkedList() {
clear();
delete sentinel;
sentinel = nullptr;
std::cout << "destructor" << std::endl;
}

void clear();

bool empty() const { return sentinel == sentinel->next; }
size_type size() const { return count; }

Node* begin() { return sentinel->next; }
const Node* begin() const { return sentinel->next; }

Node* end() { return sentinel; }
const Node* end() const { return sentinel; }

// insert at begining
void push_front(const T&);

// insert at the end
void push_back(const T&);

// insert at the nth position, that is, after (n-1)the position
// the range of position is [1, size()]
void insert(size_type, const T&);

// delete at the begining
void pop_front();

// delete the last element
void pop_back();

// delete at nth position
void erase(size_type);

// reverse the order iteratively
void reverse();

// remove elements with specific values
void remove(const T&);

private:
struct Node{
T data;
Node* next;
};

Node* sentinel;
size_type count;


Node* create_sentinel(){
Node* nil = new Node;
nil->next = nil;
return nil;
}


Node* create(const T& val = T()){
Node* new_node = new Node;
new_node->data = val;
new_node->next = sentinel;
return new_node;
}
};

// O(n)
template <typename T>
CircularLinkedList<T>::CircularLinkedList(size_type n, const T& val):
sentinel(create_sentinel()), count(0){
std::cout << "constructor with parameters" << std::endl;

Node* current = sentinel;
while(count != n){
current->next = create(val);
current = current->next;
++count;
}
}

// O(n)
template <typename T>
CircularLinkedList<T>::CircularLinkedList(const CircularLinkedList& l):
sentinel(create_sentinel()), count(0){
Node* current = sentinel;
const Node* temp = l.begin();
while(temp != l.end()){
current->next = create(temp->data);
current = current->next;
temp = temp->next;
++count;
}
}

// O(n)
template <typename T>
CircularLinkedList<T>& CircularLinkedList<T>::operator=
(const CircularLinkedList& l){
if(&l != this){
clear();
Node* current = sentinel;
const Node* temp = l.begin();
while(temp != l.end()){
current->next = create(temp->data);
current = current->next;
temp = temp->next;
++count;
}
}
return *this;
}

// O(n)
template <typename T>
void CircularLinkedList<T>::clear(){
Node* current = sentinel;
if(current->next != sentinel){
Node* temp = current->next;
current->next = temp->next;
delete temp;
--count;
}
}

// O(1)
template <class T>
void CircularLinkedList<T>::push_front(const T& val) {
Node* current = sentinel;
Node* new_node = create(val);
new_node->next = current->next;
current->next = new_node;
++count;
}

// O(n)
template <class T>
void CircularLinkedList<T>::push_back(const T& val) {
Node* current = sentinel;
while(current->next != sentinel)
current = current->next;

current->next = create(val);
++count;
}

// O(n)
template <class T>
void CircularLinkedList<T>::insert(size_type position, const T& val){
if(position < 1 || position > size())
throw std::domain_error("Invalid Position");

Node* new_node = create(val);
Node* temp = sentinel;
for (size_type i = 0; i != position-1; ++i)
temp = temp->next;
new_node->next = temp->next;
temp->next = new_node;
++count;
}

// O(1)
template <class T>
void CircularLinkedList<T>::pop_front(){
erase(1);
}

// O(n)
template <class T>
void CircularLinkedList<T>::pop_back(){
erase(size());
}

// O(n)
template <class T>
void CircularLinkedList<T>::erase(size_type position){
if(position < 1 || position > size())
throw std::domain_error("Invalid Position");

Node* current = sentinel;
for(size_type i = 0; i != position - 1; ++i)
current = current->next;

Node* temp = current->next;
current->next = temp->next;
delete temp;
--count;
}

// O(n)
template <class T>
void CircularLinkedList<T>::remove(const T& val){
Node* current = sentinel;
while(current->next != sentinel){
if(current->next->data == val){
Node* temp = current->next;
current->next = temp->next;
delete temp;
--count;
}else current = current->next;
}
}

// O(n)
template <class T>
void CircularLinkedList<T>::reverse(){
if (size() < 2) return;
Node* current = sentinel->next;
Node* PREV = sentinel;
Node* NEXT;
while(current->next != sentinel){
NEXT = current->next;
current->next = PREV;
PREV = current;
current = NEXT;
}
current->next = PREV;
sentinel->next = current;
}

#endif /* CIRCULARLINKEDLIST_H_ */
-

Test program and results

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
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
/*
* this program tests all operations that provided by the
* CircularLinkedList<T> class
* created by Liam on: 28 May 2018
*/

#include <iostream>
#include "CircularLinkedList.h"

using std::endl; using std::cout;

template <class Pointer>
void print(Pointer begin, Pointer end){
while(begin != end){
cout << begin->data << " ";
begin = begin->next;
}
cout<< endl;
}

int main(){

{ // construct an empty linked list
CircularLinkedList<int> s;
if(s.empty())
cout << "s is an empty linked list\n"
"the size of s1 is: " << s.size() << endl;

// call destructor once reaches the end of this block
}
cout << endl;

{ // construct a linked list that contains 10 elements, all values are 100
CircularLinkedList<int> s(10, 100);

// construct a linked list by copying from s
CircularLinkedList<int> s_copy(s);
if(!s.empty() && !s_copy.empty()){
cout << "the size of s is: " << s.size() << endl;
cout << "the size of s_copy is: " << s_copy.size() << endl;
}

// print the contents of s_copy
cout << "all elements in s_copy: ";
print(s.begin(), s.end());

// call destructor twice
}
cout << endl;

{ // assignment
CircularLinkedList<int> s(10, 100);
CircularLinkedList<int> s_copy;
s_copy = s;

// print the contents of s_copy
cout << "all elements in s_copy: ";
print(s_copy.begin(), s_copy.end());
}
cout << endl;

{ // push front
CircularLinkedList<double> s;
for(int i = 5; i != 0; --i)
s.push_front(i);

cout << "after adding elements at front, s becomes: ";
print(s.begin(), s.end());

// push back
for(int i = 5; i != 0; --i)
s.push_back(i);

cout << "after adding elements at the end, s becomes: ";
print(s.begin(), s.end());


// insert at position 5
for(int i = 5; i != 0; --i)
s.insert(i, 0);

cout << "after inserting elements in-between, s becomes: ";
print(s.begin(), s.end());

// delete from the begining
for(int i = 5; i != 0; --i)
s.pop_front();

cout << "after deleting from the begining, s becomes: ";
print(s.begin(), s.end());

// delete from the end
for(int i = 5; i != 0; --i)
s.pop_back();

cout << "after deleting from the end, s becomes: ";
print(s.begin(), s.end());

// erase at in-between positions
for(int i = 3; i != 0; --i)
s.erase(3);

cout << "after deleting from other positions, s becomes: ";
print(s.begin(), s.end());

}
cout << endl;

{ // remove
CircularLinkedList<int> s(5, 5);
for(int i = 5; i != 0; --i)
s.insert(5, i);

cout << "at present, s contains following elements: ";
print(s.begin(), s.end());

s.remove(5);
cout << "after removing all elements equal 5, s becomes: ";
print(s.begin(), s.end());
}
cout << endl;

{ // test reverse function
CircularLinkedList<int> s;
for(int i = 0; i != 10; ++i)
s.push_back(i);

cout << "at present, s contains following elements: ";
print(s.begin(), s.end());

s.reverse();
cout << "reverse: ";
print(s.begin(), s.end());

s.reverse();
cout << "reverse again: ";
print(s.begin(), s.end());

}

return 0;
}
+ -

Outputs:

-
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
default constructor
s is an empty linked list
the size of s1 is: 0
destructor

constructor with parameters
the size of s is: 10
the size of s_copy is: 10
all elements in s_copy: 100 100 100 100 100 100 100 100 100 100
destructor
destructor

constructor with parameters
default constructor
all elements in s_copy: 100 100 100 100 100 100 100 100 100 100
destructor
destructor

default constructor
after adding elements at front, s becomes: 1 2 3 4 5
after adding elements at the end, s becomes: 1 2 3 4 5 5 4 3 2 1
after inserting elements in-between, s becomes: 0 1 0 2 0 3 0 4 0 5 5 4 3 2 1
after deleting from the begining, s becomes: 3 0 4 0 5 5 4 3 2 1
after deleting from the end, s becomes: 3 0 4 0 5
after deleting from other positions, s becomes: 3 0
destructor

constructor with parameters
at present, s contains following elements: 5 5 5 5 1 2 3 4 5 5
after removing all elements equal 5, s becomes: 1 2 3 4
destructor

default constructor
at present, s contains following elements: 0 1 2 3 4 5 6 7 8 9
reverse: 9 8 7 6 5 4 3 2 1 0
reverse again: 0 1 2 3 4 5 6 7 8 9
destructor
+ + + + Header file1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374 + ... + +
+ + Read more » + +
+ +
+ + + + + -
+ +
+ + + + + + + +
-
+ +
+

+ + + + + + + + + + - +
+ -
- +
+ + +
-

- - -

+ + + +

+ +

+ -
- - - - -
- - -

Head files

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
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
#ifndef DOUBLYLINKEDLIST_H_
#define DOUBLYLINKEDLIST_H_

#include <cstddef>
#include <stdexcept>
#include <iostream>

template <typename T>
class DoublyLinkedList{
struct Node;
public:
typedef std::size_t size_type;
typedef T value_type;

// default constructor
DoublyLinkedList(): ptrToHead(nullptr), count(0){
std::cout << "default constructor" << std::endl;
}

// constructor with parameters
explicit DoublyLinkedList(size_type, const T&val = T());

// copy constructor
DoublyLinkedList(const DoublyLinkedList&);

// assignment operator
DoublyLinkedList& operator=(const DoublyLinkedList&);

// destructor
~DoublyLinkedList() {
std::cout << "destructor" << std::endl;
clear();
}

void clear();
bool empty() const { return ptrToHead == nullptr; }
size_type size() const { return count; }
Node* begin() { return ptrToHead; }
const Node* begin() const { return ptrToHead; }

void push_front(const T&);
void push_back(const T&);
void insert(size_type, const T&);

void pop_front();
void pop_back();
void erase(size_type);

void remove(const T&);
void reverse();

private:
struct Node{
T data;
Node* prev;
Node* next;
};

Node* ptrToHead;
size_type count;

Node* create(const T& val){
Node* new_node = new Node;
new_node->data = val;
new_node->prev = nullptr;
new_node->next = nullptr;
return new_node;
}
};

// constructor with parameters:O(n)
template <typename T>
DoublyLinkedList<T>::DoublyLinkedList(size_type n, const T&val){
std::cout << "constructor with parameters" << std::endl;
if(n > 0){
ptrToHead = create(val);
count = 1;

Node* current = ptrToHead;
while(count != n){
current->next = create(val);
current->next->prev = current;
current = current->next;
++count;
}
}
}

// copy constructor: O(n)
template <typename T>
DoublyLinkedList<T>::DoublyLinkedList(const DoublyLinkedList& l)
: ptrToHead(nullptr), count(0){
std::cout << "copy constructor" << std::endl;
if(!l.empty()){
ptrToHead = create(l.begin()->data);
count = 1;

const Node* temp = l.begin();
Node* current = ptrToHead;
while(count != l.size()){
current->next = create(temp->next->data);
current->next->prev = current;
current = current->next;
temp = temp->next;
++count;
}
}
}

// assignment operator: O(n)
template <typename T>
DoublyLinkedList<T>& DoublyLinkedList<T>::operator= (const DoublyLinkedList& l)
{
std::cout << "assignment operator" << std::endl;
if(&l != this){
clear();
if(!l.empty()){
ptrToHead = create(l.begin()->data);
count = 1;

const Node* temp = l.begin();
Node* current = ptrToHead;
while(count != l.size()){
current->next = create(temp->next->data);
current->next->prev = current;
current = current->next;
temp = temp->next;
++count;
}
}
}
return *this;
}

// O(n)
template <typename T>
void DoublyLinkedList<T>::clear(){
Node* current = ptrToHead;
while(current != nullptr){
ptrToHead = ptrToHead->next;
delete current;
current = ptrToHead;
--count;
}
}

// O(1)
template <typename T>
void DoublyLinkedList<T>::push_front(const T& val){
Node* new_node = create(val);
if(ptrToHead != nullptr){
new_node->next = ptrToHead;
ptrToHead->prev = new_node;
}
ptrToHead = new_node;
++count;
}

// O(n)
template <typename T>
void DoublyLinkedList<T>::push_back(const T& val){
Node* new_node = create(val);
if(ptrToHead == nullptr){
ptrToHead = new_node;
++count;
return;
}

Node* current = ptrToHead;
while(current->next != nullptr)
current = current->next;

current->next = new_node;
new_node->prev = current;
++count;
}

// O(n)
template <typename T>
void DoublyLinkedList<T>::insert(size_type position, const T& val){
if(position < 1 || position > size())
throw std::domain_error("Invalid Position");
else if(position == 1)
push_front(val);
else{
Node* new_node = create(val);
Node* current = ptrToHead;

for(size_type i = 0; i != position - 1; ++i)
current = current->next;
current->prev->next = new_node;
new_node->next = current;
new_node->prev = current->prev;
current->prev = new_node;
++count;
}
}

// O(1)
template <typename T>
void DoublyLinkedList<T>::pop_front(){ erase(1); }

// O(n)
template <typename T>
void DoublyLinkedList<T>::pop_back(){ erase(size()); }

// O(n)
template <typename T>
void DoublyLinkedList<T>::erase(size_type position){
if(position < 1 || position > size())
throw std::domain_error("Invalid Position");

Node* current = ptrToHead;

if(size() == 1){
ptrToHead = nullptr;
}else if(position == 1){
ptrToHead = ptrToHead->next;
ptrToHead->prev = nullptr;
}else if(position == size()){
while(current->next != nullptr)
current = current->next;
current->prev->next = nullptr;
}else{
for (size_type i = 0; i != position - 1; ++i){
current = current->next;
}
current->next->prev = current->prev;
current->prev->next = current->next;
}

delete current;
--count;
}

// O(n)
template <typename T>
void DoublyLinkedList<T>::remove(const T& val){
Node* current = ptrToHead;
while(current != nullptr){
if(current->data == val){
Node* temp = current->next;
if(current->prev == nullptr){
ptrToHead = ptrToHead->next;
if(ptrToHead != nullptr){
ptrToHead->prev = nullptr;
}
}else if(current->next == nullptr){
current->prev->next = nullptr;
}else{
current->next->prev = current->prev;
current->prev->next = current->next;
}

delete current;
current = temp;
}else
current = current->next;
}
}

// O(n)
template <typename T>
void DoublyLinkedList<T>::reverse(){
Node* current = ptrToHead;
Node* PREV = nullptr;
while(current != nullptr){
current->prev = current->next;
current->next = PREV;
PREV = current;
current = current->prev;
}
ptrToHead = PREV;
}
#endif /* DOUBLYLINKEDLIST_H_ */
- - -

Test program and results

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
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
/*
* this program tests all operations that provided by the
* DoublyLinkedList<T> class
* created by Liam on: 28 Apr 2018
*/

#include <iostream>
#include "DoublyLinkedList.h"

using std::endl; using std::cout;

// print and reverse print
template <class T>
void print(T& l){
cout << "print in order: ";
for(auto it = l.begin(); it != nullptr; it = it->next){
cout << it->data << " ";
}

cout << "\n" << "print in reverse: ";
auto it = l.begin();
while(it->next != nullptr){
it = it->next;

}
while(it != nullptr){
cout << it->data << " ";
it = it->prev;
}

cout << endl;
}

int main(){

{ // construct an empty linked list
DoublyLinkedList<int> s;
if(s.empty())
cout << "s is an empty linked list\n"
"the size of s1 is: " << s.size() << endl;

// call destructor once reaches the end of this block
}
cout << endl;

{ // construct a linked list that contains 10 elements, all values are 100
DoublyLinkedList<int> s(10, 100);

// construct a linked list by copying from s
DoublyLinkedList<int> s_copy(s);
if(!s.empty() && !s_copy.empty()){
cout << "the size of s is: " << s.size() << endl;
cout << "the size of s_copy is: " << s_copy.size() << endl;
}

// print the contents of s_copy
print(s_copy);

// call destructor twice
}
cout << endl;

{ // assignment
DoublyLinkedList<int> s(10, 100);
DoublyLinkedList<int> s_copy;
s_copy = s;

// print the contents of s_copy
print(s_copy);
}
cout << endl;

{ // push front
DoublyLinkedList<double> s;
for(int i = 5; i != 0; --i)
s.push_front(i);

cout << "after adding elements at front:\n";
print(s);

// push back
for(int i = 5; i != 0; --i)
s.push_back(i);

cout << "after adding elements at the end:\n";
print(s);

// insert at position 5
for(int i = 5; i != 0; --i)
s.insert(i, 0);

cout << "after inserting elements in-between:\n";
print(s);

// delete from the begining
for(int i = 5; i != 0; --i)
s.pop_front();

cout << "after deleting from the begining:\n";
print(s);

// delete from the end
for(int i = 5; i != 0; --i){
s.pop_back();
}

cout << "after deleting from the end:\n";
print(s);

// erase at in-between positions
for(int i = 3; i != 0; --i)
s.erase(3);

cout << "after deleting from other positions:\n";
print(s);

}
cout << endl;

{ // remove
DoublyLinkedList<int> s(5, 5);
for(int i = 5; i != 0; --i)
s.insert(5, i);

cout << "at present:\n";
print(s);

s.remove(5);
cout << "after removing all elements:\n";
print(s);
}
cout << endl;

{ // test reverse function
DoublyLinkedList<int> s;
for(int i = 0; i != 10; ++i)
s.push_back(i);

cout << "at present, s contains following elements:\n";
print(s);

s.reverse();
cout << "after reverse:\n";
print(s);

}

return 0;
}
- -

Outputs:

-
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
default constructor
s is an empty linked list
the size of s1 is: 0
destructor

constructor with parameters
copy constructor
the size of s is: 10
the size of s_copy is: 10
print in order: 100 100 100 100 100 100 100 100 100 100
print in reverse: 100 100 100 100 100 100 100 100 100 100
destructor
destructor

constructor with parameters
default constructor
assignment operator
print in order: 100 100 100 100 100 100 100 100 100 100
print in reverse: 100 100 100 100 100 100 100 100 100 100
destructor
destructor

default constructor
after adding elements at front:
print in order: 1 2 3 4 5
print in reverse: 5 4 3 2 1
after adding elements at the end:
print in order: 1 2 3 4 5 5 4 3 2 1
print in reverse: 1 2 3 4 5 5 4 3 2 1
after inserting elements in-between:
print in order: 0 1 0 2 0 3 0 4 0 5 5 4 3 2 1
print in reverse: 1 2 3 4 5 5 0 4 0 3 0 2 0 1 0
after deleting from the begining:
print in order: 3 0 4 0 5 5 4 3 2 1
print in reverse: 1 2 3 4 5 5 0 4 0 3
after deleting from the end:
print in order: 3 0 4 0 5
print in reverse: 5 0 4 0 3
after deleting from other positions:
print in order: 3 0
print in reverse: 0 3
destructor

constructor with parameters
at present:
print in order: 5 5 5 5 1 2 3 4 5 5
print in reverse: 5 5 4 3 2 1 5 5 5 5
after removing all elements:
print in order: 1 2 3 4
print in reverse: 4 3 2 1
destructor

default constructor
at present, s contains following elements:
print in order: 0 1 2 3 4 5 6 7 8 9
print in reverse: 9 8 7 6 5 4 3 2 1 0
after reverse:
print in order: 9 8 7 6 5 4 3 2 1 0
print in reverse: 0 1 2 3 4 5 6 7 8 9
destructor
- -
- - - - -
-
-
-
- - - + + + - - - -
- + + - + - -
-

- -

+
+ + + + + + + + 1,967 + + - + -
- - +
+ @@ -819,185 +1102,178 @@

-

Logical View

How to efficiently store a sequence of values (aka. a List) of a given type is crucial for any non-trivial program due to the limited memory. The way we store or organsize data introduces an important concept, that is, data structures. A data structure is proposed to manage data in a specific way so that we can use it efficiently according to specific needs.

-

In C++/C, the most common facility we use is so called built-in array, which stores a given number of elements in a contiguous memory block. All elements are stored in certain order indicated by their addresses. When we want to access any one of the elements, we pass the order of that element to the computer. Then the computer will calculate the address of that element based on the initial address of the array, and then return us the associated value. Since the addresses are contiguous and the size of memory occupied by the given type is fixed, we can always get any element in constant time: there is only one arithmetic operation needed. Naturally, we can modify any one of elements in constant time as well. However, the shortingcoming is obvious: an array has fixed length. There are two ways to solve this problem, one is that we can preset a large enough size for the array, however, doing so is very inefficient in terms of memory usage. Another way is to dynamically allocated an array when it is needed. However, doing so possibly invalidates all pointers/iterators as all elements are moved to a new allocated storage, which is also very inefficient in terms of time cost(O(n)). Even if the allocated storage is sufficient, any modifications of the array, such as, insert or delete element, also invalidates parts or all of the pointers/iterators, and hence these operations have high time cost: O(n).

-

An alternative data structure is named Linked List, which stores data in a noncontinuous memory space through a manner that each element are connected and ordered by pointers. More specific, an element of a singly linked list is an single object (aka. Linked Node) that contains two members: one is the data stored in the object, and the other is the address (i.e. a pointer variable) of the next element.

-

Basic Operations

The entrance of a singly linked list is a pointer to the Head Node, i.e. the first Linked Node. If the linked list is empty, the pointer to the Head Node is a nullptr. To access one element, we have to start from the Head Node and move one Node by one Node. Therefore, unlike the array, the time to access elements of the linked list is proportational to the size of the linked list, that is, the time complexity is O(n). Naturally if we want to insert or delete one element into/from the list, we have to find the position or the specific element first. The time complexity is also O(n). One advantage compared to the array is that it doesn’t worry about the size of the storage. We do not need to preset the length of the list and hence no extra memory is wasted. But it does need extra memory for storing the pointer variable in each Linked Node. Another advantage is that the operations such as insert or delete doesn’t change other elements, which is crucial for us when we manipulate an object by pointers or iterators. Now let’s briefly summarize these two elementary data structures: Array and Linked List.

- - - - - - - - - - - - - - - - - - - - - - - - - - - - -
ArrayLinked List
Cost of accessing an element of an elementO(1)O(n)
Memory requirementFixed size: used memory and unused memoryNo unused memory but need extra memory for pointer variables
Cost of Inserting or deleting an elementInserting at beging: O(n); Insering at the end: O(1) if there exists unused memory but O(n) is the array is full; Inserting at middle: O(n)Inserting at begining:O(1); Inserting at the end: O(n); Inserting at middle: O(n)
Source:mycodeschool
-

From above table, we can see that whether to use an array or a linked list depends on that what is the most frequent operation the program performs and what is the size of the data structure.

-

Implementations

To define a abstract data type based on the singly linked list, the first step is to write the Linked Node:

-
1
2
3
4
5
template <typename T>
struct Node{
T data;
Node* next;
};
- -

The Node struct is templated for satisfying different underlying types. It contains two items: one is named data which for storing values of T type and ther other named next which is a pointer to a Node object. Next we define the class type based on the singly linked list model described above. In fact, we can incorporate the Node type into our singly linked list class as a private member for the purpose of hidding the implementation details. As mentioned above, the only information we know is the pointer to the Head Node, therefore, I declare a private member ptrToHead to indicate the address of the Head Node. Also, I define a unsigned integer to count the number of elements stored in a linked list. For the sake of convenience, I write a private function create to create a Node with a particular value. Following code shows the full view of the SinglyLinkedList class template. Noting that this implementation is not a STL style implementation.

-
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
template <typename T>
class SinglyLinkedList{
struct Node; // forward declaration
template<typename X>
friend void reverse(SinglyLinkedList<X>&,
typename SinglyLinkedList<X>::Node*)
public:

SinglyLinkedList(): ptrToHead(nullptr), count(0) { // create an empty linked_list
std::cout << "default constructor" << std::endl;
}
explicit SinglyLinkedList(const size_type, const T& val = T()); // create an linked list with size
SinglyLinkedList(const SinglyLinkedList&); // copy constructor
SinglyLinkedList& operator= (const SinglyLinkedList&); // assignment operator
~SinglyLinkedList(); // destructor
void clear(); // clear
Node* begin() { return ptrToHead; } // get pointer to Head Node
const Node* begin() const { return ptrToHead; }
bool empty() const { return ptrToHead == nullptr; } // check whether is empty
size_type size() const { return count; } // get the size of the list
void push_front(const T&); // insert at begining
void push_back(const T&); // insert at the end
void insert(size_type, const T&); // insert at the nth position
void pop_front(); // delete at the begining
void pop_back(); // delete the last element
void erase(size_type); // delete at nth position
void reverse(); // reverse the order iteratively
void remove(const T&); // remove elements with specific values

private:
// nested Node type
struct Node{
T data;
Node* next;
};

// data members
Node* ptrToHead;
size_type count;

// create a new Node
Node* create(const T& val){
Node* new_node = new Node;
new_node->data = val;
new_node->next = nullptr;
return new_node;
}
};
- -

Let’s define the special members first:

-

constructors

The default constructor initializes the ptrToHead to nullptr and count to 0, hence constructs an empty linked list. The second constructor constructs a linked list with a size and a value of T type. If no value supplied, the data member in each Node will be default-initialized or value-initialized depending on whether there exists a user-defined default constructor in the definition of T type. Also I specify this constructor as explicit to avoid potential type conversion. To validate my implementation, I instrument these special members by adding outputs when these members are called.

-
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
// constructor that takes a size and a value: O(n)
template <class T>
SinglyLinkedList<T>::SinglyLinkedList(size_type n, const T& val){
std::cout << "constructor with parameters" << std::endl;
if(n > 0){
ptrToHead = create(val);
count = 1;
Node* temp = ptrToHead;
while(count != n){
temp->next = create(val);
temp = temp->next;
++count;
}
}
}
-

The logic is simple: first create the Head Node and store its address into ptrToHead, at the same time, update the count to 1 representing that currently we have one element in our linked list; Then, create the rest Nodes and link one by one. We use a temporary pointer to move forward as we don’t want to modify the ptrToHead. The time cost is proportional to the length of the linked list and therefore the time complexity is big oh of n, where n represents the number of elements stored in the linked list.

-

Next is the copy constructor which controls the copy operation. The definition is similar to above constructor except that, the copy constructor constructs an object using values stored in the argument. If the argument is an empty linked list, we construct an empty linked list as well through the initialization list.

-
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
// copy constructor: O(n)
template <class T>
SinglyLinkedList<T>::SinglyLinkedList(const SinglyLinkedList& l): ptrToHead(nullptr), count(0) {
std::cout << "copy constructor" << std::endl;
if(!l.empty()){
ptrToHead = create(l.begin()->data);
count = 1;
Node* temp1 = ptrToHead;
const Node* temp2 = l.begin();
while(count != l.size())
{
temp1->next = create(temp2->next->data);
temp1 = temp1->next;
temp2 = temp2->next;
++count;
}
}
}
- -

The assignment operator is similar to our copy costructor except that it needs to obliterate the old values stored in the left-hand side operand. If the argument is an empty linked list, we only need to execute the clear() function to delete all nodes (if exist) to get an empty linked list.

-
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
// assignment operator: O(n)
template <class T>
SinglyLinkedList<T>& SinglyLinkedList<T>::operator= (const SinglyLinkedList& l){
std::cout << "assignment operator" << std::endl;
if(&l != this){
clear();
if(!l.empty()){
ptrToHead = create(l.begin()->data);
count = 1;
Node* temp1 = ptrToHead;
const Node* temp2 = l.begin();
while(count != l.size())
{
temp1->next = create(temp2->next->data);
temp1 = temp1->next;
temp2 = temp2->next;
++count;
}
}
}
return *this;
}
- -

destructor & clear member

The destructor is implemented by executing the clear() function.

-
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
// destructor
template <class T>
SinglyLinkedList<T>::~SinglyLinkedList() {
std::cout << "destructor" << std::endl;
clear();
}

// clear function: the time complexity O(n)
template <class T>
void SinglyLinkedList<T>::clear(){
Node* current = ptrToHead;
while(current != nullptr)
{
ptrToHead = ptrToHead->next;
delete current;
current = ptrToHead;
--count;
}
}
-

Due to the ptrToHead is the only entrance for us, we cannot delete it directly. Therefore, we do it by virtue of a temporary pointer, which points to the Node to be deleted. For example, let it points to the Head Node, then we can release the ptrToHead and let the ptrToHead points to our next Node, and then we clear the Head Node by delete the temporary pointer.

-

push_front, push_back, insert

The push_front means that we can add a new element at the very begining of our linked list. It is simple to do this, making the ptrToHead points to our new Node. But noting that when the linked list is not empty, we should stores the address of the original Head Node into the next member of our new Node.

-
1
2
3
4
5
6
7
8
9
// insert at begining: O(1)
template <class T>
void SinglyLinkedList<T>::push_front(const T& val) {
Node* new_node = create(val);
if(ptrToHead != nullptr)
new_node->next = ptrToHead;
ptrToHead = new_node;
++count;
}
- -

The case of appending a new Node at the end of the linked list is a little bit complex. The function is shown below.

-
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
// insert at the end: O(n)
template <class T>
void SinglyLinkedList<T>::push_back(const T& val) {
Node* new_node = create(val);
if(ptrToHead == nullptr){
ptrToHead = new_node;
++count;
pointToHead->data << std::endl;
return;
}

Node* temp = ptrToHead;
while(temp->next != nullptr)
temp = temp->next;
temp->next = new_node;
++count;
}
-

If the linked list is empty, we simply create a new Node and let ptrToHead points to the new Node. If the linked list is not empty, we need to let the next member of the last Node points to the newly created Node. To find the address of the last Node, we use the condition:

-
1
temp->next == nullptr
-

This condition stops the while loop and by then, temp is the address of the last Node.

-

Now we consider the case that inserting a new Node at the nth position, where n is in the range [1, size()]. The case that we insert a new Node at the first position can be tanckled by the push_front function.

-

The graph below illustrate a 5-Node linked list and the case that we intend to insert the new Node at a non-Head position. For example, we insert at the third position, that is, when n == 3:

-

Singly Linked List Example

-

We can observe that:

-
    -
  1. the newly created Node becomes the third Node and the original third Node becomes the fourth Node.
  2. -
  3. before inserting
    1
    add2->next == add2
    - after inserting
    1
    add2->next == addx
  4. -
  5. to link following Nodes, we let
    1
    addx->next == add3
    -Now everything is clear: if we want to insert at the nth position, we have to find the n-1 position and link the n-1th Node to the newly created Node. Let’s starting from the ptrToHead and show the relations between Nodes, Addresses and Iteration times:
    1
    2
    3
    4
    5
    6
    7
            counter i       Address         Node
    when i = 0 ptrToHead 1st Node
    i = 1 add2 2ed Node
    i = 2 add3 3ed Node
    ... ... ...
    i = n - 2 add(n - 1) n - 1 Node
    i = n - 1 addn n Node
    - -
  6. -
-

Obviously, we will stop our loop when i == n - 2 and by then we can manipulate the n - 1 Node. Following code shows a full view of the insert function.

-
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
// insert at the nth position, n belongs to [1, size()]: O(n)
// the positions means that we add the new node after (n-1)th position
template <class T>
void SinglyLinkedList<T>::insert(size_type position, const T& val){
if(position < 1 || position > size())
throw std::domain_error("Invalid Position");
else if(position == 1)
push_front(val);
else{
Node* new_node = create(val);
Node* temp = ptrToHead;
for(size_type i = 0; i != position - 2; ++i)
temp = temp->next;
new_node->next = temp->next;
temp->next = new_node;
++count;
}
}
- -

pop_front, pop_back, erase

The idea behind these three functions are similar to the inserting operations described above. Hence no further discussion here.

-
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
// delete at the begining: O(1)
template <class T>
void SinglyLinkedList<T>::pop_front(){
if(ptrToHead == nullptr){
throw std::domain_error("Empty Linked List");
}

Node* temp = ptrToHead;
ptrToHead = ptrToHead->next;
delete temp;
--count;
}

// delete the last element: O(n)
template <class T>
void SinglyLinkedList<T>::pop_back(){
if(ptrToHead == nullptr){
throw std::domain_error("Empty Linked List");
}
erase(size());
}

// delete at nth position: O(n)
// n belongs to [1, size()], the position after n - 1
template <class T>
void SinglyLinkedList<T>::erase(size_type position){
if(position < 1 || position > size())
throw std::domain_error("Invalid Position");
else if(position == 1)
pop_front();
else{
Node* current = ptrToHead;
for (size_type i = 0; i != position-2; ++i)
current = current->next;
Node* temp = current->next;
current-> next = temp->next;
delete temp;
--count;
}
}

## remove
// remove elements with specific values: O(n^2)
template <class T>
void SinglyLinkedList<T>::remove(const T& val){
Node* current = ptrToHead;
size_type i = 0;
while(current != nullptr){
if(current->data == val){
current = current->next;
erase(i + 1);
}
else{
current = current->next;
++i;
}
}
}

The remove function allows us to remove all elements that contains data values equal to a supplied value. My solution is to find the positions of the Nodes that should be removed and then call the **erase** function by passing the position. Taking an example(see below graph), suppose one want to remove any element that has value **v3**, we should find the previous position, that is, **2ed Node**, and then break the link and rebuilt the link between **2ed Node** and **4th Node** through:
-

temp = add2->next
temp = temp->next

-
1
2
3
4

![Remove operation](/images/remove.PNG)

If we loop through the linked list starting from the **ptrToHead**, the mapping relations are as follows:
-
counter i       values           position

when i = 0 v1 1
i = 1 v2 2
i = 2 v3 3
… … …
i = n - 2 v(n - 1) n - 1
i = n - 1 vn n

-
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
When i == 2, the position that should be deleted is positioned at **i+1**. Therefore, we pass **i+1** to the **erase** function. But noting that before we delete the Node, we should let **current** points to the Node that closely followed the Node to be deleted. In addition, we should not advance the counter **i** as now the Node pointed by **current** hasn't be checked. But if a Node doesn't satisfy the condition, we move forward the **current** as well as the counter **i**. 

## reverse
In our **SinglyLinkedList**, we define two **reverse** functions, one of which is a member defined based on iteration while the other one is a non-member function defined based on recursion.
​```c++
// reverse the order: iteration version O(n)
template <class T>
void SinglyLinkedList<T>::reverse(){
Node* prev = nullptr;
Node* current = ptrToHead;
Node* next;
while(current != nullptr){
next = current->next;
current->next = prev;
prev = current;
current = next;
}
ptrToHead = prev;
}
-

The basic idea behind the reverse function is that let each Node stores the address of the previous Node.
The initial information is that:

-
    -
  1. the previous address for the Head Node is nullptr
  2. -
  3. the current address for the Head Node is ptrToHead
  4. -
-

There are four steps in each iteration:

-
    -
  1. temporarily stores the address of next Node as next = current->next will be rewrite
  2. -
  3. rewrite the current->next with the previous address
  4. -
  5. in next iteration, the previous address is the current address in this iteration, hence let prev = current
  6. -
  7. in next iteration, the current address is the next address in this iteration, hence let current = next
  8. -
-

The recursive version starts from the last Node but still uses the same idea. Don’t forget to add this function template as the friend of our SinglyLinkedList class.

-
1
2
3
4
5
6
7
8
9
10
11
12
13
// reverse the order: recursion version O(n)
// space complexity: O(n)
template<typename X>
void reverse(SinglyLinkedList<X>& l, typename SinglyLinkedList<X>::Node* p){
if(p->next == nullptr || l.empty()){
l.ptrToHead = p;
return;
}
reverse(l, p->next);
typename SinglyLinkedList<X>::Node* q = p->next;
q->next = p;
p->next = nullptr;
}
- -

Test

I have tested every member in this class template and the results show that this SinglyLinkedList works perfectly. Please find the test program and results below:

-
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
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
/*
* this program tests all operations that provided by the
* SinglyLinkedList<T> class
* created by Liam on: 28 Apr 2018
*/

#include <iostream>
#include "SinglyLinkedList.h"

using std::endl; using std::cout;

template <class T>
void print(T& l){
auto it = l.begin();
for(; it != nullptr; it = it->next){
cout << it->data << " ";
}
cout << endl;
}

int main(){

{ // construct an empty linked list
SinglyLinkedList<int> s;
if(s.empty())
cout << "s is an empty linked list\n"
"the size of s1 is: " << s.size() << endl;

// call destructor once reaches the end of this block
}
cout << endl;

{ // construct a linked list that contains 10 elements, all values are 100
SinglyLinkedList<int> s(10, 100);

// construct a linked list by copying from s
SinglyLinkedList<int> s_copy(s);
if(!s.empty() && !s_copy.empty()){
cout << "the size of s is: " << s.size() << endl;
cout << "the size of s_copy is: " << s_copy.size() << endl;
}

// print the contents of s
cout << "all elements in s: ";
print(s);

// call destructor twice
}
cout << endl;

{ // assignment
SinglyLinkedList<int> s(10, 100);
SinglyLinkedList<int> s_copy;
s_copy = s;

// print the contents of s
}
cout << endl;

{ // push front
SinglyLinkedList<double> s;
for(int i = 5; i != 0; --i)
s.push_front(i);

cout << "after adding elements at front, s becomes: ";
print(s);

// push back
for(int i = 5; i != 0; --i)
s.push_back(i);

cout << "after adding elements at the end, s becomes: ";
print(s);


// insert at position 5
for(int i = 5; i != 0; --i)
s.insert(i, 0);

cout << "after inserting elements in-between, s becomes: ";
print(s);

// delete from the begining
for(int i = 5; i != 0; --i)
s.pop_front();

cout << "after deleting from the begining, s becomes: ";
print(s);

// delete from the end
for(int i = 5; i != 0; --i)
s.pop_back();

cout << "after deleting from the end, s becomes: ";
print(s);

// erase at in-between positions
for(int i = 3; i != 0; --i)
s.erase(3);

cout << "after deleting from other positions, s becomes: ";
print(s);

}
cout << endl;

{ // remove
SinglyLinkedList<int> s(5, 5);
for(int i = 5; i != 0; --i)
s.insert(5, i);

cout << "at present, s contains following elements: ";
print(s);

s.remove(5);
cout << "after removing all elements equal 5, s becomes: ";
print(s);
}
cout << endl;

{ // test reverse function
SinglyLinkedList<int> s;
for(int i = 0; i != 10; ++i)
s.push_back(i);

cout << "at present, s contains following elements: ";
print(s);

s.reverse();
cout << "reverse: ";
print(s);

s.reverse();
cout << "reverse again: ";
print(s);
}

return 0;
}
- -

Outputs:

-
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
default constructor
s is an empty linked list
the size of s1 is: 0
destructor

constructor with parameters
copy constructor
the size of s is: 10
the size of s_copy is: 10
all elements in s: 100 100 100 100 100 100 100 100 100 100
destructor
destructor

constructor with parameters
default constructor
assignment operator
destructor
destructor

default constructor
after adding elements at front, s becomes: 1 2 3 4 5
after adding elements at the end, s becomes: 1 2 3 4 5 5 4 3 2 1
after inserting elements in-between, s becomes: 0 1 0 2 0 3 0 4 0 5 5 4 3 2 1
after deleting from the begining, s becomes: 3 0 4 0 5 5 4 3 2 1
after deleting from the end, s becomes: 3 0 4 0 5
after deleting from other positions, s becomes: 3 0
destructor

constructor with parameters
at present, s contains following elements: 5 5 5 5 1 2 3 4 5 5
after removing all elements equal 5, s becomes: 1 2 3 4
destructor

default constructor
at present, s contains following elements: 0 1 2 3 4 5 6 7 8 9
reverse: 9 8 7 6 5 4 3 2 1 0
reverse again: 0 1 2 3 4 5 6 7 8 9
destructor
- -
+ + + + Head files12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747 + ... + +
+ + Read more » + +
+ + +
+ + + + + -
+ +
+ + + + + + + +
-
+ +
+

+ + + + + + + + + + - +
+ -
- +
+ + +
-

- - -

+ + + +

+ +

+
+ @@ -1005,87 +1281,357 @@

-

Quick Sort

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
/*----------------------------------------------------------------------------- 
* main.cpp || Created on: 18 May 2018 || Author: Liam
*-----------------------------------------------------------------------------
* this program tests three implementations of the quick sort algorithm
*
* Logic:
* 1. select an element from the sequence as the pivot and rearrange the
* sequence such that all elements less than the pivot are towards the left
* of it and all elements greater than the pivot are towards the right of it.
* 2. above process is called partitioning of the sequence. we recursively
* partition the sequence till that there is only one element left in each
* segment.
* 3. for convenience, we can always select the last element as the pivot. If
* so, we may encounter the case that the position of the pivot
* is still at the end (or begining) of the rearranged sequence. Such case is
* the worst case and the time complexity is O(n^). To achieve an average
* case, in which the time complexity is O(nlogn), we can randomly select
* the pivot and then put the pivot at the end of the sequence for the
* rearangement.
*
* Complexity analysis:
* time complexity:
*
* Best case: always be balanced at the midpoint in each partition
* T(n) = 2T(n/2) + cn
* = 2^k T(n/2^k) + kcn
* where k = logn
* therefore = O(nlogn)
*
* Average case: when randomly selected as any one of the position,
* the partition index or iterator is an average index
* T(n) = T(n - i) + T(i - 1) + cn
* = 1/n * summation(T(n - i) + T(i - 1)) + cn
* = O(nlogn)
*
* Worst case: unblanced in each partition
* T(n) = T(n - 1) + cn
* = T(n - 2) + c(n - 1) + cn
* = T(n - k) + c(n - k + 1 + ... + n)
* where k = n-1
* therefore
* T(n) = T(1) + c*(2 + 3 + ... + n)
* = O(n^2)
*
* auxiliary space: (non-stable) worst-case = O(1)
*-----------------------------------------------------------------------------
*/
- -

Implementations

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
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
 #ifndef SORTINGALGORITHMS_H_
#define SORTINGALGORITHMS_H_

#include <ctime>
#include <cstddef>
#include <cstdlib>
#include <iterator>
#include <stdexcept>
#include <algorithm>
#include <iostream>

// array-based version, first always is the index to indicate the
// first position of a sequence, last is the index to indicate the
// last position of a sequence. partitionIndex is the split point
template <typename T>
std::size_t Partition(T*p, const std::size_t first, const std::size_t last){
T pivot = p[last];
std::size_t partitionIndex = first;
for(std::size_t i = first; i != last; ++i){
if(p[i] <= pivot){
T temp = p[i];
p[i] = p[partitionIndex];
p[partitionIndex] = temp;
++partitionIndex;
}
}
T temp = p[partitionIndex];
p[partitionIndex] = p[last];
p[last] = temp;
return partitionIndex;
}

template <typename T>
void QuickSort(T*p, const std::size_t first, const std::size_t last){
if(first >= last) return;
std::size_t partitionIndex = Partition(p, first, last);
if(partitionIndex != 0){
QuickSort(p, first, partitionIndex - 1);
}
QuickSort(p, partitionIndex + 1, last);
}

// iterator-based version STL style (C++11)
// partitionIter denotes the position of the
// split point
template<typename BidirrectionalIterator>
BidirrectionalIterator Partition(BidirrectionalIterator begin,
BidirrectionalIterator end){
auto pivot = std::prev(end);
auto partitionIter = begin;

for(auto iter = begin; iter != pivot; ++iter){
if(*iter <= *pivot){
std::iter_swap(iter, partitionIter);
++partitionIter;
}
}
std::iter_swap(partitionIter, pivot);
return partitionIter;
}

template <typename BidirrectionalIterator>
void QuickSort(BidirrectionalIterator begin, BidirrectionalIterator end){
if(std::distance(begin, end) <= 1) return;
auto partitionIter = Partition(begin, end);
if(std::prev(partitionIter) != begin){
QuickSort(begin, partitionIter);
}
QuickSort(std::next(partitionIter), end);
}

// user-defined comparator + improved using random selected pivot
// return a random integer in the range [0, n)
int nrand(int n)
{
if(n <= 0 || n > RAND_MAX)
throw std::domain_error("Argument to nrand is out of range");

const int bucket_size = RAND_MAX /n;
int r;

do r = rand() / bucket_size;
while(r >= n);

return r;
}

template<typename BidirrectionalIterator, typename Comparator>
BidirrectionalIterator Partition(BidirrectionalIterator begin,
BidirrectionalIterator end, Comparator comp){
std::srand (std::time(nullptr));
auto randomIndex = nrand(std::distance(begin, end));
std::cout << randomIndex << "nihao" << std::endl;
auto pivot = std::prev(end);
std::iter_swap(pivot, std::next(begin, randomIndex));

auto partitionIter = begin;
for(auto iter = begin; iter != pivot; ++iter){
if(comp(*iter, *pivot)){
std::iter_swap(iter, partitionIter);
++partitionIter;
}
}
std::iter_swap(partitionIter, pivot);
return partitionIter;
}

template <typename BidirrectionalIterator, typename Comparator>
void QuickSort(BidirrectionalIterator begin, BidirrectionalIterator end,
Comparator comp){
if(std::distance(begin, end) <= 1) return;
auto partitionIter = Partition(begin, end, comp);
if(std::prev(partitionIter) != begin){
QuickSort(begin, partitionIter, comp);
}
QuickSort(std::next(partitionIter), end, comp);
}

#endif /* SORTINGALGORITHMS_H_ */

-

Test program-main.cpp

-
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
65
66
67
68
69
70
71
#include <iostream>	// std::cout, endl
#include <string> // std::string
#include <list> // std::list
#include <vector> // std::vector
#include "SortingAlgorithms.h"

using std::cout; using std::cin;
using std::endl; using std::list;
using std::vector; using std::string;

// struct defined for testing
struct student{
string name;
int age;
};

// comparator 1
template<class T>
bool compare_age(const T& x, const T& y){
return x.age < y.age;
}

// comparator 2
template<class T>
bool compare_name(const T& x, const T& y){
return x.name < y.name;
}

int main(){
// test 1: array-based version
double arr[10] = {10, 9, 8, 7, 6, 5, 4, 3, 2, 1};
QuickSort(arr, 0, 9);
cout << "Sorted array: ";
for(int i = 0; i != 10; ++i)
cout << arr[i] << " ";
cout << "\n";

// test 2: iterator-based version
string str("eclipseworkspace");
QuickSort(str.begin(), str.end());
cout << "Sorted string " << str << "\n";

list<int> l{10, 9, 8, 7, 6, 5, 4, 3, 2, 1};
QuickSort(l.begin(), l.end());
cout << "Sorted list: ";
for (auto i: l)
cout << i << " ";

cout << endl;

// test 3: user-defined comparator
vector<student> students;
while(cin){
student temp;
cin >> temp.name >> temp.age;
if(cin)
students.push_back(temp);
}

QuickSort(students.begin(), students.end(), compare_age<student>);
cout << "Sorted vector according to age:\n";
for (auto i: students)
cout << i.name << " " << i.age << "\n";

QuickSort(students.begin(), students.end(), compare_name<student>);
cout << "Sorted vector according to name:\n";
for (auto i: students)
cout << i.name << " " << i.age << "\n";

return 0;
}
+ -

Outputs:

-
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
Sorted array: 1 2 3 4 5 6 7 8 9 10 
Sorted string acceeeiklopprssw
Sorted list: 1 2 3 4 5 6 7 8 9 10
John 18
Mike 16
Liam 23
Anna 26
Bobo 21
2nihao
1nihao
1nihao
Sorted vector according to age:
Mike 16
John 18
Bobo 21
Liam 23
Anna 26
2nihao
1nihao
Sorted vector according to name:
Anna 26
Bobo 21
John 18
Liam 23
Mike 16
+ + + + Logical ViewHow to efficiently store a sequence of values (aka. a List) of a given type is crucial for any non-trivial program due to the limited memo + ... + +
+ + Read more » + +
+ +
+ + + + + -
+ +
+ + + + + + + +
-
+ +
+

+ + +
+ + + + + + + - +
-
- + +
+ + +
-

- - -

+ + + +

+ +

+ +
+ + + + + +
+ + + + + + + + Quick Sort1234567891011121314151617181920212223242526272829303132333435363738394041424344454647/*----------------------------------------------------- + ... + +
+ + Read more » + +
+ + + +
+ + + + + + + + + + +
+ + + + + + + + +
+ +
+
+ + + +
+ + + + + + + + + + + +
+ + + +
+ + + + + + + +
+ + + +

+ +

+ + +
+ @@ -1093,95 +1639,357 @@

-

Merge Sort

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
/*----------------------------------------------------------------------------- 
* main.cpp || Created on: 16 May 2018 || Author: Liam
*-----------------------------------------------------------------------------
* this program tests three implementation of the merge sort algorithm.
*
* Logic:
* 1. recursively partition the sequence a mid point, until that there is only
* one element left, that is when it cannot be partitioned further.
* 2. each pair of partitioned parts will be rearranged in non-decreasing order.
*
* Complexity analysis:
* time complexity: Best, Average, Worst = O(nlogn)
* auxiliary space: worst-case = O(n)
*
*-----------------------------------------------------------------------------
*/
+ + + + + + Merge Sort12345678910111213141516/*----------------------------------------------------------------------------- * main.cpp || Created on: 16 May 201 + ... + +
+ + Read more » + +
+ + + +
+ + + + + + + + + + +
+ + + + + -

Implementations:

-
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
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
#ifndef SORTINGALGORITHMS_H_
#define SORTINGALGORITHMS_H_

#include <cstddef>
#include <vector>
#include <iterator>
#include <algorithm>

// array-based version
template <typename T>
void Merge(T* left, std::size_t left_size, T* right, std::size_t right_size){
T left_copy[left_size];
T right_copy[right_size];

/* O(n): n = left_size + right_size */
for(std::size_t i = 0; i != left_size; ++i)
left_copy[i] = left[i];
for(std::size_t i = 0; i != right_size; ++i)
right_copy[i] = right[i];

/* O(n): n = left_size + right_size */
std::size_t i, j, k;
i = j = k = 0;
while(i != left_size && j != right_size){
if(left_copy[i] <= right_copy[j]){
left[k] = left_copy[i];
++i;
}else{
left[k] = right_copy[j];
++j;
}
++k;
}

while(i != left_size){
left[k] = left_copy[i];
++i;
++k;
}

while(j != right_size){
left[k] = right_copy[j];
++j;
++k;
}
}

template <typename T>
void MergeSort(T* p, std::size_t n){
std::size_t mid = n/2;
if(mid == 0) return;

MergeSort(p, mid);
MergeSort(p + mid, n - mid);

/* O(n): n = n*/
Merge(p, mid, p + mid, n - mid);
}

// iterator-based version STL style (C++11)
template <typename ForwardIterator>
void Merge(ForwardIterator begin, ForwardIterator midIterator,
ForwardIterator end){
typedef typename std::iterator_traits<ForwardIterator>::value_type Type;

/* O(n), n = end - begin*/
std::vector<Type> left(begin, midIterator);
std::vector<Type> right(midIterator, end);

auto iter_l = left.begin();
auto iter_r = right.begin();

/* O(n), n = end - begin*/
while(iter_l != left.end() && iter_r != right.end()){
*begin++ = *iter_l <= *iter_r ? *iter_l++ : *iter_r++;
}

std::copy(iter_l, left.end(), begin);
std::copy(iter_r, right.end(), begin);
}

template <typename ForwardIterator>
void MergeSort(ForwardIterator begin, ForwardIterator end){

// O(1) for random access iterator
// O(n) for others: n = end - begin
auto mid = std::distance(begin, end)/2;
if(mid == 0) return;

// O(1) for random access iterator
// O(n) for others: n = end - begin
auto midIterator = std::next(begin, mid);
MergeSort(begin, midIterator);
MergeSort(midIterator, end);

// O(n): n = end - begin
Merge(begin, midIterator, end);
}

// iterator-based version with user-defined comparator
template <typename ForwardIterator, typename Comparator>
void Merge(ForwardIterator begin, ForwardIterator midIterator,
ForwardIterator end, Comparator comp){
typedef typename std::iterator_traits<ForwardIterator>::value_type Type;

/* O(n), n = end - begin*/
std::vector<Type> left(begin, midIterator);
std::vector<Type> right(midIterator, end);

auto iter_l = left.begin();
auto iter_r = right.begin();

/* O(n), n = end - begin*/
while(iter_l != left.end() && iter_r != right.end()){
*begin++ = comp(*iter_l, *iter_r) ? *iter_l++ : *iter_r++;
}

std::copy(iter_l, left.end(), begin);
std::copy(iter_r, right.end(), begin);

}

template <typename ForwardIterator, typename Comparator>
void MergeSort(ForwardIterator begin, ForwardIterator end, Comparator comp){

// O(1) for random access iterator
// O(n) for others: n = end - begin
auto mid = std::distance(begin, end)/2;
if(mid == 0) return;

// O(1) for random access iterator
// O(n) for others: n = end - begin
auto midIterator = std::next(begin, mid);
MergeSort(begin, midIterator, comp);
MergeSort(midIterator, end, comp);

// O(n): n = end - begin
Merge(begin, midIterator, end, comp);
}

#endif /* SORTINGALGORITHMS_H_ */
+ + +
+ +
+

+ + + +
-

Test program-main.cpp

-
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
65
66
67
68
69
70
71
#include <iostream>	// std::cout, endl
#include <string> // std::string
#include <list> // std::list
#include <vector> // std::vector
#include "SortingAlgorithms.h"

using std::cout; using std::cin;
using std::endl; using std::list;
using std::vector; using std::string;

// struct defined for testing
struct student{
string name;
int age;
};

// comparator 1
template<class T>
bool compare_age(const T& x, const T& y){
return x.age <= y.age;
}

// comparator 2
template<class T>
bool compare_name(const T& x, const T& y){
return x.name <= y.name;
}

int main(){
// test 1: array-based version
double arr[10] = {10, 9, 8, 7, 6, 5, 4, 3, 2, 1};
MergeSort(arr, 10);
cout << "Sorted array: ";
for(int i = 0; i != 10; ++i)
cout << arr[i] << " ";
cout << "\n";

// test 2: iterator-based version
string str("eclipseworkspace");
MergeSort(str.begin(), str.end());
cout << "Sorted string " << str << "\n";

list<int> l{10, 9, 8, 7, 6, 5, 4, 3, 2, 1};
MergeSort(l.begin(), l.end());
cout << "Sorted list: ";
for (auto i: l)
cout << i << " ";

cout << endl;

// test 3: user-defined comparator
vector<student> students;
while(cin){
student temp;
cin >> temp.name >> temp.age;
if(cin)
students.push_back(temp);
}

MergeSort(students.begin(), students.end(), compare_age<student>);
cout << "Sorted vector according to age:\n";
for (auto i: students)
cout << i.name << " " << i.age << "\n";

MergeSort(students.begin(), students.end(), compare_name<student>);
cout << "Sorted vector according to name:\n";
for (auto i: students)
cout << i.name << " " << i.age << "\n";

return 0;
}
-

Outputs:

-
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
Sorted array: 1 2 3 4 5 6 7 8 9 10 
Sorted string acceeeiklopprssw
Sorted list: 1 2 3 4 5 6 7 8 9 10
John 18
Mike 16
Liam 23
Anna 26
Bobo 21
Sorted vector according to age:
Mike 16
John 18
Bobo 21
Liam 23
Anna 26
Sorted vector according to name:
Anna 26
Bobo 21
John 18
Liam 23
Mike 16
+ + + + + + + + + +
+ + + +
+ + + + + + + +
+ + + +

+ +

+ + + +
+ + + + + +
+ + + + + + + + Insertion Sort123456789101112131415161718192021222324/*----------------------------------------------------------------------------- * main.cpp || Cr + ... + +
+ + Read more » + +
+ +
+ + + + + -
+ +
+ + + + + + + +
-
+ +
+
+ + +
+ + + + + + + - +
+ -
- +
+ + + - -
-

+ +
+ + + +

+ +

+ + +

-
+ @@ -1189,36 +1997,58 @@

-

Insertion Sort

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
/*----------------------------------------------------------------------------- 
* main.cpp || Created on: 15 May 2018 || Author: Liam
*-----------------------------------------------------------------------------
* this program tests three versions of insertion sort implementations.
*
* Logic:
* 1. the sequence can always be divided into two parts: sorted and unsorted.
* We loop thru from the second position to the last position, before each
* loop, the elements on the left-side of the position are sorted. we can
* call this position as sortedIndex. After each loop, [0, sortedIndex] is
* sorted, and then we forward the sortedIndex 1 position.
* 2. in each loop, an embeded iteration starts from the initial position to
* the prev of the sortedIndex, or in reverse order, from the prev of the
* sortedIndex to the begining, comparing each value denoted in the range
* with the value denoted by sortedIndex. When found the first element that
* is greater than the value denoted by sortedIndex, we insert here
* the element denoted by sortedIndex.
*
* Complexity analysis:
* time complexity: Best = O(n), Average, Worst O(n^2)
* auxiliary space: worst-case = O(1)
*
*-----------------------------------------------------------------------------
*/
- -

Implementations

-
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
#ifndef SORTINGALGORITHMS_H_
#define SORTINGALGORITHMS_H_

#include <cstddef>
#include <algorithm>
#include <iterator>

// array-based version
template <class T>
void InsertionSort(T* p, std::size_t n){
if (n == 0) return;
for(std::size_t i = 1; i != n; ++i){
T value = p[i];
std::size_t sortedIndex = i;
// the left-side of the sortedIndex is sorted
while(sortedIndex >0 && p[sortedIndex - 1] > value){
p[sortedIndex] = p[sortedIndex - 1];
sortedIndex = sortedIndex - 1;
}
p[sortedIndex] = value;
}
}

// iterator-based version STL-stype (C++11)
template <typename ForwardIterator>
void InsertionSort(ForwardIterator begin, ForwardIterator end){
if(begin == end) return;
for (auto iter = std::next(begin); iter != end; ++iter){
auto insertPoint = std::upper_bound(begin, iter, *iter); // logN
std::rotate(insertPoint, iter, std::next(iter)); // N
}
}

// iterator-based version with comparator
template <typename ForwardIterator, typename Comparator>
void InsertionSort(ForwardIterator begin, ForwardIterator end,
Comparator comp){
if(begin == end) return;
for (auto iter = std::next(begin); iter != end; ++iter){
auto insertPoint = std::upper_bound(begin, iter, *iter, comp);
std::rotate(insertPoint, iter, std::next(iter));
}
}

#endif /* SORTINGALGORITHMS_H_ */
- -

Test program-main.cpp

-
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
65
66
67
68
69
70
71
#include <iostream> // std::cin, cout, endl
#include <vector> // std::vector
#include <string> // std::string
#include <list> // std::list
#include "SortingAlgorithms.h"

using std::cout; using std::vector;
using std::endl; using std::string;
using std::list; using std::cin;

// struct defined for testing
struct student{
string name;
int age;
};

// comparator 1
template<class T>
bool compare_age(const T& x, const T& y){
return x.age < y.age;
}

// comparator 2
template<class T>
bool compare_name(const T& x, const T& y){
return x.name < y.name;
}

int main(){
// test 1: array-based version
double arr[10] = {10, 9, 8, 7, 6, 5, 4, 3, 2, 1};
InsertionSort(arr, arr+ 10);
cout << "Sorted array: ";
for(int i = 0; i != 10; ++i)
cout << arr[i] << " ";
cout << "\n";

// test 2: iterator-based version
string str("eclipseworkspace");
InsertionSort(str.begin(), str.end());
cout << "Sorted string " << str << "\n";

list<int> l{10, 9, 8, 7, 6, 5, 4, 3, 2, 1};
InsertionSort(l.begin(), l.end());
cout << "Sorted list: ";
for (auto i: l)
cout << i << " ";

cout << endl;

// test 3: user-defined comparator
vector<student> students;
while(cin){
student temp;
cin >> temp.name >> temp.age;
if(cin)
students.push_back(temp);
}

InsertionSort(students.begin(), students.end(), compare_age<student>);
cout << "Sorted vector according to age:\n";
for (auto i: students)
cout << i.name << " " << i.age << "\n";

InsertionSort(students.begin(), students.end(), compare_name<student>);
cout << "Sorted vector according to name:\n";
for (auto i: students)
cout << i.name << " " << i.age << "\n";

return 0;
}
- + -

Outputs

-
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
Sorted array: 1 2 3 4 5 6 7 8 9 10 
Sorted string acceeeiklopprssw
Sorted list: 1 2 3 4 5 6 7 8 9 10
John 18
Mike 16
Liam 23
Anna 26
Bobo 21
Sorted vector according to age:
Mike 16
John 18
Bobo 21
Liam 23
Anna 26
Sorted vector according to name:
Anna 26
Bobo 21
John 18
Liam 23
Mike 16
+ + + + + + +
+ + Read more » + +
+ +
+ + + + + -
+ +
+ + + + + + + +
-
-

+ + +
+ -
+ + + @@ -1226,179 +2056,265 @@

{ - if (!event.target.matches('.tabs-comment .tab-content .tab-pane')) return; - let commentClass = event.target.classList[1]; - localStorage.setItem('comments_active', commentClass); - }); - } - + +

+ - - + - + + - + + + @@ -1425,179 +2053,265 @@

{ - if (!event.target.matches('.tabs-comment .tab-content .tab-pane')) return; - let commentClass = event.target.classList[1]; - localStorage.setItem('comments_active', commentClass); - }); - } - + + + - - + - + + + + + + + + + + + - +
+ -
- +
+ + +
-

- - -

+ + + +

+ +

+ -
- - - - -
- - -

Generic function - an example

Generic functions are functions written in a way that is independent of any particular type. When we use a generic program, we supply the type(s) or value(s) on which that instance of the program will operate(Lippman etc. 2012). In C++, we can create generic functions by defining template functions, allowing writing a single definition for a family of functions or types that behave similarly but have different types of paratmters. The only difference can be summarized as parameters, namely, template parameters. Take the median function as an example, its template can be defined as follows.

-
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
template<class T>
T median(vector<T> v)
{
typedef typename vector<T>::size_type vec_sz;

vec_sz size = v.size();
if(size == 0)
throw domain_error("median of an empty vector");

sort(v.begin(), v.end());

vec_sz mid = size/2;

return size % 2 ? (v[mid] + v[mid - 1])/2 : v[mid];
}
-

From the definition, we observe

-
    -
  1. A template starts with keyword template followed by type parameter(s) enclosed by a pair of angle brackets.
  2. -
  3. type parameter(s) define the names that can be used within the scope of the function. They refer to types, not variables. In other words, the types of template parameters can be regarded as variables that have types specified by type parameters(s).
  4. -
  5. type parameter(s) are specified following the keyword class or typename.
  6. -
  7. the template tells implementation that vector::size_type is the name of a type by adding keyword typename before.
  8. -
-

When we call median and pass a template argument vector, the implementation will create and compile an instance of the function as if the function median(vector). This is so called template instantiation. But how is the template compiled?

-

When the compiler encounters the definition of a template, it doesn’t generate code. It generates only when the template is instantiated. Remembering that when we call a function, we need to supply only its declaration. But if we want to call the template, we need to supply not only its declaration but also its definition in order to instantiate it. Therefore, the header file of a template generally includes the source file also via a #include or directly. I found an article How To Organize Template Source Code, where gives detailed explinations on how to organize template source code and three solutions.

-

Due to the feature of generating code during instantiation, compilation errors may occur during three stages:

-
    -
  1. the first stage is when we compile the template itself. But the compiler can only detect some syntax errors like forgetting a semicolon, misspelling a variable name etc..
  2. -
  3. the second stage is when we use a template, before it is instantiated. The compiler typically will check that whether the number of the arguments is appropriate, whether two arguments that are supposed to have the same type do so.
  4. -
  5. the type-related errors mostly occurs during the third stage when the template is instantiated. Though implementations manage instantiation on their own ways, the type-related errors may be reported at link time due to that implementations assume the type is possibly defined in other unit and hence leave it to linker to resolve.
  6. -
-

In fact, we have applied many templates in previous programs, such as vector and list, and all standard library algorithms.

-

Algorithms and iterators

As shown in above example, the template is type independent. However, it is also clear that the function to instantiate requires its argument supporting all operations included in the function. In above case, the types stored in the vectors that are passed to the median function must support addition and division.

-

The standard algorithms are not limited by the type of containers and obviously are data-structure independent function templates. It is known that the parameters taken by algorithms includes iterators and others. This implies that iterators to pass should support certain operations used inside of the algorithms. But we also know that some containers may support operations that others do not. For example, vector support random access elements via iterators while list doesn’t. Therefore, it is important to restrict the right of iterators depending on operations that an algorithm include and operations different containers support. For this reason, the library defines five iterator categories with specifying the collection of iterator operations for each category. By doing so, we know exactly what effect of an algorithm can have on an container and which container can use which algorithms.

-

Sequential read-only access

Some algorithms only require iterators that can access elements sequentially. For example, the find algorithm:

-
1
2
3
4
5
6
7
template <class In, class X>
In find(In beg, In end, const X &x)
{
while (begin != end && *begin != x)
++begin;
return begin;
}
-

The iterators of type In access elements sequentially via
operations *, ++. Also, they can be compared using !=. Alternatively,

-
1
2
3
4
5
6
7
8
template <class In, class X>
In find(In begin, In end, const X &x)
{
if (begin == end || *begin == x)
return begin;
begin++;
return find(begin, end, x);
}
-

This version of find uses another strategy, recursively calling itself. We have observed that the iterators may also need to support operations ++(postfix) and ==.

-

Furthermore, the iterators ought to support member access via ->, e.g. it->first. It has the same effect as (*it).first.

-

In summary, iterators that offers sequential read-only access to elements of a container should supports ++(both prefix and postfix), == and !=, * and ->. Such iterators are named as input iterators. All standard container meet the requirements of input iterator.

-

Sequential write-only access

Some algorithms may require write elements of a sequence via iterators, for example

-
1
2
3
4
5
6
7
template <class In, class Out>
Out copy(In begin, In end, Out dest)
{
while(begin != end)
*dest++ = *begin++;
return dest;
}
-

The copy algorithm takes three iterators, the first two of which denote the range of a sequence to copy while the third iterator denotes the initial position of the destination. The iterators of type In offer sequential read-only access to elements and hence are input iterators. The type Out gives the third iterator the ability to write elements via *dest = and dest++. As with the find algorithm above, such iterators should also support ++dest.

-

The fact that iterators of type Out are used for output only also implies that

-
    -
  1. each element pointed by the iterator is written a value only once and then the iterator is incremented.

    -
  2. -
  3. the iterator can not be incremented twice without assignments to the elements that it refers to.

    -
  4. -
-

In summary, such iterators are output iterators. All standard containers as well as back_inserter meet the requirements of output iterator. Noting that in this function, the left side deference operator is used to write to the underlying elements while the right side deference operator is used to read the underlying elements only.

-

Sequential read-write access

There is another situation that we want to both read and write the elements of a sequence, but only sequentially. For example

-
1
2
3
4
5
6
7
8
9
10
template<class For, class X>
void replace(For beg, For end, const X &x, const X &y)
{
while (beg != end)
{
if (*beg == x)
*beg = y;
++beg;
}
}
-

The replace algorithm examines each element in the range [beg, end) and assigns value y to those elements that are equal to x. Apparently, iterators of type For should support all operations supported by an input operator as well as an output operator. Moreover, they can read and write the same elements multiple times. Such a type is a forward iterator. Some operations that such a type need to support are

-
    -
  1. *it (for both reading and writing)
  2. -
  3. ++ (both prefix and postfix)
  4. -
  5. == and !=
  6. -
  7. ->
  8. -
-

All standard containers meet the requirements of forward iterator.

-

Reversible access

All above iterators access elements in a container in a forward sequence. But some functions may require get elements in reverse order, for example

-
1
2
3
4
5
6
7
8
9
template <class Bi> void reverse(Bi begin, Bi end)
{
while (begin != end)
{
--end;
if (begin != end)
swap (*begin++, *end);
}
}
-

The iterators of type Bi moves backward from end to begin via . Then, the swap algorithm exchanges values of two elements. Such iterators meet all requirements of forward iterator and support (both prefix and postfix). They are bidirectional iterators. All standard containers meet the requirements of bidirectional iterators.

-

Random access

All above iterators access elements in a forward or backward sequence, however, some functions need to access elements starting from arbitrary positions. For example

-
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
template<class Ran, class X>
bool binary_search(Ran begin, Ran end, const X &x)
{
while (begin < end)
{
// find the midpoint of the range
Ran mid = begin + (end - begin)/2;

// see which part of the range contains x; keep looking only in that part
if (x < *mid)
end = mid;
else if (*mid <x)
begin = mid + 1;

// if we got here, then *mid == x so we are done
else
return true;
}
return false;
}
-

The binary search algorithm looks for a particular element in a sorted container. It always starts to search from the middle point of a sequence, which relies on the ability to do arithmetic on iterators. Such an iterator is called a random access iterator. Specifically, if p and q are random access iterators, they should support all operations that a bidirectional iterator supports, as well as following arithmetic operations

-
    -
  1. p + n, p - n, n + p
  2. -
  3. p-q
  4. -
  5. p[n] (equivalent to *(p + n))
  6. -
  7. p > q, p < q, p <= q, p >= q
  8. -
-

Standard sort algorithm requires random-access iterators. Therefore, vector and string can use standard sort function as their iterators are random-access iterators. list iterators are not random-access iterators and hence list defines its own member sort instead of using the standard sort.

-

Summary

From above analysis, we know that forward, bidirectional and random-access iterators are also valid input iterators as they all meet the requirements of input iterator. In addition, all forward, bidirectional and random-access iterators that are not constant iterators are also valid output iterators.

-

From the perspective of containers, different category of iterator makes some containers distinct, for example, list iterators are bidirectional iterators, forward_list iterators are forward_iterators, vector and string iterators are random access iterators.

-

But why we need input/output iterator? One reason is that not all iterators are assicoated with containers. For example, back_inserter() is an iterator that meet and only meet the requirements of output iterator.

-

Another typical example is that the standard library provides iterators that can be bound to input and output streams, namely, istream_iterator and ostream_iterator.
Apparently, istream_iterator is input iterator that allows us to read successive elements from an input stream.
ostream_iterator is output iterator that allows us to write sequentially to an output stream.

-
1
2
3
vector<int> v;
// read ints from the standard input and append them to v
copy(istream_iterator<int>(cin), istream_iterator<int>(), back_insert(v));
-

This example shows that the first istream_iterator is bound to cin and expects to read values of type int. But the second istream_iterator is not bound to any file.
This is because istream_iterator type has a default value with a sepcial property such that any istream_iterator that has reached end-of-file or is an error state will appear to be equal to the default value. Therefore, we can use the default value of a istream_iterator together with the first input iterator to denote the sequence in the input stream.

-

Similarly, we can use ostream_iterator to write elements to an output stream.

-
1
2
// write the elements of v each separated from the other by a space
copy(v.begin(), v.end(), ostream_iterator<int> (cout, " "));
-

This statement uses copy algorithm to copy the vector v onto the standard output. ostream_iterator is bound to cout with an additional argument, that is, a space in this case. This additional argument specifies a value to be written after each element and typically is a string literal.

-

Rewrite the split function

Now we apply the new knowledge learned above to rewrite the split function as a template such that it is data-structure independent.

-
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
template <class Out>
void split(const string &str, Out os)
{
typedef string::const_iterator iter;

iter i = str.begin();
while(i != str.end())
{
// ignore leading spaces
i = find_if(i, str.end(), not_space);

// find end of next word
iter j = find_if(i, str.end(), space);

// copy the characters in [i,j)
if(i != str.end())
*os++ = string(i, j);
}
}
-

This function has return type void, i.e. nothing to return. If we want to store each word contained in a line of inputs into a vector, we just need to pass an output iterator. This is also true for a list. For example

-
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
int main()
{
// list<string> words;
vector<string> words;

string line;
while(getline(cin, line))
{
split(line, back_inserter(words));
}

for(vector<string>::const_iterator it = words.begin(); it != words.end(); ++it)
cout << *it << endl;
return 0;
}
-

Alternatively, we may write all words onto the standard output directly. I take this as an exercise and present a complete program below.

-

mainfunction.cpp

-
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
#include <iostream>
#include <iterator>
#include <string>
#include "split.h"

using std::cin; using std::ostream_iterator;
using std::cout; using std::string;
using std::endl; using std::back_inserter;

int main(){

string line;
while (getline(cin, line))
{
split(line, ostream_iterator<string>(cout, "\n"));
}

return 0;
}
- -

split.h

-
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
#ifndef GUARD_SPLIT_H
#define GUARD_SPLIT_H

#include <string>
#include <algorithm>

// true if the argument is whitespace, false otherwise
bool space(char c)
{
return isspace(c);
}

// false is the argument is whitespace, true otherwise
bool not_space(char c)
{
return !isspace(c);
}

// template declaration and definition
template <class Out>
void split(const std::string &str, Out os)
{
typedef std::string::const_iterator iter;

iter i = str.begin();
while(i != str.end())
{
// ignore leading spaces
i = std::find_if(i, str.end(), not_space);

// find end of next word
iter j = std::find_if(i, str.end(), space);

// copy the characters in [i,j)
if(i != str.end())
*os++ = std::string(i, j);
i = j;
}
}
#endif /* GUARD_SPLIT_H */
- -

Noting that I didn’t separate the definition and declaration of the split template to make them be visible to compiler in the point of instantiation.

-

Let’s type some words

-
1
2
3
4
what 
a
beautiful
name
-

The results are as expected

-
1
2
3
4
5
6
7
8
what
what
a
a
beautiful
beautiful
name
name
-

Again type

-
1
what a beautiful name
-

The program gives

-
1
2
3
4
what
a
beautiful
name
- -
- - - - -
-
-
-
- - - + + + - - - -
- + + - + - -
-

- -

+
+ + + + + + + + 3,315 + + - + -
- - +
+ @@ -1781,107 +1817,157 @@

-

Exercise 7-5

Reimplement the grammar program using a list as the data structure in which webuild the sentence.

-

Solutions & Results

There is no any other differences between the list-based version and the vector-based version except that we replace vectors with lists literally, and hence No further discussion about this exercise. The original program can be found here Example 3.

-

Exercise 7-6

Reimplement the gen_sentence program using two vectors: One will hold the fullyunwound, generated sentence, and the other will hold the rules and will be used as a stack.Do not use any recursive calls.

-

Solution & Results

To be updated.

-

Exercise 7-7

Change the driver for the cross-reference program so that it writes line if there is only one line and lines otherwise.

-

Solution & Results

The solution is to add and check a condition that whether the vector where holds line numbers only contain one line number. If there is only one line number, we use line else use lines.

-
1
2
3
4
if ((it->second).size() == 1)
cout << "line:" << endl;
else
cout << "lines:" << endl;
-

I only present the revised file here and please find other file in Exercise 7-4.

-

mainfunction.cpp

-
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
#include <map>		// to get the declaration of map
#include <string> // to get the declaration of string
#include <vector> // to get the declaration of vector
#include <iostream> // to get the declaration of cin, cout, endl;
#include <sstream> // to get the declaration of ostringstream
#include <cctype> // to get the declaration of isspace
#include "split.h" // to get the declaration of function split
#include "xref.h" // to get the declaration of xref

using std::map; using std::cout;
using std::cin; using std::endl;
using std::string; using std::vector;
using std:: ostringstream; using std::isspace;

int main()
{
// call xret using split by default
map<string, vector<int> > ret = xref(cin);

// set the length for each line of outputs
string::size_type line_length = 20;

// write the result
for(map<string, vector<int> >::const_iterator it = ret.begin(); it != ret.end(); ++it)
{
// write the word
cout << it->first << " occurs on " << endl;

if ((it->second).size() == 1)
cout << "line:" << endl;
else
cout << "lines:" << endl;

// followed by one or more line numbers
vector<int>::const_iterator line_it = it->second.begin();
cout << *line_it; // write the first line number

// scan the rest line numbers
++line_it;
ostringstream os;
while(line_it != it->second.end())
{
// store line numbers into ostringstream object
os << ", " << *line_it;
++line_it;
}

// get the contents from line_numbers
string line_numbers = os.str();

// write each line of outputs
for(string::size_type i = 0; i != line_numbers.size(); ++i)
{
cout << line_numbers[i];
if((i + 1) % line_length == 0)
cout << endl;
}
// write a blank line to separate each words
cout << endl;
}
return 0;
}
-

I use the inputs as same as inputs used in exercise 7-4 and get following results, showing the effect of above changes.

-
1
2
3
4
5
6
7
ABC occurs on lines:
1, 2, 3, 4, 5, 6, 7,
8, 9, 10, 11, 12, 13
, 14, 15, 16, 17, 18
, 19, 20, 21
DEF occurs on line:
2
- -

Exercise 7-8

Change the cross-reference program to find all the URLs in a file, and write all the lines
on which each distinct URL occurs.

-

Solution & Results

Please find the program and analysis in Example 2-Test 2.

-

Exercise 7-9

(difficult) The implementation of nrand in §7.4.4/135 will not work for arguments greater than RAND_MAX. Usually, this restriction is no problem, because RAND_MAX is often the largest possible integer anyway. Nevertheless, there are implementations under which RAND_MAX is much smaller than the largest possible integer. For example, it is not uncommon for RAND_MAX to be 32767 (2^15 -1) and the largest possible integer to be 2147483647 (2^31 -1). Reimplement nrand so that it works well for all values of n.

-

Solution & Results

Recalling nrand function

-
1
2
3
4
5
6
7
8
9
10
11
12
13
int nrand(int n)
{
if(n <= 0 || n > RAND_MAX)
throw domain_error("Argument to nrand is out of range");

const int bucket_size = RAND_MAX /n;
int r;

do r = rand() / bucket_size;
while(r >= n);

return r;
}
-

nrand generates a random numbers in the range [0, n). The idea behind this function is that we divide the range[0, n(RAND_MAX/n)) into *n** pieces of equal size. Assuming RAND_MAX = 32767, n = 1000, random numbers r and random numbers generated from rand() have following relationships:

-
1
2
3
4
5
6
7
r (values)                   rand() (values of the range)

0 [0, 32)
1 [32, 64)
2 [64, 96)
... ...
999 [31968, 32000)
-

To be continued.

+ + + + Exercise 7-0Compile, execute, and test the programs in this chapter. +Solution & ResultsPlease find the programs and detailed analysis in Example 1 + ... + +
+ + Read more » + +
+ + +
+ + + + + -
+ +
+ + + + + + + +
-
+ +
+ + + +

+ + + + + + + - +
+ -
- +
+ + +
-

- - -

+ + + +

+ +

+
+ @@ -1889,121 +1975,58 @@

-

Exercise 7-0

Compile, execute, and test the programs in this chapter.

-

Solution & Results

Please find the programs and detailed analysis in Example 1, 2 and Example 3.

-

Exercise 7-1

Extend the program from §7.2/124 to produce its output sorted by occurrence count.That is, the output should group all the words that occur once, followed by those that occur twice, and so on.

-

Solution & Results

The key to the solution is building a map from occurrence numbers to corresponding words. The original program builds a map from each distinct word to its occurrence numbers. Therefore, we can simply inverse the original map. But noting there may be more than one words have the same occurrence numbers. The revised program is shown below:

-
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
#include <iostream>		// to get the declaration of cin, cout, endl
#include <vector> // to get the declaration of vector
#include <string> // to get the declaration of string
#include <map> // to get the declaration of map

using std::cin; using std::cout;
using std::endl; using std::string;
using std::map; using std::vector;

int main()
{
// store each word and an associated counter
string s;
map<string, int> counters;

// read the input, keeping track of each word and how often we see it
while (cin >> s)
{
++counters[s];
}

// sort words stored in counters according to the occurence count
map<int, vector<string> > sorted_counters;
for (map<string, int>::const_iterator it = counters.begin(); it != counters.end(); ++it)
{
sorted_counters[it->second].push_back(it->first);
}

// write the words and associated counts
cout << "Words and their associated counts:" << endl;
for (map<string, int>::const_iterator it = counters.begin(); it != counters.end(); ++it)
{
cout << it->first << "\t" << it->second << endl;
}

// write a blank line to separate the outputs of two maps
cout << endl;

// write occurrence count followed by the corresponding words
cout << "Occurrence count and the corresponding words:" << endl;
for (map<int, vector<string> >::const_iterator it = sorted_counters.begin(); it != sorted_counters.end(); ++it)
{
cout << it->first;
for (vector<string>::const_iterator i = (it->second).begin(); i != (it->second).end(); ++i)
cout << ' ' << *i;
cout << endl;
}

return 0;
}
-

It can be observed that the values of the original map are stored into the new map as keys while the keys are stored as values in the new map. In addition, we specify vector to hold more words that have same occurrence numbers. Now let’s type some words and check the results.

-
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
Inputs:

a dog and a cat
cat is good dog is bad
human is ugly

Outputs:

Words and their associated counts:
a 2
and 1
bad 1
cat 2
dog 2
good 1
human 1
is 3
ugly 1

Occurrence count and the corresponding words:
1 and bad good human ugly
2 a cat dog
3 is
-

Yeah, it correctly sorts the words according to their occurrence numbers.

-
-

Exercise 7-2

Extend the program in §4.2.3/64 to assign letter grades by ranges:

-
1
2
3
4
5
A   90-100
B 80-89.99...
C 70-79.99...
D 60-69.99...
F < 60
-

The output should list how many students fall into each category.

-

Solution & Results

The key to solution is building a map from the letter grades, A, B, C, D, F, to the number of students who have the corresponding final grades. Therefore, the map can be defined as:

-
1
map<string, int> grades_count;
-

The strategy can be divided into four steps:

-
    -
  1. read students’ information
  2. -
  3. calculate final grade for each student
  4. -
  5. check the range of each final grade and get a letter grade (i.e. the key)
  6. -
  7. increment the value associated with the key returned in step 3
  8. -
-

Step 1 and step 2 are familar.

-

Step 3 needs a function on the final grade. I uses a simple if-else statement to complete it:

-
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
string letter_grade(double &grade)
{
if(grade < 0 || grade > 100)
throw domain_error("grade is outside of[0, 100]");
else if (grade >= 90)
return "A";
else if(grade >= 80 && grade < 90)
return "B";
else if(grade >= 70 && grade < 80)
return "C";
else if(grade >= 60 && grade < 70)
return "D";
else
return "F";
}
-

Step 4 is accomplished by the statement:

-
1
++grades_count[letter_grade(final_grade)];
-

letter_grade(final_grade) returns a letter grade based on the function above. Then, the map grades_count stores (if it is new ) the letter as the key and returns the associated value. Finally, applies ++ operator to increment the associated value, showing the counting process.

-

The complete program

The complete program is displayed below including files: mainfunction.cpp, grade.cpp, grade.h, Student_info.cpp and Student_info.h.

-

mainfunction.cpp

-
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
#include <iostream>		// to get the declaration of cin, cout, endl
#include <stdexcept> // to get the declaration of domain_error
#include <string> // to get the declaration of string
#include <vector> // to get the declaration of vector
#include <map> // to get the declaration of map
#include "Student_info.h" // to get the declaration of Student_info
#include "grade.h" // to get the declaration of grade

using std::cin;
using std::cout; using std::string;
using std::endl; using std::vector;
using std::domain_error; using std::map;

string letter_grade(double &grade)
{
if(grade < 0 || grade > 100)
throw domain_error("grade is outside of[0, 100]");
else if (grade >= 90)
return "A";
else if(grade >= 80 && grade < 90)
return "B";
else if(grade >= 70 && grade < 80)
return "C";
else if(grade >= 60 && grade < 70)
return "D";
else
return "F";
}

int main()
{
// read and store all the records
vector<Student_info> students;
Student_info record;
while(read(cin, record))
{
students.push_back(record);
}

map<string, int> grades_count;
for (vector<Student_info>::size_type i = 0; i != students.size(); ++i)
{
// compute each final grade letter grades and counting the letter grades
try{
double final_grade = grade(students[i]);
++grades_count[letter_grade(final_grade)];
} catch(domain_error e){
cout << e.what();
}
}

for(map<string, int>::const_iterator it = grades_count.begin();
it != grades_count.end(); ++it)
cout << it->first << '\t' << it->second << endl;

return 0;
}
- -

grade.cpp

-
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 <algorithm>
#include <stdexcept>
#include <vector>
#include "grade.h"
#include "Student_info.h"

using std::domain_error; using std::istream;
using std::vector; using std::sort;

// grade function 1
double grade(const Student_info &s)
{
return grade(s.midterm, s.final, s.homework);
}

// grade function 2
double grade(double midterm, double final, const vector<double> &hw)
{
if (hw.size() == 0)
throw domain_error("student has done no homework");
return grade(midterm, final, median(hw));
}

// grade function 3
double grade(double midterm, double final, double homework)
{
return 0.2*midterm + 0.4*final + 0.4*homework;
}

// compute the median of vector<double>
double median(vector<double> vec)
{
// get the size of the vector
typedef vector<double>::size_type vec_size;
vec_size size = vec.size();

// check whether the empty is empty
if (size == 0)
throw domain_error("median of an empty vector");

// sort the grades
sort(vec.begin(), vec.end());

// compute the median homework grade
vec_size mid = size/2;
return size % 2 == 0 ? (vec[mid] + vec[mid - 1])/2 : vec[mid];
}
- -

grade.h

-
1
2
3
4
5
6
7
8
9
10
11
12
#ifndef GUARD_grade_h
#define GUARD_grade_h

// grade.h
#include<vector>
#include "Student_info.h"

double grade(const Student_info &);
double grade(double, double, const std::vector<double> &);
double grade(double, double, double);
double median(std::vector<double>);
#endif
- -

Student.info

-
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
#include "Student_info.h"
using std::vector; using std::istream;

bool compare(const Student_info &x, const Student_info &y)
{
return x.name < y.name;
}

istream & read(istream &is, Student_info &s)
{
// reads and store the student's name, midterm and final exam grades
is >> s.name >> s.midterm >> s.final;

// reads and store all homework grades
read_hw(is, s.homework);
return is;
}

istream & read_hw(istream &in, vector<double> &hw)
{
if (in){
//get rid of previous contents
hw.clear();

// read homework grades
double x;
while(in >> x)
hw.push_back(x);

// clear the stream so that input will work for the next student
in.clear();
}
return in;
}
-

Student_info.h

-
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
#ifndef GUARD_Student_info
#define GUARD_Student_info

#include<iostream>
#include<string>
#include<vector>

struct Student_info{
std::string name;
double midterm, final;
std::vector<double> homework;
};

bool compare(const Student_info &, const Student_info &);
std::istream & read(std::istream &, Student_info &);
std::istream & read_hw(std::istream &, std::vector<double> &);
#endif
- -

Performance Test

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
Inputs:

Phqgh 24.7879 58.6263 64.0505
Nlfdx 95.4242 27.3636 91.0404
Cxggb 16.1818 95.4747 26.7172
Uxwfn 35.9495 3.11111 22.3333
Tkjpr 68.4747 44.6263 57.3737
Pnrvy 16.3535 90.4242 88.0606
Syycq 5.90909 29.7071 50.0606
Ffmzn 84.5455 56.404 66.7677
Vwsre 23.3737 38.1818 82.2929
Fxtls 4.30303 77.0606 73.8687
Dpooe 29.7778 73.9798 12.8687
Ejuvp 55.7475 31.5253 50.5051
Poeyl 91.0707 37.5758 87.5354
Jvrvi 21.8889 22.4646 6.30303
Hwqnq 55.101 59.2424 37.4848
Jjloo 91.3636 74.202 96.2121
Whmsn 34.5354 99.1818 38
Sfzkv 48.8384 7.21212 10.1717
Lyjyh 51 49.1919 56.9899
Nkkuf 89.0202 95.8586 93.4343

Outputs:

A 1
B 1
C 1
D 5
F 12
- -
-

Exercise 7-3

The cross-reference program from §7.3/126 could be improved: As it stands, if a word occurs more than once on the same input line, the program will report that line multiple times. Change the code so that it detects multiple occurrences of the same line number and inserts the line number only once.

-

Solution & Results

In the original program, we built a map from each distinct word to the line numbers in which the word appears.

-
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
map<string, vector<int> > xref(istream &in,
vector<string> find_words(const string &))
{
string line;
int line_number = 0;
map<string, vector<int> > ret;

// read the next line
while(getline(in, line))
{
++line_number;

// break the input line into words
vector<string> words = find_words(line);

// remember that each word occurs on the current line
for(vector<string>::const_iterator it = words.begin(); it != words.end();
++it)
{
ret[*it].push_back(line_number);
}
}
return ret;
}
- -

However, the line numbers for each word may repeatedly recorded. To avoid this problem, one solution is to check whether the line number has already been recorded. If the line number has been recorded, we ignore it, otherwise, we store it into the vector. We do not need to check all elements in the vector instead we only check the last stored line number. This is because that if a line number is repeatedly recorded, two elements (i.e. same line numbers) must be adjacent. Therefore, I add if statement as follows:

-
1
2
if(ret[*it].empty() || *(ret[*it].end() - 1) != line_number)
ret[*it].push_back(line_number);
-

I’ll give the complete program as well as test results in next exercise.

-

Exercise 7-4

The output produced by the cross-reference program will be ungainly if the input file is large. Rewrite the program to break up the output if the lines get too long.

-

Solution & Results

The key to the solution is managing the length of each line of outputs. Theoretically, the strategy can be divided into three steps:

-
    -
  1. convert all line numbers (except the first one) associated with a word into strings.
  2. -
  3. count the number of characters that have been written.
  4. -
  5. when the predetermined length of a line is reached, write a new line.
  6. -
-

The first two steps seems tedious to us. Fortunately, we can use stringstream objects to accomplish these easily.

-

stringstream is a stream class defined standard library. It provides IO facilities that operate on strings. For example, ostringstream object uses a string buffer to hold a sequences of characters for printing all outputs together. In addition, the sequence of characters can be accessed directly as a string object, using member function str. In this case, I define such an object os to hold all line numbers as well as additional spaces between two line numbers.

-
1
os << ", " << *line_it;
-

line_it is an iterator that refers to one of line numbers stored in a vector.

-

Once all line numbers have been stored into os, we can access all contents to be written as a string object.

-
1
string line_numbers = os.str();
- -

Now, the next is to print all line numbers in one or more lines depending on the predetermined length of one line.

-
1
2
3
4
5
6
for(string::size_type i = 0; i != line_numbers.size(); ++i)
{
cout << line_numbers[i];
if((i + 1) % line_length == 0)
cout << endl;
}
-

When i+1th element (i.e. line_number[i])
is written, the if statement check that if the number of characters that have been written equals to the length (or multiple lengths) of a line, a newline character is inserted into the output stream.

-

A complete program

I integerated the changes described in exercise 7-3 and this exercise into a new program including files: mainfunction.cpp, xref.cpp, xref.h, split.cpp, split.h.

-

mainfunction.cpp

-
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
#include <map>		// to get the declaration of map
#include <string> // to get the declaration of string
#include <vector> // to get the declaration of vector
#include <iostream> // to get the declaration of cin, cout, endl;
#include <sstream> // to get the declaration of ostringstream
#include <cctype> // to get the declaration of isspace
#include "split.h" // to get the declaration of function split
#include "xref.h" // to get the declaration of xref

using std::map; using std::cout;
using std::cin; using std::endl;
using std::string; using std::vector;
using std:: ostringstream; using std::isspace;

int main()
{
// call xret using split by default
map<string, vector<int> > ret = xref(cin);

// set the length for each line of outputs
string::size_type line_length = 20;

// write the result
for(map<string, vector<int> >::const_iterator it = ret.begin(); it != ret.end(); ++it)
{
// write the word
cout << it->first << " occurs on line(s):" << endl;

// followed by one or more line numbers
vector<int>::const_iterator line_it = it->second.begin();
cout << *line_it; // write the first line number

// scan the rest line numbers
++line_it;
ostringstream os;
while(line_it != it->second.end())
{
// store line numbers into ostringstream object
os << ", " << *line_it;
++line_it;
}

// get the contents from line_numbers
string line_numbers = os.str();

// write each line of outputs
for(string::size_type i = 0; i != line_numbers.size(); ++i)
{
cout << line_numbers[i];
if((i + 1) % line_length == 0)
cout << endl;
}
// write a blank line to separate each words
cout << endl;
}
return 0;
}
- -

xref.cpp

-
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
#include <iostream>	// to get the decalration of istream
#include <map> // to get the declaration of map
#include <vector> // to get the declaration of vector
#include <string> // to get the declaration of string
#include "split.h" // to get the declaration of split
#include "xref.h" // to get the declatation of xref

using std::map; using std::vector;
using std::string; using std::istream;

map<string, vector<int> > xref(istream &in,
vector<string> find_words(const string &))
{
string line;
int line_number = 0;
map<string, vector<int> > ret;

// read the next line
while(getline(in, line))
{
++line_number;

// break the input line into words
vector<string> words = find_words(line);

// remember that each word occurs on the current line
for(vector<string>::const_iterator it = words.begin(); it != words.end();
++it)
{
if(ret[*it].empty() || *(ret[*it].end() - 1) != line_number)
ret[*it].push_back(line_number);
}
}
return ret;
}
- -

xref.h

-
1
2
3
4
5
6
7
8
9
10
11
12
#ifndef GUARD_XREF_H
#define GUARD_XREF_H

#include <map>
#include <vector>
#include <string>
#include <iostream>
#include "split.h"

std::map<std::string, std::vector<int> > xref(std::istream &,
std::vector<std::string> find_words(const std::string &) = split);
#endif /*GUARD_XREF_H */
- -

split.cpp

-
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
#include <vector>		// to get the declarartion of vector
#include <string> // to get the declaration of string
#include <algorithm> // to get the declaration of find_if
#include "split.h" // to get the declaration of split

using std::vector; using std::string;
using std::find_if;

// true if the argument is whitespace, false otherwise
bool space(char c)
{
return isspace(c);
}

// false if the argument is whitesapce, true otherwise
bool not_space(char c)
{
return !isspace(c);
}

// function extracts words from a line of input
vector<string> split(const string &str)
{
typedef string::const_iterator iter;
vector<string> ret;

iter i = str.begin();
while (i != str.end()){
// ignore leading blanks
i = find_if(i, str.end(), not_space);

// find end of next word
iter j = find_if(i, str.end(), space);

// copy the characters in [i,j)
if(i != str.end())
ret.push_back(string(i, j));
i = j;
}
return ret;
}
- -

split.h

-
1
2
3
4
5
6
7
8
9
10
11
#ifndef GUARD_SPLIT_H
#define GUARD_SPLIT_H

#include <vector>
#include <string>

bool space(char);
bool not_space(char);
std::vector<std::string> split(const std::string &);

#endif /* GUARD_SPLIT_H */
- -

Performance test

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
Inputs:

ABC
ABC DEF DEF
ABC
ABC ABC
ABC
ABC
ABC
ABC
ABC
ABC
ABC
ABC
ABC
ABC
ABC
ABC
ABC
ABC
ABC
ABC
ABC

Outputs:

ABC occurs on line(s):
1, 2, 3, 4, 5, 6, 7,
8, 9, 10, 11, 12, 13
, 14, 15, 16, 17, 18
, 19, 20, 21
DEF occurs on line(s):
2
- -

From this test, we observe that

-
    -
  1. if one word appears more than one times in the same line, the line number is only recorded once. This shows the change described in exercise 7-3 works well.
  2. -
  3. once a line of outputs exceeds 20 characters, it wraps. This verifies the solution given above.
  4. -
-

Noting that I also change the program such that it always writes line numbers starting from a new line.

-
-

To be continued.

+ + + + Example 3 - Generating sentencesThis section introduces how to write a program that can randomly generate a sentence given certain grammar rules. For + ... + +
+ + Read more » + +
+ + +
+ + + + + -
+ +
+ + + + + + + +
-
-

+ + + +
- + + + @@ -2011,179 +2034,265 @@

{ - if (!event.target.matches('.tabs-comment .tab-content .tab-pane')) return; - let commentClass = event.target.classList[1]; - localStorage.setItem('comments_active', commentClass); - }); - } - + + + - - + -
+
+ + +
+ + +
+ + + + - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + - - + - @@ -2193,8 +2302,8 @@

@@ -2207,6 +2316,22 @@

+ + + + + + + - - + + + - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + - + - - - + + + + + + + + + + + + - - Liam's Blog @@ -51,203 +132,248 @@ - - -
-
+ -
-
-
+
- - -
+
+
+ + -
- + + + + + + +
+ -
- +
+ + +
-

- - -

+ + + +

+ +

+
+ @@ -255,189 +381,178 @@

-

Example 3 - Generating sentences

This section introduces how to write a program that can randomly generate a sentence given certain grammar rules. For example, given following input

-
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
Categories        Rules

<noun> cat
<noun> dog
<noun> table
<noun-phrase> <noun>
<noun-phrase> <adjective> <noun-phrase>
<adjective> large
<adjective> brown
<adjective> absurd
<verb> jumps
<verb> sits
<location> on the stairs
<location> under the sky
<location> wherever it wants
<sentence> the <noun-phrase> <verb> <location>
- -

The program might generate such a sentence:

-
1
the cat sits on the stairs
-

Some stylized facts can be observed:

-
    -
  1. There are two types of element in the inputs, one type contains a string enclosed by a pair of angle brackets and the other type contains one or more strings. We can always find direct or indirect mapping relations from each first type element to the second type elements.

    -
  2. -
  3. The first type represents categories of the components that constitute a sentence. One or more categories can construct compound categories. The second type represents the smallest building blocks of a sentence.

    -
  4. -
  5. The sentence structure is determined by the Rule associated with the category . A Rule may contain either categories or most basic building blocks or mixed.

    -
  6. -
-

Therefore, to construct a sentence, we need to

-
    -
  1. find , and then find the associated Rule.
  2. -
  3. start to find each element of a sentence follow the instructions of the Rule.
  4. -
  5. if the element is already the most basic building block, then we just store it into a vector for the final output. If the element is a category (i.e. the first type of element), we’ll find the associated Rule recursively, until that we find any of the most basic building blocks (i.e. the second type of element).
  6. -
-

Now, the soluction strategy can be logically divided into three parts:

-
    -
  • part 1: read and store the grammar including categories and rules into a map
  • -
  • part 2: applying above steps to find all needed elements for a sentence
  • -
  • part 3: write the sentence on the output device.
  • -
-

Read the grammar

Seen from above example, we can built a map from the first colunm to the second column, two elements of which in each row construct a key-value pair. The elements in the first column have data type string. But what’s the data type for the elements of the second column? We know that each rule may contains one or more strings and hence each rule can be stored into a vector:

-
1
vector<string> Rule;
-

Also, the mapping from the first column to the second column has one-to-many relations,for example, one maps to several rules such as cat, dog and table. Therefore, we can store each rule into a high-level vector:

-
1
vector<vector<string>> Rule_collection;
-

For the sake of brevity, we can use type alias in declaring such a map:

-
1
2
3
typedef vector<string> Rule;
typedef vector<Rule> Rule_collection;
typedef map<string, Rule_collection> Grammar;
-

Let’s see how to read the grammar into such a map:

-
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
Grammar read_grammar(istream &in)
Grammar ret;
string line;

// read the input
while (getline(in, line))
{
// split the input into words
vector<string> entry = split(line);
if(!entry.empty())
// use the category to store the associated rule
ret[entry[0]].push_back(Rule(entry.begin() + 1, entry.end()));
}
return ret;
}
-

The function returns a map Grammar that contains all grammar rules to be applied to generate a sentence in the next step. There is only one argument, an input stream object, to be passed.

-

Inside of the function body, the first statement defines an empty map ret for holding the grammar, and the second statement defines an empty string for holding each row of input containing the key (i.e. one category) and the value (i.e. one rule). The next is a while loop to read the input repeatedly and read one line once. When the first line is read in, we need to extract all words contained in the line. Then, for the first word (i.e. the category represented by a string enclosed by a pair of angle brackets), we store it into the map as the key, while for the following words, we store them as the value that associates with the key. The core statement is

-
1
ret[entry[0]].push_back(Rule(entry.begin() + 1, entry.end()));
-

It seems complex but actually there is noting new in it. entry is the vector returned by the split function and hence contains all words extracted from the line of inputs. ret[entry[0]] stores the first word (if the word is new for the key), i.e. the category, and returns its associated value, i.e. Rule_collection. Then, we uses push_back to store the associated Rule which is a vector . The Rule is filled with values from the range [entry.begin() + 1, entry.end()) using

-
1
vector<string>(iterator_first, iterator_last);
-

The last statement is to return the Grammar ret.

-

Generate a sentence

Let’s consider the function that generate a sentence.

-
1
2
3
4
5
6
7
// generating the sentence
vector<string> gen_sentence(const Grammar &g)
{
vector<string> ret;
gen_aux(g, "<sentence>", ret);
return ret;
}
-

Apparently, we need a vector to hold the generated sentence. The only argument to be passed is the value returned by the function read_grammar.

-

The function that really deals with generating a sentence is named as gen_aux. It has three parameters, the first one is the grammar produced by read_grammar, the second one is a keyword ““ to be searched, the third one is a vector to hold the final results.

-

The function is defined below:

-
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
void gen_aux(const Grammar &g, const string &word, vector<string> &ret)
{
if(!bracketed(word)){
ret.push_back(word);
}
else
{
// locate the rule that corresponds to word
Grammar::const_iterator it = g.find(word);
if(it == g.end())
throw logic_error("empty rule");

// fetch the set of possible rules
const Rule_collection &c = it->second;

// from which we select one at random
const Rule &r = c[nrand(c.size())];

// recursively expand teh selected rule
for (Rule::const_iterator i = r.begin(); i != r.end(); ++i)
gen_aux(g, *i, ret);
}
}
-

The logic of this function is exactly as same as we described above. Ignoring the if-else statement first, the first step is to find the category :

-
1
Grammar::const_iterator it = g.find(word);
-

The member function find finds and returns an iterator that refers to the element with the key equivalent to the given k. If such element doesn’t exist in the map, the find function returns iterator g.end(). Therefore, if there exists such an iterator, getting the associated Rule_collection:

-
1
2
3
4
5
if(it == g.end())
throw logic_error("empty rule");

// fetch the set of possible rules
const Rule_collection &c = it->second;
-

By now, there exists two problems to be solved:

-
    -
  1. how to randomly pick one rule from Rule_collection
  2. -
  3. if one rule is picked, how to deal with the case that its elements are still categories.
  4. -
-

Let’s put question 1 last and solve question 2 first. Assuming we have picked one rule from the Rule_collection, we then scan each element of it and store the element if the element is the most basic building block. But if the element is still one of the categories, what we need to do is to find the lower level rule (i.e. its associated value). Therefore, we just repeat above processes until the element is not a category.
The if-else statement controls the recursive process while the condition that ceases the recursion is a predicate which returns true if the element is not a category:

-
1
2
3
4
bool bracketed(const string &s)
{
return s.size() > 1 && s[0] == '<' && s[s.size() - 1] == '>';
}
- -

The function gen_sentence is recursively called in the for loop:

-
1
2
3
// recursively expand teh selected rule
for (Rule::const_iterator i = r.begin(); i != r.end(); ++i)
gen_aux(g, *i, ret);
- -

Random drawing

Now the last piece is to randomly pick a rule from Rule_collection.
One possible solution is using rand() % n. rand() is an algorithm defined in standard header and gives a random integer in the range [0, RAND_MAX]. The upper bound is a large number defined in . rand() % n, computes the remainder when deviding the random number by n and hence gives a random integer in the range [0, n). If we set n = c.size(), we can then obtain an random index that yields a rule via c[rand() % c.size]. However, this solution is not a good choice due to(Koenig and Moo 2000):

-
    -
  1. rand() returns pseudo-random numbers. Many C++ implementations’ pseudo-random numbers give remainders that aren’t very random when the quotients are small integers.

    -
  2. -
  3. if n is large and RAND_MAX is not evenly divisible by n,some remainder will appear more often than others.

    -
  4. -
-

To circumvent these issues, we can divide the range of available numbers into buckets of exactly equal size:

-
1
const int bucket_size = RAND_MAX /n;
-

Then, bucket 0 has values from [0, bucket_size), bucket 1 has values from [bucket_size, bucket_size*2)…..

-

Then we can random draw a number and get the bucket number where the random number is located in via:

-
1
int r = rand() / bucket_size;
-

But there exist situations that the random number does not fall into any bucket due to the fact that RAND_MAX may be not evenly divisible by n. Therefore, we uses a do while statement to repeat above statement until it finds a random number that locates in the range of one of buckets.

-

The function nrand is shown below:

-
1
2
3
4
5
6
7
8
9
10
11
12
13
14
// return a random integer in the range [0, n)
int nrand(int n)
{
if(n <= 0 || n > RAND_MAX)
throw domain_error("Argument to nrand is out of range");

const int bucket_size = RAND_MAX /n;
int r;

do r = rand() / bucket_size;
while(r >= n);

return r;
}
-

Noting that rand() uses a seed to generate the sequence, which should be initialized to some distinctive value using void srand(unsigned int seed). A common practice is to use distinctive runtime value, like the value returned by function time. For example

-
1
srand (time(NULL));
-

In addition, srand() is in fact has global effect on rand() and hence can be stated at the very begining of the main function.

-

A complete program

Now I files all functions and code discussed above and present the compete program below. The main function easy to understand and hence no further analysis here.

-

mainfunction.cpp

-
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 <cstdlib>		// to get the declaration of srand
#include <ctime> // to get the declaration of time
#include <vector> // to get the declaration of vector
#include <string> // to get the declaration of string
#include "read_grammar.h" // to get the declaration of read_grammar
#include "gen_sentence.h" // to get the declaration of gen_sentence

using std::cin; using std::cout;
using std::vector; using std::endl;
using std::string; using std::srand;
using std::time;

int main()
{
// initialize random number generator with distinctive runtime value
srand (time(NULL));

// generate the sentence
vector<string> sentence = gen_sentence(read_grammar(cin));

// write the first word, if any
vector<string>::const_iterator it = sentence.begin();
if(!sentence.empty())
{
cout << *it;
++it;
}

// write the rest of the words, each preceded by a space
while(it != sentence.end())
{
cout << " " << *it;
++it;
}
cout << endl;
return 0;
}
- -

type_alias.h

-
1
2
3
4
5
6
7
8
9
10
11
12
#ifndef GUARD_TYPE_ALIAS_H
#define GUARD_TYPE_ALIAS_H

#include <vector>
#include <string>
#include <map>

typedef std::vector<std::string> Rule;
typedef std::vector<Rule> Rule_collection;
typedef std::map<std::string, Rule_collection> Grammar;

#endif /* GUARD_TYPE_ALIAS_H */
- -

read_grammar.cpp

-
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
#include <iostream>		// to get the declaration of istream
#include <vector> // to get the declaration of vector
#include <string> // to get the declaration of string
#include "split.h" // to get the declaration of split
#include "type_alias.h" // to get the declaration of type alias
#include "read_grammar.h" // to get the declaration of read_grammar

using std::istream; using std::vector;
using std::string;

// read a grammar from a given input stream
Grammar read_grammar(istream &in)
{
Grammar ret;
string line;

// read the input
while (getline(in, line))
{
// split the input into words
vector<string> entry = split(line);
if(!entry.empty())
// use the category to store the associated rule
ret[entry[0]].push_back(Rule(entry.begin() + 1, entry.end()));
}
return ret;
}
- -

read_grammar.h

-
1
2
3
4
5
6
7
8
9
#ifndef GUARD_READ_GRAMMAR_H
#define GUARD_READ_GRAMMAR_H

#include <iostream>
#include "type_alias.h"

Grammar read_grammar(std::istream &);

#endif /* GUARD_READ_GRAMMAR_H */
- -

gen_sentence.cpp

-
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 <vector>		// to get the declaration of vector
#include <string> // to get the declaration of string
#include <stdexcept> // to get the declaration of domain_error, logic_error
#include <cstdlib> // to get the declaration of rand
#include "type_alias.h" // to get the declaration of type_alias
#include "gen_sentence.h" // to get the declatation of gen_sentence

using std::vector; using std::string;
using std::domain_error; using std::logic_error;
using std::rand;

// generating the sentence
vector<string> gen_sentence(const Grammar &g)
{
vector<string> ret;
gen_aux(g, "<sentence>", ret);
return ret;
}

// auxillary gen function
void gen_aux(const Grammar &g, const string &word, vector<string> &ret)
{
if(!bracketed(word)){
ret.push_back(word);
}
else
{
// locate the rule that corresponds to word
Grammar::const_iterator it = g.find(word);
if(it == g.end())
throw logic_error("empty rule");

// fetch the set of possible rules
const Rule_collection &c = it->second;

// from which we select one at random
const Rule &r = c[nrand(c.size())];

// recursively expand teh selected rule
for (Rule::const_iterator i = r.begin(); i != r.end(); ++i)
gen_aux(g, *i, ret);
}
}

// the predicate
bool bracketed(const string &s)
{
return s.size() > 1 && s[0] == '<' && s[s.size() - 1] == '>';
}

// return a random integer in the range [0, n)
int nrand(int n)
{
if(n <= 0 || n > RAND_MAX)
throw domain_error("Argument to nrand is out of range");

const int bucket_size = RAND_MAX /n;
int r;

do r = rand() / bucket_size;
while(r >= n);

return r;
}
- -

split.cpp

-
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
#include <vector>	// to get the declarartion of vector
#include <string> // to get the declaration of string
#include <algorithm> // to get the declaration of find_if
#include "split.h" // to get the declaration of split

using std::vector; using std::string;
using std::find_if;

// true if the argument is whitespace, false otherwise
bool space(char c)
{
return isspace(c);
}

// false if the argument is whitesapce, true otherwise
bool not_space(char c)
{
return !isspace(c);
}

// function extracts words from a line of input
vector<string> split(const string &str)
{
typedef string::const_iterator iter;
vector<string> ret;

iter i = str.begin();
while (i != str.end()){
// ignore leading blanks
i = find_if(i, str.end(), not_space);

// find end of next word
iter j = find_if(i, str.end(), space);

// copy the characters in [i,j)
if(i != str.end())
ret.push_back(string(i, j));
i = j;
}
return ret;
}
-

split.h

-
1
2
3
4
5
6
7
8
9
10
11
#ifndef GUARD_SPLIT_H
#define GUARD_SPLIT_H

#include <vector>
#include <string>

bool space(char);
bool not_space(char);
std::vector<std::string> split(const std::string &);

#endif /* GUARD_SPLIT_H */
- -

Test performance

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
Inputs:

<noun> cat
<noun> dog
<noun> table
<noun-phrase> <noun>
<noun-phrase> <adjective> <noun-phrase>
<adjective> large
<adjective> brown
<adjective> absurd
<verb> jumps
<verb> sits
<location> on the stairs
<location> under the sky
<location> wherever it wants
<sentence> the <noun-phrase> <verb> <location>

Outputs:

the large brown dog sits wherever it wants
-

The program works as expected.

-
+ + + + IntroductionUnlike sequential containers, associative containers store data into a sequence depending on the values of the elements themselves rather + ... + +
+ + Read more » + +
+ + +
+ + + + + -
+ +
+ + + + + + + +
-
+ +
+

+ + +
+ + + + + + + - +
-
- + +
+ -
-

- - -

+ + +
+ + + +

+ +

+
+ @@ -445,169 +560,358 @@

-

Introduction

Unlike sequential containers, associative containers store data into a sequence depending on the values of the elements themselves rather than the sequence where we inserted them. An associative container supports efficient lookup and retrieval by a key which is the part of each element. The most common kind of assiciative data structure, namely the associative array, is one that stores key-value pairs, associating a value with each each key. In C++, such an associative array is called a map.

-

The keys in fact plays a role as an index for the values, which are similar to the index of a vector. But the difference is that the keys can be int* type or string type or any other types that allows ordering.

-

Example 1 - counting words

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
int main()
{
string s;
map<string, int> counters; // store each word and an associated counter

// read the input, keeping track of each word and how often we see it
while (cin >> s)
{
++counters[s];
}
// write the words and associated counts
for (map<string, int>::const_iterator it = counters.begin(); it != counters.end(); ++it)
{
cout << it->first << "\t" << it->second << endl;
}
return 0;
}
-

Key points

-
    -
  1. when we define a map, we need to specify the types for both key and value, i.e. a key-value pair. In this case, the key has type of string while the value has type of int. Such a container can be described as a map from string to int.

    -
  2. -
  3. from above, we know that each element in a map has type pair which is a data structure that holds two elements named first and second. For a map that has a key of type K, and a value of type V, the asociated pair type is pair<const K, V>. In this case, the elements of counters have type of pair<const string, int>.

    -
  4. -
  5. the map counters is constructed with default initialization, leading to an empty map.

    -
  6. -
  7. when we start to store words, the element pair<string, int> is value-initialized, that is, the key is initialized as an empty string and the value is initialized as 0.

    -
  8. -
  9. map supports operations via subscripting.

    -
    c[k] returns the **k**-associated value; if **k** doesn't exist, it adds **k** to the container, initialize and returns its associated value.

    In this case, when a word appears for the first time, counters[s] returns the associated value which is initialized with value 0. Then we increment the value via ++counters[s].

    -
  10. -
  11. the for loop shows how to access elements in a map using iterators. The operations are similar to those on a vactor. When we deference a map<string, int> iterator, we get a pair<const string, int>. Therefore, it->first extracts the value of the first element in the pair, that is, the key, while it->second gives the value of the second element in the pair, that is, the value associated with the key.

    -
  12. -
-

We can also access the value via subscripting as described at key point 5. For example,

-
1
cout << counters[it->first];
-

has the same effect as

-
1
cout << it->second;
- -

Example 2 - Generating a cross-reference table

This example extends above program such that the new program can generate a cross-reference table that indicates where each word occurs in the input. It requires:

-
    -
  1. read a line at a time for the purpose of obtaining the associated line number.
  2. -
  3. To separate each word from a line, we need a function like split described in Chpter 6. But rather than calling the function independently, we will pass it as an argument to the cross-reference function (denoted by xref).
  4. -
-
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
// find all the lines that refer to each word in the input
map<string, vector<int> > xref(istream & in,
vector<string> find_words(const string &) = split)
{
string line;
int line_number = 0;
map<string, vector<int> > ret;

// read the next line
while(getline(in, line))
{
++line_number;

// break the input line into words
vector<string> words = find_words(line);

// remember that each word occurs on the current line
for (vector<string>::const_iterator it = words.begin(); it != words.end(); ++it)
{
ret[*it].push_back(line_number);
}
return ret;
}
}
-

Let’s analyse this function:

-
    -
  1. what xref should return is a map<string, vector > as in fact we aims to build a map from the word to its line number. The key, representing each distinct word, has type string. The value, representing line numbers associated with each key, has type vector due to the fact that one word may appears in different lines.

    -
  2. -
  3. xref takes two arguments, one is an input stream object; another one is a function to extract words from a line. We are familar with how to define a function parameter. But what’ new here is to use ** = split** in defining such a function parameter.This indicates that this parameter has a default argument, i.e. split. It means that if xref is called without passing this argument, it uses the default argument. If xref is called with passing a new argument, it uses the new argument. For example

    -
    1
    2
    xref(cin); // uses split to find words in the input stream
    xref(cin, find_urls); // uses the function named find_urls to find words
  4. -
  5. the function begins with defining three varaibles: the first is a string named line to hold each line of input; the second is a integer that denotes the line number; the third is a map<string, vector > to hold each pair of word and line numbers.

    -
  6. -
  7. every iteration of the while statement, one line of input is read and broken into words. Then, each word is accessed via iterator and stored into the map together its line number. The core statement is:

    -
    1
    ret[*it].push_back(line_number);
    -

    As mentioned earlier, if *it, a word, doesn’t appear before, it will be stored and ret[it] returns an default initialized associated value, i.e. an empty vector. Then, we use *push_back** to append the line number to the end of the vector. If *it has already appeared before, ret[*it] returned the associated value (i.e. vector) and the new line number will be appended to the end of the vector.

    -
  8. -
-

A complete program

Now, I add #include directives and present all files including: mainfunction.cpp, xref.cpp, xref.h, split.cpp, split.h. This program uses the default split function.

-

mainfunction.cpp

-
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
int main()
{
// call xref using split by default
map<string, vector<int> > ret = xref(cin);

// write the results
for(map<string, vector<int> >::const_iterator it = ret.begin();
it != ret.end(); ++it)
{
// write the word
cout << it->first << " occurs on line(s): ";

// followed by one or more line_numbers
vector<int>::const_iterator line_it = it->second.begin();
cout << *line_it; // write the first line number

++line_it;
while (line_it != it->second.end())
{
cout << ", " << *line_it;
++line_it;
}
// write a new line to separate each word from the next
cout << endl;
}
return 0;
}
-

There is nothing new in above code. The program writes the first line number and the rest(if there exist) separately for the purpose of separating line numbers with a comma followed by a space.

-

xref.cpp

-
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
#include <iostream>	// to get the decalration of istream
#include <map> // to get the declaration of map
#include <vector> // to get the declaration of vector
#include <string> // to get the declaration of string
#include "split.h" // to get the declaration of split
#include "xref.h" // to get the declatation of xref

using std::map; using std::vector;
using std::string; using std::istream;

map<string, vector<int> > xref(istream &in,
vector<string> find_words(const string &))
{
string line;
int line_number = 0;
map<string, vector<int> > ret;

// read the next line
while(getline(in, line))
{
++line_number;

// break the input line into words
vector<string> words = find_words(line);

// remember that each word occurs on the current line
for(vector<string>::const_iterator it = words.begin(); it != words.end();
++it)
{
ret[*it].push_back(line_number);
}
}
return ret;
}
-

xref.h

-
1
2
3
4
5
6
7
8
9
10
11
12
13
#ifndef GUARD_XREF_H
#define GUARD_XREF_H

#include <map>
#include <vector>
#include <string>
#include <iostream>
#include "split.h"

std::map<std::string, std::vector<int> > xref(std::istream &,
std::vector<std::string> find_words(const std::string &) = split);

#endif /*GUARD_XREF_H */
-

It is worth noting that if xref is separated from the main function file, the default argument for the parameter (i.e. split in this case) should be put into its header file only. This is becasue that the default argument for a given parameter can only be specified once. (more discussion can be found on this page Default value of function parameter -).

-

split.cpp

-
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
#include <vector>	// to get the declarartion of vector
#include <string> // to get the declaration of string
#include <algorithm> // to get the declaration of find_if
#include "split.h" // to get the declaration of split

using std::vector; using std::string;
using std::find_if;

// true if the argument is whitespace, false otherwise
bool space(char c)
{
return isspace(c);
}

// false if the argument is whitesapce, true otherwise
bool not_space(char c)
{
return !isspace(c);
}

// function extracts words from a line of input
vector<string> split(const string &str)
{
typedef string::const_iterator iter;
vector<string> ret;

iter i = str.begin();
while (i != str.end()){
// ignore leading blanks
i = find_if(i, str.end(), not_space);

// find end of next word
iter j = find_if(i, str.end(), space);

// copy the characters in [i,j)
if(i != str.end())
ret.push_back(string(i, j));
i = j;
}
return ret;
}
- -

split.h

-
1
2
3
4
5
6
7
8
9
10
11
#ifndef GUARD_SPLIT_H
#define GUARD_SPLIT_H

#include <vector>
#include <string>

bool space(char);
bool not_space(char);
std::vector<std::string> split(const std::string &);

#endif /* GUARD_SPLIT_H */
- -

Test 1

From a simple test shown below, it can be seen that the program works as expected.

-
1
2
3
4
5
6
7
8
9
10
11
12
13
14
Inputs:

do you like me
no I do not like you

Outputs:

I occurs on line(s): 2
do occurs on line(s): 1 2
like occurs on line(s): 1 2
me occurs on line(s): 1
no occurs on line(s): 2
not occurs on line(s): 2
you occurs on line(s): 1 2
- -

Test 2

Sometimes we may want to use another strategy to extract words, for example, when extracting URLs like we did in Finding URLs. I add revelent files first.

-

find_urls.cpp

-
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
// function that finds and returns an URL
#include "find_urls.h"
#include <vector>
#include <string>
#include "delimit.h"

using std::vector;
using std::string;

vector<string> find_urls(const string &s)
{
vector<string> ret;
typedef string::const_iterator iter;
iter b = s.begin(), e = s.end();

// look through the entire input
while (b != e)
{
// look for one or more letters followed by ://
b = url_beg(b, e);

// if we found it
if(b != e)
{
// get the rest of the URL
iter after = url_end(b, e);

// remember the URL
ret.push_back(string(b, after));

// advance b and check for more URLs on this line
b = after;
}
}
return ret;
}
- -

find_urls.h

-
1
2
3
4
5
6
7
8
9
#ifndef GUARD_FINDINGURLS_H
#define GUARD_FINDINGURLS_H

#include <vector>
#include <string>

std::vector<std::string> find_urls(const std::string &);

#endif /* GUARD_FINDINGURLS_H */
- -

dilimit.cpp

-
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
// contains three functions: not_url_char, url_beg, url_end
#include <string>
#include <algorithm>
#include "delimit.h"

using std::string; using std::find;
using std::find_if; using std::search;

// predicate on a char, check whether it is a char that can appear in a URL
bool not_url_char(char c)
{
// characters, in addition to alphanumerics, that can appear in a URL
static const string url_ch = "~;/?:@=&$-_.+!*'(),";

// see whether c can appear in a URL and return the negative
return !(isalnum(c) || find(url_ch.begin(), url_ch.end(), c) != url_ch.end());
}

// function that returns an iterator that refers to the first element of a URL
string::const_iterator url_beg(string::const_iterator b, string::const_iterator e)
{
static const string sep = "://";
typedef string::const_iterator iter;

// i marks where the separator was found
iter i = b;
while((i = search(i, e, sep.begin(), sep.end())) != e)
{
// make sure the seperator isn't at the begining of the proticol-name
if(i != b && i + sep.size() != e)
{
// beg marks the begining of the protocol-name
iter beg = i;
while(beg != b && isalpha(beg[-1]))
--beg;

// is there at least one appropriate character before and after the sep
if (beg != i && !not_url_char(i[sep.size()]))
return beg;
}
// the seperator we found wasn't part of a URL advance i past this separator
i += sep.size();
}
return e;
}

// function that returns an iterator that denotes the postion one past the last element
string::const_iterator url_end(string::const_iterator b, string::const_iterator e)
{
return find_if(b, e, not_url_char);
}
- -

delimit.h

-
1
2
3
4
5
6
7
8
9
10
#ifndef GUARD_DELIMIT_H
#define GUARD_DELIMIT_H

#include <string>

bool not_url_char(char);
std::string::const_iterator url_beg(std::string::const_iterator, std::string::const_iterator);
std::string::const_iterator url_end(std::string::const_iterator, std::string::const_iterator);

#endif /* GUARD_DELIMIT_H */
- -

Now, let’s call xref with passing argument find_urls:

-
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
int main()
{
// call xref using split by default
map<string, vector<int> > ret = xref(cin, find_urls);

// write the results
for(map<string, vector<int> >::const_iterator it = ret.begin();
it != ret.end(); ++it)
{
// write the word
cout << it->first << " occurs on line(s): ";

// followed by one or more line_numbers
vector<int>::const_iterator line_it = it->second.begin();
cout << *line_it; // write the first line number

++line_it;
while (line_it != it->second.end())
{
cout << ", " << *line_it;
++line_it;
}
// write a new line to separate each word from the next
cout << endl;
}
return 0;
}
-

Then, we type following inputs:

-
1
2
3
A typical URL could have the form https://en.wikipedia.org/wiki/URL, 
which indicates a protocol (http), a hostname (www.example.com),
and a file name (index.html). http://www.cplusplus.com/reference/algorithm/search/?kw=search
- -

The program give results as expected:

-
1
2
http://www.cplusplus.com/reference/algorithm/search/?kw=search occurs on line(s): 3
https://en.wikipedia.org/wiki/URL, occurs on line(s): 1
-
+ + + + Exercise 6-0Compile, execute, and test the programs in this chapter. +Solution & ResultsPlease find all programs and detailed analysis on Using lib + ... + +
+ + Read more » + +
+ + +
+ + + + + -
+ +
+ + + + + + + +
-
+ +
+

+ + +
+ + + + + + + - +
+ -
- +
+ + +
-

- - -

+ + + +

+ +

+ +
+ + + + + +
+ + + + + + + + Revisit the classifying programRecalling the extract_fails function developed in last chapter:1234567891011121314151617vector<Student_info> extr + ... + +
+ + Read more » + +
+ + + +
+ + + + + + + + + + +
+ + + + + + + + +
+ +
+
+ + + +
+ + + + + + + + + + + +
+ + + +
+ + + + + + + +
+ + + +

+ +

+ + +
+ @@ -615,206 +919,178 @@

-

Exercise 6-0

Compile, execute, and test the programs in this chapter.

-

Solution & Results

Please find all programs and detailed analysis on Using library algorithms (Part 1), Using library algorithms (Part 2) and Using library algorithms (Part 3).

-

Exercise 6-1

Reimplement the frame and hcat operations from §5.8.1/93 and §5.8.3/94 to use iterators.

-

Solution & Results

This exercise has been completed here Putting strings together.

-

Exercise 6-2

Write a program to test the find_urls function.

-

Solution & Results

Please find the program and analysis on Using library algorithms (Part 1).

-

Exercise 6-3, 6-4

6-3: What does this program fragment do?

-
1
2
3
vector<int> u(10, 100);
vector<int> v;
copy(u.begin(), u.end(), v.begin());
-

Write a program that contains this fragment, and compile and execute it.

-

6-4: Correct the program you wrote in the previous exercise to copy from u into v. Thereare at least two possible ways to correct the program. Implement both, and describe the relative advantages and disadvantages of each approach.

-

Soltion & Results

The first statement creates an object of vector and initializes it with 10 elements that all equals to 100. The second statement creates an empty (i.e. due to default initialization) vector v . The third statement calls an algorithm copy to copy values in the range [u.begin(), u.end()) into the destination denoted by the third argument v.begin(). However, the destination sequence should be at least as large as the input range. Therefore, the fragment will lead to compilation errors.

-

Method 1

To correct this, we can initialize the v as an vector that has the same size as u. For example

-
1
vector<int> v(10);
-

This statement means that v contains 10 elements that all equals to 0. Then, the program should work fine.

-

Method 2

Alternatively, we can use back_inserter to append the copied elements to v. It naturelly increases the size of v and hence ensures enough space for holding those elements. The third statement is replaced by:

-
1
copy(u.begin(), u.end(), back_inserter(v));
- -

Method 3

There is also an iterator adaptor inserter that creates an insert iterator for successive insertion into a container. Therefore, we can replace the back_inserter with inserter shown as follows

-
1
copy(u.begin(), u.end(), inserter(v, v.begin()));
-

This statement means that the elements of u are copied and inserted into v starting from the begining position of v.

-

comparison

All three methods can correct the original program. The first method is not practical as in most cases we probably don’t know the number of elements to copy. Contrarily, method 2 and method 3 ensures enough space for holding elements to copy. However, two iterator adaptors works differently:

-
    -
  1. back_inserter constructs a back-insert iterator that inserts new elements at the end of a container. The container should have member function push_back.

    -
  2. -
  3. inserter constructs an insert iterator that inserts new elements into a container successively starting from a specified position. The container should have member function insert.

    -
  4. -
-

Apparently, inserter provides more flexibility in inserting elements while back_inserter has more limitations. But more research should be done on their performances when dealing with massive data. I present a simple program to show that all three methods work fine and lead to same results.

-

A complete test program

Test 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
#include <iostream>
#include <vector>
#include <iterator>
#include <algorithm>

using std::cout; using std::endl;
using std::copy; using std::vector;
using std::inserter;using std::back_inserter;

void print(vector<int> &vec)
{
for(vector<int>::const_iterator iter = vec.begin(); iter != vec.end(); ++iter)
cout << *iter << endl;
}

int main()
{
vector<int> u(10, 100);

// method 1: let v has the same size as u
vector<int> v1(10);
copy(u.begin(), u.end(), v1.begin());
cout << "The results of method 1:" << endl;
print(v1);

cout << endl;

// method 2: back_inserter
vector<int> v2;
copy(u.begin(), u.end(), back_inserter(v2));
cout << "The results of method 2:" << endl;
print(v1);

cout << endl;

// method 3: inserter
vector<int> v3;
copy(u.begin(), u.end(), inserter(v3, v3.begin()));
cout << "The results of method 3:" << endl;
print(v3);

return 0;
}
- -

Test Results

-
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
The results of method 1:
100
100
100
100
100
100
100
100
100
100

The results of method 2:
100
100
100
100
100
100
100
100
100
100

The results of method 3:
100
100
100
100
100
100
100
100
100
100
- -

Exercise 6-5

Write an analysis function to call optimistic_median.

-

Solution & Results

Please find the analysis function here Using library algorithms (Part 2).

-

Exercise 6-6

Note that the function from the previous exercise and the functions from §6.2.2/113and §6.2.3/115 do the same task. Merge these three analysis functions into a singlefunction.

-

Solution & Results

Please find the solution strategy and analysis here Using library algorithms (Part 2).

-

Exercise 6-7

The portion of the grading analysis program from §6.2.1/110 that read and classified student records depending on whether they did (or did not) do all the homework is similar to the problem we solved in extract_fails. Write a function to handle this subproblem.

-

Solution & Results

This exercise requires us to seperate the students’ records into two groups: one group contains records that did all homeworks while the other one contains records that did not complete all homeworks. Specifically, we rewrite the main function such that all records will be stored into a vector did first, and then we extract and store the other group records into didnt via a function named extract_didnt. This function can be written exactly the same as the extract_fails function developed using the single-pass solution (see Using library algorithms (Part 3)).

-

I revised the original program described on Using library algorithms (Part 2) by creating a file contains extract_didnt function and the predicate did_all_hw:

-

extract_didnt.cpp

-
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
#include <vector>		// to get the declaration of vector
#include <algorithm> // to get the declaration of stable_partition
#include "Student_info.h" // to get the declaration of Student_info
#include "extract_didnt.h" // to get the declaration of extract_didnt

using std::vector;
using std::stable_partition;


bool did_all_hw(const Student_info &s)
{
return ((find(s.homework.begin(), s.homework.end(), 0)) == s.homework.end());
}

vector<Student_info> extract_didnt(vector<Student_info> &did)
{
vector<Student_info>::iterator iter = stable_partition(did.begin(), did.end(), did_all_hw);
vector<Student_info> didnt(iter, did.end());
did.erase(iter, did.end());
return didnt;
}
-

extract_didnt.h

-
1
2
3
4
5
6
7
8
9
10
#ifndef GUARD_EXTRACT_DIDNT_H
#define GUARD_EXTRACT_DIDNT_H

#include <vector>
#include "Student_info.h"

bool did_all_hw(const Student_info &);
std::vector<Student_info> extract_didnt(std::vector<Student_info> &);

#endif /* GUARD_EXTRACT_DIDNT_H */
- -

Correspondingly, the main function becomes

-

mainfunction.cpp

-
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
#include <iostream>			// to get the declaration of cin, cout, endl
#include <vector> // to get the declaration of vector
#include "Student_info.h" // to get the declaration of Student_inf"
#include "grades_analysis.h" // to get the declaration of three analysis function
#include "write_analysis.h" // to get the declaration of write_analysis function
#include "extract_didnt.h"

using std::vector; using std::cout;
using std::cin; using std::endl;

int main()
{
vector<Student_info> did;

// read the student records
Student_info student;
while(read(cin, student))
{
did.push_back(student);
}

// extract records that don't complete all the homeworks
vector<Student_info> didnt = extract_didnt(did);

// verify thatthe analyses will show us something
if(did.empty())
{
cout << "No student did all the homework!" << endl;
return 1;
}
if(didnt.empty())
{
cout << "Every student did all the homework!" << endl;
return 1;
}

// do the analysis
write_analysis(cout, "median", median_analysis, did, didnt);
write_analysis(cout, "average", average_analysis, did, didnt);
write_analysis(cout, "median of homework turned in", optimistic_median_analysis, did, didnt);

return 0;
}
- -

Exercise 6-8

Write a single function that can be used to classify students based on criteria of your choice. Test this function by using it in place of the extract_fails program, and use it in the program to analyze student grades.

-

Solution & Results

This exercise asks us to generalize the program in exercise 6-7 such that the program can deal with various kinds of criteria. A possible solution is to pass the criteria as arguments to the more generalized function classify which will classifies an input sequence according to the criteria. By doing so, we can pass different criteria to classify the students’ records based on our own preference.
Let’s define such classify function and put it in a sparate file.

-

classify.cpp

-
1
2
3
4
5
6
7
8
9
10
11
12
13
#include <vector>
#include <algorithm>
#include "Student_info.h"

using std::vector; using std::stable_partition;

vector<Student_info> classify(vector<Student_info> &v, bool criteria(const Student_info &s))
{
vector<Student_info>::iterator iter = stable_partition(v.begin(), v.end(), criteria);
vector<Student_info> criteria_false(iter, v.end());
v.erase(iter, v.end());
return criteria_false;
}
- -

classify.h

-
1
2
3
4
5
6
7
8
9
10
#ifndef GUARD_CLASSIFY_H
#define GUARD_CLASSIFY_H

#include <vector>
#include "Student_info.h"

std::vector<Student_info> classify(std::vector<Student_info> &,
bool criteria(const Student_info &));

#endif /* GUARD_CLASSIFY_H */
- -

Simialr to the extract_didnt function defined in exercise 6-7, this function returns a vector that contains all records that do not satisfy the criteria. The input sequence has also been modified and consequently contains only the records that statisfy the criteria.

-

To test the new function, the test program is designed to classify the students’ records as three groups (if available):

-
    -
  1. students who fail the final grade
  2. -
  3. students who pass the grade, and are awarded the first class honours
  4. -
  5. students who pass the grade, and are awarded the second class honours
  6. -
-

Firstly, we classify students according to criteria pgrade, as a result, the records are divided into two groups. By then, students contains only the passing records. Secondly, we further classify students according to another criterion which allowing us to extract students who are awarded the first class honours.

-

The test program includes following files:

-
    -
  1. classify.cpp, classify.h(as shown above)
  2. -
  3. mainfunction.cpp
  4. -
  5. criteria.cpp, criteria.h
  6. -
  7. grade.cpp, grade.h
  8. -
  9. Student_info.cpp, Student_info.h
  10. -
  11. print.cpp, print.h
  12. -
-

I present ecah file according to above order in below followed by the test results.

-

A complete program

mainfunction.cpp

-
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
65
66
67
68
69
#include <iostream>			// to get the declaration of cin, cout, endl
#include <algorithm> // to get the declaration of max
#include <vector> // to get the declaration of vector
#include <string> // to get the declaration of string
#include "Student_info.h" // to get the declaration of Student_info
#include "criteria.h" // to get the declaration of criteria
#include "classify.h" // to get the declaration of classify
#include "print.h" // to get the declaration of print

using std::vector; using std::cout;
using std::cin; using std::endl;
using std::string; using std::max;

int main()
{
vector<Student_info> students;
Student_info record;
string::size_type maxlen = 0;

// read and store all the records, and find the length of the longest name
while(read(cin, record))
{
maxlen = max(maxlen, record.name.size());
students.push_back(record);
}

// classfiy students according to the criteria: fail and pass
vector<Student_info> fails = classify(students, pgrade);

// verify that the analyses will show us something
if(fails.empty())
{
cout << "All students pass the grade!" << endl;
return 1;
}
if(students.empty())
{
cout << "All students fail the grade!" << endl;
return 1;
}

// classfiy students according to the criteria: first class and second class
vector<Student_info> first_class_honours = classify(students, second_class_honours);

// write lines of outputs
cout << "Students who fails the grade include:" << endl;
print(fails, maxlen);

cout << endl;
if(!first_class_honours.empty())
{
cout << "Students who are awarded first class honours include:" << endl;
print(first_class_honours, maxlen);
}
else
cout << "None of students is awarded first class honours." << endl;

cout << endl;
if(!students.empty())
{
cout << "Students who are awarded second class honours include:" << endl;
print(students, maxlen);
}
else
cout << "congratulations, all the students who "
"pass the grade are awarded first class honours." << endl;

return 0;
}
- -

criteria.cpp

-
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
#include "grade.h"
#include "Student_info.h"

bool fgrade(const Student_info &s)
{
return grade(s) < 60;
}

bool pgrade(const Student_info &s)
{
return !fgrade(s);
}

bool first_class_honours(const Student_info &s)
{
return grade(s) >= 85;
}

bool second_class_honours(const Student_info &s)
{
return (!fgrade(s) && !first_class_honours(s));
}
- -

criteria.h

-
1
2
3
4
5
6
7
8
9
10
#ifndef GUARD_CRITERIA_H
#define GUARD_CRITERIA_H

#include "Student_info.h"

bool fgrade(const Student_info &);
bool pgrade(const Student_info &);
bool first_class_honours(const Student_info &);
bool second_class_honours(const Student_info &s);
#endif /* GUARD_CRITERIA_H */
- -

grade.cpp

-
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 <algorithm>
#include <stdexcept>
#include <vector>
#include "grade.h"
#include "Student_info.h"

using std::domain_error; using std::istream;
using std::vector; using std::sort;

// grade function 1
double grade(const Student_info &s)
{
return grade(s.midterm, s.final, s.homework);
}

// grade function 2
double grade(double midterm, double final, const vector<double> &hw)
{
if (hw.size() == 0)
throw domain_error("student has done no homework");
return grade(midterm, final, median(hw));
}

// grade function 3
double grade(double midterm, double final, double homework)
{
return 0.2*midterm + 0.4*final + 0.4*homework;
}

// compute the median of vector<double>
double median(vector<double> vec)
{
// get the size of the vector
typedef vector<double>::size_type vec_size;
vec_size size = vec.size();

// check whether the empty is empty
if (size == 0)
throw domain_error("median of an empty vector");

// sort the grades
sort(vec.begin(), vec.end());

// compute the median homework grade
vec_size mid = size/2;
return size % 2 == 0 ? (vec[mid] + vec[mid - 1])/2 : vec[mid];
}
-

grade.h

-
1
2
3
4
5
6
7
8
9
10
11
12
#ifndef GUARD_grade_h
#define GUARD_grade_h

// grade.h
#include<vector>
#include "Student_info.h"

double grade(const Student_info &);
double grade(double, double, const std::vector<double> &);
double grade(double, double, double);
double median(std::vector<double>);
#endif
- -

Student_info.cpp

-
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 "Student_info.h"
using std::vector; using std::istream;

// argument to the function sort
bool compare(const Student_info &x, const Student_info &y)
{
return x.name < y.name;
}

// read the info
istream & read(istream &is, Student_info &s)
{
// reads and store the student's name, midterm and final exam grades
is >> s.name >> s.midterm >> s.final;

// reads and store all homework grades
read_hw(is, s.homework);
return is;
}

// read all homework grades
istream & read_hw(istream &in, vector<double> &hw)
{
if (in){
//get rid of previous contents
hw.clear();

// read homework grades
double x;
while(in >> x)
hw.push_back(x);

// clear the stream so that input will work for the next student
in.clear();
}
return in;
}
- -

Student_info.h

-
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
#ifndef GUARD_Student_info
#define GUARD_Student_info

#include<iostream>
#include<string>
#include<vector>

struct Student_info{
std::string name;
double midterm, final;
std::vector<double> homework;
};

bool compare(const Student_info &, const Student_info &);
std::istream & read(std::istream &, Student_info &);
std::istream & read_hw(std::istream &, std::vector<double> &);
#endif
- -

print.cpp

-
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
#include <iostream>
#include <vector>
#include <iomanip>
#include <string>
#include "grade.h"
#include "print.h"
#include "Student_info.h"

using std::cout; using std::setprecision;
using std::endl; using std::streamsize;
using std::string; using std::vector;

void print(const vector<Student_info> &records, const string::size_type &maxlen)
{
for (vector<Student_info>::const_iterator iter = records.begin(); iter != records.end(); ++iter)
{
// write the name, blanks
cout << (*iter).name << string(maxlen + 1 - (*iter).name.size(), ' ');

// compute and write the final grade
double final_grade = grade(*iter);
streamsize prec = cout.precision();
cout << setprecision(3) << final_grade << setprecision(prec) << endl;
}
}
- -

print.h

-
1
2
3
4
5
6
7
8
9
10
#ifndef GUARD_PRINT_H
#define GUARD_PRINT_H

#include <string>
#include <vector>
#include "Student_info.h"

void print(const std::vector<Student_info> &records, const std::string::size_type &maxlen);

#endif /* GUARD_PRINT_H */
- -

Test results

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
Inputs:

Phqgh 24.7879 58.6263 64.0505
Nlfdx 95.4242 27.3636 91.0404
Cxggb 16.1818 95.4747 26.7172
Uxwfn 35.9495 3.11111 22.3333
Tkjpr 68.4747 44.6263 57.3737
Pnrvy 16.3535 90.4242 88.0606
Syycq 5.90909 29.7071 50.0606
Ffmzn 84.5455 56.404 66.7677
Vwsre 23.3737 38.1818 82.2929
Fxtls 4.30303 77.0606 73.8687
Dpooe 29.7778 73.9798 12.8687
Ejuvp 55.7475 31.5253 50.5051
Poeyl 91.0707 37.5758 87.5354
Jvrvi 21.8889 22.4646 6.30303
Hwqnq 55.101 59.2424 37.4848
Jjloo 91.3636 74.202 96.2121
Whmsn 34.5354 99.1818 38
Sfzkv 48.8384 7.21212 10.1717
Lyjyh 51 49.1919 56.9899
Nkkuf 89.0202 95.8586 93.4343

Outputs:

Students who fails the grade include:
Phqgh 54
Cxggb 52.1
Uxwfn 17.4
Tkjpr 54.5
Syycq 33.1
Vwsre 52.9
Dpooe 40.7
Ejuvp 44
Jvrvi 15.9
Hwqnq 49.7
Sfzkv 16.7
Lyjyh 52.7

Students who are awarded first class honours include:
Jjloo 86.4
Nkkuf 93.5

Students who are awarded second class honours include:
Nlfdx 66.4
Pnrvy 74.7
Ffmzn 66.2
Fxtls 61.2
Poeyl 68.3
Whmsn 61.8
- -

The test program shows our function works perfectly.

-
-

Exercise 6-9

Use a library algorithm to concatenate all the elements of a vector.

-

Solution & Results

The complete program below gives two possible solutions.

-

The logic of the first solution is that

-
    -
  1. take each element in vector vec as an independent container (i.e. a string).
  2. -
  3. apply copy to each independent string and copy all characters from it into a new string to hold the final result.
  4. -
  5. loop thru the vector vec and repeat step 2.
  6. -
-

Finally, each character contained in each element of the vector vec is copied and stored into the new string, which has the same effect as concatenating all the elements of vec.

-

The first solution should works fine but is not a better solution. Because if we don’t use copy, we can simply concatenate each element of vec without accessing each character.

-

The second solution applies accumulate algorithm to concatenate each element from vec directly. accumulate returns the sum of all elements of a sequence denoted by its first two arguments. The third argument provides the initial value for the summation and determines the type of the returned value. In this case, the initial value is set to an empty string.

-

A complete 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
#include <iostream>	// to get the declaration of cout
#include <algorithm> // to get the declaration of copy
#include <iterator> // to get the declaration of back_inserter
#include <vector> // to get the declaration of vector
#include <string> // to get the declaration of string
#include <numeric> // to get the declaration of accumulate

using std::vector; using std::string;
using std::cout; using std::copy;
using std::endl; using std::back_inserter;
using std::accumulate;

int main()
{
vector<string> vec{"Please", "write", "an", "analysis", "function"};

// method 1
string vecCopy1;
for(vector<string>::const_iterator iter = vec.begin(); iter != vec.end(); ++iter)
{
copy((*iter).begin(), (*iter).end(), back_inserter(vecCopy1));
}
cout << vecCopy1 << endl;

// method 2
string vecCopy2;
vecCopy2 = accumulate(vec.begin(), vec.end(), vecCopy2);
cout << vecCopy2 << endl;

return 0;
}
-

The results below show that both two methods work fine and give correct results.

-

Results

-
1
2
Pleasewriteananalysisfunction
Pleasewriteananalysisfunction
- -
-

Reference

Koenig, A. and Moo, B.E., 2000. Accelerated C++.

-

Lippman, S.B., Lajoie, J. and Moo, B.E., 2013. C++ Primer, 5th edt.

+ + + + Revisit the grading programThis section redesigns the grading program described in Organizing programs with data structures. The new program is requir + ... + +
+ + Read more » + +
+ + +
+ + + + + -
+ +
+ + + + + + + +
-
+ +
+

+ + +
+ + + + + + + - +

- + @@ -822,166 +1098,178 @@

-

Revisit the classifying program

Recalling the extract_fails function developed in last chapter:

-
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
vector<Student_info> extract_fails(vector<Student_info>& students)
{
vector<Student_info> fail;
vector<Student_info>::size_type i = 0;
// invariant:elements [0, i) of students represent passing grades
while (i != students.size())
{
if (fgrade(students[i]))
{
fail.push_back(students[i]);
students.erase(students.begin() + i);
}
else
++i;
}
return fail;
}
-

Instead of using member functions like push_back and erase, we can also apply library algorithms to accomplish the task that extracts failing records. Following parts introduce two algorithmic solutions.

-

A two-pass solution

1
2
3
4
5
6
7
8
vector<Student_info> extract_fails(vector<Student_info> &students)
{
vector<Student_info> fails;
remove_copy_if(students.begin(), students.end(), back_inserter(fails), pgrade);

students.erase(remove_if(students.begin(), students.end(), fgrade), students.end());
return fails;
}
-

This function uses remove_copy_if algorithm to copy all failing records from students into a new vector fails. remove_copy_if finds and “removes”(i.e. not copy) all values that satisfy the predicate pgrade. Apparently, pgrade is a predicate on grades and returns true if the final grade is equal or greater than 60. In other words, it is a predicate that reverts the results of calling fgrade.

-
1
2
3
4
bool pgrade(const Student_info &s)
{
return !fgrade(s);
}
-

However, remove_copy_if works similar to remove_copy. Both two algorithms won’t change the input sequence instead they copy the remaining values into a new vector. Therefore, we need one step more to modify students:

-
1
students.erase(remove_if(students.begin(), students.end(), fgrade), students.end());
-

In this statements, remove_if finds and “removes” all values that satisfies the predicate fgrade. However, the tricky point is that how does it remove those faling records?

-

The removal is done by replacing the elements for which pred returns true by the next element for which it does not, and signaling the new size of the shortened range by returning an iterator to the element that should be considered its new past-the-end element. The relative order of the elements not removed is preserved, while the elements between the returned iterator and last are left in a valid but unspecified state (see remove_if).

-

For example, we have a sequence of final grades:

-
1
s0(pass), s1(fail), s2(fail), s3(pass), s4(pass), s5(fail)
-

After the execution of remove_if function like above, the sequence becomes

-
1
s0(pass), ns1(pass), ns2(pass), s3(pass), s4(pass), s5(fail)
-

It copies passing records, s3 and s4, and replacing the faling records, s1 and s2. Then, it returns an iterator that refers to s3. The elements beteen s3 and s.end() (i.e. one past s5) are still there. Therefore, we need to erase them using erase function, resulting that only passing grades are left. The erase function here takes two arguments which denote a range of elements to be erased. It returns the new students.end().

-

A single pass solution

The two pass solution seems not very ideal as it computes each final grade twice. Another algorithm stable_partition solves this by seperating a sequence into two pieces using a predicate. For example,

-
1
stable_partition(students.begin(), students.end(), pgrade);
-

This algorithm rearranges the sequence and put all elements that satisfy pgrade ahead of the elements that do not satisfy pgrade. Then, it returns an iterator that denotes the first element of the second group (i.e. one past the last element of the first group). If all values do not satisfy the pgrade, the returned iterator refers to the first element of the sequence.

-

Now we can rewrite the extract_fail function applying stable_partition algorithm.

-
1
2
3
4
5
6
7
vector<Student_info> extract_fails(vector<Student_info> &students)
{
vector<Student_info>::iterator iter = stable_partition(students.begin(), students.end(), pgrade);
vector<Student_info> fails(iter, students.end());
students.erase(iter, students.end());
return fails;
}
-

A complete program

I revised the program that developed in Exercise 5-6 by replacing the original extract_fails function with above two functions. The new program accomplishes a simple comparison of the two alternatives. All files and the test results can be found in the remainder of this post.

-

file list

-
    -
  1. mainfunction.cpp.
  2. -
  3. extract_fails.cpp, extract_fails.h: declares and defines functions including fgrade, pgrade, extract_fails_m1, extract_fails_m2.
  4. -
  5. grade.cpp, grade.h: declares and defines functions including grade, median.
  6. -
  7. Student_info.cpp, Student_info.h: declares and defines functions including compare, read, read_hw, and Student_info type struct.
  8. -
  9. print.cpp, print.h: declares and defines functions including print.
  10. -
-
-

mainfunction.cpp

-
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
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
// Accelerated C++ Solutions Exercises 6-0: the revised extract_fails
#include <algorithm>
#include <stdexcept>
#include <iostream>
#include <vector>
#include <string>
#include <iterator>
#include <chrono>
#include "Student_info.h"
#include "grade.h"
#include "print.h"
#include "extract_fails.h"

using std::cin; using std::sort; using std::max;
using std::cout; using std::endl; using std::domain_error;
using std::string; using std::max; using std::stable_partition;
using std::vector; using std::remove_copy;
using std::remove_copy_if; using std::back_inserter;

int main()
{
vector<Student_info> students;
Student_info record;
string::size_type maxlen = 0;

// read and store all the records, and find the length of the longest name
while(read(cin, record))
{
maxlen = max(maxlen, record.name.size());
students.push_back(record);
}
try{
// measure the performance for two pass solution
typedef std::chrono::high_resolution_clock Clock;
Clock::time_point startTime = Clock::now(); // get current time
vector<Student_info> fails = extract_fails_m1(students); // extract records for failing students
Clock::time_point endTime = Clock::now(); // get current time

cout << "The two pass solution took me "
<< std::chrono::duration_cast<std::chrono::duration<double>>(endTime - startTime).count() << " seconds" << endl;


// measure the performance for one pass solution
startTime = Clock::now(); // get current time
fails = extract_fails_m2(students); // extract records for failing students
endTime = Clock::now(); // get current time

cout << "The one pass solution took me "
<< std::chrono::duration_cast<std::chrono::duration<double>>(endTime - startTime).count() << " seconds" << endl;

// write each line of outputs for passing students
if (!students.empty())
{
//alphabetize the records

sort(students.begin(), students.end(), compare);
cout << "Students who passed: " << endl;
print(students, maxlen);
}
else
cout << "What a pity! all students failed.";

// write a blank line
cout << endl;

// // write each line of outputs for failing students
if(!fails.empty())
{
//alphabetize the records

sort(fails.begin(), fails.end(), compare);
cout << "Students who failed: " << endl;
print(fails, maxlen);
}
else
cout << "Congratulations! all students passed.";

}catch(domain_error e){
cout << e.what();
}

return 0;
}
- -

extract_fails.cpp

-
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
#include <vector>			
#include <algorithm>
#include <iterator>
#include "Student_info.h"
#include "grade.h"
#include "extract_fails.h"

using std::vector; using std::stable_partition;
using std::remove_if; using std::remove_copy_if;
using std::back_inserter;

// predicate
bool fgrade(const Student_info &s)
{
return grade(s) < 60;
}
// predicate
bool pgrade(const Student_info &s)
{
return !fgrade(s);
}

// two-pass solution
vector<Student_info> extract_fails_m1(vector<Student_info> &students)
{
vector<Student_info> fails;
remove_copy_if(students.begin(), students.end(), back_inserter(fails), pgrade);

students.erase(remove_if(students.begin(), students.end(), fgrade), students.end());
return fails;
}

// single-pass solution
vector<Student_info> extract_fails_m2(vector<Student_info> &students)
{
vector<Student_info>::iterator iter = stable_partition(students.begin(), students.end(), pgrade);
vector<Student_info> fails(iter, students.end());
students.erase(iter, students.end());
return fails;
}
- -

extract_fails.h

-
1
2
3
4
5
6
7
8
9
10
11
12
#ifndef GUARD_EXTRACT_FAILS_H
#define GUARD_EXTRACT_FAILS_H

#include <vector>
#include "Student_info.h"

bool fgrade(const Student_info &);
bool pgrade(const Student_info &);
std::vector<Student_info> extract_fails_m1(std::vector<Student_info> &);
std::vector<Student_info> extract_fails_m2(std::vector<Student_info> &);

#endif /* GUARD_EXTRACT_FAILS_H */
- -

grade.cpp

-
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
#include <algorithm>
#include <stdexcept>
#include <vector>
#include "grade.h"
#include "Student_info.h"

using std::domain_error; using std::istream;
using std::vector; using std::sort;

// grade function 2
double grade(const Student_info &s)
{
return grade(s.midterm, s.final, s.homework);
}

// grade function 2
double grade(double midterm, double final, const vector<double> &hw)
{
if (hw.size() == 0)
throw domain_error("student has done no homework");
return grade(midterm, final, median(hw));
}

// grade function 3
double grade(double midterm, double final, double homework)
{
return 0.2*midterm + 0.4*final + 0.4*homework;
}

// compute the median of vector<double>
double median(vector<double> vec)
{
// get the size of the vector
typedef vector<double>::size_type vec_size;
vec_size size = vec.size();

// check whether the empty is empty
if (size == 0)
throw domain_error("median of an empty vector");

// sort the grades
sort(vec.begin(), vec.end());

// compute the median homework grade
vec_size mid = size/2;
return size % 2 == 0 ? (vec[mid] + vec[mid - 1])/2 : vec[mid];
}
···

**grade.h**
```c++
#ifndef GUARD_grade_h
#define GUARD_grade_h

// grade.h
#include<vector>
#include "Student_info.h"

double grade(const Student_info &);
double grade(double, double, const std::vector<double> &);
double grade(double, double, double);
double median(std::vector<double>);
#endif
- -

Student_info.cpp

-
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 <vector>
#include <iostream>
#include "Student_info.h"

using std::vector; using std::istream;

bool compare(const Student_info &x, const Student_info &y)
{
return x.name < y.name;
}

istream & read(istream &is, Student_info &s)
{
// reads and store the student's name, midterm and final exam grades
is >> s.name >> s.midterm >> s.final;

// reads and store all homework grades
read_hw(is, s.homework);
return is;
}

istream & read_hw(istream &in, vector<double> &hw)
{
if (in){
//get rid of previous contents
hw.clear();

// read homework grades
double x;
while(in >> x)
hw.push_back(x);

// clear the stream so that input will work for the next student
in.clear();
}
return in;
}
- -

Student_info.h

-
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
#ifndef GUARD_Student_info
#define GUARD_Student_info

#include<iostream>
#include<string>
#include<vector>

struct Student_info{
std::string name;
double midterm, final;
std::vector<double> homework;
};

bool compare(const Student_info &, const Student_info &);
std::istream & read(std::istream &, Student_info &);
std::istream & read_hw(std::istream &, std::vector<double> &);
#endif
- -

print.cpp

-
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
#include <iostream>
#include <vector>
#include <iomanip>
#include <string>
#include "grade.h"
#include "print.h"
#include "Student_info.h"

using std::cout; using std::setprecision;
using std::endl; using std::streamsize;
using std::string; using std::vector;

void print(const vector<Student_info> &records, const string::size_type &maxlen)
{
for (vector<Student_info>::const_iterator iter = records.begin(); iter != records.end(); ++iter)
{
// write the name, blanks
cout << (*iter).name << string(maxlen + 1 - (*iter).name.size(), ' ');

// compute and write the final grade
double final_grade = grade(*iter);
streamsize prec = cout.precision();
cout << setprecision(3) << final_grade << setprecision(prec) << endl;
}
}
- -

print.h

-
1
2
3
4
5
6
7
8
9
10
#ifndef GUARD_PRINT_H
#define GUARD_PRINT_H

#include <string>
#include <vector>
#include "Student_info.h"

void print(const std::vector<Student_info> &, const std::string::size_type &);

#endif /* GUARD_PRINT_H */
- -

Performance comparison

- - - - - - - - - - - - - - - - - - - - - - -
Number of linesTwo-pass solutionSingle-pass solution
100.001035 seconds0.000000 seconds
10000.006006 seconds0.000993 seconds
100000.017035 seconds0.003023 seconds
-

The results shows as expected that the single-pass solution has much better performance that the two-pass solution.

-

A simple summary

Algorithm act on container elements but do not act on containers and do not change the size of a container.

+ + + + Generic algorithms for operations on stringscopyRecalling the vcat function described in Vertical concatenation:1234for (vector<string>::const_i + ... + +
+ + Read more » + +
+ + +
+ + + + + -
+ +
+ + + + + + + +
-
+ +
+ + + +

+ + + + + + + - +
+ -
- +
+ + +
-

- - -

+ + + +

+ +

+
+ @@ -989,209 +1277,179 @@

-

Revisit the grading program

This section redesigns the grading program described in Organizing programs with data structures. The new program is required to include extra two grading schemes(Koenig and Moo 2000):

-

1. using the average instead of the median, and treating those assignments that the student failed to turn in as zero.
2. using the median of only the assignments that the student actually submitted.

-

Further, the program should solve following problems based on these grading schemes:

-

1. reading all the student records, separating the students who did all the homework from the others.
2. apply each of the grading schemes to all the students in each group, and report the median grade of each group.

-

Now, let’s follow the instructions and finish this program step by step.

-

Reading and Separating the students records

According to the first problem, we need to read and separate the students records into two groups, one group of which includes students who did all homeworks, and another group includes students who didn’t submit all homeworks. The read function has been introduced before and hence no further analysis. What is the next is to use a predict on students’ records and store the records separately based on the check results.

-
1
2
3
4
bool did_all_hw(const Student_info &s)
{
return ((find(s.homework.begin(), s.homework.end(), 0)) == s.homework.end());
}
-

The return statements in the function body means that if none of the homework grades equal to 0, the function returns true. The idea behind it is that overdue homeworks are given 0 grades.

-

Now this part can be accomplished with following 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
// to hold two groups of students records
vector<Student_info> did, didnt;
Student_info student;

// read all the records and separating them based on whether all homework was done
while(read(cin, student))
{
if(did_all_hw(student))
did.push_back(student);
else
didnt.push_back(student);
}

// check that both groups contain dada
if (did.empty())
{
cout << "No student did all the homework!" << endl;
return 1;
}
if (didnt.empty())
{
cout << "Every student did all the homework!" << endl;
return 1;
}
-

Three grading schemes

Before we go into the second problem, we need to solve three grading schemes first. All schemes compute the final grade as the weighted average of midterm exam grade, final exam grade, and homework grade. The grade function below returns the final grade if it is called.

-
1
2
3
4
double grade(double midterm, double final, double homework)
{
return 0.2*midterm + 0.4*final + 0.4*homework;
}
-

Midterm grade and final exam grade are fixed values once the information are read in. However, the homework grade can be computed using different methods depending on the grading schemes. Specifically:

-
    -
  1. scheme 1: computes the homework grade as the median value of homework grades.
  2. -
  3. scheme 2: computes the homework grade as the average value of homework grades.
  4. -
  5. scheme 3: computes the homework grade as the median value of homework grades excluding the zero grades (i.e. grades for overdue homeworks).
  6. -
-

In addition, if one did not do homework at all, his homework grade would be set to 0.

-

Fundamentally, there are two types of homework grade, one is the median value and another is the average value. Here are the two functions that return the median value and average value of a sequence of double values stored in a vector, respectively.

-
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
// fundermental functions 1: returns the median value of vector<double>
double median(vector<double> vec)
{
// get the size of the vector
typedef vector<double>::size_type vec_size;
vec_size size = vec.size();

// check whether the empty is empty
if (size == 0)
throw domain_error("median of an empty vector");

// sort the grades
sort(vec.begin(), vec.end());

// compute the median homework grade
vec_size mid = size/2;
return size % 2 == 0 ? (vec[mid] + vec[mid - 1])/2 : vec[mid];
}

// fundermental functions 2: returns the average value of vector<double>
double average(const vector<double> &v)
{
// check whether the empty is empty
if (v.empty())
throw domain_error("average of an empty vector");

return accumulate(v.begin(), v.end(), 0.0) / v.size();
}
-

What’s the new idea in above functions is that we compute the average using the accumulate algorithm. The accomulate is defined in the staandard header . It takes three arguments, the first two of which denotes a range of values to be summed while the third arguments gives the initial value for the summation. It is worth noting that we uses 0.0 rather than 0 due to the fact the type of the resulted sum is the type of the third argument.

-

Note that I slightly changes the average function provided in the book for the purpose of avoiding the case that v.size() == 0. As mentioned earlier, if one did not submit one or more homeworks, he would get 0 grades for the corresponding homeworks. But, the program may encounter the case that there is no any inputs for the homeworks, e.g. when someone did not do homeworks at all. To response this case, the homework grade is set to 0 directly. We need to catch the exception and handle the case in next step.

-

The median grading scheme

We are familar with the first grading scheme, that is, the original scheme we used to compute the final grade, in previous chapters. For convenience sake, I renamed the original functions grade as median_grade (as shown below).

-
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
/ grading scheme 1: final grade is based on the median homework grade
double median_grade(const Student_info &s)
{
return median_grade(s.midterm, s.final, s.homework);
}

// grading scheme 1: overloaded median_grade function
double median_grade(double midterm, double final, const vector<double> &hw)
{
if (hw.size() == 0)
throw domain_error("student has done no homework");
return grade(midterm, final, median(hw));
}

// grading scheme 1: auxiliary median_grade function
double median_grade_aux(const Student_info &s)
{
try{
return median_grade(s);
}catch (domain_error){
// students who did no homework at all, get 0 homework grade
return grade(s.midterm, s.final, 0);
}
}
-

It can be observed that a new function, median_grade_aux ,is included for the median grading scheme. The reason is that we’ll pass the median_gade as an argument, however, we cann’t pass an overloaded function easily. Therefore, we define an auxiliary function and handle the exception in this function. As analysed above, the function return a 0 homework grade once catches an exception.

-

The average grading scheme

The average based final grade is computed exactly the same as the median version as long as we replace the median value as the average value. The function handles the exception in the same manner.

-
1
2
3
4
5
6
7
8
9
10
// grading scheme 2: final grade is based on average homework grades
double average_grade(const Student_info &s)
{
try{
return grade(s.midterm, s.final, average(s.homework));
}catch (domain_error){
// students who did no homework at all, get 0 homework grade
return grade(s.midterm, s.final, 0);
}
}
-

The optimistic_median grading scheme

The third grading scheme is named as optimistic median as it ignores the zero homework grades and consequently improves students’ overall performances. Following code shows how this function works.

-
1
2
3
4
5
6
7
8
9
10
11
12
// grading scheme 3: final grade is based on median of the completed homework grades,
// and students who did no homework at all will get 0 homework grade
double optimistic_median(const Student_info &s)
{
vector<double> nonzero;
remove_copy(s.homework.begin(), s.homework.end(), back_inserter(nonzero), 0);

if(nonzero.empty())
return grade(s.midterm, s.final, 0);
else
return grade(s.midterm, s.final, median(nonzero));
}
-

remove_copy algorithm takes four arguments. It searches from the range denoted by first two iterators, and finds all values that match a value given by its fourth argument. Then, these values are “removed” and the remaining elements are copied into a new vector nonzero. It doesn’t change the input sequence. In addition, back_inserter is applied to append the copied values to the new vector, which ensures that the space is enough for holding all the elements.

-

Analyze the grades

The next step is to apply each of above schemes to each group, that is, to compute the median of each group. Let’s have a look at the function that returns the median grade of a vector students based on the median homework grade.

-
1
2
3
4
5
6
7
double median_analysis(const vector<Student_info> &students)
{
vector<double> grades;

transform(students.begin(), students.end(), back_inserter(grades), median_grade_aux);
return median(grades);
}
-

The function introduces a new algorithm transform which calls median_grade_aux, namely the transform function, and stores the result into a new vector. The first two iterators specify a range of elements to transform. back_inserter provides the destination and ensures that there is enough for the elements. Finally, the function returns the median value of the final grades.

-

Analogously, we can define another two functions that return average homework grade based median grade and optimistic median homework grade based median grade.

-
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
// average
double average_analysis(const vector<Student_info> &students)
{
vector<double> grades;

transform(students.begin(), students.end(), back_inserter(grades), average_grade);
return median(grades);
}

// median of the completed homework
double optimistic_median_analysis(const vector<Student_info> &students)
{
vector<double> grades;
transform(students.begin(), students.end(), back_inserter(grades), optimistic_median);
return median(grades);
}
-

Report the analysis

As described above, we need to compute three types of median grade for both two groups. In addition, we would better to generate a report such that we can compare two groups regarding to one specific type of median grade. The code below shows how we incorporate these driven factors into a analysis function:

-
1
2
3
4
5
6
7
8
void write_analysis(ostream &out, const string &name,
double analysis(const vector<Student_info> &),
const vector<Student_info> &did,
const vector<Student_info> &didnt)
{
out << name << ": median(did) = " << analysis(did)
<< ": median(didnt) = " << analysis(didnt) << endl;
}
-

This function takes four arguments, of which: the first is an object of the outstream; the second is a function; the third and fourth arguments are two objects of vector. What’ new here is that when passing a function as a parameter, we declare the parameter exactly as the same as ddeclare the function itself. Apparently, we’ll pass three analysis function defined above to this write analysis function.

-

A complete program

Now, we can complete the main function:

-

mainfunction.cpp

-
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
#include <iostream>	    // to get the declaration of cin, cout, endl
#include <vector> // to get the declaration of vector
#include "Student_info.h" // to get the declaration of Student_info
#include "did_all_hw.h" // to get the declatation of the predicate on students' records
#include "grades_analysis.h"// to get the declaration of three analysis function
#include "write_analysis.h" // to get the declaration of write_analysis function

using std::vector; using std::cout;
using std::cin; using std::endl;

int main()
{
// students who did and didn't do all their homework
vector<Student_info> did, didnt;

// read the student records and partition time
Student_info student;
while(read(cin, student))
{
if(did_all_hw(student))
did.push_back(student);
else
didnt.push_back(student);
}
// verify thatthe analyses will show us something
if(did.empty())
{
cout << "No student did all the homework!" << endl;
return 1;
}
if(didnt.empty())
{
cout << "Every student did all the homework!" << endl;
return 1;
}

// do the analysis
write_analysis(cout, "median", median_analysis, did, didnt);
write_analysis(cout, "average", average_analysis, did, didnt);
write_analysis(cout, "median of homework turned in", optimistic_median_analysis, did, didnt);

return 0;
}
-

I had seperate other functions into different files according to my own preference:

-
    -
  1. mainfunction.cpp (as shown above).

    -
  2. -
  3. did_all_hw.cpp, did_all_hw.h: including the function did_all_hw.

    -
  4. -
  5. gradingSchemes.cpp, gradingSchemes.h: covering following functions

    -
      -
    • grade, median, average, median_grade, median_grade_aux, average_grade, optimistic_median.
    • -
    -
  6. -
  7. grades_analysis.cpp, grades_analysis.h: covering following functions

    -
      -
    • median_analysis, average_analysis, optimistic_grade_median.
    • -
    -
  8. -
  9. write_analysis.cpp, write_analysis.h: including the function write_analysis.

    -
  10. -
  11. Student_info.cpp, Stuent_info.h: covering following functions

    -
      -
    • compare, read, read_hw.
    • -
    -
  12. -
-

All profiles are presented below in order.

-

did_all_hw.cpp

-
1
2
3
4
5
6
7
8
9
10
#include <algorithm>		// to get the declaration of find
#include "Student_info.h" // to get the declaration of Student_info
#include "did_all_hw.h" // to get the declaration of did_all_hw itself

using std::find;

bool did_all_hw(const Student_info &s)
{
return ((find(s.homework.begin(), s.homework.end(), 0)) == s.homework.end());
}
-

did_all_hw.h

-
1
2
3
4
5
6
7
8
#ifndef GUARD_DID_ALL_HW_H
#define GUARD_DID_ALL_HW_H

#include "Student_info.h"

bool did_all_hw(const Student_info &s);

#endif /* GUARD_DID_ALL_HW_H */
- -

gradingSchemes.cpp

-
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
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
#include <algorithm>	    // to get the declaration of remove_copy
#include <numeric> // to get the declaration of accumulate
#include <stdexcept> // to get the declaration of domain_error
#include <vector> // to get the declaration of vector
#include "Student_info.h" // to get the declaration of Student_info
#include "gradingSchemes.h" // to get the declaration of all functions here to keep consistent

using std::domain_error; using std::istream;
using std::vector; using std::sort;
using std::accumulate;

// final grade function returns weighted average of midterm exam grade,
// final exam grade, and homework grade which will be computed
// using different methods depending on grading schemes
double grade(double midterm, double final, double homework)
{
return 0.2*midterm + 0.4*final + 0.4*homework;
}

// fundermental functions 1: returns the median value of vector<double>
double median(vector<double> vec)
{
// get the size of the vector
typedef vector<double>::size_type vec_size;
vec_size size = vec.size();

// check whether the empty is empty
if (size == 0)
throw domain_error("median of an empty vector");

// sort the grades
sort(vec.begin(), vec.end());

// compute the median homework grade
vec_size mid = size/2;
return size % 2 == 0 ? (vec[mid] + vec[mid - 1])/2 : vec[mid];
}

// fundermental functions 2: returns the average value of vector<double>
double average(const vector<double> &v)
{
// check whether the empty is empty
if (v.empty())
{ throw domain_error("average of an empty vector");}

return accumulate(v.begin(), v.end(), 0.0) / v.size();
}


// grading scheme 1: final grade is based on the median homework grade
double median_grade(const Student_info &s)
{
return median_grade(s.midterm, s.final, s.homework);
}

// grading scheme 1: overloaded median_grade function
double median_grade(double midterm, double final, const vector<double> &hw)
{
if (hw.size() == 0)
throw domain_error("student has done no homework");
return grade(midterm, final, median(hw));
}

// grading scheme 1: auxiliary median_grade function
double median_grade_aux(const Student_info &s)
{
try{
return median_grade(s);
}catch (domain_error){
// students who did no homework at all, get 0 homework grade
return grade(s.midterm, s.final, 0);
}
}

// grading scheme 2: final grade is based on average homework grades
double average_grade(const Student_info &s)
{
try{
return grade(s.midterm, s.final, average(s.homework));
}catch (domain_error){
// students who did no homework at all, get 0 homework grade
return grade(s.midterm, s.final, 0);
}
}

// grading scheme 3: final grade is based on median of the completed homework grades,
// and students who did no homework at all will get 0 homework grade
double optimistic_median(const Student_info &s)
{
vector<double> nonzero;
remove_copy(s.homework.begin(), s.homework.end(), back_inserter(nonzero), 0);

if(nonzero.empty())
return grade(s.midterm, s.final, 0);
else
return grade(s.midterm, s.final, median(nonzero));
}
- -

gradingScheme.h

-
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
#ifndef GUARD_GRADING_SCHEMES_H
#define GUARD_GRADING_SCHEMES_H

#include<vector>
#include "Student_info.h"

double grade(double midterm, double final, double homework);
double median(std::vector<double> vec);
double average(const std::vector<double> &v);
double median_grade(const Student_info &s);
double median_grade(double midterm, double final, const std::vector<double> &hw);
double median_grade_aux(const Student_info &s);
double average_grade(const Student_info &s);
double optimistic_median(const Student_info &s);

#endif /* GUARD_GRADING_SCHEMES_H */
- -

grades_analysis.cpp

-
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
#include <vector>		// to get the declaration of vector
#include <iterator> // to get the declaration of back_inserter
#include <stdexcept> // to get the declaration of domain_error
#include <algorithm> // to get the declaration of transform
#include "Student_info.h" // to get the declaration of Student_info
#include "grades_analysis.h" // to get the declaration of all functions here to keep consistent
#include "gradingSchemes.h" // to get the declaration of median_grade_aux, average_grade, optimistic_median

using std::vector; using std::transform;
using std::domain_error; using std::back_inserter;

// median
double median_analysis(const vector<Student_info> &students)
{
vector<double> grades;

transform(students.begin(), students.end(), back_inserter(grades), median_grade_aux);
return median(grades);
}

// average
double average_analysis(const vector<Student_info> &students)
{
vector<double> grades;

transform(students.begin(), students.end(), back_inserter(grades), average_grade);
return median(grades);
}

// median of the completed homework
double optimistic_median_analysis(const vector<Student_info> &students)
{
vector<double> grades;
transform(students.begin(), students.end(), back_inserter(grades), optimistic_median);
return median(grades);
}
- -

grades_analysis.h

-
1
2
3
4
5
6
7
8
9
10
11
#ifndef GUARD_GRADES_ANALYSIS_H
#define GUARD_GRADES_ANALYSIS_H

#include <vector>
#include "Student_info.h"

double median_analysis(const std::vector<Student_info> &students);
double average_analysis(const std::vector<Student_info> &students);
double optimistic_median_analysis(const std::vector<Student_info> &students);

#endif /* GUARD_GRADES_ANALYSIS_H */
- -

write_analysis.cpp

-
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
#include <iostream>		// to get the declaration of cout, endl, ostream
#include <string> // to get the declaration of string
#include <vector> // to get the declaration of vector
#include "Student_info.h" // to get the declatation of Student_info
#include "write_analysis.h" // to get the declatation of write_analysis itself

using std::cout; using std::endl;
using std::string; using std::vector;
using std::ostream;

void write_analysis(ostream &out, const string &name,
double analysis(const vector<Student_info> &),
const vector<Student_info> &did,
const vector<Student_info> &didnt)
{
out << name << ": median(did) = " << analysis(did)
<< ": median(didnt) = " << analysis(didnt) << endl;
}
-

write_analysis.h

-
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
#ifndef GUARD_WRITE_ANALYSIS_H
#define GUARD_WRITE_ANALYSIS_H

#include <iostream>
#include <string>
#include <vector>
#include "Student_info.h"

void write_analysis(std::ostream &out, const std::string &name,
double analysis(const std::vector<Student_info> &),
const std::vector<Student_info> &did,
const std::vector<Student_info> &didnt);


#endif /* GUARD_WRITE_ANALYSIS_H */
- -

Student_info.cpp

-
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
#include "Student_info.h"
using std::vector; using std::istream;

// argument to the function sort
bool compare(const Student_info &x, const Student_info &y)
{
return x.name < y.name;
}

// read the info
istream & read(istream &is, Student_info &s)
{
// reads and store the student's name, midterm and final exam grades
is >> s.name >> s.midterm >> s.final;

// reads and store all homework grades
read_hw(is, s.homework);
return is;
}

// read all homework grades
istream & read_hw(istream &in, vector<double> &hw)
{
if (in)
{
//get rid of previous contents
hw.clear();

// read homework grades
double x;
while(in >> x)
hw.push_back(x);

// clear the stream so that input will work for the next student
in.clear();
}
return in;
}
- -

Student_info.h

-
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
#ifndef GUARD_Student_info
#define GUARD_Student_info

#include<iostream>
#include<string>
#include<vector>

struct Student_info{
std::string name;
double midterm, final;
std::vector<double> homework;
};

bool compare(const Student_info &, const Student_info &);
std::istream & read(std::istream &, Student_info &);
std::istream & read_hw(std::istream &, std::vector<double> &);
#endif
-

A simple Test

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
Inputs:

Xxzrz 87.1414 3.48485 18 58 0
Oketl 98.0909 57.7273 92 38 22
Gzqrc 91.1515 88.5657 34 2 11
Ojway 86.7576 33.697 16 44 42
Psajl 99.5758 76.9293 12 75 89
Aovlz 88.0101 89.5556 85 2 23
Cpwsr 57.3232 32.697 89 21 54
Izcob 55.3434 49.4141 60 45 12
Ijtvd 96.8788 29.4949 49 66 37
Ldvgy 5.88889 82.5556 1 14 34
Mborx 55.8586 53.1212 45 32 8
Xohgm 82.8182 44.9697 61 29 22
Xotog 59.9293 39.5354 10 54 24
Nfetc 22.6869 18.8788 91 58 5
Kljug 24.3434 74.7273 70 33 59
Zjenp 70.6364 68.9293 80 2 85
Pjsrd 25.4343 24.2323 81 61 72
Lchhb 31.9293 42.2222 0 64 86
Zobiw 70.3535 33.1111 67 96 60
Fsksr 24.1919 25.7677 2 58 94
Dcyzj 84.1818 64.1919 87 0 52
Qcozi 15.7677 27.4343 9 64 58
Eeddp 74.2525 27.2929 20 23 28
Mmtat 61.9596 25.6465 16 2 60
Muvnp 47.5354 20.9091 63 88 24
Azuxm 13.5859 78.6566 0 77 7

Outputs:
median: median(did) = 46.1475: median(didnt) = 42.9273
average: median(did) = 45.4202: median(didnt) = 44.3273
median of homework turned in: median(did) = 46.1475: median(didnt) = 52.1273
- -

Merging three analysis functions into one single function

It has been observed that three analysis functions are in fact defined in the same manner. The only difference between them is the statements:

-
1
transform(students.begin(), students.end(), back_inserter(grades), //transform function to be passed);
-

Therefore, we can redefine the analysis function such that the transform function can be passed to the analysis function as an argument. We have learned how to do this from the write_analysis function. Now let’s define it:

-
1
2
3
4
5
6
7
double analysis(const vector<Student_info> &students, double grading_scheme(const Student_info &s))
{
vector<double> grades;

transform(students.begin(), students.end(), back_inserter(grades), grading_scheme);
return median(grades);
}
-

For example, if we want to compute the average based median grade, we would call this function like:

-
1
analysis(did, average_grade);
-

Correspondingly, we need to change the write_analysis function as well.

-
1
2
3
4
5
6
7
8
void write_analysis(ostream &out, const string &name,
double grading_scheme(const Student_info &s),
const vector<Student_info> &did,
const vector<Student_info> &didnt)
{
out << name << ": median(did) = " << analysis(did, grading_scheme)
<< ": median(didnt) = " << analysis(didnt, grading_scheme) << endl;
}
-

I define the new write_analysis function such that it takes the grading_scheme functions as its second arguments. To qualify the use of the analysis function, I add #include “grade_analysis.h” into write_analysis.cpp. In addition, I add #include “gradingSchemes.h” into mainfunction.cpp for qualifing the use of three grading_scheme functions. Please find the revised files shown in below.

-

modified file 1: grades_analysis.cpp

-
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
#include <vector>		// to get the declaration of vector
#include <iterator> // to get the declaration of back_inserter
#include <stdexcept> // to get the declaration of domain_error
#include <algorithm> // to get the declaration of transform
#include "Student_info.h" // to get the declaration of Student_info
#include "grades_analysis.h" // to get the declaration of all functions here to keep consistent
#include "gradingSchemes.h" // to get the declaration of the median function

using std::vector; using std::transform;
using std::domain_error; using std::back_inserter;

double analysis(const vector<Student_info> &students, double grading_scheme(const Student_info &s))
{
vector<double> grades;

transform(students.begin(), students.end(), back_inserter(grades), grading_scheme);
return median(grades);
}
- -

modified file 2: grades_analysis.h

-
1
2
3
4
5
6
7
8
9
10
#ifndef GUARD_GRADES_ANALYSIS_H
#define GUARD_GRADES_ANALYSIS_H

#include <vector>
#include "Student_info.h"

double analysis(const std::vector<Student_info> &students,
double grading_scheme(const Student_info &s));

#endif /* GUARD_GRADES_ANALYSIS_H */
- -

modified file 3: write_analysis.cpp

-
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
#include <iostream>		// to get the declaration of cout, endl, ostream
#include <string> // to get the declaration of string
#include <vector> // to get the declaration of vector
#include "Student_info.h" // to get the declatation of Student_info
#include "write_analysis.h" // to get the declatation of write_analysis itself
#include "grades_analysis.h" // to get the declaration of analysis function

using std::cout; using std::endl;
using std::string; using std::vector;
using std::ostream;

void write_analysis(ostream &out, const string &name,
double grading_scheme(const Student_info &s),
const vector<Student_info> &did,
const vector<Student_info> &didnt)
{
out << name << ": median(did) = " << analysis(did, grading_scheme)
<< ": median(didnt) = " << analysis(didnt, grading_scheme) << endl;
}
- -

modified file 4: write_analysis.h

-
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
#ifndef GUARD_WRITE_ANALYSIS_H
#define GUARD_WRITE_ANALYSIS_H

#include <iostream>
#include <string>
#include <vector>
#include "Student_info.h"

void write_analysis(std::ostream &out, const std::string &name,
double grading_scheme(const Student_info &s),
const std::vector<Student_info> &did,
const std::vector<Student_info> &didnt);


#endif /* GUARD_WRITE_ANALYSIS_H */
- -

modified file 5: mainfunction.cpp

-
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
#include <iostream>		// to get the declaration of cin, cout, endl
#include <vector> // to get the declaration of vector
#include "Student_info.h" // to get the declaration of Student_info
#include "did_all_hw.h" // to get the declatation of the predicate on students' records
#include "write_analysis.h" // to get the declaration of write_analysis function
#include "gradingSchemes.h" // to get the declaration of three grading functions

using std::vector; using std::cout;
using std::cin; using std::endl;

int main()
{
// students who did and didn't do all their homework
vector<Student_info> did, didnt;

// read the student records and partition time
Student_info student;
while(read(cin, student))
{
if(did_all_hw(student))
did.push_back(student);
else
didnt.push_back(student);
}
// verify thatthe analyses will show us something
if(did.empty())
{
cout << "No student did all the homework!" << endl;
return 1;
}
if(didnt.empty())
{
cout << "Every student did all the homework!" << endl;
return 1;
}

// do the analysis
write_analysis(cout, "median", median_grade_aux, did, didnt);
write_analysis(cout, "average", average_grade, did, didnt);
write_analysis(cout, "median of homework turned in", optimistic_median, did, didnt);

return 0;
}
- -

I tested this program using the same inputs as I used in the original program. They work exactly the same and yield same outputs.

-
-

To be continued.

+ + + + Exercise 5-9Write a program to write the lowercase words in the input followed by the uppercase words. +Solution & ResultsThe solution strategy is + ... + +
+ + Read more » + +
+ + +
+ + + + + -
+ +
+ + + + + + + +
-
+ +
+

+ + +
+ + + + + + + - +
-
- + +
+ + +
-

- - -

+ + + +

+ +

+
+ @@ -1199,168 +1457,178 @@

-

Generic algorithms for operations on strings

copy

Recalling the vcat function described in Vertical concatenation:

-
1
2
3
4
for (vector<string>::const_iterator i = bottom.begin(); i != bottom.end(); ++i)
{
ret.push_back((*i));
}
-

Using a for loop, all elements of vector bottom are copied and appended to the end of the vector ret.
An alternative method is to use the insert function:

-
1
ret.insert(ret.end(), bottom.begin(), bottom.end());
-

These two methods rely on the member functions of a specified container. A more general solution, using generic algorithm copy, to solve the same question,

-
1
copy(bottom.begin(), bottom.end(), back_inserter(ret));
-

The generic algorithms provided in the standard library implement classic algorithms via iterator operations. They do not depend on any specific type of container. The copy algorithm is an algorithm that writes elements to a container. It takes three iterators, of which, the first two iterators indicates the input range while the third iterator denotes the starting point of the destination sequence.

-

back_inserter() is a iterator adaptor which is a function that returns an appropriate iterator (has type of back_insert_iterator) according to the argument. All intertor adaptors are defined in header . In this case, back_inserter() takes a container as its arguments and yields an iterator that, when used as a destination, appends values to the container. But noting that

-
1
copy(bottom.begin(), bottom.end(), ret.end())
-

is not allowed as there is no element at ret.end.

-

After this function completes, the size of ret increases by bottom.size(). The copy function returns an iterator ((has type of back_insert_iterator) that refers to the next postion of the last element of ret.

-

find_if algorithm

Now, using another generic algorithm find_if, we can simplify the split function described in Taking strings apart.

-
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
// true if the argument is whitespace, false otherwise
bool space(char c)
{
return isspace(c);
}

// false is the argument is whitespace, true otherwise
book not_space(char c)
{
return !isspace(c);
}

vector<string> split(const_string &str)
{
typedef string::const_iterator iter;
vector<string> ret;

iter i = str.begin();
while(i != str.end())
{
// ignore leading blanks
i = find_if(i, str.end(), not_space)

// find end of next word
iter j = find_if(i, str.end(), space);

// copy the characters in [i, j)
if(i != str.end())
ret.push_back(string(i, j));
i = j;
}
return ret;
}
-

First, in this function, we use iterators instead of indices. The core algorithm is that use find_if to firstly find a nonwhiltespace character and a whitespace that closely follows, which determines the range of a word.

-

find_if algorithm takes three arguments, of which, first two are iterators that denotes a sequence while the third one is a predicate on characters. The algorithm calls the predicate to test each element starting from *i, and stops when the predicate returns true, i.e. the character is not whitespace in this case. It returns an iterator that refers to the first element that satisfies the predicate. If find_if failed to find an element that satifies the predicate, it returns its second arguments, i.e. str.end() in this case.

-

It has been observed that we didn’t use isspace() directly instead we write our own functions. This is because that isspace() is overloaded depending on arguments and can’t be passed as an argument to a template function.

-

Another new usage is string(i,j), which constructs a new string that copies the value from the range [i, j).

-

equal algorithm

Now we introduce another algorithm equal, which can simplify the isPalindromes function described in Exercise 5-10.

-
1
2
3
4
bool is_palindrome(const string &s)
{
return equal(s.begin(), s.end(), s.rbegin());
}
-

It is consise enough compared with the one I wrote before. The equal algorithm takes three iterators, of which first two indicate the range of the first sequence while the third iterator denotes the inital position of the second sequence. It compares these two sequence and returns true otherwise returns false.

-

It is known that begin() returns an iterator that refers to the first element in a container. On the contrary, rbegin() returns an reverse iterator that refers to the last element in a container. Similarly, rend() returns to an iterator that refers to the position that before the first element.

-

Finding URLs

Considering that one or more URLs are embedded in a string, we are requested to write a function that finds each URL. A URL is a sequence of characters of the form:

-
1
protocol-name://resource-name
-

where protocol-name contains only letters, and resource-name may consist of letters, digits, and certain punctuation characters(Koenig and Moo 2000).

-

To some extent, this exercise is similar to the split function. Though determining the range of such a string is much complex than finding the range for a word, the idea is the same. The strategy can be divided into three steps:

-
    -
  1. looking for the characters :// that might be a part of a URL.
  2. -
  3. if we find these characters, then it looks backward to find the protocol-name and determines the begining position of the URL; then, it looks forward to find the resource-name and determines the ending position of the URL.
  4. -
  5. finally, we store the URL according the two positions and continue searching for next URL until we finishes the whole document held in the single string.
  6. -
-

We starts from the last step, providing the first two steps have been completed.

-

function to find URLs

-
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
vector<string> find_urls(const string &s)
{
vector<string> ret;
typedef string::const_iterator iter;
iter b = s.begin(), e = s.end();

// look through the entire input
while (b != e)
{
// look for one or more letters followed by ://
b = url_beg(b, e);

// if we found it
if(b != e)
{
// get the rest of the URL
iter after = url_end(b, e);

// remember the URL
ret.push_back(string(b, after));

// advance b and check for more URLs on this line
b = after;
}
}
return ret;
}
-

Function url_beg responsibles for finding the :// and then finding the begining position of the URL accordingly. url_end responsibles for finding the end position of the URL based on the results of url_beg.

-

As mentioned earlier, string(b, after) constructs the URL with two iterators which denotes a range [b, after) of a sequence, i.e. the URL. In other words, b is the interator that denotes the first element of the URL while after denotes the position that one past the last element in the URL. After stores the URL into vector ret, we set the initial position for finding next URL as the end of the previous URL, with setting b = after.

-

Now we consider url_end function first, providing that the begining position of a URL has been found.

-
1
2
3
4
string::const_iterator  url_end(string::const_iterator b, string::const_iterator e)
{
return find_if(b, e, not_url_char);
}
-

We have explained the find_if algorithm in above. It calls the precidate not_url_char and test each element starting from the position where b denotes, and stops until finds the element that makes the predicate returns true. It returns an iterator that refers to the first element that satifies the predicate, and returns its second arguments e if can’t find an element that satisfies the predicate. Now let’s write the predicate:

-
1
2
3
4
5
6
7
8
bool not_url_char(char c)
{
// characters, in addition to alphanumerics, that can appear in a URL
static const string url_ch = "~;/?:@=&$-_.+!*'(),";

// see whether c can appear in a URL and return the negative
return !(isalnum(c) || find(url_ch.begin(), url_ch.end(), c) != url_ch.end());
}
-

Yeah, the statements in the function body seems to be both compact and informative. The first statement defines a const string that contains all punctuation characters that can appear in a URL. What’s new here is the storage class specifier static. Local variables that declaraed with specifier static have static storage duration and are initialized only the first time. On all other calls, the declarations are skipped. In other words, the variable exists starting from the first time declaration to when the program finishes. By doing so, the const string url_ch avoids being declared each time when the predicate is called.

-

The return statement contains three expressions:

-
    -
  • expression 1: isalnum(c). It returns true is c is a letter or digits, otherwise returns false.
  • -
  • expression 2: find(url_ch.begin(), url_ch.end(), c) != url_ch.end(). find algorithm takes three arguments, of which the first two are interators that denote the input sequence while the third one is a value to search for in the range. It returns an iterator to the first element in the range that compares equal to the value. If no such element is found, it returns the second arguments. In this case, if any character that equals to c, the expression is evaluated to true. If no character that matches with c, the expression is evaluated to false.
  • -
  • expression 3: !(expression 1 || expression 2). If and only if both expressions are evaluated to false, the expression 3 is evaluated to true.
  • -
-

In brief, if the scanned character is not a letter, not a digit, and not any punctuation character that can appear in a URL, it is regarded as not a URL character and it is the element that one past the last element of the URL.

-

Finally, we return to the first step, writing the function url_beg to find the initial position of the URL. One concern we have is that :// may not guaranntee a URL, for example, in the case that these characters appear at the end of a string. Therefore, we also need to make sure that there at least one or more letters before :// and at lease one character follows it.

-
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
string::const_iterator url_beg(string::const_iterator b, string::const_iterator e)
{
static const string sep = "://";
typedef string::const_iterator iter;

// i marks where the separator was found
iter i = b;
while((i = search(i, e, sep.begin(), sep.end())) != e)
{
// make sure the seperator isn't at the begining of the proticol-name
if(i != b && i + sep.size() != e)
{
// beg marks the begining of the protocol-name
iter beg = i;
while(beg != b && isalpha(beg[-1]))
--beg;

// is there at least one appropriate character before and after the sep
if (beg != i && !not_url_char(i[sep.size()]))
return beg;
}
// the seperator we found wasn't part of a URL advance i past this separator
i += sep.size();
}
return e;
}
-

Let’s analyse from the while loop. There appears a new algorithm search in the condition:

-
1
(i == search(i, e, sep.begin(), sep.end())) != e
-

The search algorithm takes four iterators, of which the first two indicate the sequence while the last two denotes the initial and final positions of the sequence to be searched for. It returns an iterator the refers to the first element of [sep.begin(), sep.end()) if such sequence can be found in [i, e), otherwise it returns e. Now we know the condition is evaluated to true if :// can be found in the range of [i, e).

-

Assuming that :// is found in the first iteration, i is assigned the iterator that denotes :. To make sure :// reveals a valid URL, we need to make sure it is neither the start of s nor the ending of s. This is managed by the first if statement inside of the while loop. The next is to find the begining position of the URL using following statements.

-
1
2
3
iter beg = i;
while(beg != b && isalpha(beg[-1]))
--beg;
-

beg[-1] accesses the element that before the one denoted by beg, it has the same effect as

-
1
*(beg - 1)
-

If the element denoted by beg is a letter within the range of [b, i), the loop continues with taking one position back each iteration. After the loop finishes, we got the initial position of the URL. However, the while body may fail to be executed even once, if the condition is evaluated to false the first time. Therefore, we need to verify another condition to make sure there exists at least one appropriate character before and after the sep.

-
1
2
if (beg != i && !not_url_char(i[sep.size]))
return beg;
-

The first expression beg != i ensures that there is at least one letter before :. In other words, the while loop above is executed at least once.

-

The sencond expression !not_url_char(i[sep.size]) ensures that there is at least one appropriate character follows ://. i[sep.size()] has the same effect as

-
1
*(i + sep.size())
-

which denotes the first character after sep (i.e. “://“).

-

At this phase, if the condition is evaluated to true, then the function returns the “qualified” iterator to the function caller, otherwise, the function keeps looking untill the last character.

-

A complete program

The files below show the complete program. A simple test can also be found after the program.

-

mainfunction.cpp

-
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
#include <iostream>
#include <vector>
#include <string>
#include "find_urls.h"

using std::cout; using std::endl;
using std::string; using std::vector;

int main()
{
vector<string> urls;
string doc{"A typical URL could have the form https://en.wikipedia.org/wiki/URL, "
"which indicates a protocol (http), a hostname (www.example.com), "
"and a file name (index.html). http://www.cplusplus.com/reference/algorithm/search/?kw=search"};
urls = find_urls(doc);

for (vector<string>::const_iterator iter = urls.begin(); iter != urls.end(); ++iter)
cout << *iter << endl;
}
- -

find_urls.cpp

-
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
// function that finds and returns an URL
#include "find_urls.h"
#include <vector>
#include <string>
#include "delimit.h"

using std::vector;
using std::string;

vector<string> find_urls(const string &s)
{
vector<string> ret;
typedef string::const_iterator iter;
iter b = s.begin(), e = s.end();

// look through the entire input
while (b != e)
{
// look for one or more letters followed by ://
b = url_beg(b, e);

// if we found it
if(b != e)
{
// get the rest of the URL
iter after = url_end(b, e);

// remember the URL
ret.push_back(string(b, after));

// advance b and check for more URLs on this line
b = after;
}
}
return ret;
}
-

find_urls.h

-
1
2
3
4
5
6
7
8
9
#ifndef GUARD_FINDINGURLS_H
#define GUARD_FINDINGURLS_H

#include <vector>
#include <string>

std::vector<std::string> find_urls(const std::string &);

#endif /* GUARD_FINDINGURLS_H */
- -

delimit.cpp

-
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
// contains three functions: not_url_char, url_beg, url_end
#include <string>
#include <algorithm>
#include "delimit.h"

using std::string; using std::find;
using std::find_if; using std::search;

// predicate on a char, check whether it is a char that can appear in a URL
bool not_url_char(char c)
{
// characters, in addition to alphanumerics, that can appear in a URL
static const string url_ch = "~;/?:@=&$-_.+!*'(),";

// see whether c can appear in a URL and return the negative
return !(isalnum(c) || find(url_ch.begin(), url_ch.end(), c) != url_ch.end());
}

// function that returns an iterator that refers to the first element of a URL
string::const_iterator url_beg(string::const_iterator b, string::const_iterator e)
{
static const string sep = "://";
typedef string::const_iterator iter;

// i marks where the separator was found
iter i = b;
while((i = search(i, e, sep.begin(), sep.end())) != e)
{
// make sure the seperator isn't at the begining of the proticol-name
if(i != b && i + sep.size() != e)
{
// beg marks the begining of the protocol-name
iter beg = i;
while(beg != b && isalpha(beg[-1]))
--beg;

// is there at least one appropriate character before and after the sep
if (beg != i && !not_url_char(i[sep.size()]))
return beg;
}
// the seperator we found wasn't part of a URL advance i past this separator
i += sep.size();
}
return e;
}

// function that returns an iterator that denotes the postion one past the last element
string::const_iterator url_end(string::const_iterator b, string::const_iterator e)
{
return find_if(b, e, not_url_char);
}
-

delimit.h

-
1
2
3
4
5
6
7
8
9
10
#ifndef GUARD_DELIMIT_H
#define GUARD_DELIMIT_H

#include <string>

bool not_url_char(char);
std::string::const_iterator url_beg(std::string::const_iterator, std::string::const_iterator);
std::string::const_iterator url_end(std::string::const_iterator, std::string::const_iterator);

#endif /* GUARD_DELIMIT_H */
- -

Test results

-
1
2
https://en.wikipedia.org/wiki/URL,
http://www.cplusplus.com/reference/algorithm/search/?kw=search
-

The program just has function to roughly grab the URLs, but works as expected.

-
-

To be continued.

+ + + + Exercise 5-5Write a function named center(const vector&) that returns a picture in which all the lines of the original picture are padded out to t + ... + +
+ + Read more » + +
+ + +
+ + + + + -
+ +
+ + + + + + + +
-
+ +
+

+ + +
+ + + + + + + - +
+ -
- +
+ + +
-

- - -

+ + + +

+ +

+
+ @@ -1368,133 +1636,178 @@

-

Exercise 5-9

Write a program to write the lowercase words in the input followed by the uppercase words.

-

Solution & Results

The solution strategy is pretty straightforward: check each entered word to see whether it contains one or more uppercase letters, and store words into two containers according to the check results. I present the code and test performance in below.

-

A complete 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
#include <iostream>
#include <string>
#include <vector>

using std::cin; using std::cout;
using std::endl; using std::string;
using std::vector;

// function declaration
bool pureLowercaseWords(const string &word); // a predicate on words
void print(const vector<string> &Words); // print elements in a vector one by one

int main()
{
vector<string> lowercase_Words; // for holding words that contains only lower case letters
vector<string> uppercase_Words; // for holding words that contains at lease one upper case letters
string word;
while(cin >> word)
{
if(!word.empty())
{
if(pureLowercaseWords(word))
lowercase_Words.push_back(word);
else
uppercase_Words.push_back(word);
}
}
cout << "Words that contain only lowercase letters: " << endl;
print(lowercase_Words);
cout << "Words that contain at lease one uppercase letters: " << endl;
print(uppercase_Words);
return 0;
}

bool pureLowercaseWords(const string &word)
{
for (string::const_iterator iter = word.begin(); iter != word.end(); ++iter)
{
if (isupper(*iter))
return false;
}
return true;
}

void print(const vector<string> &Words);
{
for (vector<string>::const_iterator iter = Words.begin(); iter != Words.end(); ++iter)
cout << *iter << endl;
}
- -

Test
with inputs: University of Oxford is one of best universities all over the world according to QS Ranking

-

The program works as expected:

-
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
Words that contain only lowercase letters: 
of
is
one
of
best
universities
all
over
the
world
according
to
Words that contain at lease one uppercase letters:
University
Oxford
QS
Ranking
- -
-

Exercise 5-10

Palindromes are words that are spelled the same right to left as left to right. Write aprogram to find all the palindromes in a dictionary. Next, find the longest palindrome.

-

Solution & Results

This project has two goals:

-
    -
  1. telling whether a word is a palindrome.
  2. -
  3. find the longest palindrome.
  4. -
-

If a word is not a palindromes, we dicard it directly. On the contrary, if a word is a palindromes, we need to store it for generating a final report. To find the longest palindrome, we can compare the size of two words and keep the longer word, finally obtaining the longest palindrome(or the first observed longest one).

-

To determine whether a word is a palindrome, one key point is that whether the palindromes are case sensitive. I treat the palindromes as case insensitive in this program and hence the predicate on a word is based on the lowercase(or uppercase) version of a word. Another point is that whether a single character is a palindrome. I regard it as a palindrome and treat a single character as a one-letter word.

-

Apparently, the key is to find an algorithm that can identify a palindrome. According to the definition, palindromes are words that are spelled the same right to left as left to right. A possible solution is to divide the characters in a word into two groups, which are symmetrically located at two side of a mid point, and compare them one pair by one pair. If all pairs are same, the word is a palindromes.

-

Intuitively, if the number of characters in a word is odd, then, there exists one unique mid point, which splits two groups of characters. The graph below shows this case and illustrates the relationship of one pair of characters regarding to their corresponding iterators.

-

A word contains odd number characters

-

Clearly, this process can be translated to a while loop and the stopping point is the mid point (i.e. the position that iterator word.begin() + size/2 refers to).

-

Similarly, if the number of characters in a word is even, characters can be exactly splitted into two groups, i.e. word.size()/2 pairs.

-

A word contains even number characters

-

It can be seen from above graph, the stopping point is also the position that word.begin() + size/2 refers to. Before this point, the last pair of characters(i.e. two mid elements) are compared.

-

Now the predicate can be write as:

-
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
bool isPalindromes(const string &word)
{
typedef string::const_iterator iter;
iter startPosition = word.begin();
iter endPosition = word.end() - 1;
iter midPosition = word.begin() + word.size()/2;
while (startPosition != midPosition)
{
if(tolower(*startPosition) != tolower(*endPosition))
return false;
++startPosition;
--endPosition;
}
return true;
}
-

Firstly, I choose to compare two endpoints and then move both points 1 position forward to the middle in the following iteration. Finally, the while loop stops when all pairs of characters have been compared.

-

Once finishes this function, we can write the main function directly. The code below shows the complete program. Note that I seperate the condition word.size() == 1 from isPalindromes(word) simply for the purpose of computational efficiency.

-
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 <algorithm>
#include <iostream>
#include <string>
#include <vector>

using std::cin; using std::cout;
using std::endl; using std::string;
using std::vector; using std::max;

// function declarations
bool isPalindromes(const string &word);
void print(const vector<string> &palindromes);

int main()
{
vector<string> palindromes; // hold all identified palindromes
string longest; // hold the first observed longest palindrome

// read words
string word;
while(cin >> word)
{
if(word.size() == 1 || isPalindromes(word))
{
if(longest.size() < word.size())
longest = word;
palindromes.push_back(word);
}
}

// print all palindromes
cout << "Following words are palindromes:" << endl;
print(palindromes);

// print the longest
cout << "The longest palindrome(first observed) is:\n" << longest << endl;
return 0;
}

void print(const vector<string> &palindromes)
{
for(vector<string>::const_iterator iter = palindromes.begin();
iter != palindromes.end(); ++iter)
cout << *iter << endl;
}

// please add the predicate here
-

Now let’s test how the program performs. I entered following words successively:

-
1
Rotator Anna Oxford Civic Sampling Kayak Level Madam Mom Noon shape racecar radar Redder Refer Repaper Please Rotor again Sagas impossible Solos distribution Stats Tenet Wow
- -

The results are displayed below:

-
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
Following words are palindromes:
Rotator
Anna
Civic
Kayak
Level
Madam
Mom
Noon
racecar
radar
Redder
Refer
Repaper
Rotor
Sagas
Solos
Stats
Tenet
Wow
The longest palindrome(first observed) is:
Rotator
-

Yeah, the program works perfectly.

-

Exercise 5-11

In text processing it is sometimes useful to know whether a word has any ascenders or descenders. Ascenders are the parts of lowercase letters that extend above the text line;in the English alphabet, the letters b, d, f, h, k, l, and t have ascenders. Similarly, the descenders are the parts of lowercase letters that descend below the line; In English,theletters g, j, p, q, and y have descenders. Write a program to determine whether a word has any ascenders or descenders. Extend that program to find the longest word in the dictionary that has neither ascenders nor descenders.

-

Solution & Results

The idea to solve this exercise is similar to that described in above exercise. The first step is to check whether a word contains any ascenders or descenders, by means of comparing each character in the word to ascenders or descenders. The second step is to store the word into corresponding vector according to the check results in step 1. The longest word that has neither ascenders nor descenders can be found using the same method as above.

-

I present the complete program below followed by a performance test. Noting that I defined two global variables which can be accessed anywhere inside of the file. But they cannot be modified as I add const in defining them. The purpose is to avoid that these two objects will be created repeatedly if they are put into the precidate. This problem can be easily solved with specifier static, which will be introduced in chapter 6.

-
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
65
66
67
68
69
70
71
72
// Accelerated C++ Solutions Exercises 5-11
#include <algorithm>
#include <iostream>
#include <string>
#include <vector>

using std::cin; using std::cout;
using std::endl; using std::string;
using std::vector; using std::max;

// global variables
const string ascenders = "bdfhlt";
const string descenders = "gjpqy";

// function declarations
bool noAscDes(const string &word);
void print(const vector<string> &words);

int main()
{
vector<string> asc_des; // hold words that contain any ascenders or descenders
vector<string> no_asc_des; // hold words that has neither ascenders nor descenders
string longest_no; // find the longest word that has neither ascenders nor descenders

// read words
string word;
while(cin >> word)
{
if(noAscDes(word))
{
if (longest_no.size() < word.size())
longest_no = word;
no_asc_des.push_back(word);
}
else
asc_des.push_back(word);
}

cout << "Following words contain either ascenders or descenders:" << endl;
print(asc_des);
cout << "Following words contain neither ascenders nor descenders:" << endl;
print(no_asc_des);
cout << "The longest word that has neither ascenders nor descenders is:\n" << longest_no << endl;

return 0;
}

bool noAscDes(const string &word)
{
typedef string::const_iterator iter;
for (iter i = word.begin(); i != word.end(); ++i)
{
for(iter j = ascenders.begin(); j != ascenders.end(); ++j)
{
if (*i == *j)
return false;
}
for (iter k = descenders.begin(); k != descenders.end(); ++k)
{
if (*i == *k)
return false;
}
}
return true;
}

void print(const vector<string> &words)
{
for(vector<string>::const_iterator iter = words.begin();
iter != words.end(); ++iter)
cout << *iter << endl;
}
- -

Test results

-
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
Inputs:
throughout the course of history there have been many famous speeches that changed the world from on the mount to the inaugural speeches of modern leaders their words have become an inspiration to millions of people

Outputs:
Following words contain either ascenders or descenders:
throughout
the
of
history
there
have
been
many
famous
speeches
that
changed
the
world
from
the
mount
to
the
inaugural
speeches
of
modern
leaders
their
words
have
become
inspiration
to
millions
of
people
Following words contain neither ascenders nor descenders:
course
on
an
The longest word that has neither ascenders nor descenders is:
course
-

As above results show, It works fine.

-
-

References

Koenig, A. and Moo, B.E., 2000. Accelerated C++.

-

Lippman, S.B., Lajoie, J. and Moo, B.E., 2013. C++ Primer, 5th edt.

+ + + + Exercise 5-2, 5-3, 5-45-2: Write the complete new version of the student-grading program, which extracts records for failing students, using vectors. + ... + +
+ + Read more » + +
+ + +
+ + + + + -
+ +
+ + + + + + + +
-
+ +
+

+ + +
+ + + + + + + - +
-
- + +
+ + +
-

- - -

+ + + +

+ +

+
+ @@ -1502,415 +1815,179 @@

-

Exercise 5-5

Write a function named center(const vector&) that returns a picture in which all the lines of the original picture are padded out to their full width, and the padding is as evenly divided as possible between the left and right sides of the picture. What are the properties of pictures for which such a function is useful? How can you tell whether a given picture has those properties?

-

Solution & Results

The full width is the size of the longest string and can be obtained via a for loop.

-
1
2
3
4
5
for (vector<string>::const_iterator iter = p.begin();
iter != p.end(); ++iter)
{
maxlen = max(maxlen, (*iter).size());
}
-

The key is to compute the number of spaces (denoted by paddingLeft) needed for the left side padding. If we want that the padding is as evenly divided as possible, paddingLeft can be set as half of maxlen - (*iter).size(), which is the total number of spaces needed to pad out to the full width of a line.

-

If (maxlen - (*iter).size()) is even, the numbers of spaces on both sides of the original string are the same (i.e. the case of that padding is evenly divided).

-

If (maxlen - (*iter).size()) is odd, paddingLeft is in fact has the value of ((maxlen - (*iter).size()) - 1)/2, and hence is one space less that the right side padding.

-

In summary, the program logic is

-
    -
  1. find maxlen.
  2. -
  3. compute paddingLeft.
  4. -
  5. construct a new line based on the string in the original picture and paddingLeft spaces.
  6. -
  7. store each new line into a vector and return it once finishes.
  8. -
-

The complete program and tests can be found below.

-

mainfunction.cpp

-
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 <string>
#include <vector>
#include <algorithm>

using std::cout; using std::string;
using std::endl; using std::vector;
using std::max;

// function declaration
vector<string> center(const vector<string> &p);

int main()
{
// create an original picture
vector<string> p;
p.push_back("this is an");
p.push_back("example");
p.push_back("to");
p.push_back("illustrate");
p.push_back("framing");

// generate the centered picture
vector<string> np = center(p);
for(vector<string>::const_iterator iter = np.begin();
iter != np.end(); ++iter)
cout << *iter << endl;

return 0;
}

// define the function that returns a centered picture
vector<string> center(const vector<string> &p)
{
vector<string> centeredPicture;
string::size_type maxlen = 0;

// get the length of the longest string
for (vector<string>::const_iterator iter = p.begin();
iter != p.end(); ++iter)
{
maxlen = max(maxlen, (*iter).size());
}

// pad out the left side
for (vector<string>::const_iterator iter = p.begin();
iter != p.end(); ++iter)
{
string::size_type paddingLeft = (maxlen - (*iter).size())/2;
string s = string(paddingLeft, ' ') + (*iter);
centeredPicture.push_back(s);
}

return centeredPicture;
}
- -

Test Results

-
1
2
3
4
5
this is an
example
to
illustrate
framing
- -
-

Exercise 5-6

Rewrite the extract_fails function from §5.1.1/77 so that instead of erasing each failing student from the input vector v, it copies the records for the passing students to the beginning of v, and then uses the resize function to remove the extra elements from the end of v. How does the performance of this version compare with the one in §5.1.1/77?

-

Solution & Results

The original function is

-

Method 1

-
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
vector<Student_info> extract_fails_method1(vector<Student_info>& students)
{
vector<Student_info> fail;
vector<Student_info>::size_type i = 0;
// invariant:elements [0, i) of students represent passing grades
while (i != students.size())
{
if (fgrade(students[i]))
{
fail.push_back(students[i]);
students.erase(students.begin() + i);
}
else
++i;
}
return fail;
}
- -

The revised version is

-

Method 2

-
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
vector<Student_info> extract_fails_method2(vector<Student_info>& students)
{

vector<Student_info> fail;
vector<Student_info>::size_type i = 0, j = students.size();
// invariant:elements [0, i) of students represent passing grades
while (i != students.size())
{
if (fgrade(students[i]))
{
fail.push_back(students[i]};
}
else
{
// the size increases by 1
students.insert(students.begin(), students[i]);
// the indice move forward by 1
++i;
}
++i;
}
students.resize(j - fail.size());
return fail;
}
-

The next step is to measure the performance of these two function, using the same methodology as that applied in Exercise 5-4. I add above two methods into the file fails.cpp and fails.h as alternatives. To avoid redundancy, I only present the file that contains the main function in below.

-
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
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
// Accelerated C++ Solutions Exercises 5-2. 5-3, 5-4
#include <algorithm> // to get declaration of max, sort
#include <iostream> // to get declaration of cin, cout, endl
#include <stdexcept> // to get declaration of domain_error
#include <string> // to get declaration of string
#include <chrono>
#include "Student_info.h"
#include "grade.h"
#include "fails.h"
#include "print.h"
#include "info.h"


using std::cin; using std::string;
using std::cout; using std::max;
using std::endl; using std::sort;
using std::domain_error;

int main()
{
info students;
Student_info record;
string::size_type maxlen = 0;

// read and store all the records, and find the length of the longest name
while(read(cin, record))
{
maxlen = max(maxlen, record.name.size());
students.push_back(record);
}

try{
info students_copy = students;

// measure the performance for method1
typedef std::chrono::high_resolution_clock Clock;
Clock::time_point startTime = Clock::now(); // get current time
info fails = extract_fails_method1(students); // extract records for failing students
Clock::time_point endTime = Clock::now(); // get current time

cout << "Method 1 took me "
<< std::chrono::duration_cast<std::chrono::duration<double>>(endTime - startTime).count()
<< " seconds" << endl;

// measure the performance for method2
startTime = Clock::now(); // get current time
fails = extract_fails_method2(students_copy); // extract records for failing students
endTime = Clock::now(); // get current time

cout << "Method 2 took me "
<< std::chrono::duration_cast<std::chrono::duration<double>>(endTime - startTime).count()
<< " seconds" << endl;

// write each line of outputs for passing students
if (!students.empty())
{
//alphabetize the records

sort(students.begin(), students.end(), compare);
// students.sort(compare);
cout << "Students who passed: " << endl;
print(students, maxlen);
}
else
cout << "What a pity! all students failed.";

// write a blank line
cout << endl;

// write each line of outputs for failing students
if(!fails.empty())
{
//alphabetize the records

sort(fails.begin(), fails.end(), compare);
//fails.sort(compare);
cout << "Students who failed: " << endl;
print(fails, maxlen);
}
else
cout << "Congratulations! all students passed.";

}catch(domain_error e){
cout << e.what();
}

return 0;
}
- -

The table below gives the comparison of these two methods, showing that method 2 performs slightly better than method 1. The result is in fact not reliable as it strongly depends on the number of passing students and failing students contained in the raw data. More experiments need to be done for more robust results.
|Number of lines|Method 1-erase| Method 2-insert&resize|
| :— | :— | :— |
|10|0.000 seconds|0.000 seconds|
|1000|0.147114 seconds|0.117083 seconds|
|10000|0.848616 seconds|0.735503 seconds|

-
-

Exercise 5-7

Given the implementation of frame in §5.8.1/93, and the following code fragment

-
1
2
vector<string> v;
frame(v);
-

describe what happens in this call. In particular, trace through how both the width functiona nd the frame function operate. Now, run this code. If the results differ from your expectations, first understand why your expectations and the program differ, and then change one to match the other.

-

Solution & Results

Let’t recall the frame function

-
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
vector<string> frame(const vector<string> &v)
{
// to hold each rows of framed picture to be written
vector<string> ret;
// get the length of the longest string
string::size_type maxlen = width(v);

// first line of outputs
string border(maxlen + 4, '*');
ret.push_back(border);

for (vector<string>::const_iterator iter = v.begin(); iter != v.end(); ++iter)
{
// new rows except two border lines
string temp = "* " + (*iter) + string(maxlen - (*iter).size(), ' ') + " *";
ret.push_back(temp);
}

// bottom line of outputs
ret.push_back(border);
return ret;
}
-

Once calling this function with passing an empty vector v, I expected following procedures happen one after another.

-
    -
  1. the argument is passed by const reference. v refers to the empty vector v.

    -
  2. -
  3. an empty vector ret is created.

    -
  4. -
  5. an object of type string::size_type is created and named as maxlen. maxlen is initialized with a value returned by width function.

    -
  6. -
  7. the computer enters into width function (as shown below). The arguments is also passed by const reference and hence parameter v refers to the empty vector v defined in the main function.

    -

    4.1. object maxlen is created and initialized with value of 0.

    -

    4.2. the computer enters into a for loop. The init-statement is evaluated and iter is initialized as v.begin(). Then then condition is evaluated and the result is false as v.begin() == v.end() due to the fact that no any elements in the container.

    -

    4.3 for loop quits and maxlen that equals to 0 is returned.

    -
    1
    2
    3
    4
    5
    6
    7
    8
    9
    string::size_type width(const vector<string> &v)
    {
    string::size_type maxlen = 0;
    for (vector<string>::const_iterator iter = v.begin(); iter != v.end(); ++iter)
    {
    maxlen = max(maxlen, (*iter).size());
    }
    return maxlen;
    }
  8. -
  9. the computer goes back to the frame function and maxlen is initialized with value 0 (step 3 finishes).

    -
  10. -
  11. object border is created and initialized with 4 asterisks. Then, it is stored into vector ret.

    -
  12. -
  13. the next for loop is similar to the for loop inside the width function. It quits and has no any effects.

    -
  14. -
  15. object border is stored into vector ret again. By then, ret has two elements, both of which are strings filled by 4 asterisks.

    -
  16. -
  17. ret is returned. The computer goes back to the function caller.

    -
  18. -
-

According to my expection, the picture returned by frame only consist of two lines of strings each formed by 4 asterisks. Let’s verify this the program displayed below.

-

A complete 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
#include <string>
#include <vector>
#include <iostream>
#include <algorithm>

using std::string; using std::cout;
using std::endl; using std::max;
using std::vector;

// function declarations
string::size_type width(const vector<string> &v);
vector<string> frame(const vector<string> &v);

int main()
{
vector<string> v;
vector<string> p = frame(v);
for (vector<string>::iterator iter = p.begin(); iter != p.end(); ++iter)
cout << *iter <<endl;
return 0;
}

// please fill this part with the width function described above
// please fill this part with the frame function described above
-

As expected, the program produces following outputs

-
1
2
****
****
- -
-

Exercise 5-8

In the hcat function from §5.8.3/95, what would happen if we defined s outside the scope of the while? Rewrite and execute the program to confirm your hypothesis.

-

Solution & Results

Code analysis

Recalling the hcat function:

-
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
vector<string> hcat(const vector<string> &left, const vector<string> &right)
{
// to hold the each line of outputs
vector<string> ret;

// add one space column between two pictures
string::size_type width1 = width(left) + 1;

// iterators to look at elements from left and right respectively
vector<string>::const_iterator iter_i = left.begin(), iter_j = right.begin();

// continue until we've see all rows from two pictures
while (iter_i != left.end() || iter_j != right.end())
{
// construct new string to hold characters from two pictures
string s;

// copy a row from left side picture
if (iter_i != left.end())
{
s = (*iter_i);
++iter_i;
}
// pad to full width
s += string(width1 - s.size(), ' ');

// copy a row from right picture
if (iter_j != left.end())
{
s += (*iter_j);
++iter_j;
}

// store s into vector to form a new picture
ret.push_back(s);
}
return ret;
}
-

Let’s analyse what happens if we define s outside the scope of the while loop:

-
    -
  1. s is an empty string before entering into the while loop.

    -
  2. -
  3. the first iteration:

    -

    2.1. s is assigned with the copy of the first element from the left picture, and then is padded out to the full width (left side).

    -

    2.2. s is concatenated with the first element from the right picture.

    -

    2.3. s is stored into vector ret to formatted the first line of the new picture.

    -
  4. -
  5. the nth interation:

    -

    case 1: the numbers of rows of both pictures are the same. The process of the rest iterations are similar to the first iteration until the while condition is evaluated to false. As a result, all rows of both pictures are copied into the new picture. The function has the same effect as the original one, where s is defined inside the while loop.

    -

    case 2: the left side picture has less rows than the right side picture. When iter_i == left.end() but iter_j != right.end(), the first if statements are ignored but the next statement (shown below) will result in compilation errors.

    -
    1
    s += string(width1 - s.size(), ' ');
    -

    remembering that s.size() is the summation of widths of both sides, i.e. width1 plus the width of the right side picture. Therefore, width1 - s.size() is negative.

    -

    case 3: if left side picture has more rows than the right side picture. The process of the rest iterations are similar to case 1. The function has same effect as the original function, where s is defined inside of the while loop.

    -
  6. -
-

Rewrite the original program

Now, let’s verify above expectations using following program, covering three cases and two functions(i.e. the original one and the modified one). The files of the program includes mainfunction.cpp, pics.cpp, pics.h, width.cpp. width.h, print.cpp and print.h. More details about the original program can be found in putting strings together.

-

mainfunction.cpp

-
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
#include <iostream>
#include <string>
#include <vector>
#include "pics.h"
#include "print.h"

using std::cout; using std::endl;
using std::vector; using std::string;

int main()
{
vector<string> pic1;
pic1.push_back("aaaaaa");
pic1.push_back("bbbbbbbbbbbbb");
pic1.push_back("ccc");

vector<string> pic2;
pic2.push_back("ddddddddddddd");
pic2.push_back("eeeeeee");
pic2.push_back("fffffffffff");
pic2.push_back("ggggg");

vector<string> left, right;
// Test case 1
left = right = pic1;

// Test case 2
// left = pic1; right = pic2;

// Test case 3
// left = pic2; right = pic1;

vector<string> hcatPicture1 = hcat_function1(left, right);
vector<string> hcatPicture2 = hcat_function2(left, right);

cout << "The original function produces a picture shown as follows: " << endl;
print(hcatPicture1);
cout << endl;
cout << "The modified function produces a picture shown as follows: " << endl;
print(hcatPicture2);

return 0;
}
- -

pics.cpp

-
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
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
// functions that generate different pictures
#include <string> // to get declaration of string
#include <vector> // to get declaration of vector
#include "width.h"
#include "pics.h"

using std::string; using std::vector;

// to horizontally concatenate two pictures: original function
vector<string> hcat_function1(const vector<string> &left, const vector<string> &right)
{
// to hold the each line of outputs
vector<string> ret;

// add one space column between two pictures
string::size_type width1 = width(left) + 1;

// iterators to look at elements from left and right respectively
vector<string>::const_iterator iter_i = left.begin(), iter_j = right.begin();

// continue until we've see all rows from two pictures
while (iter_i != left.end() || iter_j != right.end())
{
// construct new string to hold characters from two pictures
string s;

// copy a row from left side picture
if (iter_i != left.end())
{
s = (*iter_i);
++iter_i;
}
// pad to full width
s += string(width1 - s.size(), ' ');

// copy a row from right picture
if (iter_j != left.end())
{
s += (*iter_j);
++iter_j;
}

// store s into vector to form a new picture
ret.push_back(s);
}
return ret;
}

// to horizontally concatenate two pictures: modified function
vector<string> hcat_function2(const vector<string> &left, const vector<string> &right)
{
// to hold the each line of outputs
vector<string> ret;

// add one space column between two pictures
string::size_type width1 = width(left) + 1;

// iterators to look at elements from left and right respectively
vector<string>::const_iterator iter_i = left.begin(), iter_j = right.begin();

// construct new string to hold characters from two pictures
string s;
// continue until we've see all rows from two pictures
while (iter_i != left.end() || iter_j != right.end())
{
// copy a row from left side picture
if (iter_i != left.end())
{
s = (*iter_i);
++iter_i;
}
// pad to full width
s += string(width1 - s.size(), ' ');

// copy a row from right picture
if (iter_j != right.end())
{
s += (*iter_j);
++iter_j;
}

// store s into vector to form a new picture
ret.push_back(s);
}
return ret;
}
- -

pics.h

-
1
2
3
4
5
6
7
8
9
10
11
12
13
#ifndef GUARD_PICS_H
#define GUARD_PICS_H

#include <string>
#include <vector>

std::vector<std::string> hcat_function1(const std::vector<std::string> &left,
const std::vector<std::string> &right);

std::vector<std::string> hcat_function2(const std::vector<std::string> &left,
const std::vector<std::string> &right);

#endif /* GUARD_PICS_H */
- -

width.cpp

-
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
// function returns the size of the longest string in a vector<string>
#include <string> // to get declaration of string
#include <vector> // to get declaration of vector
#include <algorithm> // to get declaration of max
#include "width.h" // to get declaration of the function itself

using std::string; using std::vector; using std::max;


string::size_type width(const vector<string> &v)
{
string::size_type maxlen = 0;
for (vector<string>::const_iterator iter = v.begin(); iter != v.end(); ++iter)
{
maxlen = max(maxlen, (*iter).size());
}
return maxlen;
}
- -

width.h

-
1
2
3
4
5
6
7
8
9
#ifndef GUARD_WIDTH_H
#define GUARD_WIDTH_H

#include <string>
#include <vector>

std::string::size_type width(const std::vector<std::string> &v);

#endif /* GUARD_WIDTH_H */
- -

print.cpp

-
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
// function to write each elements from a vector<string>
#include "print.h"
#include <iostream> // to get declaration of cout and endl
#include <string> // to get declaration of string
#include <vector> // to get declaration of vector

using std::cout; using std::string;
using std::endl; using std::vector;

void print(const vector<string> &pics)
{
// loop thru the vector and write elements one by one
for (vector<string>::const_iterator iter = pics.begin(); iter != pics.end(); ++iter)
{
cout << (*iter) << endl;
}
}
- -

print.h

-
1
2
3
4
5
6
7
8
9
#ifndef GUARD_OUTPUT_H
#define GUARD_OUTPUT_H

#include <string>
#include <vector>

void print(const std::vector<std::string> &pics);

#endif /* GUARD_OUTPUT_H */
-

Test results

Case 1

-
1
2
3
4
5
6
7
8
9
The original function produces a picture shown as follows: 
aaaaaa aaaaaa
bbbbbbbbbbbbb bbbbbbbbbbbbb
ccc ccc

The modified function produces a picture shown as follows:
aaaaaa aaaaaa
bbbbbbbbbbbbb bbbbbbbbbbbbb
ccc ccc
- -

Case 2

-
1
2
3
4
This application has requested the Runtime to terminate it in an unusual way. 
Please contact the application's support team for more information.
terminate called after throwing an instance of 'std::length_error'
what(): basic_string::_M_create
- -

Case 3

-
1
2
3
4
5
6
7
8
9
10
11
The original function produces a picture shown as follows: 
ddddddddddddd aaaaaa
eeeeeee bbbbbbbbbbbbb
fffffffffff ccc
ggggg

The modified function produces a picture shown as follows:
ddddddddddddd aaaaaa
eeeeeee bbbbbbbbbbbbb
fffffffffff ccc
ggggg
- -

The results of all three cases are exactly as I expected and analysed in last section.

-
-

To be continued.

+ + + + Exercise 5-0Compile, execute, and test the programs in this chapter +Solution & ResultsPlease find solutions and detailed analysis in Chapter 5 Seq + ... + +
+ + Read more » + +
+ + +
+ + + + + -
+ +
+ + + + + + + +
-
+ +
+

+ + +
+ + + + + + + - +
+ -
- +
+ + +
-

- - -

+ + + +

+ +

+ -
- - - - -
- - -

Exercise 5-2, 5-3, 5-4

5-2: Write the complete new version of the student-grading program, which extracts records for failing students, using vectors. Write another that uses lists. Measure the performance difference on input files of ten lines, 1,000 lines, and 10,000 lines.

-

5-3: By using a typedef, we can write one version of the program that implements either a vector-based solution or a list-based one. Write and test this version of the program.

-

5-4: Look again at the driver functions you wrote in the previous exercise. Note that it ispossible to write a driver that differs only in the declaration of the type for the data structure that holds the input file. If your vector and list test drivers differ in any other way, rewrite them so that they differ only in this declaration.

-

Solution

I’ll give solution to 5-4 directly as these three exercises are closely connected. The strategy can be divided into three steps:

-
    -
  1. write drivers for using list or vector to hold input files.
  2. -
  3. add the function to extract records for failing students, and add the drivers for files where the declaration of specified container is needed.
  4. -
  5. change indices to iterators if necessary because list doesn’t support access elements via indices.
  6. -
  7. measure the performance difference of two containers on input files of 10 lines, 1000 lines and 10000 lines.
  8. -
-

Step 1

To switch the use of list and vector, I follows the suggestion of 5-3;

-
1
2
// typedef list<Student_info> info;
typedef vector<Student_info> info;
-

The alias info can represent either the type list or vector and hence allows us switch from one version to another simply via modifying this declaration.

-

However, the type declared above is not only used in the file that contains the main function, but may also needed in other files. Therefore, I separate the declaration from the main function and create a header for this reason.
info.h

-
1
2
3
4
5
6
7
8
9
10
11
#ifndef GUARD_INFO_H
#define GUARD_INFO_H

#include <list>
#include <vector>
#include "Student_info.h"

//typedef std::vector<Student_info> info;
typedef std::list<Student_info> info;

#endif /* GUARD_INFO_H */
-

All files that want to use the name info include the header where declares the name info.

-

Step 2

This step adds two functions: one is to extract the records for failing students; another one is a predicate on failing grades.
It can be observed that the header “info.h” is included for the purpose of defining the container fail to holding information of the failing students.
fails.cpp

-
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
#include "Student_info.h"
#include "grade.h"
#include "fails.h"
#include "info.h"

// the predicate for students who failed
bool fgrade(const Student_info &s)
{
return grade(s) < 60;
}

// function to extract the failed student records
info extract_fails(info &students)
{
info fail;
info::iterator iter = students.begin();

while(iter != students.end())
{
if(fgrade(*iter))
{
fail.push_back(*iter);
iter = students.erase(iter);
}
else
{
++iter;
}
}
return fail;
}
- -

fails.h

-
1
2
3
4
5
6
7
8
9
10
#ifndef GUARD_FAILS_H
#define GUARD_FAILS_H

#include "Student_info.h"
#include "info.h"

bool fgrade(const Student_info &s);
info extract_fails(info &students);

#endif /* GUARD_FAILS_H*/
- -

Step 3

The last change needs to be done is using iterators instead of indices. To avoid messy, I rewrite the output section as a function named print.

-

print.cpp

-
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>
#include <iomanip>
#include <string>
#include "grade.h"
#include "print.h"
#include "info.h"

using std::cout; using std::setprecision;
using std::endl; using std::streamsize;
using std::string;

void print(const info &records, const string::size_type &maxlen)
{
for (info::const_iterator iter = records.begin(); iter != records.end(); ++iter)
{
// write the name, blanks
cout << (*iter).name << string(maxlen + 1 - (*iter).name.size(), ' ');

// compute and write the final grade
double final_grade = grade(*iter);
streamsize prec = cout.precision();
cout << setprecision(3) << final_grade << setprecision(prec) << endl;
}
}
-

print.h

-
1
2
3
4
5
6
7
8
9
#ifndef GUARD_PRINT_H
#define GUARD_PRINT_H

#include <string>
#include "info.h"

void print(const info &records, const std::string::size_type &maxlen);

#endif /* GUARD_PRINT_H */
- -

Note that I didn’t add try block here as the exceptions may be thrown early in the process of extracting the failing records.

-

Step 4

To measure the performance of the vector based program and list based program, I uses members of the time library . Specifically:

-
1
2
3
4
5
6
7
8
typedef std::chrono::high_resolution_clock Clock;
Clock::time_point startTime = Clock::now(); // get current time
info fails = extract_fails(students); // extract records for failing students
Clock::time_point endTime = Clock::now(); // get current time

cout << "It took me "
<< std::chrono::duration_cast<std::chrono::seconds>(endTime - startTime).count()
<< " seconds" << endl;
-

The usage of std::chrono::high_resolution_clock refers to
high_resolution_clock.

-

A complete program

By now, I have introduced the main changes relative to the original program. Therefore, the complete program includes following files

- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
header filessource files
info.h
fails.hfails.cpp
print.hprint.cpp
Student_info.hStudent_info.cpp
grade.hgrade.cpp
mainfunction.cpp
-

I present the rest files in below. Noting that there are two major differences between the vector-based version and the list-based version. First is the declaration metioned above. The second is that the usage of sort function for two types of containers are different, for vectors, the statement is

-
1
sort(students.begin(), students.end(), compare);
-

while for lists,

-
1
students.sort(compare);
-

Strictly speaking, my solution doesn’t meet the requirements of 5-4. But I haven’t find a better one.

-

mainfunction.cpp

-
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
65
66
67
68
69
70
71
72
73
74
75
76
#include <algorithm>		// to get declaration of max, sort
#include <iostream> // to get declaration of cin, cout, endl
#include <stdexcept> // to get declaration of domain_error
#include <string> // to get declaration of string
#include <chrono>
#include "Student_info.h"
#include "grade.h"
#include "fails.h"
#include "print.h"
#include "info.h"


using std::cin; using std::string;
using std::cout; using std::max;
using std::endl; using std::sort;
using std::domain_error;

int main()
{
info students;
Student_info record;
string::size_type maxlen = 0;

// read and store all the records, and find the length of the longest name
while(read(cin, record))
{
maxlen = max(maxlen, record.name.size());
students.push_back(record);
}

try{
// measure the performance
typedef std::chrono::high_resolution_clock Clock;
Clock::time_point startTime = Clock::now(); // get current time
info fails = extract_fails(students); // extract records for failing students
Clock::time_point endTime = Clock::now(); // get current time

cout << "It took me "
<< std::chrono::duration_cast<std::chrono::seconds>(endTime - startTime).count()
<< " seconds" << endl;

// write each line of outputs for passing students
if (!students.empty())
{
//alphabetize the records

//sort(students.begin(), students.end(), compare);
students.sort(compare);
cout << "Students who passed: " << endl;
print(students, maxlen);
}
else
cout << "What a pity! all students failed.";

// write a blank line
cout << endl;

// // write each line of outputs for failing students
if(!fails.empty())
{
//alphabetize the records

//sort(fails.begin(), fails.end(), compare);
fails.sort(compare);
cout << "Students who failed: " << endl;
print(fails, maxlen);
}
else
cout << "Congratulations! all students passed.";

}catch(domain_error e){
cout << e.what();
}

return 0;
}
- -

Student_info.cpp

-
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 <vector>
#include <iostream>
#include "Student_info.h"

using std::vector; using std::istream;

bool compare(const Student_info &x, const Student_info &y)
{
return x.name < y.name;
}

istream & read(istream &is, Student_info &s)
{
// reads and store the student's name, midterm and final exam grades
is >> s.name >> s.midterm >> s.final;

// reads and store all homework grades
read_hw(is, s.homework);
return is;
}

istream & read_hw(istream &in, vector<double> &hw)
{
if (in){
//get rid of previous contents
hw.clear();

// read homework grades
double x;
while(in >> x)
hw.push_back(x);

// clear the stream so that input will work for the next student
in.clear();
}
return in;
}
- -

Student_info.h

-
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
#ifndef GUARD_Student_info
#define GUARD_Student_info

// Student_info.header file
#include<iostream>
#include<string>
#include<vector>

struct Student_info{
std::string name;
double midterm, final;
std::vector<double> homework;
};

bool compare(const Student_info &, const Student_info &);
std::istream & read(std::istream &, Student_info &);
std::istream & read_hw(std::istream &, std::vector<double> &);
#endif
- -

grade.cpp

-
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 <algorithm>
#include <stdexcept>
#include <vector>
#include "grade.h"
#include "Student_info.h"

using std::domain_error; using std::istream;
using std::vector; using std::sort;

// grade function 1
double grade(const Student_info &s)
{
return grade(s.midterm, s.final, s.homework);
}

// grade function 2
double grade(double midterm, double final, const vector<double> &hw)
{
if (hw.size() == 0)
throw domain_error("student has done no homework");
return grade(midterm, final, median(hw));
}

// grade function 3
double grade(double midterm, double final, double homework)
{
return 0.2*midterm + 0.4*final + 0.4*homework;
}

// compute the median of vector<double>
double median(vector<double> vec)
{
// get the size of the vector
typedef vector<double>::size_type vec_size;
vec_size size = vec.size();

// check whether the empty is empty
if (size == 0)
throw domain_error("median of an empty vector");

// sort the grades
sort(vec.begin(), vec.end());

// compute the median homework grade
vec_size mid = size/2;
return size % 2 == 0 ? (vec[mid] + vec[mid - 1])/2 : vec[mid];
}
- -

grade.h

-
1
2
3
4
5
6
7
8
9
10
11
12
#ifndef GUARD_grade_h
#define GUARD_grade_h

// grade.h
#include<vector>
#include "Student_info.h"

double grade(const Student_info &);
double grade(double, double, const std::vector<double> &);
double grade(double, double, double);
double median(std::vector<double>);
#endif
- -

Results

I also wrote a naive program that “randomly” generates thousands of names and grades.

-
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
#include <cstdlib>
#include <iostream>
#include <string>

using std::cout; using std::endl;
using std::string;

int main()
{
string initials{"ABCDEFGHIJKLMNOPQRSTUVWXYZ"};
string letters{"abcdefghijklmnopqrstuvwxyz"};

for (int i = 0; i != 10000; ++i)
{
int x = rand() % 26;
int y = rand() % 26;
int z = rand() % 26;
int p = rand() % 26;
int q = rand() % 26;
double midterm = rand() % 100 + (rand() % 100)/99.0;
double final = rand() % 100 + (rand() % 100)/99.0;
double homework = rand() % 100 + (rand() % 100)/99.0;

string name{initials[x], letters[y], letters[z], letters[p], letters[q]};
cout << name << ' ' << midterm << ' ' << final << ' ' << homework << endl;
}

return 0;
}
-

The table below gives the performance difference of two version pograms on input files of ten lines, 1,000 lines, and 10,000 lines.

- - - - - - - - - - - - - - - - - - - - - - - -
Number of linesvectorlist
100.00107 seconds0.00007 seconds
10000.136711 seconds0.003036 seconds
100000.826406 seconds0.015598 seconds
-

Apparently, the list based program has much better performance than the vector based program, with using much less time in extracting the failing students’ records regardless of the file size. In addition, along with the increase of file size, the time taken by the list version program increases much slower than that taken by the vector version.

-
-

To be continued.

- - -
- - - - -
-
-
-
- - - + + + - - - -
- + + - + - -
-

- -

+
+ + + + + + + + 2,104 + + - + -
- - +
+ @@ -1918,100 +1995,58 @@

-

Exercise 5-0

Compile, execute, and test the programs in this chapter

-

Solution & Results

Please find solutions and detailed analysis in Chapter 5 Sequential Containers .

-

Exercise 5-1

Design and implement a program to produce a permuted index. A permuted index is one in which each phrase is indexed by every word in the phrase. So, given the following input,

-
1
2
The quick brown fox 
jumped over the fence
-

the output would be

-
1
2
3
4
5
6
7
8
      The quick     brown fox 
jumped over the fence
The quick brown fox
jumped over the fence
jumped over the fence
The quick brown fox
jumped over the fence
The quick brown fox
- -

A good algorithm is suggested in The AWK Programming Language by Aho, Kernighan, and Weinberger (Addison-Wesley, 1988). That solution divides the problem into three steps:

-
    -
  1. Read each line of the input and generate a set of rotations of that line. Each rotation puts the next word of the input in the first position and rotates the previous first word to the end of the phrase. So the output of this phase for the first line of our input would be

    -
    1
    2
    3
    4
    The quick brown fox
    quick brown fox The
    brown fox The quick
    fox The quick brown
    -

    Of course, it will be important to know where the original phrase ends and where the rotated beginning begins.

    -
  2. -
  3. Sort the rotations.

    -
  4. -
  5. Unrotate and write the permuted index, which involves finding the separator, putting the phrase back together, and writing it properly formatted.

    -
  6. -
-

Solution & Results

program logic

The solution strategy provided above is quite straightforward. But before implementing such strategy, several stylized facts observed from the example should be listed.

-
    -
  1. the output can be divided into two groups.
  2. -
  3. the right group of lines contains the key words (i.e. the alphabetically indexed words). The key word can be any word of a phrase. Therefore, when the first word of a phrase is indexed, the right part contains a complete phrase.
  4. -
  5. the left part and right part of the same line are complementary each other, which leads to a complete phrase. When the right part contains a complete phrase, the left part contains nothing but spaces.
  6. -
-

As described above, we can split the first phrase as follows

-
1
2
3
4
                    The quick brown fox
The quick brown fox
The quick brown fox
The quick brown fox
-

Rather than generating rotations, I would like to split one phrase into all possible combinations. Each combination will be stored into a data structure defined as below.

-
1
2
3
4
struct line{
std::string left; // contains the left part of a phrase
std::string right;// contains the right part of a phrase
};
-

In addition, we can define a vector to hold all combinations (i.e. objects of type line), covering all phrases to be entered.

-
1
vector<line> combinations;
-

Once all combinations have been stored into the vector, we can sort the vector according to the value of the right part of each combination.

-
1
sort(combinations.begin(), combinations.end(), compare);
- -
1
2
3
4
bool compare(const line &x, const line &y)
{
return x.right < y.right;
}
-

By doing so, we have alphabetized all key words. The next is to format the left parts. The content of left part of each line has been fixed as the left part is bound with the right part. All left parts are lined up vertically on the right side while their left side are padded out with certain amount of spaces depending on the longest string of the left parts.

-

Assuming the length of the longest string is obtained and stored in maxlen, the left part of outputs for each line will be

-
1
string(maxlen - combinations[z].left.size(), ' ') + combinations[z].left
- -

It is worth noting that the permuted index is case-insensitive. Therefore, the strings that compared in the compare function should be converted to lower-case letters. In addition, a line of phrase is easily to be splitted if there is no extra spaces before or after the the line, or between words. However, a common case is that users enter more spaces than needed. Therefore, it is necessary to clean needless spaces before we split one line.

-

In summary, the program can be divided into five logic parts

-
    -
  1. read in multiple lines of inputs
  2. -
  3. clean each line for removing needless spaces
  4. -
  5. split each line into all possible combinations
  6. -
  7. store all combinations for all lines and get maxlen.
  8. -
  9. sort and print each line
  10. -
-

files

The main function is shown below

-

mainfunction.cpp

-
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
#include<iostream>      // to get declaration of cin, cout and endl
#include<string> // to get declaration of string and getline
#include<vector> // to get declaration of vector
#include<algorithm> // to get declaration of sort
#include "cleaning.h"
#include "line.h"

using std::cin; using std::vector;
using std::cout; using std::string;
using std::endl; using std::getline;
using std::sort;


int main()
{
// hold combinations
vector<line> combinations;

// hold the length of the longest string
string::size_type maxlen = 0;

// read in
string words;
while(getline(cin, words))
{
// split each line, store all combinations, record maxlen
if(!words.empty())
split(cleaning(words), combinations, maxlen);
}

// format the outputs
cout << endl;
sort(combinations.begin(), combinations.end(), compare);

// write each line of outputs: left part + 4 spaces + right part
for (vector<line>::size_type z = 0; z != combinations.size(); ++z)
cout << (string(maxlen - combinations[z].left.size(), ' ') + combinations[z].left)
<< " " << combinations[z].right << endl;
return 0;
}
- -

I put the split function and compare funtion into one file named line.

-

line.cpp

-
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
#include <algorithm>
#include <string>
#include <vector>
#include <cctype>
#include "line.h"

using std::string; using std::tolower;
using std::vector; using std::isupper;
using std::max;

// function to get rotations of a line and store all rotations into vector<line>
void split(string words, vector<line> &combs, string::size_type &maxlen)
{
// store the original lines
line originLine;
originLine.right = words;
originLine.left = "";
combs.push_back(originLine);
maxlen = max(maxlen, originLine.left.size());

// store the splitted lines
string::size_type i = 0;
while (i != words.size())
{
line rotateLine;
if (isspace(words[i]))
{
rotateLine.right = words.substr(i+1, words.size() - i);
rotateLine.left = words.substr(0, i);
combs.push_back(rotateLine);
maxlen = max(maxlen, rotateLine.left.size());
}
++i;
}
}

// change characters to lowercase letters
string tolowerString(string c)
{
for (string::iterator iter = c.begin(); iter != c.end(); ++iter)
{
if (isupper(*iter))
*iter = tolower(*iter);
}
return c;
}

// define the arguments for sort function
bool compare(const line &x, const line &y)
{
return tolowerString(x.right) < tolowerString(y.right);
}
- -

line.h

-
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
#ifndef GUARD_LINE_H
#define GUARD_LINE_H

#include <string>
#include <vector>

struct line{
std::string left;
std::string right;
};

void split(std::string words, std::vector<line> &combs, std::string::size_type &maxlen);
std::string tolowerString(std::string x);
bool compare(const line &x, const line &y);

#endif /* GUARD_LINE_H */
- -

The final part is the cleaning function that is used to remove extra spaces.

-

cleaning.cpp

-
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
// this function removes needless spaces for a sentence
#include <string>
#include <cctype>
#include "cleaning.h"

using std::string;
using std::isspace;

string cleaning(string words)
{
string::iterator iter = words.begin();

// remove spaces before the real sentence begins
while(isspace(*iter))
iter = words.erase(iter);

// remove extra spaces between two words
while((iter+1) != words.end())
{
if (isspace(*iter) && isspace(*(iter + 1)))
iter = words.erase(iter);
else
++iter;
}

// remove the trailing space
if(isspace(*iter))
words.erase(iter);
return words;
}
- -

cleaning.h

-
1
2
3
4
5
6
7
8
#ifndef GUARD_CLEANING_H
#define GUARD_CLEANING_H

#include <string>

std::string cleaning(std::string words);

#endif /* GUARD_CLEANING_H */
-

Test performance

Let’s test the required inputs

-
1
2
3
4
5
6
7
8
9
10
11
The quick brown fox 
jumped over the fence

The quick brown fox
jumped over the fence
The quick brown fox
jumped over the fence
jumped over the fence
The quick brown fox
jumped over the fence
The quick brown fox
-

To show the robustness of this program, I choose a text fragment from The Declaration of Independence as the inputs:

-
1
2
3
4
5
6
7
8
9
He has refused for a long time, 
after such dissolution,
to cause others to be elected;
whereby the legislative powers,
incapable of annihilation,
have returned to the people at large for their exercise;
the State remaining in the meantime exposed
to all the dangers of invasion from
without and convulsion within.
- -

The resulted output perfectly follows the requirement of the permuted index

-
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
                            He has refused for    a long time,
after such dissolution,
to all the dangers of invasion from
without and convulsion within.
incapable of annihilation,
have returned to the people at large for their exercise;
to cause others to be elected;
to cause others to be elected;
without and convulsion within.
to all the dangers of invasion from
after such dissolution,
to cause others to be elected;
have returned to the people at large for their exercise;
the State remaining in the meantime exposed
He has refused for a long time,
have returned to the people at large for their exercise;
to all the dangers of invasion from
He has refused for a long time,
have returned to the people at large for their exercise;
He has refused for a long time,
the State remaining in the meantime exposed
incapable of annihilation,
to all the dangers of invasion from
have returned to the people at large for their exercise;
whereby the legislative powers,
He has refused for a long time,
the State remaining in the meantime exposed
incapable of annihilation,
to all the dangers of invasion from
to cause others to be elected;
have returned to the people at large for their exercise;
whereby the legislative powers,
He has refused for a long time,
the State remaining in the meantime exposed
have returned to the people at large for their exercise;
the State remaining in the meantime exposed
after such dissolution,
to all the dangers of invasion from
whereby the legislative powers,
the State remaining in the meantime exposed
have returned to the people at large for their exercise;
the State remaining in the meantime exposed
have returned to the people at large for their exercise;
He has refused for a long time,
to all the dangers of invasion from
to cause others to be elected;
to cause others to be elected;
have returned to the people at large for their exercise;
whereby the legislative powers,
without and convulsion within.
without and convulsion within.
- -
-

To be continued.

+ + + + Putting strings togetherThis part provides a series of exercises about various concatenations of strings. The topic is similar to that in Chpater 2 Lo + ... + +
+ + Read more » + +
+ + +
+ + + + + -
+ +
+ + + + + + + +
-
-

+ + + +
- + + + @@ -2019,179 +2054,265 @@

{ - if (!event.target.matches('.tabs-comment .tab-content .tab-pane')) return; - let commentClass = event.target.classList[1]; - localStorage.setItem('comments_active', commentClass); - }); - } - + + + - - + -
+
+ + +
+ + +
+ + + + - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + - - + - @@ -2201,8 +2322,8 @@

@@ -2215,6 +2336,22 @@

+ + + + + + + - - + + + - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + - + - - - + + + + + + + + + + + + - - Liam's Blog @@ -51,212 +132,248 @@ - - -
-
+ -
-
-
+
- - -
+
+
+ + -
- + + + + + + +
+ -
- +
+ + +
-

- - -

+ + + +

+ +

+
+ @@ -264,137 +381,178 @@

-

Putting strings together

This part provides a series of exercises about various concatenations of strings. The topic is similar to that in Chpater 2 Looping and counting.

-

Exercise 1

Briefly speaking, the first exercise requires us to design a program that can add a frame for a picture (shown as below).

-

The original picture

-
1
2
3
4
5
this is an
example
to
illustrate
framing
-

The framed picture

-
1
2
3
4
5
6
7
**************
* this is an *
* example *
* to *
* illustrate *
* framing *
* ************
-

The original picture is in fact formed by several lines of strings, of which each string is an element stored in the vector named p. The framed picture adds four edges for the original picture with asterisks. There is one space between the left-edge and the initial character of the strings, and one space between the right-edge and the end of the longest string.

-

Exercise 2

Once we have the framed picture, we can vertically concatenate it with the original one. The program should generate a final picture like this

-
1
2
3
4
5
6
7
8
9
10
11
12
this is an
example
to
illustrate
framing
**************
* this is an *
* example *
* to *
* illustrate *
* framing *
**************
-

Two pictures are lined up along the left-hand border.

-

Exercise 3

We can also perform horizontal concatenation on two pictures like the below picture shows

-
1
2
3
4
5
6
7
this is an **************
example * this is an *
to * example *
illustrate * to *
framing * illustrate *
* framing *
**************
-

Two pictures are lined up along the top border. In addition, there is a blank column that seperates two pictures.

-

Solutions

The book provides solutions to each exercise listed above, I’ll put all together and write a program that can generate above pictures once.

-

The framed picture

Framing a picture is an old question. The solution strategy can be divided into three steps

-
    -
  1. obtain the length (denoted by maxlen) of the longest string in p as the width of the framed picture depends on maxlen.
  2. -
  3. create a string object that filled by asterisks for generating the top line and bottom line. The number of the astersiks is maxlen + 4.
  4. -
  5. the middle lines are formed by 1 space character and the corresponding string in p, and a certain number of spaces. The number of spaces is the difference between the maxlen and the size of the corresponding string to be concatenated.
  6. -
-

Below shows the function that gives the length of the longest string in a object of vector. I uses iterators instead of indices for all functions.

-
1
2
3
4
5
6
7
8
9
string::size_type width(const vector<string> &v)
{
string::size_type maxlen = 0;
for (vector<string>::const_iterator iter = v.begin(); iter != v.end(); ++iter)
{
maxlen = max(maxlen, (*iter).size());
}
return maxlen;
}
-

The function below generates the framed picture while keeping the original picture unchanged.

-
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
vector<string> frame(const vector<string> &v)
{
// to hold each rows of framed picture to be written
vector<string> ret;
// get the length of the longest string
string::size_type maxlen = width(v);

// first line of outputs
string border(maxlen + 4, '*');
ret.push_back(border);

for (vector<string>::const_iterator iter = v.begin(); iter != v.end(); ++iter)
{
// new rows except two border lines
string temp = "* " + (*iter) + string(maxlen - (*iter).size(), ' ') + " *";
ret.push_back(temp);
}

// bottom line of outputs
ret.push_back(border);
return ret;
}
-

Vertical concatenation

In fact we can generate the required picture without concatenation. The first step is to generate the original picture and then the second step is to generate the framed picture. They are closely connect if no blank line between two outputs. This implies that we can create a new object of verctor and copy all strings contained in two pictures into it. The function is shown below.

-
1
2
3
4
5
6
7
8
9
vector<string> vcat(const vector<string> &top, const vector<string> &bottom)
{
// copy the top picture
vector<string> ret = top;

// copy the bottom picture one line by one line
ret.insert(ret.end(), bottom.begin(), bottom.end());
return ret;
}
-

What’s new here is the insert function. vector, string and list all support insert function. The first parameter of this insert function means that the new elements will be inserted into a position that before the parameter specifies. The second and the third parameters indicate that the elements in the range of [bottom.begin(), bottom.end()) will be copied and inserted. This increases the length of vector. It has the same effect as the for loops shown below.

-
1
2
3
4
for (vector<string>::const_iterator i = bottom.begin(); i != bottom.end(); ++i)
{
ret.push_back((*i));
}
-

Horizontal concatenation

Similar to vertical voncatenation, we can generate the required picture without concatenation through alternately write rows of the original picture and the framed picture until all rows from two pictures are written. If the number of rows of two pictures are different, the one that has less number of rows will be replenished with blank strings.

-

Therefore, the key to the solution is to concatenate two strings, of which one from the original picture and another from the framed picture. As we did in the fisrt exercise, we need to pad enough spaces for each row of the original picture. Now, the number of spaces will be the difference between maxlen + 1 and the size of each string itself as we want one space column between two pictures. The code below show the function.

-
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
vector<string> hcat(const vector<string> &left, const vector<string> &right)
{
// to hold the each line of outputs
vector<string> ret;

// add one space column between two pictures
string::size_type width1 = width(left) + 1;

// iterators to look at elements from left and right respectively
vector<string>::const_iterator iter_i = left.begin(), iter_j = right.begin();

// continue until we've see all rows from two pictures
while (iter_i != left.end() || iter_j != right.end())
{
// construct new string to hold characters from two pictures
string s;

// copy a row from left side picture
if (iter_i != left.end())
{
s = (*iter_i);
++iter_i;
}
// pad to full width
s += string(width1 - s.size(), ' ');

// copy a row from right picture
if (iter_j != left.end())
{
s += (*iter_j);
++iter_j;
}

// store s into vector to form a new picture
ret.push_back(s);
}
return ret;
}
- -

A complete program

I package all three pictures into one file and the width function into another file. In addition, I define a function more to deal with output. All files are displayed below with detailed comments for each step.
output.cpp

-
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
// function to write each elements from a vector<string>
#include <iostream> // to get declaration of cout and endl
#include <string> // to get declaration of string
#include <vector> // to get declaration of vector
#include "output.h"

using std::cout; using std::string;
using std::endl; using std::vector;

void output(const vector<string> &pics)
{
// loop thru the vector and write elements one by one
for (vector<string>::const_iterator iter = pics.begin(); iter != pics.end(); ++iter)
{
cout << (*iter) << endl;
}
}
-

output.h

-
1
2
3
4
5
6
7
8
9
#ifndef GUARD_OUTPUT_H
#define GUARD_OUTPUT_H

#include <string>
#include <vector>

void output(const std::vector<std::string> &pics);

#endif /* GUARD_OUTPUT_H */
- -

width.cpp

-
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
// function returns the size of the longest string in a vector<string>
#include <string> // to get declaration of string
#include <vector> // to get declaration of vector
#include <algorithm> // to get declaration of max
#include "width.h" // to get declaration of the function itself

using std::string; using std::vector; using std::max;

string::size_type width(const vector<string> &v)
{
string::size_type maxlen = 0;
for (vector<string>::const_iterator iter = v.begin(); iter != v.end(); ++iter)
{
maxlen = max(maxlen, (*iter).size());
}
return maxlen;
}
-

width.h

-
1
2
3
4
5
6
7
8
9
#ifndef GUARD_WIDTH_H
#define GUARD_WIDTH_H

#include <string>
#include <vector>

std::string::size_type width(const std::vector<std::string> &v);

#endif /* GUARD_WIDTH_H */
- -

pics.cpp

-
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
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
// functions that generate different pictures
#include <string> // to get declaration of string
#include <vector> // to get declaration of vector
#include "width.h"
#include "pics.h"

using std::string; using std::vector;

// function to generate a framed picture
vector<string> frame(const vector<string> &v)
{
// to hold each rows of framed picture to be written
vector<string> ret;
// get the length of the longest string
string::size_type maxlen = width(v);

// first line of outputs
string border(maxlen + 4, '*');
ret.push_back(border);

for (vector<string>::const_iterator iter = v.begin(); iter != v.end(); ++iter)
{
// new rows except two border lines
string temp = "* " + (*iter) + string(maxlen - (*iter).size(), ' ') + " *";
ret.push_back(temp);
}

// bottom line of outputs
ret.push_back(border);
return ret;
}

// function to vertically concatenate two pictures
vector<string> vcat(const vector<string> &top, const vector<string> &bottom)
{
// copy the top picture
vector<string> ret = top;

// copy the bottom picture one line by one line
ret.insert(ret.end(), bottom.begin(), bottom.end());
return ret;
}

// function to horizontally concatenate two pictures
vector<string> hcat(const vector<string> &left, const vector<string> &right)
{
// to hold the each line of outputs
vector<string> ret;

// add one space column between two pictures
string::size_type width1 = width(left) + 1;

// iterators to look at elements from left and right respectively
vector<string>::const_iterator iter_i = left.begin(), iter_j = right.begin();

// continue until we've see all rows from two pictures
while (iter_i != left.end() || iter_j != right.end())
{
// construct new string to hold characters from two pictures
string s;

// copy a row from left side picture
if (iter_i != left.end())
{
s = (*iter_i);
++iter_i;
}
// pad to full width
s += string(width1 - s.size(), ' ');

// copy a row from right picture
if (iter_j != right.end())
{
s += (*iter_j);
++iter_j;
}

// store s into vector to form a new picture
ret.push_back(s);
}
return ret;
}
-

pics.h

-
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
#ifndef GUARD_PICS_H
#define GUARD_PICS_H

#include <string>
#include <vector>

std::vector<std::string> frame(const std::vector<std::string> &v);

std::vector<std::string> vcat(const std::vector<std::string> &top,
const std::vector<std::string> &bottom);

std::vector<std::string> hcat(const std::vector<std::string> &left,
const std::vector<std::string> &right);

#endif /* GUARD_PICS_H */
- -

mainfunction.cpp

-
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
#include <iostream>		// to get declaration of cout and endl
#include <string> // to get declaration of string
#include <vector> // to get declaration of vector
#include "width.h"
#include "pics.h"
#include "output.h"

using std::cout; using std::vector;
using std::endl; using std::string;

int main()
{
// to hold the original picture
vector<string> p;
p.push_back("this is an");
p.push_back("example");
p.push_back("to");
p.push_back("illustrate");
p.push_back("framing");

vector<string> p1 = frame(p);
vector<string> p2 = vcat(p, p1);
vector<string> p3 = hcat(p, p1);


output(p); // print the original picture
cout << endl; // write a blank line to seperate two pictures
output(p1); // print the framed picture
cout << endl;
output(p2); // print the vertically concatenated picture of p and p1
cout << endl;
output(p3); // print the horizontally concatenated picture of p and p1

return 0;
}
-

Test performance

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
this is an
example
to
illustrate
framing

**************
* this is an *
* example *
* to *
* illustrate *
* framing *
**************

this is an
example
to
illustrate
framing
**************
* this is an *
* example *
* to *
* illustrate *
* framing *
**************

this is an **************
example * this is an *
to * example *
illustrate * to *
framing * illustrate *
* framing *
**************
-

The program works perfectly.

+ + + + Recalling the student-grading program in Organizing programs with data structures, and thinking about how to separate students into two categories, pa + ... + +
+ + Read more » + +
+ + +
+ + + + + -
+ +
+ + + + + + + +
-
+ +
+

+ + +
+ + + + + + + - +
-
- + +
+ + +
-

- - -

+ + + +

+ +

+
+ @@ -402,469 +560,538 @@

-

Recalling the student-grading program in Organizing programs with data structures, and thinking about how to separate students into two categories, passed and failed according to their final grades. Let’s define pass/fail criteria in final grades.

-
1
2
3
4
5
// predicate to determine whether a student failed
bool fgrade(const Student_info &s)
{
return grade(s) < 60;
}
-

If the computed grade is less than 60, it is a failing grade and the predicate yields a true value.

-

vector

An indices approach

The next is to examine each element in students based on the function fgrade, and store the records for the students who failed and who passed seperately.

-
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
vector<Student_info> extract_fails(vector<Student_info> &students)
{
vector<Student_info> pass, fail;

for (vector<Student_info>::size_type i = 0;
i != students.size(); ++i)
{
if (fgrade(students[i]))
fail.push_back(students[i]);
else
pass.push_back(students[i]);
}
students = pass;
return fail;
}
-

This function returns a vector named fail which contains the records for students who failed, and changes the original students to a vector which contains only the records for students who passed. It can be seen that the vector pass exists temporarily and seems to be superfluous from the perspective of the limited memory.

-

Alternatively, we can remove the pass and operate on the original student directly. The member function erase defined in the class template vector allows users to remove elements, either a single or a range, from a vector. Now let’s see how to change above code using erase.

-
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
vector<Student_info> extract_fails(vector<Student_info> &students)
{
vector<Student_info> fail;
vector<Student_info>::size_type i = 0;

// loop invariant: [0, i) of students represent passing grades
while(i != students.size())
{
if(fgrade(students[i]))
{
fail.pushback(students[i]);
students.erase(students.begin() + i);
}
else
++i;
}
return fail;
}
-

This function only creates one vector to hold the records for students who failed. The logic is quite straightforward:

-
    -
  1. when the first iteration starts, i is 0 and students.size() yields the original length of the vector.
  2. -
  3. then, the while body is executed. If the ith student failed, the record will be stored into fail, and also be erased from the original vector. Noting that there are two side effects: first, the length of the vector decreases by 1; the index of all reminder elements move forward by 1. By then,the element students[1] in the original vector now becomes students[0] in the new vector as the original studnets[0] has been removed. Therefore, the examination starts from i = 0 again.
  4. -
  5. if the ith student passed, the record will be kept and the next loop examines the following element, i.e. students[i+1]. Therefore, i increases by 1 before the end of the current loop preparing for next loop.
  6. -
-

What’s new here is the arguments of erase function.

-
1
students.erase(students.begin() + i);
-

We have mentioned in previous chapters that students.begin() denotes the initial element (i.e. corresponding to index 0) in the vector students. Increasing by i, the arguments denotes the ith element. The reason to use students.begin() is that the parameter type defined in erase is const_iterator (this is discussed laster).

-

Due to the length of the vector is not fixed, the students.size() should be used in the condition as it always yields the current length when it is evaluated. If a precomputed size is used, e.g. using size defined as below to replace students.size(), students[i] may refer to nonexist elements or the loop becomes endless once any of elements is removed.

-
1
vector<Student_info>::size_type size = students.size();
- -

An iterators approach

It has been observed that each function defined above access elements from the vector in sequential order. Also it is known that indices allows us to access at random. From the perspective of the library, using indices has the same effect as that we request permissions to access elements randomly. In other words, indices privide the ability that we don’t want to use or we don’t need at all.

-

Beyond indices, C++ supports another mechanism iterators that we can use to access elements in a vector. Not all standard containers support indices, but all standard containers supports iterators.

-

iterators provide more flexibility in supporting certain operations depending on different types of containers.

-

In specific, an iterator is a value that

-
    -
  • identifies a container and an element in the container
  • -
  • let us examine the value stored in that element
  • -
  • provides operations for moving between elements in the container
  • -
  • restricts the available operations in ways that correspond to what the container can handle efficiently.
  • -
-

the syntax

If we use indices for the iteration, for example,

-
1
2
3
for (vector<Student_info>::size_type i = 0; 
i != students.size(); ++i)
cout << students[i].name << endl;
-

alternatively, we can use iterators also

-
1
2
for (vector<Student_info>::const_iterator iter = students.begin(); iter != students.end(); ++iter)
cout << (*iter).name << endl;
-

From above code, we know that the type of the iter is
vector::const_iterator. More general, vector defines two associated itertor types:

-
1
2
vector<type>::const_iterator
vector<type>::iterator
-

The difference is that iterators of type const_iterator have only read access. If we want to use an iterator to change values in a vector, we should define it using iterator type.

-

In addition, iter is initialized with the value of students.begin(). begin and end function return values that denotes the begining or one past the end of a container, respectively. Noting that students.begin() returns a value of type iterator but is converted to the type const_iterator in above code.

-

The next is to increase the value of iter using ++iter. The effect of the increment operator on iter is up to how the iterator type defines. As a result, the iterator denotes the next element in the container after this expression is executed.

-

The statement in the for body shows how to indirect access to the elements in students.

-
1
cout << (*iter).name;
-

The dereference operator () applies to *iter** and returns an lvalue (i.e. the element) to which the iterator refers.

-

Alternatively, we can dereference an iterator and fetch the element via

-
1
iter->name
-

It has same effect as (*iter).name.

-

rewrite functions using iterators

Now let’s use iterators to rewrite the extract_fails function.

-
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
vector<Student_info> extract_fails(vector<Student_info> &students)
{
vector<Student_info> fail;
vector<Student_info>::const_iterator iter = students.begin();

// loop invariant: [0, i) of students represent passing grades
while(iter != students.end())
{
if(fgrade(*iter))
{
fail.pushback(*iter);
iter = students.erase(iter);
}
else
++iter;
}
return fail;
}
-

As mentioned earlier, we pass iter directly to the erase function as the parameter has type of const_iterator.

-

Noting that calling erase function invalidates all iterators that refer to elements after the one that was just removed. But the erase function will return an iterator that is positioned on the element that follows the erased one. Therefore, the statement becomes

-
1
iter = students.erase(iter);
-

Iterators also support equility (=) and inequaility (!=) operations. Two iterators are equal if they denote the same element or they are off-the-end iterator for the same container.

-

Iterator arithmetic

All standard containers support above iterator operations. But iterators for vector and string also support additional operations as shown below.

-
    -
  1. iter +/n. Adding/substracting an integer n to an iterator yields an iterator that denotes the nth emelemt (if available) after/before the original element, or one past the end of the container.

    -
  2. -
  3. iter1 - iter2. Substracting two iterators yields an number when added to the right-hand operand yields the left operand. The iterators must denote elements within the container or one past the end of the container.

    -
  4. -
  5. >, >=, <, <=. One iterator is less than another if it denotes an element that appears before the element that denoted by the other iterator. Both iterators must denote elements within the container or one past the end of the container.

    -
  6. -
-

list

vector allows us to arbitrarily access elements efficiently. Moreover, it performs well when adding or deleting the element at the end of the vector. However, vector is not a good choice when it come to inserting or removing elements from the interior of the vector, such as functions illustrated above.

-

To deal with such problems, the standard library provides another data structure list for us to efficiently insert or delete elements anywhere in the container. On the other hand, list doesn’t support random access via indices. It only supports bidirectional sequential access via iterators.

-

The list type is defined in the standard header . Same as the vector, list is also a class template. In addition, list and vector share many operations. For example, we can use list to rewrite the function extract_fails.

-
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
// use list instead of vector
list<Student_info> extract_fails(list<Student_info> &students)
{
list<Student_info> fails;
list<Student_info>::iterator iter = students.begin();

while(iter != students.end())
{
if (fgrade(*iter))
{
fail.push_back(*iter);
iter = students.erase(iter);
}
else
++iter;
}
return fail;
}
-

The code above has no distinct difference comparing with the vector version. However, the important difference between lists and vectors is that how they affect on iterators.

-
    -
  1. For vectors, the erase operation invalidates all iterators that refer to elements including the one was just removed and the subsequent elements. It is because that when removing one element, all following elements move one position towards the one erased. The push_back operation invalidates all iterators due to the fact the entirely vector might be reallocated to a new area for holding the new element.

    -
  2. -
  3. For lists, the erase and push_back operations do not invalidate other iterators except the one that was erased.

    -
  4. -
-

Another difference is that the list type does’t support the standard sort function. It has its own member funciton sort which can be called as follows

-
1
2
list <Student_info> students;
students.sort(compare);
-

Taking strings apart

A string can be regarded as a special container that only contains characters. It supports many operations, such as random access through indices and iterators, similar to a vector. Therefore, we can access and operate on a specific character. In addition, the standard header defines a bunch of functions that can be used to deal with single character in a string. The table below gives the details of each function

- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
cctype functionsSource: Lippman etc. 2012, p.82
isalnum(c)true if c is a letter or a digit
isalpha(c)true if c is a letter
iscntrl(c)true if c is a control character
isdigit(c)true if c is a digit
isgraph(c)true if c is not a space but is printable
islower(c)true if c is a lowercase letter
isprint(c)true if c is a printable character
ispunct(c)true if c is a punctuation character
isspace(c)true if c is whitespace
isupper(c)true if c is an uppercase letter
isxdigit(c)true if c is a hexademical digit
tolower(c)if c is an uppercase letter, it returns the lowercase letter; otherwise returns c unchanged
toupper(c)if c is a lowercase letter, it returns the uppercase letter; otherwise returns c unchanged
-

For example, we can write a funciton to extract each word from a string like

-
1
The wealth of the mind is the only wealth
-

We can use a string member function substr to construct a new string object with its value initialized to a copy of a substring of the original string. substr defines two parameters, of which one is the inital position of the substring in the original string and the other one is the length of the substring. Therefore, the key to solve this project is to find the index of the begining of each word and the length. It can be observed that each word starts from a nonwhitespace character and ends with a character followed by whitespace. Assuming the first character of a word is positioned at indice i and the whitespace that closely follows the end of the word has indice j, the length of the word will be j - i (imagine the half-open range).

-

The solution is provided in the book and shown as below

-
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
vector<string> split(const string &s)
{
vector<string> ret;
typedef string::size_type string_size;
string_size i = 0;

// invariant: we have processed characters [original value of i, i)
while(i != s.size())
{
// ignore leading blanks
// invariant: characters in range [original i, current i) are all spaces
while(i != s.size() && isspace(s[i]))
++i;

// find end of the next word
string_size j = i;

// invariant: none of the characters in range [original j, current j)
while(j != s.size() && !isspace(s[j]))
++j;
// if we found some nonwhitespace characters
if (i != j)
{
// copy from s starting at i and taking j - i characters
ret.push_back(s.substr(i, j-i));
i = j;
}
}
return ret;
}
-

Noting that, when the function try to recognize the last word, the third while loop will stop because of j == s.size() even if !isspace(s[j]) is still true in the case that no whitespaces follows the last word. I add #include directives and using declarations, and test the program using the example string described above.

-
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
#include<iostream>
#include<vector>
#include<string>

using std::cout; using std::vector;
using std::endl; using std::string;

// function declaration
vector<string> split(const string &s);

int main()
{
// define a string contains words separated by whitespaces
string s{"The wealth of the mind is the only wealth"};

// define a vector to hold splitted words
vector<string> words = split(s);

// write each line that contains one word
for (vector<string>::size_type i = 0; i != words.size(); ++i)
cout << words[i] << endl;

return 0;
}

// Please define the split function here
-

Test results

-
1
2
3
4
5
6
7
8
9
The
wealth
of
the
mind
is
the
only
wealth
-
-

To be continued.

+ + + + Exercise 4-6Rewrite the Student_info structure to calculate the grades immediately and store only the final grade. +Solution & ResultsI didn’t unde + ... + +
+ + Read more » + +
+ + +
+ + + + + -
+ +
+ + + + + + + +
-
+ +
+

+ + +
+ + + + + + + - +
+ -
- +
+ + +
-

- - -

+ + + +

+ +

+ +
+ + + + + +
+ + + + + + + + Exercise 4-0Compile, execute, and test the programs in this chapter +Solution & ResultsThis exercise has been done presented in Organizing programs + ... + +
+ + Read more » + +
+ + + +
+ + + + + + + + + + +
+ + + + + + + + +
+ +
+
+ + + +
+ + + + + + + + + + -
-
- - - -
- -

Exercise 4-6

Rewrite the Student_info structure to calculate the grades immediately and store only the final grade.

-

Solution & Results

I didn’t understand very well about the original program when I went through it first time. Now I try to rewrite it in this program with detailed explinations on each step. The strategy can be divided into three parts:

-
    -
  1. analyse the requirements
  2. -
  3. break the ultimate goal into smaller ones that are logically connected
  4. -
  5. design algorithms to accomplish small goals
  6. -
  7. logically verify the correctness of each part and put all together
  8. -
  9. test the performance
  10. -
-

Requirements interpretation

What we have?

The program asks to enter a file that contains multiple students’ information including name, midterm exam grade, final exam grades and a sequence of homework grades. The example below shows an input sample

-
1
2
3
4
Robin 90 87 79 88 81 73 45 
Brendan 70 69 88 100 91 75 66
Arsenii 99 87 89 88 74 90 70
Liam 83 66 100 76 87 91 78
-

Note that the number of students is unkown and the number of homework grades is unknown as well.

-

What we need to do?

The program is required to generate a final grade report for a class. Specifically

-
    -
  1. each line of the report contains the information of one student, including name and a final grade.
  2. -
  3. the final grade is the weighted average of midterm, final and homework grades, of which the mediter exam grade counts for 20%, the final exam grade counts for 40%, and the median homework grade counts for 40%.
  4. -
  5. each line of outputs follows an alphabetical order according to the names.
  6. -
  7. the final grades are vertically aligned.
  8. -
-

An output sample can be found here

-
1
2
3
4
Arsenii 89.8
Brendan 76.8
Liam 77.8
Robin 84.4
-

others info

We are required to define a data structure to hold each student’s information including name and his final grade. This can be finished immediately.

-
1
2
3
4
5
// a structure contains name and final grade
struct Student_info{
string name;
double grade;
};
-

It can be seen that Student_info in fact have all information of outputs. Therefore, once it is filled with correct information, the rest is to format the report to be written.

-

Decompose the program

Overall structure

Assuming that we have filled the information for one student(i.e. one object of Student_info), we need to store it for making preparations for generating the report. As the number of student is unknown, vector is flexible enough to hold each Student_info object. Thus, the overall structure of this program can be

-
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
int main()
{
// to hold the information for each student
vector<Student_info> students;

// one Student_info object to be filled
Student_info record;

// loop invariant: we have stored students.size() students already
while (// condition statement)
{
// put the recorded student into the vector students
students.push_back(record);
}

// write statements to format the report
}
-

What the condition for this while loop should be? The condition for us to store record is that it has been filled(i.e. both name and grade contains correct information). Therefore, the condition shoule be true if record is successfully filled, and should be false if the attempt is failure.

-

There are two members in an object of type Student_info, one is name and the other one is grade. name comes from supplied information while grade can be computed using the information. It is necessary to write a function that not only can read and deal with supplied information but also meet the requirments of being the condition of above while loop. Let’s declare this function

-
1
istream &read(istream &is, Student_info &s);
-

The first parameter is a reference that refers to the object of input stream (i.e. istream). The second parameter is also a reference that refers to record. The reason to pass arguments by reference rather than value is that:

-
    -
  1. the function needs to return a value that shows whether the reading attempt is successful. It is the pre-condition for filling record.
  2. -
  3. the istream objects can’t be copied.
  4. -
  5. the function needs to return the filled record.
  6. -
  7. By passing by reference, we can avoid seting two return values.
  8. -
-

As the condition of the while loop, read function returns an istream type object which is evaluated to be true if it is valid and otherwise it is evaluted to be false. Therefore, read function should return a valid istream object after finishing filling the record, and return an invalid istream object after finishing filling all records.

-

Fill in the information

Let’s firstly consider how to fill the information for on student. The name can be stored directly into s.name. To hold midterm and final grades, we define two double type variables while to hold homework grades we define a variable of type vector.

-
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
istream & read(istream &is, Student_info &s)
{
// reads and store the student's name, midterm and final exam grades
double midterm, final;
is >> s.name >> midterm >> final;

// reads and store all homework grades
vector<double> hw;

// call functions that store all homework grades
// call functions that compute the final grade

// return a valid is if both name and grade contain correct information
return is;
}
- -

To compute the final grade, what we lack is the median value of homework grades. But before we can compute the median grade, we need to store all possible homework grades using a function similar to read.

-
1
2
3
4
5
6
7
8
9
10
11
istream & read_hw(istream &in, vector<double> &hw)
{
// read homework grades
double x;

// loop invariant: we have read hw.size() homework grades
while(in >> x)
hw.push_back(x);

return in;
}
-

This function is ok if there is only one student but doesn’t work for the input file shown above. When it finishes reading all homework grades of the first student, it terminates because that it encounters next student’s name rather than a double value, leading to the input stream being failure state. Therefore, we can add in.clear() before return in to reset the error state to good.

-
1
2
// clear the stream so that input will work for the next student
in.clear();
- -

Computations

Once we obtained the homework grades, we can compute the median value and then the final grade. The algorithms for these computations are not complicated. The function below will be called in the read function.

-
1
2
3
4
5
6
double grade(double midterm, double final, const vector<double> &hw)
{
if (hw.size() == 0)
throw domain_error("student has done no homework");
return grade(midterm, final, median(hw));
}
-

In this function, two functions are called. One is the median(hw) function which returns the median value, and anther one is overloaded grade function that returns the final grade which is again returned to the read function. Two functions are presented as follows

-
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
// function to calculate median
double median(vector<double> vec)
{
// get the size of the vector
typedef vector<double>::size_type vec_size;
vec_size size = vec.size();

// check whether the empty is empty
if (size == 0)
throw domain_error("median of an empty vector");

// sort the grades
sort(vec.begin(), vec.end());

// compute the median homework grade
vec_size mid = size/2;
return size % 2 == 0 ? (vec[mid] + vec[mid - 1])/2 : vec[mid];
}
-
1
2
3
4
5
// function to calculate final grade
double grade(double midterm, double final, double homework)
{
return 0.2*midterm + 0.4*final + 0.4*homework;
}
- -

How does eof works?

Now, we can complete the read function and the while loop. Noting that I add throw statement after finishing reading the homework grades. Correspondingly, I add the try block to catch any exception that might be thrown when reading students’ information.

-
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
istream & read(istream &is, Student_info &s)
{
// reads and store the student's name, midterm and final exam grades
double midterm, final;
is >> s.name >> midterm >> final;

// reads and store all homework grades
vector<double> hw;
read_hw(is, hw);
if(hw.empty())
throw domain_error("student has done no homework");

// get the value of final grade
s.grade = grade(midterm, final, hw);

// return a valid is if both name and grade contain correct information
return is;
}
-
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
int main()
{
// to hold the information for each student
vector<Student_info> students;

// one Student_info object to be filled
Student_info record;

// loop invariant: we have stored students.size() students already
try{
while(read(cin, record))
{
maxlen = max(maxlen, record.name.size());
students.push_back(record);
}
}catch(domain_error e){
cout << e.what() << endl;
}

// write statements to format the report
}
-

Theoretically, we have finished most parts of the program. The next step is to format outputs if above functions work fine. Let’s logically verify this part with following steps

-
    -
  1. when the computer executes the while loop, the condition is evaluated.
  2. -
  3. the computer enter into the read function. The function stores name, midterm and final, and create an empty object hw.
  4. -
  5. then the computer enters into the read_hw function. This function creates an object x and starts another while loop.
  6. -
  7. the condition in >> x is evaluated. In most cases, the condition is true until it encounters next student’s name. As a result, the input stream is changed to failure state. By then, it has stored all homework grades of the first student into the vector hw.
  8. -
  9. the state of in is reset to be good by using in.clear().
  10. -
  11. in is returned and the implementation goes back to the function read.
  12. -
  13. grade function is called. Inside this function, another grade function is called and median function is called.
  14. -
  15. s.grade is obtained, and is is returned. By then, record contains correct information of the first student.
  16. -
  17. step 1 continues, is is evaluated to be true which indicates that record is filled successfully.
  18. -
  19. the computer moves to the while body and record is stored into students. By then, both the name and final grade of the first student is available for us.
  20. -
  21. the while loop starts over again from step 1 - 10.
  22. -
-

Yeah, it works fine by now. However, how to stop it? Typically we can stop a while loop by sending a signal eof to set the input stream being a failure state. Let’s type Ctrl+Z/D and see how it works.

-
    -
  1. same as above
  2. -
  3. same as above. Though name, midterm are uninitialized, the implementation would’t stop at this position.
  4. -
  5. same as above
  6. -
  7. the condition in >> x is evaluated to be false due to the eof
  8. -
  9. same as above. By then, hw is still empty.
  10. -
  11. same as above
  12. -
  13. grade function is called. An exception is thrown due to the fact of step 5.
  14. -
  15. the program fails
  16. -
-

So, what’s the problem? The immediate cause is the empty hw. We actually even don’t want to create the hw since we want to terminate the while loop immediately. Therefore, we need to add a condition like follow code shows

-
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
istream & read(istream &is, Student_info &s)
{
// reads and store the student's name, midterm and final exam grades
double midterm, final;
is >> s.name >> midterm >> final;

// to check whether eof is sent
if(is)
{
// reads and store all homework grades
vector<double> hw;
read_hw(is, hw);
if(hw.empty())
throw domain_error("student has done no homework");

// get the value of final grade
s.grade = grade(midterm, final, hw);

// return a valid is if both name and grade contain correct information
}
return is;
}
-

Why don’t add the if condition at the very begining of the read function? It is because that the input stream hasn’t start to read anything at that position. Now we recheck each step after sending eof signal.

-
    -
  1. same as above
  2. -
  3. midterm and final is created.
  4. -
  5. eof is read and the state of is is set to be failure.
  6. -
  7. condition is is evaluated to be false
  8. -
  9. return is. The computer goes back the step 1.
  10. -
  11. the condition in step 1 is evaluated to be false, while loop stops.
  12. -
-

Now, this part of code should work as expected.

-

Formatting the outputs

The next is to format each line of outputs according to requirements analysed above.
To alphabetize the outputs, we can use following code

-
1
sort(students.begin(), students.end(), compare);
-

where compare is defined as follows

-
1
2
3
4
bool compare(const Student_info &x, const Student_info &y)
{
return x.name < y.name;
}
- -

To line up the final grades, we need to find the longest name and hence add one statement in the first while loop. maxlen will finally record the length of the longest name.

-
1
maxlen = max(maxlen, record.name.size());
-

The final grade can be obtained via

-
1
double final_grade = students[i].grade;
- -

####

-

Put all together

Following gives each file of the program. No more discussion here.
mainfunction.cpp

-
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
#include <algorithm>
#include <iomanip>
#include <ios>
#include <iostream>
#include <stdexcept>
#include <string>
#include <vector>
#include "Student_info.h"
#include "grade.h"

using std::cin; using std::setprecision;
using std::cout; using std::sort;
using std::endl; using std::streamsize;
using std::domain_error; using std::string;
using std::max; using std::vector;

int main()
{
vector<Student_info> students;
Student_info record;
string::size_type maxlen = 0;

// read and store all the records, and find the length of the longest name
try{
while(read(cin, record))
{
maxlen = max(maxlen, record.name.size());
students.push_back(record);
}
}catch(domain_error e){
cout << e.what() << endl;
}

// alphabetize the records
sort(students.begin(), students.end(), compare);

// write each line of outpurs
for (vector<Student_info>::size_type i = 0; i != students.size(); ++i)
{
// write the name, blanks
cout << students[i].name << string(maxlen + 1 - students[i].name.size(), ' ');

// compute and write the final grade
double final_grade = students[i].grade;
streamsize prec = cout.precision();
cout << setprecision(3) << final_grade << setprecision(prec) << endl;
}
return 0;
}
-

Student_info.cpp

-
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
#include "Student_info.h"
#include "grade.h"
using std::vector; using std::istream; using std::cin; using std::cout;

// function to define the additional arguments in max
bool compare(const Student_info &x, const Student_info &y)
{
return x.name < y.name;
}

// function to read data for inputs and fill information
istream & read(istream &is, Student_info &s)
{
// reads and store the student's name, midterm and final exam grades
double midterm, final;
is >> s.name >> midterm >> final;
if (is)
{
// reads and store all homework grades
vector<double> hw;
read_hw(is, hw);
if(hw.empty())
throw domain_error("student has done no homework");

// get the final grade
s.grade = grade(midterm, final, hw);
}
return is;
}

// function to read homework grades
istream & read_hw(istream &in, vector<double> &hw)
{
// read homework grades
double x;
while(in >> x)
hw.push_back(x);

// clear the stream so that input will work for the next student
in.clear();

return in;
}
-

Student_info.h

-
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
#ifndef GUARD_Student_info
#define GUARD_Student_info

// Student_info.header file
#include<iostream>
#include<string>
#include<vector>

struct Student_info{
std::string name;
double grade;
};

bool compare(const Student_info &, const Student_info &);
std::istream & read(std::istream &, Student_info &);
std::istream & read_hw(std::istream &, std::vector<double> &);
#endif
-

grade.cpp

-
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
#include <algorithm>
#include <stdexcept>
#include <vector>
#include "grade.h"

using std::domain_error; using std::istream;
using std::vector; using std::sort;

// grade function 1
double grade(double midterm, double final, const vector<double> &hw)
{
if (hw.size() == 0)
throw domain_error("student has done no homework");
return grade(midterm, final, median(hw));
}

// grade function 2
double grade(double midterm, double final, double homework)
{
return 0.2*midterm + 0.4*final + 0.4*homework;
}

// compute the median of vector<double>
double median(vector<double> vec)
{
// get the size of the vector
typedef vector<double>::size_type vec_size;
vec_size size = vec.size();

// check whether the empty is empty
if (size == 0)
throw domain_error("median of an empty vector");

// sort the grades
sort(vec.begin(), vec.end());

// compute the median homework grade
vec_size mid = size/2;
return size % 2 == 0 ? (vec[mid] + vec[mid - 1])/2 : vec[mid];
}
-

grade.h

-
1
2
3
4
5
6
7
8
9
10
#ifndef GUARD_grade_h
#define GUARD_grade_h

// grade.h
#include<vector>

double grade(double, double, const std::vector<double> &);
double grade(double, double, double);
double median(std::vector<double>);
#endif
- -

Performance test

1
2
3
4
5
6
7
8
9
10
11
12
13
Inputs

Robin 90 87 79 88 81 73 45
Brendan 70 69 88 100 91 75 66
Arsenii 99 87 89 88 74 90 70
Liam 83 66 100 76 87 91 78

Outputs:

Arsenii 89.8
Brendan 76.8
Liam 77.8
Robin 84.4
-

It performs as same as the original program provided in Chapter 4.

-
-

Exercise 4-7

Write a program to calculate the average of the numbers stored in a vector.

-

Solution & Results

It’s a simple project and the solution can be divided into three steps

-
    -
  1. define a function to read and store data into a vector.
  2. -
  3. define a function to compute the average value.
  4. -
  5. set precision to format the double value of outputs.
  6. -
-

All details can be found in other exercises and hence no more explinations here.

-

Below shos the only file including both functions and main function.

-
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
#include <iostream>
#include <vector>
#include <stdexcept>
#include <iomanip>

using std::cin; using std::cout;
using std::endl; using std::domain_error;
using std::vector; using std::istream;
using std::streamsize; using std::setprecision;

// define function to read data into a vector
istream &read(istream &is, vector<double> &doubleValues)
{
double number;
while(is >> number)
doubleValues.push_back(number);
return is;

}

// define function to compute the average of a data sequence
double average(const vector<double> &doubleValues)
{
if (doubleValues.size() == 0)
throw domain_error("An empty vector");
typedef vector<double>::size_type vec_size;
double sum = 0;
for (vec_size i = 0; i != doubleValues.size(); ++i)
sum += doubleValues[i];
return sum/doubleValues.size();
}

int main()
{
vector<double> doubleValues;
read(cin, doubleValues);
try{
double averageValue = average(doubleValues);
streamsize prec = cout.precision();
cout << setprecision(3) << averageValue
<< setprecision (prec) << endl;
}catch(domain_error){
cout << "You must enter at least 1 number. Pleast try again.";
}
return 0;
}
-

Test

-
1
2
3
4
5
Input: 
1.2 5.89 9.33 12.7 7.8 4

Output:
6.82
- -
-

Exercise 4-8

If the following code is legal, what can we infer about the return type of f?

-
1
double d = f()[n];
-

Solution & Results

From the code, we can infer

-
    -
  1. f()[n] is possible a value of type int or float or double or bool.
  2. -
  3. f() supports accessing the element using subscript and hence it could be a container.
  4. -
  5. f() has the form of a function.
  6. -
-

According to above analysis, f() could be a function that returns a container which can hold values of types listed above. As far as I know, the container is vector and returns a type of vector(inside types including int, float, double and bool).

-

To verify my analysis, I wrote a simple program shown as below

-
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>
#include <vector>

using std::cout;
using std::cin;
using std::vector;
using std::endl;

// funtion to return a vector<double>
vector<double> f(vector<double> number)
{
number.push_back(11);
number.push_back(20.5);
return number;
}

int main(){
vector<double> number;
double x = f(number)[0];
double y = f(number)[1];
cout << x << endl;
cout << y << endl;
return 0;
}
-

It works as expected and gives results

-
1
2
11
20.5
-

If I change the type double to any of types listed above, the program works fine.

-
-

Reference

Koenig, A. and Moo, B.E., 2000. Accelerated C++.

-

Lippman, S.B., Lajoie, J. and Moo, B.E., 2013. C++ Primer, 5th edt.

- -
+ - - - -
-
-
-

- +
+ -
- +
+ + +
-

- - -

+ + + +

+ +

+
+ @@ -872,174 +1099,182 @@

-

Exercise 4-0

Compile, execute, and test the programs in this chapter

-

Solution & Results

This exercise has been done presented in Organizing programs with functions and Organizing programs with data structures.

-
-

Exercise 4-1

We noted in §4.2.3/65 that it is essential that the argument types in a call to maxmatch exactly. Will the following code work? If there is a problem, how would you fix it?

-
1
2
3
int maxlen;
Student_info s;
max(s.name.size(), maxlen);
- -

Solution & Results

It doesn’t work. There exist two problems here.

-

First, the max function defined in standard header requires that both arguments must have the same type. But in this piece of code, s.name.size() has the type of string::size_type while maxlen is a int type. To fix this, we need to define maxlen as a variable of type string::size_type.

-

Second, it should be initialized as a variable of built-in type(assuming no problem with type int) is undefined if it is not initialized explicitly. For a variable of other types, the default value (if available, otherwise the variable is undefined) depends on how each type defines. I did a simple experiment as follows

-
1
2
3
4
5
6
7
8
9
10
11
#include<iostream>
#include<string>

using std::cout;
using std::string;

int main(){
string::size_type y;
cout << y;
return 0;
}
-

The result is

-
1
4200939
-

with warning

-
1
'y' is used uninitialized in this function
-

The experiment shows that the type of string::size_type doesn’t support default initialization. To fixed this, we need to explicitly initialize it.

-

The correct code should be:

-
1
2
3
string::size_type maxlen = 0;
Student_info s;
max(s.name.size(), maxlen);
-
-

Exercise 4-2, 4-3

4-2: Write a program to calculate the squares of int values up to 100. The program should write two columns: The first lists the value; the second contains the square of that value.Use setw to manage the output so that the values line up in columns.

-

4-3: What happens if we rewrite the previous program to allow values up to but not including 1000 but neglect to change the arguments to setw? Rewrite the program to be more robust in the face of changes that allow i to grow without adjusting the setw arguments.

-

Solution & Result

algorithms

Exercise 4-3 is a generalized version of 4-2. Specifically, the program is required to write two colomns as follows

-
1
2
3
4
5
6
7
8
9
  0        0
1 1
2 4
3 9
... ...
99 9801
100 10000
... ...
n n*n
-

Each line contains an integer value followed by the square of the integer. The range of integers in the first column starts from 0 to 1000 (excluded). Most importantly, each line should be formated such that the values line up in columns.

-

The key to the solution is to find the longest number in each column. Then, we can set the width of the first column as the number of digits of the corresponding longest number. Analogously, the width of the second column will be set as the number of digits of the longest number in it, with an additional space to seperate from the first column.

-

For an ascending sequence, the longest numbers in both columns depend on the largest number. For exercise 4-2, the largest number is 100 and 10000 (square of 100) in the first and second column, respectively. We can write and format each line as below shows

-
1
2
3
4
5
for (int i = 0; i != 101; ++i)
{
// 100 has three digits and 10000 has 5 digits, 1 additional space for seperate two columns
cout << setw(3) << i << setw(6) << i*i << endl;
}
-

Exercise 4-3 requires more flexibility such that no needs to change setw arguments when the largest number changes. Naturally, the key is to compute the number of digits of a user-defined largest number, e,g. defined as maxNum.

-
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
// hold the number of digits of maxNum and its square
int n = 0;
int m = 0;

// hold the values of the largest number and its square
int j = maxNum;
int k = maxNum*maxNum;
// bounds check
if (j >= 1000 || j < 0)
{
cout << "Enter a number greater than or equal to 0 and less than 1000. Please try again ";

return 1;
}

// computations
if (j == 0)
{
m = n = 1;
}
else
{
// loop invariant: we have counted n digits of the value j
while (j != 0)
{
// each operation j reduces one digit
j /= 10;

// maintain the loop invariant
++n;
}

// loop invariant: we have counted n digits of the value j
while (k != 0)
{
k /= 10;
++m;
}
}
-

organize the program with functions

Obviously, above code is partly repeated. I’ll rewrite the computations as a function which returns the number of digits of an entered value.
width.cpp

-
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
// function to compute the number of digits of an integer value
#include "width.h"

int width(int num)
{
if (num == 0)
return 1;
else
{
int n = 0;
while(num != 0)
{
num /= 10;
++n;
}
return n;
}
}
-

width.h

-
1
2
3
4
5
#ifndef GUARD_width_h
#define GUARD_width_h

int width(int);
#endif
-

mainfunction.cpp

-
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
#include <iostream>
#include <iomanip>
#include "width.h"

using std::cin; using std::cout;
using std::endl; using std::setw;

int main()
{
// asks to enter the upper bound
cout << "Enter a number greater than or equal to 0 and less than 1000: ";

// read the largest number
int maxNum;
cin >> maxNum;

// bounds check
if (maxNum >= 1000 || maxNum < 0)
{
cout << "The entered value is beyond the allowed value range. Please try again.";
return 1;
}

// write the outputs
for (int i = 0; i != maxNum + 1; ++i)
{
cout << setw(width(maxNum)) << i
<< setw(width(maxNum*maxNum) + 1) << i*i << endl;
}
return 0;
}
-

Test performance

Test 1: the upper limit is 10

-
1
2
3
4
5
6
7
8
9
10
11
12
Enter a number greater than or equal to 0 and less than 1000: 10
0 0
1 1
2 4
3 9
4 16
5 25
6 36
7 49
8 64
9 81
10 100
-

Test 2: the upper limit is 101

-
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
Enter a number greater than or equal to 0 and less than 1000: 101
0 0
1 1
2 4
3 9
.. ..
90 8100
91 8281
92 8464
93 8649
94 8836
95 9025
96 9216
97 9409
98 9604
99 9801
100 10000
101 10201
-

Test 3: the upper limit is 1000

-
1
2
Enter a number greater than or equal to 0 and less than 1000: 1000
The entered value is beyond the allowed value range. Please try again.
-

It can be seen from these tests that the program perform as expected.

-
-

Exercise 4-4

Now change your squares program to use double values instead of ints. Use manipulators to manage the output so that the values line up in columns

-

Solution & Results

The difference between this exercise and last exercise is that the function width defined above can not compute the number of digits of a double value. Admittedly, I didn’t found very good strategy to complete this project. I circument the problem applying the type conversion technique. Specifically

-
    -
  1. divide the maxNum/maxNum*maxNum into integer part and fractional part.
  2. -
  3. compute the number of digits of the integer part using same function as shown above.
  4. -
  5. control the fractional part with an additional user-defined variable places which represents the number of places after the dot point.
  6. -
-

The arguments of *setw for the first column is:

-
1
width(maxNum) + 1 + places
-

The number 1 leaves room for the decimal point.

-

The arguments of *setw for the first column is:

-
1
width(maxNum*maxNum) + 1 + 1 + places
-

One of two values of 1 is added for the decimal point while the other one is added for seperating from the first column. I didn’t change anything in the width function described above and hence the arguments to be passed are converted to int type, leading to that the returned value of width is the width of the part before the decimal point.

-

Beyond this, the program asks to enter an initial value and an value of the increment for numbers of the first column. For example, we set the initial value as 0.0 and increment as 0.5, the first column becomes

-
1
2
3
4
5
0.0
0.5
1.0
...
n
-

where n is the largest number (i.e. maxNum) that available for outputs in the range of [0.0, 1000). It will be computed in the program.

-

To format outputs, I uses function fixed together with setprecision to fixed number of decimal places and showpoint to enable the display of trailing 0. A detailed comparison between these three can be found in C++ - Working with batches of data.

-

Please find the midth.cpp and midth.h in exercise 4-2/3. The main function file is shown below.

-
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
// Accelerated C++ Solutions Exercises 4-4
#include <iostream>
#include <iomanip>
#include "width.h"

using std::cin; using std::endl;
using std::cout; using std::setw;
using std::setprecision; using std::fixed;
using std::streamsize; using std::showpoint;
using std::noshowpoint;

int main()
{
// asks toset the range for outputs
double minNum, incrementByValue;
cout << "Enter an intial value greater than or equal to 0.0 and less than 1000.0: ";
cin >> minNum;
cout <<"Enter the increment for each line of outputs: ";
cin >> incrementByValue;

// bounds check
if (minNum >= 1000.0 || minNum < 0.0)
{
cout << "The entered value is beyond the allowed value range. Please try again.";
return 1;
}

// asks to enter a value that determines decimal places
cout << "How many places you want to keep after decimal point?\n"
"Enter an integer to determine decimal places: ";
int places;
cin >> places;

// get the largest value that available for outputs
double maxNum = minNum;
while ((maxNum + incrementByValue) < 1000.0)
{
maxNum += incrementByValue;
}

// write the outputs
for (double i = minNum; i < 1000.0; i += incrementByValue)
{
streamsize prec = cout.precision();
cout << showpoint << fixed << setprecision(places)
<< setw(width(maxNum) + places + 1) << i
<< setw(width(maxNum*maxNum) + places + 2) << i*i
<< setprecision(prec) << noshowpoint << endl;
}
return 0;
}
-

It is worth noting that we cannot use equality and inequality operators in the floating point value conditions as floating values cannot be precisely represented in the computer world. Due to this limitation, I change the condition i != 1000 to i < 1000.0. I did several tests to show how is the performance.

-

Test 1

-
1
2
3
4
5
6
7
8
9
10
11
12
13
14
Enter an intial value greater than or equal to 0.0 and less than 1000.0: 0
Enter the increment for each line of outputs: 100
How many places you want to keep after decimal point?
Enter an integer to determine decimal places: 0
0. 0.
100. 10000.
200. 40000.
300. 90000.
400. 160000.
500. 250000.
600. 360000.
700. 490000.
800. 640000.
900. 810000.
-

Test 2

-
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
Enter an intial value greater than or equal to 0.0 and less than 1000.0: 0.5234
Enter the increment for each line of outputs: 0.5
How many places you want to keep after decimal point?
Enter an integer to determine decimal places: 4
0.5234 0.2739
1.0234 1.0473
1.5234 2.3207
2.0234 4.0941
2.5234 6.3675
3.0234 9.1409
3.5234 12.4143
4.0234 16.1877
4.5234 20.4611
5.0234 25.2345
5.5234 30.5079
6.0234 36.2813
6.5234 42.5547
.... ....
996.0234 992062.6133
996.5234 993058.8867
997.0234 994055.6601
997.5234 995052.9335
998.0234 996050.7069
998.5234 997048.9803
999.0234 998047.7537
999.5234 999047.0271
- -

Test 3

-
1
2
3
4
5
6
Enter an intial value greater than or equal to 0.0 and less than 1000.0: 999
Enter the increment for each line of outputs: 1
How many places you want to keep after decimal point?
Enter an integer to determine decimal places: 1
999.0 998001.0
```
- -
-

Exercise 4-5

Write a function that reads words from an input stream and stores them in a vector. Use that function both to write programs that count the number of words in the input, and to count how many times each word occurred.

-

Solution & Results

This exercise is a variant of Chapter 3 Exercise 3-3. I uses exactly the same solution strategy in this project. Therefore, no more discussion here. The code and tests can be found below.

-

mainfunction.cpp

-
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
#include <iostream>
#include <string>
#include <vector>
#include <iomanip>
#include <stdexcept>
#include "wordsRead.h"

using std::cin; using std::string;
using std::cout; using std::vector;
using std::endl; using std::setw;
using std::domain_error;

int main()
{
// hold the information of each words
vector<words_info> words;

// type alias
typedef vector<words_info>::size_type vec_size;
typedef string::size_type str_size;
str_size word_size = 0;

// count the number of words
int totalNum = 0;

// asks to enter words
cout << "Please enter words: ";


try{
// read, count and store words
wordsRead(cin, words, totalNum, word_size);

// write the total number of inputs
cout << "The total number of words is: " << totalNum << endl;

// write each distinct word and its occurrence number
for (vec_size i = 0; i != words.size(); ++i)
{
// format each line
int n = 9 + word_size - words[i].wordName.size();
cout << words[i].wordName << setw(n) << " appears "
<< words[i].count << " times"<< endl;
}

}catch(domain_error){
cout << "You must enter at least one word. Please try again.";
}
return 0;
}
- -

wordsRead.cpp

-
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
// function to read, count and store words
#include <iostream>
#include <string>
#include <vector>
#include <stdexcept>
#include "wordsRead.h"

using std::istream; using std::domain_error;
using std::string; using std::vector;

istream & wordsRead(istream &is, vector<words_info> &words, int &totalNum, string::size_type &word_size)
{
words_info word;

// loop invariant: we have read totalNum words now
while(is >> word.wordName)
{
// maintain the loop invariant
++totalNum;

// find the size of the longest word
if (word_size < word.wordName.size())
word_size = word.wordName.size();

// set flag to find each distinct word
// flag == true: the word is distinct
// flag == false: the word exists
bool flag = true;
for (vector<words_info>::size_type i = 0; i != words.size(); ++i)
{
// compare with previous distinct words
if (word.wordName == words[i].wordName)
{
++words[i].count;
flag = false;
}
}

// if the word has not been entered, store
if (flag == true)
{
word.count = 1;
words.push_back(word);
}

}

if (totalNum == 0)
throw domain_error("No input");
return is;
}
-

wordsRead.h

-
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
#ifndef GUARD_wordsRead_h
#define GUARD_wordsRead_h

#include <iostream>
#include <string>
#include <vector>

struct words_info{
std::string wordName;
int count;
};

std::istream & wordsRead(std::istream &, std::vector<words_info> &words, int &, std::string::size_type &);


#endif /* GUARD_wordsRead_h */
- -

Test Performance

Test 1

-
1
2
3
4
5
6
7
8
9
10
11
Please enter words: I am a good teacher and you are a good student
The total number of words is: 11
I appears 1 times
am appears 1 times
a appears 2 times
good appears 2 times
teacher appears 1 times
and appears 1 times
you appears 1 times
are appears 1 times
student appears 1 times
-

Test 2

-
1
2
3
4
5
6
7
8
9
10
11
12
13
Please enter words: what happens depends on the range of the values that the types permit
The total number of words is: 13
what appears 1 times
happens appears 1 times
depends appears 1 times
on appears 1 times
the appears 3 times
range appears 1 times
of appears 1 times
values appears 1 times
that appears 1 times
types appears 1 times
permit appears 1 times
- -
-

To be continued.

+ + + + Previous chapters mainly covers topics including + +main function structure +statements such as expression statements and flow-of-control statements. +bu + ... + +
+ + Read more » + +
+ + +
+ + + + + -
+ +
+ + + + + + + +
-
+ +
+

+ + +
+ + + + + + + - +
-
- + +
+ + +
-

- - -

+ + + +

+ +

+
+ @@ -1047,330 +1282,178 @@

-

The program we have accomplished in last chapter is good enough for computing one students’ final grade, however, is unpractical in reality when it comes to generating a final grade report for a class. Assuming that we have a file that records all students’ information including their names, midterm and final exam grades, and homework grades. For example

-
1
2
3
Robin 90 87 79 88 81 73 45
Brendan 70 69 88 100 91 75 66
...
-

The program is required to compute the final grade for each student and generate a report like

-
1
2
Bredan 76.8
Robin 84.4
-

In specific, there are three requirements

-
    -
  1. in the final grade, the mediter exam grade counts for 20%, the final exam grade counts for 40%, and the median homework grade counts for 40%.
  2. -
  3. the output follows an alphabetical order according to the names.
  4. -
  5. the final grades are vertically aligned.
  6. -
-

A similar program has been done in Exercise 3-5, which can keep track of grades for several students at once though it uses the average homework grade rather than median value. The whole structure is simply a while loop. The program in last chapter teaches us how to fullfill the first requirement with functions. Now we focus on how to meet the second the third requirements.

-

Data struct

Last chapter mainly introdues how to write functions to deal with computations as well as data reading. However, the information such as name, medterm and final exam grades are still left there. If more information such as age, weight and grade need to be added, the program would be bloated. In fact, all these information can be integrated as a user-defined data structures as follows

-
1
2
3
4
5
struct Student_info {
string name;
double midterm, final;
vector<double> homework;
} objectName;
-

The code defines a struct that contains a group of data members. Student_info is the name of this type. Each data member is declared with a type and a name. objectName is an object of such type. Another way to declare an object is

-
1
Student_info objectName;
-

Note that there must be a semicolon at the end of the curly braces when defining a struct type.

-

It has been observed that each object of such type holds information for one student. We can store all students’ information into a vector, e.g. vector record.

-

reading data

The function that an object of Student_info reads data is similar to that for a vector.

-
1
2
Student_info record;
read(cin, record);
-

The read function

-
1
2
3
4
5
6
7
8
9
istream & read(istream &is, Student_info &s)
{
// reads and store the student's name, midterm and final exam grades
is >> s.name >> s.midterm >> s.final;

// reads and store all homework grades
read_hw(is, s.homework);
return is;
}
-

Since the function is similar to the read_hw function defined in this page, no more discussed here.

-

grade function

Now the data have been stored into a sturct and concequently the grade function becomes
overloaded function 1

-
1
2
3
4
double grade(const Student_info &s)
{
return grade(s.meterm, s.final, s.homework)
}
-

overloaded function 2

-
1
2
3
4
5
6
double grade(double midterm, double final, const vector<double> &hw)
{
if (hw.size() == 0)
throw domain_error("student has done no homework");
return grade(midterm, final, median(hw));
}
-

overloaded function 3

-
1
2
3
4
double grade(double midterm, double final, double homework)
{
return 0.2*midterm + 0.4*final + 0.4*homework;
}
-

stores all structs into a vector

Once the record finishes reading data for inputs, we can store it into a vector.

-
1
2
3
4
5
6
7
vector<Student_info> students;
Student_info record;

while(read(cin, record))
{
students.push_back(record);
}
-

alphabetize students

Up to now, we have finished the code for all computations and data reading. The next step is to sort the students in an alphabetical order according to students’ names. In previous chapters, we uses the standard algorithm sort to accomplish sorting the homework. It can also be used to sort the students, but before we apply it we need to learn how it works on the homework.

-

homework is a vector that contains all values of homework grades. The sort function compares objects in the vector using <. It is clear when using < to compare two numerical values but doesn’t works for the element type of a struct. Regarding to this case, the sort function provides an optional argument, a predicate, for us to define the ways to compare elements.

-

A predicate is a function that typically yields a true value of type bool. Let’s see how is it defined

-
1
2
3
4
bool compare(const Student_info &x, const Student_info &y)
{
return x.name < y.name;
}
-

The usage of the sort function is

-
1
sort(students.begin(), students.end, compare);
-

It means that the sort function will compare elements in the students only according to its member name rather than using < directly. As for the effect of < on strings, the expression is evaluated to be true if x.name is alphabetically ahead of y.name. Specifically, when compare two strings

-
    -
  1. the result is the result of comparing the first character at which the strings differ.
  2. -
  3. if all characters of one string equal to the corresponding characters of another string, then the shorter one is less than the longer one.
  4. -
-

align the final grade vertically

Now we deal with the third requirement. Each line of outputs is formed by s.name, a blank string, and the grade. The key to solve this problem is to write a blank string with appropriate length such that all lines have same total length ahead of the final grades while the total length depends on the longest name. The minimum number of spaces between a name and a grade is one. The process can be logically divided into three steps

-
    -
  1. find the longest name
  2. -
  3. calculate the total length ahead of the grade: the size of the longest name plus one(space).
  4. -
  5. Create a blank string for each line with length: the total length minus the size of each name.
  6. -
-

To find the longest name, we use another the max function defined in the header . The syntax is

-
1
max(el, e2);
-

It returns the larger one of two expressions which yield values of the same type. The comparison is similar to Exercise 3-4 strategy 2.

-

A complete program

Above steps show the core technicals that deals with three requirements mentioned at the begining. The new program built on struct and functions is presented as below

-
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
int main()
{
vector<Student_info> students;
Student_info record;
string::size_type maxlen = 0;

// read and store all the records, and find the length of the longest name
while(read(cin, record))
{
maxlen = max(maxlen, record.name.size());
students.push_back(record);
}

// alphabetize the records
sort(students.begin(), students.end(), compare);

// write each line of outpurs
for (vector<Student_info>::size_type i = 0; i != students.size(); ++i)
{
// write the name, blanks
cout << students[i].name << string(maxlen + 1 - students[i].name.size(), ' ');

// compute and write the final grade
try{
double final_grade = grade(students[i]);
streamsize prec = cout.precision();
cout << setprecision(3) << final_grade << setprecision(prec);
} catch(domain_error e){
cout << e.what();
}
cout << endl;
}
return 0;
}
-

In this program, we uses a new function what to write the diagnostic message if an exception is thrown. The catch clause named the diagnostic message as e, i.e. the object that contains the message. The message can be obtained from what().

-

Seperate compilation

Strictly speaking, above program is not a complete program as it doesn’t work when it is executed. Because we haven’t add the functions to be called in this program. It can be done by putting all stuff into a single file, which however may increase complexity and reduce readability. Alternatively, we can seperate the program into several files and compile these files seperately. In fact, we uses seperate compilation since the first program. For example, we can use IO class objects by means of including the header and declarations rather than defining the type in our programs. How this is done? Let’s write our own header files!

-

header file and source file

To support seperate compilation, C++ distinguishes declarations and definitions which allows muliple files sharing one definition. For example, if we want to seperate the median function from above program, we need to put its definition into a source file named median.cpp (depending on your c++ implementations), and put its declarations into a header file named median.h. By doing so, the median function is allowed to be accessed in programs as long as we include its header file like

-
1
#include "median.h"
-

The header file is enclosed by double quotes rather than angle brackets, which makes it distinct from the standard library headers. The source file is created as follows

-
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
// source file for median function 
#include <algorithm> // to get declaration of sort
#include <stdexcept> // to get declaration of domain_error
#include <vector> // to get declaration of vector
#include <median.h>

// declarations for names
using std::domain_error;
using std::vector;
using std::sort;

// compute the median of vector<double>
double median(vector<double> vec)
{
// get the size of the vector
typedef vector<double>::size_type vec_size;
vec_size size = vec.size();

// check whether the empty is empty
if (size == 0)
throw domain_error("median of an empty vector");

// sort the grades
sort(vec.begin(), vec.end());

// compute the median homework grade
vec_size mid = size/2;
return size % 2 == 0 ? (vec[mid] + vec[mid - 1])/2 : vec[mid];
}
-

Note this file includes all needed headers for the median function itself. It contains both the function declarations and definitions, which allows the compiler to check the consistency between the declarations and definitions. The header file can be written as

-
1
2
3
4
5
6
7
#ifndef GUARD_median_h
#define GUARD_median_h

#include<vector>
double median(std::vector<double>);

#endif
-

There are several new points here. First, the file include the needed header , and use std::vector instead of using std::vector. This is because we are not sure whether a user want a using declaration in their program as once we add using declaration, all programs that include this header file get a using std::vector. This has been also emphasized in C++ - Getting Started.
Second, #ifndef directive responsible for checking whether GUARD_median_h is defined. GUARD_median_h (aka. header guard) is the name of a preprocessor variable that has two status: defined or not defined. The #define directive takes the name and defines it as a preprocessor variable. ifndef is true if the preprocessor variable is undefined and the preprocessor will process following contents until encounter endif. If ifndef is false, subsquent attempts to include median.h will be overlooked to avoid multiple inclusion.

-

Reorganize the final grade program

The reminder of this post aims to reorganize the final grade program applying the technical, seperate compilation,introduced in this post. Let’s list all functions and data structures needed in this program.

-
    -
  1. read function to read students’ information.
  2. -
  3. read_hw function to read homework grades for each student.
  4. -
  5. compare function as an optional argument in sort.
  6. -
  7. three grade functions(overloaded) to compute the final grade.
  8. -
  9. median function to compute median of homework grades.
  10. -
  11. Beyond above functions, we also defined a data structure, Student_info to hold student’s information.
  12. -
-

Logically speaking, these entities can be divided into two groups:

-
    -
  • group 1 including 6, 1, 2, 3 deals with information
  • -
  • group 2 including 4, 5 deals with computation
  • -
-

Therefore, we can package two groups into two independent files seperately.

-

Group 1

Student_info.h

-
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
#ifndef GUARD_Student_info
#define GUARD_Student_info

// Student_info.header file
#include<iostream>
#include<string>
#include<vector>

struct Student_info{
std::string name;
double midterm, final;
std::vector<double> homework;
};

bool compare(const Student_info &, const Student_info &);
std::istream & read(std::istream &, Student_info &);
std::istream & read_hw(std::istream &, std::vector<double> &);
#endif
-

Student_info.cpp

-
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
// source file for Student_info related functions
#include "Student_info.h"
using std::vector; using std::istream;

bool compare(const Student_info &x, const Student_info &y)
{
return x.name < y.name;
}

istream & read(istream &is, Student_info &s)
{
// reads and store the student's name, midterm and final exam grades
is >> s.name >> s.midterm >> s.final;

// reads and store all homework grades
read_hw(is, s.homework);
return is;
}

istream & read_hw(istream &in, vector<double> &hw)
{
if (in){
//get rid of previous contents
hw.clear();

// read homework grades
double x;
while(in >> x)
hw.push_back(x);

// clear the stream so that input will work for the next student
in.clear();
}
return in;
}
-

Group 2

grade.h

-
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
#ifndef GUARD_grade_h
#define GUARD_grade_h

// grade.h
#include<vector>
#include "Student_info.h"

double grade(const Student_info &);
double grade(double, double, const std::vector<double> &);
double grade(double, double, double);
double median(std::vector<double>);
#endif

**grade.cpp**
```c++
#include <algorithm>
#include <stdexcept>
#include <vector>
#include "grade.h"
#include "student_info.h"

using std::domain_error; using std::istream;
using std::vector; using std::sort;

// grade function 1
double grade(const Student_info &s)
{
return grade(s.midterm, s.final, s.homework);
}

// grade function 2
double grade(double midterm, double final, const vector<double> &hw)
{
if (hw.size() == 0)
throw domain_error("student has done no homework");
return grade(midterm, final, median(hw));
}

// grade function 3
double grade(double midterm, double final, double homework)
{
return 0.2*midterm + 0.4*final + 0.4*homework;
}

// compute the median of vector<double>
double median(vector<double> vec)
{
// get the size of the vector
typedef vector<double>::size_type vec_size;
vec_size size = vec.size();

// check whether the empty is empty
if (size == 0)
throw domain_error("median of an empty vector");

// sort the grades
sort(vec.begin(), vec.end());

// compute the median homework grade
vec_size mid = size/2;
return size % 2 == 0 ? (vec[mid] + vec[mid - 1])/2 : vec[mid];
}
-

Test

1
2
3
4
5
6
7
8
9
10
11
12
13
Inputs:

Robin 90 87 79 88 81 73 45
Brendan 70 69 88 100 91 75 66
Arsenii 99 87 89 88 74 90 70
Liam 83 66 100 76 87 91 78

Outputs:

Arsenii 89.8
Brendan 76.8
Liam 77.8
Robin 84.4
+ + + + + + Exercise 3-5Write a program that will keep track of grades for several students at once. The program could keep two vectors in sync: The first should + ... + +
+ + Read more » + +
+ +
+ + + + + -
+ +
+ + + + + + + +
-
+ +
+

+ + +
+ + + + + + + - +
-
- + +
+ + +
-

- - -

+ + + +

+ +

+ -
- - - - -
- - -

Previous chapters mainly covers topics including

-
    -
  1. main function structure
  2. -
  3. statements such as expression statements and flow-of-control statements.
  4. -
  5. built-in types, such as int, float, double, bool and char.
  6. -
  7. standard library IO mechanism.
  8. -
  9. standard library string.
  10. -
  11. standard library vector.
  12. -
-

We have achieved several goals through certain statements and operations on objects of different types. However, the program becomes unmanageable along with increasingly complex functions and growing information. For this reason, this chapter introduces how to organize programs and data.

-

Functions

Basics

writing a function

If we break a program(e.g. A complete program) into pieces, we found that it is in fact constituted by data information, homework grade computation and final grade computation. Both computations can be organized as a function, which is a named block of code. The functions will be called When the computation results are needed. Let’s start with writing a function to compute the final grade, assuming that the homework grade has been computed.

-
1
2
3
4
5
// compute the final grade of a student
double grade(double midterm, double final, double homework)
{
return 0.2*midterm + 0.4*final + 0.4*homework;
}
-

Basically, it has the same structure as the main function except that we use empty parameter list in previous main function.

-

In general, a function includes four parts:

-
    -
  1. return type. In this case, it has return type of double.
  2. -
  3. function name. In this case, the function is names as grade.
  4. -
  5. parameter list enclosed in parentheses (). In this case, there are three parameters seperated by commas. All three parameters have type of double. They are defined like variables but only be created when the function is called.
  6. -
  7. function body enclosed in curly braces {}. The return statements returns the result to function caller.
  8. -
-

calling a function

When calling the function, the excution of function caller is suspended and execution of the called function begins. We must supply corresponding arguments for the purpose of initializing the parameters. In other words, arguments are the initializers for a function’s parameters. Arguments can be variables or expressions or even values. But they must be provided in the same order as well as the same type as the parameters. If we replace the computation in the original program, it would be like

-
1
2
cout << "You final grade is " << setprecision(3)
<< grade(midterm, final, sum/count) << setprecision(prec) << endl;
-

The first parameter midterm will be initialized by copying the value of argument midterm into it. So do the other parameters. This is what so called call by value. Essentially, these parameters are created in an area independent from the variables in the calling function though they have the same values. Therefore, if the function manipulate these parameters, it wouldn’t change values of the calling function variables. In addition, the parameters are local to the function and only exist start from calling the function to returning from the function. Therefore, it doesn’t matter that we use same name as the variable in the calling function.

-

Once the execution encounters the return statement in the function body, the execution of the function ends and back to the calling function.

-

Writing a median function

Now we consider writing a median function that computes the median value of the homework grades. Let’s list four parts of a median function:

-
    -
  1. the return type should be double.
  2. -
  3. it is named as median for clarity.
  4. -
  5. what we need for computation is only a vector of double type (assuming that we have read all grades).
  6. -
  7. computing algorithms.
  8. -
-

It’s pretty straightforward

-
1
2
3
4
double median(vector<double> vec)
{
// algorithms to be written
}
-

The algorithm in the original program is

-
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
// get the size of the vector
typedef vector<double>::size_type vec_size;
vec_size size = homework.size();

// check special case
if (size == 0)
{
cout << endl << "You must enter your grades. "
"Please try again." << endl;
return 1;
}

// sort the grades
sort(homework.begin(), homework.end());

// compute the median homework grade
vec_size mid = size/2;
double median;
median = size % 2 == 0 ? (homework[mid] + homework[mid - 1])/2 : homework[mid];
-

To write this piece of code into the function, we need to first change the variable name homework to vec as this function suites for more general cases. Nevertheless, you don’t have to do it if you dislike. The second step is to remove the variable median and add return because what this function need to do to return the median value. The last step is to change the code that deals with the case of empty vector.

-
1
2
if (size == 0)
throw domain_error("median of an empty vector");
-

This is because the original code can not be used here due to it returns another value 1 (unless we change the function structure). In real word programming, throw an exception is a more general way to complain. The usage is explained in next part. Now the function is accomplished as shown below

-
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
// function to compute the median of a vector<double>
double median(vector<double> vec)
{
// get the size of the vector
typedef vector<double>::size_type vec_size;
vec_size size = vec.size();

// check whether the empty is empty
if (size == 0)
throw domain_error("median of an empty vector");

// sort the grades
sort(vec.begin(), vec.end());

// compute the median homework grade
vec_size mid = size/2;
return size % 2 == 0 ? (vec[mid] + vec[mid - 1])/2 : vec[mid];
}
-

try blocks and Exception handling

“Exceptions are run-time anomalies—such as losing a database connection or encountering unexpected input—that exist outside the normal functioning of a program.” - Lippman etc. 2012

-

throw expressions

throw expressions is used to detect the exceptions, which is followed by an exception onject that describes the problems that it encounters. It stops the execution of the current function and passes an exception object to the caller for handling it.

-
1
2
3
4
// the detecting part
if (size == 0)
// throw raises exceptions
throw domain_error("median of an empty vector");
-

For example, the exception object domian_error contains the information of that the caller can use to act on the exception. It is a type that the standard library defines in header for use in reporting the logic error: argument is out side the values that the function can accept. What closely follows is a string enclosed by parentheses to describe the problem.

-

the try block

The try block is the handling part uses to deal with an exception. Once the exception is thrown, it catches the exception and handle it according to the type of the exception object. The general syntax is

-
1
2
3
4
5
6
7
try{
// statements including the detecting part
} catches(exception object1){
// handler-statements
} catches(exception object2){
// handler-statements
} ...
-

The catch clause handles the exception and hence is termed as “exception handler”. If the statements between try and catch don’t throw any exceptions during execution, the program ignores the handler-statements and continue to next part.

-

It is worthing noting that each pair of curly braces forms a name scope. The application of the try block will be finished at the end of this post.

-

Finish the grade function

Now we can embed the median function in the grade function.

-
1
2
3
4
5
6
7
// function to compute the final grade which is the weighted average grade of medterm exam grade, final exam grade and the median homework grade
double grade(double midterm, double final, const vector<double> &hw)
{
if (hw.size() == 0)
throw domain_error("student has done no homework");
return grade(midterm, final, median(hw));
}
-

reference and call by reference

It has been noted that this grade function differs from the previous one mentioned at the beigining in the part of parameter list. The third parameter here has a compound type with modifier reference. Recalling that to declare a variable needs a type and a name. More generally, a declaration is a base type followed by a list of declarators including a name and an optional type modifier.

-
1
2
3
4
5
    base type modifier name 
```
A variable declared in above form has a type named **compound type** which is built from the base type. In this case, the third parameter has a type of **vector<double>** with modifier **reference** which indicates that the onject named **hw** refers to its **initializer**. In other words, a reference is a **alias** and **hw** is simply another name for the argument to be passed. In addition, a reference to a reference is in fact that both references refer to the original object. For example
```c++
vector<double> &hw1 = hw; // hw1 is another name for the vector homework
-

In contrast to call by value, this is termed as call by reference. When we operate on a reference, we actually operate on the object that the reference refers. For the purpose of computational effiency, passing argument by reference can avoid copies, particularly for objects of large containers or class types. But, it is not a good habit to modify the value of the object that the reference refers. It’s complete ok in this case as the object is passed by copy in the median function where we will operate on the homework grades. Beyond this, there is a const qualifier before the reference, which restrict the values of the object to be changed when operating on the reference.

-

overloaded function

Recalling the previous grade function

-
1
2
3
4
5
6
```c++
// compute the final grade of a student
double grade(double midterm, double final, double homework)
{
return 0.2*midterm + 0.4*final + 0.4*homework;
}
-

These two functions have the same name but different parameter list. This is termed as function overloading with either different types or numbers of parameters. If two function only differs in return type, functions can not be overloaded. When calling the overloaded function, the complier determines which function to call according to the supplied arguments and the defined parameters in each funciton.

-

Writing a reading function

Finally, we need to solve the problem that how to read home work grades into a vector. The oringal code is

-
1
2
3
4
5
6
double x;
vector<double> homework;

// enter homework grades followed by end-of-file
while(cin >> x)
homework.push_back(x);
-

So, what’s this function should return?
Obviously, the purpose is to fill the vector homework and therefore it should return a filled vector. Beyond this, the function is required to return another value to the stream to indicate whether the attempted input was successful.
Intuitively, it works like this:

-

Funtion work flow

-

But it is hard to deal with two returns in one function and alternatively we can define a parameter as a reference type for the purpose of changing the values in homework directly. See the code below

-
1
2
3
4
5
6
// read the homework grades from an input stream into a vector homework
istream & read_hw(istream &in, vector<double> &hw)
{
// statements to be filled
return in;
}
-

lvalue

We are familar with the second parameter which refers to its initializer to be passed, i.e. the vector homework. Since we intend to modify the passed arguments, the const qualifier has been dropped. There is an important difference between a const reference and a nonconst reference. For a const reference, the arguments to be passed can be any value while a non const reference can only refer to a lvalue object (i.e. a nontemporary object). Any expressions that generate arithmetic values are not lvalue. For example

-
1
2
3
4
int i = 10;
int &j = i; // correct: j is bound to i
int &m = 10; // error: initializer must be an nontemporary object
const int &n = 10; // correct: a const reference
-

member function clear

The first parameter is also a type of non-const reference which refers to the object cin. This is because we hope to change its internal state. As a result, the return type is also a reference as in is a reference. Another reason is that there is no copy or assign for IO objects.

-

Now we consider read entered grades into homework. Remember that We propose to write a program that can deal with multiple students’ records. One problem is that the vector might contain the grades of the last student. To keep the vector empty, we use hw.clear() to discard any contents the vector might have had.

-

Similarly, we also need to keep the cin be valid for each student. In previous chapter, We have explained that once we finishes typing in the homework grades a signal end-of-file needs to be sent for terminating the loop. The signal will change the internal state of the cin to be false. In addition, end-of-file is not the only input that can stop the loop. If we enter values of an improper type, the library would mark the input stream as being in failure state as well. For this reason, we use in.clear() to clear the error state of cin after finishing the input for one student. Note that both the vector and the object of istream have member function clear but the effects are completely different. The function is shown below

-
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
// read homework grades from an input stream inti a vector
istream & read_hw(istream &in, vector<double> &hw)
{
if (in){
//get rid of previous contents
hw.clear();

// read homework grades
double x;
while(in >> x)
hw.push_back(x);

// clear the stream so that input will work for the next student
in.clear();
}
return in;
}
-

A complete program

Up to now, we have changed the computations in the original program to functions including a function to homework grades, a function to calculate the median of homework grades and a function to calculate the final grade. A complete program is presented below

-
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
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
// include directives
#include<iostream> // to get declaration of cin, cout, endl,
#include<istream> // to get declaration of istream
#include<string> // to get declaration of string
#include<vector> // to get declaration of vector
#include<algorithm>// to get declaration of sort
#include<ios> // to get declaration of streamsize
#include<stdexcept>// to get declaration of domain_error
#include<iomanip> // to get declaration of setprecision

// add using declarations
using std::cin; using std::setprecision;
using std::cout; using std::streamsize;
using std::endl; using std::domain_error;
using std::string; using std::istream;
using std::vector; using std::sort;

// declare functions
istream & read_hw(istream &in, vector<double> &hw);
double median(vector<double> vec);
double grade(double midterm, double final, double homework);
double grade(double midterm, double grade, const vector<double> &hw);

// main function
int main()
{
// ask for and read the student's name
cout << "Please enter your first name: ";
string name;
cin >> name;
cout << "Hello, " << name << "!" << endl;

// ask for and read the midterm and final grades
cout << "Please enter your midterm and final exam grades: ";
double midterm, final;
cin >> midterm >> final;

// ask for the homework grades
cout << "Enter all your homework grades, followed by end-of-file: ";

// read the homework grades
vector<double> homework;
read_hw(cin, homework);

// compute and generate the final grade, if possible
try {
double final_grade = grade(midterm, final, homework);
streamsize prec = cout.precision();
cout << "Your final grade is " << setprecision(3)
<< final_grade << setprecision(prec) <<endl;
} catch(domain_error) {
cout << endl << "You must enter your grade. Please trt again." << endl;
return 1;
}
return 0;
}

// define function to read homework grade
istream & read_hw(istream &in, vector<double> &hw)
{
if (in){
//get rid of previous contents
hw.clear();

// read homework grades
double x;
while(in >> x)
hw.push_back(x);

// clear the stream so that input will work for the next student
in.clear();
}
return in;
}

// define function to calculate median value
double median(vector<double> vec)
{
// get the size of the vector
typedef vector<double>::size_type vec_size;
vec_size size = vec.size();

// check whether the empty is empty
if (size == 0)
throw domain_error("median of an empty vector");

// sort the grades
sort(vec.begin(), vec.end());

// compute the median homework grade
vec_size mid = size/2;
return size % 2 == 0 ? (vec[mid] + vec[mid - 1])/2 : vec[mid];
}

// define a function to calculate final grade
double grade(double midterm, double final, const vector<double> &hw)
{
if (hw.size() == 0)
throw domain_error("student has done no homework");
return grade(midterm, final, median(hw));
}

//define a function to calculate final grade (function overloading)
double grade(double midterm, double final, double homework)
{
return 0.2*midterm + 0.4*final + 0.4*homework;
}
- -

See from above program, why the statements inside the try block are not organized as such form

-
1
2
3
4
5
6
7
try {
streamsize_prec = cout.precision();
cout << "Your final grade is " << setprecision(3)
<< grade(midterm, final, homework)
<< setprecision(prec) <<endl;
} catch...
...
-

In doing so, we probably can’t control the outputs as the grade function may be called after or before the string literals depending on the implementation. Also, if any exception is thrown, the precision may not be reset back to the original value as expected.

-
-

To be continued.

- - -
- - - - -
-
-
-
- - - - - - - -
- + + + - + + - -
-

- -

-
+ @@ -1378,122 +1461,179 @@

-

Exercise 3-5

Write a program that will keep track of grades for several students at once. The program could keep two vectors in sync: The first should hold the student’s names, and the second the final grades that can be computed as input is read. For now, you should assume a fixed number of homework grades.

-

Solution & Results

The exercise tries to make the original program (see the first program) more practical. There are two more requirements compared with the original one: first, it requires computing the final grades for several students at once; second, it requires keep tracking both the students’ name and their final grades.

-

To meet the first requirements, we can add a while loop which allows us compute the final grades multiple times for different student. The condition will be an variable whose value depends on users. Let’s finish this first

-
1
2
3
4
5
6
7
8
9
10
11
12
13
14
// set initial status
bool running = true;
while(running)
{
// compound statements


char flag;
cout << "Do you want to check more students? Please input Y/N: ";
cin >> flag;

if (flag == 'Y') ;
else running = false;
}
-

To meet the second requirement, we can store both names and the final grades into two vectors.

-
1
2
vector<string> names;
vector<double> finalGrades;
-

Each time we store a student’s name, we will store his final grade after computation. When needs writing the outputs, we can use same index for both vectors as there is a one-to-one correspondence.

-

Below is the modified 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
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
#include <iomanip>
#include <ios>
#include <iostream>
#include <string>
#include <vector>

using std::cin; using std::setprecision;
using std::cout; using std::string;
using std::endl; using std::streamsize;
using std::vector;

int main()
{
vector<string> names;
vector<double> finalGrades;
bool running = true;
while(running)
{
// ask for and read the student's name
cout << "Please enter your first name: ";
string name;
cin >> name;

// store the name into names
names.push_back(name);
cout << "Hello, " << name << "!" << endl;

// ask for and read the midterm and final grades
cout << "Please enter you midterm and final exam grades: ";
double midterm, final;
cin >> midterm >> final;

// ask for the number of homework grades
cout << "Please enter the number of your homeworks: ";
int numofHomeworks;
cin >> numofHomeworks;

// ask for all homework grades
cout << "Enter all your homework grades: ";

// the number and the sum of grades read so far
int count = 0;
double sum = 0;

// a variable into which to read
double x;

// invariant: we have read count grades so far, and sum is the sum of the first count grades
while (count < numofHomeworks)
{
++count;
cin >> x;
sum += x;
}

// compute the fianl grade and store into finalGrades
double finalGrade = 0.2 * midterm + 0.4 * final + 0.4 * sum/count;
finalGrades.push_back(finalGrade);

// check condition
char flag;
cout << "Do you want to check more students? Please input Y/N: ";
cin >> flag;
if (flag == 'Y') ;
else running = false;
}

// prepare for writing outputs
typedef vector<string>::size_type vec_size;


for (vec_size i = 0; i != names.size(); ++i)
{
streamsize prec = cout.precision();
cout << names[i] << "'s final grade is: " << setprecision(3) << finalGrades[i]
<< setprecision(prec) << endl;
}
return 0;
}
-

It’s not complex once figure out the original program. I test the program and it works well (see below).
Test

-
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
Please enter your first name: Conor
Hello, Conor!
Please enter you midterm and final exam grades: 88 70
Please enter the number of your homeworks: 3
Enter all your homework grades: 65 76 81
Do you want to check more students? Please input Y/N: Y
Please enter your first name: Brendan
Hello, Brendan!
Please enter you midterm and final exam grades: 70 80
Please enter the number of your homeworks: 2
Enter all your homework grades: 90 85
Do you want to check more students? Please input Y/N: Y
Please enter your first name: Robin
Hello, Robin!
Please enter you midterm and final exam grades: 90 90
Please enter the number of your homeworks: 5
Enter all your homework grades: 80 70 60 50 40
Do you want to check more students? Please input Y/N: N

Conor's final grade is: 75.2
Brendan's final grade is: 81
Robin's final grade is: 78
-

Note that the final grades don’t retain the tail zeros and the dot point though we set precision arguments as 3. Please find more experiments about setprecision at Working with batches of data .

-
-

Exercise 3-6

The average-grade computation in the first program might divide by zero if the student didn’t enter any grades. Division by zero is undefined in C++, which means that the implementation is permitted to do anything it likes. What does your C++ implementation do in this case? Rewrite the program so that its behavior does not depend on how the implementation treats division by zero.

-

Solution & Results

To figure out how c++ implementation deals with division by 0, I did several experiments.
Experiment 1

-
1
2
3
4
5
6
7
8
9
10
#include <iostream>
using std::cout;
using std::endl;

int main(){
int i = 10;
int j = 0;
cout << i/j << endl;
returo 0;
}
-

The result shows that disivion by 0 with both integers makes the program crash.

-

Experiment 2

-
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
#include <iostream>
using std::cout;
using std::endl;

int main(){
int i = 10;
double j = 0;
cout << i/j << endl;

double x = 10;
int y = 0;
cout << x/y << endl;

double m = 10;
double n = 0;
cout << m/n << endl;

return 0;
}
-

This program works and returns three inf which means infinity.

-

Exerpriment 3

-
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
#include <iostream>
using std::cout;
using std::endl;

int main(){

double h = 0;
double k1 = 0;
int k2 = 0;

cout << h/k1 << endl;
cout << h/k2 << endl;

return 0;
}
-

The third program is the case of the original program and it returns nan which means Not-A-Number.

-

There is one question on quora Why does division by zero return INF (infinite) with floats, but makes the program crash with integers in C++?. Many answers can be found there but I can’t understand the mechanism well.

-

Anyway, division by 0 is a special case and should be treated seperately. For the original program, one way is to check the count before the division. For example, we can add below piece of code after the while loop finishes

-
1
2
3
4
5
   if (count == 0)
{
cout << "No homework grades entered, please try again.";
return 1;
}
-

Alternatively, we can set a default value for the average homework grade.

-
1
2
3
4
5
double homeworkGrade;
if (count == 0)
homeworkGrade = 0;
else
homeworkGrade = sum/count;
-

Now I apply the second method and present the modified program below.

-
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
#include <iomanip>
#include <ios>
#include <iostream>
#include <string>

using std::cin; using std::setprecision;
using std::cout; using std::string;
using std::endl; using std::streamsize;

int main()
{
// ask for and read the student's name
cout << "Please enter your first name: ";
string name;
cin >> name;
cout << "Hello, " << name << "!" << endl;

// ask for and read the midterm and final grades
cout << "Please enter you midterm and final exam grades: ";
double midterm, final;
cin >> midterm >> final;

// ask for the homework grades
cout << "Enter all your homework grades, "
"followed by end-of-file: ";

// the number and the sum of grades read so far
int count = 0;
double sum = 0;

// a variable into which to read
double x;

// invariant: we have read count grades so far, and sum is the sum of the first count grades
while (cin >> x)
{
++count;
sum += x;
}

// special case treatment
double homeworkGrade;
if (count == 0)
homeworkGrade = 0;
else
homeworkGrade = sum/count;

// write a blank line to seperate outputs
cout << endl;

// write the result
streamsize prec = cout.precision();
cout << "Your final grade is " << setprecision(3) <<
0.2 * midterm + 0.4 * final + 0.4 * homeworkGrade
<<setprecision(prec) << endl;
return 0;
}
-

Test

-
1
2
3
4
5
6
Please enter your first name: Brendan
Hello, Brendan!
Please enter you midterm and final exam grades: 95 77
Enter all your homework grades, followed by end-of-file:

Your final grade is 49.8
-

Now, it works better than the original one.

-
-

Reference

Koenig, A. and Moo, B.E., 2000. Accelerated C++.

-

Lippman, S.B., Lajoie, J. and Moo, B.E., 2013. C++ Primer, 5th edt.

+ + + + Exercise 3-0Compile, execute, and test the programs in this chapter +Solution & ResultsThis exercise has been accomplished in C++ - Working with ba + ... + +
+ + Read more » + +
+ + +
+ + + + + -
+ +
+ + + + + + + +
-
+ +
+ + + +

+ + + + + + + - +
+ -
- +
+ + +
-

- - -

+ + + +

+ +

+
+ @@ -1501,213 +1641,178 @@

-

Exercise 3-0

Compile, execute, and test the programs in this chapter

-

Solution & Results

This exercise has been accomplished in C++ - Working with batches of data with detailed explination.

-
-

Exercise 3-1

Suppose we wish to find the median of a collection of values. Assume that we have read some of the values so far, and that we have no idea how many values remain to be read. Prove that we cannot afford to discard any of the values that we have read. Hint: One proof strategy is to assume that we can discard a value, and then find values for the unread—and therefore unknown—part of our collection that would cause the median to be the value that we discarded.

-

Solution & Results

It is known that the median value of a data sample is sensitive to the number of elements. If the number is an odd number, the mid element is unique and the median value is the value of this mid element. However, if the number is an even number, there exist two mid elements and the median value is the average value of these two elements. Consider that, once a value is discarded, the number of the elements changes from odd (or even) to even (odd), and hence the median value would be inaccurate.

-

For example, follow sequence of values is part of a data sample,

-
1
2 8 3 4 9 7 0
-

the unknown part is

-
1
11 6 13 9
-

The ture median value is 7. If 0 is discarded, the median value becomes 7.5. If 7 is discarded, the median value is still 7. If 9 is discarded, the median value is 6.5. This indicates that the median value would be an unreliable measure for a data sample if any of values is discarded.

-
-

Exercise 3-2

Write a program to compute and print the quartiles (that is, the quarter of the numbers with the largest values, the next highest quarter, and so on) of a set of integers.

-

Solutions & Results

Quartiles of an ordered dataset are values that divide the data set into four equal parts, i.e. quarters. An intuitive strategy is to find the median value of the whole data set, and then find the median values of the divided two parts respecitively. As a result, there will exist three median values which are first quartile, second quartile and third quartile relative to the whole data set. Therefore, once we know how to compute the median value, we know how to compute quartiles. Now I’ll enter into the details of how to implement such a computing algorithm.

-

data preparation

At the very beiging stage, we need to store and sort all values

-
1
2
3
4
5
6
7
 cout << "Please enter a sequence of integers, "
"followed by end-of-file: ";
int x;
vector<int> integers; // to hold all values
while(cin >> x)
integers.push_back(x);
sort(integers.begin(), integers.end());
-

find the median value

Now we have a sorted data set. Due to the calculation of a median value depends on the number of elements in the data set, we need to consider several cases. The number of elements is the size of integers.

-
1
2
typedef vector<int>::size_type vec_size;
vec_size size = integers.size();
-
    -
  1. if there is no elements(i.e. size == 0), it is impossible to compute the median value.

    -
    1
    2
    3
    4
    5
    6
    if (size == 0)
    {
    cout << "You must enter at least one integer. "
    "Please try again.";
    return 0;
    }
    -
  2. -
  3. if size is an even number, the median value is the average value of two mid elements. In addition, size/2 is exactly devided and represent the number of each side elements.
    Due to the index of a vector starts from 0, the mid elements should be integers[size/2 - 1] and integers[size/2]. Therefore, the median value is the average of the corresponding two values. As shown in above graph, the number of elements of both sides equals to size/2 because

    -
    1
    2
    left side:  number = size/2 - 1 - 0 + 1 = size/2
    right side: number = size - 1 - size/2 + 1 = size/2
    -

    If size is an even number

    -
  4. -
  5. if size is an odd number, the median value is the value of the unique mid element. size/2 yields the same value as (size-1)/2 in c++. Then the mid element is exact the integers[(size-1)/2] as both sides of this element has same number of elements(as shown in below graph). This can also be verified mathematically

    -
    1
    2
    left side:  number = (size-1)/2 - 1 - 0 + 1 = (size-1)/2
    right side: number = size - 1 - {(size-1)/2 + 1} + 1 = (size-1)/2;
    -

    If size is an odd number

    -
  6. -
-

Now let’s translate the algorithm into real code

-
1
2
3
4
vec_size mid = size/2;
double Q2; // the median is in fact the second quartile denoted by Q2
Q2 = size % 2 == 0 ? (integers[mid - 1] + integers[mid])/2.0
: integers[mid];
-

Note that when calculate the average of two mid elements, I divide by 2.0 rather than 2 for the purpose of avoiding the loss of precision.This is because the quotient of two integers will be an integer.

-

find the first quartile

From above analysis, we have known how to find a median, i.e. the second quartile of the dataset. The median has divided the data into two equal groups: first group is from the smallest value to the median and the other one is from the median value to the largest value. Therefore, the middle value of the first group is in fact the first quartile(also known as lower quartile) and the middle value of the second group is the third quartile (also known as upper quartile). We can apply the same method to find both middle values. Similarly, the first step is to find the size (denoted by half_size) for both groups and discuss different cases.

-

From above analysis, we know that

-
    -
  1. if size is an even number, half_size == size/2.
  2. -
  3. if size is an odd number, half_size == (size-1)/2. See wikipedia-Quartile Method 1
  4. -
-
1
2
vec_size half_size; // defind a variable to represent the size of two equal groups.
half_size = size % 2 == 0 ? size/2 : (size-1)/2;
-

Now let’s find the middle value of the first group values.

-
    -
  1. if half_size == 0, then size == 1. In this case, the single element is all we have and hence all quartiles equals to the value of the single element.
  2. -
-
1
2
3
4
// variables to hold the first quartile and third quartile
double Q1, Q3;
if (half_size == 0)
Q1 = Q3 = integers[0];
-
    -
  1. if half_size is an even number, half_size/2 is exactly divided and the mid elements are integers[half_size/2] and integers[half_size/2 - 1].

    -
  2. -
  3. if half_size is an odd number, half_size/2 gives the value of (half_size - 1)/2. The middle value is integers[(half_size-1)/2].

    -
  4. -
-

Both two cases are exactly the same as finding the median for the whole dataset.

-
1
2
vec_size mid_first = half_size/2;
Q1 = half_size % 2 == 0 ? (integers[mid_first - 1] + integers[mid_first])/2.0 : integers[mid_first];
- -

find the third quartile

The upper quartile is computed as same as the lower quartile except the index to be applied. If size is even, then the starting point for the second half is mid, while if size is odd, the starting point is mid+1. Therefore

-
1
2
3
vec_size mid_second = size % 2 == 0 ? (half_size/2 + mid) : (half_size/2 + mid + 1);
Q3 = half_size % 2 ? (integers[mid_second - 1] + integers[mid_second])/2.0
: integers[mid_second];
- -

A complete program

Now let’s put all pieces of code togther and add appropriate headers as well s using declarations.

-
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
65
#include <algorithm>
#include <iomanip>
#include <ios>
#include <iostream>
#include <string>
#include <vector>

using std::cin; using std::sort;
using std::cout; using std::streamsize;
using std::endl; using std::string;
using std::setprecision; using std::vector;

int main()
{
// ask for and read integers
cout << "Please enter a sequence of integers, "
"followed by end-of-file: ";
int x;
vector<int> integers; // to hold all values

while(cin >> x)
integers.push_back(x);
sort(integers.begin(), integers.end());

// get the size of the dataset
typedef vector<int>::size_type vec_size;
vec_size size = integers.size();

if (size == 0)
{
cout << "You must enter at least one integer. "
"Please try again.";
return 0;
}

// find the median value which in fact is the second quartile denoted by Q2
vec_size mid = size/2;
double Q2;
Q2 = size % 2 == 0 ? (integers[mid - 1] + integers[mid])/2.0 : integers[mid];

// get the size of two equal groups.
vec_size half_size;
half_size = size % 2 == 0 ? size/2 : (size-1)/2;

// find the first quartile and third quartile denoted by Q1 and Q3 respectively
double Q1, Q3;
if (half_size == 0)
{
Q1 = Q3 = integers[0];
}
else
{
vec_size mid_first = half_size/2;
Q1 = half_size % 2 == 0 ? (integers[mid_first - 1] + integers[mid_first])/2.0 : integers[mid_first];

vec_size mid_second = size % 2 == 0 ? (half_size/2 + mid) : (half_size/2 + mid + 1);
Q3 = half_size % 2 == 0 ? (integers[mid_second - 1] + integers[mid_second])/2.0 : integers[mid_second];
}

streamsize prec = cout.precision();
cout << setprecision(3) << "The first quartile is: " << Q1 << endl;
cout << "The second quartile is: " << Q2 << endl;
cout << "The second quartile is: " << Q3 << setprecision(prec) << endl;
return 0;
}
- -

Test performance

Test sequence 1: 1 2

-
1
2
3
4
Please enter a sequence of integers, followed by end-of-file: 1 2
The first quartile is: 1
The second quartile is: 1.5
The second quartile is: 2
-

Test sequence 2: 1 2 3

-
1
2
3
4
Please enter a sequence of integers, followed by end-of-file: 1 2 3
The first quartile is: 1
The second quartile is: 2
The second quartile is: 3
-

Test sequence 3: 1 2 3 4

-
1
2
3
4
Please enter a sequence of integers, followed by end-of-file: 1 2 3 4
The first quartile is: 1.5
The second quartile is: 2.5
The second quartile is: 3.5
-

Test sequence 3: 1 3 5 6 9 0 3 2 5 3 8

-
1
2
3
4
Please enter a sequence of integers, followed by end-of-file: 1 3 5 6 9 0 3 2 5 3 8 
The first quartile is: 2
The second quartile is: 3
The second quartile is: 6
-

Yeah, it works perfectly.

-
-

Exercise 3-3

Write a program to count how many times each distinct word appears in its input.

-

Solution & Results

Intuitive explanations

The purposes of this program is to write out each distinct word followed by its occurrence numbers in the input. And what we have is only unknown amount of words to be entered. Imagine that there is only one word(e.g. word1) in total, then the output will be:

-
1
word1 appears 1 times
-

Now in the case of two words, e.g. word1 and word2, if word1 is the same as word2, then the output will be:

-
1
word1 appears 2 times
-

otherwise

-
1
2
word1 appears 1 times
word2 appears 1 times
-

From these two cases, we have seen that

-
    -
  1. word1 needs to be stored and its occurrence needs to be recorded
  2. -
  3. word2 needs to be compared with word1. As a result, it will be discarded if they are the same and the occurrence number of word1 is increased by 1, and it will be stored if they are different and its occurrence number increases by 1.
  4. -
-

By analogy, following entered words will be compared with each stored word, and will be discarded if there already exist one same word otherwise will be stored, meanwhile, the corresponding occurrence numbers are adjusted. Finally, all stored words are distinct with eachother and their occurrence numbers have been clearly recorded. Now I enter the details of this program.

-

vectors and the structure

To hold each distinct word and the associated number of occurrence, I define two vectors whis have types of int and string respectively.

-
1
2
vector<int> counter;
vector<string> words;
-

The next step is to ask for enterring word by word and store the distinct word. This can be accomplished with a while statement. To write all distinct words as well as the occurrence numbers, we can loop through words and counter using index from 0 to words.size() - 1.
Therefore, the whole structure is

-
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
int main()
{
vector<int> counter;
vector<string> words;
typedef vector<string>::size_type vec_size;

string word;
while(cin >> word)
{
/* pseudocode
* if the word is a new distinct word
* words.push_back(word);
* counter.push_back(1);
* if the word already exists
* adjust the number of occurrence for
* the existed distinct word
*/
}

// if there is no inputs, send warning
if (words.empty())
{
cout << "You must input at least one word. Please try again.";
return 1;
}

for (vec_size i = 0; i != words.size(); ++i)
{
cout << words[i] << " appears " << counter[i] << " times" << endl;
}
return 0;
}
-

loop invariants

The only part that is needed is write the while body. The loop invariant is that words contains each distinct word entered and counter contains the associated number of occurrence. Therefore, two goals need to be accomplished inside the while loop:

-
    -
  1. check whether the current is distinct from all existed distinct words
  2. -
  3. adjust word and counter to maintain the loop invariant
  4. -
-

The first goal can be accomplished by comparing the current word with each word stored in words. In addition, a flag is set to indicate the status of the outcomes, that is, a distinct word or an existed word. Let’s see the code below

-
1
2
3
4
5
6
7
8
9
10
11
12
int flag = 0; // initial status
for (vec_size i = 0; i != words.size(); ++i)
{
if (word == words[i])
{
// if the word is already existed, discard the word but change the counter
++counter[i];

// change status show this word is not a distinct word
flag = 1;
}
}
-

The second goal is partly accomplished by the code above and the rest case is when the current word is a distinct word. Accordingly, the word should be stored into words and the initial value for occurrence number is 1.

-
1
2
3
4
5
if (flag == 0)
{
words.push_back(word);
counter.push_back(1);
}
- -

Now the program is finished, and please find the complete version in the following part. I also did several tests and it works as expected. Note that I omitted the process that verify the correctness of the loop invariants here.

-

A complete 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
56
57
58
59
60
61
62
63
64
65
66
#include <string>
#include <vector>
#include <iostream>

using std::cin; using std::string;
using std::cout; using std::vector;
using std::endl;

int main()
{
// vectors for holding each distinct word and its occurrence numbers
vector<int> counter;
vector<string> words;

// using type alias for convenience
typedef vector<string>::size_type vec_size;

// ask for and read words one by one
cout << "Please enter a sequence of words, and followed by end-of-file: ";
string word;

// while loop invariant: words and counter contains each distinct word and the associated times
while(cin >> word)
{
// set flags to indicate whether the current word already exists
// flag == 0: the word is distinct from each existed distinct word
// flag == 1: the word is already exist
int flag = 0;

// to maintain the outer loop invariant, compare the current word
// with each existed distinct word using following for loops

// for loop invariant: the current word has been compared with the ith distinct word
for (vec_size i = 0; i != words.size(); ++i)
{
// adjust the number of occurrence by 1 for existed distinct words
if (word == words[i])
{
++counter[i];
flag = 1; // change flag value to indicate that this word is distinct
}
}

// maintain the outer loop invariant: store the new distinct word and its occurrence number
if (flag == 0)
{
words.push_back(word);
counter.push_back(1);
}
}

// send warning if there is no any words entered
if (words.empty())
{
cout << "You must input at least one word. Please try again.";
return 1;
}

// write a blank line to seperate the outputs
cout << endl;
for (vec_size i = 0; i != words.size(); ++i)
{
cout << words[i] << " appears " << counter[i] << " times" << endl;
}
return 0;
}
-

Test performance

Test 1: house

-
1
2
3
Please enter a sequence of words followed by end-of-file: house

house appears 1 times
-

Test 2: house number one and number two

-
1
2
3
4
5
6
7
Please enter a sequence of words followed by end-of-file: house number one and number two

house appears 1 times
number appears 2 times
one appears 1 times
and appears 1 times
two appears 1 times
- -
-

Exercise 3-4

Write a program to report the length of the longest and shortest string in its input.

-

Solution & Results

Strategy 1

A very simple solution strategy to this exercise is that store the length of each string into a vector and then implement a library sort algorithm. I won’t go into details as it is simple. Please find the code and tests below.

-
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
#include <algorithm>
#include <string>
#include <vector>
#include <iostream>

using std::cin; using std::string;
using std::cout; using std::vector;
using std::endl;

int main()
{
// ask for words
cout << "Please enter words followed by end-of-file: ";
string word;

// creat a vector for holding the length of each string
typedef string::size_type word_size;
vector<word_size> length;

// read word and store the length
while(cin >> word)
{
length.push_back(word.size());
}

if (length.empty())
{
cout << "You must input at least one word."
"Please try again.";
return 1;
}

// sort all lengths in an nondecreasing order
sort(length.begin(), length.end());
cout << "The length of the shortest string is: " << length[0] << endl;
cout << "The length of the longest string is: " << length[length.size() - 1] << endl;
return 0;
}
-

Test 1: I am a good teacher

-
1
2
3
Please enter words followed by end-of-file: I am a good teacher 
The length of the shortest string is: 1
The length of the longest string is: 7
-

Test 2: what are you going to do

-
1
2
3
Please enter words followed by end-of-file: what are you going to do
The length of the shortest string is: 2
The length of the longest string is: 5
-

Strategy 2

Above strategy is computational inefficient due to the fact that it sorts all length values while we only need two extremes. An alternative strategy is to use insert sort algorithm but only sort one round for each of extremes. For example, now there is a sequence of integers

-
1
n1 n2 n3 n4 ... nx ny nz
-

Step 1: assumming the largest number is n1.

-

Step 2: compare n1 and n2, if n1 >= n2, we exchange their positions with eachother. But if n1 < n2, we keep their order and assume n2 is the largest number.

-

Step 3: compare the larger number of step 2 with n3. Deal with the comparison result as same as that in step 2.

-

Step 4: continue comparison until the last number. Now the number in the rightest position is the final result, i.e. the largest number.

-

By analogy, we can find the smallest number. In this case, the precedure is simpler as we don’t need to exchange their positions. For finding the largest number, we simply discard the smaller value in each comparison. It is pretty easy to understand and no more detailed description here. Please see the complete program below.

-
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
#include <iostream>
#include <string>

using std::cin; using std::endl;
using std::cout; using std::string;


int main()
{
// ask for inputs
cout << "Please enter words followed by end-of-file: ";
string word;
typedef string::size_type str_size;

// variables for holding the length values of the longest and shortest strings
str_size longestLength = 0;
str_size shortestLength = 0;

// read words one by one
while(cin >> word)
{
// insert sort to get the largest value and samllest value only
if (longestLength == 0 || longestLength < word.size())
longestLength = word.size();
if (shortestLength == 0 || shortestLength > word.size())
shortestLength = word.size();
}

// if there is no inputs, send warning
if (longestLength == 0)
{
cout << "You must enter at least one word. Please try again.";
return 1;
}

cout << "The length of the shortest string is: " << shortestLength << endl;
cout << "The length of the longest string is: " << longestLength << endl;
return 0;
}
-

Test 1: I am a good teacher

-
1
2
3
Please enter words followed by end-of-file: I am a good teacher 
The length of the shortest string is: 1
The length of the longest string is: 7
-

Test 2: what are you going to do

-
1
2
3
Please enter words followed by end-of-file: what are you going to do
The length of the shortest string is: 2
The length of the longest string is: 5
-

The results of above two programs are exactly the same.

-
-

To be continued.

+ + + + Imagine a course in which each student’s final exam counts for 40% of the final grade, the midterm exam counts for 20%, and the average homework grade + ... + +
+ + Read more » + +
+ + +
+ + + + + -
+ +
+ + + + + + + +
-
+ +
+

+ + +
+ + + + + + + - +
-
- + +
+ + +
-

- - -

+ + + +

+ +

+
+ @@ -1715,240 +1820,208 @@

-

Imagine a course in which each student’s final exam counts for 40% of the final grade, the midterm exam counts for 20%, and the average homework grade makes up the remaining 40%. Now we are asked to write a program that reads a student’s exam and homework grades and computes a final grade.

-
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 <iomanip>
#include <ios>
#include <iostream>
#include <string>

using std::cin; using std::setprecision;
using std::cout; using std::string;
using std::endl; using std::streamsize;

int main()
{
// ask for and read the student's name
cout << "Please enter your first name: ";
string name;
cin >> name;
cout << "Hello, " << name << "!" << endl;

// ask for and read the midterm and final grades
cout << "Please enter you midterm and final exam grades: ";
double midterm, final;
cin >> midterm >> final;

// ask for the homework grades
cout << "Enter all your homework grades, "
"followed by end-of-file: ";

// the number and the sum of grades read so far
int count = 0;
double sum = 0;

// a variable into which to read
double x;

// invariant: we have read count grades so far, and sum is the sum of the first count grades
while (cin >> x)
{
++count;
sum += x;
}

// write the result
streamsize prec = cout.precision();
cout << "Your final grade is " << setprecision(3)
<< 0.2 * midterm + 0.4 * final + 0.4 * sum/count
<< setprecision(prec) << endl;
return 0;
}
-

More about IO system

The goal of above program is to compute a final grade, which is a simple math question-computing the weighted average. Let’s start from the #include directives

-
1
2
3
4
#include <iomanip>
#include <ios>
#include <iostream>
#include <string>
-

We are familar with which is the header that defines the standard input/output stream objects, and which is the header that defines string type objects. Correspondingly, objects cin , cout and endl are defined in the iostream library, and string is defined in the string class. These names are defined in the namespace std, and need to be declared in the form of std::name or using std::name before we can use them.

-

Similarly, the is a header that defines the type streamsize which represents sizes. The defines the manipulator setprecision which sets the decimal precision. Both names streamsize and setprecision are defined in the namespace std.

-

setprecision, shownpoint & fixed

setprecision is used to format floating-point values, such as float and double type values. It manipulates the stream by causing the subsequent output on that stream to be written with a given number of digits. The syntax is

-
1
<< setprecision(int n) <<
-

The parameter n determines the number of digits to be written. In this case, setprecision(3) means the output will remain three digits. Let’s do some experiments to explore more about setprecision

-

Experiment 1

-
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
#include <iomanip>
#include <ios>
#include <iostream>

using std::cin; using std::setprecision;
using std::cout; using std::endl;


int main()
{
int i = 12345;
float f1 = 12345;
float f2 = 3.148;;
float f3 = 0.03148;
float f4 = 0.03108;
float f5 = 0.03100;
float f6 = 1000.435;

cout << setprecision(3) << i << endl;
cout << f1 << endl;
cout << f2 << endl;
cout << f3 << endl;
cout << f4 << endl;
cout << f5 << endl;
cout << f6 << endl;

return 0;
}
-

The result is shown below

-
1
2
3
4
5
6
7
12345
1.23e+004
3.15
0.0315
0.0311
0.031
1e+003
-

It can be seen from this experiment that

-
    -
  1. setprecision doesn’t work on int type values
  2. -
  3. the parameter n (in this case is 3) controls the number of significant digits (i.e. count from the first non-zero number).
  4. -
  5. it follows the rounding principles.
  6. -
  7. it omits the trailing 0s.
  8. -
-

If one want to keep the trailing zero, the manipulator showpoint can be used. showpoint is declared in the ios library and its name is also in the namespace std. It sets the format flag and always includes the decimal point as well as the tail 0 for matching the precision. The showpoint flag can be unset with the noshowpoint manipulator.

-

If we want control the precision of the decimal part, we can use fixed manipulator to fixed the decimal part. Together with the setprecision(n), the number of digits in the fractional part will be fixed at n. If there is no enough numbers after the decimal point, zero will be added to match the precision.

-

To veryfy the usages of showpoint and fixed, I did tests as follows.

-

Experiment 2

-
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
#include <iomanip>
#include <ios>
#include <iostream>

using std::cin; using std::setprecision;
using std::cout; using std::fixed;
using std::endl; using std::showpoint;
using std::noshowpoint;


int main()
{
float f1 = 1000.00;
float f2 = 3.14800;
float f3 = 0.03148;


cout << showpoint << setprecision(4) << f1 << endl;
cout << f2 << endl;
cout << f3 << noshowpoint << endl;

// write a bank line to sepearate outputs
cout << endl;

cout << fixed << setprecision(6) << f1 << endl;
cout << f2 << endl;
cout << f3 << endl;

return 0;
}
-

The result is as analysed above

-
1
2
3
4
5
6
7
1000.
3.148
0.03148

1000.000000
3.148000
0.031480
-

streamsize

Once we changed the precision, the subsequent output would be formated to match the precision. If we want to change back, we can reset the precision to the original setting if we know the precision value. If we don’t know the previous setting of the cout, the cout.presicion returns us the value and its type is streamsize.

-

Without using setprecision, we could use cout.precision(n) to set the precision. The usage is shown as below

-

Experiment 3

-
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
#include <iomanip>
#include <ios>
#include <iostream>

using std::cin; using std::setprecision;
using std::cout; using std::fixed;
using std::endl; using std::streamsize;


int main()
{
float f1 = 1000.00;
float f2 = 3.14800;
float f3 = 0.03148;

// return the current precision
streamsize prec1 = cout.precision();

// set precision to 2
cout << setprecision(2);

// set precision to 3, return previous value
streamsize prec2 = cout.precision(3);

// the outputs should have three decimal digits
cout << fixed << f1 << endl;
cout << f2 << endl;
cout << f3 << endl;

// reset precision value to its previous value
cout.precision(prec2);

// the outputs should have two decimal digits
cout << endl;
cout << fixed << f1 << endl;
cout << f2 << endl;
cout << f3 << endl;

// reset the the initial precision value
cout.precision(prec1);

cout << endl;
cout << fixed << f1 << endl;
cout << f2 << endl;
cout << f3 << endl;

// check the original value of precision
cout << endl;
cout << "The origin precision value is: " << prec1 << endl;

return 0;
}
-

The comments show the expected results according to that

-
    -
  1. streamsize precision() returns the the value of the current floating-point precision.
  2. -
  3. streamsize precision(int n) sets the precision to a new value.
  4. -
-

The program gives result as I expected

-
1
2
3
4
5
6
7
8
9
10
11
12
13
1000.000
3.148
0.031

1000.00
3.15
0.03

1000.000000
3.148000
0.031480

The origin precision value is: 6
-

Return to while statement

Let’s get back to the example. The statements in the function body begin with writing a greeting as illustrated in previous chapters. Then, it reads two values into two variables midterm and final. The input operations can be chained as

-
1
cin >> midterm >> final;
-

This statement is equivalent to

-
1
2
cin >> midterm;
cin >> final;
-

The next statement shows a new form of writing string literals.

-
1
2
cout << "Enter all your homework grades, "
"followed by end-of-file: ";
-

It seems that two string lterals will be written, but in fact it has the same effect as the following statement

-
1
cout << "Enter all your homework grades, followed by end-of-file: ";
-

This is because two or more string literals separated only by whitespace are concatenated automatically.

-

What closely follow is the while loop.

-
1
2
3
4
5
6
7
8
9
10
11
12
int count = 0;
double sum = 0;

// a variable into which to read
double x;

// invariant: we have read count grades so far, and sum is the sum of the first count grades
while (cin >> x)
{
++count;
sum += x;
}
-

count is defined for counting the number of inputs as well as describing the loops. sum is defined for holding the summation of homework grades. Note that it is initialized with an int value 0 though it is a double type variable. Therefore, the int value will be converted automatically to double type with the fractional part of 0.

-

Since the number of homework grades is unkonwn, we could not use the while loop as before as we don’t know the number of loops. The while loop here makes it available to input multiple times continuously. As we know, the condition in a while loop should yield a value of bool, that is, true or false, otherwise the value (if available) will be converted to the type of bool. According to Koenig and Moo (2000), the istream class provides a conversion that can be used to convert cin to a value that can be used in a condition. In addition, the value depends on the iternal state of the istream object, which will remember whether the last attempt to read worked. Hence, the cin >> x will theoretically always yield a true value as long as we keep inputing right type values. In this example, it reminds us to send “EOF” signal, which will change the value to false, to stop the loop.

-

vector

The example above shows how to compute an average value of a batch of data. In reality, the goal is probably to compute the median, or other statistics in the data. However, the inputted data are not stored in above program and hence are unavailable to access. Naturally, the first step is to store the data and the second step is to access or compute the target value via algorithms. Now we learn how to deal with these problems with vector.

-

Defining and Initializing vectors

The vector is a class template. Before using the vector in a program, we need to include the header and qualify the name either explicitly or using using declaration. For example

-
1
2
#include <vector>
using std::vector
-

A vector is a container that holds a sequence of objects of the same type. The syntax of creating a vector is

-
1
vector<T> name; // default initialization
-

The name is the template name. The T insides the angle brackets represents the type of the contained objects. It could be built-in types like int, char, or class types such as string, or even vector which means that the element contained in this vector is also a vector.

-

The vector template defines how to initialze vectors. When a vector is defalut initialized, it has no elements, which creats an empty vector. We can also initialize vectors using copy initialization or direct initialization or list initialization (see the table below).

- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
Ways to initialize a vectorreference: Lippman etc. 2012
vector v1default initialization and creat an empty vector
vector v2(v1)copy all elements in v1 to v2
vector v2 = v1has same effect as the last one
vector v3(n, val)direct initialization with n elements of the same value val
vector v4(n)v4 has n copies of value-initialized object
vector v5{a, b, c}v5 has three elements initialized with initializers a, b, c respectively
vector v5 = {a, b, c}has same effect as the last one
-

What new usage here is the value-initialization

-
1
vector<T> v4(n)
-

The n here only provide the number of elements that will be contained in the vector. But there is no initializers provided. In this case, all elements will be initialized following the principle of default initialization determined by the type of the elements. For example

-
1
2
vector<int> v1(10); // 10 elements will be initialized with 0
vector<string> v2(10); // 10 elements will be initialized to empty string type objects
-

If the type of the contained elements does’t support default initialization, the initialization of the vector would be failure.

-

In addition, it is worth noting the difference between

-
1
(1)    vector<int> v1(10);
-

and

-
1
(2)    vector<int> v2{10};
-
1
(3)    vector<int> v3(10, 1);
-

and

-
1
(4)    vector<int> v4{10, 1};
-

The first one is value-initialization as explained above while the second is list initialization with initializer 10. The third one is direct initialization with value 1 and the total number of elements is 10. The fourth one is list initialization with initializers 10 and 1, and the total number of elements is 2.

-

However, there exist some special cases

-
1
2
3
(5)    vector<string> v5("hello");

(6) vector<string> v6{10}; // 10 value-initialized elements
-

Example (5) is incorrect as we can not copy the string lterals to a vector. Example (6) is correct but has the same effect as example (1) rather than list initialization. This is because 10 can not be used to initialize an element of string type, instead it can be used to initialize the vector with 10 value-initialized strings.

-

Now returing to the beigining of this post, I’ll define a vector for the purpose of holding all homework grades.

-
1
2
double x;
vector<double> homework;
-

Operations on vectors

Reading the elements

One feature of a vector is that it has variable size, which is particularly helpful for us when the number of elements is unknown. The member function push_back can add a new element at the end of the vector after the current last element. The usage is shown below

-
1
2
3
4
while(cin >> x)
{
homework.push_back(x);
}
-

The x is passed as an argument and its value is copied to the new element. As a result, the size of the vector homework is increased by 1.

-

Implementing algorithms

Now all data have been stored into a vector. Assuming the goal of the program is to compute the median of the data set. It is known that the median value depends on the number of the stored elements (i.e. the size of the vector).

-
    -
  1. if there exist an odd number of numbers, the median value is the value of the middle number.
  2. -
  3. if there exist an even number of numbers, there is no single middle number and the median value is the average value of two middle numbers.
  4. -
-

For a program, we also need to consider the case of no elements.

-
    -
  1. if there is no elements at present, throwing a warning and asking to input again.
  2. -
-

Similar as a string, the size of a vector can be obtained through its member function size. For example, homework.size() returns the size of the vector. The returned value has a type of vector::size_type. Now we can translate above conditions to real code

-
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
// obtain the size of homework
typedef vector<double>::size_type vec_size;
vecsize size = homework.size();

if (size == 0)
{ cout << "You must enter your grades. "
"Please try again." << endl;

// return 1 instead of 0 to indicate failure
return 1;
}
else
{
if (size % 2 == 0)
// compute median
else
// compute median
}
-

Note that an alternative for the condition size == 0 is the empty function which returns true if vector is empty else returns false. The usage is the same as that for strings.

-

Type Aliases

For convenience, we uses type alias instead of using vector::size_type directly in defining variables of such type. A type alias defines the name vec_size as a synonym for vector::size_type. There are two ways to define type alias

-
1
2
3
(1)    typedef double length; // length is a synonym for double

(2) using length = double; // length is a synonmy for double
-

The second method is the new feature of new standards c++2011.

-

sort function

Now the rest of the work is to find the median and basically speaking, is to sort the data set. This can be done by using a library algoritm.

-
1
2
3
#include <algorithm>
... // other code
sort(homework.begin(), homework.end());
-

The sort function is defined in the library algorithm and therefore the header is added. It sorts the values in a container in an nondecreasing order. The arguments to sort specify the range of the data to be sorted. begin and end are member functions of the vector and represents the first element and (one past)the last element in homework respectively.
They are iterators and will further discussed in chapter 6.

-

After we obtained a ordered sequence of values, now I illustrate how to determine the median value.
If size is an even number

-

Similar as a string, we can access individual elements using subscript operator([]) and the index uses an asymmetrical range from 0 to the size of homework (excluded). If the size of homework is an even number, then size is exactly devided by 2. Due to the index starts from 0 ranther than 1, the mid elements should be homework[size/2 - 1] and homework[size/1]. Therefore, the median value is the average of the corresponding two values. As shown in above graph, the number of elements of both sides equals to size/2 because

-
1
2
left side:  number = size/2 - 1 - 0 + 1 = size/2
right side: number = size - 1 - size/2 + 1 = size/2
-

If size is an odd number, the result of size/2 is in fact the value of (size-1)/2. Then the mid element is exact the homework[(size-1)/2] as both sides of this element has same number of elements(as shown in below graph). This can also be verified mathematically

-
1
2
left side:  number = (size-1)/2 - 1 - 0 + 1 = (size-1)/2
right side: number = size - 1 - {(size-1)/2 + 1} + 1 = (size-1)/2;
-

If size is an odd number

-

Now let’s translate the algorithms to real code

-
1
2
3
4
5
6
7
8
9
{ 
vec_sz mid = size/2;
double median;

if (size % 2 == 0)
median = (homework[mid] + homework[mid-1])/2.0;
else
median = homework[mid]
}
-

Note that when calculate the average of two mid elements, I divide by 2.0 rather than 2 for the purpose of avoiding the loss of precision.This is because the quotient of two integers will be an integer.

-

The conditional (or ternary) operator

An alternative statements for above if-else clause is to use the conditional (or ternary) operator (?:). The syntax is

-
(condition 1) ? expression 1 : expression 2

It means that if condition 1 evaluates to true, then expression 2 is evaluated, and if condition 1 evaluates to false, then expression 3 is evaluated instead.

-

Therefore, we can change above code as

-
1
2
3
4
5
6
{ 
vec_sz mid = size/2;
double median;

median = size % 2 == 0) ? (homework[mid] + homework[mid-1])/2.0 : median = homework[mid];
}
- -

A complete program

Finally, I put all pieces of code together and obtained the complete program. Also, it works well when I test it.

-
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
#include <algorithm>
#include <iomanip>
#include <ios>
#include <iostream>
#include <string>
#include <vector>

using std::cin; using std::sort;
using std::cout; using std::streamsize;
using std::endl; using std::string;
using std::setprecision; using std::vector;

int main()
{
// ask for and read the student's name
cout << "Please enter your first name: ";
string name;
cin >> name;
cout << "Hello, " << name << "!" << endl;

// ask for and read the midterm and final grades
cout << "Please enter your midterm and final grades: ";
double midterm, final;
cin >> midterm >> final;

// ask for the student entered some homework grades
cout << "Enter all your homework grades, "
"followed by end-of-file: ";
double x;
vector<double> homework;

while(cin >> x)
homework.push_back(x);

// check that the student entered homework grades
typedef vector<double>::size_type vec_size;
vec_size size = homework.size();
if (size == 0)
{
cout << endl << "You must enter your grades. "
"Please try again." << endl;
return 1;
}

// sort the grades
sort(homework.begin(), homework.end());

// compute the median homework grade
vec_size mid = size/2;
double median;
median = size % 2 == 0 ? (homework[mid] + homework[mid - 1])/2
: homework[mid];

// compute and write the final grade
streamsize prec = cout.precision();
cout << "Your final grade is " << setprecision(3)
<< 0.2 * midterm + 0.4 * final + 0.4 * median
<< setprecision(prec) << endl;

return 0;
}
-

Test and results:

-
1
2
3
4
5
Please enter your first name: Bruce
Hello, Bruce!
Please enter your midterm and final grades: 80 90
Enter all your homework grades, followed by end-of-file: 50 60 70 80 90
Your final grade is 80
+ + + + + + Arithmetic types + + +Arithmetic types in C++ + + + + + + +Type +Meaning +Minimum size + + +bool +boolean +NA + + +char +character +8bits + + +wchar_t +wide character +16bits + + + + ... + +
+ + Read more » + +
+ +
+ + + + + -
+ +
+ + + + + + + +
-
+ +
+

+ + +
+ + + + + + + - +
+ -
- +
+ + +
-

- - -

+ + + +

+ +

+
+ @@ -1956,105 +2029,59 @@

-

Arithmetic types

- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
Arithmetic types in C++
TypeMeaningMinimum size
boolbooleanNA
charcharacter8bits
wchar_twide character16bits
char16_tUnicode character16bits
char32_tUnicode character32bits
shortshort integer16bits
intinteger16bits
longlong integer32bits
long longlong integer64bits
floatsingle-precision floating-point6 significant digits
doubledouble-precision floating-point10 significant digits
long doubleextended-precision floating-point10 significant digits
+ + + + Exercise 2-6What does the following code do?123456int i = 0;while (i < 10){ i += 1; std::cout << i << std::endl;} +Solut + ... + +
+ + Read more » + +
+ + +
+ + + + + -
+ +
+ + + + + + + +
-
-

+ + + +
- + + + @@ -2062,179 +2089,265 @@

{ - if (!event.target.matches('.tabs-comment .tab-content .tab-pane')) return; - let commentClass = event.target.classList[1]; - localStorage.setItem('comments_active', commentClass); - }); - } - + + + - - + -
+
+ + +
+ + +
+ + + + - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + - - + - @@ -2244,8 +2357,8 @@

@@ -2258,6 +2371,22 @@

+ + + + + + + - - + + + - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + - + - - - + + + + + + + + + + + + - - Liam's Blog @@ -51,392 +132,248 @@ - - -
-
+ -
-
-
+
- - -
+
+
+ + -
- + + + + + + +
+ -
- +
+ + +
-

- - -

+ + + +

+ +

+ -
- - - - -
- - -

Exercise 2-6

What does the following code do?

-
1
2
3
4
5
6
int i = 0;
while (i < 10)
{
i += 1;
std::cout << i << std::endl;
}
-

Solution & Results

The program writes 10 rows of numbers, starting from 1 to 10.

-

The while statement starts from testing the condition and then executes the while body if the condition is true. It stops executing the while body until the condition becomes false. Let’s analyse the first iteration

-
    -
  • First, the condition of the first time iteration is true as 0 < 10 is true.
  • -
  • Second, the expression i += 1; is evaluated, and the variable i becomes 1 after the execution.
  • -
  • then, the following statement is executed and the variable i is written on the output device.
  • -
  • finally, the while loop starts all over again from testing the condition.
  • -
-

From above steps we have seen that

-
    -
  • the first number to output is 1.
  • -
  • i is increased by 1 each iteration.
  • -
-

Accordingly, the final iteration can be deducted

-
    -
  • when i = 9, the row of output is 9 and the condition is still true.
  • -
  • after the evaluation of i += 1;, i equals 10.
  • -
  • then the output is 10 in the following step.
  • -
  • the while statement starts again and tests the condition, but the condition 10 < 10 is false.
  • -
  • the while statement finishes.
  • -
-

Now I complete the program and test it

-
1
2
3
4
5
6
7
8
9
10
11
12
#include <iostream>

int main()
{
int i = 0;
while (i < 10)
{
i += 1;
std::cout << i << std::endl;
}
return 0;
}
-

As expected, it writes 10 rows of outputs from 1 to 10 with one number in each row.

-
1
2
3
4
5
6
7
8
9
10
1
2
3
4
5
6
7
8
9
10
-

Note that the cursor appears on the next line of the final number 10 due to the following manipulator endl.

-

We can also explain the program from the perspective of its goal. Condiser that, we want the program to print out 10 rows, each of which contains a number, starting from 1 to 10 orderly. The loop invariant can be expressed as: we have written i rows now and the number in current row is i. To verify the loop invariant, we need to verify it at two specific points:

-
    -
  1. the first point is before the first time that the condition is evaluated. In this case, it is correct as there is 0 output at current position.
  2. -
  3. the second point is before the end of the while body. Once the first statement is executed, i is increased by 1. To maintain the loop invariant, it needs writing a row which contains the number i. Therefore, the loop statement works as expected in each iteration.
  4. -
-

For clarity, I add comments for the program

-
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
#include <iostream>

int main()
{
// loop invariant: we have written i rows now and the number in current row is i

int i = 0;

while (i < 10)
{
// i changes with increment of 1
i += 1;

// to maintain the loop invariant, write 1 row
std::cout << i << std::endl;
}
return 0;
}
-

Analysis

See deatiled analysis in C++ - Looping and counting.

-
-

Exercise 2-7

Write a program to count down from 10 to -5.

-

Solution & Results

This exercise is similar to the program in the last exercise. There are 16 numbers in total and hence there should be overal 16 loop times. Naturally, we use the range [0, 16) to describe the loop statements. The loop invariant can be expressed as we have written i rows and the number that written in this row is j.

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

int main()
{
// The loop invariant can be expressed as we have
// written i rows and the number written in this row is j.

int i = 0;

while (i < 16)
{
// write a row of outputs
cout << endl;

// to maintain the loop invariant, increase the value of i by adding 1
++i;
}
return 0;
}
-

What is the value of j? As mentioned above, the loop invariant needs to be verified at two specific points. First, before the first time that the condition is evaluated, we have written 0 rows. Second, before the end of the while body, we have written 1 row and the number should be 10. Therefore, the inital value of j should be 10. It is still not clear now. I’ll further verify the loop invariant in the second and third iteration.

-

The second iteration:

-
    -
  • before the the condition is evaluated, the loop invariant is correct as currently there is one row and the output is 10.
  • -
  • before the end of the while body, i increases by 1. The number to output is 9. To maintain the loop invariant, j should be decreased by 1.
  • -
-

The third iteration:

-
    -
  • before the the condition is evaluated, the loop invariant is correct as currently there are 2 rows and the second output is 9.
  • -
  • before the end of the while body, i increases by 1. The number to output is 8. To maintain the loop invariant, j should be decreased by 1.
  • -
-

It has been seen from above descriptions, each iteration i changes with increment by 1 while j changes with decrement by 1. Therefore, the sum of i and j should be constant, and hence j = 10 - i as j has initial value 10. In other words, the loop invariant is i + j = 10, of which the i represents the row number and j represents the number contained in the i row of outputs.

-

Accordingly, the complete program is

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

int main()
{
// loop invariant: we have written i rows and the number
// written in this row is j = 10-i.

int i = 0;
int j = 10;

while (i < 16)
{
// write a row of outputs
std::cout << j << std::endl;

// to maintain the loop invariant, increase the value
// of i by adding 1, change the value o j to 10 - i
++i;
j = 10 -i;
}
return 0;
}
-

The program works as expected with following outputs

-
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
10
9
8
7
6
5
4
3
2
1
0
-1
-2
-3
-4
-5
- -
-

Exercise 2-8

Write a program to generate the product of the numbers in the range [1, 10).

-

Solution & Results

The product of the numbers in the range [1, 10) is

-
1
1 x 2 x 3 x 4 x 5 x 6 x 7 x 8 x 9
-

Essentially, it is a factorial of number 9. I can transform above expression as

-
1
9! = 1 x 2 x 3 x ... x 8 x 9
-

or

-
1
9! = 9 x 8!
-

Therefore, the calculation can be designed as a loop statement containing 8 times loops. In each iteration, we calculate the factorial of a number from 2 till 9. The loop invariant is that we have calculated the factorial i times and the number f. I’ll use the range [0, 8) to count the loops. The complete program is shown as below

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

int main()
{
int f = 1;
int product = 1;

// we have calculated the factorial i times and the number is f
for (int i = 0; i != 8; ++i)
{
// increase the value of f by 1
++f;

// to maintain the loop invariant, calculate the factorial of number f
product *= f;
}
std::cout << product << std::endl;

return 0;
}
-

The results is

-
1
362880
- -
-

Exercise 2-9

Write a program that asks the user to enter two numbers and tells the user which number is larger than the other.

-

Solution & Results

It’s a simple exercise, and I’ll skip analysis and present the program directly

-
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
#include <iostream>
using std::cout;
using std::cin;
using std::endl;

int main()
{
cout << "Please enter two integers: ";
// read two numbers
int a, b;
cin >> a >> b;

if (a == b)
cout << a << " equals to " << b << endl;
else if (a > b)
cout << a << " is greater than " << b << endl;
else if (a < b)
cout << b << " is greater than " << a << endl;
return 0;
}
-

Test 1

-
1
2
Please enter two numbers: 5 8
8 is greater than 5
-

Test 2

-
1
2
Please enter two numbers: 4 2
4 is greater than 2
-

Test 3

-
1
2
Please enter two numbers: 100 100
100 equals to 100
-

Exercise 2-10

Explain each of the uses of std:: in the following program:

-
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
#include <iostream>

int main()
{
int k = 0;
while (k != 5)
{
// invariant: we have written k asterisks so far
using std::cout;
cout << "*";
++k;
}
std::cout << std::endl;
// std:: is required here
return 0;
}
-

Solution & Results

The first std:: in line 9

-
1
using std::cout;
-

The using declaration qualify us to use the name cout, which is defined in the namespace std, directly instead of std::cout. However, the declaration is only valid within the block of the while statement as the curly braces form a name scope.

-

This also explains the requirements of the std:: in line 13. If one want to use the unqualified cout, endl inside the main function body, he should write the using declarations for each different name before the start of the main function. For example

-
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
#include <iostream>
using std::cout;
using std::endl;

int main()
{
int k = 0;
while (k != 5)
{
// invariant: we have written k asterisks so far
// using std::cout - is not required now
cout << "*";
++k;
}
std::cout << std::endl;
// std:: is not required now
return 0;
}
-

Now, both programs work well and give the results

-
1
*****
-

Analysis

See C++ - Getting Started.

-
-

References

Koenig, A. and Moo, B.E., 2000. Accelerated C++.

-

Lippman, S.B., Lajoie, J. and Moo, B.E., 2013. C++ Primer, 5th edt.

- - -
+ - - - -
-
-
-
- - - + + + - - - -
- + + - + - -
-

- -

+
+ + + + + + + + 3,166 + + - + -
- - +
+ @@ -444,156 +381,178 @@

-

Exercise 2-4

The framing program writes the mostly blank lines that seperate the borders from the greeting one character at a time. Change the program so that it writes all the spaces needed in a single output expression.

-

Solution & Results

Without considering the frame itself, there only exist two types of row. The first type is the rows that full of spaces. The second type is the row that contains the greeting and spaces symmetricly located on two sides of the greeting. Therefore, one of the possible solutions is to write one row directly each time instead of writing one character. First, I defines two variables representing two blank strings

-
1
2
3
   // two blank strings containing different number of spaces
const string blankStringTopBottom(cols - 2, ' ');
const string blankStringLeftRight(pad, ' ');
-

The first string is in fact the first type of row and can be written directly given certain conditions. The second row can be obtained by means of catenate operations

-
1
2
   // the string that contains both the greeting and blanks
const string greetingRow = blankStringLeftRight + greeting + blankStringLeftRight;
-

To maintain the loop invariant, the c need to be adjusted after each output. Due to one row is written each time (ignoring the borders), c increases cols - 1 every time.

-
1
c += cols - 2;
-

The modified program is

-
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
65
66
#include <iostream>
#include <string>

// using namespace std and names
using std::cout; using std::cin;
using std::endl; using std::string;

int main()
{
// ask for the person's name
cout << "Please enter your first name: ";

// read the name
string name;
cin >> name;

// build the message that we intend to write
const string greeting = "Hello, " + name + "!";

// the number of blanks surrounding the greeting
const int pad = 1;

// the number of rows and columns
const int rows = pad * 2 + 3;
const string::size_type cols = greeting.size() + pad * 2 + 2;

// two blank strings containing different number of spaces
const string blankStringTopBottom(cols - 2, ' ');
const string blankStringLeftRight(pad, ' ');

// the string that contains both the greeting and blanks
const string greetingRow = blankStringLeftRight + greeting + blankStringLeftRight;


// write a blank line to separate the output from the input
cout << endl;

// write rows rows of output
// invariant: we have written r rows so far
for (int r = 0; r != rows; ++r)
{
string::size_type c = 0;
// we have written c characters so far in the current row
while (c != cols)
{
// are we on the border?
if (r == 0 || r == rows - 1 ||
c == 0 || c == cols - 1)
{
cout << "*";
++c;
}
else
{ // is it time to write the greeting row?
if (r == pad + 1)
cout << greetingRow;
// is it time to write the non-greeting rows?
else
cout << blankStringTopBottom;
c += cols - 2;
}
}
cout << endl;
}
return 0;
}
-

A simple test has been done and the outputs are written on the console window as below

-
1
2
3
4
5
6
7
Please enter your first name: Bruce

*****************
* *
* Hello, Bruce! *
* *
*****************
-

Analysis

See deatiled analysis in C++ - Looping and counting.

-
-

Exercise 2-5

Write a set of “*” characters so that they form a square, a rectangle, and a triangle.

-

Solution & Results

The design ideas

The correct program for this exercise is non-unique as the requirements are loose. It doesn’t specify the details of each shape. I tried to design a program that is flexible enough to meet more needs and preferences. For example, users may ask the program to write various rectangles (or squares/triangles) with different lengths or different appearance such as solid or hollow shapes. In summary, I intend to design a program with following properties

-
    -
  1. provide choices for three types of shapes, i.e. square, rectangle and triangle.
  2. -
    • -
    • for a square, the length of the side is customizable
    • -
    • for a rectangle, the length and the width are customizable, and the base can be the long side or the short side.
    • -
    • for a triangle, the height is customizable and the triangle is a equilateral triangle.
    • -
    -
  3. -
  4. for all shapes, there are two types of appearance: solid and hollow.
  5. -
-

The Primary Structure

Due to there are three types of shape, we can use if else statement as one of the choices of the primary structure and provide three banches for the three shapes. First, flags are needed for indicating what shape type is to be written. For example:

-
1
2
3
4
5
/* flags for what shape to print
* shapeType == 1, print a square shape
* shapeType == 2, print a rectangle shape
* shapeType == 3, print a triangle shape
*/
-

In addition, each type of shape can be a solid shape or hollow shape. Therefore, a choice for the appearance will be provided for users before the computer enters into each branch. For other properties, such as lengths of sides and heights, are not the same for each types of shape and will be solved in the branches. Therefore, the whole structure of the program is

-
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
int main()
{
// select a shape
cout << "Select a shape type\n";
cout << "1. A Square\n2. A Rectangle\n3. A Triangle\n";
cout << "Please enter 1 or 2 or 3: ";

// read the shape type
int shapeType;
cin >> shapeType;

// write a blank line for clarity
cout << endl;

// select appearance
cout << "Do you want a solid shape?\n";
cout << "yes: a solid shape\nno: a hollow shape\n";
cout << "Please enter y or n: ";

// read the shape appearance
char shapeAppearance;
cin >> shapeAppearance;

// write a blank line for clarity
cout << endl;

if (shapeType == 1)
{
// statements for generating a square
}
else if (shapeType == 2)
{
// statements for generating a rectangle
}
else if (shapeType == 3)
{
// statements for generating a triangles
}
return 0;
}
- -

Algorithms

A square shape

The core part of the program is how to impelment the algorithms that can generate different shapes. I starts with the implementation of writing a square. Assuming the edge length of the square is 10, the program aims to generate two different squares as follows

-
1
2
3
4
5
6
7
8
9
10
**********          **********
* * **********
* * **********
* * **********
* * and **********
* * **********
* * **********
* * **********
* * **********
********** **********
-

Note that for all the shapes, the length or height means the number of columns or rows formed by asterisks (and spaces).

-

The algorithm is similar as that in the framed greeting program except that it doesn’t need to write the greeting itself. Essentially, there are two types of string:one is full filled with asterisks and another one is filled by blanks and two asterisks located at both ends of the blanks. The structure could be a for loop or while loop, which is up to your preference. Firstly, I define the variables that are needed.

-
1
2
3
4
5
6
7
8
9
10
// read in edge length for a square
int edgeLength;
cin >> edgeLength;

// string variable type 1: full of asterisks
string asteriskString(edgeLength, '*');

// string variable type 2: blanks and asterisks
string blankString(edgeLength - 2, ' ');
string mixedString = "*" + blankString + "*";
-

The solid square shape is simple and only needs one type of the string type vairable. If we choose one of the loop statements, the loop invariant can be expressed as: we have written r rows of output, of which r is the counting variable.

-
1
2
3
4
5
6
// invariant: we have written r rows of output
for (int r = 0; r != edgeLength; ++r)
{
// write a row whose length is the edge length
cout << asteriskString << endl;
}
-

To maintaint the loop invariant, r is increased by 1 after each iteration. Once the for loop finishes, the condition r != edgeLength is false and r = edgeLength. Then, the loop invariant becomes: we have written edgeLength rows of outputs, which keeps the correctness of our loop statement.

-

For a hollow square shape, the output of each row is conditional on its position.

-
1
2
3
4
5
// is it the first row or last row of outputs?
if (r == 0 || r == edgeLength - 1)
cout << asteriskString << endl;
else
cout << mixedString << endl;
-

Now, we can combine two cases together

-
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
   if (shapeType == 1)
{
cout << "Please enter the edge length: ";

// read the edge length
int edgeLength;
cin >> edgeLength;

// write a blank line to separate inputs and outputs
cout << endl;

// string type 1: full of asterisks
string asteriskString(edgeLength, '*');

// string type 2: blanks and asterisks
string blankString(edgeLength - 2, ' ');
string mixedString = "*" + blankString + "*";

for (int r = 0; r != edgeLength; ++r)
{
// is it rows of a solid shape or
// the first/ last row of a hollow shape ?

if (shapeAppearance == 'y' || r == 0 || r == edgeLength - 1)
// write a row whose length is the edge length
cout << asteriskString << endl;
else
cout << mixedString << endl;
}
}
-

A rectangle shape

Square shapes can be regarded as a special case of rectangles. We can simply modify the above algorithm to generate a rectangle. I use base lenght and height instead of the length and width for convenience.

-
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
else if (shapeType == 2)
{
cout << "Please enter the base length and height\n";
cout << "The base lenght = ";

// read the base length
int baseLength;
cin >> baseLength;

cout << "The height = ";

// read the height
int height;
cin >> height;

// write a blank line to separate inputs and outputs
cout << endl;

// string type 1: full of asterisks
string asteriskString(baseLength, '*');

// string type 2: blanks and asterisks
string blankString(baseLength - 2, ' ');
string mixedString = "*" + blankString + "*";

for (int r = 0; r != height; ++r)
{
// is it rows of a solid shape or
// the first/ last row of a hollow shape ?

if (shapeAppearance == 'y' || r == 0 || r == height - 1)
cout << asteriskString << endl;
else
cout << mixedString<< endl;
}
}
- -

A triangle shape

For a triangle, the ideal result is to print out follow two shapes, providing the height is 4.

-
1
2
3
4
   *                   *
*** and * *
***** * *
******* *******
-

The solid trangle shape is formed only by asterisks ignoring the outer blanks. However, the number of asterisks of each row is different with eachother. From the table below, we can see a pattern in the numbers of asterisks of the rows.

-

| The rth Row| The number of columns of each row |
| :—: | :—: | :—: |
|0|1|
|1|3|
|2|5|
|3|7|
|4|9|
|…|…|
|r|(r*2 + 1)|
|Height|loop finishes|

-

The table shows that when the program writes the rth row, it needs to write asterisks in a total number of r*2 + 1.
It also shows that the number of columns is height*2 - 1.

-

For the hollow triangle shapes, there are two types of rows: the first type is rows filled only by asterisks such as the first row and the last row; another type is rows formed by blanks with two asterisks located at both ends of the blanks. The table gives the pattern in the numbers of blanks of the rows.

-

| The rth Row| The number of blanks in each row |
| :—: | :—: | :—: |
|0|0|
|1|1|
|2|3|
|3|5|
|4|7|
|…|…|
|r|((r-1)*2 + 1)|
|Height|loop finishes|

-

Correspondingly, I define variables inside a for loop as below

-
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
for (int r = 0; r != height; ++r)
{
// is it the row of a solid shape, or the first or the last row of the hollow shape
if (shapeAppearance == 'y' || r == 0 || r == height - 1)
{
// string type 1: full of asterisks
string asteriskString(r*2 + 1, '*');
}
else
{
// string type 2: blanks and asterisks
string blankString(((r-1)*2 + 1), ' ');
string mixedString = "*" + blankString + "*";
}
}
-

What we need next is to find the condition for writing each row as each row has different initial position. Before the real start of each row, we only need to output spaces. The table below gives the pattern of the initial column position of each row.

-

| The rth Row| The column number of the initial position |
| :—: | :—: | :—: |
|height | loop finishes|
|height - 1| 0|
|height - 2|1|
|…|…|
|r|height - r - 1|
|…|…|
|0|height - 1|

-

The table shows that when the loop processes the column number height - r - 1, it starts to write the strings that I defined above. When we incorporate the column loops into the row loops, the program becomes

-
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
else if (shapeType == 3)
{
// read the height
cout << "Please enter the height: ";
int height;
cin >> height;

// write a blank line to separate inputs and outputs
cout << endl;

// loop invariant: we have written r rows now
for (int r = 0; r != height; ++r)
{
// loop invariant: we have written c characters so far in the current line
int c = 0;

while(c != (height*2 - 1))
{ // // is it the row of a solid shape, or the first or the last row of the hollow shape
if (shapeAppearance == 'y'
|| r == 0
|| r == height - 1)
{
// is it time to write the real row?
if (c == height - r - 1)
{
// string type 1: full of asterisks
string asteriskString(r*2 + 1, '*');
cout << asteriskString;

// maintain the loop invariant
c += (r*2 + 1);
}
else
{
cout << ' ';
// maintain the loop invariant
++c;
}
}
else
{
// is it time to write the real row?
if (c == height - r - 1)
{
// string type 2: blanks and asterisks
string blankString(((r-1)*2 + 1), ' ');
string mixedString = "*" + blankString + "*";

cout << mixedString;
// maintain the loop invariant
c += ((r-1)*2 + 1 + 2);
}
else
{
cout << ' ';
++c;
}
}
}
cout << endl;
}
}
-

A complete program and tests

Let’s put all pieces together

-
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
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
//Accelerated C++ Solutions Exercises 2-5
#include <iostream>
#include <string>

// using namespace std and names
using std::cout; using std::cin;
using std::endl; using std::string;

int main()
{
// select a shape
cout << "Select a shape type\n";
cout << "1. A Square\n2. A Rectangle\n3. A Triangle\n";
cout << "Please enter 1 or 2 or 3: ";

// read the shape type
int shapeType;
cin >> shapeType;

// write a blank line for clarity
cout << endl;

// select appearance
cout << "Do you want a solid shape?\n";
cout << "yes: a solid shape\nno: a hollow shape\n";
cout << "Please enter y or n: ";

// read the shape appearance
char shapeAppearance;
cin >> shapeAppearance;

// write a blank line for clarity
cout << endl;

if (shapeType == 1)
{
cout << "Please enter the edge length: ";
int edgeLength;
cin >> edgeLength;

// write a blank line to separate inputs and outputs
cout << endl;

// string type 1: full of asterisks
string asteriskString(edgeLength, '*');

// string type 2: blanks and asterisks
string blankString(edgeLength - 2, ' ');
string mixedString = "*" + blankString + "*";

for (int r = 0; r != edgeLength; ++r)
{
if (shapeAppearance == 'y' || r == 0 || r == edgeLength - 1)
// write a row whose length is the edge length
cout << asteriskString << endl;
else
cout << mixedString << endl;
}
}
else if (shapeType == 2)
{
cout << "Please enter the base length and height\n";
cout << "The base lenght = ";

// read the base length
int baseLength;
cin >> baseLength;

cout << "The height = ";

// read the height
int height;
cin >> height;

// write a blank line to separate inputs and outputs
cout << endl;

// string type 1: full of asterisks
string asteriskString(baseLength, '*');

// string type 2: blanks and asterisks
string blankString(baseLength - 2, ' ');
string mixedString = "*" + blankString + "*";

for (int r = 0; r != height; ++r)
{
if (shapeAppearance == 'y' || r == 0 || r == height - 1)
cout << asteriskString << endl;
else
cout << mixedString<< endl;
}
}
else if (shapeType == 3)
{
cout << "Please enter the height: ";
int height;
cin >> height;

// write a blank line to separate inputs and outputs
cout << endl;

// loop invariant: we have written r rows now
for (int r = 0; r != height; ++r)
{
// loop invariant: we have written c characters so far in the current line
int c = 0;

while(c != (height*2 - 1))
{
if (shapeAppearance == 'y' || r == 0 || r == height - 1)
{
if (c == height - r - 1)
{
// string type 1: full of asterisks
string asteriskString(r*2 + 1, '*');
cout << asteriskString;

// maintain the loop invariant
c += (r*2 + 1);
}
else
{
cout << ' ';
// maintain the loop invariant
++c;
}
}
else
{
if (c == height - r - 1)
{
// string type 2: blanks and asterisks
string blankString(((r-1)*2 + 1), ' ');
string mixedString = "*" + blankString + "*";

cout << mixedString;
// maintain the loop invariant
c += ((r-1)*2 + 1 + 2);
}
else
{
cout << ' ';
++c;
}
}
}
cout << endl;
}
}
return 0;
}
-

Note that I used the same variables names asteriskString, blankString and mixedString in all three branches, but it does not matter the correctness as each branch is an independent block. I did three tests and the program works as expected. Please see the results below

-

Test 1

-
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
Select a shape type
1. A Square
2. A Rectangle
3. A Triangle
Please enter 1 or 2 or 3: 1

Do you want a solid shape?
yes: a solid shape
no: a hollow shape
Please enter y or n: y

Please enter the edge length: 5

*****
*****
*****
*****
*****
-

Test 2

-
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
Select a shape type
1. A Square
2. A Rectangle
3. A Triangle
Please enter 1 or 2 or 3: 2

Do you want a solid shape?
yes: a solid shape
no: a hollow shape
Please enter y or n: n

Please enter the base length and height
The base lenght = 8
The height = 4

********
* *
* *
********
-

Test 3

-
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
Select a shape type
1. A Square
2. A Rectangle
3. A Triangle
Please enter 1 or 2 or 3: 3

Do you want a solid shape?
yes: a solid shape
no: a hollow shape
Please enter y or n: n

Please enter the height: 6

*
* *
* *
* *
* *
***********
- -
-

To be continued.

+ + + + Exercise 2-4The framing program writes the mostly blank lines that seperate the borders from the greeting one character at a time. Change the program + ... + +
+ + Read more » + +
+ + +
+ + + + + -
+ +
+ + + + + + + +
-
+ +
+

+ + +

+ + + + + + + - +
-
- + +
+ + +
-

- - -

+ + + +

+ +

+
+ @@ -601,273 +560,178 @@

-

In last chapter, the authors Koenig and Moo presents how to write a program that can produce a framed greeting. This chapter is to make the program more flexible so that we can change the size of the frame easily. Let’s have a look at the framed greeting again:

-
1
2
3
4
5
********************
* *
* Hello, Estragon! *
* *
********************
-

It has been observed that the greeting work has following characteristics:

-
    -
  • it consist of three elements: the greeting itself, asterisks and spaces.
  • -
  • the greeting itself will be fixed once we finished inputing.
  • -
  • the greeting itself is centrally located, meaning that it is surrounded by blank rows and the same number of black columns.
  • -
  • the outmost rows and columns are constituted by asterisks.
  • -
-

Clearly, the size of the frame is determined by the length of the greeting itself and the number of blank lines(i.e. spaces in this case). Now, we can define a variable named pad that represents the length of spaces (e.g. 1):

-
1
const int pad = 1;
-

Then, we can indicate the number of rows with defining another variable:

-
1
const int rows = 1 + 2 + 2 * pad;
-

The number 1 means the row of the greeting itself and the number 2 means two edges formed by asterisks. Once we know the number of rows, we can write the program similar as the the original one which defines several variables for each row and prints one by one. However, we need to change the code every time we change the frame size. This problem can be tackled with using iterative statements which will fully utilize the information listed above including the position of each type of elements.

-

While statement & for statement

A while statement repeatedly executes a given statement as long as a pre-dertimined condition is true. The syntax of a while statement is

-
1
2
while (condition)
statement
-

The processing of while statement starts from testing the condition and then executes the statement (aka, the while body) if the condition is true, and it starts all over again until the condition becomes false. Our purpose is to write each row, totalling rows times. Hence, what the program need here is a counting variable which is used to control the outputs.

-
1
2
3
4
5
6
7
int r = 0; // counting variable with initial value 0
while (r != rows)
{
// write a row of output
std::cout << std::endl;
++r;
}
-

The while loop requires a single statement but the logic of our program need more. Hence, we can use a compound statement which refers to a block formed by a pair of curly braces. As shown above, We can write a sequence of statements and declarations or empty statement in the block. Note that the block forms a scope and the right brace (}) indicates the end of the statement.

-

The condition is an expression that returns bool type value (true or false). When the condition yields an arithmetic type value, the value will be converted to a value of bool type:

-
    -
  1. zero values convert to false
  2. -
  3. non-zero values convert to true
  4. -
-

The inequality operator (!=) test the inequality between r and rows. The table below gives the logical and rational operators (all returns a value of bool type):

- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
AssociativityOperatorFunction
Right!Logical NOT
Left<less than
Left<=less than or equal
Left>greater than
Left>=greater than or equal
Left==equality
Left!=inequality
Left&&Logical AND
Left||Logical OR
Source: C++ Primer 2012
-

The ++() is the increment (decrement) operator which adds (subtract) 1 to the variable r. The expression is equivalent to

-
1
r = r + 1;
-

If we want to add another variable with varing values each time, we can use compound assignment operators (refer to arithmetic operators)

- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
+=-=*=/=%/
For example, x += y; is equivalent to
1
x = x + y;
Comparing with the ordinary assignment, compound assignment only evaluate the left operand once while the ordinary assignment evaluate twice.
-

Let’s get back to the while statement. According to the condition, the counting process will starts from 0 till rows-1 and the statement is expected to be executed rows times. In this case, an alternative loop structure can also be considered, a for statement

-
1
2
for (init-statement; condition; expression) // for header
statement // for body
-

The for header controls the for body which contains the statement that needs to be executed. The init-statement can only be one of three statements: declaration statement, an expression statement and anull statement(using a semicolon).
The processing order of the for statement is

-
    -
  1. at the begining of the loop, init-statement will be executed.
  2. -
  3. then, condition will be evaluated and the for body would be executed if the condition returns true. Otherwise, the loop terminates.
  4. -
  5. if condition is true, the last step of the loop is to execute expression.
  6. -
-

In this case, the for statement can be write as

-
1
2
3
4
5
for (int r; r != rows; ++r)
{
// write a row of output
std::cout << std::endl;
}
-

Loop invariant

To verify the correctness of the loops, the book introduces two techniques. The first is that the condition must be false when the loop finishes. In this case, when the while statement finishes, r != rows is false. Another one is to use loop invariant which is a property that is true before and after each loop. In this example, the loop invariant is not the core language but a comment before the while statement:

-
1
// invariant: we have written r rows so far
-

The loop invariant shows that the while statement works as expected, and in turn the program should meet the requirements that invariant is true in each iteration. There are two specific points for verify this:

-
    -
  1. the first point is before the first time that the condition is evaluated. In this example, we know it is correct as there is no output yet.
  2. -
  3. the second point is before the end of the while body. Once the while body is executed, one row will be written. To meet the requirement, we increase the value of r by adding 1 each time.
  4. -
-

When the while statement finishes, there will be rows lines are written as r = rows. The below piece of code shows how the loop invariant plays its role.

-
1
2
3
4
5
6
7
8
9
10
11
12
13
14
// invariant: we have written r rows so far
int r = 0;
// setting r to 0 makes the invariant true
while (r != rows)
{
// we can assume the invariant is true here

// writting a row of output makes the invariant false
std::cout << std::endl;

// incrementing r makes the invariant true again
++r;
}
// we can conclude that the invariant is true here
-

Up to now, we have figured how to control the loops for producing right number of rows. Similarly, we can use while loop or for loop to produce every character of each specific row. By the analogy, the first step is to define a variable to representing the number of columns. But the difference is that the number of columns is not only determined by blanks but also by the size of the greeting itself.

-
1
const std::string::size_type cols = greeting.size() + pad * 2 + 2;
-

The variable cols is defined as a std::string::size_type to keep consistent with the type of greeting.size(). This knowledge has been introduced in my last notes. We can also use type specifiers auto or decltype. For example

-
1
const auto cols = greeting.size() + pad*2 + 2;
-

What if we use int type directly? Then the variable might be insufficient if one input a name that is long enough. Now we can write the structure for writing a specific row.

-
1
2
3
4
5
6
7
8
std::string::size_type c = 0;

// invariant: we have written c characters so far in the current row
while (c != cols)
{
// write one or more characters
// adjust the value of c to maintain the invariant
}
-

if statements

Now We already have the whole structure of the program. What we need next is to determine what character is needed to be written for each row and column. As listed at the begining of this notes, we can utilize the characteristic of the target. I’ll further transform the information to plain english for clarify the logic of the program. There are three components in the outputs: asterisk, spaces and the greeting. The asterisks form the border of the frame and will be written when the loop is processing

-
    -
  • the first row or
  • -
  • the last row or
  • -
  • the first column or
  • -
  • the last column
  • -
-

These conditions can be expressed by means of the logical operators and relational operators

-
1
r == 0 || r == rows - 1 || c == 0 || cols - 1
-

The relational operators == have lower precedence than the arithmetic operators. Therefore, r == rows - 1 is equivalent to r == (rows - 1).

-

The logical OR operator has lower precedence than the relational operators. Therefore, above expression means

-
1
(r == 0) || (r == rows - 1) || (c == 0) || (cols - 1)
-

Moreover, the logical OR (as well as the logical AND) is left associative and the expression is evluated using the short-circuit strategy, that is, “the right side of an || is evaluated if and only if the left side is false (true for the logical AND)” (Lippman ect. 2012, p.154).

-

To evaluate above condition, we use the if statement

-
1
2
if (condition) 
statement
-

or if else statement

-
1
2
3
4
if (condition)
statement1
else
statement2
-

or nested if statement

-
1
2
3
4
if (condition1)
statement1
else if (condition2)
statement2
-

Now we have got a complete structure for printing out the border

-
1
2
3
4
5
6
7
8
9
10
11
if (r == 0 || r == rows - 1 || c == 0 || cols - 1)
{
std::cout << "*";
++c;
}
else
{
if (other condition)
// write one or more noborder characters
// adjust the value of c to maintain the invariant
}
-

By the analogy, we can determine the condition for outputing the greeting

-
1
r == pad + 1 && c == pad + 1
-

Note that we don’t need to write the condition of the each character of the greeting as once we find the row and the initial position we can print out the variable directly. Therefore, the value of the counting variable c will add a value of greeting.size() to maintain the invairant. Finally, it is not necessary to find the condition for the space characters as the remainder of the frame can only be spaces. Let’s combine three situations together

-
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
if (r == 0 || r == rows - 1 || c == 0 || cols - 1)
{
std::cout << "*";
++c;
}
else
{
if (r == pad + 1 && c == pad + 1)
{
std::cout << greeting;
c += greeting.size();
}
else
{
std::cout <<" ";
++c;
}
}
-

One common mistake in using if statement is that missing the curly braces after if or else when there are multiple statements. We should pay much attention on this in the case of nested if statements as there may exist more if branches than else branches. It is confusing how each else match with the if branches, which is commonly termed as dangling else. In the default seeting, C++ specifies that each else is matched with the closest preceding unmatched if. To avoid the ambiguity, it is recommended to control execution with curly braces.

-

The complete framing program

Now we can put all pieces together to get the complete framing program. Note there are several ways to organize the if statements and both while loop and for loop are available. Therefore, the program below is only one of the correct programs.

-
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
#include <iostream>
#include <string>

// using namespace std and names
using std::cout; using std::cin;
using std::endl; using std::string;

int main()
{
// ask for the person's name
cout << "Please enter your first name: ";

// read the name
string name;
cin >> name;

// build the message that we intend to write
const string greeting = "Hello, " + name + "!";

// the number of blanks surrounding the greeting
const int pad = 2;

// the number of rows and columns
const int rows = pad * 2 + 3;
const string::size_type cols = greeting.size() + pad * 2 + 2;

// write a blank line to separate the output from the input
cout << endl;

// write rows rows of output
// invariant: we have written r rows so far
for (int r = 0; r != rows; ++r)
{
// we have written c characters so far in the current row
string::size_type c = 0;
while (c != cols)
{
// are we on the border?
if (r == 0 || r == rows - 1 ||
c == 0 || c == cols - 1)
{
cout << "*";
++c;
}
else
{ // is it time to write the greeting?
if (r == pad + 1 && c == pad + 1)
{
cout << greeting;
c += greeting.size();
}
else
{
cout <<" ";
++c;
}
}
}
cout << endl;
}
return 0;
}
-

Now we can test the program with changing the number of blanks to 2, and type Bruce after the input prompt. It gives the outputs as we expected.

-
1
2
3
4
5
6
7
8
9
Please enter your first name: Bruce

*******************
* *
* *
* Hello, Bruce! *
* *
* *
*******************
+ + + + + + In last chapter, the authors Koenig and Moo presents how to write a program that can produce a framed greeting. This chapter is to make the program mo + ... + +
+ + Read more » + +
+ +
+ + + + + -
+ +
+ + + + + + + +
-
+ +
+

+ + +
+ + + + + + + - +
-
- + +
+ + +
-

- - -

+ + + +

+ +

+
+ @@ -875,119 +739,179 @@

-

Exercise 2-0

Compile and run the program presented in this chapter

-

Solution & Results

This exercise has been accomplished in C++ - Looping and counting with detailed explination.

-
-

Exercise 2-1

Change the framing program so that it writes its greeting with no separation from the frame.

-

Solution & Results

This can be easily accomplished by changing the number of the blanks to 0.

-
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
#include <iostream>
#include <string>

// using namespace std and names
using std::cout; using std::cin;
using std::endl; using std::string;

int main()
{
// ask for the person's name
cout << "Please enter your first name: ";

// read the name
string name;
cin >> name;

// build the message that we intend to write
const string greeting = "Hello, " + name + "!";

// the number of blanks surrounding the greeting
// change to 0 to meet the requirement
const int pad = 0;

// the number of rows and columns
const int rows = pad * 2 + 3;
const string::size_type cols = greeting.size() + pad * 2 + 2;

// write a blank line to separate the output from the input
cout << endl;

// write rows rows of output
// invariant: we have written r rows so far
for (int r = 0; r != rows; ++r)
{
string::size_type c = 0;
// we have written c characters so far in the current row
while (c != cols)
{
// are we on the border?
if (r == 0 || r == rows - 1 ||
c == 0 || c == cols - 1)
{
cout << "*";
++c;
}
else
{ // is it time to write the greeting?
if (r == pad + 1 && c == pad + 1)
{
cout << greeting;
c += greeting.size();
}
else
{
cout <<" ";
++c;
}
}
}
cout << endl;
}
return 0;
}
-

Again, once we typed Bruce after the input prompt, the program writes below outputs on the console window

-
1
2
3
4
5
Please enter your first name: Bruce

***************
*Hello, Bruce!*
***************
-

Analysis

See deatiled analysis in C++ - Looping and counting.

-
-

Exercise 2-2, 2-3

2-2. Change the framing program so that it uses a different amount of space to seperate sides from the greeting than it uses to seperate the top and botton borders from the greeting.

-

2-3. Rewrite the framing program to ask the user to supply the amount of spacing to leave between the frame and the greeting.

-

These two exercises will be answered together.

-

Solution & Results

It has been seen from the program (as shown in Exercise 2-1), the variable pad controls the spaces. Therefore, it is necessary to replace the pad with two seperate variables for controling the spaces on the leftside and rightside, and the spaces on the upside and downside, respectively. For example, I define two variables with initializers 2 and 3 seperately

-
1
2
const int lr_sides_pad  = 2; // controls left and right blanks
const int tb_sides_pad = 3; // controls top and bottom blanks
-

Correspondingly, the size of rows becomes

-
1
const int rows = tb_sides_pad * 2 + 3
-

and the size of cols becomes

-
1
const string::size_type cols = greeting.size() + lr_sides_pad * 2 + 2;
-

In addition, the condition for determining the position of the greeting changes to

-
1
r == tb_sides_pad + 1 && c == lr_sides_pad + 1
-

Up to now, I have replaced all pad with above two new variables. However, the frame size is still predetermined in the program and not very flexible. We further change it so that the size can meet each user’s preference. We need to redifine the variables and assign user-defined values to them.

-
1
2
3
4
5
6
7
8
9
10
11
12
13
// ask for the number of blanks that on the left or right side
cout << "Please enter the number of spaces between the greeting and the left border (or the right boder): ";

// read the number of blanks that on the left or right side
int lr_sides_pad;
cin >> lr_sides_pad;

// ask for the number of blanks that on the upper or below side
cout << "Please enter the number of spaces between the greeting and the top border (or the bottom boder): ";

// read the number of blanks that on the left or right side
int tb_sides_pad;
cin >> tb_sides_pad;
- -

Now the program becomes

-
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
65
66
67
68
69
70
71
72
73
74
#include <iostream>
#include <string>

// using namespace std and names
using std::cout; using std::cin;
using std::endl; using std::string;

int main()
{
// ask for the person's name
cout << "Please enter your first name: ";

// read the name
string name;
cin >> name;

// build the message that we intend to write
const string greeting = "Hello, " + name + "!";

// ask for the number of blanks that on the left or right side
cout << "Please enter the number of spaces between the greeting and the left border (or the right boder): ";

// read the number of blanks that on the left or right side
int lr_sides_pad;
cin >> lr_sides_pad;

// ask for the number of blanks that on the upper or below side
cout << "Please enter the number of spaces between the greeting and the top border (or the bottom boder): ";

// read the number of blanks that on the left or right side
int tb_sides_pad;
cin >> tb_sides_pad;

// the number of rows and columns
const int rows = tb_sides_pad * 2 + 3;
const string::size_type cols = greeting.size() + lr_sides_pad * 2 + 2;

// write a blank line to separate the output from the input
cout << endl;

// write rows rows of output
// invariant: we have written r rows so far
for (int r = 0; r != rows; ++r)
{
string::size_type c = 0;
// we have written c characters so far in the current row
while (c != cols)
{
// are we on the border?
if (r == 0 || r == rows - 1 ||
c == 0 || c == cols - 1)
{
cout << "*";
++c;
}
else
{ // is it time to write the greeting?
if (r == tb_sides_pad + 1 &&
c == lr_sides_pad + 1)
{
cout << greeting;
c += greeting.size();
}
else
{
cout <<" ";
++c;
}
}
}
cout << endl;
}
return 0;
}
-

I performed three tests and present the results here

-

Test 1: input: Bruce 2 3

1
2
3
4
5
6
7
8
9
10
11
12
13
Please enter your first name: Bruce
Please enter the number of spaces between the greeting and the left border (or the right border): 2
Please enter the number of spaces between the greeting and the top border (or the bottom border): 3

*******************
* *
* *
* *
* Hello, Bruce! *
* *
* *
* *
*******************
-

Test 2: input: Bruce 4 0

1
2
3
4
5
6
7
Please enter your first name: Bruce
Please enter the number of spaces between the greeting and the left border (or the right border): 4
Please enter the number of spaces between the greeting and the top border (or the bottom border): 0

***********************
* Hello, Bruce! *
***********************
-

Test 3: input: Bruce 5 5

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
Please enter your first name: Bruce
Please enter the number of spaces between the greeting and the left border (or the right border): 5
Please enter the number of spaces between the greeting and the top border (or the bottom border): 5

*************************
* *
* *
* *
* *
* *
* Hello, Bruce! *
* *
* *
* *
* *
* *
*************************
-

Analysis

See deatiled analysis in C++ - Looping and counting.

-
-

To be continued.

+ + + + Exercise 2-0Compile and run the program presented in this chapter +Solution & ResultsThis exercise has been accomplished in C++ - Looping and count + ... + +
+ + Read more » + +
+ + +
+ + + + + -
+ +
+ + + + + + + +
-
+ +
+

+ + +
+ + + + + + + - +
+ -
- +
+ + +
-

- - -

+ + + +

+ +

+
+ @@ -995,182 +919,179 @@

-

Exercise 1-0

Compile, execute, and test the programs in this chapter.

-

Solution & Results

The first program:

-
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
// ask for a person's name, and greet the person
#include <iostream>
#include <string>

int main()
{
// ask for the person's name
std::cout << "Please enter your first name: ";

// read the name
std::string name; // define name
std::cin >> name; // read into

// write a greeting
std::cout << "Hello, " << name << "!" << std:: endl;
return 0;
}
-

Test: type Liam according to the prompt and the console window displays as follows:

-
1
2
Please enter your first name: Liam
Hello, Liam!
-

The second 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
// ask for a person's name, and generate a framed greeting
#include <iostream>
#include <string>

int main()
{
std::cout << "Please enter your first name: ";
std::string name;
std::cin >> name;

// build the message that we intend to write
const std::string greeting = "Hello, " + name + "!";

// build the second and forth lines of the output
const std::string spaces(greeting.size(), ' ');
const std::string second = "* " + spaces + " *";

// build the first and fifth lines of the output
const std::string first(second.size(), '*');

// write it all
std::cout << std::endl;
std::cout << first << std::endl;
std::cout << second << std::endl;
std::cout << "* " << greeting << " *" << std::endl;
std::cout << second << std::endl;
std::cout << first << std::endl;

return 0;
}
-

Test: type Liam according to the prompt and the console window displays as follows:

-
1
2
3
4
5
6
7
Please enter your first name: Liam

****************
* *
* Hello, Liam! *
* *
****************
- -

Analysis

See C++ - Working with strings.

-
-

Exercise 1-1

Are the following definitions valid? Why or why not?

-
1
2
const std::string hello = "Hello";
const std::string message = hello + ", world" + "!";
-

Solution & Results

Yes, above definitions are valid.

-

The first statement defines a string type variable named hello and initialize the variable with copying string literals Hello into it. The second statement defines a variable named message and copy the value of the right side of the = into it. The right side is an expression that the concatenation operator + operates on the object hello and two string literals. First, the operator is left-associative and hence:

-
1
2
const std::string message = hello + ", world" + "!";
= (hello + ", world") + "!";
-

Due to the fact that the result of (hello + “, world”) is also a string type object, the whole expression is simply to concatenate a string and a string literals, which is legal and valid. The test is shown below:

-
1
2
3
4
5
6
7
8
9
10
11
12
13
#include <iostream>
#include <string>
using std::cout; using std::endl; using std::string;

int main()
{
const std::string hello = "Hello";
const std::string message = hello + ", world" + "!";
cout << hello << endl;
cout << message << endl;
return 0;

}
-

The program runs ok and display results on the console window as below:

-
1
2
Hello
Hello, world!
-

Analysis

As stated by Lippman ect. (2012): “When we mix strings and string or character literals, at least one operand to each + operator must be of string type”. For more analysis, please see C++ - Working with strings.

-
-

Exercise 1-2

Are the following definitions valid? Why or why not?

-
1
2
const std::string exclam = "!";
const std::string message = "Hello" + ", world" + exclam;
-

Solution & Results

The definition of exclam is valid but the definition of message is illegal and hence invalid. This is because that the operator + is left-associative and cannot concatenate two string literals. Follow program shows my test:

-
1
2
3
4
5
6
7
8
9
10
11
12
13
#include <iostream>
#include <string>
using std::cout; using std::endl; using std::string;

int main()
{
const std::string exclam = "!";
const std::string message = "Hello" + ", world" + exclam;
cout << exclam << endl;
cout << message << endl;
return 0;

}
-

As expected, the compilation reports errors as below:

-
1
invalid operands of types 'const char [6]' and 'const char [8]' to binary 'operator+'.
-

Analysis

See Exercise 1-1 and
C++ - Working with strings.

-
-

Exercise 1-3

Is the following program valid? If so, what does it do? If not, why not?

-
1
2
3
4
5
6
7
8
9
10
11
#include <iostream>
#include <string>

int main()
{
{ const std::string s = "a string";
std::cout << s << std::endl; }
{ const std::string s = "another string";
std::cout << s << std::endl; }
return 0;
}
-

Solution & Results

Yes, it is a valid program.
The program intends to print two different strings, both of which have the same name s within the main function body.
I firstly present the result when running the program:

-
1
2
a string
another string
-

These two varibles are not conflict because their names have different scopes that formed by two pairs of curly braces. Specifically:

-
    -
  • Both variables are local variables as they are inside of the main function.
  • -
  • The first s is visible from its declaration until the end of its scope, that is, the scope formed by the first nested curly braces.
  • -
  • The second s is visible from its declaration until the end of the second nested curly braces.
  • -
  • Two names refer to different entities in different scope.
  • -
-

From the perspective of memory management, the first variable exists only when the part of the program within the first nested braces is executing, and disappears and returns the memory it occupied once the computer reaches the end of the braces. It has limited lifetime and so does the second variable.

-

Analysis

See C++ - Working with strings.

-
-

Exercise 1-4

Question 1

Is the following program valid?

-
1
2
3
4
5
6
7
8
9
10
11
#include <iostream>
#include <string>

int main()
{
{ const std::string s = "a string"; //outer scope
std::cout << s << std::endl;
{ const std::string s = "another string"; // inner scope
std::cout << s << std::endl; }}
return 0;
}
-

Solution & Results

Yes, the program is valid.
In constrast with the last program, the scopes of two names are not independent with eachother but are nested:

-
    -
  • The scope of the first s is the outer scope, containing the other scope, that is, the inner scope where the second s is in.
  • -
  • The name that declared in the outer scope can be can be accessed and reused in the nested scope, i.e. inner scope.
  • -
-

Therefore, the logic of this program can be described as:

-
    -
  1. the first variable is defined and initialized with string literals “a string” and is printed out in the following statement.
  2. -
  3. “the variable” is redefined in the inner scope and initialized with string literals another string. Then, it is printed out in the following step.
  4. -
  5. the second s is destroyed at the end of its scope, that is, the first right brace (}).
  6. -
  7. the first s is still available after the first right brace, but is destroyed once the computer reaches the second right brace.
  8. -
-

Note that two variables are different and the second variable doesn’t overwriting the first one though they both use the name s. To confirm this, I simply add one statement between the first right curly brace and second right curly brace:

-
1
std::cout << s <<std::endl;
-

Then, I run the program and it yields:

-
1
2
3
a string
another string
a string
-

Question 2

what if we change } } to };} in the third line from the end?

-
1
2
3
4
5
6
7
8
9
10
11
#include <iostream>
#include <string>

int main()
{
{ const std::string s = "a string";
std::cout << s << std::endl;
{ const std::string s = "another string";
std::cout << s << std::endl; };}
return 0;
}
-

Solution & Results

The program is still valid after adding an semicolon betwween the first and second right curly brace. The semicolon typically working as a statement terminator in C++. In this case, it does nothing except forming a null statement. This program leads to the same results as the program that without adding a semicolon.

-
1
2
a string
another string
-

Analysis

I didn’t found very good interpretations about the role of the semicolon in C++. For more analysis, please move to What is the semicolon in C++? and What is the function of semicolon in C++? , where some good answers have been provided by the forum users.

-
-

Exercise 1-5

Is this program valid? If so, what does it do? If not, say why not, and rewrite it to be valid.

-
1
2
3
4
5
6
7
8
9
10
11
12
13
#include <iostream>
#include <string>

int main()
{
{ std::string s = "a string";
{ std::string x = s + ", really";
std::cout << s << std::endl;
}
std::cout << x << std::endl;
}
return 0;
}
-

Solution & Results

No, this program is invalid. S
imilarly as last two exercises, this question tries to test my understanding on the scope of a name. The scope of name s is outter scope and can be accessed in the inner scope where the variable x is defined. Therefore, it is correct that initializing x with a expression which oncatenates a string and string literals. It is also ok to output s inside the nested scope. However, the statement std::cout << x << std::endl; would be invalid due to the fact that x doesn’t exist anymore once the computer reaches the end of its scope, that is, the first right curly brace. As expected, when running this program, error occurs:

-
1
'x' was not declared in this scope
-

To correct it, we can simply put the statement:std::cout << x << std::endl; into the inner scope, or directly remove the nested curly braces as long as the name is not redefined in a same scope. Both corrections work well and following results can be seen on the console window:

-
1
2
a string
a string, really
-

Analysis

See Exercise 1-3, Exercise 1-4 and
C++ - Working with strings.

-
-

Exercise 1-6

What does the following program do if, when it asks you for input, you type two names(for example, Samuel Beckett)? Predict the behavior before running the program, then try it.

-
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
#include <iostream>
#include <string>

int main()
{ // prompt that asks for input
std::cout << "What is your name? ";

// define an string type variable named **name** which is empty initially.
std::string name;

// read the contents into **name**
std::cin >> name;

// output greetings as well as asking for input
std::cout << "Hello, " << name
<< std::endl << "And what is yours?";

// read new contents into the same variable
std::cin >> name;

// output greeting
std::cout << "Hello, " << name
<< "; nice to meet you too!" << std::endl;
return 0;
}
-

Solution & Results

The program intends to create a greeting conversation between two people. For For clarity, I added comments for each statement first. Let’s analyse this program step by step:

-
    -
  1. once click the running button, the computer reads the first statement and stores the contents *What is your name? * into buffer.
  2. -
  3. the cin statement triggers the flush of cout, and user can type two names Samuel Beckett according to the prompt. I predict following contents would be written on the output device:
    1
    What is your name? Samuel Beckett
  4. -
  5. then, the cin reads from the first character untill it encounters the whitespace. Therefore, only Samuel is stored into name.
  6. -
  7. the fouth statement would flush the cout because of the manipulator endl. Then, the console windows should have following outputs:
    1
    Hello, Samuel
  8. -
  9. the fifth statement cin flushes buffer and following sentence will be printed out straight after above contents.
    1
    And what is yours?
  10. -
  11. then, cin starts reading the rest content Beckett and stores into name. The new name Beckett rewrites this variable.
  12. -
  13. finally, the last buffer flush happens once the computer reads std::endl. Following contents are expected to appear on the console window.
    1
    Hello, Beckett; nice to meet you too!
  14. -
  15. finished.
  16. -
-

As ecpected, the final results of this program is shown as follows:

-
1
2
3
What is your name? Samuel Beckett
Hello, Samuel
And what is yours?Hello, Beckett; nice to meet you too!
-

Analysis

I am not entirly sure about my analysis, and may update this in the futuer if I get new ideas.

-
-

References

Koenig, A. and Moo, B.E., 2000. Accelerated C++.

-

Lippman, S.B., Lajoie, J. and Moo, B.E., 2013. C++ Primer, 5th edt.

+ + + + Exercise 1-0Compile, execute, and test the programs in this chapter. +Solution & ResultsThe first program:1234567891011121314151617// ask for a pe + ... + +
+ + Read more » + +
+ + +
+ + + + + -
+ +
+ + + + + + + +
-
+ +
+

+ + +
+ + + + + + + - +
-
- + +
+ + +
-

- - -

+ + + +

+ +

+
+ @@ -1178,182 +1099,178 @@

-

Variables, Initialization & Declarations

Conventionally, I present the program that provided in Accelerated C++: Practical Programming by Example here:

-
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
// ask for a person's name, and greet the person
#include <iostream>
#include <string>

int main()
{
// ask for the person's name
std::cout << "Please enter your first name: ";

// read the name
std::string name; // define name
std::cin >> name; // read into

// write a greeting
std::cout << "Hello, " << name << "!" << std::endl;
return 0;
}
-

The program is based on Hello, world! and is improved to say Hello to anyone you specified. It asks you to input a name (e.g. Batman) and then output as follows:

-
1
Hello, Batman!
-

Variables

To realize this function, a variable is defined to hold input. According to Koenig (2000), “a variable is an object that has a name” while “an object is a part of the computer’s memory that has a type”. Therefore, a variable should have:

-
    -
  1. name: for identifying the object that is needed to be manipulated.
  2. -
  3. type: determines the size and layout of the variable’s memory, range of values that can be stored within the memory, and operations that can be applied to the variable.
  4. -
-

In this case, the variable is named name and its type is std::string. The std:: means that the string type is defined in namespace std. Therefore, we need to include the standard header string as same as include the header iostream at the begining of this program. This statement also indicates the syntax of defining variables: a type specifier followed by one name (or more names sepreated by commas), and ends with a semicolon.

-

As this program shows, the variable name is defined within the function body and hence it is a local variable, i.e. the variable is only valid when the function body is executed. Once the computer reachers the }, it destroys the variable name and returns the memory that the variable occupied during its lifetime.

-

Initialization

An object is initialized and gets a specified value (i.e. initializer) when it is created. C++ defines several types of initializations. The example provided above uses default initialization in defining the variable name.

-

Default initialization

The default initialization means that no initializer is applied to a variable when it is defined. In the case of default initialization, the variable gets a default value which depends on its type and may also depends on where the variable is defined. Note that the default value of an object of built-in type, e.g. arithmetic type, depends on where it is defined:

-
    -
  • variables defined outside any function body are initialized to zero (or null character for char type?).
  • -
  • in general, variables of built-in type defined inside a function are uninitialized.
  • -
-

For example, I write a program as below for testing:

-
1
2
3
4
5
6
7
8
9
10
11
12
13
14
#include<iostream>
using std::cout;
using std::endl;

int i; bool h; char m;

int main()
{
int j; bool k; char n;
cout << i << ' ' << j << endl;
cout << h << ' ' << k << endl;
cout << m << ' ' << n << endl;;
return 0;
}
-

The results is:

-
1
2
3
0 4201179
0 0
a
- -

with warnings:

-
1
2
3
'j' is used uninitialized in this function [-Wuninitialized]
'k' is used uninitialized in this function [-Wuninitialized]
'n' is used uninitialized in this function [-Wuninitialized]
-

Clearly, it will result severe errors if we try to access the value or copy the value of such uninitialized variables.

-

Each class defines different ways to initialize objects of the class type. If default initialization is allowed, the default value of an object is determined by the class. For example, the default initialization of a string leads to an empty string (i.e. no characters). In the first program, the variable name is the empty string.

-

Copy & Direct initialization

Another way to initialize variables is using =, that is copy initialize by copying the initializer on the right side into the created object. For example:

-
1
2
3
int i = 10;
char j = 'a';
string s1 = "Hello";
-

The values used to initialize objects can also be expressions:

-
1
2
int i = 10;
int j = i*10;
-

The copy initialization seems like assignment, however, is completely different. As Lippman etc. (2012) point out: “Initialization happens when a variable is given a value when it is created. Assignment obliterates an object’s current value and replaces that value with a new one.”

-

We can also initialize above objects without using “=”:

-
1
2
3
int i(10);
char j('a');
string s1("Hello");
-

This way is named direct initialization. The string type variables can also be initialized using an alternative form:

-
1
2
3
4
5
6
7
8
// direct initialization, s1 is aaaaa
string s1(5, 'a');
// copy initialization, s2 is aaaaa
string s2 = string(5, 'a');

// decomposition of s2
string s0(5, 'a'); // s0 is aaaaa
string s2 = s0; // copy s0 into s2
-

The differences between copy initialization and direct initialization will not be disscussed until chapter 9;

-

List initialization

The C++ 11 standards supports an alternative form for the copy initialization and direct initialization using curly braces. For example:

-
1
2
3
4
int i = {10}; // same as _int i = 10;_
int j{10}; // same as _int j(10);_
string s1 = {"Hello"}; // same as _string s1 = "Hello";_
string s2 {"Hello"}; // same as _string s2("Hello");_
-

This form is known as list initialization. The major difference between list initialization and above methods is that list initialization doesn’t allow narrowing for the conversion of built-in type variables. To show this property, I performed an experiment using following codes:

-
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
#include<iostream>
using std::cout;
using std::endl;

int main()
{
double i = 10.9876;
int num1 = i;
int num2(i);
int num3{i};

cout << num1 << endl; // ok but result is truncated: 10
cout << num2 << endl; // ok but result is truncated: 10
cout << num3 << endl; // warning: narrowing conversion of 'i' from 'double' to 'int' inside { }
return 0;
}
-

Hence, list initialization is a more reliable approach compared with other alternatives.

-

Declarations

As analysed above, the definition of a variable requires type, name and initializers. For convenience, C++ supports separate compilation, that is, allows a program to be split into several files and compiled independently. In fact, we have already used the separate compilation in previous examples, such as the usage of IO system. The std::cout is defined in the header iostream but can be used in our programs through simply including the header file. To realize this function, a variable needs declaration which requires stating the type and name. The difference between declaration and definition is that the variable may be explicitly initialized in its definition. To declare a a variable only, we need add extern and don’t explicitly initialize it:

-
1
2
3
extern int i; // declares but not defines i
int i; // declares and defines i
extern int i = 10; // declares and defines i
-

But it should be noted that(Lippman etc. 2012):

-
    -
  • It is an error to provide an initializer on an extern inside a function.
  • -
  • Variables must be defined exactly once but can be declared many times.
  • -
-

Operations on strings

Reading & Writing strings

To read the inputs into name, this program uses std::cin and operator >>. It will discard whitespace characters such as space, tab and backspace, and then reads chars into name until it encounters another whitespace. For example, following three piece of inputs yield same outputs when we run the program:

-
1
2
3
Bruce
[a tab space]Bruce
Bruce Lee
-

Results:

-
1
2
3
Hello, Bruce!
Hello, Bruce!
Hello, Bruce!
-

The IO library accumulates and stores the characters using an internal data structure named buffer, and flushes the buffer by writing the contents to the output device only when necessary. There are three events that cause the system to flush the buffer:

-
    -
  • the buffer might be full and the library will flush it automatically.
  • -
  • the library might be asked to read from the standard input stream. Then, it will flush the buffer immediately. This indicates another side effect of input operation.
  • -
  • when we explicitly say to do so: e.g. std::endl.
  • -
-

The input operator >> is also left-associative and returns the left-hand operands as their results. Therefore, multiple reads can be done like below codes :

-
1
2
3
string s1, s2; // define two string type variables
cin >> s1 >> s2; // reads from cin into s1 and s2 seprately
cout << s1 << s2 << endl; // writes two strings
-

In some cases, we would like to read chars as well as whitespaces. To fulfill this, we need to use getline instead of >> with the syntax:

-
1
std::getline(std::cin, name)
-

The getline function will read chars into variable name until it encounters line feed (note that the line feed will also be read but not stored into the variable). I modified the program using getline:

-
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
#include <iostream>
#include <string>

int main()
{
// ask for the person's name
std::cout << "Please enter your full name: ";

// read the name
std::string name; // define name
std::getline(std::cin, name); // read into

// write a greeting
std::cout << "Hello, " << name << "!" << std::endl;
return 0;
}
-

When I run the program and input “Bruce Lee”, it results:

-
1
2
Please enter your first name: Bruce Lee
Hello, Bruce Lee!
- -

The string size and concatenate operations

Now we go into a little bit complex operations and aim to produce a framed greeting like below:

-
1
2
3
4
5
6
Please enter your full name: Bruce Lee 
*********************
* *
* Hello, Bruce Lee! *
* *
*********************
-

Let’s provide the program first:

-
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
// ask for a person's name, and generate a framed greeting
#include <iostream>
#include <string>

int main()
{
std::cout << "Please enter your full name: ";
std::string name;
std::getline(std::cin, name);

// build the message that we intend to write
const std::string greeting = "Hello, " + name + "!";

// build the second and forth lines of the output
const std::string spaces(greeting.size(), ' ');
const std::string second = "* " + spaces + " *";

// build the first and fifth lines of the output
const std::string first(second.size(), '*');

// write it all
std::cout << std::endl;
std::cout << first << std::endl;
std::cout << second << std::endl;
std::cout << "* " << greeting << " *" << std::endl;
std::cout << second << std::endl;
std::cout << first << std::endl;

return 0;
}
-

The first three statements are exactly the same as those in the last program. However, we define a new string type variable named greeting and initialize it with the message that we will write using opetator +:

-
1
const std::string greeting = "Hello, " + name + "!";
-

The keyword const means that the value of the variable keeps constant since the first time read in. The + concatenate two strings (may also be one string and one string literals but cannot be two string literals) into a single string. As mentioned in previous chapter, operators have different effect on operands depending on the types of operands, which is commonly termed as overloaded. Same as operators >> and <<, + is also left-associative.

-

The remainder parts of the frame is simply constituted by asterisks and spaces while the numbers of asterisks and spaces are determined by the size of greeting. Recalling the ways to initialize a string type variable filled by same chars(e.g. ‘a’):

-
1
string s0(5, 'a'); // s0 is aaaaa
-

What we need here is to figure out the size of greeting. greeting.size() is a member function of the object. By calling this member function, we will obtain an integer that means the number of chars in greeting. The returned value is in fact not a int type value, but a string::size_type. In this case, we know that this value is unsigned and hence cann’t compare this value with other signed values for avoiding errors. If we don’t know what exactly the type is, we can use type deduction with specifiers auto and decltype. For example:

-
1
auto len = greeting.size();
-

The variable len is string::size_type. Similarly:

-
1
decltype(greeting.size) len;
-

The differences of these two specifiers are:

-
    -
  • the variable that defined using auto must have initializer.
  • -
  • auto‘s type deduction ignores top-level consts but keeps low-level consts. But decltype‘s type deduction returns type including top-level consts. (This will be discussed again in chapter 10).
  • -
-

Random access using a subscript

If there needs to access some characters of the string, we can use the subscript operator([]) to access and sequentially operate on individual characters. Inside the operator, an index value is required to denote the position of the character to be accessed. The operation returns a reference to the character of the given position.

-

The index value should be a value of type string::size_type, and its range should be >= 0 and < size(). In other words, it use asymmetric range [0, size()). To show more about the random access, I did a experiment as below

-
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
#include <iostream>
#include <vector>

using std::cin; using std::string;
using std::cout; using std::endl;

int main()
{
string str = {"abcdef"};

// obtain the size of str1
string::size_type strSize = str.size();

// define an signed type variable
int x = 0;

// write the string one character by one character
cout << str[x] << str[1] << str[2] << str[3]
<<str[4] << str[strSize-1] << endl;

// access the character beyond the range of [0, strSize-1)
cout << str[strSize] << str[strSize + 1] << str[10] << endl;

return 0;
}
-

Results:

-
1
2
abcdef
b
-

The example above shows that it leads to unknown results if we use a subscript that beyond the range of [0, size()). In addition, it works if we use a integer value has a signed type (in contrast, the string::size_type is unsigned integeral value).
This is because the index value of the signed type can be converted to the type of string::size_type.

-

Other operations

empty function returns a bool type value that indicates whether the string type variable is empty.

-
1
greeting.empty(); // if greeting is empty, it returns 1, otherwise returns 0;
-

<, <=, >, >= “test whether one string is less than, less than or equal to, greater than, or greater than or equal to another. These operators use the same strategy as a (case-sensitive) dictionary” (Lippman etc. 2012, p80).

-
-

Next: C++ - Looping and counting.

+ + + + Variables, Initialization & DeclarationsConventionally, I present the program that provided in Accelerated C++: Practical Programming by Example h + ... + +
+ + Read more » + +
+ + +
+ + + + + -
+ +
+ + + + + + + +
-
+ +
+

+ + +
+ + + + + + + - +
+ -
- +
+ + +
-

- - -

+ + + +

+ +

+
+ @@ -1361,247 +1278,181 @@

-

All the programs were compiled and excuted within the setting of Eclipse CDT and MinGW64

-
-

Exercise 0-0

Compile and run the Hello, world! program

-

Solution & Results

Compile and run Hello, world! program with MinGW + Eclipse CDT:

-

Hello, world!

-

Analysis

See C++ - Getting Started.

-
-

Exercise 0-1

What does the following statement do?

-
1
3 + 4;
-

Solution & Results

The expression statement yields 7 as its results because it contains two int type operands (i.e. 3 and 4), and one operator (i.e. addition). However, it has no side effects on the state of the program and the implementation. Hence, there should be nothing displayed on the console when the program is excuated. As expected, the graph below shows the result along with a warning description statement has no effect ‘3+4’.

-

Expression Statement: 3+4;

-

Analysis

See C++ - Getting Started.

-
-

Exercise 0-2

Write a program that, when run, writes

-
1
This (") is a quote, and this (\) is a backslash.
-

Solution & Results

It seems that this program is exactly the same as the Hello, world! program in Exercise 0-0 once we replace the string literals. In doing so, however, the compiler reports errors as below graph shows.

-

Compilation errors

-

The the occurrence of errors is due to the facts:

-
    -
  1. Characters should be enclosed in double quotes and the string literals is not allowed to span lines.
  2. -
  3. Some characters such as backslash or single/double quotes have special meaning in C++ language.
  4. -
  5. Some characters such as backspace or control character are nonprintable.
  6. -
-

Therefore, it is impossible to use such characters directly. Instead, the language provides escape sequence to represent such characters. The escape sequence begins with a backslash, i.e. in the form of \+character. The follow graph shows the right solution to this question.

-

String literals output

-

Analysis

The below table gives the escape sequences that are defined in C++ language.

- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
newline\nhorizontal tab\talert bell\a
vertical\vbackspace\bdouble quote\“
backslash\\question mark?single quote\‘
carriage return\rformfeed\f
-

One can choose another syntax for using Escape sequence: a backslash followed by by hexadecimal or octal digits while the value represents the numerical value of the character. For example, the linefeed command in C++ can be written in three ways:

- - - - - - - - - - - - - - - - - - - -
character\n
octal digits\12
hexadecimal digits\x0a
-

If a backslash followed by more than three octal digits, the escape sequence is only valid for the first three digits while the rest digits will be read as normal character. For example, ‘\12345’ is equivalent to ‘S’ followed by ‘4’ and ‘5’. But if a backslash followed by the hex digits, the escape sequence uses all the hex digits.

-
-

Exercise 0-3

The string literal “\t” represents a tab character; different C++ implementations display tabs in different ways. Experiment with your implementation to learn how it treats tabs.

-

Solution & Results

In my view, the different implementations could be interpretated as: different C++ compilers or different versions, which may have different interpretation of C++ standards or different support for certain C++ features. Different IDEs or editors might also have an impact on the final displays of results due to various settings. Actually, I am not very sure about my understanding on this.

-

To test how different implementations display tabs, I wrote an output statement in which the Horizontal tab ‘\t’ is inserted into a string literals in a random manner. Then, I run the program in four different environments. First I present the codes here:

-
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
// Accelerated C++ Solution to Exercises 0-3
#include <iostream>
using std::cout;
using std::endl;

int main()
{
// for loops for generating 10 groups' string literals.
for (int j = 3; j < 13; j++)
{ // for loops for generating each character of the string literals
for (char i = 'A'; i < 'z'; i++)
{
cout << i;
// the insertation of the horizontal tab is
// conditional on the bool expression: (int)i % j == 0
if ((int)i % j == 0)
cout << '\t';
}
cout << endl;
}
return 0;
}
-

The following content displays four environment settings and the results.

-

Linux GNU(G++) + Vim Editor

Linux GNU + Vim

-

Linux GNU(G++) + Eclipse

Linux GNU + Eclipse

-

Windows MinGW + Vim Editor

MinGW + Vim

-

Windows MinGW + Eclipse

MinGW + Eclipse

-

Unfortunately, the experiments failed to response the question correctly as there is no difference between these implementations regarding to displaying tabs. But it reveals that the tab stops every 8 spaces and always aligns the followed text to the next stop, regardless of the position where the tab is inserted into. I will keep focus on this question and try to find a better solution to this question.

-

Analysis

To be continued.

-
-

Exercise 0-4

Write a program that, when run, writes the Hello, world! program as its out put.

-

Solution & Results

The question is not complicated. But, be carefull about the output of the nexted double quotes.

-

Hello, world! program print

-

Analysis

See Solution to Exercise 0-2.

-
-

Exercise 0-5

Is this a valid program? Why or why not?

-
1
2
#include <iostream> 
int main() std::cout << "Hello, world!" << std::endl;
-

Solution & Results

The program is invalid as shown below:

-

A invalid program

-

It is clear that curly braces are missing for a complete main function in this program. A correct main function should include a function body which is a block of statements enclosed by a pair of curly braces.

-

Analysis

If one has no any programming knowledge, he probabily can’t figure out the error only from the error reports:

-
1
expected initializer before 'std'
-

Why the compile error shows like this? A possible reason is that the compiler treats codes outside the function as declarations of variables. To define a variable, one should firstly specify the type specifier and then declare the name of the variable, and finally add a semicolon. Once the variable is created, it is initialized. Consider that the compiler treats int main() as a declaration of a variable, it will find that the initialization of this variable is failed due to lacking of a semicolon.

-
-

Exercise 0-6

Is this a valid program? Why or why not?

-
1
2
#include <iostream>
int main() {{{{{{std::cout << "Hello, world! << std::endl;}}}}}}
-

Solution & Results

In contrast to the program in Exercise 0-5, this program is valid because it has a complete structure of a main function. Beyond the outmost curly bracces, the rest curly braces form independent blocks and can be nested.

-

A valid program

-

Analysis

See C++ - Getting Started and Solution to Exercise 0-5.

-
-

Exercise 0-7

Is this a valid program? Why or why not?

-
1
2
3
4
5
6
7
8
#include <iostream>
int main()
{
/*This is a comment that extends over several lines
because it uses /* and */ as its starting and ending delimiters */
std::cout << "Does this work?" << std::endl;
return 0;
}
-

Solution & Results

The program is invalid:

-

A invalid program

-

As mentioned in C++ - Getting Started, comment pairs cannot nest. The warning also confirms this restriction.

-
1
..\Exercise0_7.cpp:7:25: warning: "/*" within comment [-Wcomment]
-

To Solve the problem, we can add single line comments before each lines of comments. Below graph gives the correct implementation:

-

A valid program

-

Analysis

See C++ - Getting Started.

-
-

Exercise 0-8

Is this a valid program? Why or why not?

-
1
2
3
4
5
6
7
8
#include <iostream>
int main()
{
// This is a comment that extends over several lines
// by using // at the begining of each line instead of
// using /* or */ to delimit comments.
std::cout << "Does this work?" << std::endl;
return 0;
-

Solution & Results

It is a valid program which successfully corrects the program in Exercise0-7

-

A valid program

-

Analysis

See C++ - Getting Started and Solution to Exercise 0-7.

-
-

Exercise 0-9

What is the shortest valid program?

-

Solution & Results

As I mentioned in C++ - Getting Started, a valid program must contain a main function while a main function includes four elements: return type, function name, parameter list and function body.

-

Therefore, the shortest valid program works like this:

-

The shortest valid program

-

Andrew Koenig who is the author of Accelerated C++: Practical Programming by Example states:

-
    -
  1. generally, functions must include at least one return statement.
  2. -
  3. the main function is special and may omit the return. In this case, the implementation will assume a return value of zero.
  4. -
-

The shortest valid program I present here omits the return statement. But as pointed out by Andrew Koenig, explicitly including a return from main function is a good practice.

-

Analysis

See C++ - Getting Started.

-
-

Exercise 0-10

Rewrite the Hello, world! program so that a newline occurs everywhere that whitespace is allowed in the program.

-

Solution & Results

Recalling the Hello, world! program:

-
1
2
3
4
5
6
#include <iostream>
int main()
{
std::cout << "Hello, world!" << std::endl;
return 0;
}
-

There exits one space in the string literals Hello, world!”. I would replace the space with a newline character \n. The graph below shows my implementation and result:

-

New Hello, world! program

-

Analysis

See C++ - Getting Started.

-

References

Koenig, A. and Moo, B.E., 2000. Accelerated C++.

-

Lippman, S.B., Lajoie, J. and Moo, B.E., 2013. C++ Primer, 5th edt.

+ + + + All the programs were compiled and excuted within the setting of Eclipse CDT and MinGW64 + +Exercise 0-0Compile and run the Hello, world! program +Soluti + ... + +
+ + Read more » + +
+ + +
+ + + + + -
+ +
+ + + + + + + +
-
+ +
+

+ + +
+ + + + + + + - +
-
- + +
+ + +
-

- - -

+ + + +

+ +

+
+ @@ -1609,113 +1460,156 @@

-

Introduction

To have a better understanding on the C++ programming language, I decided to learn it from the very begining and take detailed notes from the textbooks Accelerated C++: Practical Programming by Example and C++ Primer (5th Edition), and some online C++ tutorials. In addition, I plan to complete the exercises of each chapter in the Accelerated C++ with providing detailed codes analysis and implementation processes.
Please check the solutions to the first chapter exercises here: Accelerated C++ Solutions to Exercises Chapter 0

-

Hello, world!

Let’s start by writing a simple program, Hello, world!, and then analyse its structure.

-
1
2
3
4
5
6
7
8
9
10
11
12
/*
* A simple C++ Program
* Author: Mr. Nobody
*/
#include <iostream>

int main()
{
// output Hello, world!
std::cout << "Hello, world!" << std::endl;
return 0;
}
-

0.1 Comments

As shown above, there are two types of comments in C++:

-
    -
  1. Single line comment indicated by two slash signs //.
  2. -
  3. Comment pairs using two delimiters, beigining with a /* and ending with a */.
  4. -
-

0.2 #, #include

Lines begining with # are preprocessor directives which are preprocessed before actual compilation, i.e. the compilation of the program itself. These preprocessor directives are not part of the program statement, and hence semicolons ;, marking the end of most statements in C++, are not required.

-

The directive #include gives preprocessor instructions to include a header file, i.e. copy the entire content of the specified header or file and insert. There are two ways to use **#include#:

-
1
2
#include <header>
#include "file"
-

The angle-brackets <> are generally used to include a header of the standard library, e.g. iostream, string…, while the quotes “” include a file that is typically programmer-defined. For example, the program above includes a standard header named iostream to accomplish Input-Output (IO) as the C++ language doesn’t define any statements to do IO.

-

0.3 The main function

Every C++ program must contain a main function. The main function is being called when the operating system runs a program. As same as other functions, the main function also includes four elements: return type, function name, parameter list and function body.

-

The main function requires an int (i.e. integers) type return value. A return value of zero tells the implementation that the program ran successfully while a non-zero value indicates errors. The example above has an empty parameter list, showing that there is nothing between the parentheses (()).

-

The function body begins with an open curly brace ({) and ends with a close curly brace (}). The braces indicate that all the statements inside are part of the same function, of which the return statement terminates the execution of the function by returning a value to the function’s caller. Of course, the value that returned should has the same type as the defined return type of the function. In the case of main function, if the return statement is omitted, the implementation would assume a return value of zero (not recommended).

-

0.4 IO & using declarations

Before the return statement inside the braces, the first statement (as shown below) achieves the goal of this program.

-
1
std::cout << "Hello, world!" << std::endl;
-

As mentioned above, the statement writes Hello, World! on the standard output. The std:: indicates the followed name is part of the namespace named std. The names std::cout and std::cin refer to the objects of the standard output stream (ostream) and input stream (istream), which had already been defined in the iostream library. Another two objects that are defined in the iostream library are cerr and clog. The cerr, representing standard error, is used to output warning or error messages. The clog is used to output the general information about the execution of the program. The name std::endl, which is a manipulator, ends the current line of output. The output operator (<<) and input operator (>>) indicate that what follows is inserted to std::cout and std::cin. To use these objects, firstly we need to include the associated standard header, namely iostream.

-

To simplify the usage of library names from namesapce std, one could use unqualified names cout or cin instead of std::cout or std::cin by the means of using declarations. For example, above program is exactly as same as the below program:

-
1
2
3
4
5
6
7
8
9
10
11
12
13
/*
* A simple C++ Program
* Author: Mr. Nobody
*/
#include <iostream>
using std::cout;
using std::endl;
int main()
{
// output Hello, world!
cout << "Hello, world!" << endl;
return 0;
}f
-

It should be noted that Headers should not include using declarations for the purpose of avoiding potential name confilits.

-

0.5 Expressions & Scope

An Expression typically contains operators and operands. It becomes an expression statement when it is followed by a semicolon. The expression statement asks the implementation to compute or evaluate the expression but discards the results. However, the computation may lead to side effects such as printing a result. For example:

-
1
2
3 + 4;
std::cout << "Hello, world!" << std::endl;
-

The first statement yields a result 7 but has no side effects. In other words, it is useless because the result is discarded. The second statement is useful because of its side effects, that is, the string is inserted into the standard output stream and hence is printed out.

-

In this case, << symbols are operators while std::cout, std::endl and Hello, world! are operands. Every operand has a type that determines the effect of the operators. As mentioned above, std::cout has type std::ostream. Then, the operator << writes the given value (i.e. its right-hand operand) on the given ostream (i.e. the left-hand operand)

-

The operator << is left-associative, that is, the operator returns its left-hand operands. The second << has following relationships with its left and right operands, connecting the output requests.

-
1
(std::cout << "Hello, world!") << std::endl;
-

The scope of a name is the part of a program, in which the name is valid. The above program shows two kinds of scopes: one kind is namespace scope, i.e. scope name std and scope operator ::; another kind is block scope formed by curly braces.

-

0.6 Others

Another aspect is that C++ programs do not have strict requirements on indentation and spaces, allowing improving readability based on personal preference. However, three entities are not allowed to be used in a free-form.

-
    -
  1. string literals which enclosed in double quotes are generally not span lines.
  2. -
  3. preprocessor directive should be put on its own line.
  4. -
  5. // type comments should ends at the end of the current line, and /***/ type comments cannot nest.
  6. -
-
-

Next: C++ - Working with strings.

+ + + + IntroductionTo have a better understanding on the C++ programming language, I decided to learn it from the very begining and take detailed notes from + ... + +
+ + Read more » + +
+ + +
+ + + + + -
+ +
+ + + + + + + +
-
+ +
+

+ + +
+ + + + + + + - +
+ -
- +
+ + +
-

- - -

+ + + +

+ +

+
+ @@ -1723,39 +1617,58 @@

-

Welcome to Hexo! This is your very first post. Check documentation for more info. If you get any problems when using Hexo, you can find the answer in troubleshooting or you can ask me on GitHub.

-

Quick Start

Create a new post

1
$ hexo new "My New Post"
- -

More info: Writing

-

Run server

1
$ hexo server
- -

More info: Server

-

Generate static files

1
$ hexo generate
- -

More info: Generating

-

Deploy to remote sites

1
$ hexo deploy
- -

More info: Deployment

+ + + + Welcome to Hexo! This is your very first post. Check documentation for more info. If you get any problems when using Hexo, you can find the answer in + ... + +
+ + Read more » + +
+ + +
+ + + + + -
+ +
+ + + + + + + +
-
-

+ + + +
- + + + @@ -1763,179 +1676,265 @@

{ - if (!event.target.matches('.tabs-comment .tab-content .tab-pane')) return; - let commentClass = event.target.classList[1]; - localStorage.setItem('comments_active', commentClass); - }); - } - + + + - - + -
+
+ + +
+ + +
+ + + + - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + - - + - @@ -1945,8 +1944,8 @@

@@ -1959,6 +1958,22 @@

- - - Fundamentals of Python - /2018/06/13/Fundamentals-of-Python/ - The Python Conceptual Hierarchy

    -
  1. Programs are composed of modules
  2. -
  3. Modules contain statements
  4. -
  5. Statements contain expressions
  6. -
  7. Expressions create and process objects
  8. -
- - -

Once create an object, you bind its operation set for all time, i.e. you can perform only string operations on a string and list operations on a list. This characteristic is typically known as dynamically typed - a model that keeps track of types for you automatically instead of requiring declaration code but it is also strongly typed, that means you can perform on an object only operations that are valid for its type.

-

Numbers

    -
  1. + performs addition
  2. -
  3. one asterisk () performs multiplication and two asterisks (*) are used for exponentiation, e.g.
    2**2 # 2 to the power 2
  4. -
-]]> - - Programming - - - Notes - Python - - - - C++ Implementations: Linked List-Based Stack - /2018/05/30/C-Implementations-Linked-List-based-Stack/ - Implementation

- -
#ifndef MYSTACK_H_
#define MYSTACK_H_

#include <cstddef>
#include <iostream>
#include <stdexcept>

template <typename T>
class MyStack{
struct Node;

public:
typedef std::size_t size_type;
typedef T value_type;

MyStack(): ptrToHead(nullptr), count(0) {
std::cout << "default constructor" << std::endl;
}

// O(n)
MyStack(const MyStack& s): ptrToHead(nullptr), count(0){
std::cout << "copy constructor" << std::endl;
if(!s.empty()){
ptrToHead = create(s.ptrToHead->data);
++count;
Node* current = ptrToHead;
const Node* temp = s.ptrToHead;
while(count != s.count){
current->next = create(temp->next->data);
current = current->next;
temp = temp->next;
++count;
}
}
}

// O(n)
MyStack& operator=(const MyStack& s){
std::cout << "assignment operator" << std::endl;
if(&s != this){
clear();
if(!s.empty()){
ptrToHead = create(s.ptrToHead->data);
++count;
Node* current = ptrToHead;
const Node* temp = s.ptrToHead;
while(count != s.count){
current->next = create(temp->next->data);
current = current->next;
temp = temp->next;
++count;
}
}
}
return *this;
}

// O(n)
~MyStack() {
std::cout << "destructor" << std::endl;
clear();
}

// O(n)
void clear() {
Node* current = ptrToHead;
while(current != nullptr){
ptrToHead = ptrToHead->next;
delete current;
current = ptrToHead;
--count;
}
}

// O(1)
bool empty() const { return ptrToHead == nullptr; }
size_type size() const { return count; }

// O(1)
void push(const T& val){
Node* new_node = create(val);
if(ptrToHead != nullptr)
new_node->next = ptrToHead;
ptrToHead = new_node;
++count;
}

// O(1)
void pop(){
if(ptrToHead == nullptr)
throw std::domain_error("Stack underflow");

Node* temp = ptrToHead;
ptrToHead = ptrToHead->next;
delete temp;
--count;
}

// O(1)
T top() const {
if(ptrToHead == nullptr)
throw std::domain_error("stack underflow");
return ptrToHead->data;
}

private:
struct Node{
T data;
Node* next;
};

// data members
Node* ptrToHead;
size_type count;

// private function to create a new Node given a value
Node* create(const T& val = T()){
Node* new_node = new Node;
new_node->next = nullptr;
new_node->data = val;
return new_node;
}
};
#endif /* MYSTACK_H_ */
- -

Test and results

/*
* this program tests all operations that provided by the MyStack<int> class
* created by Liam on: 27 May 2018
*/

#include <iostream>
#include <stdexcept>
#include "MyStack.h"

using std::cout;
using std::endl;
using std::domain_error;

int main(){
{ // test default constructor
MyStack<int> s;
if(!s.empty())
cout << "s is an empty stack\n";

// test push
for (int i = 0; i != 10; ++i)
s.push(i);

// test top, pop
while(s.size() != 0){
cout << s.top() << " ";
s.pop();
}

cout << "\n";
// test stack underflow
try{
s.pop();
}catch(domain_error e){
cout << e.what() << "\n";
}
}

cout << "\n";

{ // test copy and assignment operation
MyStack<int> s;
for (int i = 0; i != 10; ++i)
s.push(i);

MyStack<int> s_copy(s);
while(s_copy.size() != 0){
cout << s_copy.top() << " ";
s_copy.pop();
}
cout << "\n";
s.pop();

s_copy = s;
while(s_copy.size() != 0){
cout << s_copy.top() << " ";
s_copy.pop();
}
cout << "\n";
}
}
- -

Outputs:

-
default constructor
9 8 7 6 5 4 3 2 1 0
Stack underflow
destructor

default constructor
copy constructor
9 8 7 6 5 4 3 2 1 0
assignment operator
8 7 6 5 4 3 2 1 0
destructor
destructor
]]> - - Algorithms - - - C++ - Data Structures - Algorithms - - - - C++ notes for financial mathematics - /2018/05/28/C-notes-for-financial-mathematics/ - -
  • Inf: positive infinity
    -Inf: negative infinity
    NaN: not a number
    -
  • -
  • Noting that two decimals can only be approximately equal.

    -
  • -
  • Casting an int to a float is risky since the float data type uses binary scientific notation with only a handful of significant figures. So an int cannot be represented precisely using a float.

    -
  • -
  • - -]]>
    - - Programming - - - C++ - financial modelling - -
    - - C++ Implementations - Doubly Circular Linked List - /2018/05/28/C-Implementations-Doubly-Circular-Linked-List/ - Header file

    #ifndef CIRCULARLINKEDLIST_H_
    #define CIRCULARLINKEDLIST_H_

    #include <cstddef>
    #include <iostream>

    template <typename T>
    class CircularLinkedList{
    struct Node;
    public:
    typedef std::size_t size_type;
    typedef T value_type;

    // default constructor
    CircularLinkedList(): sentinel(create_sentinel()), count(0) {
    std::cout << "default constructor" << std::endl;
    }
    CircularLinkedList(size_type, const T&val = T());
    CircularLinkedList(const CircularLinkedList&);
    CircularLinkedList& operator= (const CircularLinkedList&);
    ~CircularLinkedList();

    bool empty() const { return sentinel->next == sentinel; }
    size_type size() const { return count; }
    Node* begin() { return sentinel->next; }
    const Node* begin() const { return sentinel->next; }
    Node* end() { return sentinel; }
    const Node* end() const { return sentinel; }

    void clear();
    void push_front(const T&);
    void push_back(const T&);
    void insert(size_type, const T&);
    void pop_front();
    void pop_back();
    void erase(size_type);
    void reverse();
    void remove(const T&);

    private:
    struct Node{
    T data;
    Node* prev;
    Node* next;
    };

    Node* sentinel;
    size_type count;

    Node* create_sentinel(){
    Node* nil = new Node;
    nil->prev = nil;
    nil->next = nil;
    return nil;
    }

    Node* create(const T&val){
    Node* new_node = new Node;
    new_node->data = val;
    new_node->prev = sentinel;
    new_node->next = sentinel;
    return new_node;
    }

    };

    // O(n)
    template <typename T>
    CircularLinkedList<T>::CircularLinkedList(size_type n, const T& val)
    : sentinel(create_sentinel()), count(0){
    std::cout << "construtor with parameters" << std::endl;
    Node* current = sentinel;
    while(count != n){
    current->next = create(val);
    current->next->prev = current;
    current = current->next;
    ++count;
    }
    sentinel->prev = current;
    }

    // O(n)
    template <typename T>
    CircularLinkedList<T>::CircularLinkedList(const CircularLinkedList& l)
    : sentinel(create_sentinel()), count(0){
    std::cout << "copy constructor" << std::endl;
    Node* current = sentinel;
    const Node* temp = l.begin();
    while(count != l.size()){
    current->next = create(temp->data);
    current->next->prev = current;
    current = current->next;
    temp = temp->next;
    ++count;
    }
    sentinel->prev = current;

    }

    // O(n)
    template <typename T>
    CircularLinkedList<T>& CircularLinkedList<T>::operator=
    (const CircularLinkedList& l){
    std::cout << "assignment operator" << std::endl;
    if(&l != this){
    clear();
    if(!l.empty()){
    Node* current = sentinel;
    const Node* temp = l.begin();
    while(count != l.size()){
    current->next = create(temp->data);
    current->next->prev = current;
    current = current->next;
    temp = temp->next;
    ++count;
    }
    sentinel->prev = current;
    }
    }
    return *this;
    }

    // O(n)
    template <typename T>
    void CircularLinkedList<T>::clear(){
    while(sentinel->next != sentinel){
    Node* temp = sentinel->next;
    sentinel->next = temp->next;
    sentinel->next->prev = sentinel;
    delete temp;
    --count;
    }
    }

    // O(n)
    template <typename T>
    CircularLinkedList<T>::~CircularLinkedList() {
    clear();
    delete sentinel;
    sentinel = nullptr;
    std::cout << "destructor" << std::endl;
    }

    // O(1)
    template <typename T>
    void CircularLinkedList<T>::push_front(const T& val){
    Node* new_node = create(val);
    new_node->next = sentinel->next;
    new_node->next->prev = new_node;
    sentinel->next = new_node;
    new_node->prev = sentinel;
    ++count;
    }

    // O(n)
    template <typename T>
    void CircularLinkedList<T>::push_back(const T& val){
    Node* current = sentinel;
    while(current->next != sentinel)
    current = current->next;

    current->next = create(val);
    current->next->prev = current;
    sentinel->prev = current->next;
    ++count;
    }

    // O(n)
    template <typename T>
    void CircularLinkedList<T>::insert(size_type position, const T& val){
    if(position < 1 || position > size())
    throw std::domain_error("Invalid Position");
    Node* current = sentinel;
    Node* new_node = create(val);
    for(size_type i = 0; i != position; ++i)
    current = current->next;

    current->prev->next = new_node;
    new_node->prev = current->prev;
    new_node->next = current;
    current->prev = new_node;
    ++count;
    }

    // O(1)
    template <typename T>
    void CircularLinkedList<T>::pop_front(){
    erase(1);
    }

    // O(n)
    template <typename T>
    void CircularLinkedList<T>::pop_back(){
    erase(size());
    }

    // O(n)
    template <typename T>
    void CircularLinkedList<T>::erase(size_type position){
    if(position < 1 || position > size())
    throw std::domain_error("Invalid Position");

    Node* current = sentinel;
    for(size_type i = 0; i != position; ++i)
    current = current->next;

    current->next->prev = current->prev;
    current->prev->next = current->next;
    delete current;
    --count;
    }

    // O(n)
    template <class T>
    void CircularLinkedList<T>::remove(const T& val){
    Node* current = sentinel;
    while(current->next != sentinel){
    if(current->next->data == val){
    Node* temp = current->next;
    current->next = temp->next;
    temp->next->prev = current;
    delete temp;
    --count;
    }else current = current->next;
    }
    }

    // O(n)
    template <class T>
    void CircularLinkedList<T>::reverse(){
    if (size() < 2) return;
    sentinel->prev = sentinel->next;
    Node* current = sentinel;
    Node* NEXT;
    while(current->next != sentinel){
    NEXT = current->next;
    current->next = current->prev;
    current->prev = NEXT;
    current = NEXT;
    }
    current->next = current->prev;
    current->prev = sentinel;
    sentinel->next = current;
    }

    #endif /* CIRCULARLINKEDLIST_H_ */
    - -

    Test program and results

    /*
    * this program tests all operations that provided by the
    * CircularLinkedList<T> class
    * created by Liam on: 28 Apr 2018
    */

    #include <iostream>
    #include "CircularLinkedList.h"

    using std::endl; using std::cout;

    // print and reverse print
    template <class T>
    void print(T& l){
    cout << "print in order: ";
    for(auto i = l.begin(); i != l.end(); i = i->next)
    cout << i->data << " ";
    cout << endl;

    cout << "print in reverse: ";

    for(auto i = (l.end())->prev; i != l.end(); i = i->prev)
    cout << i->data << " ";
    cout << endl;
    }

    int main(){

    { // construct an empty linked list
    CircularLinkedList<int> s;
    if(s.empty())
    cout << "s is an empty linked list\n"
    "the size of s1 is: " << s.size() << endl;

    // call destructor once reaches the end of this block
    }
    cout << endl;

    { // construct a linked list that contains 10 elements, all values are 100
    CircularLinkedList<int> s(10, 100);

    // construct a linked list by copying from s
    CircularLinkedList<int> s_copy(s);
    if(!s.empty() && !s_copy.empty()){
    cout << "the size of s is: " << s.size() << endl;
    cout << "the size of s_copy is: " << s_copy.size() << endl;
    }

    // print the contents of s_copy
    print(s_copy);
    // call destructor twice
    }
    cout << endl;

    { // assignment
    CircularLinkedList<int> s(10, 100);
    CircularLinkedList<int> s_copy;
    s_copy = s;

    // print the contents of s_copy
    print(s_copy);
    }
    cout << endl;

    { // push front
    CircularLinkedList<double> s;
    for(int i = 5; i != 0; --i)
    s.push_front(i);

    cout << "after adding elements at front:\n";
    print(s);

    // push back
    for(int i = 5; i != 0; --i)
    s.push_back(i);

    cout << "after adding elements at the end:\n";
    print(s);

    // insert at position 5
    for(int i = 5; i != 0; --i)
    s.insert(i, 0);

    cout << "after inserting elements in-between:\n";
    print(s);

    // delete from the begining
    for(int i = 5; i != 0; --i)
    s.pop_front();

    cout << "after deleting from the begining:\n";
    print(s);

    // delete from the end
    for(int i = 5; i != 0; --i){
    s.pop_back();
    }

    cout << "after deleting from the end:\n";
    print(s);

    // erase at in-between positions
    for(int i = 3; i != 0; --i)
    s.erase(3);

    cout << "after deleting from other positions:\n";
    print(s);

    }
    cout << endl;

    { // remove
    CircularLinkedList<int> s(5, 5);
    for(int i = 5; i != 0; --i)
    s.insert(5, i);

    cout << "at present:\n";
    print(s);

    s.remove(5);
    cout << "after removing all elements:\n";
    print(s);
    }
    cout << endl;

    { // test reverse function
    CircularLinkedList<int> s;
    for(int i = 0; i != 10; ++i)
    s.push_back(i);

    cout << "at present, s contains following elements:\n";
    print(s);

    s.reverse();
    cout << "after reverse:\n";
    print(s);

    }

    return 0;
    }
    - -

    Outputs:

    -
    default constructor
    s is an empty linked list
    the size of s1 is: 0
    destructor

    construtor with parameters
    copy constructor
    the size of s is: 10
    the size of s_copy is: 10
    print in order: 100 100 100 100 100 100 100 100 100 100
    print in reverse: 100 100 100 100 100 100 100 100 100 100
    destructor
    destructor

    construtor with parameters
    default constructor
    assignment operator
    print in order: 100 100 100 100 100 100 100 100 100 100
    print in reverse: 100 100 100 100 100 100 100 100 100 100
    destructor
    destructor

    default constructor
    after adding elements at front:
    print in order: 1 2 3 4 5
    print in reverse: 5 4 3 2 1
    after adding elements at the end:
    print in order: 1 2 3 4 5 5 4 3 2 1
    print in reverse: 1 2 3 4 5 5 4 3 2 1
    after inserting elements in-between:
    print in order: 0 1 0 2 0 3 0 4 0 5 5 4 3 2 1
    print in reverse: 1 2 3 4 5 5 0 4 0 3 0 2 0 1 0
    after deleting from the begining:
    print in order: 3 0 4 0 5 5 4 3 2 1
    print in reverse: 1 2 3 4 5 5 0 4 0 3
    after deleting from the end:
    print in order: 3 0 4 0 5
    print in reverse: 5 0 4 0 3
    after deleting from other positions:
    print in order: 3 0
    print in reverse: 0 3
    destructor

    construtor with parameters
    at present:
    print in order: 5 5 5 5 1 2 3 4 5 5
    print in reverse: 5 5 4 3 2 1 5 5 5 5
    after removing all elements:
    print in order: 1 2 3 4
    print in reverse: 4 3 2 1
    destructor

    default constructor
    at present, s contains following elements:
    print in order: 0 1 2 3 4 5 6 7 8 9
    print in reverse: 9 8 7 6 5 4 3 2 1 0
    after reverse:
    print in order: 9 8 7 6 5 4 3 2 1 0
    print in reverse: 0 1 2 3 4 5 6 7 8 9
    destructor
    ]]> - - Algorithms - - - C++ - Data Structures - Algorithms - - - - C++ Implementations: Singly Circular Linked List with a sentinel - /2018/05/26/C-Implementations-Circular-Doubly-Linked-List/ - Header file
    #ifndef CIRCULARLINKEDLIST_H_
    #define CIRCULARLINKEDLIST_H_

    #include <cstddef>
    #include <iostream>

    template <typename T>
    class CircularLinkedList{
    struct Node;

    public:
    typedef std::size_t size_type;
    typedef T value_type;

    // create an empty linked list
    CircularLinkedList(): sentinel(create_sentinel()), count(0){
    std::cout << "default constructor" << std::endl;
    }

    // create an linked list with user supplied size and value
    explicit CircularLinkedList(size_type, const T& val = T());

    // copy constructor
    CircularLinkedList(const CircularLinkedList&);

    // assignment operator
    CircularLinkedList& operator=(const CircularLinkedList&);

    // destructor
    ~CircularLinkedList() {
    clear();
    delete sentinel;
    sentinel = nullptr;
    std::cout << "destructor" << std::endl;
    }

    void clear();

    bool empty() const { return sentinel == sentinel->next; }
    size_type size() const { return count; }

    Node* begin() { return sentinel->next; }
    const Node* begin() const { return sentinel->next; }

    Node* end() { return sentinel; }
    const Node* end() const { return sentinel; }

    // insert at begining
    void push_front(const T&);

    // insert at the end
    void push_back(const T&);

    // insert at the nth position, that is, after (n-1)the position
    // the range of position is [1, size()]
    void insert(size_type, const T&);

    // delete at the begining
    void pop_front();

    // delete the last element
    void pop_back();

    // delete at nth position
    void erase(size_type);

    // reverse the order iteratively
    void reverse();

    // remove elements with specific values
    void remove(const T&);

    private:
    struct Node{
    T data;
    Node* next;
    };

    Node* sentinel;
    size_type count;


    Node* create_sentinel(){
    Node* nil = new Node;
    nil->next = nil;
    return nil;
    }


    Node* create(const T& val = T()){
    Node* new_node = new Node;
    new_node->data = val;
    new_node->next = sentinel;
    return new_node;
    }
    };

    // O(n)
    template <typename T>
    CircularLinkedList<T>::CircularLinkedList(size_type n, const T& val):
    sentinel(create_sentinel()), count(0){
    std::cout << "constructor with parameters" << std::endl;

    Node* current = sentinel;
    while(count != n){
    current->next = create(val);
    current = current->next;
    ++count;
    }
    }

    // O(n)
    template <typename T>
    CircularLinkedList<T>::CircularLinkedList(const CircularLinkedList& l):
    sentinel(create_sentinel()), count(0){
    Node* current = sentinel;
    const Node* temp = l.begin();
    while(temp != l.end()){
    current->next = create(temp->data);
    current = current->next;
    temp = temp->next;
    ++count;
    }
    }

    // O(n)
    template <typename T>
    CircularLinkedList<T>& CircularLinkedList<T>::operator=
    (const CircularLinkedList& l){
    if(&l != this){
    clear();
    Node* current = sentinel;
    const Node* temp = l.begin();
    while(temp != l.end()){
    current->next = create(temp->data);
    current = current->next;
    temp = temp->next;
    ++count;
    }
    }
    return *this;
    }

    // O(n)
    template <typename T>
    void CircularLinkedList<T>::clear(){
    Node* current = sentinel;
    if(current->next != sentinel){
    Node* temp = current->next;
    current->next = temp->next;
    delete temp;
    --count;
    }
    }

    // O(1)
    template <class T>
    void CircularLinkedList<T>::push_front(const T& val) {
    Node* current = sentinel;
    Node* new_node = create(val);
    new_node->next = current->next;
    current->next = new_node;
    ++count;
    }

    // O(n)
    template <class T>
    void CircularLinkedList<T>::push_back(const T& val) {
    Node* current = sentinel;
    while(current->next != sentinel)
    current = current->next;

    current->next = create(val);
    ++count;
    }

    // O(n)
    template <class T>
    void CircularLinkedList<T>::insert(size_type position, const T& val){
    if(position < 1 || position > size())
    throw std::domain_error("Invalid Position");

    Node* new_node = create(val);
    Node* temp = sentinel;
    for (size_type i = 0; i != position-1; ++i)
    temp = temp->next;
    new_node->next = temp->next;
    temp->next = new_node;
    ++count;
    }

    // O(1)
    template <class T>
    void CircularLinkedList<T>::pop_front(){
    erase(1);
    }

    // O(n)
    template <class T>
    void CircularLinkedList<T>::pop_back(){
    erase(size());
    }

    // O(n)
    template <class T>
    void CircularLinkedList<T>::erase(size_type position){
    if(position < 1 || position > size())
    throw std::domain_error("Invalid Position");

    Node* current = sentinel;
    for(size_type i = 0; i != position - 1; ++i)
    current = current->next;

    Node* temp = current->next;
    current->next = temp->next;
    delete temp;
    --count;
    }

    // O(n)
    template <class T>
    void CircularLinkedList<T>::remove(const T& val){
    Node* current = sentinel;
    while(current->next != sentinel){
    if(current->next->data == val){
    Node* temp = current->next;
    current->next = temp->next;
    delete temp;
    --count;
    }else current = current->next;
    }
    }

    // O(n)
    template <class T>
    void CircularLinkedList<T>::reverse(){
    if (size() < 2) return;
    Node* current = sentinel->next;
    Node* PREV = sentinel;
    Node* NEXT;
    while(current->next != sentinel){
    NEXT = current->next;
    current->next = PREV;
    PREV = current;
    current = NEXT;
    }
    current->next = PREV;
    sentinel->next = current;
    }

    #endif /* CIRCULARLINKEDLIST_H_ */
    -

    Test program and results

    /*
    * this program tests all operations that provided by the
    * CircularLinkedList<T> class
    * created by Liam on: 28 May 2018
    */

    #include <iostream>
    #include "CircularLinkedList.h"

    using std::endl; using std::cout;

    template <class Pointer>
    void print(Pointer begin, Pointer end){
    while(begin != end){
    cout << begin->data << " ";
    begin = begin->next;
    }
    cout<< endl;
    }

    int main(){

    { // construct an empty linked list
    CircularLinkedList<int> s;
    if(s.empty())
    cout << "s is an empty linked list\n"
    "the size of s1 is: " << s.size() << endl;

    // call destructor once reaches the end of this block
    }
    cout << endl;

    { // construct a linked list that contains 10 elements, all values are 100
    CircularLinkedList<int> s(10, 100);

    // construct a linked list by copying from s
    CircularLinkedList<int> s_copy(s);
    if(!s.empty() && !s_copy.empty()){
    cout << "the size of s is: " << s.size() << endl;
    cout << "the size of s_copy is: " << s_copy.size() << endl;
    }

    // print the contents of s_copy
    cout << "all elements in s_copy: ";
    print(s.begin(), s.end());

    // call destructor twice
    }
    cout << endl;

    { // assignment
    CircularLinkedList<int> s(10, 100);
    CircularLinkedList<int> s_copy;
    s_copy = s;

    // print the contents of s_copy
    cout << "all elements in s_copy: ";
    print(s_copy.begin(), s_copy.end());
    }
    cout << endl;

    { // push front
    CircularLinkedList<double> s;
    for(int i = 5; i != 0; --i)
    s.push_front(i);

    cout << "after adding elements at front, s becomes: ";
    print(s.begin(), s.end());

    // push back
    for(int i = 5; i != 0; --i)
    s.push_back(i);

    cout << "after adding elements at the end, s becomes: ";
    print(s.begin(), s.end());


    // insert at position 5
    for(int i = 5; i != 0; --i)
    s.insert(i, 0);

    cout << "after inserting elements in-between, s becomes: ";
    print(s.begin(), s.end());

    // delete from the begining
    for(int i = 5; i != 0; --i)
    s.pop_front();

    cout << "after deleting from the begining, s becomes: ";
    print(s.begin(), s.end());

    // delete from the end
    for(int i = 5; i != 0; --i)
    s.pop_back();

    cout << "after deleting from the end, s becomes: ";
    print(s.begin(), s.end());

    // erase at in-between positions
    for(int i = 3; i != 0; --i)
    s.erase(3);

    cout << "after deleting from other positions, s becomes: ";
    print(s.begin(), s.end());

    }
    cout << endl;

    { // remove
    CircularLinkedList<int> s(5, 5);
    for(int i = 5; i != 0; --i)
    s.insert(5, i);

    cout << "at present, s contains following elements: ";
    print(s.begin(), s.end());

    s.remove(5);
    cout << "after removing all elements equal 5, s becomes: ";
    print(s.begin(), s.end());
    }
    cout << endl;

    { // test reverse function
    CircularLinkedList<int> s;
    for(int i = 0; i != 10; ++i)
    s.push_back(i);

    cout << "at present, s contains following elements: ";
    print(s.begin(), s.end());

    s.reverse();
    cout << "reverse: ";
    print(s.begin(), s.end());

    s.reverse();
    cout << "reverse again: ";
    print(s.begin(), s.end());

    }

    return 0;
    }
    - -

    Outputs:

    -
    default constructor
    s is an empty linked list
    the size of s1 is: 0
    destructor

    constructor with parameters
    the size of s is: 10
    the size of s_copy is: 10
    all elements in s_copy: 100 100 100 100 100 100 100 100 100 100
    destructor
    destructor

    constructor with parameters
    default constructor
    all elements in s_copy: 100 100 100 100 100 100 100 100 100 100
    destructor
    destructor

    default constructor
    after adding elements at front, s becomes: 1 2 3 4 5
    after adding elements at the end, s becomes: 1 2 3 4 5 5 4 3 2 1
    after inserting elements in-between, s becomes: 0 1 0 2 0 3 0 4 0 5 5 4 3 2 1
    after deleting from the begining, s becomes: 3 0 4 0 5 5 4 3 2 1
    after deleting from the end, s becomes: 3 0 4 0 5
    after deleting from other positions, s becomes: 3 0
    destructor

    constructor with parameters
    at present, s contains following elements: 5 5 5 5 1 2 3 4 5 5
    after removing all elements equal 5, s becomes: 1 2 3 4
    destructor

    default constructor
    at present, s contains following elements: 0 1 2 3 4 5 6 7 8 9
    reverse: 9 8 7 6 5 4 3 2 1 0
    reverse again: 0 1 2 3 4 5 6 7 8 9
    destructor
    ]]>
    - - Algorithms - - - C++ - Data Structures - Algorithms - -
    - - C++ Implementations: Doubly Linked List - /2018/05/26/C-Implementations-Doubly-Linked-List/ - Head files
    #ifndef DOUBLYLINKEDLIST_H_
    #define DOUBLYLINKEDLIST_H_

    #include <cstddef>
    #include <stdexcept>
    #include <iostream>

    template <typename T>
    class DoublyLinkedList{
    struct Node;
    public:
    typedef std::size_t size_type;
    typedef T value_type;

    // default constructor
    DoublyLinkedList(): ptrToHead(nullptr), count(0){
    std::cout << "default constructor" << std::endl;
    }

    // constructor with parameters
    explicit DoublyLinkedList(size_type, const T&val = T());

    // copy constructor
    DoublyLinkedList(const DoublyLinkedList&);

    // assignment operator
    DoublyLinkedList& operator=(const DoublyLinkedList&);

    // destructor
    ~DoublyLinkedList() {
    std::cout << "destructor" << std::endl;
    clear();
    }

    void clear();
    bool empty() const { return ptrToHead == nullptr; }
    size_type size() const { return count; }
    Node* begin() { return ptrToHead; }
    const Node* begin() const { return ptrToHead; }

    void push_front(const T&);
    void push_back(const T&);
    void insert(size_type, const T&);

    void pop_front();
    void pop_back();
    void erase(size_type);

    void remove(const T&);
    void reverse();

    private:
    struct Node{
    T data;
    Node* prev;
    Node* next;
    };

    Node* ptrToHead;
    size_type count;

    Node* create(const T& val){
    Node* new_node = new Node;
    new_node->data = val;
    new_node->prev = nullptr;
    new_node->next = nullptr;
    return new_node;
    }
    };

    // constructor with parameters:O(n)
    template <typename T>
    DoublyLinkedList<T>::DoublyLinkedList(size_type n, const T&val){
    std::cout << "constructor with parameters" << std::endl;
    if(n > 0){
    ptrToHead = create(val);
    count = 1;

    Node* current = ptrToHead;
    while(count != n){
    current->next = create(val);
    current->next->prev = current;
    current = current->next;
    ++count;
    }
    }
    }

    // copy constructor: O(n)
    template <typename T>
    DoublyLinkedList<T>::DoublyLinkedList(const DoublyLinkedList& l)
    : ptrToHead(nullptr), count(0){
    std::cout << "copy constructor" << std::endl;
    if(!l.empty()){
    ptrToHead = create(l.begin()->data);
    count = 1;

    const Node* temp = l.begin();
    Node* current = ptrToHead;
    while(count != l.size()){
    current->next = create(temp->next->data);
    current->next->prev = current;
    current = current->next;
    temp = temp->next;
    ++count;
    }
    }
    }

    // assignment operator: O(n)
    template <typename T>
    DoublyLinkedList<T>& DoublyLinkedList<T>::operator= (const DoublyLinkedList& l)
    {
    std::cout << "assignment operator" << std::endl;
    if(&l != this){
    clear();
    if(!l.empty()){
    ptrToHead = create(l.begin()->data);
    count = 1;

    const Node* temp = l.begin();
    Node* current = ptrToHead;
    while(count != l.size()){
    current->next = create(temp->next->data);
    current->next->prev = current;
    current = current->next;
    temp = temp->next;
    ++count;
    }
    }
    }
    return *this;
    }

    // O(n)
    template <typename T>
    void DoublyLinkedList<T>::clear(){
    Node* current = ptrToHead;
    while(current != nullptr){
    ptrToHead = ptrToHead->next;
    delete current;
    current = ptrToHead;
    --count;
    }
    }

    // O(1)
    template <typename T>
    void DoublyLinkedList<T>::push_front(const T& val){
    Node* new_node = create(val);
    if(ptrToHead != nullptr){
    new_node->next = ptrToHead;
    ptrToHead->prev = new_node;
    }
    ptrToHead = new_node;
    ++count;
    }

    // O(n)
    template <typename T>
    void DoublyLinkedList<T>::push_back(const T& val){
    Node* new_node = create(val);
    if(ptrToHead == nullptr){
    ptrToHead = new_node;
    ++count;
    return;
    }

    Node* current = ptrToHead;
    while(current->next != nullptr)
    current = current->next;

    current->next = new_node;
    new_node->prev = current;
    ++count;
    }

    // O(n)
    template <typename T>
    void DoublyLinkedList<T>::insert(size_type position, const T& val){
    if(position < 1 || position > size())
    throw std::domain_error("Invalid Position");
    else if(position == 1)
    push_front(val);
    else{
    Node* new_node = create(val);
    Node* current = ptrToHead;

    for(size_type i = 0; i != position - 1; ++i)
    current = current->next;
    current->prev->next = new_node;
    new_node->next = current;
    new_node->prev = current->prev;
    current->prev = new_node;
    ++count;
    }
    }

    // O(1)
    template <typename T>
    void DoublyLinkedList<T>::pop_front(){ erase(1); }

    // O(n)
    template <typename T>
    void DoublyLinkedList<T>::pop_back(){ erase(size()); }

    // O(n)
    template <typename T>
    void DoublyLinkedList<T>::erase(size_type position){
    if(position < 1 || position > size())
    throw std::domain_error("Invalid Position");

    Node* current = ptrToHead;

    if(size() == 1){
    ptrToHead = nullptr;
    }else if(position == 1){
    ptrToHead = ptrToHead->next;
    ptrToHead->prev = nullptr;
    }else if(position == size()){
    while(current->next != nullptr)
    current = current->next;
    current->prev->next = nullptr;
    }else{
    for (size_type i = 0; i != position - 1; ++i){
    current = current->next;
    }
    current->next->prev = current->prev;
    current->prev->next = current->next;
    }

    delete current;
    --count;
    }

    // O(n)
    template <typename T>
    void DoublyLinkedList<T>::remove(const T& val){
    Node* current = ptrToHead;
    while(current != nullptr){
    if(current->data == val){
    Node* temp = current->next;
    if(current->prev == nullptr){
    ptrToHead = ptrToHead->next;
    if(ptrToHead != nullptr){
    ptrToHead->prev = nullptr;
    }
    }else if(current->next == nullptr){
    current->prev->next = nullptr;
    }else{
    current->next->prev = current->prev;
    current->prev->next = current->next;
    }

    delete current;
    current = temp;
    }else
    current = current->next;
    }
    }

    // O(n)
    template <typename T>
    void DoublyLinkedList<T>::reverse(){
    Node* current = ptrToHead;
    Node* PREV = nullptr;
    while(current != nullptr){
    current->prev = current->next;
    current->next = PREV;
    PREV = current;
    current = current->prev;
    }
    ptrToHead = PREV;
    }
    #endif /* DOUBLYLINKEDLIST_H_ */
    - - -

    Test program and results

    /*
    * this program tests all operations that provided by the
    * DoublyLinkedList<T> class
    * created by Liam on: 28 Apr 2018
    */

    #include <iostream>
    #include "DoublyLinkedList.h"

    using std::endl; using std::cout;

    // print and reverse print
    template <class T>
    void print(T& l){
    cout << "print in order: ";
    for(auto it = l.begin(); it != nullptr; it = it->next){
    cout << it->data << " ";
    }

    cout << "\n" << "print in reverse: ";
    auto it = l.begin();
    while(it->next != nullptr){
    it = it->next;

    }
    while(it != nullptr){
    cout << it->data << " ";
    it = it->prev;
    }

    cout << endl;
    }

    int main(){

    { // construct an empty linked list
    DoublyLinkedList<int> s;
    if(s.empty())
    cout << "s is an empty linked list\n"
    "the size of s1 is: " << s.size() << endl;

    // call destructor once reaches the end of this block
    }
    cout << endl;

    { // construct a linked list that contains 10 elements, all values are 100
    DoublyLinkedList<int> s(10, 100);

    // construct a linked list by copying from s
    DoublyLinkedList<int> s_copy(s);
    if(!s.empty() && !s_copy.empty()){
    cout << "the size of s is: " << s.size() << endl;
    cout << "the size of s_copy is: " << s_copy.size() << endl;
    }

    // print the contents of s_copy
    print(s_copy);

    // call destructor twice
    }
    cout << endl;

    { // assignment
    DoublyLinkedList<int> s(10, 100);
    DoublyLinkedList<int> s_copy;
    s_copy = s;

    // print the contents of s_copy
    print(s_copy);
    }
    cout << endl;

    { // push front
    DoublyLinkedList<double> s;
    for(int i = 5; i != 0; --i)
    s.push_front(i);

    cout << "after adding elements at front:\n";
    print(s);

    // push back
    for(int i = 5; i != 0; --i)
    s.push_back(i);

    cout << "after adding elements at the end:\n";
    print(s);

    // insert at position 5
    for(int i = 5; i != 0; --i)
    s.insert(i, 0);

    cout << "after inserting elements in-between:\n";
    print(s);

    // delete from the begining
    for(int i = 5; i != 0; --i)
    s.pop_front();

    cout << "after deleting from the begining:\n";
    print(s);

    // delete from the end
    for(int i = 5; i != 0; --i){
    s.pop_back();
    }

    cout << "after deleting from the end:\n";
    print(s);

    // erase at in-between positions
    for(int i = 3; i != 0; --i)
    s.erase(3);

    cout << "after deleting from other positions:\n";
    print(s);

    }
    cout << endl;

    { // remove
    DoublyLinkedList<int> s(5, 5);
    for(int i = 5; i != 0; --i)
    s.insert(5, i);

    cout << "at present:\n";
    print(s);

    s.remove(5);
    cout << "after removing all elements:\n";
    print(s);
    }
    cout << endl;

    { // test reverse function
    DoublyLinkedList<int> s;
    for(int i = 0; i != 10; ++i)
    s.push_back(i);

    cout << "at present, s contains following elements:\n";
    print(s);

    s.reverse();
    cout << "after reverse:\n";
    print(s);

    }

    return 0;
    }
    - -

    Outputs:

    -
    default constructor
    s is an empty linked list
    the size of s1 is: 0
    destructor

    constructor with parameters
    copy constructor
    the size of s is: 10
    the size of s_copy is: 10
    print in order: 100 100 100 100 100 100 100 100 100 100
    print in reverse: 100 100 100 100 100 100 100 100 100 100
    destructor
    destructor

    constructor with parameters
    default constructor
    assignment operator
    print in order: 100 100 100 100 100 100 100 100 100 100
    print in reverse: 100 100 100 100 100 100 100 100 100 100
    destructor
    destructor

    default constructor
    after adding elements at front:
    print in order: 1 2 3 4 5
    print in reverse: 5 4 3 2 1
    after adding elements at the end:
    print in order: 1 2 3 4 5 5 4 3 2 1
    print in reverse: 1 2 3 4 5 5 4 3 2 1
    after inserting elements in-between:
    print in order: 0 1 0 2 0 3 0 4 0 5 5 4 3 2 1
    print in reverse: 1 2 3 4 5 5 0 4 0 3 0 2 0 1 0
    after deleting from the begining:
    print in order: 3 0 4 0 5 5 4 3 2 1
    print in reverse: 1 2 3 4 5 5 0 4 0 3
    after deleting from the end:
    print in order: 3 0 4 0 5
    print in reverse: 5 0 4 0 3
    after deleting from other positions:
    print in order: 3 0
    print in reverse: 0 3
    destructor

    constructor with parameters
    at present:
    print in order: 5 5 5 5 1 2 3 4 5 5
    print in reverse: 5 5 4 3 2 1 5 5 5 5
    after removing all elements:
    print in order: 1 2 3 4
    print in reverse: 4 3 2 1
    destructor

    default constructor
    at present, s contains following elements:
    print in order: 0 1 2 3 4 5 6 7 8 9
    print in reverse: 9 8 7 6 5 4 3 2 1 0
    after reverse:
    print in order: 9 8 7 6 5 4 3 2 1 0
    print in reverse: 0 1 2 3 4 5 6 7 8 9
    destructor
    ]]>
    - - Algorithms - - - C++ - Data Structures - Algorithms - -
    - - C++ Implementations: Singly Linked List - /2018/05/19/C-Implementations-Singly-Linked-List/ - Logical View

    How to efficiently store a sequence of values (aka. a List) of a given type is crucial for any non-trivial program due to the limited memory. The way we store or organsize data introduces an important concept, that is, data structures. A data structure is proposed to manage data in a specific way so that we can use it efficiently according to specific needs.

    -

    In C++/C, the most common facility we use is so called built-in array, which stores a given number of elements in a contiguous memory block. All elements are stored in certain order indicated by their addresses. When we want to access any one of the elements, we pass the order of that element to the computer. Then the computer will calculate the address of that element based on the initial address of the array, and then return us the associated value. Since the addresses are contiguous and the size of memory occupied by the given type is fixed, we can always get any element in constant time: there is only one arithmetic operation needed. Naturally, we can modify any one of elements in constant time as well. However, the shortingcoming is obvious: an array has fixed length. There are two ways to solve this problem, one is that we can preset a large enough size for the array, however, doing so is very inefficient in terms of memory usage. Another way is to dynamically allocated an array when it is needed. However, doing so possibly invalidates all pointers/iterators as all elements are moved to a new allocated storage, which is also very inefficient in terms of time cost(O(n)). Even if the allocated storage is sufficient, any modifications of the array, such as, insert or delete element, also invalidates parts or all of the pointers/iterators, and hence these operations have high time cost: O(n).

    -

    An alternative data structure is named Linked List, which stores data in a noncontinuous memory space through a manner that each element are connected and ordered by pointers. More specific, an element of a singly linked list is an single object (aka. Linked Node) that contains two members: one is the data stored in the object, and the other is the address (i.e. a pointer variable) of the next element.

    -

    Basic Operations

    The entrance of a singly linked list is a pointer to the Head Node, i.e. the first Linked Node. If the linked list is empty, the pointer to the Head Node is a nullptr. To access one element, we have to start from the Head Node and move one Node by one Node. Therefore, unlike the array, the time to access elements of the linked list is proportational to the size of the linked list, that is, the time complexity is O(n). Naturally if we want to insert or delete one element into/from the list, we have to find the position or the specific element first. The time complexity is also O(n). One advantage compared to the array is that it doesn’t worry about the size of the storage. We do not need to preset the length of the list and hence no extra memory is wasted. But it does need extra memory for storing the pointer variable in each Linked Node. Another advantage is that the operations such as insert or delete doesn’t change other elements, which is crucial for us when we manipulate an object by pointers or iterators. Now let’s briefly summarize these two elementary data structures: Array and Linked List.

    - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
    ArrayLinked List
    Cost of accessing an element of an elementO(1)O(n)
    Memory requirementFixed size: used memory and unused memoryNo unused memory but need extra memory for pointer variables
    Cost of Inserting or deleting an elementInserting at beging: O(n); Insering at the end: O(1) if there exists unused memory but O(n) is the array is full; Inserting at middle: O(n)Inserting at begining:O(1); Inserting at the end: O(n); Inserting at middle: O(n)
    Source:mycodeschool
    -

    From above table, we can see that whether to use an array or a linked list depends on that what is the most frequent operation the program performs and what is the size of the data structure.

    -

    Implementations

    To define a abstract data type based on the singly linked list, the first step is to write the Linked Node:

    -
    template <typename T>
    struct Node{
    T data;
    Node* next;
    };
    - -

    The Node struct is templated for satisfying different underlying types. It contains two items: one is named data which for storing values of T type and ther other named next which is a pointer to a Node object. Next we define the class type based on the singly linked list model described above. In fact, we can incorporate the Node type into our singly linked list class as a private member for the purpose of hidding the implementation details. As mentioned above, the only information we know is the pointer to the Head Node, therefore, I declare a private member ptrToHead to indicate the address of the Head Node. Also, I define a unsigned integer to count the number of elements stored in a linked list. For the sake of convenience, I write a private function create to create a Node with a particular value. Following code shows the full view of the SinglyLinkedList class template. Noting that this implementation is not a STL style implementation.

    -
    template <typename T>
    class SinglyLinkedList{
    struct Node; // forward declaration
    template<typename X>
    friend void reverse(SinglyLinkedList<X>&,
    typename SinglyLinkedList<X>::Node*)
    public:

    SinglyLinkedList(): ptrToHead(nullptr), count(0) { // create an empty linked_list
    std::cout << "default constructor" << std::endl;
    }
    explicit SinglyLinkedList(const size_type, const T& val = T()); // create an linked list with size
    SinglyLinkedList(const SinglyLinkedList&); // copy constructor
    SinglyLinkedList& operator= (const SinglyLinkedList&); // assignment operator
    ~SinglyLinkedList(); // destructor
    void clear(); // clear
    Node* begin() { return ptrToHead; } // get pointer to Head Node
    const Node* begin() const { return ptrToHead; }
    bool empty() const { return ptrToHead == nullptr; } // check whether is empty
    size_type size() const { return count; } // get the size of the list
    void push_front(const T&); // insert at begining
    void push_back(const T&); // insert at the end
    void insert(size_type, const T&); // insert at the nth position
    void pop_front(); // delete at the begining
    void pop_back(); // delete the last element
    void erase(size_type); // delete at nth position
    void reverse(); // reverse the order iteratively
    void remove(const T&); // remove elements with specific values

    private:
    // nested Node type
    struct Node{
    T data;
    Node* next;
    };

    // data members
    Node* ptrToHead;
    size_type count;

    // create a new Node
    Node* create(const T& val){
    Node* new_node = new Node;
    new_node->data = val;
    new_node->next = nullptr;
    return new_node;
    }
    };
    - -

    Let’s define the special members first:

    -

    constructors

    The default constructor initializes the ptrToHead to nullptr and count to 0, hence constructs an empty linked list. The second constructor constructs a linked list with a size and a value of T type. If no value supplied, the data member in each Node will be default-initialized or value-initialized depending on whether there exists a user-defined default constructor in the definition of T type. Also I specify this constructor as explicit to avoid potential type conversion. To validate my implementation, I instrument these special members by adding outputs when these members are called.

    -
    // constructor that takes a size and a value: O(n)
    template <class T>
    SinglyLinkedList<T>::SinglyLinkedList(size_type n, const T& val){
    std::cout << "constructor with parameters" << std::endl;
    if(n > 0){
    ptrToHead = create(val);
    count = 1;
    Node* temp = ptrToHead;
    while(count != n){
    temp->next = create(val);
    temp = temp->next;
    ++count;
    }
    }
    }
    -

    The logic is simple: first create the Head Node and store its address into ptrToHead, at the same time, update the count to 1 representing that currently we have one element in our linked list; Then, create the rest Nodes and link one by one. We use a temporary pointer to move forward as we don’t want to modify the ptrToHead. The time cost is proportional to the length of the linked list and therefore the time complexity is big oh of n, where n represents the number of elements stored in the linked list.

    -

    Next is the copy constructor which controls the copy operation. The definition is similar to above constructor except that, the copy constructor constructs an object using values stored in the argument. If the argument is an empty linked list, we construct an empty linked list as well through the initialization list.

    -
    // copy constructor: O(n)
    template <class T>
    SinglyLinkedList<T>::SinglyLinkedList(const SinglyLinkedList& l): ptrToHead(nullptr), count(0) {
    std::cout << "copy constructor" << std::endl;
    if(!l.empty()){
    ptrToHead = create(l.begin()->data);
    count = 1;
    Node* temp1 = ptrToHead;
    const Node* temp2 = l.begin();
    while(count != l.size())
    {
    temp1->next = create(temp2->next->data);
    temp1 = temp1->next;
    temp2 = temp2->next;
    ++count;
    }
    }
    }
    - -

    The assignment operator is similar to our copy costructor except that it needs to obliterate the old values stored in the left-hand side operand. If the argument is an empty linked list, we only need to execute the clear() function to delete all nodes (if exist) to get an empty linked list.

    -
    // assignment operator: O(n)
    template <class T>
    SinglyLinkedList<T>& SinglyLinkedList<T>::operator= (const SinglyLinkedList& l){
    std::cout << "assignment operator" << std::endl;
    if(&l != this){
    clear();
    if(!l.empty()){
    ptrToHead = create(l.begin()->data);
    count = 1;
    Node* temp1 = ptrToHead;
    const Node* temp2 = l.begin();
    while(count != l.size())
    {
    temp1->next = create(temp2->next->data);
    temp1 = temp1->next;
    temp2 = temp2->next;
    ++count;
    }
    }
    }
    return *this;
    }
    - -

    destructor & clear member

    The destructor is implemented by executing the clear() function.

    -
    // destructor
    template <class T>
    SinglyLinkedList<T>::~SinglyLinkedList() {
    std::cout << "destructor" << std::endl;
    clear();
    }

    // clear function: the time complexity O(n)
    template <class T>
    void SinglyLinkedList<T>::clear(){
    Node* current = ptrToHead;
    while(current != nullptr)
    {
    ptrToHead = ptrToHead->next;
    delete current;
    current = ptrToHead;
    --count;
    }
    }
    -

    Due to the ptrToHead is the only entrance for us, we cannot delete it directly. Therefore, we do it by virtue of a temporary pointer, which points to the Node to be deleted. For example, let it points to the Head Node, then we can release the ptrToHead and let the ptrToHead points to our next Node, and then we clear the Head Node by delete the temporary pointer.

    -

    push_front, push_back, insert

    The push_front means that we can add a new element at the very begining of our linked list. It is simple to do this, making the ptrToHead points to our new Node. But noting that when the linked list is not empty, we should stores the address of the original Head Node into the next member of our new Node.

    -
    // insert at begining: O(1)
    template <class T>
    void SinglyLinkedList<T>::push_front(const T& val) {
    Node* new_node = create(val);
    if(ptrToHead != nullptr)
    new_node->next = ptrToHead;
    ptrToHead = new_node;
    ++count;
    }
    - -

    The case of appending a new Node at the end of the linked list is a little bit complex. The function is shown below.

    -
    // insert at the end: O(n)
    template <class T>
    void SinglyLinkedList<T>::push_back(const T& val) {
    Node* new_node = create(val);
    if(ptrToHead == nullptr){
    ptrToHead = new_node;
    ++count;
    pointToHead->data << std::endl;
    return;
    }

    Node* temp = ptrToHead;
    while(temp->next != nullptr)
    temp = temp->next;
    temp->next = new_node;
    ++count;
    }
    -

    If the linked list is empty, we simply create a new Node and let ptrToHead points to the new Node. If the linked list is not empty, we need to let the next member of the last Node points to the newly created Node. To find the address of the last Node, we use the condition:

    -
    temp->next == nullptr
    -

    This condition stops the while loop and by then, temp is the address of the last Node.

    -

    Now we consider the case that inserting a new Node at the nth position, where n is in the range [1, size()]. The case that we insert a new Node at the first position can be tanckled by the push_front function.

    -

    The graph below illustrate a 5-Node linked list and the case that we intend to insert the new Node at a non-Head position. For example, we insert at the third position, that is, when n == 3:

    -

    Singly Linked List Example

    -

    We can observe that:

    -
      -
    1. the newly created Node becomes the third Node and the original third Node becomes the fourth Node.
    2. -
    3. before inserting
      add2->next == add2
      - after inserting
      add2->next == addx
    4. -
    5. to link following Nodes, we let
      addx->next == add3
      -Now everything is clear: if we want to insert at the nth position, we have to find the n-1 position and link the n-1th Node to the newly created Node. Let’s starting from the ptrToHead and show the relations between Nodes, Addresses and Iteration times:
              counter i       Address         Node
      when i = 0 ptrToHead 1st Node
      i = 1 add2 2ed Node
      i = 2 add3 3ed Node
      ... ... ...
      i = n - 2 add(n - 1) n - 1 Node
      i = n - 1 addn n Node
      - -
    6. -
    -

    Obviously, we will stop our loop when i == n - 2 and by then we can manipulate the n - 1 Node. Following code shows a full view of the insert function.

    -
    // insert at the nth position, n belongs to [1, size()]: O(n)
    // the positions means that we add the new node after (n-1)th position
    template <class T>
    void SinglyLinkedList<T>::insert(size_type position, const T& val){
    if(position < 1 || position > size())
    throw std::domain_error("Invalid Position");
    else if(position == 1)
    push_front(val);
    else{
    Node* new_node = create(val);
    Node* temp = ptrToHead;
    for(size_type i = 0; i != position - 2; ++i)
    temp = temp->next;
    new_node->next = temp->next;
    temp->next = new_node;
    ++count;
    }
    }
    - -

    pop_front, pop_back, erase

    The idea behind these three functions are similar to the inserting operations described above. Hence no further discussion here.

    -
    // delete at the begining: O(1)
    template <class T>
    void SinglyLinkedList<T>::pop_front(){
    if(ptrToHead == nullptr){
    throw std::domain_error("Empty Linked List");
    }

    Node* temp = ptrToHead;
    ptrToHead = ptrToHead->next;
    delete temp;
    --count;
    }

    // delete the last element: O(n)
    template <class T>
    void SinglyLinkedList<T>::pop_back(){
    if(ptrToHead == nullptr){
    throw std::domain_error("Empty Linked List");
    }
    erase(size());
    }

    // delete at nth position: O(n)
    // n belongs to [1, size()], the position after n - 1
    template <class T>
    void SinglyLinkedList<T>::erase(size_type position){
    if(position < 1 || position > size())
    throw std::domain_error("Invalid Position");
    else if(position == 1)
    pop_front();
    else{
    Node* current = ptrToHead;
    for (size_type i = 0; i != position-2; ++i)
    current = current->next;
    Node* temp = current->next;
    current-> next = temp->next;
    delete temp;
    --count;
    }
    }

    ## remove
    // remove elements with specific values: O(n^2)
    template <class T>
    void SinglyLinkedList<T>::remove(const T& val){
    Node* current = ptrToHead;
    size_type i = 0;
    while(current != nullptr){
    if(current->data == val){
    current = current->next;
    erase(i + 1);
    }
    else{
    current = current->next;
    ++i;
    }
    }
    }

    The remove function allows us to remove all elements that contains data values equal to a supplied value. My solution is to find the positions of the Nodes that should be removed and then call the **erase** function by passing the position. Taking an example(see below graph), suppose one want to remove any element that has value **v3**, we should find the previous position, that is, **2ed Node**, and then break the link and rebuilt the link between **2ed Node** and **4th Node** through:
    -

    temp = add2->next
    temp = temp->next

    -

    ![Remove operation](/images/remove.PNG)

    If we loop through the linked list starting from the **ptrToHead**, the mapping relations are as follows:
    -
    counter i       values           position

    when i = 0 v1 1
    i = 1 v2 2
    i = 2 v3 3
    … … …
    i = n - 2 v(n - 1) n - 1
    i = n - 1 vn n

    -
    When i == 2, the position that should be deleted is positioned at **i+1**. Therefore, we pass **i+1** to the **erase** function. But noting that before we delete the Node, we should let **current** points to the Node that closely followed the Node to be deleted. In addition, we should not advance the counter **i** as now the Node pointed by **current** hasn't be checked. But if a Node doesn't satisfy the condition, we move forward the **current** as well as the counter **i**. 

    ## reverse
    In our **SinglyLinkedList**, we define two **reverse** functions, one of which is a member defined based on iteration while the other one is a non-member function defined based on recursion.
    ​```c++
    // reverse the order: iteration version O(n)
    template <class T>
    void SinglyLinkedList<T>::reverse(){
    Node* prev = nullptr;
    Node* current = ptrToHead;
    Node* next;
    while(current != nullptr){
    next = current->next;
    current->next = prev;
    prev = current;
    current = next;
    }
    ptrToHead = prev;
    }
    -

    The basic idea behind the reverse function is that let each Node stores the address of the previous Node.
    The initial information is that:

    -
      -
    1. the previous address for the Head Node is nullptr
    2. -
    3. the current address for the Head Node is ptrToHead
    4. -
    -

    There are four steps in each iteration:

    -
      -
    1. temporarily stores the address of next Node as next = current->next will be rewrite
    2. -
    3. rewrite the current->next with the previous address
    4. -
    5. in next iteration, the previous address is the current address in this iteration, hence let prev = current
    6. -
    7. in next iteration, the current address is the next address in this iteration, hence let current = next
    8. -
    -

    The recursive version starts from the last Node but still uses the same idea. Don’t forget to add this function template as the friend of our SinglyLinkedList class.

    -
    // reverse the order: recursion version O(n)
    // space complexity: O(n)
    template<typename X>
    void reverse(SinglyLinkedList<X>& l, typename SinglyLinkedList<X>::Node* p){
    if(p->next == nullptr || l.empty()){
    l.ptrToHead = p;
    return;
    }
    reverse(l, p->next);
    typename SinglyLinkedList<X>::Node* q = p->next;
    q->next = p;
    p->next = nullptr;
    }
    - -

    Test

    I have tested every member in this class template and the results show that this SinglyLinkedList works perfectly. Please find the test program and results below:

    -
    /*
    * this program tests all operations that provided by the
    * SinglyLinkedList<T> class
    * created by Liam on: 28 Apr 2018
    */

    #include <iostream>
    #include "SinglyLinkedList.h"

    using std::endl; using std::cout;

    template <class T>
    void print(T& l){
    auto it = l.begin();
    for(; it != nullptr; it = it->next){
    cout << it->data << " ";
    }
    cout << endl;
    }

    int main(){

    { // construct an empty linked list
    SinglyLinkedList<int> s;
    if(s.empty())
    cout << "s is an empty linked list\n"
    "the size of s1 is: " << s.size() << endl;

    // call destructor once reaches the end of this block
    }
    cout << endl;

    { // construct a linked list that contains 10 elements, all values are 100
    SinglyLinkedList<int> s(10, 100);

    // construct a linked list by copying from s
    SinglyLinkedList<int> s_copy(s);
    if(!s.empty() && !s_copy.empty()){
    cout << "the size of s is: " << s.size() << endl;
    cout << "the size of s_copy is: " << s_copy.size() << endl;
    }

    // print the contents of s
    cout << "all elements in s: ";
    print(s);

    // call destructor twice
    }
    cout << endl;

    { // assignment
    SinglyLinkedList<int> s(10, 100);
    SinglyLinkedList<int> s_copy;
    s_copy = s;

    // print the contents of s
    }
    cout << endl;

    { // push front
    SinglyLinkedList<double> s;
    for(int i = 5; i != 0; --i)
    s.push_front(i);

    cout << "after adding elements at front, s becomes: ";
    print(s);

    // push back
    for(int i = 5; i != 0; --i)
    s.push_back(i);

    cout << "after adding elements at the end, s becomes: ";
    print(s);


    // insert at position 5
    for(int i = 5; i != 0; --i)
    s.insert(i, 0);

    cout << "after inserting elements in-between, s becomes: ";
    print(s);

    // delete from the begining
    for(int i = 5; i != 0; --i)
    s.pop_front();

    cout << "after deleting from the begining, s becomes: ";
    print(s);

    // delete from the end
    for(int i = 5; i != 0; --i)
    s.pop_back();

    cout << "after deleting from the end, s becomes: ";
    print(s);

    // erase at in-between positions
    for(int i = 3; i != 0; --i)
    s.erase(3);

    cout << "after deleting from other positions, s becomes: ";
    print(s);

    }
    cout << endl;

    { // remove
    SinglyLinkedList<int> s(5, 5);
    for(int i = 5; i != 0; --i)
    s.insert(5, i);

    cout << "at present, s contains following elements: ";
    print(s);

    s.remove(5);
    cout << "after removing all elements equal 5, s becomes: ";
    print(s);
    }
    cout << endl;

    { // test reverse function
    SinglyLinkedList<int> s;
    for(int i = 0; i != 10; ++i)
    s.push_back(i);

    cout << "at present, s contains following elements: ";
    print(s);

    s.reverse();
    cout << "reverse: ";
    print(s);

    s.reverse();
    cout << "reverse again: ";
    print(s);
    }

    return 0;
    }
    - -

    Outputs:

    -
    default constructor
    s is an empty linked list
    the size of s1 is: 0
    destructor

    constructor with parameters
    copy constructor
    the size of s is: 10
    the size of s_copy is: 10
    all elements in s: 100 100 100 100 100 100 100 100 100 100
    destructor
    destructor

    constructor with parameters
    default constructor
    assignment operator
    destructor
    destructor

    default constructor
    after adding elements at front, s becomes: 1 2 3 4 5
    after adding elements at the end, s becomes: 1 2 3 4 5 5 4 3 2 1
    after inserting elements in-between, s becomes: 0 1 0 2 0 3 0 4 0 5 5 4 3 2 1
    after deleting from the begining, s becomes: 3 0 4 0 5 5 4 3 2 1
    after deleting from the end, s becomes: 3 0 4 0 5
    after deleting from other positions, s becomes: 3 0
    destructor

    constructor with parameters
    at present, s contains following elements: 5 5 5 5 1 2 3 4 5 5
    after removing all elements equal 5, s becomes: 1 2 3 4
    destructor

    default constructor
    at present, s contains following elements: 0 1 2 3 4 5 6 7 8 9
    reverse: 9 8 7 6 5 4 3 2 1 0
    reverse again: 0 1 2 3 4 5 6 7 8 9
    destructor
    - -
    -]]>
    - - Algorithms - - - C++ - Data Structures - Algorithms - -
    - - Sorting Algorithms C++ Implementations - Quick Sort - /2018/05/18/Sorting-Algorithms-C-Implementations-Quick-Sort/ - Quick Sort
    /*----------------------------------------------------------------------------- 
    * main.cpp || Created on: 18 May 2018 || Author: Liam
    *-----------------------------------------------------------------------------
    * this program tests three implementations of the quick sort algorithm
    *
    * Logic:
    * 1. select an element from the sequence as the pivot and rearrange the
    * sequence such that all elements less than the pivot are towards the left
    * of it and all elements greater than the pivot are towards the right of it.
    * 2. above process is called partitioning of the sequence. we recursively
    * partition the sequence till that there is only one element left in each
    * segment.
    * 3. for convenience, we can always select the last element as the pivot. If
    * so, we may encounter the case that the position of the pivot
    * is still at the end (or begining) of the rearranged sequence. Such case is
    * the worst case and the time complexity is O(n^). To achieve an average
    * case, in which the time complexity is O(nlogn), we can randomly select
    * the pivot and then put the pivot at the end of the sequence for the
    * rearangement.
    *
    * Complexity analysis:
    * time complexity:
    *
    * Best case: always be balanced at the midpoint in each partition
    * T(n) = 2T(n/2) + cn
    * = 2^k T(n/2^k) + kcn
    * where k = logn
    * therefore = O(nlogn)
    *
    * Average case: when randomly selected as any one of the position,
    * the partition index or iterator is an average index
    * T(n) = T(n - i) + T(i - 1) + cn
    * = 1/n * summation(T(n - i) + T(i - 1)) + cn
    * = O(nlogn)
    *
    * Worst case: unblanced in each partition
    * T(n) = T(n - 1) + cn
    * = T(n - 2) + c(n - 1) + cn
    * = T(n - k) + c(n - k + 1 + ... + n)
    * where k = n-1
    * therefore
    * T(n) = T(1) + c*(2 + 3 + ... + n)
    * = O(n^2)
    *
    * auxiliary space: (non-stable) worst-case = O(1)
    *-----------------------------------------------------------------------------
    */
    - -

    Implementations

     #ifndef SORTINGALGORITHMS_H_
    #define SORTINGALGORITHMS_H_

    #include <ctime>
    #include <cstddef>
    #include <cstdlib>
    #include <iterator>
    #include <stdexcept>
    #include <algorithm>
    #include <iostream>

    // array-based version, first always is the index to indicate the
    // first position of a sequence, last is the index to indicate the
    // last position of a sequence. partitionIndex is the split point
    template <typename T>
    std::size_t Partition(T*p, const std::size_t first, const std::size_t last){
    T pivot = p[last];
    std::size_t partitionIndex = first;
    for(std::size_t i = first; i != last; ++i){
    if(p[i] <= pivot){
    T temp = p[i];
    p[i] = p[partitionIndex];
    p[partitionIndex] = temp;
    ++partitionIndex;
    }
    }
    T temp = p[partitionIndex];
    p[partitionIndex] = p[last];
    p[last] = temp;
    return partitionIndex;
    }

    template <typename T>
    void QuickSort(T*p, const std::size_t first, const std::size_t last){
    if(first >= last) return;
    std::size_t partitionIndex = Partition(p, first, last);
    if(partitionIndex != 0){
    QuickSort(p, first, partitionIndex - 1);
    }
    QuickSort(p, partitionIndex + 1, last);
    }

    // iterator-based version STL style (C++11)
    // partitionIter denotes the position of the
    // split point
    template<typename BidirrectionalIterator>
    BidirrectionalIterator Partition(BidirrectionalIterator begin,
    BidirrectionalIterator end){
    auto pivot = std::prev(end);
    auto partitionIter = begin;

    for(auto iter = begin; iter != pivot; ++iter){
    if(*iter <= *pivot){
    std::iter_swap(iter, partitionIter);
    ++partitionIter;
    }
    }
    std::iter_swap(partitionIter, pivot);
    return partitionIter;
    }

    template <typename BidirrectionalIterator>
    void QuickSort(BidirrectionalIterator begin, BidirrectionalIterator end){
    if(std::distance(begin, end) <= 1) return;
    auto partitionIter = Partition(begin, end);
    if(std::prev(partitionIter) != begin){
    QuickSort(begin, partitionIter);
    }
    QuickSort(std::next(partitionIter), end);
    }

    // user-defined comparator + improved using random selected pivot
    // return a random integer in the range [0, n)
    int nrand(int n)
    {
    if(n <= 0 || n > RAND_MAX)
    throw std::domain_error("Argument to nrand is out of range");

    const int bucket_size = RAND_MAX /n;
    int r;

    do r = rand() / bucket_size;
    while(r >= n);

    return r;
    }

    template<typename BidirrectionalIterator, typename Comparator>
    BidirrectionalIterator Partition(BidirrectionalIterator begin,
    BidirrectionalIterator end, Comparator comp){
    std::srand (std::time(nullptr));
    auto randomIndex = nrand(std::distance(begin, end));
    std::cout << randomIndex << "nihao" << std::endl;
    auto pivot = std::prev(end);
    std::iter_swap(pivot, std::next(begin, randomIndex));

    auto partitionIter = begin;
    for(auto iter = begin; iter != pivot; ++iter){
    if(comp(*iter, *pivot)){
    std::iter_swap(iter, partitionIter);
    ++partitionIter;
    }
    }
    std::iter_swap(partitionIter, pivot);
    return partitionIter;
    }

    template <typename BidirrectionalIterator, typename Comparator>
    void QuickSort(BidirrectionalIterator begin, BidirrectionalIterator end,
    Comparator comp){
    if(std::distance(begin, end) <= 1) return;
    auto partitionIter = Partition(begin, end, comp);
    if(std::prev(partitionIter) != begin){
    QuickSort(begin, partitionIter, comp);
    }
    QuickSort(std::next(partitionIter), end, comp);
    }

    #endif /* SORTINGALGORITHMS_H_ */

    -

    Test program-main.cpp

    -
    #include <iostream>	// std::cout, endl
    #include <string> // std::string
    #include <list> // std::list
    #include <vector> // std::vector
    #include "SortingAlgorithms.h"

    using std::cout; using std::cin;
    using std::endl; using std::list;
    using std::vector; using std::string;

    // struct defined for testing
    struct student{
    string name;
    int age;
    };

    // comparator 1
    template<class T>
    bool compare_age(const T& x, const T& y){
    return x.age < y.age;
    }

    // comparator 2
    template<class T>
    bool compare_name(const T& x, const T& y){
    return x.name < y.name;
    }

    int main(){
    // test 1: array-based version
    double arr[10] = {10, 9, 8, 7, 6, 5, 4, 3, 2, 1};
    QuickSort(arr, 0, 9);
    cout << "Sorted array: ";
    for(int i = 0; i != 10; ++i)
    cout << arr[i] << " ";
    cout << "\n";

    // test 2: iterator-based version
    string str("eclipseworkspace");
    QuickSort(str.begin(), str.end());
    cout << "Sorted string " << str << "\n";

    list<int> l{10, 9, 8, 7, 6, 5, 4, 3, 2, 1};
    QuickSort(l.begin(), l.end());
    cout << "Sorted list: ";
    for (auto i: l)
    cout << i << " ";

    cout << endl;

    // test 3: user-defined comparator
    vector<student> students;
    while(cin){
    student temp;
    cin >> temp.name >> temp.age;
    if(cin)
    students.push_back(temp);
    }

    QuickSort(students.begin(), students.end(), compare_age<student>);
    cout << "Sorted vector according to age:\n";
    for (auto i: students)
    cout << i.name << " " << i.age << "\n";

    QuickSort(students.begin(), students.end(), compare_name<student>);
    cout << "Sorted vector according to name:\n";
    for (auto i: students)
    cout << i.name << " " << i.age << "\n";

    return 0;
    }
    - -

    Outputs:

    -
    Sorted array: 1 2 3 4 5 6 7 8 9 10 
    Sorted string acceeeiklopprssw
    Sorted list: 1 2 3 4 5 6 7 8 9 10
    John 18
    Mike 16
    Liam 23
    Anna 26
    Bobo 21
    2nihao
    1nihao
    1nihao
    Sorted vector according to age:
    Mike 16
    John 18
    Bobo 21
    Liam 23
    Anna 26
    2nihao
    1nihao
    Sorted vector according to name:
    Anna 26
    Bobo 21
    John 18
    Liam 23
    Mike 16
    ]]>
    - - Algorithms - - - C++ - Algorithms - -
    - - Sorting Algorithms C++ Implementations - Merge Sort - /2018/05/17/Sorting-Algorithms-C-Implementations-Merge-Sort/ - Merge Sort
    /*----------------------------------------------------------------------------- 
    * main.cpp || Created on: 16 May 2018 || Author: Liam
    *-----------------------------------------------------------------------------
    * this program tests three implementation of the merge sort algorithm.
    *
    * Logic:
    * 1. recursively partition the sequence a mid point, until that there is only
    * one element left, that is when it cannot be partitioned further.
    * 2. each pair of partitioned parts will be rearranged in non-decreasing order.
    *
    * Complexity analysis:
    * time complexity: Best, Average, Worst = O(nlogn)
    * auxiliary space: worst-case = O(n)
    *
    *-----------------------------------------------------------------------------
    */
    - -

    Implementations:

    -
    #ifndef SORTINGALGORITHMS_H_
    #define SORTINGALGORITHMS_H_

    #include <cstddef>
    #include <vector>
    #include <iterator>
    #include <algorithm>

    // array-based version
    template <typename T>
    void Merge(T* left, std::size_t left_size, T* right, std::size_t right_size){
    T left_copy[left_size];
    T right_copy[right_size];

    /* O(n): n = left_size + right_size */
    for(std::size_t i = 0; i != left_size; ++i)
    left_copy[i] = left[i];
    for(std::size_t i = 0; i != right_size; ++i)
    right_copy[i] = right[i];

    /* O(n): n = left_size + right_size */
    std::size_t i, j, k;
    i = j = k = 0;
    while(i != left_size && j != right_size){
    if(left_copy[i] <= right_copy[j]){
    left[k] = left_copy[i];
    ++i;
    }else{
    left[k] = right_copy[j];
    ++j;
    }
    ++k;
    }

    while(i != left_size){
    left[k] = left_copy[i];
    ++i;
    ++k;
    }

    while(j != right_size){
    left[k] = right_copy[j];
    ++j;
    ++k;
    }
    }

    template <typename T>
    void MergeSort(T* p, std::size_t n){
    std::size_t mid = n/2;
    if(mid == 0) return;

    MergeSort(p, mid);
    MergeSort(p + mid, n - mid);

    /* O(n): n = n*/
    Merge(p, mid, p + mid, n - mid);
    }

    // iterator-based version STL style (C++11)
    template <typename ForwardIterator>
    void Merge(ForwardIterator begin, ForwardIterator midIterator,
    ForwardIterator end){
    typedef typename std::iterator_traits<ForwardIterator>::value_type Type;

    /* O(n), n = end - begin*/
    std::vector<Type> left(begin, midIterator);
    std::vector<Type> right(midIterator, end);

    auto iter_l = left.begin();
    auto iter_r = right.begin();

    /* O(n), n = end - begin*/
    while(iter_l != left.end() && iter_r != right.end()){
    *begin++ = *iter_l <= *iter_r ? *iter_l++ : *iter_r++;
    }

    std::copy(iter_l, left.end(), begin);
    std::copy(iter_r, right.end(), begin);
    }

    template <typename ForwardIterator>
    void MergeSort(ForwardIterator begin, ForwardIterator end){

    // O(1) for random access iterator
    // O(n) for others: n = end - begin
    auto mid = std::distance(begin, end)/2;
    if(mid == 0) return;

    // O(1) for random access iterator
    // O(n) for others: n = end - begin
    auto midIterator = std::next(begin, mid);
    MergeSort(begin, midIterator);
    MergeSort(midIterator, end);

    // O(n): n = end - begin
    Merge(begin, midIterator, end);
    }

    // iterator-based version with user-defined comparator
    template <typename ForwardIterator, typename Comparator>
    void Merge(ForwardIterator begin, ForwardIterator midIterator,
    ForwardIterator end, Comparator comp){
    typedef typename std::iterator_traits<ForwardIterator>::value_type Type;

    /* O(n), n = end - begin*/
    std::vector<Type> left(begin, midIterator);
    std::vector<Type> right(midIterator, end);

    auto iter_l = left.begin();
    auto iter_r = right.begin();

    /* O(n), n = end - begin*/
    while(iter_l != left.end() && iter_r != right.end()){
    *begin++ = comp(*iter_l, *iter_r) ? *iter_l++ : *iter_r++;
    }

    std::copy(iter_l, left.end(), begin);
    std::copy(iter_r, right.end(), begin);

    }

    template <typename ForwardIterator, typename Comparator>
    void MergeSort(ForwardIterator begin, ForwardIterator end, Comparator comp){

    // O(1) for random access iterator
    // O(n) for others: n = end - begin
    auto mid = std::distance(begin, end)/2;
    if(mid == 0) return;

    // O(1) for random access iterator
    // O(n) for others: n = end - begin
    auto midIterator = std::next(begin, mid);
    MergeSort(begin, midIterator, comp);
    MergeSort(midIterator, end, comp);

    // O(n): n = end - begin
    Merge(begin, midIterator, end, comp);
    }

    #endif /* SORTINGALGORITHMS_H_ */
    - -

    Test program-main.cpp

    -
    #include <iostream>	// std::cout, endl
    #include <string> // std::string
    #include <list> // std::list
    #include <vector> // std::vector
    #include "SortingAlgorithms.h"

    using std::cout; using std::cin;
    using std::endl; using std::list;
    using std::vector; using std::string;

    // struct defined for testing
    struct student{
    string name;
    int age;
    };

    // comparator 1
    template<class T>
    bool compare_age(const T& x, const T& y){
    return x.age <= y.age;
    }

    // comparator 2
    template<class T>
    bool compare_name(const T& x, const T& y){
    return x.name <= y.name;
    }

    int main(){
    // test 1: array-based version
    double arr[10] = {10, 9, 8, 7, 6, 5, 4, 3, 2, 1};
    MergeSort(arr, 10);
    cout << "Sorted array: ";
    for(int i = 0; i != 10; ++i)
    cout << arr[i] << " ";
    cout << "\n";

    // test 2: iterator-based version
    string str("eclipseworkspace");
    MergeSort(str.begin(), str.end());
    cout << "Sorted string " << str << "\n";

    list<int> l{10, 9, 8, 7, 6, 5, 4, 3, 2, 1};
    MergeSort(l.begin(), l.end());
    cout << "Sorted list: ";
    for (auto i: l)
    cout << i << " ";

    cout << endl;

    // test 3: user-defined comparator
    vector<student> students;
    while(cin){
    student temp;
    cin >> temp.name >> temp.age;
    if(cin)
    students.push_back(temp);
    }

    MergeSort(students.begin(), students.end(), compare_age<student>);
    cout << "Sorted vector according to age:\n";
    for (auto i: students)
    cout << i.name << " " << i.age << "\n";

    MergeSort(students.begin(), students.end(), compare_name<student>);
    cout << "Sorted vector according to name:\n";
    for (auto i: students)
    cout << i.name << " " << i.age << "\n";

    return 0;
    }
    - -

    Outputs:

    -
    Sorted array: 1 2 3 4 5 6 7 8 9 10 
    Sorted string acceeeiklopprssw
    Sorted list: 1 2 3 4 5 6 7 8 9 10
    John 18
    Mike 16
    Liam 23
    Anna 26
    Bobo 21
    Sorted vector according to age:
    Mike 16
    John 18
    Bobo 21
    Liam 23
    Anna 26
    Sorted vector according to name:
    Anna 26
    Bobo 21
    John 18
    Liam 23
    Mike 16
    ]]>
    - - Algorithms - - - C++ - Algorithms - -
    - - Sorting Algorithms C++ Implementations - Insertion Sort - /2018/05/15/Sorting-Algorithms-C-Implementations-Insertion-Sort/ - Insertion Sort
    /*----------------------------------------------------------------------------- 
    * main.cpp || Created on: 15 May 2018 || Author: Liam
    *-----------------------------------------------------------------------------
    * this program tests three versions of insertion sort implementations.
    *
    * Logic:
    * 1. the sequence can always be divided into two parts: sorted and unsorted.
    * We loop thru from the second position to the last position, before each
    * loop, the elements on the left-side of the position are sorted. we can
    * call this position as sortedIndex. After each loop, [0, sortedIndex] is
    * sorted, and then we forward the sortedIndex 1 position.
    * 2. in each loop, an embeded iteration starts from the initial position to
    * the prev of the sortedIndex, or in reverse order, from the prev of the
    * sortedIndex to the begining, comparing each value denoted in the range
    * with the value denoted by sortedIndex. When found the first element that
    * is greater than the value denoted by sortedIndex, we insert here
    * the element denoted by sortedIndex.
    *
    * Complexity analysis:
    * time complexity: Best = O(n), Average, Worst O(n^2)
    * auxiliary space: worst-case = O(1)
    *
    *-----------------------------------------------------------------------------
    */
    - -

    Implementations

    -
    #ifndef SORTINGALGORITHMS_H_
    #define SORTINGALGORITHMS_H_

    #include <cstddef>
    #include <algorithm>
    #include <iterator>

    // array-based version
    template <class T>
    void InsertionSort(T* p, std::size_t n){
    if (n == 0) return;
    for(std::size_t i = 1; i != n; ++i){
    T value = p[i];
    std::size_t sortedIndex = i;
    // the left-side of the sortedIndex is sorted
    while(sortedIndex >0 && p[sortedIndex - 1] > value){
    p[sortedIndex] = p[sortedIndex - 1];
    sortedIndex = sortedIndex - 1;
    }
    p[sortedIndex] = value;
    }
    }

    // iterator-based version STL-stype (C++11)
    template <typename ForwardIterator>
    void InsertionSort(ForwardIterator begin, ForwardIterator end){
    if(begin == end) return;
    for (auto iter = std::next(begin); iter != end; ++iter){
    auto insertPoint = std::upper_bound(begin, iter, *iter); // logN
    std::rotate(insertPoint, iter, std::next(iter)); // N
    }
    }

    // iterator-based version with comparator
    template <typename ForwardIterator, typename Comparator>
    void InsertionSort(ForwardIterator begin, ForwardIterator end,
    Comparator comp){
    if(begin == end) return;
    for (auto iter = std::next(begin); iter != end; ++iter){
    auto insertPoint = std::upper_bound(begin, iter, *iter, comp);
    std::rotate(insertPoint, iter, std::next(iter));
    }
    }

    #endif /* SORTINGALGORITHMS_H_ */
    - -

    Test program-main.cpp

    -
    #include <iostream> // std::cin, cout, endl
    #include <vector> // std::vector
    #include <string> // std::string
    #include <list> // std::list
    #include "SortingAlgorithms.h"

    using std::cout; using std::vector;
    using std::endl; using std::string;
    using std::list; using std::cin;

    // struct defined for testing
    struct student{
    string name;
    int age;
    };

    // comparator 1
    template<class T>
    bool compare_age(const T& x, const T& y){
    return x.age < y.age;
    }

    // comparator 2
    template<class T>
    bool compare_name(const T& x, const T& y){
    return x.name < y.name;
    }

    int main(){
    // test 1: array-based version
    double arr[10] = {10, 9, 8, 7, 6, 5, 4, 3, 2, 1};
    InsertionSort(arr, arr+ 10);
    cout << "Sorted array: ";
    for(int i = 0; i != 10; ++i)
    cout << arr[i] << " ";
    cout << "\n";

    // test 2: iterator-based version
    string str("eclipseworkspace");
    InsertionSort(str.begin(), str.end());
    cout << "Sorted string " << str << "\n";

    list<int> l{10, 9, 8, 7, 6, 5, 4, 3, 2, 1};
    InsertionSort(l.begin(), l.end());
    cout << "Sorted list: ";
    for (auto i: l)
    cout << i << " ";

    cout << endl;

    // test 3: user-defined comparator
    vector<student> students;
    while(cin){
    student temp;
    cin >> temp.name >> temp.age;
    if(cin)
    students.push_back(temp);
    }

    InsertionSort(students.begin(), students.end(), compare_age<student>);
    cout << "Sorted vector according to age:\n";
    for (auto i: students)
    cout << i.name << " " << i.age << "\n";

    InsertionSort(students.begin(), students.end(), compare_name<student>);
    cout << "Sorted vector according to name:\n";
    for (auto i: students)
    cout << i.name << " " << i.age << "\n";

    return 0;
    }
    - - -

    Outputs

    -
    Sorted array: 1 2 3 4 5 6 7 8 9 10 
    Sorted string acceeeiklopprssw
    Sorted list: 1 2 3 4 5 6 7 8 9 10
    John 18
    Mike 16
    Liam 23
    Anna 26
    Bobo 21
    Sorted vector according to age:
    Mike 16
    John 18
    Bobo 21
    Liam 23
    Anna 26
    Sorted vector according to name:
    Anna 26
    Bobo 21
    John 18
    Liam 23
    Mike 16
    ]]>
    - - Algorithms - - - C++ - Algorithms - -
    - - C++ - Revisiting character pictures - /2018/05/14/C-Revisiting-character-pictures/ - - - Programming - - - C++ - Notes - - - - Sorting Algorithms C++ Implementations - Bubble Sort - /2018/05/14/Sorting-Algorithms-C-Implementations-Bubble-Sort/ - Bubble Sort
    /*-----------------------------------------------------------------------------
    * main.cpp || Created on: 13 May 2018 || Author: Liam
    *-----------------------------------------------------------------------------
    * this program tests the bubble sort algorithm implemented in three ways
    *
    * Logic:
    * 1. each time find the largest number of the unsorted sequence and put it at
    * the last position of the sequence. Then the unsorted sequence becomes the
    * sequence, which is the original sequence excludes the last element.
    * * compare adjacent numbers one pair by one pair from the begining
    * position to the second last position, and exchange the position of
    * two numbers if left-side number is larger than the right-side number
    * * after each iteration, right-most position of the unsorted sequence
    * holds the largest element, and hence the unsorted sequence becomes
    * the sequence, which is the original sequence excludes the last
    * element.
    * 2. repectively perform step 1. There are two cases that the iteration stops:
    * * the unsorted sequence only has one number left
    * * there is no any exchange happens in last iteration, which means that
    * all elements are in order already. Therefore, we do not need to
    * perform step 1 again. In this case, we get the best time complexity
    * if the sequence is completely sorted after the first iteration.
    *
    * Complexity analysis:
    * time complexity: Best = O(n), Average, Worst = O(n^2)
    * auxiliary space: worst-case = O(1)
    *-----------------------------------------------------------------------------
    */
    - -

    Implementations

     #ifndef SORTINGALGORITHMS_H_
    #define SORTINGALGORITHMS_H_

    #include <cstddef>
    #include <algorithm>
    #include <iterator>

    // array based version
    template <typename T>
    void BubbleSort(T* p, std::size_t n){
    if (n == 0) return;
    for(std::size_t i = n-1; i != 0; --i){
    bool flag = true;
    for(std::size_t j = 0; j != i; ++j){
    if(p[j] > p[j+1]){
    T temp = p[j];
    p[j] = p[j+1];
    p[j+1] = temp;
    flag = false;
    }
    }
    if(flag == true) break;
    }
    }

    // iterator based version STL style (c++11)
    template <typename BidirectionalIterator>
    void BubbleSort(BidirectionalIterator begin, BidirectionalIterator end){
    if(begin == end) return;
    for(auto endIter = std::prev(end); endIter != begin; --endIter){
    bool flag = true;
    for(auto iter = begin; iter != endIter; ++iter){
    auto iterNext = std::next(iter);
    if(*iter > *iterNext){
    std::iter_swap(iter, iterNext);
    flag = false;
    }
    }
    if(flag == true) break;
    }
    }

    // iterator based version with user-defined comparator
    template <typename BidirectionalIterator, typename Comparator>
    void BubbleSort(BidirectionalIterator begin, BidirectionalIterator end,
    Comparator comp){
    if(begin == end) return;
    for(auto endIter = std::prev(end); endIter != begin; --endIter){
    bool flag = true;
    for(auto iter = begin; iter != endIter; ++iter){
    auto iterNext = std::next(iter);
    if(comp(*iterNext, *iter)){
    std::iter_swap(iter, iterNext);
    flag = false;
    }
    }
    if(flag == true) break;
    }
    }

    #endif /* SORTINGALGORITHMS_H_ */

    -

    Test Program-main.cpp

    -
    #include <iostream>
    #include <vector>
    #include <string>
    #include <list>
    #include "SortingAlgorithms.h"

    using std::cout; using std::vector;
    using std::endl; using std::string;
    using std::list; using std::cin;

    // struct defined for testing
    struct student{
    string name;
    int age;
    };

    // comparator 1
    template<class T>
    bool compare_age(const T& x, const T& y){
    return x.age < y.age;
    }

    // comparator 2
    template<class T>
    bool compare_name(const T& x, const T& y){
    return x.name < y.name;
    }

    int main(){
    // test 1: array-based version
    double arr[10] = {10, 9, 8, 7, 6, 5, 4, 3, 2, 1};
    BubbleSort(arr, arr+ 10);
    cout << "Sorted array: ";
    for(int i = 0; i != 10; ++i)
    cout << arr[i] << " ";
    cout << "\n";

    // test 2: iterator-based version
    string str("eclipseworkspace");
    BubbleSort(str.begin(), str.end());
    cout << "Sorted string " << str << "\n";

    list<int> l{10, 9, 8, 7, 6, 5, 4, 3, 2, 1};
    BubbleSort(l.begin(), l.end());
    cout << "Sorted list: ";
    for (auto i: l)
    cout << i << " ";

    cout << endl;

    // test 3: user-defined comparator
    vector<student> students;
    while(cin){
    student temp;
    cin >> temp.name >> temp.age;
    if(cin)
    students.push_back(temp);
    }

    BubbleSort(students.begin(), students.end(), compare_age<student>);
    cout << "Sorted vector according to age:\n";
    for (auto i: students)
    cout << i.name << " " << i.age << "\n";

    BubbleSort(students.begin(), students.end(), compare_name<student>);
    cout << "Sorted vector according to name:\n";
    for (auto i: students)
    cout << i.name << " " << i.age << "\n";

    return 0;
    }
    - -

    Outputs:

    -
    Sorted array: 1 2 3 4 5 6 7 8 9 10 
    Sorted string acceeeiklopprssw
    Sorted list: 1 2 3 4 5 6 7 8 9 10
    John 18
    Mike 16
    Liam 23
    Anna 26
    Bobo 21
    Sorted vector according to age:
    Mike 16
    John 18
    Bobo 21
    Liam 23
    Anna 26
    Sorted vector according to name:
    Anna 26
    Bobo 21
    John 18
    Liam 23
    Mike 16
    ]]>
    - - Algorithms - - - C++ - Algorithms - -
    - - Sorting Algorithms C++ Implementations-Selection Sort - /2018/05/09/Sorting-Algorithms-C-Implementations/ - Selection Sort
    /*-----------------------------------------------------------------------------
    * Created on: 9 May 2018 || Author: Liam
    *-----------------------------------------------------------------------------
    * This file contains three generic functions that implement
    * the selection sort algorithm, and a test program
    *
    * Logical steps:
    * 1. select the minimum value from the sequence
    * 2. put the minimun value in the first position
    * 3. ignore the first position and sort the remaining
    * sequence by repetitively executing step 1 and 2
    * till the last number
    *
    * Complexity analysis:
    * time complexity: Best, Average and Worst-case = O(n^2)
    * auxiliary space: worst-case = O(1)
    *
    *-----------------------------------------------------------------------------
    */
    -

    Implementations

    -
    #ifndef SORTINGALGORITHMS_H_
    #define SORTINGALGORITHMS_H_

    #include <cstddef>
    #include <iterator>
    #include <algorithm>

    // built-in array-based version
    template <typename T>
    void SelectionSort(T* p, std::size_t n){
    if(n > 0){
    for(std::size_t i = 0; i != n-1; ++i){
    std::size_t imin = i;
    for(std::size_t j = imin+1; j != n; ++j){
    if(p[i] > p[j])
    imin = j;
    }
    T temp = p[i];
    p[i] = p[imin];
    p[imin] = temp;
    }
    }
    }

    // iterator-based version STL style (C++11)
    template <typename ForwardIterator>
    void SelectionSort(ForwardIterator begin, ForwardIterator end){
    for (; begin != end; ++begin){
    auto imin = std::min_element(begin, end);
    if(imin != begin)
    std::iter_swap(begin, imin);
    }
    }

    // iterator-based version with comparator
    template <typename ForwardIterator, typename Comparator>
    void SelectionSort(ForwardIterator begin, ForwardIterator end,
    Comparator comp){
    for (; begin != end; ++begin){
    auto imin = std::min_element(begin, end, comp);
    if(imin != begin)
    std::iter_swap(begin, imin);
    }
    }
    #endif /* SORTINGALGORITHMS_H_ */
    - -

    Test Program-main.cpp

    -
    #include <iostream>	// std::cout, endl
    #include <string> // std::string
    #include <list> // std::list
    #include <vector> // std::vector
    #include "SortingAlgorithms.h"

    using std::cout; using std::cin;
    using std::endl; using std::list;
    using std::vector; using std::string;

    // struct defined for testing
    struct student{
    string name;
    int age;
    };

    // comparator 1
    template<class T>
    bool compare_age(const T& x, const T& y){
    return x.age < y.age;
    }

    // comparator 2
    template<class T>
    bool compare_name(const T& x, const T& y){
    return x.name < y.name;
    }

    int main(){
    // test 1: array-based version
    double arr[10] = {10, 9, 8, 7, 6, 5, 4, 3, 2, 1};
    SelectionSort(arr, arr+ 10);
    cout << "Sorted array: ";
    for(int i = 0; i != 10; ++i)
    cout << arr[i] << " ";
    cout << "\n";

    // test 2: iterator-based version
    string str("eclipseworkspace");
    SelectionSort(str.begin(), str.end());
    cout << "Sorted string " << str << "\n";

    list<int> l{10, 9, 8, 7, 6, 5, 4, 3, 2, 1};
    SelectionSort(l.begin(), l.end());
    cout << "Sorted list: ";
    for (auto i: l)
    cout << i << " ";

    cout << endl;

    // test 3: user-defined comparator
    vector<student> students;
    while(cin){
    student temp;
    cin >> temp.name >> temp.age;
    if(cin)
    students.push_back(temp);
    }

    SelectionSort(students.begin(), students.end(), compare_age<student>);
    cout << "Sorted vector according to age:\n";
    for (auto i: students)
    cout << i.name << " " << i.age << "\n";

    SelectionSort(students.begin(), students.end(), compare_name<student>);
    cout << "Sorted vector according to name:\n";
    for (auto i: students)
    cout << i.name << " " << i.age << "\n";

    return 0;
    }
    -

    Outputs:

    -
    Sorted array: 1 2 3 4 5 6 7 8 9 10 
    Sorted string acceeeiklopprssw
    Sorted list: 1 2 3 4 5 6 7 8 9 10
    John 18
    Mike 16
    Liam 23
    Anna 26
    Bobo 21
    Sorted vector according to age:
    Mike 16
    John 18
    Bobo 21
    Liam 23
    Anna 26
    Sorted vector according to name:
    Anna 26
    Bobo 21
    John 18
    Liam 23
    Mike 16
    ]]>
    - - Algorithms - - - C++ - Algorithms - -
    - - C++ - Managing memory (almost) automatically - /2018/05/09/C-Managing-memory-almost-automatically/ - In last chapter, we write a new class named Student_info to encapsulate the pointer to Core so that we do not need to concern about the memory management. Now we’ll further improve our class by seperating the class into two classes: one is a pure interface class and the other is a single pointerlike class which manages the underlying memory. The purpose to do so is that we then can use the pointerlike class with mutiple interface classes. In addition, by doing so, we can avoid copying objects unnecessarily. So what do we mean by saying copy an object? If an object x refers to an object y, does copying x cause y to be copied too ?

    -
      -
    1. if y is a memer of x, the answer must be yes
    2. -
    3. if x is nothing but a pointer to y, the answer is no.
    4. -
    -

    This chapter defines three versions of our pointerlike class, each of which differs from the others in how it defines copying.

    -

    Handles that copy their objects

    It is known that pointer is a primitive, low-level data structure. Working with pointers directly may leads to severe mistakes due to the fact that pointers are independent of the objects to which they point(Koenig and Moo 2000):

    -

    1. Copying a pointer doesn’t copy the corresponding object, leading to surprises if two pointers inadvertently point to the same object.

    -
    int* p = new int(10);   // p: pointer to an int object that has value 10
    int* q = p; // q: points to the same int object
    *q = 100; // if we modify the object pointed by q
    cout << *p; // we inevitably changes the object pointed by p
    // the output is 100
    -

    2. Destroying a pointer doesn’t destroy its object, leading to memory leaks.

    -
    void nameless(size_t n){
    int* p = new int[n];// local variable p is destroyed when this function
    } // finishes, however, the dynamically allocated // array still exists on the heap.
    -

    3. Deleting an object without destroying a pointer to it leads to a dangling pointer, which causes undefined behavior if the program uses the pointer.

    -
    int* p = new int(10);   // p: pointer to an int object that has value 10
    int* q = p; // q: points to the same int object
    delete p; // destroy the object pointed by p
    p = nullptr; // p points to nowhere now
    *q = 100; // undefined behavior as the object pointed by q has been destroyed.
    - -

    4. Creating a pointer without initializing it leaves the pointer unbound, which also causes undefined behavior if the program uses it.

    -
    int* p;                 // unnitialized variable p, which is unbound to any object
    *p = 100; // undefined behavior
    -

    The Student_info class allows us to use pointers without worrying about above problems. Now we still let Student_info to provide the interface, but makes the handle class be independent of the type of the object that it manages. The properties that our class will provide are :

    -

    1. A Handle is a value that refers to an object.

    -

    2. We can copy a Handle object.

    -

    3. We can test a Handle object to determine whether it is bound to another object.

    -

    4. We can use a Handle to trigger polymorphic behavior when it points to an object of a class that belongs to an inheritance hierarchy. That is, if we call a virtual function through our class, we want the implementation to choose the function to run dynamically, just as if we’d called the function through a real pointer.

    -

    Our Handle class will take over the memory management and therefore, we should attach only one Handle to any object, and we should not access the object directly through a built-in pointer. To tackle problems when using a built-in pointer,

    -
      -
    1. When we copy a Handle object, we’ll make a new copy of the object so that each Handle points to its own copy, such as what the copy constructor does in the Student_info class, calling clone() to create a new object.
    2. -
    3. When we destroy a Handle, it will destroy the associated object, such as what the destructor does in the Student_info.
    4. -
    5. We allows users to create unbound Handles but we will throw an exception if the user attempts to access the object to which an unbound Handle refers. Users who want to avoid the exception can test to see whether the Handle is bound, for example, the operations defined in the Student_info class check whether the handle object was bound to a real object.
    6. -
    -

    A generic Handle class

    Now, let’s write the Handle class:

    -
    template <class T> class Handle{
    public:
    Handle(): p(0) {}
    Handle(const Handle& s): p(0) {
    if (s.p) p = s.p->clone();
    }

    Handle& operator=(const Handle& rhs){
    if(&rhs != this){
    delete p;
    p = rhs.p ? rhs.p->clone() : 0;
    }
    return *this;
    }
    ~Handle() { delete p; }

    Handle(T* t): p(t) { }
    operator bool() const { return p; } // type conversion
    T& operator*() const;
    T& operator->() const;
    private:
    T* p;
    };
    -

    Firstly, we observe that the Handle is a class template and can accommodate to any type. For example, Handle holds a pointer to an object of Core type.

    -

    The default constructor initializes the pointer to a nullptr. The copy constructor lets the Handle object refers to a newly created object that has the same value as the object pointed by the passed argument. The operator= is samilar to the copy constructor except that it destroyes the original object pointed by the Handle object. The destructor is obvious. All these four members are defined exactly the same as those defined in the Student_info class.

    -

    The other constructor that takes an argument lets us to bind the pointer to an actual object:

    -
    Handle<Core> student(new Grad);
    -

    Finally, we define three operator functions: the first one operator bool() tests the value of a Handle, and returns true if the Handle is bound to an object, and false otherwise(in fact converts the Handle type to a bool type value); The other two deine operator* and operator-> which give access to the object bound to the Handle:

    -
    template <class T>
    T& Handle<T>::operator* () const{
    if(p)
    return *p;
    else
    throw runtime_error("unbound Handle");
    }

    template <class T>
    T* Handle<T>::operator->() const {
    if(p)
    return p;
    else
    throw runtime_error("unbound Handle");
    }
    -

    The operator* allows us to access *student.p by using *student. It yields a reference to the bound object.
    The -> operator is used to access a member whose name appears in its right operand from an object named by its left operand. It returns a value that can be treated as a pointer. Therefore, if x is a value that defines operator->, then

    -
    x->y;
    -

    is equivalent to

    -
    (x.operator->())->y;
    -

    is equivalent to

    -
    x.p->y; // p is pointer data member, for example, p is Core*
    -

    By doing so, we can use the Handle object as if we are using a pointer to the associated object. Both operator* and operator-> yield either a reference or a pointer, through which we obtain dynamic binding. For example, if we execute student->grade(), we’re calling grade() through student.p, that is, a pointer. The particular version of grade to be run depends on the type of the object to which student.p points to. Similarly, if we execute (*student).grade(), we’re calling grade() through a reference to the object and so the implementation will decide which particular version of the function to call.

    -

    Using a generic Handle

    int main(){
    vector< Handle<Core> > students; // changed type
    Handle<Core> record;
    char ch;
    string::size_type maxlen = 0;

    // read and store the data
    while(cin >> ch){
    if (ch == 'U')
    record = new Core; ( // allocate a Core object
    else
    record = new Grad; // allocate a Grad object
    record->read(cin); // Handle<T>::->, then virtual call to read
    maxlen = max(maxlen, record->name().size());
    students.push_back(record);
    }

    // write the names and grades
    for (vector< Handle<Core> >::size_type i = 0; i != students.size(); ++i){
    // students[i] is a Handle, which we deference to call the function
    cout << students[i]->name()
    << string(maxlen + 1 - students[i]->name().size(), ' ');
    try{
    double final_grade = students[i]->grade();
    streamsize prec = cout.precision();
    cout << setprecision(3) << final_grade
    << setprecision(prec) << endl;
    }catch (domain_error e){
    cout << e.what() << endl;
    }
    }
    return 0;
    }
    - -

    We can rewrite our Student_info class to a pure interface class:

    -
    class Student_info{
    Student_info () { } // calls default constructor of Handle<Core>
    Student_info (std::istream& is) { read(is); }
    // no copy, assign, or destructor: they're no longer needed

    std::istream& read(std::istream);

    std::string name() const {
    if(cp)
    return cp->name();
    else
    throw runtime_error("uninitialized Student");
    }

    double grade() const {
    if(cp)
    return cp->grade()
    else
    throw runtime_error("uninitialized Student");
    }
    static bool compare(const Student_info& s1, const Student_info& s2){
    return s1.name() < s2.name();
    }
    private:
    Handle<Core> cp;
    };

    Since the **Handle** class defines constructor, copy constructor, assignment operator, as well as destructor, we do not need to define these members for our **Student_info** again. Here we need one step more, that is, to redefine the **read** function:
    ```c++
    istream& Student_info::read(istream& is){
    char ch;
    is >> ch;
    if(ch == 'U')
    cp = new Core(is); // implicitly converts from a Core* to a Handle<Core> through Handle::Handle(T*)
    else // then assigns the value from the temporary Handle object to cp
    cp = new Grad(is);

    return is;
    }
    -

    when we execute the cp = new Core(is), the right-hand side creates a new Core object from input stream, which we implicitly convert to a Handle using Handle(T*) constructor. That Handle value is then assigned to cp by calling the assignment operator. The assignment constructs and destroys an extra copy of the Core object that we created. The reason behind this is that copying or assigning a Handle object always makes a new copy of the object that the Handle points to. Doing so can avoid the dangling pointer as each Handle only points to its own object, however, may also make uncessary copies like above assignment operation.

    -

    Reference-counted handles

    This section solves above problem by providing a Handle class that does not copy the underlying object when the Handle itself is copied. To avoid danglling pointer problem, we’ll need to free that object at the point when the last Handle that points to it goes away. We’ll use a reference count to keep track of how many objects refer to another object. Each time we create a new handle that refers to our target object, we increment the reference count object, while each time a referring object goes away we decrement the reference count. Finally, when the last referring object goes away, we know that it is safe to destroy the target object.

    -
    template <class T> class Ref_handle{
    public:
    // manage reference count as well as pointer
    Ref_handle(): refer(new size_t(1)), p(0) {}
    Ref_handle(T* t): refptr(new size_t(1)), p(t) {}
    Ref_handle(const Ref_handle& h): refptr(h.refptr), p(h.p){
    ++*refptr;
    }

    Ref_handle& operator=(const Ref_handle&);
    ~Ref_handle();

    // as before
    operator bool() const { return p; }
    T& operator*() const {
    if(p)
    return *p;
    throw std::runtime_error("unbound Ref_handle");
    }

    T* operator->() const{
    if(p)
    return p;
    throw std::runtime_error("unbound Ref_handle");
    }

    private:
    T* p;
    size_t* refptr; // newly added
    };
    -

    Above code shows our Ref_handle class:

    -
      -
    1. we add a new pointer, refptr, to to the new handle class to track the reference count

      -
    2. -
    3. if we default construct a Ref_handle object or construct from an existed T, we initialized *refptr** to 1

      -
    4. -
    5. if we construct a Ref_handle object from another Ref_handle, we do not copy the underlying object but instead only copy the value of the pointer from the passed argument. Then, our Ref_handle object points to the same object as the passed argument. In addition, we let the refptr points to the counter object pointed by h.refptr, then increment the counter value by 1 as there is a new pointer Ref_handle.p points to the object pointed by h.p now.

      -
    6. -
    7. the assignment operator also modifies the counter object instead of copying the underlying object:

      -
      template <class T>
      Ref_handle<T>& Ref_handle<T>::operator=(const Ref_handle& rhs){
      ++*rhs.refptr;
      // free the left-hand side, destroy pointers if appropriate
      if(--*refptr == 0){
      delete refptr;
      delete p;
      }

      // copy in values from the right-hand side
      refptr = rhs.refptr;
      p = rhs.p;
      return *this;
      }
      -

      the assignment operation typically involves obliterating the value of the left-hand side operand. If the operand is a pointer, we execute delete p to free the space occupied by the object that pointed by p, if there is no other pointers points to the same object. Therefore, we executes delete p as well as delete refptr conditional on –*refptr == 0. If –*refptr == 0 is false, we do not execute delete operation, but we still need to decrement the counter object pointed by refptr, which has been done by executing –*refptr ==0. However, we also need to avoid self-assignment and hence we increment *refptr first.

      -

      The next step is to bind our Ref_handle to the object that pointed by the passed argument. Like what the copy constructor does, we copy pointers but don’t copy the underlying object.

      -
    8. -
    9. the destructor checks whether the Ref_handle object being destroyed is the last one bound to its T object:

      -
      template <class T> Ref_handle<T>::~Ref_handle(){
      if (--*refptr == 0){
      delete refptr;
      delete p;
      }
      }
      - -
    10. -
    -

    This version of *Ref_handle class works well for classes that can share state between copies of different objects, however, cannot provide valuelike behavior like the Handle class described in last section. It does avoid needless copying, however, avoid all copying even we want to copy the underlying data. Next, we discuss how to write a Handle that let us decide when to share data.

    -

    Handles that let you decide when to share data

    Now we write our last version of generic handle class, which not only preserves the performance of Ref_handle but also provides the valuelike behavior of Handles. In general, the new handle class, named as Ptr, will copy the object if we are about to change the contents, but only if there is another handle attached to the same object.

    -
    template <class T> class Ptr{
    public:
    // new member to copy the object conditionally when needed
    void make_unique(){
    if(*refptr != 1){
    --*refptr;
    refptr = new size_t(1);
    p = p ? p->clone() : 0;
    }
    }

    // the rest of the class looks like Ref_handle except for its name
    Ptr(): refptr(new size_t(1)), p(0) {}
    Ptr(T* t): refptr(new size_t(1)), p(t) {}
    Ptr(const Ptr& h): refptr(h.refptr), p(h.p) {
    ++*refptr;
    }

    Ptr& operator=(const Ptr&);
    ~Ptr();
    operator bool() const { return p; }
    T& operator*() const;
    T* operator->() const;

    private:
    T* p;
    size_t* refptr;
    };
    -

    This new Ptr class has the mostly same members and implementations as the Ref_handle class, except that it defines a new make_unique function. The make_unique function calls the clone() function to copy the underlying object only in the condition that the reference count is not 1. More specific, if *refptr == 1, then it means that there is no any other Ptr objects are bound to the underlying object, and hence there is no need to do a underlying copy again;but if *refptr != 1, it means that there still other Ptr(s) are bound to the underlying object, and hence it is necessary to make our Ptr points to its own object to avoid bring changes to the object pointed by other Ptr(s). If we intend to add a function that can change the contents of the underlying object, we should call make_unique to make a copy of the underlying object.

    -

    An improvement on controllable handles

    There is one problem when we use above Ptr to deal with some classes that do not have a member function clone(). In such case, we will define an intermediary global function that we can both call and create:

    -
    template <class T> 
    T* clone(const T* tp){
    return tp->clone();
    }
    -

    Then we can change our make_unique member to call it

    -
    template <class T>
    void Ptr<T>::make_unique(){
    if(*refptr != 1){
    --*refptr;
    refptr = new size_t(1);
    p = p ? clone(p) : 0; // call global version of clone
    }
    }
    -

    The new clone function does’t make any change to the make_unique function and hence works well for our Student_info class. Another example, the Vec doesn’t provide a clone function, how can we define an intermediary function to let the Ptr< Vec > work?

    -
    template <>
    Vec<char>* clone(const Vec<char>* vp){
    return new Vec<char>(*vp);
    }
    -

    The novelty here is template<>, which indicates a function is a template specialization. The template specialization means that the template is a particular version of a template function for the argument type. If we pass clone a Vec*, the compiler will use this specialized version of clone. If we pass other types of pointers, it will instantiate the general template form of clone.

    -]]>
    - - Programming - - - C++ - Notes - -
    - - C++ - Using inheritance and dynamic binding - /2018/05/08/C-Using-inheritance-and-dynamic-binding/ - Inheritance

    In this chapter, we intend to extend our grading program such that it meets the new requirements: students can take undergraduate or graduate credit while graduate students have to write a thesis in addition to the homework and exams. In other words, a record for graduate credit is the same as for undergraduate credit except that it has extra properties related to the thesis. This problem can be abstracted and solved by a mechanism called inheritance, which is one of the cornerstones of OOP.

    -

    Specifically, we’ll write two classes, the first class is the abstraction of the core requirements and is named Core while the second class represents the requirements for graduate credit and hence named Grad. The Grad class captures extra requirements but has same core requirements as the Core class. Therefore, We write two classes such that the Grad class can inherit the properties from the Core class. Typically, we say that the Grad class is derived from or inherits from the base class, i.e. the Core class here. let’s see how to define these two classes:

    -
    class Core{
    public:
    Core();
    Core(std::istream&);
    std::string name() const;
    std::istream& read(std::istream&);
    double grade() const;

    private:
    std::istream& read_common(std::istream&);
    std::string n;
    double midterm, final;
    std::vector<double> homework;
    };
    - -
    class Grad: public Core{
    public:
    Grad();
    Grad(std::istream&);
    double grade() const;
    std::istream& read(std::istream&);
    private:
    double thesis;
    };
    -

    Since Grad class inherits from Core class, every member of Core is also a member of Grad, except for the constructor, assignment operator, and destructor. The Grad class also has its own members, such as the thesis and its own constructors. It can also redefine members from the base class, such as the grade and read function.

    -

    The keyword public in public Core means that Grad inherits from Core is part of its interface rather than its implementation. In other words, the public interface to Core becomes part of the public interface to Grad. For example, if we have a Grad object, we can call its name member thought Grad doesn’t define its own name function.

    -

    Beyond the four data members from the Core class, Grad has a member thesis and calculates grade() using different algorithm. It have two constructors, and four member functions, two of which redefine the corresponding members of Core, and name and read_common functions.

    -

    Protection revisited

    As it stands, four data members as well as the read_common function are inaccessible to member functions in Grad as they are private and only available to the Core members and its friends. But we do need these data members and read_common for defining the grade and read functions in the Grad. To achieve this goal, we rewrite the Core class using a protection lable:

    -
    class Core{
    // public members are available for users of the derived class
    public:
    Core();
    Core(std::istream&);
    std::string name() const;
    std::istream& read(std::istream&);
    double grade() const;

    // protected members are available for member functions of the derived class but not available for users
    protected:
    std::istream& read_common(std::istream&);
    double midterm, final;
    std::vector<double> homework;

    // only available for members of the class itself and its friends.
    private:
    std::string n;
    };
    -

    The protected members are available for derived classes but still inaccssible users of the classes. n is still private but Grad can access the name by calling its member function name.

    -

    Operations

    The next is to implement four constructors(each class has one default constructor and one constructor with arguments) and six operations including common functions name, read_common, and read and grade for two class respectively. We’ll read the thesis grade closely after the final exam grade but precede the homework grades.

    -
    // name function
    string Core::name() const { return n; }

    // grade function for Core
    double Core::grade() const {
    return ::grade(midterm, final, homework);
    }

    // read_common function
    istream& Core::read_common(istream& in){
    in >> n >> midterm >> final;
    return in;
    }

    // read function for Core
    istream& Core::read(istream& in){
    read_common(in);
    read_hw(in, homework);
    return in;
    }
    -
    // read for Grad
    istream& Grad::read(istream& in){
    Core::read_common(in);
    in >> thesis;
    read_hw(in, Core::homework);
    return in;
    }

    // grade for Grad
    double Grad::grade() const{
    return min(Core::grade(), thesis);
    }
    -

    The Grad::grade function shows that we calculate the final grade as the lesser between the grade excluding thesis, and thesis. Though we can call members of Core directly, we’d better explicitly call some functions for avoiding ambiguity. For example, if we don’t explicitly call Core::grade(), the compiler may use the Grad::grade dirctly.

    -

    Inheritance and constructors

    Derived objects are constructed by(Koenig and Moo 2000):
    1. Allocating space for the entire object (base-class members as well as derived members)
    2. Calling the base-class constructor to initialize the base-class part(s) of the object
    3. Initializing the members of the derived class as directed by the constructor initializer
    4. Executing the body of the derived-class constructor, if any

    -

    Clearly, the constructor of a derived class not only constructs its own members but also constructs data members of the base class.

    -
    class Core{
    public:
    // default constructor for Core
    Core(): midterm(0), final(0) {}

    // build a Core from an istream
    Core(istream& is){ read(is); }

    // ...
    };

    class Grad: public Core{
    public:
    // default constructor for Grad: first implicitly calls the default constructor Core::Core()
    Grad(): thesis(0) {}

    // build a Grad from istream: first implicitly calls Core::Core()
    Grad(std::istream& is) { read(is); }

    //...
    };
    -

    For example, when execute

    -
    Grad g; // create an empty object
    -

    The computer allocates enough space to hold five data members for the Grad object, run the Core default constructor to initialize the data members in the Core part of g, and then run the default constructor of Grad. Again, when execute

    -
    Grad g(cin);
    -

    the computer will run the Core default constructor, followed by the Grad::Grad(istream&) constructor to read values into five data memners.

    -

    Polymorphism and virtual functions

    There is also a support function for the Student_info program, that is, the compare function that acts as the predicate of the std::sort algorithm.

    -
    bool compare(const Core& c1, const Core& c2){
    return c1.name() < c2.name();
    }
    -

    How does it work on the Grad class objects? For example

    -
    Grad g(cin);    // read a Grad record
    Grad g2(cin); // read a Grad record

    Core c(cin); // read a Core record
    Core c2(cin); // read a Core record

    compare(g, g2); // compare two Grad records
    compare(c, c2); // compare two Core records
    compare(g, c); // compare Grad record with a Core record
    -

    The compare function can take two Core objects as well as two Grad objects, even one Core and one Grad. For the function body, it makes sence as any Grad object has a member name, which it inherits from the base. But why we can pass a Grad object to a function expecting a Core&? The reason is that Grad is inherited from Core and hence has a Core part. Then, we can bind compare‘s reference parameters to the Core portions of Grad objects, as if we bind them to plain Core objects.

    -

    Obtaining a value without knowing the object’s type

    The compare function described above works properly. However, if we intend to compare the grade rather than the name, the function seems inappropriate for Grad objects as two classes have different grade function. A right logical manner is that the compare function can invoke the right grade function according to the type of the object that we pass, only at the stage of run time. To support this kind of run time selection, C++ provides virtual functions:

    -
    class Core{
    public:
    virtual double grade() const; // virtual added
    };
    -

    When we call compare(grade-version), the implementation will determine which version of the grade should execute by looking at the actual type of the objects to which the reference c1 and c2 are bound. If the argument is Grad,then it calls Grad::grade and calls Core::grade otherwise.

    -

    Noting that the keyword virtual may be used only inside the class definition. If we seperate the declaration and definition, we do not need to repeatedly use it in the definition.

    -

    Dynamic binding

    Another point about the virtual is that it is relevant only when the function is called through a reference or a pointer. If we call the function on behalf of the object, then we know the exact type of the object at compile time. In contrast, a reference or a pointer to a base class object may refer or point to a base-class object or to an object of a type derived from the base class. Assuming we write compare_grades:

    -
    // incorrect implementation
    bool compare_grades(Core c1, Core c2){
    return c1.grade() < c2.grade();
    }
    -

    In this case, we know exactly that both two objects are Core type. Even we call the function with Grad objects, the Grad objects will be cut down to its Core part and a copy of that part will be passed to the compare_grades function. This case is known as statically bound, that is, the calls to Grad are bound at compile to Core::grade. Obviously, the dynamic binding is that the function is dynamically bound at run time. If we call a virtual function through a pointer or a reference, the version of virtual function to use depends on the type of the object which the reference or pointer is bound.

    -
    Core c;
    Grad g;
    Core* p;
    Core& r = q;

    c.grade(); // statically bound to Core::grade()
    g.grade(); // statically bound to Grad::grade()
    p->grade(); // dynamically bound, depending on the type of the object to which p points
    r.grade(); // dynamically bound, depending on the type of the object to which r refers
    - -

    The fact that we can use a derived type where a pointer or reference to the base is expected is an example of polymorphism, meaning of many form. When we call the virtual function by a pointer or reference, we make a polymorphic call. We’ll make the read function virtual as well and then the version of the read function to be called depends on the type of the object on which it is invoked.

    -

    Recap

    class Core{
    public:
    Core(): midterm(0), final(0) {}
    Core(std::istream& is) { read(is); }

    std::string name() const;

    virtual std::istream& read(std::istream&);
    virtual double grade() const;

    protected:
    // accessible to derived classes
    std::istream& read_common(std::istream&);
    double midterm, final;
    std::vector<double> homework;

    private:
    // accessible only to Core
    std::string n;
    };

    class Grad:: public Core{
    public:
    Grad(): thesis(0) {}
    Grad(std::istream& is) { read(is); }

    double grade() const;
    std::istream& read(std::istream);

    private:
    double thesis;
    };
    bool compare(const Core&, const Core&);
    - -

    Using inheritance to solve our problem

    Now we can write our student grading prorgam described in chapter 9. The problem is how can we write a program that can handle with both Core objects and Grad objects. To achieve our goal, we need to eliminate these type dependencies(Koenig and Moo 2000):

    -

    1. The definition of the vector in which we store the elements as we read them
    2. The definition of the local temporary into which we read the records
    3. The read function
    4. The grade function

    -

    Now we’ll see how to solve these problems.

    -

    Containers of virtually unkown type

    Consider if we define a vector as follows:

    -
    vector<Core> students;      // must hold Core objects, not polymorphic types
    Core record; // Core object, not a type derived from Core
    -

    It is impossible to hold the Grad objects as we explicitly declare that a vector hold objects of type Core. Then, if we call the read function or grade function we indeed call Core::read or Core::grade. However, we have mentioned in above section, if we call those functions through pointers or references, these functions are dynamically bound at run time, depending on the type of the object which the reference or pointer is bound. Therefore, a natural solution is that define a vector that stores the pointer to each element rather than the element itself.

    -
    vector<Core*> students;
    Core* record;
    -

    Howvever, this doesn’t work as no one knows where the record points initially. If the computer executes data reading, the program would crash.

    -
    while(record->read(cin)) { // crash! }
    -

    Now we provide a verbose solution to this problem: let users manually control the type of the object. We use lable U to represent that the type is a Core object, and use G to represent that the type is a Grad object. Before we implement this strategy, we should rewrite our compare function such that it can sort two pointers.

    -
    bool compare_Core_ptrs(const Core* cp1, const Core* cp2){
    return compare(*cp1, *cp2);
    }
    -

    Noting that we can’t name this predicate as compare as we cannot pass an overloaded function as a template argument. Now, let’s see the whole program:

    -
    // this work almost work
    int main(){
    vector<Core*> students; // store pointers, not objects
    Core* record;
    char ch;
    string::size_type maxlen = 0;

    // read and store the data
    while(cin >> ch){
    if(ch == 'U')
    record = new Core; // allocate a Core object
    else
    record = new Grad; // allocate a Grad object
    record->read(cin); // virtual call
    maxlen = max(maxlen, record->name().size()); // dereference
    students.push_back(record);
    }

    // pass the version of compare that works on pointers
    sort(student.begin(), student.end(), compare_Core_ptrs);

    // write the names and grades
    for (vector<Core*>::size_type i = 0; i != student.size(); ++i){
    cout << students[i]->name()
    << string(maxlen + 1 - students[i]->name.size(), ' ');
    try{
    double final_grade = students[i]->grade();
    streamsize prec = cout.precision();
    cout << setprecision(3) << final_grade
    << setprecision(prec) << endl;
    }catch(domain_error e){
    cout << e.what() << endl;
    }
    delete student[i]; // free the object allocating when reading
    }
    return 0;
    }
    -

    Virtual destructors

    Above program almost works. The only problem occurs when we delete the object. When we store each pointer, we store each as Core* though they may point to a Grad. Therefore, the delete operation can only delete pointers to Core. To solve this problem, we define a virtual destructor:

    -
    class Core{
    public:
    virtual ~Core() {}
    // as before
    };
    -

    Now, when we execute delete students[i], the destructor that will be run depends on the type of the object to which student[i] actually points. A virtual destructor is needed any time it is possible that an object of derived type is destroyed through a pointer to base.

    -

    A simple handle class

    The above approach does solve the problem but seems complex. Users have to manage the pointers and memory properly to avoid potential bugs. we can use the technique handle class to encapsulate the pointer to Core:

    -
    class Student_info{
    public:
    // constructors and copy control
    Student_info(): cp(0) {}
    Student_info(std::istream& is): cp(0) { read(is); }
    Student_info(const Student_info&);
    Student_info& operator=(const Student_info&);
    ~Student_info() { delete cp; }

    // operations
    std::istream& read(std::istream&);
    std::string name() const {
    if(cp) return cp->name();
    else throw std::runtime_error("unitialized Student");
    }
    double grade() const{
    if(cp) return cp->grade();
    else throw std::runtime_error("unitialized Student");
    }
    static bool compare (const Student_info& s1, const Student_info& s2){
    return s1.name() < s2.name(); }
    private
    Core* cp;
    };
    -

    Now the Student_info object represents either a Core or Grad. This handle class hids the details of implementations related to pointers as used in above program, and provides an interface that is consistent with the Core and Grad. Users do not need to worry about memory management any more as all has been done by the handle class. The novelty is that we define the compare function as a static member which is associated with a class rather than a particular object.
    We can call it through Student_info::compare() directly even without creating any object first. Therefore, static function member cannot access nonstatic data members of objects of the class as there is no object associated with the function and hence no members to use.

    -

    Reading the handle

    The first constructor construct a nullptr. The second constructor constructs an object from the input stream, relying on the read function:

    -
    istream& Student_info::read(istream& is){
    delete cp; // delete previous object, if any

    char ch; // get record type
    is >> ch;

    if(ch == 'U'){
    cp = new Core(is); // construct Core from istream
    }
    else{
    cp = new Grad(is); // construct Grad from istream
    }
    return is;
    }
    -

    The read function allocate the space and construct the right type object according to the information from input stream. It starts by freeing the existing object (if any) to which the handle object was previously bound. It is worth noting that if cp is a nullptr, we still can use delete without causing any error.

    -

    Copying the handle objects

    It also defines the copy constuctor and assignment operator. These two operations typically need to allocate new objects and then initialize or assign values from the object from which we are copying. But the problem is how can we know the type of the object from which we are copying? The object, i.e. cp, may point to a Core or a Grad. The solution is to define a new virtual function:

    -
    class Core{
    friend class Student_info;

    protected:
    virtual Core* clone() const { return new Core(*this); }
    // as before
    };
    -

    The clone() function creates a new object that holds copies of the values in the original. The Core doesn’t have a user-defined copy constructor but have a synthesized copy constructor which copies each member from the existing Core object into the newly created object. The member is inaccessible to users and non-derived classes. Therefore, we declare the Student_info as a friend. Then all members of the Student_info are friends of Core. The Grad class inherits this member, but will return a new Grad:

    -
    class Grad: public Core{
    protected:
    Grad* clone() const { return new Grad(*this); }
    // as before
    };
    -

    In general, when we redefine a member function from the base class, we keep the parameter list and the return type unchanged. However, if the base-class function returns a pointer (or reference) to a base class, then the derived-class function can return a pointer or reference to a corresponding derived class.

    -

    In addition to above, the derived class doesn’t inherit the friend class from the base class. In this case, it is unnecessary to declare the Student_info as the friend class of Grad due to the fact that the Student_info class never refers to Grad::clone directly instead through the virtual function defined in Core.

    -

    The copy constructor and assignment operator are defined as follows:

    -
    Student_info::Student_info(const Student_info& s): cp(0) {
    if (s.cp) cp = s.cp->clone();
    }

    Student_info& Student_info:: operator=(const Student_info& s){
    if(&s != this){
    delete cp;
    if(s.cp)
    cp = s.cp->clone();
    else
    cp = 0;
    }
    return *this;
    }
    -

    One may wonder that why we can access the private member cp of object s. It is because that private only restricts data access from other classes. In other words, if both objects are instances of the same class, they are allowed to access private members with each other.

    -

    Using the handle class

    int main(){
    vector<Student_info> students;
    Student_info record;
    string::size_type maxlen = 0;

    // read and store the data
    while (record.read(cin)){
    maxlen = max(maxlen, record.name().size());
    students.push_back(record);
    }

    // alphabetize the student records
    sort(students.begin(), students.end(), Student_info::compare);

    // write the names and grades
    for (vector<Student_info>::size_type i = 0;
    i != students.size(); ++i){
    cout << students[i].name()
    << string(maxlen + 1 - students[i].name.size(), ' ');
    try{
    double final_grade = students[i].grade();
    streamsize prec = cout.precision();
    cout << setprecision(3) << final_grade
    << setprecision(prec) << endl;
    }catch (domain_error e){
    cout << e.what() << endl;
    }
    }
    return 0;
    }
    -

    Now the program takes either a undergraduate record or a graduate record. It first reads the character that says what kind of record we are about to read, then creates a new object and initializes it from input stream. Then it stores objects and deal with sorting, printing as same as the previous program. When it exits from the main, all created objects are deleted automatically through the destructor defined in the Student_info class.

    -

    Subtleties

    We are allowed to store Core or Grad objects into a vector due to the fact that push_back function takes a reference to the vector‘s value type. But the result is that the vector only stores the Core part of a Grad object.

    -

    If we want to declare a virtual function, we must give it the same interface in the base and the derived classes.

    -]]>
    - - Programming - - - C++ - Notes - -
    - - C++ - Defining abstract data types(Part 2) - /2018/05/06/Defining-abstract-data-types-Part-2/ - The full version of Vec class template described in C++ - Defining abstract data types is presented below.

    -

    Vec.h

    -
    #ifndef GUARD_VEC_H
    #define GUARD_VEC_H

    #include <iostream> // std::cout, std::endl
    #include <cstddef> // std::size_t
    #include <algorithm> // std::max
    #include <memory> // std::allocator, std::uninitialized_fill, std::uninitialized_copy

    template <class T> class Vec{
    public:
    // member types
    typedef T* iterator;
    typedef const T* const_iterator;
    typedef std::size_t size_type;
    typedef T value_type;

    // constructors
    Vec() {
    std::cout << "calling default constructor" << std::endl;
    create();
    }

    explicit Vec(size_type n, const T& t = T()) {
    std::cout << "calling the explicit constructor" << std::endl;
    create(n, t);
    }

    // copy constructor,
    Vec(const Vec& v) {
    std::cout << "calling copy constructor" << std::endl;
    create(v.begin(), v.end());
    }

    //assignment operator
    Vec& operator=(const Vec&);

    // destructor
    ~Vec() {
    std::cout << "calling destructor" << std::endl;
    uncreate();
    }

    // indexing operator
    const T& operator[](size_type i) const {
    std::cout << "calling operation[]" << endl;
    return data[i];
    }

    // push_back function
    void push_back(const T& t){
    if(avail == limit)
    grow();
    unchecked_append(t);
    }

    // size function
    size_type size() const { return avail - data; }

    // begin(), end() function
    iterator begin() { return data; }
    const_iterator begin() const { return data; }
    iterator end() { return avail; }
    const_iterator end() const { return avail; }

    private:
    iterator data; // first element in the Vec
    iterator avail; // (one past) the last element in the Vec
    iterator limit; // (one past) the allocated memory

    // facilities for memory allocation
    std::allocator<T> alloc; // object to handle memory allocation

    // allocate and initialize the underlying array
    void create();
    void create(size_type, const T&);
    void create(const_iterator, const_iterator);

    // destroy the elements in the array and free the memory
    void uncreate();

    // support functions for push_back
    void grow();
    void unchecked_append(const T&);
    };

    // initialize data members to nullptr
    template <class T> void Vec<T>::create()
    {
    data = avail = limit = nullptr;
    }

    // create and initialize data members with a size and a value
    template <class T> void Vec<T>::create(size_type n, const T& val)
    {
    data = alloc.allocate(n);
    limit = avail = data + n;
    std::uninitialized_fill(data, limit, val);
    }

    // create and initialize data members by copying values from an input sequence
    template <class T> void Vec<T>::create(const_iterator i, const_iterator j)
    {
    data = alloc.allocate(j - i);
    limit = avail = std::uninitialized_copy(i, j, data);
    }

    // destruct the class object using destroy and deallocate functions
    template <class T> void Vec<T>::uncreate()
    {
    if(!data){
    // destroy the elements in reverse order
    iterator it = avail;
    while(it != data)
    alloc.destroy(--it);
    alloc.deallocate(data, limit - data);
    }
    // reset pointers to indicate that Vec is empty again
    data = limit = avail = 0;
    }

    // assign values from right-hand operand to the left-hand operand
    template <class T>
    Vec<T>& Vec<T>::operator= (const Vec& rhs)
    {
    std::cout << "calling operator= function" << std::endl;

    // check for self-assignment
    if(&rhs != this)
    {
    // free the array in the left-hand side
    uncreate();

    // copy elements from the right-hand to the left-hand side
    create(rhs.begin(), rhs.end());
    }
    return *this;
    }

    // reallocate storage to hold more elements
    template <class T> void Vec<T>::grow()
    {
    // when growing, allocate twice as much as space as currently in use
    size_type new_size = std::max(2*(limit-data), ptrdiff_t(1));

    // allocate new space and copy existing elements to the new space
    iterator new_data = alloc.allocate(new_size);
    iterator new_avail = std::uninitialized_copy(data, avail, new_data);

    // return the old space
    uncreate();

    // reset pointers to point to the newly allocated space
    data = new_data;
    avail = new_avail;
    limit = data + new_size;
    }

    // add new element at the end of the vector
    template <class T> void Vec<T>::unchecked_append(const T& val)
    {
    alloc.construct(avail++, val);
    }
    #endif /* GUARD_VEC_H */
    - -

    Test

    -

    Now, let’s test our Vec class to see how does it work.

    -

    main.cpp

    -
    #include <iostream>
    #include "Vec.h"

    using std::cout; using std::cin;
    using std::endl;

    int main()
    {
    {
    Vec<int> v; // call default constructor
    Vec<int> v1(10, 100); // call explicit constructor
    Vec<int> v2(v1); // call copy constructor
    v = v1; // call assignment operator

    // the destructor is expected to be called three times
    }

    cout << endl;
    {
    Vec<int> v; // call default constructor
    if(v.size() == 0){ // test size
    v.push_back(10); // test push_back function
    }

    // test indexing operator
    cout << "The first element is: " << v[0] << "\n";

    // call the destructor
    }

    cout << endl;
    {
    Vec<int> v(5, 100); // call explicit constructor

    // test iterator
    for(Vec<int>::iterator it = v.begin(); it != v.end(); ++it)
    cout << *(it) << " ";

    cout << "\n";
    // call the destructor
    }
    return 0;
    }
    - -

    Outputs

    -
    calling default constructor
    calling the explicit constructor
    calling copy constructor
    calling operator= function
    calling destructor
    calling destructor
    calling destructor

    calling default constructor
    calling operation[]
    The first element is: 10
    calling destructor

    calling the explicit constructor
    100 100 100 100 100
    calling destructor
    -

    The test program generates outputs as expected. As mentioned in last post, the operator= function returns a reference to the new constructed class object can be more efficient than returning the value directly. The reason behind this is that returning a value unnecessarily calls the copy constructor and destructor. Let’s verify this by evaluating following statements within the setting of returning by value:

    -
    Vec<int> v;			// call default constructor
    Vec<int> v1(10, 100); // call explicit constructor
    v = v1; // call assignment operator
    - -

    Outputs

    -
    calling default constructor
    calling the explicit constructor
    calling operator= function
    calling copy constructor
    calling destructor
    calling destructor
    calling destructor
    -

    Comparing with the original version, the outputs are the same. However, the previous program evalutes four statements including a copy construction as well. This program only creates two objects: v and v1, but additionally calls the copy constructor and destructor once for each after calling the assignment operator. The results confirms our expectation.

    -]]>
    - - Programming - - - C++ - Notes - -
    - - Accelerated C++ Solutions to Exercises (Chapter 11) - /2018/05/06/Accelerated-C-Solutions-to-Exercises-Chapter-11/ - Exercise 11-0

    Compile, execute, and test the programs in this chapter.

    -

    Solution & Results

    Please find the programs and analysis in Defining abstract data types(Part 2).

    -

    Exercise 11-1, 11-2, 11-3, 11-4

    11-1: The Student_info structure that we defined in Chapter 9 did not define a copy constructor, assignment operator, or destructor. Why not?

    -

    11-2: That structure did define a default constructor. Why?

    -

    11-3: What does the synthesized assignment operator for Student_info objects do?

    -

    11-4: How many members does the synthesized Student_info destructor destroy?

    -

    Solution & Results

    Recalling the Student_info class:

    -
    #ifndef GUARD_STUDENT_INFO
    #define GUARD_STUDENT_INFO

    #include <string>
    #include <iostream>
    #include <vector>

    class Student_info
    {
    public:
    Student_info (); // default constructor
    Student_info (std::istream &); // constructor with argument
    std::string name() const { return n; } // inline member function return name
    bool valid() const { return !homework.empty(); } // inline member function check state
    std::istream & read(std::istream &); // member function read in data
    double grade() const; // member function calculate final grade

    private:
    std::string n;
    double midterm, final;
    std::vector<double> homework;
    };

    std::istream & read_hw(std::istream &, std::vector<double> &); // nonmember function read data into a vector
    bool compare(const Student_info &, const Student_info &); // nonmember function compare two string

    #endif
    - -

    If we don’t explicitly define a copy constructor, assignment operator and destructor, the compiler will synthesizes default versions of the unspecified operation. In this case, members midterm, final are built-in type variables and hence are copied and assigned by copying or assigning their value. But the destructors for built-in types do nothing. Members string and vector are class type variables and hence are copied, assigned, or destoryed by calling the constructor, assignment operator, and destructor for the data element. It is known that both these two standard classes define the corresponding behaviours in their headers. Therefore, it is unnecessary to define these operations in our class again. When the computer evaluates an assignment, for example

    -
    Student_info record(cin);   // construct from input stream
    Student_info record_copy; // construct an empty object
    record_copy = record; // assignment
    ```
    it calls the default assignment operators for each data member as if:
    ```c++
    n = record.n; // call assignment operator defined in the string class
    midterm = record.m; // assign values
    final = record.final; // assign values
    homework = record.homework; // call assignment operator defined in the vector class
    -

    These operations typically involves obliterating the values of the left-hand side operand and then copying values from right-hand side operand into the left-hand side operand. By analogy, we know how the synthesized copy constructor work. When a Student_info class object is destructed, the synthesized destructor detroyes its data members by calling their destructors respectively. For midterm and final, their destructors have no work to do. Therefore, the synthesized Student_info destructor destroyes two data members.

    -

    The compiler will synthesize a default constructor for us if and only if we don’t explicitly define any constructors, even a copy constructor. In this case, we explicitly define a contructor with argument and hence no synthesized version for us. In addition, we do need a user-defined default constructor as the built-in types in local scope are undefined following the synthesized operation.

    -

    Exercise 11-5

    Instrument the Student_info class to count how often objects are created, copied,assigned, and destroyed. Use this instrumented class to execute the student record programs from Chapter 6. Using the instrumented Student_info class will let you see how many copies the library algorithms are doing. Comparing the number of copies will let you estimate what proportion of the cost differences we saw are accounted for by the use of each library class. Do this instrumentation and analysis.

    -

    Solution & Results

    To be filled.

    -
    -

    Exercise 11-6, 11-7

    Add an operation to remove an element from a Vec and another to empty the entire Vec. These should behave analogously to the erase and clear operations on vectors.

    -

    Once you’ve added erase and clear to Vec, you can use that class instead of vector in most of the earlier programs in this book. Rewrite the Student_info programs from Chapter 9 and the programs that work with character pictures from Chapter 5 to use Vecs instead of vectors.

    -

    Solution & Results

    The original version can be found in C++ - Defining abstract data types(Part 2). The program below only shows the new contents including the erase functions and the clear function.

    -

    Vec.h

    -
    #ifndef GUARD_VEC_H
    #define GUARD_VEC_H

    #include <cstddef>
    #include <algorithm>
    #include <memory>

    template <class T> class Vec{
    public:
    // as before

    // erase function
    iterator erase(iterator iter);

    // overloaded erase function
    iterator erase(iterator beg, iterator end);

    // clear function
    void clear() { erase(begin(), end()); }

    private:
    // as before
    };

    template <class T>
    typename Vec<T>::iterator Vec<T>::erase(iterator iter){
    if (iter + 1 != avail)
    std::uninitialized_copy(iter + 1, avail, iter);
    --avail;
    alloc.destroy(avail);
    return iter;
    }

    template <class T>
    typename Vec<T>::iterator Vec<T>::erase(iterator first, iterator last){
    if(last != avail)
    std::uninitialized_copy(last, avail, first);
    iterator new_avail = avail - (last - first);
    iterator it = new_avail;
    while (it != avail)
    alloc.destroy(it++);
    avail = new_avail;
    return first;
    }
    -

    The first erase function takes one parameter, an iterator, and removes the element pointed by the iterator. The second erase function takes two iterators, denoting a range [first, last), and removes all elements in this range. Both erase functions return an iterator pointing to the new location of the element that followed the last element erased by the function call. Noting that the position of limit keeps unchanged and hence the capacity of this vector remains the same. I only destroy these elements but do not free the space because the destructor will free the space occupied by the range [data, limit). The clear function calls the erase function and erase all elements in the range [first(), end()). The test program below shows that all three members work as expected.

    -

    mainfunction.cpp

    -
    #include <iostream>
    #include "Vec.h"

    using std::cout; using std::cin;
    using std::endl;

    int main()
    {
    Vec<double> v;

    // stores 0-9 into the Vec
    for(int i = 0; i != 10; ++i)
    v.push_back(i);

    // traverse
    cout << "The original list is: ";
    for(auto i: v)
    cout << i << " ";
    cout <<"\n";

    // erase one by one starting from begin()
    Vec<double>::iterator i = v.begin();
    while(i != v.end())
    {
    v.erase(i);
    for(auto i: v)
    cout << i << " ";
    cout << "\n";
    }

    Vec<double> v1(10, 10);
    cout << "The size of v1 is: " << v1.size() << "\n";

    // erase first 5 elements
    v1.erase(v1.begin(), v1.begin() + 5);
    cout << "The size of v1 is: " << v1.size() << "\n";
    cout << "The rest elements are: ";
    for(auto i: v1)
    cout << i << " ";
    cout << "\n";

    // clear the Vec
    v1.clear();
    cout << "The size of v1 is: " << v1.size() << "\n";

    return 0;
    }
    - -

    Outputs

    -
    The original list is: 0 1 2 3 4 5 6 7 8 9 
    1 2 3 4 5 6 7 8 9
    2 3 4 5 6 7 8 9
    3 4 5 6 7 8 9
    4 5 6 7 8 9
    5 6 7 8 9
    6 7 8 9
    7 8 9
    8 9
    9

    The size of v1 is: 10
    The size of v1 is: 5
    The rest elements are: 10 10 10 10 10
    The size of v1 is: 0
    -

    It is easy to rewrite the Student_info programs from Chapter 9 and the programs that work with character pictures from Chapter 5. No more discussion here.

    -
    -

    Exercise 11-8

    Write a simplified version of the standard list class and its associated iterator.

    -

    Solution & Results

    To be filled.

    Exercise 11-9

    The grow function in §11.5.1/208 doubles the amount of memory each time it needsmore. Estimate the efficiency gains of this strategy. Once you’ve predicted how much of a difference it makes, change the grow function appropriately and measure the difference.

    -

    Solution & Results

    There is an article written by the authors Andrew Koenig and Barbara E. Mooon about this topic C++ Made Easier: How Vectors Grow.

    -]]>
    - - Programming - - - Accelerated C++ Solutions - -
    - - C++ Implementation: Dynamic Array-based Stack - /2018/04/24/C-Implementations-Dynamic-Array-based-Stack/ - Stack is one of the rudimentary data structures that use pointers, with a main feature that it implements the Delete operation following last in, first out (i.e. LIFO). More specific, a stack is a dynamic set that allows Insert and Delete operations, which are typically named push and pop respectively.

    -

    The program given below illustrates an ADT named my_stack, which implements the stack based on a dynamic allocated array.
    my_stack is a class template and provides an interface that allows following operations:

    -
    my_stack ms;        // create a stack with fixed capacity 1000
    my_stack s(100); // create a stack with a user-supplied capacity
    s.get_capacity(); // get the current capacity of s
    s.size(); // get the number of elements contained in s
    s.empty(); // check whether the stack is empty
    s.push(); // insert an new element into the stack at the end of it
    s.pop(); // delete the last inserted element from the stack, and return the deleted element
    s.top_element(); // return the top element only
    -

    Noting that the capacity of a stack means how many elements the stack can contain while the size means how many elements have the stack stored.

    -

    stack implementation

    -
    #ifndef MYSTACK_H_
    #define MYSTACK_H_

    #include <iostream> // std::cout, std::endl
    #include <cstddef> // std::size_t
    #include <stdexcept>// std::domain_error

    template <class T> class MyStack{
    public:
    typedef std::size_t size_type;

    // default constructor
    MyStack(): top(0), capacity(1000) {
    std::cout << "default constructor" << std::endl;
    p = new T[capacity];
    }

    // constructor with user-defined capacity
    explicit MyStack(size_type t): top(0), capacity(t) {
    std::cout << "constructor with user-defined size" << std::endl;
    p = new T[capacity];
    }

    // copy constructor
    MyStack(const MyStack& s): top(0), capacity(s.capacity){
    std::cout << "copy constructor" << std::endl;
    p = new T[capacity];
    T* temp = s.p;
    while(top != s.top){
    p[top] = *temp;
    ++top;
    ++temp;
    }
    }

    // assignment operator
    MyStack& operator=(const MyStack& s){
    std::cout << "assignment operator" << std::endl;
    if(&s != this){
    clear();
    capacity = s.capacity;
    p = new T[capacity];
    T* temp = s.p;
    while(top != s.top){
    p[top] = *temp;
    ++top;
    ++temp;
    }
    }
    return *this;
    }

    // destructor
    ~MyStack() {
    delete[] p;
    p = nullptr;
    }

    void clear(){
    top = 0;
    }

    // capacity: O(1)
    size_type get_capacity() const { return capacity; }

    // size: O(1)
    size_type size() const { return top; }

    // empty
    bool empty() const { return top == 0; }

    // top_element: O(1)
    T top_element() const { return p[top - 1]; }

    // push element: O(1)
    void push(const T& t){
    if(top == capacity)
    throw std::domain_error("stack overflow");
    p[top] = t;
    ++top;
    }

    // pop element and return the deleted element: O(1)
    T pop() {
    if(top == 0)
    throw std::domain_error("stack underflow");
    --top;
    return p[top];
    }

    private:
    size_type top; // count the number of elements
    size_type capacity; // capacity of the stack
    T* p; // a hidden pointer to head
    };

    #endif /* MYSTACK_H_ */
    - -

    The shortcoming of above stack is that it cannot grow automatically. Except constructors, each of member functions has constant complexity. The follwing program tests each operation listed above and shows that the my_stack works as expected.

    -

    stack test

    -
    /*
    * this program tests all operations that provided by the MyStack<int> class
    * created by Liam on: 27 Apr 2018
    */

    #include <iostream> // std::cout, std::endl
    #include <stdexcept> // std::domain_error
    #include "MyStack.h" // MyStack

    using std::cout;
    using std::endl;
    using std::domain_error;

    int main(){

    { // test default constructor
    MyStack<int> s;

    // test member capacity()
    cout << "The capacity of the stack is: " << s.get_capacity() << "\n";

    // test member empty()
    if(s.empty())
    cout << "The stack is empty\n";

    // test member push(const T& t)
    for(int i = 0; i != 10; ++i)
    s.push(i);

    // test member pop
    while(s.size() != 0)
    cout << s.pop() << " ";
    }

    cout << "\n\n";

    { // test constructor with size
    MyStack<int> s(10);

    for(unsigned int i = 0; i != s.get_capacity(); ++i)
    s.push(i);

    // test the case of overflow
    try{
    s.push(10);
    }catch(std::domain_error e){
    cout << e.what() << "\n";
    }

    // test copy constructor
    MyStack<int> s_copy(s);
    cout << "The top element in MyStack is: ";
    cout << s_copy.top_element() << "\n";

    // test assignment operator
    s.pop();
    s_copy = s;

    while(s_copy.size() != 0)
    cout << s_copy.pop() << " ";

    try{
    s_copy.pop();
    }catch(domain_error e){
    cout << e.what() << "\n";
    }

    }
    return 0;
    }
    - -

    Outputs

    -
    default constructor
    The capacity of the stack is: 1000
    The stack is empty
    9 8 7 6 5 4 3 2 1 0

    constructor with user-defined size
    stack overflow
    copy constructor
    The top element in MyStack is: 9
    assignment operator
    8 7 6 5 4 3 2 1 0 stack underflow
    - -]]>
    - - Algorithms - - - C++ - Data Structures - Algorithms - -
    - - C++ - Making class objects act like values - /2018/04/22/C-Making-class-objects-act-like-values/ - As last chapter introduces, we can control what happens when objects are created, copied, assigned, and destroyed by defining special members. Now we intend to make class objects act like objects of built-in types through controlling more operations such as type conversion. A typical example is that the standard library class string provides rich set of operators and supports automatic conversions. Following the standard string, we’ll write our own Str class.

    -

    A simple string class

    Analogous to the Vec class built in last chapter, we could write our Str based on dynamiclly allocated array. But it is also known that the standard string share many operations with the standard vector while the major difference is that a string is a container that only contains char elements. Therefore, we can design Str based on Vec rather than the lower level data structure.

    -
    class Str{
    public:
    typedef Vec<char>::size_type size_type;

    // default constructor; create an empty Str
    Str() { }

    // create a Str containing n copies of c
    Str(size_type n, char c): data(n, c) { }

    // create a Str from a null-terminated array of char
    Str(const char* cp){
    std::copy(cp, cp + std::strlen(cp), std::back_inserter(data));
    }

    // create a Str from the range denoted by iterators b and e
    template <class In> Str(In b, In e){
    std::copy(b, e, std::back_inserter(data));
    }

    private:
    Vec<char> data;
    };
    - -

    It can be observed that our Str is implemented through a hidden Vec. There are four constructors defined in above class. The first functon is a default constructor that creates an empty Str through invoking the Vec default constructor. It is worth noting that we have to explicitly define a default constructor thought it does exactly . If we don’t define a default constructor,the compiler won’t synthesize one for us as there exist other constructors. The second constructor takes a size and a character and initializes the only data member data by invoking another Vec constructor that takes a size and a value. The third constructor allows us to create a string with passing an argument that is a pointer to char, that is, a null-terminated array of char. It uses the standard algorithm copy to copy the elements from the array of char, covering the range [cp, cp+std::strlen(cp)) into data. cp points to the first character of the array and cp + std::strlen(cp), where strlen(cp) returns the length of the array excluding the ‘\0’, points to one past the last character in the array. Similarly, the last constructor creates a string by taking two input iterators that denotes a sequence of characters. But it is worth nothing that it is not a function but a function template. It accepts different kinds of iterators, which implies that it can construct a string object from various containers like the array of char, standard vector, standard list etc..

    -

    We also observed that the Str doesn’t define a copy constructor, assignment operator and default destructor. The synthesized operations call the corresponding members of Vec when we copy or assign or destruct the Str object. In fact, the Str class does no memory allocation and hence doesn’t require a destructor. According to the rule of three, a class that needs no destructor doesn’t need an explicit copy constructor or assignment operator either.

    -

    Automatic conversions

    In the case of the Str class, the conversions may happen when we assign a string literal to a string type object. For example:

    -
    Str t;    // default initialize t
    t = "hello"; // assign a new value to t
    -

    The first statement creates an empty string and the second statement assigns the value of the right-hand side to the left-hand side. However, the left-hand side has type Str while the right-hand side has type const char*. In addition, we didn’t define the assignment operator. How does the compiler evaluates this expression? It turns out that the compiler will call the constructor that takes the a const char*. In other words, the statement invokes the same constructor as the following statement:

    -
    Str t("hello");
    -

    This example indicates that constructors also acts a user-defined conversion which determines how to transform to and from objects of class type. In generally, we define conversions by defining a constructor with a single argument. The above statement t = “hello”; involves two steps: firstly, calling the Str(const char*) to construct an unnamed local temporary of type Str from the string literal; then calls the synthesized assignment operator to assign this temporary to t.

    -

    Str operations

    Now we further extent the operations of our Str class such that a Str type string s supports following operations:

    -
    cin >> s;    // use the input operator to read a string
    cout << s; // use the output operator to write a string
    s[i]; // use the index operator to access a character
    s1 + s2; // use the addition operator to concatenate two strings
    - -

    indexing operator

    We have learned how to define a operator, such as operator=, in defining the Vec class. We can define these operators in a similar manner. All above operators are binary operators and hence each operator function takes two parameters, one of which may be implicit if the function is a member. We are familar with the indexing operator. Let’s define it first:

    -
    class Str{
    public:
    // constructors as before
    char& operator[](size_type i) { return data[i]; }
    const char& operator[](size_type i) const { return data[i]; }

    private:
    Vec<char> data;
    };
    -

    We define two operators to take the case that access elements of a const string into consideration. Details of the implementation go to the indexing operator defined in the Vec.

    -

    input and output operator

    Now let’s think about how to implement the input operator >> and the output operator <<. The first problem is should these operators be members of a class? Due to the operator (e.g. >>) changes the state of a string, we might think it should be a member of the Str. But it is also known that the left operand is bound to the first parameter while the right operand is bound to the second parameter. Thus,

    -
    cin >> s;
    -

    is equivalent to

    -
    cin.operator >> (s);
    -

    which calls the overloaded >> operator defined for the object cin. This implies that the operator should be a member of the istream class. However, we cannot define such operation as we don’t have the definition of the istream class. If we define the operator in Str, it should invoke the input operation through

    -
    s.operation>> (cin);
    -

    or equivalently,

    -
    s >> cin;
    -

    which obviously would flout the conventions used throughout the library.

    -

    Know then that both the input and output operators should be non-member functions. Let’s declare two non-member functions:

    -
    std::istream& operator>>(std::istream&, Str&);
    std::ostream& operator<<(std::ostream&, const Str&);
    -

    To write the output operator, we need to access each character stored in the Str. Therefore, the implementation could be

    -
    ostream& operator<<(ostream& os, const Str& s)
    {
    for(Str::size_type i = 0; i != s.size(); ++i)
    os << s[i];
    return os;
    }
    -

    To use this function, we have to define the size member first

    -
    class Str{
    public:
    size_type size() const { return data.size(); }
    // as before
    };
    -

    Friends

    Unlike the output operator, the input operator is a little bit complex. The logic is that each time read one character from the input stream and then add the character to our Str. The experience of using the standard string tells us that when reading data from the input stream, it discards the leading whitespace. Beyond this, we should also take into consider the case that there exist old values in the Str. Let’s see how following code deal with these problems.

    -
    // this code won't compile quite yet
    istream& operator>>(istream& is, Str& s)
    {
    // obliterate existing value(s)
    s.data.clear();

    // read and discard leading whitespace
    char c;
    while(is.get(c) && isspace(c))
    ; // nothing to do except testing the condition

    // if still something to read, do so until next whitespace character
    if(is) {
    do s.data.push_back(c);
    while(is.get(c) && !isspace(c));

    // if we read whitespace, then put it back on the stream
    if(is)
    is.unget();
    }
    return is;
    }
    -
      -
    1. the first step is to obliterate the old values.
    2. -
    3. the second step is to discard the leading whitespace. Two conditions control the while loop, one is that whether characters are available and another one is that whether the character read from the input stream is a space. The member function get extracts one character each time, if there exists characters, it returns the character and will be evaluated to true. If there no available character, it returns eof and will be evaluated to false. In summary, if the while loop ceases, there would be two situations, no character is available or the character is not a whitespace anymore.
    4. -
    5. then we perform reading process if there still available character in the input stream after step 2. The reading process calls the member function push_back to append one character one time. It stops if it reads nothing from the stream or encounters a whitespace. In the case that it encounters a whitespace, there might be other characters following the extracted whitespace. Therefore, we should put the extracted whitespace back on the stream. This is done by calling another member function of the istream class, that is, unget which decreases the current location by one character such that the extracted character can be extracted again next.
    6. -
    -

    The logic is perfect and we do solve the problems mentioned earlier. However, above code fails to compile due to that operator>> is not allowed to access the private data member data defined in the Str. We could add public member functions clear and push_back to our Str class like we did for our Vec class. But in this case, well solve this problem with an alternative method, using keyword friend.

    -
    class Str{
    friend std::istream& operator>>(std::istream&, Str&);
    };
    -

    A friend gives the function operator>> access and write rights to the private data members defined in the Str class. In other words, if making one function a friend of a class, we are saying that the function will be treated as a member (either public or private) by the class. Above code shows that we add the declaration of operator>> into the Str class and specify it is a friend of the class.

    -

    Other binary operators

    We also consider that define the addition operator as a non-member function. The reason is that the addition operation doesn’t change values of the left-hand operand as well as the right-hand operand. The result of the addition operation between two strings is a string that concatenates two strings. Thus, the return type shoule be Str. Therefore, the operator= may be declared as follows:

    -
    Str operator+(const Str&, const Str&);
    -

    After we complete writing the implementation, our program would supports the concatenation operation between two Strs through

    -
    Str s1 = "xxx";
    Str s2 = "yyy";
    s1 = s1 + s2;
    -

    Our experience tells us that we can concatenate two standard strings in an alternative form:

    -
    s1 += s2;
    -

    Both two statements involve two processes: the right-hand side creates a new temporary object that is the concatenation of two strings, then the value of the constructed object is assigned to the left-hand side. The difference is that operator+= changes the value of left-hand operand. Therefore, we will define the operator+= as a public member of the Str class. Let’s see how to define operator+= first

    -
    class Str{
    public:
    Str& operator+=(const Str& s){
    std::copy(s.data.begin(), s.data.end(), std::back_inserter(data));
    return *this;
    }

    // as before

    private:
    Vec<char> data;
    };
    // as before
    Str operator+(const Str&, const Str&);
    -

    There is nothing new in above implementation of the operator+=. Now let’s define the operator+:

    -
    Str operator+(const Str& s, const Str& t){
    Str r = s;
    r += t;
    return r;
    }
    - -

    In the definition, we use the operator+= and the synthesized copy constructor to achieve the concatenation of two strings.

    -

    Mixed-type expressions

    The standard library string class also allows us to concatenate a string literal and a string regardless there order. As a result, we get a new string type object. For our Str class, we have defined the concatenation operator that takes operands of type const Str&. So, What would happen if following statement is evaluated:

    -
    const Str greeting = "Hello, " + name + "!"; // where **name** is a **Str** type object.
    -

    an alternative and equivalent statement

    -
    const Str greeting = ("Hello, " + name) + "!";
    -

    We can observe that there are two forms of +. The first + takes a string literal as its first operand and a Str as its seconnd operand, while in the other, the left operand is a Str and the right operand is a string literal. We may think that we should define two additional operator+ to handle these two case as the operator+ defined above only takes two arguments that are both const Str&. In fact, our Str class handles these expressions already, by means of calling the constructor that takes a const char*. This is because that the constructor is also a conversion operator that can convert a const char* to a Str. Let’s see how exactly the Str deal with this statement:

    -
    1. Str temp1("Hello, ");        // call Str::Str(const char*)
    2. Str temp2 = temp1 + name; // call operator+(const Str&, const Str&)
    3. Str temp3("!"); // call Str::Str(const char*)
    4. Str greeting = temp2 + temp3;// call operator+(const Str&, const Str&)
    -

    The implied conversion operations may be expensive due to multiple temporaries. But certainly, we still can explicitly define two additional versions o the operator+ to deal with this case.

    -

    Designing binary operators

    There are some rules in defining binary operators(Koening and Moo 2000):

    -

    1. If a class supports type conversions, then it is usually good practice to define binary operators as nonmember functions. By doing so, we preserve symmetry between the operands.

    -

    2. If an operator is a member of a class, then that operator’s left operand cannot be the result of an automatic conversion.

    -

    3. The left operand of a nonmember operator, and the right operand of any operator, follow the same rules as any ordinary function argument: the operand can be any type that can be converted to the parameter type.

    -

    4. like the assignment operator itself, all the compound-assignment operators (e.g. +=) should be members of the class.

    -

    Some conversions are hazardous

    Recalling the Vec class designed in last chapter, it contains a constructor that takes a size (and a value if supplied) (as shown below).

    -
    explicit Vec(size_type n, const T& t = T()) { create(n, t); }
    -

    The explicit specifies that the constructor can only construct an object explicitly. If we don’t declare the Vec constructor as explicit, then we could implicitly create a Vec of a given size. To illustrate how useful the explicit is, let’s see an example:
    . It is crucial that consider about the type conversion when defining a single parameter constructor for a class.

    -

    Conversion operators

    We have known that we can implicitly define conversion operations through defining constructors. Those cases typically involve that a class defines how to convert an object from a different type to the type of the class itself. In fact, class authors can also explicitly define conversion operators, which determines how to convert an object from its type to a target type.

    -

    A conversion operator must be defined as a member of a class, begining with the keyword operator followed by the target type name. For example,

    -
    class Student_info(){
    public:
    operator double();
    // ...
    };
    -

    The conversion operator above defines that a Student_info can be converted to a double type object. The definition of operator would say how exactly create a double from a Student_info. For example, we can convert the class object to its corresponding final grade, and then use this property in calculating an average grade for a class.

    -
    vector<Student_info> vs;
    // fill up vs

    double d = 0;
    for (int = 0; i != vs.size(); ++i)
    d += vs[i]; // vs[i] is automatically converted to double
    cout << "Average grade: " << d/vs.size() << endl;
    - -

    In fact, we use this kind conversion operator everytime when we write a loop that implicitly tests the value of an istream. See the example

    -
    if(cin >> x) { /*...*/ }
    -

    is equivalent to

    -
    cin >> x;
    if(cin) { /*...*/ }
    -

    It is known that the condition should be an expression that yields a value that is convertible to type bool. Using a value of any arithmetic or pointer type automatically converts the value to type bool, thus we can uses values of these type in the expression. But a iostream object neither an arithmetic type object nor a pointer type object. To makes the if condition works in above case, the standard library defines a conversion from type istream to void*, i.e. a pointer to void.

    -
    istream::operator void* {
    /*...*/
    }
    -

    The operator tests various status flags to to determine whether the istream is valid and return either 0 or an implementation-defined non-zero void* value to indicate the state of the stream.

    -

    It is necessary to explain the use of void*. A pointer to void is known as a universal pointer which can point to any type of object. We cannot deference such pointer because the type of the object to yield is unknown. But we can convert a void* to bool.

    -

    One might wonder why don’t the istream define conversion operator to bool directly. The reason is that doing so allows the compiler to detect the following erroneous usage:

    -
    int x;
    cin << x; // we should have to written cin >> x
    -

    If the conversion operator converts a istream object to bool, this expression would convert cin to a bool, and thereby converts the bool to int again. As a result, the converted value is shifted left by a number of bits equal to the value of x.

    -

    Conversions and memory management

    In this section, we think about the conversion that from a string type to a null-terminated arrays of characters. If we can successfully convert a string to an array of characters, we then can pass the string to a functions that requires and operates on null-terminated arrays.

    -
    class Str{
    public:
    // plausible, but problematic conversion operations
    operator char*();
    operator const char*() const;

    // as before
    private:
    Vec<char> data;
    };
    -

    If above code works, we then can write code such as

    -
    Str S;
    //...
    ifstream in(s); // wishful thinking: converts s and then open the stream named s
    -

    (Noting that since c++11, ifstream allows us to open a file using either a string type name or a c-stype(i.e. null-termintated array) name.)

    -

    There are several difficulties in defining such operator:

    -
      -
    1. we can’t simply return data as data is a Vec while we need an array of char.
    2. -
    3. if we design our Vec based on an array of char, we could return it as the converted result. However, doing so exposes the private data member, which violates the class Str‘s encapsulation. If users obtained a pointer to data, they could change the value of the string. In addition, if the string is destroyed, then the pointer becomes invalid and any related operations would be dengerous.
    4. -
    -

    To solve the encapsulation problem, we may provide only one conversion to const char*. To solve the dangling pointer problem, we may allocate a new space for a copy of the characters from data, and returning a pointer to this newly allocated space. By doing so, users can manage the allocated storage properly. However, this design probably doesn’t work either because the conversion happens implicitly and hence no pointer is provided explicitly.

    -

    The standard string class takes a different approach that allows us to get a copy of the string in a character array but also makes them do explicitly. It defines three member functions to get a character array from a string. The first is c_str() which copies the contents of the string into a null-terminated char array. The string owns the array and users are expected not to delete the pointer. The data in the array are ephemeral and is only valid until the next call of a member function that might change the string. The second data() is like c_str except that it returns an array that is not null-terminated (c++11 releases this condition and hence data() and c_str() are synonym and return the same value). Finally, the copy function takes a char* and an integer as arguments, and copies as many characters as indicated by the integer into space pointed by the char*, which soace the user must allocate and free. These functions work as we expected. However, this type of coversions seems explicitly rather than implicitly.

    -
    -]]>
    - - Programming - - - C++ - Notes - -
    - - Implementing the C++ STL Algorithms-Part 1: Simple Find Algorithms - /2018/04/15/Implementing-the-C-STL-Algorithms-Part-1-Simple-Find-Algorithms/ - find(beg, end, val)

    Possible implementation

    template <class InputIterator, class T>
    InputIterator find(InputIterator beg, InputIterator end, const T& val)
    {
    while(beg != end && *beg != val)
    ++beg;
    return beg;
    }
    -

    Key points

      -
    1. parameters beg and end are two Input iterators,denoting that the range searched is [beg, end). val is the value to search for in the range.
    2. -
    3. the algorithm returns an iterator to the first element in the range [beg, end) equal to val. If no such element is found, the function returns end.
    4. -
    5. pointers are random access iterators and hence are also valid input iterators. Therefore, the algorithm can also be applied to the built-in array.
    6. -
    7. complexity: linear
    8. -
    -

    Test program

    // test my find algorithm
    #include <iostream> // cout, endl
    #include <vector> // vector
    #include <cstring> // strlen
    #include "my_algorithms.h" // my_find

    using std::cout; using std::vector;
    using std::endl; using std::find;
    using std::strlen;

    int main()
    {
    // to find an int type element in a vector
    vector<int> vec{2, 4, 87, 9, 35, 77, 60};
    vector<int>::iterator it = my_find(vec.begin(), vec.end(), 60);
    if(it != vec.end())
    cout << "Element is found in vec: " << *it << endl;
    else
    cout << "Element is not found in vec" << endl;

    // to find an char type element in an array
    char arr[] = "computational";
    char* p = my_find(arr, arr+strlen(arr), 'u');
    if(p != arr+strlen(arr))
    cout << "Element is found in arr: " << *p << endl;
    else
    cout << "Element is not found in arr" << endl;
    return 0;
    }
    - -

    Outputs

    -
    Element is found in vec: 60
    Element is found in arr: u
    - -
    -

    find_if(beg, end, UnaryPred)

    Possible implementation

    template <class InputIterator, class UnaryPred>
    InputIterator my_find_if(InputIterator beg, InputIterator end, UnaryPred pred)
    {
    while(beg != end && !pred(*beg))
    ++beg;
    return beg;
    }
    - -

    Key points

      -
    1. beg and end are two Input iterators denoting that the range searched is [beg, end). UnaryPred is a predicate on elements in the range. Each time it takes one of the elements, and then returns a value convertible to bool.
    2. -
    3. the algorithm returns an iterator to the first element in the range for which the pred returns true. If there is no such element, the function returns end.
    4. -
    5. there is no way to copy, assign, or pass a function as an argument directly due to a function is not an object. In fact, when we pass a function, the compiler uses the pointer to function instead of using the function directly. In addition, we can call a pointer to a function with or withour deferencing the pointer. Therefore, in this function template, the argument can either be a function “object”, that is, UnaryPred pred; or a function pointer, that is, UnaryPred* pred; or a function reference, that is, UnaryPred& pred. All these three cases allows us to call the function through pred(*beg).
    6. -
    7. complexity: linear
    8. -
    -

    Test program

    #include <iostream>			// cout, endl
    #include <vector> // vector
    #include <cstring> // strlen
    #include <cctype> // isupper
    #include "my_algorithms.h" // my_find_if

    using std::cout; using std::vector;
    using std::endl; using std::find;
    using std::strlen; using std::isupper;

    // the predication 1
    bool IsEven(const int &i)
    {
    return i % 2 == 0;
    }

    // the predication 2
    bool Isupper(const char &c)
    {
    return isupper(c);
    }

    int main()
    {
    // find the first even number in vec
    vector<int> vec{2, 4, 87, 9, 35, 77, 60};
    vector<int>::iterator it = my_find_if(vec.begin(), vec.end(), IsEven);
    if(it != vec.end())
    cout << "The first even number in vec is: " << *it << endl;
    else
    cout << "There is no even number in vec" << endl;

    // find the first upper-case letter in arr
    char arr[] = "abceFghI";
    char* p = my_find_if(arr, arr + strlen(arr), Isupper);
    if(p != arr + strlen(arr))
    cout << "The first upper-case letter in arr is: " << *p << endl;
    else
    cout << "There is no upper-case letter in arr" << endl;

    return 0;
    }
    - -

    Outputs

    -
    The first even number in vec is: 2
    The first upper-case letter in arr is: F
    - -

    find_if_not(beg, end, UnaryPred)

    Possible implementation

    template <class InputIterator, class UnaryPred>
    InputIterator my_find_if_not(InputIterator beg, InputIterator end, UnaryPred pred)
    {
    while(beg != end && pred(*beg))
    ++beg;
    return beg;
    }
    - -

    In contrary to the find_if algorithm, this function returns an iterator to the first element in the range for which pred returns false. If pred returns true for all elements, the function returns end.

    -

    Test program

    #include <iostream>			// cout, endl
    #include <vector> // vector
    #include "my_algorithms.h" // my_find_if_not
    bool IsEven(const int &i)
    {
    return i % 2 == 0;
    }

    int main()
    {
    // find the first even number in vec
    vector<int> vec{2, 4, 87, 9, 35, 77, 60};
    vector<int>::iterator it = my_find_if_not(vec.begin(), vec.end(), IsEven);
    if(it != vec.end())
    cout << "The first odd number in vec is: " << *it << endl;
    else
    cout << "There is no odd number in vec" << endl;
    return 0;
    }
    - -

    Outputs

    -
    The first odd number in vec is: 87
    -
    -

    count(beg, end, UnaryPred)

    ]]>
    - - Algorithms - - - C++ - Algorithms - STL - -
    - - C++ - Defining abstract data types (Part 1) - /2018/04/14/C-Defining-abstract-data-types/ - The vector class

    This chapter mainly teaches us about how to define our own “vec” class follwing the standard library vector class template. Specifically, our vec class will provide an interface that allows following operations:

    -
    // construct a vector of T type
    vector<T> v; // empty vector
    vector<T> v(100); // vector with 100 elements

    // obtain the names of the types used by the vector
    vector<T>::const_iterator b, e;
    vector<T>::size_type i = 0;

    // use size and the index operator to look at each element in the vector
    for (i = 0; i != v.size(); ++i)
    cout << v[i].name(); // if T has a member name

    // return iterators positioned on the first and one past the last element
    b = v.begin();
    e = v.end();
    -

    Implementing the Vec class

    The standard library vector is a class template. Similarly, we define a class template to represent our vector to hold various types. We are familar with how to define a function template, now let’s see how to define a class template.

    -
    template <class T> class Vec{
    public:
    // interface
    private:
    // implementation
    };
    -

    Similar to the definition of a function template, a class template begins with the keyword template follwed by the template parameters list. In this case, there is one type parameter named T. Then we define the class as we did before, assuming that there will be public and private parts to write our interface and implementation respectively.

    -

    Now we consider the data members for our vector class. Vector is a container that can hold multiple elements. A natural solution goes to a dynamically allocated array. So what information we need for the implementation of our Vec class? The functions begin, end and size imply that we might need to store the address of the initial element, one past the address of the last element and the number of elements. But once we know the address of the first element and one past the last element, we could compute the size easily. Let’s add two data members:

    -
    template <class T> class Vec{
    public:
    // interface
    private:
    T* data; // first element in the Vec
    T* limit; // one past the last element in the Vec
    };
    - -

    Constructors

    From the interface we intend to provide, we know we need to define at least two constructors,

    -
    // construct a vector of T type
    Vec<T> v; // using default constructor
    Vec<T> v(100); // using constructor that takes a size
    -

    The default constructor leads to an empty Vec and hence there is no need to allocate space to hold the elements. Two data members can be initialized to null pointers. For the constructor that takes a size, we should allocate certain amount of storage for holding the elements. Two data members will be initialized to the corresponding addresses of that space. Each element will be initialized to a value given by the default constructor of Type T. There is also a case that users provide the initial values for the elements, for example

    -
    Vec<T> v(100, 1);   // using constructor that takes a size and an initial value
    -

    If so, we would initialize each element with the provided value. The constructor that takes a size and an initial value can be regarded as the special case of the constructor that only takes a size. The code below shows the definition of the constructors:

    -
    template <class T> class Vec{
    public:
    Vec() { create() };
    explicit Vec(size_type n, const T& val = T()) { create(n, val); }
    // remaining interface

    private:
    T* data
    T* limit;
    };
    - -

    As we haven’t talk about how to dynamically allocate space for our object, the details of implementations of each constructor will be discussed later. What we need to know here is that the constructors call another function create to initialize our data members and the elements. For the default constructor, create() initializes all data members to null pointers. For the second constructor, create(n, val) allocates enough space, and initializes all data members as well as each element with size n and value val. The second constructor takes two arguments, one is the size n and another is the value that to use in initializing the elements.

    -

    If there is no user-supplied value, val is assigned with an default value given by the default constructor of type T. One may speculate that if T is built-in type and the vector is allocated at local scope, then the elements are uninitialized as default-initializing an built-in object gives it an undefined value. However, we also know that when we create a standard vector with size only, the compiler initializes each element to 0. So, where there might be problems? Let’s do a simple experiment first.

    -
    #include <iostream>
    using std::cout; using std::endl;

    int main()
    {
    int x;
    int y = int();

    double m;
    double n = double();

    cout << x << '\n' << y << '\n' << m << '\n' << n << endl;
    };
    - -

    Outputs

    -
    1954310794
    0
    -2.2854e+251
    0
    - -

    From above example, we observe that a built-in type local variable is undefined in the case of default initialization. In contrast, int() and double() doesn’t default initialize the corresponding objects, but performs value-initialization. This means that T() only invokes default constructor if it is user-declared and otherwise it performs value-initialization. If T is built-in type, objects are zero initialized.

    -

    It also has been observed that we use a keyword explicit as the begining of the definition of the second constructor. This keyword only makes sence when the constructor takes a single argument, that is, the size. It indicates that the compiler will use the constructor only in the case that the user expressly invokes the constructor.

    -
    Vec<int> v(100); // ok, explicitly construct the Vec from an int
    Vec<int> v = 100; // error: implicitly comstruct the Vec and copy
    ```

    More about the **explicit** will be discussed in chapter 12.

    ## Type definitions
    This section defines types for our **Vec** class including **const_iterator, iterator, size_type** and **value_type**. It is known that our **Vec** class is build upon the dynamic allocated array. In addition, pointers supports the random-access-iterator operations. Therefore, we can define the types **iterator** and **const_iterator** based on pointers. For **size_type**, we can define based on **size_t**. Apparently, The **value_type** is **T**. Now let's see the code

    ```c++
    template <class T> class Vec{
    public:
    typedef T* iterator;
    typedef const T* const_iterator;
    typedef size_t size_type;
    typedef T value_type;

    Vec() { create(); }
    explicit Vec(size_type n, const T& val = T()) { create(n, val); }
    // remaining interface

    private:
    iterator data;
    iterator limit;
    };
    -

    Index and size

    for (i = 0; i != v.size(); ++i)
    cout << v[i].name(); // if T has a member name
    -

    The size function returns a value that represents the number of elements in a Vec.

    -

    The indexing operation is supported through the subscript operator [] and hence we should define an overloaded operator as we define other function: it has a name, takes arguments, and specifies a return type.

    -

    The name of such operator is obtained by appending the symbol [] to the word operator, that is, operator[].

    -

    If the operator is a function that is not a member function, then the function has as many arguments as the operator has operands. The first argument is bound to the left bound and the second is bound to the right operand. If the operator is defined as a member function, its left operand is implicitly bound to the object on which the operator invoked. In this case, the subscript operator is typically a member function. We can call it with v[i], meaning that v is the object on which it operates and i is an argument that should has type Vec::size_type.

    -

    As for the return type, the operator function ought to return a reference to the element in the Vec.

    -
    template <class T> class Vec{
    public:
    typedef T* iterator;
    typedef const T* const_iterator;
    typedef size_t size_type;
    typedef T value_type;

    Vec() { create(); }
    explicit Vec(size_type n, const T& val = T()) { create(n, val); }

    // operations: size and index
    size_type size() const { return limit - data; }

    T& operator[](size_type i) { return data[i]; }
    const T& operator[](size_type i) const { return data[i] }

    private:
    iterator data;
    iterator limit;
    };
    -

    There are sevral key points here:

    -
      -
    1. the result of (limit - data) has type ptrdiff_T, which is converted to size_type.
    2. -
    3. taking the size of a Vec doesn’t change the Vec and hence we define it as a const member.
    4. -
    5. we define two version of the operator function: one for const Vec objects and the other for nonconst Vec. It seems impossible to overload the operator function as both version have same parameter list. However, as mentioned above, the object itself is also an implicit argument to the function. Therefore, one function takes the const Vec object as an argument while the other one takes the nonconst Vec object as an argument.
    6. -
    -

    Operations that return iterators

    Next is to define member functions begin() and end(). Similar to the operator function, we need to define two versions for both functions, one version returns const_iterator so that users cannot modify the Vec by operating on the iterator; another one returns an iterator that is not restricted by qualifier const, so that users can write elements into the Vec through the iterator if they want to. The improved code is shown below.

    -
    template <class T> class Vec{
    public:
    typedef T* iterator;
    typedef const T* const_iterator;
    typedef size_t size_type;
    typedef T value_type;

    Vec() { create(); }
    explicit Vec(size_type n, const T& val = T()) { create(n, val); }

    // operations: size and index
    size_type size() const { return limit - data; }

    T& operator[](size_type i) { return data[i]; }
    const T& operator[](size_type i) const { return data[i] }

    // function to return iterators
    iterator begin() { return data; }
    const_iterator begin() const { return data; }

    iterator end() { return limit; }
    const_iterator end() const { return limit; }

    private:
    iterator data;
    iterator limit;
    };
    ```

    ---
    # Copy control
    In chapter 9, we have learned how to initialize a class object when it is created. But we haven't talked about what happens when a class object is copied, assigned and destroyed. When we define the **Student_info** class, we didn't define these operations as well. We can presume that the compiler will synthesize definitions for us. Now this section focus on how can we define these operations and how the synthesized operations exactly work.

    ## Copy constructor
    Two ways to implicitly copy a class object: one is that passing an object by value to a function; the other way is that returning an object by value. For example
    ```c++
    vector<int> v;
    double d;
    d = median(v); // copy v into the parameter in median

    string line;
    vector<string> words = split(line); // copy the return from split into words
    -

    Sometimes we also explicitly copy an object, for example using it to initialize another object.

    -
    vector<Student_info> vs;
    vector<Student_info> vs_copy = vs; // copy vs into vs_copy
    - -

    Both above copy behaviors are controlled by a special constructor called the copy constructor.

    -

    copy constructor is also a member function that has the same name as the name of class. It takes a single argument that has the same type as the class itself. In addition, the parameter is a const reference to the object to pass due to that the copy constructor should not change the object being copied from. Therefore, we can declare the copy constructor as shown below

    -
    template <class T> class Vec {
    public:
    Vec (const Vec& v); // copy constructor
    // as before
    };
    -

    When we copy a class object, we’ll need to allocate new space and then copy the contents from the source into the newly allocated storage. This is because we do not intend to change the object being copied from. For example, if we simple copy two data members, we may change the value of the elements due to the fact that the copied pointers still points to the elements contained in the object being copied from. As with the constructors, we will ask the overloaded create function to manage the memory and the details of the copy operations.

    -
    template <class T> class Vec {
    public:
    Vec (const Vec& v) { create(v.begin(), v.end()); };
    // as before
    };
    - -

    Assignment

    Like the subscript operator, the assignment operator = needs to be defined for providing us the assignment operations. The name of the assignment operator function is operator=. The argument taken by such operator function is as same as the argument taken by copy constructor above. What about the return type? We return a reference to the left operand.

    -
    template <class T> class Vec{
    public:
    Vec& operator= (const Vec&);
    // as before
    };
    - -

    It is worth noting the difference between assignment and the copy constructor. assignment always involves obliterating an existing value of the left-hand side, and then replacing it with a new value, i.e. the right-side hand. What they have in common is that both of them need to assign each of the data values. As mentioned above, we cannot assign the value of pointers to the left-hand side because that doing so would bring potential change for the right-hand side.

    -

    There might be another problem when using the assignment operator, that is how to handle self-assignment. For example:

    -
    vector<int> x(100, 10);
    x = x;
    -

    The assignment operator function will firstly obliterate the value of left-hand side then assign the value of right-hand side to the left-hand side. However, once we destroy the elements and free the space, we cannot create a new object that has the same value as the right-hand side due to both sides operands refer to the same space. To avoid this case, we add a if statement before implemeting the assignment. The code below gives the implementation of the assignment operator function:

    -
    template <class T>
    Vec<T>& Vec<T>::operator= (const Vec& rhs)
    {
    // check for self-assignment
    if(&rhs != this)
    {
    // free the array in the left-hand side
    uncreate();

    // copy elements from the right-hand to the left-hand side
    create(rhs.begin(), rhs.end());
    }
    return *this;
    }
    - -

    The code above introduces several new ideas:

    -

    First, the operator= is defined as a function template and the type parameter infers from the type parameter of the class template Vec.

    -

    Second, the return type as well as the function name are defined explicitly due to that this member function is defined outside the class. The declaration uses Vec& rather than Vec& is due to that the type parameter is implicit when we are within the scope of the template. This also explains why we use the function name Vec::operator=. Once we specifies that the function is a member of class Vec, we can omit the type parameter when defines its parameter const Vec& rhs.

    -

    Third, the if condition uses a keyword this to test whether the assigment happens between two same objects. this is a pointer that points to the object of which operator= is a member. It is valid only inside a member function. Hence, the condition means that if the address of the object (left-hand side) is as same as the address (denoted by &rhs) of the right-hand object, the assignment behavior won’t be executed.

    -

    Forth, if the leff-hand operand and the right-hand operand are not the same object, we destroy the elements and free the space first through uncreate() and then allocate new space and copy values from rhs like what the copy constructor does.

    -

    Finally, it is necessary to explain why we intend to return a reference to the left-side object. Why not return void directly? Why don’t we return a value? Move to see more discussion.

    -

    One reason is that to keep consistent with the default setting of the C++ compiler in regarding to the built-assignment operator. Another reason is that setting the return type to void doesn’t allow continues assignment. For example,

    -
    vector<int> x(100, 10);
    vector<int> y, z;
    y = z = x; // continues assignment
    - -

    Apparently, we don’t have to return a reference, instead we can return a value. Let’s take an example,

    -
    Vec<int> x(100, 10);
    vector<int> y;
    y = x; // calls assignment operator once, calls copy constructor once, calls destructor once
    -

    It can be presumed that returning an object involves calling three functions: first, assignment operator function is called and a temporary object is created, then, the return statement calls copy constructor to create a new object, finally, the destructor is called to destroy the temporary value and free the space. We’ll introduce the destructor later and will pose an experiment to verify these expectations.

    -

    Assignment is not initialization

    Now we can summarize the difference between initialization and assignment. It can be observed that the operator = has different effect in various contexts. The default setting of = invokes copy constructor and then creating a new object,which is another form of initialization. The operator= described above invokes assignment that always obliterates the privious value first.

    -

    Initialization happens

    -
      -
    1. In variable declaration
      string y;  // default initialization
    2. -
    3. For function parameters on entry to a function
      vector<int> v;
      median(v); // the parameter is copy-initialized
    4. -
    5. For the return value of a function on return from the function
      string line;
      vector<string> words = split(line); // the return value is copy-initialized, the variable is then copy-initialized
    6. -
    7. In constructor initializers
      string url_ch = "@#$%^&**((";   // copy initialization
      string spaces(url_ch.size(), ' '); // direct initialization
      - -
    8. -
    -

    Let’s see another example

    -
    vector<string> split(const string&); // function declaration
    vector<string> v; // default initialization

    v = split(line); // on entry, initialization of split's parameter from line; on exit, both initialization of the return value and assignment to v
    - -

    The split function returns an object of type vector. As analysed above, it involves calling both copy constructor (at the call site) and the assignment operator function.

    -

    Destructor

    It is known that when we allocate a space with new, we should destroy the values and free the space with delete. Therefore, it is necessary to define a member function to do the same job. In general, the destructor will be called automatically when:

    -
      -
    1. a local variable go out of scope.
    2. -
    3. members of an object are destryoed when the object of which they are a part is destroyed.
    4. -
    5. elements in a container are destoryed when the container is destroyed.
    6. -
    7. the delete operator applied to an object.
    8. -
    9. temporary objects are destroyed.
    10. -
    -

    Taking an example,

    -
    vector<string> split(const string& str){
    vector<string> ret;
    // split str into words and store in ret
    return ret;
    }
    -

    The variable ret is destroyed when the implementation encounters the return statement because it goes out of the scope. Now let’s see how to define a destructor:

    -
    template <class T> class Vec{
    public:
    ~Vec() { uncreate() };
    // as before
    }
    -

    The name of the destructor is as same as the name of the class itself, but prefixed by a tilde(~). There is no arguments taken by the destructor. To destroy the object and free the space, the destructor calls the uncreate() function, which is similar to the behavior of the assignment operator in obliterating the previous value.

    -

    Default operations

    What happens if we do not explicitly define a copy constructor, assignment operator, or destructor? In such case, the compiler will synthesizes default versions of the unspecified operation. Some general rules(koening and Moo 2000):

    -

    1. the default version are defined to operate recursively-copying, assigning or destroying each data element according to the appropriate rules for the type of that data element.
    2. Members that are of class type are copied, assigned, or destoryed by calling the constructor, assignment operator, and destructor for the data element.
    3. Members that are of built-in type are copied and assigned by copying or assigning their value. The destructor for built-in types has no work to do-even if the type is a pointer. Destoring a pointer through the default constructor doesn’t free the space at which the pointer points, resulting a memory leak as the occupied space is impossible to free.

    -

    Recalling the Student_info class defined in chapter 9:

    -
    class Student_info
    {
    public:
    Student_info (); // default constructor
    Student_info (std::istream &); // constructor with argument
    std::string name() const { return n; } // inline member function return name
    bool valid() const { return !homework.empty(); } // inline member function check state
    std::istream & read(std::istream &); // member function read in data
    double grade() const; // member function calculate final grade

    private:
    std::string n;
    double midterm, final;
    std::vector<double> homework;
    };
    -

    If we copy an object of Student_info, the synthesized copy constructor copies four data members. It invokes the string, vector copy constructors to copy the member name and homeworks respectively. It copies the two double values, midterm, final, directly. Similar procedures happen when we do assignment.

    -

    Noting that if a class defines any constructor explicitly, either a constructor or a copy constructor, the compiler will not synthesize a default constructor for that class. It is wise to provide a default constructor for the data type that to be used as a data member of a class that relies on the synthsized default constructor. We explicitly provide the default constructor in above class Student_info.

    -

    If a class needs a destructor, it almost surely needs a copy constructor as well as assignment operator. To control every copy of object of class T, we should define:

    -
    T::T(); // one or more constructors, perhaps with arguments
    T::~T(); // the destructor
    T::T(const T&); // the copy constructor
    T::operator= (const T&); // the assignment operator
    - -

    Dynamic Vecs

    This section focus on designing a dynamic Vec class through providing the push_back function which we are familiar with when using the standard vector. Theoretically, the push_back function can allocate new space to hold one more element and then we copy all elements into the new space while constructing a new last element from the argument to push_back. However, doing so would be inefficient when we call the push_back many times. One strategy is to allocate more storage than we need when necessary, that is, when we exhaust the preallocate storage. Specifically, each time the push_back allocate new space, it allocate twice as much as the current space.

    -

    For example, if we create a Vec with 100 elements, then call the push_back function for the first time, it will allocate a new space that can hold 200 elements. It then copies the original 100 elements into the new space with constructing the last element from the argument. There are still more space left for holding 99 elements more and hence the function do not need to allocate more space in next 99 calls. Moreover, the extral space keep uninitialized.

    -

    What we need to track is the address of the first element, the one past of the last constructed element, and the end of the new allocate storage(i.e. one past the available element). We’ll denote these address with three pointers, data, avail, limit respectively. The range [data, avail) contains all elements while the range [avail, limit) is the uninitialized storage. Now let’s write the push_back function:

    -
    template <class T> class Vec{
    public:
    void push_back(const T& val){
    if(avail == limit) // get space if needed
    grow();
    unchecked_append(val); // append the new element
    }

    private:
    iterator data; // as before, the pointer to the first element in the Vec
    iterator avail; // pointer to one past the last constructed element
    iterator limit; // now points to one past the available element

    // rest of the class interface and implementation as before
    };
    -

    grow() will double the space for us. unchecked_append(val) constructs the last element from the argument to push_back function. Correspondingly, we refresh the data members.

    -

    Flexible memory management

    We have basically completed the design of our Vec class template. However, we haven’t talked about the real implementation, that is, how exactly allocate new space. As memtioned in chapter 10, we can dynamically manage memory through built-in operators new and delete (or new[] and delete[]). However, there are several shortcomings if we use such operators to manage memory for our Vec class.

    -
      -
    1. if we use new[], it always initialize every element of a T array by using T::T(). If we want to initialize ourselves, we would have to initialize each element twice.
    2. -
    3. if push_back allocates new space, we want to keep the range [avail, limit) uninitialized. However, if we use new[], we cannot control this anymore.
    4. -
    -

    The standard header provides a class named allocator, that allocates a block of uninitialized memory that intended to contain objects of type T and returns a pointer to the initial element of that memory. In addition, allocator also defines members including functions to construct objects, destroy obejcts and deallocate the memory. Therefore, programmers can manage the allocated space directly and determine the unitialized space. Here introduces four member functions and two non-member functions of the allocator class:

    -
    template <class T> class allocator{
    public:
    T* allocate(size_t);
    void deallocator(T*, size_t);
    void construct(T*, const T&);
    void destroy(T*);

    // ...
    };
    template<class Out, class T> void uninitialized_fill(Out, Out, const T&);
    template<class In, class Out> Out uninitialized_copy(In, In, Out);
    -
      -
    1. the allocate member allocates typed but uninitialized storage to hold the requested number of elements. It returns a pointer that has type T and denotes the initial address of the storage.

      -
    2. -
    3. the deallocator frees this uninitialized storage with taking the pointer given by allocate and the size.

      -
    4. -
    5. construct and destroy construct or destroy a single object in the uninitialized space.

      -
    6. -
    7. the first algorithm uninitialized_fill fills this uninitialized space with value from the third argument. The first two arguments denote the range of the space that to be filled.

      -
    8. -
    9. the second algorithm uninitialized_copy copies values from a sequence specified by the first two arguments into a target sequence denoted by the third argument. The range pointed by the third argument should large enough to hold all elements contained in the range specified by the first two arguments. It finally returns a pointer to one past the last constructed element.

      -
    10. -
    11. both two algorithms assumes that the target range contains raw storage rather than elements that already hold values.

      -
    12. -
    -

    To obtain an allocator of the right type at the compiler time, we’ll add to our Vec class an allocator member. By doing so, we can use above member functions to provide efficient and flexible memory management for our Vec class.

    -

    The final Vec class

    template <class T> class Vec{
    public:
    // member types
    typedef T* iterator;
    typedef const T* const_iterator;
    typedef size_t size_type;
    typedef T value_type;

    // constructors
    Vec() { create(); }
    explicit Vec(size_type n, const T& t = T()) { create(n, t); }

    // copy constructor, assignment operator, destructor
    Vec(const Vec& v) { create(v.begin(), v.end()); }
    Vec& operator=(const Vec&);
    ~Vec() { uncreate(); }

    // indexing operator
    const T& operator[](size_type i) const { return data[i]; }

    // push_back function
    void push_back(const T& t){
    if(avail == limit)
    grow();
    unchecked_append(t);
    }

    // size function
    size_type size() const { return avail - data; }

    // begin(), end() function
    iterator begin() { return data; }
    const_iterator begin() const { return data; }
    iterator end() { return avail; }
    const_iterator end() const { return avail; }

    private:
    iterator data; // first element in the Vec
    iterator avail; // (one past) the last element in the Vec
    iterator limit; // (one past) the allocated memory

    // facilities for memory allocation
    allocator<T> alloc; // object to handle memory allocation

    // allocate and initialize the underlying array
    void create();
    void create(size_type, const T&);
    void create(const_iterator, const iterator);

    // destroy the elements in the array and free the memory
    void uncreate();

    // support functions for push_back
    void grow();
    void unchecked_append(const T&);
    };
    -

    We should note that there are four conditions (aka. class invariants) that guarantees a valid Vec:

    -
      -
    1. data points at our initial element, if we have any, and is zero otherwise.
    2. -
    3. data <= avail <= limit.
    4. -
    5. Elements have been constructed in the range[data, avail).
    6. -
    7. Elements have not been constructed in the range[avail, limit).
    8. -
    -

    Now the next is to write the implementation of different version of create functions while maintaining above class invariants.

    -
    template <class T> void Vec<T>::create()
    {
    data = avail = limit;
    }

    template <class T> void Vec<T>::create(size_type n, const T& val)
    {
    data = alloc.allocate(n);
    limit = avail = data + n;
    uninitialized_fill(data, limit, val);
    }

    template <class T> void Vec<T>::create(const_iterator i, const_iterator, j)
    {
    data = alloc.allocate(j - i);
    limit = avail = uninitialized_copy(i, j, data);
    }
    -

    The first version of create is used for initializing an empty Vec. The second one that takes a size and a value creates a Vec by allocating enough memory to hold n elements through alloc.allocate(n), and initializes all elements with val by applying the algorithm uninitialized_fill. The third version is used for copy-initialization, which takes two iterators that denote the sequence from which to copy. It calls uninitialized_copy algorithm to copy all values of the elements in [i, j) into [data, avail).

    -

    The destructor calls the uncreate member to destroy the elements and free the space that allocated by create.

    -
    template <class T> void Vec<T>::uncreate()
    {
    if(!data){
    // destroy the elements in reverse order
    iterator it = avail;
    while(it != data)
    alloc.destroy(--it);
    alloc.deallocate(data, limit - data);
    }
    // reset pointers to indicate that Vec is empty again
    data = limit = avail = 0;
    }
    -

    The uncreate function first checks whether the data is 0. This is because that alloc.deallocate requires a non-zero pointer. There are two steps to destruct the Vec: the first step is that calling the destroy function to destroy each object contained in the Vec; the second step is that calling deallocate function to free the previous allocated storage. As deallocate doesn’t destroy elements in an array, it is crucial to call destroy function first which calls the destructor of the target element to release resource that might be occupied by the target element. It is known that there is no destructor for built-in types, so how does the destroy function work? It is presumed that the destroy function treats built-in objects and other objects in different manner. It still needs further research.

    -

    Finally, we write functions to support our push_back member.

    -
    template <class T> void Vec<T>::grow()
    {
    // when growing, allocate twice as much as space as currently in use
    size_type new_size = max(2*(limit-data), ptrdiff_t(1));

    // allocate new space and copy existing elements to the new space
    iterator new_data = alloc.allocate(new_size);
    iterator new_avail = uninitialized_copy(data, avail, new_data);

    // return the old space
    uncreate();

    // reset pointers to point to the newly allocated space
    data = new_data;
    avail = new_avail;
    limit = data + new_size;
    }

    // assumes avail points at allocated, but uninitialized space
    template <class T> void Vec<T>::unchecked_append(const T& val)
    {
    alloc.construct(avail++, val);
    }
    - -

    Now we have really completed our Vec class. The next post presents some tests on our Vec type from different perspectives.

    -]]>
    - - Programming - - - C++ - Notes - -
    - - Accelerated C++ Solutions to Exercises(Chapter 10) - /2018/04/12/Accelerated-C-Solutions-to-Exercises-Chapter-10/ - Exercise 10-0

    Compile, execute, and test the programs in this chapter.

    -

    Solution & Results

    Please click here Managing memory and low-level data structures for codes and analysis.

    -
    -

    Exercise 10-1

    Rewrite the student-grading program from §9.6/166 to generate letter grades.

    -

    Solution & Results

    Since the letter grades function computes a letter grade based on a numerical grade, I would like to add it as a non-member function of the Student_info class to avoid repetitively computing the final grade. When need calculate the letter grades, we simply call the function as follows:

    -
    double final_grade = it->grade(); // it is a pointer to the Student_info object
    cout << letter_grade(final_grade); // print the letter grade
    -

    or

    -
    cout << letter_grade(it->grade());
    - -

    All files are presented at below including mainfunction.cpp, Student_info.h, Student_info.cpp, grade.h, grade.cpp.

    -

    mainfunction.cpp

    -
    // Accelerated C++ Solutions Exercises 10-1
    #include <algorithm> // to get the declaration of max, sort
    #include <iomanip> // to get the declaration of setprecision
    #include <iostream> // to get the declaration of streamsize
    #include <stdexcept> // to get the declatation of domain_error
    #include <string> // to get the declaration of string
    #include <vector> // to get the declaration of vector
    #include "Student_info.h" // to get the declaration of Student_info

    using std::cin; using std::setprecision;
    using std::cout; using std::sort;
    using std::endl; using std::streamsize;
    using std::domain_error; using std::string;
    using std::max; using std::vector;
    using std::fixed;

    int main()
    {
    vector<Student_info> students;
    Student_info record;
    string::size_type maxlen = 0;

    // read and store all the records, and find the length of the longest name
    while(record.read(cin))
    {
    maxlen = max(maxlen, record.name().size());
    students.push_back(record);
    }

    // alphabetize the records
    sort(students.begin(), students.end(), compare);

    // write each line of outpurs
    for (vector<Student_info>::const_iterator it = students.begin();
    it != students.end(); ++it)
    {
    // write the name, blanks
    cout << (*it).name() << string(maxlen + 1 - (*it).name().size(), ' ');

    // compute and write the final grade
    try{
    double final_grade = (*it).grade();
    streamsize prec = cout.precision();
    cout << fixed << setprecision(1) << final_grade << setprecision(prec);
    cout << '\t' << letter_grade(final_grade); // new added
    } catch(domain_error e){
    cout << e.what();
    }
    cout << endl;
    }
    return 0;
    }
    - -

    Student_info.h

    -
    #ifndef GUARD_STUDENT_INFO
    #define GUARD_STUDENT_INFO

    #include <string>
    #include <iostream>
    #include <vector>

    class Student_info
    {
    public:
    Student_info (); // default constructor
    Student_info (std::istream &); // constructor with argument
    std::string name() const { return n; } // inline member function return name
    bool valid() const { return !homework.empty(); } // inline member function check state
    std::istream & read(std::istream &); // member function read in data
    double grade() const; // member function calculate final grade


    private:
    std::string n;
    double midterm, final;
    std::vector<double> homework;
    };

    std::istream & read_hw(std::istream &, std::vector<double> &); // nonmember function read data into a vector
    bool compare(const Student_info &, const Student_info &); // nonmember function compare two string
    std::string letter_grade(double); // new added: nonmember function gives a letter grade

    #endif
    - -

    Student_info.cpp

    -
    #include "Student_info.h"
    #include "grade.h"
    #include <string>

    using std::vector;
    using std::istream;
    using std::string;

    // construct an empty Student_info object
    Student_info::Student_info (): midterm(0), final(0) { }

    // construct one by reading from input stream
    Student_info::Student_info (std::istream & in) { read(in); }

    // member function read data from input stream
    std::istream & Student_info::read(std::istream &in)
    {
    // reads and store the student's name, midterm and final exam grades
    in >> n >> midterm >> final;

    // reads and store all homework grades
    read_hw(in, homework);
    return in;
    }

    // member function grade
    double Student_info::grade() const
    {
    return ::grade(midterm, final, homework);
    }

    // nonmember function compare
    bool compare(const Student_info &x, const Student_info &y)
    {
    return x.name() < y.name();
    }

    // nonmember function read_hw
    istream & read_hw(istream &in, vector<double> &hw)
    {
    if (in){
    //get rid of previous contents
    hw.clear();

    // read homework grades
    double x;
    while(in >> x)
    hw.push_back(x);

    // clear the stream so that input will work for the next student
    in.clear();
    }
    return in;
    }

    // new added: non-member function to calculate the letter grade
    string letter_grade(double grade)
    {
    // range posts for numberic grades
    static const double numbers[] = {97, 94, 90, 87, 84, 80, 77, 74, 70, 60, 0};

    // name for the letter grades
    static const char* const letters[] = {"A+", "A", "A-", "B+",
    "B", "B-", "C+", "C", "C-", "D", "F"};

    // compute the number of grades given the size of the array
    // and the size of a single element
    static const std::size_t ngrades = sizeof(numbers)/sizeof(*numbers);

    // given a numberic grade, find the associated letter grade
    for (std::size_t i = 0; i < ngrades; ++i)
    {
    if (grade >= numbers[i])
    return letters[i];
    }
    return "?\?\?";
    }
    - -

    grade.h

    -
    #ifndef GUARD_GRADE_H
    #define GUARD_GRADE_H

    #include<vector>

    double grade(double, double, const std::vector<double> &);
    double grade(double, double, double);
    double median(std::vector<double>);
    #endif
    - -

    grade.cpp

    -
    #include <algorithm>
    #include <stdexcept>
    #include <vector>
    #include "grade.h"

    using std::domain_error; using std::istream;
    using std::vector; using std::sort;

    // grade function 1
    double grade(double midterm, double final, const vector<double> &hw)
    {
    if (hw.empty())
    throw domain_error("student has done no homework");
    return grade(midterm, final, median(hw));
    }

    // grade function 2
    double grade(double midterm, double final, double homework)
    {
    return 0.2*midterm + 0.4*final + 0.4*homework;
    }

    // compute the median of vector<double>
    double median(vector<double> vec)
    {
    // check whether the vec is empty
    if (vec.begin() == vec.end())
    throw domain_error("median of an empty vector");

    // sort the grades
    sort(vec.begin(), vec.end());

    // compute the median homework grade
    vector<double>::difference_type size = vec.end() - vec.begin();
    vector<double>::const_iterator mid = vec.begin() + size/2;
    return size % 2 == 0 ? (*mid + *(mid - 1))/2 : *mid;
    }
    - -

    Test

    -
    Inputs:

    Robin 90 87 79 88 81 73 45
    Brendan 70 69 88 100 91 75 66
    Arsenii 99 87 89 88 74 90 70
    Liam 83 66 100 76 87 91 78

    Outputs:

    Arsenii 89.8 B+
    Brendan 76.8 C
    Liam 77.8 C+
    Robin 84.4 B
    - -
    -

    Exercise 10-2, 10-3

    10-2: Rewrite the median function from §8.1.1/140 so that we can call it with either a vector or a built-in array. The function should allow containers of any arithmetic type.

    -

    10-3: Write a test program to verify that the median function operates correctly. Ensure that calling median does not change the order of the elements in the container.

    -

    Solution & Results

    There are two requirements: first is that, the median function can calculate the median value by taking either a vector or built-in array; second is that, the container can be container of any arithmetic type.

    -

    Apparently, our median function should be a function template. The first condition implies that the parameters should be two pointers that denote the range of inputs. Unlike the standard vector, built-in array doesn’t provide member functions like begin() and end(). We cannot simply pass the name of the built-in array to the median function as the name of the array is mere the pointer to the initial element. Alternatively, we can denote its range with [arr, arr+n), where arr is the name of the array and n is the size of the array. The first type parameter of our tempalte represents the type of supplied pointers and will be inferred from the supplied pointers in the process of instantiation.

    -

    The second condition implies that the type of the elements contained in the container should be supplied as we cannot infer the value type from pointers. Therefore, another type parameter of the function template represents the value type of the container and will be infered from the third function parameter, which is defined as any element of the container. As the following declaration shows, Pointer and T represents the pointer type and value type. The function defines three parameters, first two of which denotes the range of inputs while the third is a const reference to the first element contained in the container.

    -
    template<class Pointer, class T>
    T median(Pointer begin, Pointer end, const T& initialElement);
    - -

    Once we have the pointers, we can implement the algorithm as with the previous version. But to avoid changing the original sequence, we’d better construct a new vector to hold the input sequence. The code below gives the full program. I test it by calling the function template with a standard vector and a built-in array that contains the same elements as the vector. As expected, they yield same median value. If changing the value type to int, the function template works as well.

    -
    #include <vector>	// std::vector
    #include <iostream> // std::cout, std::endl
    #include <stdexcept> // std::domain_error
    #include <algorithm> // copy
    #include <cstddef> // size_t

    using std::vector; using std::size_t;
    using std::cout; using std::copy;
    using std::domain_error; using std::endl;

    template<class Pointer, class T>
    T median(Pointer begin, Pointer end, const T& initialElement)
    {
    if(begin == end)
    throw domain_error("median of an empty container");
    vector<T> v(begin, end);
    typename vector<T>::difference_type size = v.end() - v.begin();
    sort(v.begin(), v.end());
    typename vector<T>::const_iterator mid = v.begin() + size/2;
    return size % 2 == 0 ? (*mid + *(mid-1)) / 2 : *mid;
    }

    int main()
    {
    vector<double> vec{53, 56, 23, 78, 90, 89, 34, 12, 41, 48};
    size_t n = vec.size();
    double arr[n];
    copy(vec.begin(), vec.end(), arr);
    cout << median(vec.begin(), vec.end(), vec[0]) << endl;
    cout << median(arr, arr + n, arr[0]) << endl;
    return 0;
    }
    - -

    Test Results

    -
    50.5
    50.5
    - - -
    -

    Exercise 10-4, 10-5, 10-6

    10-4: Write a class that implements a list that holds strings.

    -

    10-5: Write a bidirectional iterator for your String_list class.

    -

    10-6: Test the class by rewriting the split function to put its output into a String_list.

    -

    Solution & Results

    define the String_list class

    The standard list provides all the possible operations and what we need to do is merely to encapsulate our data, a list, by hiding the data and instead providing an appropriate interface. I intend to implement a subset of the list class. Specifically, we can use the String_list class as follows:

    -

    Interface

    -
    // construct an empty container
    String_list s;

    // construct a container with a size and a value
    String_list s1(10, "Hello");

    // return iterator to begining and end
    s1.begin();
    s1.end();

    // construct a container with a range
    String_list s2(s1.begin(), s1.end());

    // copy construct a container
    String_list s3(s2);

    // assignment
    s = s1;

    // check the status of the container
    s1.empty();

    // check the size
    s1.size();

    // clear
    s1.chear();

    // add one element to end
    s1.push_back("Hello");
    -

    All these operations are obvious and we can directly work with list in our implementations. You can add more operations to this String_list class as long as the operations are supported by the standard list class. Let’s see how our String_list class is implemented:

    -

    String_list.h

    -
    #ifndef STRING_LIST_H_
    #define STRING_LIST_H_

    #include <list>
    #include <string>
    #include <cstddef>

    class String_list{
    private:
    typedef std::list<std::string> container;
    container SL;

    public:
    typedef container::iterator iterator;
    typedef container::const_iterator const_iterator;
    typedef container::value_type value_type;
    typedef container::size_type size_type;

    // default constructor: construct an empty String_list
    String_list() = default;

    // constructor that takes a size and a value
    explicit String_list(size_type n, const value_type& val = value_type()): SL(n, val) {}
    String_list(const_iterator beg, const_iterator end): SL(beg, end) {}

    // bidirectional iterator and const bidirectional iterator
    iterator begin() { return SL.begin(); }
    const_iterator begin() const { return SL.cbegin(); }
    iterator end() { return SL.end(); }
    const_iterator end() const {return SL.cend(); }

    // empty
    bool empty() const { return SL.empty(); }

    // size
    size_type size() const { return SL.size(); }

    // clear
    void clear() { SL.clear(); }

    // push_back
    void push_back(const value_type& v) { SL.push_back(v); }
    };

    #endif /* STRING_LIST_H_ */
    -

    It is worth noting that the class defines three constructors but ignores the copy constructor, assignment operator and the destructor. As a result, the compiler will synthesize copy constructor, assignment operator and destructor for us. The synthesized operations depend on the definition of the data member. For example, the compiler will synthesize the copy constructor by calling the default copy constructor of the list class. Therefore, we do not need to worry about that. The synthesized operations also work on the default constructor. I explicitly define the default constructor because that we need other constructors. If we didn’t explicitly define the default constructor, the compiler won’t synthesize for us due to the existence of other constructors.

    -

    Another point is that the class implicitly supports the conversion from a string literal to a string type. This property is inherent in the standard string class and is further explained in chapter 12.

    -

    The last point is that the iterator returned by begin() and end() are all bidirectional iterator. This has been defined in the standard list class. We can traverse forward or backward the container and access or rewrite the elements using the returned iterators.

    -

    rewrite the split function

    The original split function copies its output into the output stream directly. We can replace the outstream object with our String_list. Rather than passing an ostream_iterator as the argument to the split function, we can pass a reference to the container as the argument.
    Therefore, the declaration of the revisied split function is:

    -
    template <class container>
    void split(const std::string &, container&);
    -

    Then we can store each seperated word into the container by calling its push_back function. Please see the code below:

    -

    Split.h

    -
    #ifndef GUARD_SPLIT_H
    #define GUARD_SPLIT_H

    #include <string>
    #include <algorithm>
    #include "String_list.h"

    // true if the argument is whitespace, false otherwise
    bool space(char c)
    {
    return isspace(c);
    }

    // false is the argument is whitespace, true otherwise
    bool not_space(char c)
    {
    return !isspace(c);
    }

    // template declaration and definition
    template <class container>
    void split(const std::string &str, container& c)
    {
    typedef std::string::const_iterator iter;

    iter i = str.begin();
    while(i != str.end())
    {
    // ignore leading spaces
    i = std::find_if(i, str.end(), not_space);

    // find end of next word
    iter j = std::find_if(i, str.end(), space);

    // copy the characters in [i,j) and store into container
    if(i != str.end())
    c.push_back(std::string(i, j));
    i = j;
    }
    }
    #endif /* GUARD_SPLIT_H */
    - -

    Test

    I wrote a test program to test each members of our String_list class and the revised split function.

    -

    main.cpp

    -
    #include <iostream>		// std::cin, endl, cout
    #include <string> // std::string
    #include "String_list.h" // String_list
    #include "split.h" // split

    using std::cout; using std::cin;
    using std::endl; using std::string;

    int main(){
    // construct an empty container
    String_list words;

    // test the empty function
    if(words.empty())
    cout << "This is an empty container\n"
    "Please enter a sentence: ";

    // test the split function, and the push_back member of the String_list
    string line;
    while (getline(cin, line))
    {
    split(line, words);
    }

    cout << "There are " << words.size() << " word(s) in total: ";
    for(auto i: words) {
    cout << i << " ";
    }

    // test bidirectional iterators
    cout << "\nprint all words in reverse order: ";
    String_list::iterator rbeg = --words.end();
    String_list::iterator rend = --words.begin();
    while(rbeg != rend)
    {
    cout << *rbeg-- << " ";
    }

    // test the constructor that takes two input iterators
    String_list words_copy(words.begin(), words.end());
    if(!words_copy.empty()){
    cout << "\nThe size of the container is: " << words_copy.size();
    cout << "\nThe elements contained in the String_list are: ";
    for(auto i: words_copy)
    cout << i << " ";
    }

    // test the clear function
    words.clear();
    if(words.empty())
    cout << "\nThe container now is empty again";

    // test the constructor that takes a size and a value
    String_list words_new(10, "Hello");

    // test the default copy constructor
    String_list words_new_copy(words_new);
    cout << "\nThe container contains: ";
    for(auto i: words_new_copy) {
    cout << i << " ";
    }

    // test the default assignment operator
    words = words_new;
    cout << "\nNow the container words contains: ";
    for(auto i: words) {
    cout << i << " ";
    }
    return 0;
    }
    - -

    The program above is pretty straightforward and gives following results as expected:

    -
    This is an empty container
    Please enter a sentence: Stack is one of the rudimentary data structures that use pointers
    There are 11 word(s) in total: Stack is one of the rudimentary data structures that use pointers
    print all words in reverse order: pointers use that structures data rudimentary the of one is Stack
    The size of the container is: 11
    The elements contained in the String_list are: Stack is one of the rudimentary data structures that use pointers
    The container now is empty again
    The container contains: Hello Hello Hello Hello Hello Hello Hello
    Now the container words contains: Hello Hello Hello Hello Hello Hello Hello
    - -
    -

    Reference

    Koenig, A. and Moo, B.E., 2000. Accelerated C++.

    -

    Lippman, S.B., Lajoie, J. and Moo, B.E., 2013. C++ Primer, 5th edt.

    -]]>
    - - Programming - - - Accelerated C++ Solutions - -
    - - C++ - Managing memory and low-level data structures - /2018/04/10/C-Managing-memory-and-low-level-data-structures/ - Pointers

    Introduction

    pointer is a compound type that points to another base type object. A pointer is a value that represents the address of the object that it points to. What is the address of an object? The address of an object denotes the part of the computer’s memory that contains the object. For example, if x is an object, then &x is the address of x and & is the address operator. Further, if p is a pointer, then *p is the value of the object that p points to and * is the deference operator.

    -

    We are familar with these two operators& and *, however, may feel comfused about their meanings. Generally, these rules can be summarized as follows:

    -
      -
    1. when the & is used as part of a declaration, e.g.

      -
      int i = 10;
      -int &x = i;

      & follows a type and x is a reference.

      -
    2. -
    3. when the & is used as in an expression, e.g.

      -
      int *p = &i;

      & acts as an address operator.

      -
    4. -
    5. when the * is used as part of a declaration, e.g.

      -
      int *p = &i;

      * follows a type and p is a pointer.

      -
    6. -
    7. when the * is used in an expression, e.g.

      -
      int i = *p;
      -*p = i;

      it acts as an deference operator and yields the value of the object that p points to.

      -
    8. -
    -

    It can be observed that a pointer can indirect access the value an object like a reference. However, a reference itself is not an object while a pointer itself is an object. From the third rule above, we know how to define a pointer. For example

    -
    int *p;
    -

    p is a pointer that points to int type object. In other words, p has type int*. As other built-in types, a pointer might point to an unknown object unless we initialize it. Typically,we can initialize a pointer as a null pointer which means that the pointer does’t point to any object. There are several ways to do this:

    -
    int *p = 0;         // the constant 0 can be converted to a pointer type
    int *p = nullptr; // c++11 supports
    int *p = NULL; // must include header <cstdlib>
    -

    These three statements lead to an equivalent result. If we want to assign other values to a pointer, typically we uses the address-of operator:

    -
    int i = 10;
    int j = 20;
    int *p = &i; // p points to i
    int *q = &j; // q points to j
    p = q; // p points to j now
    - -

    Pointers to functions

    It is known that functions are not objects and hence there is no way to copy or assign them, or to pass them as arguments directly. But we do “pass” functions as arguments in previous chapter, for example, the write_analysis function (see declaration below) takes function as its second parameter.

    -
    void write_analysis(std::ostream &out, const std::string &name,
    double analysis(const std::vector<Student_info> &),
    const std::vector<Student_info> &did,
    const std::vector<Student_info> &didnt);
    -

    In fact this is achieved by taking the address of a function, that is, the pointer to the function. Let’s go into some details about the pointers to functions.

    -

    The syntax to define a pointer to function is, for example

    -
    int (*fp)(int);
    -

    Then, if we deference fp and call it with int argument, the result is an int type value.
    Another fact is that what we can do with a function is to take its address or call it. Therefore, when we use a function but is not to call it, we are assumed to be take its address, either using & or not. For example,

    -
    int next (int n)
    {
    return n + 1;
    }
    -
    // these two statements are equivalent
    fp = next;
    fp = &next;
    -

    Further, when we call the next function with an int variable i,

    -
    // these two statements are equivalent
    int x = fp(i);
    int x = (*fp)(i);
    -

    Finally, when we have a function that takes another function as a parameter, the compiler will translate the parameter to be a pointer. Taking the write_analysis as an example, the parameter

    -
    double analysis(const std::vector<Student_info> &)
    -

    is in fact equivalent to

    -
    double (*analysis)(const std::vector<Student_info> &)
    -

    As shown below, when we “pass” another function as an argument, analysis points to a function named median_analysis.

    -
    analysis = median_analysis; // == analysis = &median_analysis
    - -

    What if we want to write a function that returns a function pointer?
    The simplest way is to use type alias

    -
    typedef double (*analysis_fp) (const vector<Student_info>&);
    -

    analysis_fp is the name of the type of a pointer to function. Then

    -
    // get_analysis_ptr returns a pointer to an analysis function
    analysis_fp get_analysis_ptr();
    -

    declares a function get_analysis_ptr() that returns a pointer to an analysis function. This statement is equivalent to

    -
    double (*get_analysis_ptr()) (const vector<Student_info> &);
    -

    From this, we can see that get_analysis_ptr has a parameter list and hence it is a function. In addition, there is an * before it, which indicates that the function returns a pointer. Furthermore, the returned pointer also has a parameter list and hence the returned pointer points to a function that takes parameter const vector & and returns a double type value.

    -

    Now let’s look at an example

    -
    template <class In, class Pred>
    In find_if(In begin, In end, Pred f)
    {
    while (begin != end && !f(*begin))
    ++begin;
    return begin;
    }
    -

    Suppose we have a predicate function

    -
    bool is_negative(int n)
    {
    return n < 0;
    }
    -

    Let’s instanitialize the template with a vector named v

    -
    vector<int>::iterator i = find_if(v.begin(), v.end(), is_negative)
    -

    We use is_negative instead of &is_negative due to that the name of the function turns into a pointer to the function autimatially.

    -

    # Arrays
    **Array** is part of the core language rather than part of the standard library. An **array** is a kind of container that similar to a **vector**, but has fixed length. When we create an **array** object, we should specify the length of the array. For example, we can create an array that contains three elements
    ```c++
    double coords[3];
    -

    Alternatively, we can use a const object to denote the size of the array

    -
    const size_t NDim = 3;
    double coords[NDim];
    -

    size_t is a fundamental unsigned integer type that can be used to represent the size of an array. It is defined in the header . The reason to use size_t is that array is not a class type and has no member function like size_type. It is worth noting that we use const to ensure Ndim is fixed and known at compilation time.

    -

    coords is the name of the array, and in fact it is a pointer that points to the first element of the array. Hence, we can assign a value to the first element through

    -
    *coords = 1.5; // set the initial element of coords to 1.5
    - -

    Pointer arithmetic

    Another fact is that a pointer is a random access iterator. Therefore, we can access the mth element (if available) through

    -
    *(coords + m)
    -

    The pointer that points to one past the last element is coords + NDim. In other words, [coords, coords + NDim) denotes the range of address of the array.

    -

    Now, if we want to copy all elements of the array into a vector named v, we can use the copy algorithm as shown as follows

    -
    vector<double> v;
    copy(coords, coords + NDim, back_inserter(v));
    -

    Alternatively, we can construct v directly as a copy of the elements in coords using

    -
    vector<double> v(coords, coords + NDim);
    -

    C++ 2011 library provides begin and end functions to get the initial pointer and the off-the-end pointer:

    -
    int *beg = begin(coords);   // points to the initial elements
    int *last = end(coords); // points to one past the last element
    ···

    Similar to **difference_type**, **ptrdiff_t** is an signed itegeral type that represents the distance between two **pointers**. **ptrdiff_t** is also defined in the header <cstddef>.

    ## Indexing
    It is known now that **array** supports random access iterators and naturally supports indexing. Therefore, the **n**th emelent (if available) is **coords[n]** and ***coords = coords[0]**.

    ## Array initialization
    Array supports list initialization, for example
    ```c++
    // these two statements have same effect
    int a[10] = {1, 2, 3, 4, 5, 6, 7, 8, 9, 0};
    int a[] = {1, 2, 3, 4, 5, 6, 7, 8, 9, 0};
    -

    If we don’t specify the number of elements contained in the array, compiler will infer the number from the number of the supplied the initializers. But if we specify the number exactly, the number of initializers must not exceed the specified size. So, what if we specifies the size but provides initializers less than the specified number?

    -
    int a[10] = {1, 2, 3};
    -

    Then, the compiler will value initialize the rest elements. In this case, the elements have built-in type int and hence are set to 0.

    -

    String literals revisited

    As we mentioned earlier, String literals are not strings. In fact, a string literal is an array of const char with one more element, i.e. ‘\0’ ,than the number of characters in the literal. ‘\0’ is a null character. Therefore, when we create a string literal, we should specify the size one larger than it should be for the purpose of holding the extral null character. For example, a string literal Hello is created through

    -
    // two ways to initialize a string literal
    const char hello[] = {'H', 'e', 'l', 'l', 'o', '\0'};
    const char hello[6] = "Hello";
    -

    The null character marks the end of the literal. When we want to get the number of characters excluding the null character, we can use the function strlen defined in . The strlen might has following implementation

    -
    // Exanple implementation of standard-library function
    size_t strlen(const char *p)
    {
    size_t size = 0;
    while (*p++ != '\0')
    ++size;
    return size;
    }
    -

    Now, if we want to copy the string literal Hello into a string type object, we can use

    -
    // three equivalent ways to copy the string literal into a string
    string s(hello); // variable hello represents "Hello"
    string s("Hello");
    string s(hello, hello + strlen(hello)); // treats hello as the begin iterator
    - -

    Initializing arrays of character pointer

    Essentially, a string literal is just a convenient way of writing the address of the initial character of a null-terminated sequence of characters. Now let’s look at an example that shows how to generate an appropriate letter grade according to a numeric grade. The letter grades and numeric grades have following mapping relations:

    -
    If the grade is at least  97  94  90  87  84  80  77  74  70  60  0
    then the letter grade is A+ A A- B+ B B- C+ C C- D F
    -

    The program is shown below and let’s analyse it step by step.

    -
    string letter_grade(double grade)
    {
    // range posts for numberic grades
    static const double numbers[] = {97, 94, 90, 87, 84, 80, 77, 74, 70, 60, 0};

    // name for the letter grades
    static const char* const letters[] = {"A+", "A", "A-", "B+"
    "B", "B-", "C+", "C", "C-", "D", "F"};

    // compute the number of grades given the size of the array
    // and the size of a single element
    static const size_t ngrades = sizeof(numbers)/sizeof(*numbers);

    // given a numberic grade, find the associated letter grade
    for (size_t i = 0; i < ngrades; ++i)
    {
    if (grade >= numbers[i])
    return letters[i];
    }
    return "?\?\?";
    }
    -

    The function itself takes a double type value (i.e. a numeric grade), and returns a string (i.e. a letter grade). The first step is to construct two objects that hold the numeric grades and the letter grades, respectively. It is simple if we use vector or list or map as we know the numeric grade int type and the letter grade is string type. What if we use array? There is nothing new if we use string type, for example,

    -
    static const string letters[] = {"A+", "A", "A-", "B+", "B", "B-", "C+", "C", "C-", "D", "F"};
    -

    But if we treat each letter grade as a a sequence of characters, we can store them as the program shows

    -
    static const char* const letters[] = {"A+", "A", "A-", "B+", "B", "B-", "C+", "C", "C-", "D", "F"};
    - -

    It can be observed that:

    -
      -
    1. the static means that the object is initialized only once and exists in the whole process till the program terminates.
    2. -
    3. the letters is an array of const pointers to const char. Each element is a string literal, i.e. an alternative way of writing the address of the initial character. Therefore, each element is in fact can be regarded as a pointer that points to the initial letter of the string literal. For example, letters[0] gives the first letter grade A+ while *(letters[0]) gives the initial letter of the first letter grade as letters[0] is a pointer as well.
    4. -
    5. The first const means that the array elements are constant. The second const means that the address are constant.
    6. -
    7. the next statement introduces sizeof which is a function that returns a size_t type value indicating how much memory an object occupied. Therefore, sizeof(numbers) gives the number of bytes that the object numbers while sizeof(*numbers) yields the number of bytes that each element consumes. The result of the division yields a value that is the number of elements contained in the object (i.e. array numbers).
    8. -
    9. finally, using a for statement a corresponding letter grade according to the input. If there exists such a grade, return it, otherwise, return ???.
    10. -
    -

    Arguments to main

    As mentioned in chapter 0, the main function can take arguments like other functions if it defines parameters. A conventional way is to pass a sequence of stings to main as an argument. For example, when we pass say Hello, world as an argument, the program give outputs

    -
    Hello, world
    -

    How to achieve this? This is done by giving two parameters:

    -
      -
    1. an int type parameter named argc which is a value that denotes the number of pointers that pointed by argv.
    2. -
    3. another one is named as argv which is a pointer to a pointer to char. For example, the pointer letters described above. letters points to the initial element, that is, the pointer to char A as the pointer to a string literal points to the initial char of the sequence. Therefore, we can define this parameter as
    4. -
    -
    char* argv[]
    -

    or

    -
    char** argv
    -

    These two expression are equivalent. Now, let’s look at the main with arguments argc and argv:

    -
    int main(int argc, char** argv)
    {
    // if there are arguments, write them
    if(argv > 1)
    {
    // declare i outside the for because we need it after the loop
    int i;

    // write all but the last entry and argv[i] is a char*
    for (i = 1; i < argc - 1; ++i)
    cout << argv[i] << " ";

    // write the last entry but not a space
    cout << argv[i] << endl;
    }
    }
    -

    Assuming we pass Hello, world as an argument, argc will be 3 (why ?) and argv points to the first element, that is, another pointer to “Hello,” that is, character ‘H’. argv[0] can be regarded as the name (i.e. pointer again) of the first string literal, and hence we can access each string literal through argv[i], where i < argc.

    -

    You might wonder that why argc is 3 and why we use range [1, argv) rather than [0, argv) as we did before? This is because when we pass arguments, the program name will be automatically passed as one element. Specifically, if we pass sequence please give me a number, there will be 5 string literal in total, but argc = 6 because:

    -
    argv points to pointer argv[0] points to "program name"
    argv[1] points to "please"
    argv[2] points to "give"
    argv[3] points to "me"
    argv[4] points to "a"
    argv[5] points to "number"
    -

    This shows why we use the range [1, argv) to access each element.

    -

    Reading and writing files

    By now, we are familar with four iostream class objects cin, cout, clog, and cerr. This part introduces another IO facilitiy ifstream and ofstream class to deal with multiple input and output files. Specifically, the ofstream class object allows us to write data into a file, while iftream allows us to read data from a file. We can use these objects like what we operate on cin and cout such as that they also support << and >> operators as well as getline.

    -

    When defining an ifstream or oftream object, we can associate the object with a file that we intend to write or read. Naturally we supply the file name which can be a string (C++11) or a string literal. This can be done either on construction or by calling member function open. For example,

    -
    // two ways to open a file named s
    ifstream in(s);
    ifstream.open(s);
    - -

    The below program shows how to copy the contents of a file named in to a file named out.

    -
    int main()
    {
    ifstream infile("in");
    ofstream outfile("out");

    string s;
    while (getline(infile, s))
    outfile << s << endl;
    return 0;
    }
    - -

    The first statement creates an ifstream object infile and associates it with a file named in. Similarly, the second statement creates an ofstream object outfile and associates it with a file named out. Both file names are string literals, i.e. pointers to the initial character of null-terminated array. If file in doesn’t exist, there won’t be a file in being created. However, if out doesn’t exist, it will be create to hold the outputs.

    -

    If we don’t want to use string literal as name, one solution is to store the name in a string and use member function c_str to get the pointer to the array that contains a null-terminated sequence of characters (i.e., a C-string) representing the current value of the string object_ Reference to std::string::c_str. For example, file is a string variable that contains the name of a file to be read, we then associate the file with a ifstream object through

    -
    ifstream infile(file.c_str());
    - -

    Finally, let’s look at another program that produces a copy of all files whose names are given as arguments to main.

    -
    int main(int argc, char** argv)
    {
    int fail_count = 0;
    // for each file in the input list
    for (int i = 1; i < argc; ++i)
    {
    ifstream in(argv[i]);

    // if it exits, write its contents, otherwise generate an error message
    if(in)
    {
    string s;
    while(getline(in, s))
    cout << s << endl;
    }
    else
    {
    cerr << "cannot open file " << argv[i] << endl;
    ++fail_count;
    }
    }
    return fail_count;
    }
    -

    The program logic is simple: first get a name from the arguments argv, then open a same name file; if the file exists, the contents of it will be written on the output device, but if there is no such file, count and record such case with a variable fail_count; finally, returns fail_count, by then, the value of fail_count represents the number of non-existent files; if fail_count is not 0, it indicates that the program terminates abnormally.

    -

    It is worth noting that when a ifstream object fails to associate the corresponding file, the state of the input stream is set to fail. Therefore, the if condition if(in) is very useful and helpful for us to check whether our operations on the file are valid.

    -

    Three kinds of memory management

    The first kind is called automatic memory management: the system allocates memory for a local variable when it executes the variable’s definition, and deallocates that memory automatically at the end of the block that contains the definition. Therefore, we should note that once a variable has been dealloated, any pointers to it are invalid. For example,

    -
    // this function deliberately yields an invalid pointer
    // it is intended as a negative example-don't do this!
    int* invalid_pointer()
    {
    int x;
    return &x; // instant disaster
    }
    -

    This function intends to return the address of local variable x. However, the return statement ends the execution of the block and hence deallocates the memory of x, resulting that &x is invalid. To solve this problem, we can use another kine memory management, i.e. statically allocated.

    -
    // This function is completely legitimate
    int* pointer_to_static()
    {
    static int x;
    return &x;
    }
    -

    When a local object is specified as static, it is created only the first time when its definition is executed and won’t be destroied until meet the end of the program. Therefore, the function returns a valid pointer of object x.

    -

    The shortcoming of this kind memory management is that each call of such function obtains the same pointer. If we want to get a different pointer each time, we can choose an alternative memory management, the dynamic allocation.

    -

    If T is an object type, then new T is an expression that

    -
      -
    1. allocates an T type object
    2. -
    3. default-initializes the object
    4. -
    5. yields a pointer to this newly allocated object
    6. -
    7. the object exists since it is created till either the end of the program or the execution of delete p, where p is a copy of the pointer returned by the expression.
    8. -
    -

    For example,

    -
    // allocate an unnamed object of type int, and initialize it to 42
    int *p = new int(42);
    -

    We can change the value of the object by manipulating the pointer p

    -
    // change the object to 43
    ++*p;
    -

    If we want to delete it, we do

    -
    // after this execution, the occupied memory is freed and p becomes an invalid pointer
    delete p;
    - -

    Now let’s revisit the problem we met above and write a function that can return different pointer each time

    -
    int *pointer_to_dynamic()
    {
    return new int(0);
    }
    -

    This function returns a brand new pointer each time. But do not forget to release the memory through delete p.

    -

    Allocating and deallocating an array

    By analogy, we can allocate an array that contains T type values.

    -
    T* p = new T[n];
    -

    n is a non-negative integral value and new T[n] allocates an array of n objects of type T. There is also a returned pointer that points to the initial element of the array. Each object of the array is default-initialized. This means that if T is a built-in type, then the elements are undefined while if T is a class type, then the elements are initialized by the default constructor defined in that class type.

    -

    If n equals to 0, then the new return a valid off-the-end pointer as there is no element contained in the array. To deallocate the memory, we use delete[] p. Here is an example

    -
    T* p = new T[n];
    vector<T> v(p, p + n); // copy elements in the array to vector
    delete[] p;
    - -

    The new allocates an array and stay around until that the program terminates or executes the delete[]. Before deallocating the array, the system destroys each element in reverse order. Let’s look at a function

    -
    char* duplicate_share(const char* p)
    {
    // allocate enough space; remember to add one for the null
    size_t length = strlen(p) + 1;
    char* result = new char[length];

    // copy into our newly allocated space and return pointer to first element
    copy(p, p + length, result);
    return result;
    }
    -

    This function takes a pointer to char as an argument and returns a point to char. It actually copy all chars from one string literal into a new string literal. Firstly, it allocates memory for the new array of chars, then apply copy algorithm to copy each char in the original array into the newly created array. Noting that strlen gives the number of non-null characters contained in a string literal. However, when we allocate the memory for a new string literal, we need to add one position more for holding the null-character. As for the deallocation of dynamic allocation, we will discuss more in next chapter.

    -
    -

    Test examples

    Now I test severl programs analysed above. The first program tests arguments to main and letter_grade function.

    -

    Test_1.cpp

    -
    #include <iostream>		// to get the declaration of cout, endl
    #include <string> // to get the declaration of string
    #include <cstddef> // to get the declaration of size_t

    using std::cout; using std::endl;
    using std::string; using std::size_t;

    // function declaration
    string letter_grade(double);

    // main function with non-empty parameter list
    int main(int argc, char** argv)
    {
    // if there are arguments, write them
    if(argc > 1)
    {
    // declare i outside the for because we need it after the loop
    int i;

    // write all but the last entry and argv[i] is a char*
    for (i = 1; i < argc - 1; ++i)
    cout << argv[i] << " ";

    // write the last entry but not a space
    cout << argv[i] << endl;
    }

    cout << "my letter grade is: " << letter_grade(75) << endl;

    return 0;
    }

    string letter_grade(double grade)
    {
    // range posts for numberic grades
    static const double numbers[] = {97, 94, 90, 87, 84, 80, 77, 74, 70, 60, 0};

    // name for the letter grades
    static const char* const letters[] = {"A+", "A", "A-", "B+",
    "B", "B-", "C+", "C", "C-", "D", "F"};

    // compute the number of grades given the size of the array
    // and the size of a single element
    static const size_t ngrades = sizeof(numbers)/sizeof(*numbers);

    // given a numberic grade, find the associated letter grade
    for (size_t i = 0; i < ngrades; ++i)
    {
    if (grade >= numbers[i])
    return letters[i];
    }
    return "?\?\?";
    }
    - -

    Test Results

    -
    Inputs

    command line arguments: please tell me the true
    numeric grade: 75

    Outputs

    please tell me the truth
    my letter grade is: C-
    - -

    The second program produces a copy of all files whose names are given as arguments to main.

    -

    test_2.cpp

    -
    #include <iostream>		// to get the declaration of cout, endl, cerr
    #include <string> // to get the declaration string getline
    #include <fstream> // to get the declaration of ifstream

    using std::cout; using std::endl;
    using std::cerr; using std::string;
    using std::getline; using std::ifstream;

    int main(int argc, char** argv)
    {
    int fail_count = 0;
    // for each file in the input list
    for (int i = 1; i < argc; ++i)
    {
    ifstream in(argv[i]);

    // if it exits, write its contents, otherwise generate an error message
    if(in)
    {
    string s;
    while(getline(in, s))
    cout << s << endl;
    }
    else
    {
    cerr << "cannot open file " << argv[i] << endl;
    ++fail_count;
    }
    }
    return fail_count;
    }
    -

    I firstly create a file named in and write following sequences into it

    -
    what are you going to do
    to be or not to be
    that is a question
    - -

    Then I set the commond line arguments as: please tell me the truth in
    Once I click the run button, the program below gives following results

    -
    cannot open file please
    cannot open file tell
    cannot open file me
    cannot open file the
    cannot open file truth
    what are you going to do
    to be or not to be
    that is a question
    - -

    The last program tests that we can use the duplicate_share function to produce a copy of a sequence of characters.

    -
    #include <iostream>	// to get the declaration of cout,
    #include <cstring> // to get the declaration of strlen
    #include <cstddef> // to get the declaration of size_t
    #include <algorithm>// to get the declaration of copy

    using std::cout; using std::endl;
    using std::strlen; using std::size_t;
    using std::copy;

    char* duplicate_share(const char* p)
    {
    // allocate enough space; remember to add one for the null
    size_t length = strlen(p) + 1;
    char* result = new char[length];

    // copy into our newly allocated space and return pointer to first element
    copy(p, p + length, result);
    return result;
    }

    int main()
    {
    // constructe an array
    char s[] = "computational";

    // get a copy
    char* s_copy = duplicate_share(s);

    // verify the copy
    cout << s_copy << endl;
    }
    - -

    The program works as we expected and gives results

    -
    computational
    - -
    -]]>
    - - Programming - - - C++ - Notes - -
    - - Accelerated C++ Solutions to Exercises(Chapter 9) - /2018/04/08/Accelerated-C-Solutions-to-Exercises-Chapter-9/ - Exercise 9-0

    Compile, execute, and test the programs in this chapter.

    -

    Solution & Results

    Please find the solution and analysis in Defining new types.

    -

    Exercise 9-1

    Reimplement the Student_info class so that it calculates the final grade when reading the student’s record, and stores that grade in the object. Reimplement the grade function to use this precomputed value.

    -

    Solution & Results

    A similar program has been completed in exercise 4-6. Now I’ll write a new grading program based on class type. The strategy can be logically divided into three parts:

    -
      -
    1. abstract data members
    2. -
    3. design interface and write member functions
    4. -
    5. access Control
    6. -
    -

    data members and constructors

    The original program uses a class type that has four data members: name, midterm, final and homework. This exercise requires us to store the final grade in the class object, which implies that we need another data member grade representing the final grade. There seems no need to store midterm, fina and homework anymore due to that the final grade is computed when reading the student’s record. I didn’t remove them merely as theoretically these information should be kept for a student. Now, we have part of our new class type

    -
    class Student_info{
    std::string n;
    double midterm, final, g;
    std::vector<double> homework;

    // to be filled by defining/declaring member functions
    };
    -

    The first I considered was to define constructors when desigining an proper interface. The purpose is to allow us to initialize the class object through:

    -
    Student_info record;        // create an empty object
    Student_info record(cin); // create and initialize an object by reading from input stream
    -

    The first one needs a default constructor that is responsible for initializing data members. Following code shows their declarations and definitions that are put outside the class.

    -

    declaring constructors

    -
    Student_info ();        // construct an empty object 
    Student_info (std::istream &); // construct an object by reading from istream
    -

    defining constructors

    -
    Student_info::Student_info(): midterm(0), final(0), g(0) {}
    Student_info::Student_info (std::istream & in) { read(in); }
    ```
    Nothing that all three **double** type variables are value-initialized to 0 while **homework** is initialized by default constructor of **vector<double>** type, leading to an empty **homework**.

    ### other member functions and protection
    The next is to define a member function to read data into the class object. The declaration is as same as the previous one:
    ```c++
    std::istream & read(std::istream &);
    -

    When we define the read member, we should meet the requirement that computing the final grade in the process of reading.

    -
    istream & Student_info::read(istream &is)
    {
    // reads and store the student's name, midterm and final exam grades
    is >> n >> midterm >> final;
    if (is)
    {
    // reads and store all homework grades
    read_hw(is, homework);
    g = !homework.empty() ? ::grade(midterm, final, homework) : 0;
    }
    return is;
    }
    -

    Noting that I deal with the case of empty homework with an if statement which computes the final grade if the homwork is not empty and otherwise sets the final grade to 0. The purpose is to avoid the exception when reading data. Alternatively, I define extra two functions to check the validity of final grade and “catch” the state of the object. These two functions are defined inside the class

    -
    bool valid() const { return !homework.empty(); }
    std::string state() const { return valid() ? "valid" : "invalid: student has done no homework"; }
    -

    This allows users to seperate valid records and invalid records.

    -

    Finally, we define members to get the name and grade for the purpose that prevents users access data members directly.

    -
    std::string name() const { return n; }
    double grade() const { return g; }
    -

    All member functions except read and constructors are not allowed to change data members by adding qualifier const after the parameter list. To seperate the data abstraction and interface, we control access using specifier public and private.

    -

    Organize files

    Now let’s complete the header file Student_info.h and the corresponding source file.

    -
    #ifndef GUARD_STUDENT_INFO
    #define GUARD_STUDENT_INFO

    #include<iostream>
    #include<string>
    #include<vector>

    class Student_info{
    public:
    // constructors
    Student_info ();
    Student_info (std::istream &);

    // member functions to check state
    bool valid() const { return !homework.empty(); }
    std::string state() const { return valid() ? "valid" : "invalid: student has done no homework"; }

    // member functions read and functions to get name, grade and
    std::string name() const { return n; }
    double grade() const { return g; }
    std::istream & read(std::istream &);

    private:
    std::string n;
    double midterm, final, g;
    std::vector<double> homework;
    };

    // nonmember functions
    bool compare(const Student_info &, const Student_info &);
    std::istream & read_hw(std::istream &, std::vector<double> &);
    #endif
    - -

    Student_info.cpp

    -
    #include <stdexcept>
    #include "Student_info.h"
    #include "grade.h"

    using std::vector; using std::cin;
    using std::istream; using std::cout;
    using std::domain_error;

    // construct one by reading from input stream
    Student_info::Student_info(): midterm(0), final(0), g(0) {}
    Student_info::Student_info (std::istream & in) { read(in); }

    // member function read
    istream & Student_info::read(istream &is)
    {
    // reads and store the student's name, midterm and final exam grades
    is >> n >> midterm >> final;
    if (is)
    {
    // reads and store all homework grades
    read_hw(is, homework);
    g = !homework.empty() ? ::grade(midterm, final, homework) : 0;
    }
    return is;
    }

    // nonmember function compare two strings
    bool compare(const Student_info &x, const Student_info &y)
    {
    return x.name() < y.name();
    }

    // nonmember function read data into a vector
    istream & read_hw(istream &in, vector<double> &hw)
    {
    // read homework grades
    hw.clear();
    double x;
    while(in >> x)
    hw.push_back(x);

    // clear the stream so that input will work for the next student
    in.clear();

    return in;
    }
    -

    Now we can write the main function.

    -

    mainfunction.cpp

    -
    #include <algorithm>		// to get the declaration of max, sort
    #include <iomanip> // to get the declaration of setprecision
    #include <iostream> // to get the declaration of cin, cout, endl
    #include <stdexcept> // to get the declaration of domain_error
    #include <string> // to get the declaration of string
    #include <vector> // to get the declaration of vector
    #include "Student_info.h" // to get the declaration of Student_info

    using std::cin; using std::setprecision;
    using std::cout; using std::sort;
    using std::endl; using std::streamsize;
    using std::domain_error; using std::string;
    using std::max; using std::vector;

    int main()
    {
    vector<Student_info> students;
    Student_info record;
    string::size_type maxlen = 0;

    // read and store all the records, and find the length of the longest name
    while(record.read(cin))
    {
    maxlen = max(maxlen, record.name().size());
    students.push_back(record);
    }


    // alphabetize the records
    sort(students.begin(), students.end(), compare);

    // write each line of outpurs
    for (vector<Student_info>::const_iterator it = students.begin();
    it != students.end(); ++it)
    {
    // write the name, blanks
    cout << (*it).name() << string(maxlen + 1 - (*it).name().size(), ' ');
    // compute and write the final grade
    double final_grade = (*it).grade();
    streamsize prec = cout.precision();
    cout << setprecision(3) << final_grade << setprecision(prec);

    if(!(*it).valid())
    cout << string(4,' ') << (*it).state() << endl;
    else
    cout << endl;
    }
    return 0;
    }
    -

    There is nothing new except that I deal with exception with member functions valid() and state() instead the try block.

    -

    Finally, I present the header file and source file that contains overloaded grade function and median function.

    -

    grade.h

    -
    #ifndef GUARD_GRADE_H
    #define GUARD_GRADE_H

    #include<vector>

    double grade(double, double, const std::vector<double> &);
    double grade(double, double, double);
    double median(std::vector<double>);
    #endif
    - -

    grade.cpp

    -
    #include <algorithm>
    #include <stdexcept>
    #include <vector>
    #include "grade.h"

    using std::domain_error; using std::istream;
    using std::vector; using std::sort;

    // grade function 1
    double grade(double midterm, double final, const vector<double> &hw)
    {
    if (hw.empty())
    throw domain_error("student has done no homework");
    return grade(midterm, final, median(hw));
    }

    // grade function 2
    double grade(double midterm, double final, double homework)
    {
    return 0.2*midterm + 0.4*final + 0.4*homework;
    }

    // compute the median of vector<double>
    double median(vector<double> vec)
    {
    // check whether the vec is empty
    if (vec.begin() == vec.end())
    throw domain_error("median of an empty vector");

    // sort the grades
    sort(vec.begin(), vec.end());

    // compute the median homework grade
    vector<double>::difference_type size = vec.end() - vec.begin();
    vector<double>::const_iterator mid = vec.begin() + size/2;
    return size % 2 == 0 ? (*mid + *(mid - 1))/2 : *mid;
    }
    - -

    Test

    Inputs

    Nqacg 32.4444 16.3838 43
    Kmgsk 89.2525 14.7374 32
    Awhof 73.7071 73.8485
    Thyyp 92.7172 47.5556
    Zvxxc 66.9091 69.6162 0
    Asezo 67.8182 32.6364 10
    Evawh 77.798 54.9596 13
    Qhwir 75.4242 93.5758
    Nbcpz 71.6263 75.8182 47
    Dbevs 67.4949 75.3434 31

    Outputs

    Asezo 30.6
    Awhof 0 invalid: student has done no homework
    Dbevs 56
    Evawh 42.7
    Kmgsk 36.5
    Nbcpz 63.5
    Nqacg 30.2
    Qhwir 0 invalid: student has done no homework
    Thyyp 0 invalid: student has done no homework
    Zvxxc 41.2
    - -

    Users can rewrite the main function to generate a better formatted report according to their own preference, using member functions valid() and state().

    -
    -

    Exercise 9-2

    If we define the name function as a plain, nonconst member function, what other functions in our system must change and why?

    -

    Solution & Results

    If we define the name function as a nonconst member function, we need to change the compare function as well.

    -
    bool compare(const Student_info &x, const Student_info &y)
    {
    return x.name() < y.name();
    }
    -

    See from the compare function above, the arguments are passed by reference to const objects. In other words, the compare function treats Student_info object as const. If it calls non-const member functions (i.e. name()), there is no guarantee that it doesn’t modify the object, which is a potential conflict with its definition. THerefore, this is not allowed by the compiler. To correct it, we can remove the qualifer const to release its restriction on arguments as shown below.

    -
    bool compare(Student_info &x, Student_info &y)
    {
    return x.name() < y.name();
    }
    - -
    -

    Exercise 9-3, 9-4

    9-3: Our grade function was written to throw an exception if a user tried to calculate agrade for a Student_info object whose values had not yet been read. Users who care are expected to catch this exception. Write a program that triggers the exception but does not catch it. Write a program that catches the exception.

    -

    9-4: Rewrite your program from the previous exercise to use the valid function, thereby avoiding the exception altogether.

    -

    Solution & Results

    The key is to use member function valid() to avoid the exception. This exercise is simple and hence no further analysis. I only write the main function here and please find other files in Defining new types.

    -

    Trigger the exception

    -
    #include "Student_info.h"
    #include <stdexcept>
    #include <iostream>

    using std::domain_error;
    using std::cout;

    int main()
    {
    Student_info record;
    record.grade();
    return 0;
    }
    - -

    catch the exception

    -
    #include "Student_info.h"
    #include <stdexcept>
    #include <iostream>

    using std::domain_error;
    using std::cout;

    int main()
    {
    Student_info record;
    try{
    record.grade();
    }catch(domain_error e){
    cout << e.what();
    }
    return 0;
    }
    -

    avoid the exception

    -
    #include "Student_info.h"
    #include <stdexcept>
    #include <iostream>

    using std::domain_error;
    using std::cout;
    using std::cin;

    int main()
    {
    Student_info record (cin);
    if(record.valid())
    cout << record.grade();
    return 0;
    }
    - -
    -

    Exercise 9-5

    Write a class and associated functions to generate grades for students who take thecourse for pass/fail credit. Assume that only the midterm and final grades matter, and that astudent passes with an average exam score greater than 60. The report should list the students in alphabetical order, and indicate P or F as the grade.

    -

    Solution & Results

    My strategy is to define a new grade function:

    -

    declaration

    -
    std::string grade() const;
    - -

    definition

    -
    string Student_info::grade() const
    {
    return (midterm + final)/2 > 60 ? "P" : "F";
    }
    -

    So, when calling this member, it gives the letter grade P or F. The revised class type is defined as follows

    -

    Student_info.h

    -
    #ifndef GUARD_STUDENT_INFO
    #define GUARD_STUDENT_INFO

    #include <string>
    #include <iostream>
    #include <vector>

    class Student_info
    {
    public:
    Student_info (); // default constructor
    Student_info (std::istream &); // constructor with argument
    std::string name() const { return n; } // inline member function return name
    std::istream & read(std::istream &); // member function read in data
    std::string grade() const;

    private:
    std::string n;
    double midterm, final;
    };

    std::istream & read_hw(std::istream &, std::vector<double> &); // nonmember function read data into a vector
    bool compare(const Student_info &, const Student_info &); // nonmember function compare two string

    #endif

    **Student_info.cpp**
    ```c++
    #include "Student_info.h"

    using std::vector; using std::string;
    using std::istream;

    // construct an empty Student_info object
    Student_info::Student_info (): midterm(0), final(0) { }

    // construct one by reading from input stream
    Student_info::Student_info (std::istream & in) { read(in); }

    // member function read data from input stream
    std::istream & Student_info::read(std::istream &in)
    {
    // reads and store the student's name, midterm and final exam grades
    in >> n >> midterm >> final;
    return in;
    }

    // member function grade
    string Student_info::grade() const
    {
    return (midterm + final)/2 > 60 ? "P" : "F";
    }

    // nonmember function compare
    bool compare(const Student_info &x, const Student_info &y)
    {
    return x.name() < y.name();
    }
    - -

    Noting that there is no computations as previous version. What we need to do is merely to remove the needless code.

    -

    Finally, I present the test function followed by the test results

    -

    mainfunction.cpp

    -
    #include <algorithm>		// to get the declaration of max, sort
    #include <string> // to get the declaration of string
    #include <vector> // to get the declaration of vector
    #include "Student_info.h" // to get the declaration of Student_info

    using std::cin;
    using std::cout; using std::sort;
    using std::endl; using std::string;
    using std::max; using std::vector;

    int main()
    {
    vector<Student_info> students;
    Student_info record;
    string::size_type maxlen = 0;

    // read and store all the records, and find the length of the longest name
    while(record.read(cin))
    {
    maxlen = max(maxlen, record.name().size());
    students.push_back(record);
    }

    // alphabetize the records
    sort(students.begin(), students.end(), compare);

    // write each line of outpurs
    for (vector<Student_info>::const_iterator it = students.begin();
    it != students.end(); ++it)
    {
    // write the name, blanks
    cout << (*it).name() << string(maxlen + 1 - (*it).name().size(), ' ');

    // compute and write the final grade
    cout << (*it).grade() << endl;
    }
    return 0;
    }
    - -

    Test

    -
    Inputs

    Xdvdr 55.404 28.7778
    Qlyys 91.9192 60.0404
    Iutlc 12.9697 61.202
    Jygsc 58.2424 99.5657
    Wxilm 85.0606 57.2424
    Lshfy 34.8687 65.9697
    Ujruj 41.8182 89.6364
    Orbac 3.58586 56.8788
    Fyhub 99 65.2828

    Outputs

    Fyhub P
    Iutlc F
    Jygsc P
    Lshfy F
    Orbac F
    Qlyys P
    Qzaen P
    Ujruj P
    Wxilm P
    Xdvdr F
    - -
    -

    Exercise 9-6

    Rewrite the grading program for the pass/fail students so that the report shows all the students who passed, followed by all the students who failed.

    -

    Solution & Results

    One possible solution is that using ths standard library algorithm stable_partition to rearrange the elements (i.e. student records) such that all passing grades preceds all failing grades.

    -
    stable_partition(students.begin(), students.end(), pgrade);
    -

    The pgrade is defined as follows

    -
    bool fgrade(const Student_info &s)
    {
    return s.grade() < 60;
    }

    bool pgrade(const Student_info &s)
    {
    return !fgrade(s);
    }
    -

    These two function can be declared in the file Student_info.h as nonmember function( see Defining new types). I’ll present the main function and a simple test in below part.

    -

    mainfunction.cpp

    -
    #include <algorithm>		// to get the declaration of max, sort
    #include <iomanip> // to get the declaration of setprecision
    #include <iostream> // to get the declaration of streamsize
    #include <algorithm> // to get the declaration of stable_partition
    #include <string> // to get the declaration of string
    #include <vector> // to get the declaration of vector
    #include "Student_info.h" // to get the declaration of Student_info

    using std::cin;
    using std::cout; using std::setprecision;
    using std::endl; using std::streamsize;
    using std::max; using std::vector;
    using std::stable_partition; using std::string;

    int main()
    {
    vector<Student_info> students;
    Student_info record;
    string::size_type maxlen = 0;

    // read and store all the records, and find the length of the longest name
    while(record.read(cin))
    {
    maxlen = max(maxlen, record.name().size());
    students.push_back(record);
    }

    // students who passed followed by all student who failed
    stable_partition(students.begin(), students.end(), pgrade);

    // write each line of outpurs
    for (vector<Student_info>::const_iterator it = students.begin();
    it != students.end() ; ++it)
    {
    if((*it).valid())
    {
    // write the name, blanks
    cout << (*it).name() << string(maxlen + 1 - (*it).name().size(), ' ');

    // compute and write the final grade
    double final_grade = (*it).grade();
    streamsize prec = cout.precision();
    cout << setprecision(3) << final_grade << setprecision(prec) << endl;
    }
    }
    return 0;
    }
    -

    Test

    -
    Inputs

    Nmlox 42.3434 61.0808 66
    Mljwc 39.2727 80.3636 62
    Omzml 78.9596 39.8283 42
    Buvdm 20.7273 26.8384 35
    Lczfw 14.6162 72.1717 75
    Bloic 50.2525 60.798 44
    Hewvl 59.4646 98.4141 73
    Nunsg 95.1414 95.9596 8
    Gkaqw 97.7071 85.6263 93
    Isohi 49.3434 21.9293 63
    Tduzm 92.1111 18.4343 31
    Koede 82.404 36.0101 75
    Igfab 57.6061 90.9899 15
    Ejtaa 93.0404 27.8586 28
    Iwhgb 97.6364 44.3333 4
    Frbhw 40.7677 68.4747 19
    Hsskh 9.44444 90.2121 25
    Yvyel 2 20.6566 67
    Ktnaa 95.596 19.8586 84
    Pdjjb 37.5455 68.303 57

    Outputs

    Mljwc 64.8
    Lczfw 61.8
    Hewvl 80.5
    Nunsg 60.6
    Gkaqw 91
    Koede 60.9
    Ktnaa 60.7
    Nmlox 59.3
    Omzml 48.5
    Buvdm 28.9
    Bloic 52
    Isohi 43.8
    Tduzm 38.2
    Igfab 53.9
    Ejtaa 41
    Iwhgb 38.9
    Frbhw 43.1
    Hsskh 48
    Yvyel 35.5
    Pdjjb 57.6
    - -
    -

    Exercise 9-7

    The read_hw function §4.1.3/57 solves a general problem (reading a sequence ofvalues into a vector) even though its name suggests that it should be part of theimplementation of Student_info. Of course, we could change its name—but suppose,instead, that you wanted to integrate it with the rest of the Student_info code, in order to clarify that it was not intended for public access despite its apparent generality? How would you do so?

    -

    Solution & Results

    My solution is to add the read_hw as the member function. To control the access, I specify the member as private. Accordingly, the revised Student_info.h is

    -
    #ifndef GUARD_STUDENT_INFO
    #define GUARD_STUDENT_INFO

    #include <string>
    #include <iostream>
    #include <vector>

    class Student_info
    {
    public:
    Student_info (); // default constructor
    Student_info (std::istream &); // constructor with argument
    std::string name() const { return n; } // inline member function return name
    bool valid() const { return !homework.empty(); }// inline member function check state
    std::istream & read(std::istream &); // member function read in data
    double grade() const; // member function calculate final grade

    private:
    std::string n;
    double midterm, final;
    std::vector<double> homework;

    // private member function read data into a vector
    std::istream & read_hw(std::istream &, std::vector<double> &);
    };
    // nonmember function compare two string
    bool compare(const Student_info &, const Student_info &);
    #endif
    - -

    Correspondingly, when we define the read_hw outside the class, we need to specify the name scope using Student_info::read_hw. The new read_hw is given below

    -
    istream & Student_info::read_hw(istream &in, vector<double> &hw)
    {
    if (in){
    //get rid of previous contents
    hw.clear();

    // read homework grades
    double x;
    while(in >> x)
    hw.push_back(x);

    // clear the stream so that input will work for the next student
    in.clear();
    }
    return in;
    }
    -

    All other files keep unchanged and can be found in Defining new types.

    -
    -

    Reference

    Koenig, A. and Moo, B.E., 2000. Accelerated C++.

    -

    Lippman, S.B., Lajoie, J. and Moo, B.E., 2013. C++ Primer, 5th edt.

    -]]>
    - - Programming - - - Accelerated C++ Solutions - -
    - - C++ - Defining new types - /2018/04/06/C-Defining-new-types/ - Rewrite Student_info in class type

    In chapter 4, we learned how to organize data with a single data structure:

    -
    struct Student_info{
    std::string name;
    double midterm, final;
    std::vector<double> homework;
    };
    -

    The type Stuent_info holds information including name, midterm, final exam grades and a bunch of homework grades for a student. It help us to easily access the information of one student. Then, we wrote several functions to compute final grades based on the objects of such type. However, our program that uses such data type has several limitations

    -
      -
    1. users of our program have to follow some conventions. If we want to use a newly created object of Student_info, we need to ensure that we have read data into it. If one want to check whether the object contains information, he can only look at the actual data members in it. This requires users knowing well about the iternal structure of such type that the object belongs to. In other words, if I am a user but not a programmer, I need to learn each details about how to implement such type in a program.

      -
    2. -
    3. there is no data protection mechanism. Users might want to keep students’ information unchanged once read in. However, we don’t provide such mechanism in the original program.

      -
    4. -
    5. There is no universal interface for users. Function such as read** is closely connected with a Student_info object as they can change the state of an object. We might like to put them into a single header file for providing convenience for other users. However, we don’t have such structure that provids a universal interface for users.

      -
    6. -
    -

    Now we learn how to deal with these problems with a new data type-class. class type is a mechanism that combines related data values into a data structure. It is an abstract data type that similar to Student_info. But it also provides an interface that allows us to operate an object, e.g. an Student_info, while hiding all deatils of the object.

    -

    For example, we are familar with vector which is a class that provides a set of operations, such as push_back, erase for users. But we don’t know how exactly these functions are implemented.

    -

    Member function

    As analysed above, we may don’t need some details of the students’ information as well as how read function deal with these information. What users need to know is how to use the relevant functions. Therefore, we need to design such interface.

    -
    struct Student_info
    {
    std::string name;
    double midterm, final;
    std::vector<double> homework;

    std::istream & read(std::istream &);
    double grade() const;
    };
    -

    Let’s say we have such an Student_info object named record. It has four data members name, midterm, final and homework. In addition, there are two member functions named read and grade which let us to read a record from an input stream and calculate the final grade for the object. Now, though we didn’t define any such structure before, we can presume how to use it. By the analogy of other class such as vector, I can store information for one student by calling its member function read:

    -
    record.read(cin);
    -

    Again, we can calculate the final grade for record by calling member function grade():

    -
    record.grade();
    -

    Member functions can be defined inside or outside the class definition. Member functions defined inside are implicitly inline to avoid function call overhead. Apparently, member functions above are declared only and defined outside of the class. Let’s look at the read function:

    -
    istream & Student_info::read(istream &in)
    {
    in >> name >> midterm >> final;
    read_hw(in, homework);
    return in;
    }
    - -

    Comparing with the original version:

    -
      -
    1. the name of the function is Student_info::read because it is declared inside of and as a member of Student_info.
    2. -
    3. we don’t need to pass the Student_info object as an argument to the function as the function is a member of Student_info.
    4. -
    5. the member function can access data members directly using name, midterm etc. instead of record.name, record.midterm etc..
    6. -
    -

    Now let’s look at the grade member:

    -
    double Student_info::grade() const
    {
    return ::grade(midterm, final, homework);
    }
    -

    It is similar to the read member in terms of putting :: in front of the name and accessing data members without any qualification. But there are two differences:

    -

    First, putting :: in front of grade when call it means that the grade function is a version that is not a member of class Student_info. Second, we put a qualifier const after the parameter list. What’s that mean? Recalling the original version

    -
    double grade(const Student_info &) {...}
    -

    We pass Student_info object by reference to const to avoid changing the argument to pass. In the new version, we don’t need to pass the Student_info* object any more as the function itself is a member function. Therefore, we add the **const after the parameter list for the same purpose, that is, to avoid changing the state of data members of the Student_info object. Noting that, the class object may not be created with const but is referenced to const in a function. Then, the function treat the object as if it were const.

    -

    Protection

    Though we provide interface for our class, users still can access data members directly and might meddle the implementation unintentionally. Therefore it is probably sensible to restrict users’ rights such that they can only access data members through member functions. The idea behind this process (i.e. data hiding) is called encapsulation. C++ supports encapsulation with two access specifiers(aka. protection lables):

    -
      -
    1. public specifier. Members defined after a keyword public are accessible to all parts of the program. This is typically used to specify members that define the interface to the class.
    2. -
    3. private specifier. Members defined after a keyword private are accessible to the member functions of the class only. This is typically used to specify members that involves the implementation.
    4. -
    -

    Therefore, we can change our class as follows:

    -
    class Student_info 
    {
    public:
    // interface goes here
    double grade() const;
    std::istream & read(std::istream &);

    private:
    // implementation goes here
    std::string name;
    double midterm, final;
    std::vector<double> homework;
    };
    -

    As explained above, We have specified member functions as public and data memmers as private. It has been noted that we use keyword class instead struct to define the class type. Both two keywords are ok for defining a class type. The only difference is the default access level. A class may define members before its first protection lable. if we use class, those members are default private while if we use struct, those members are default public. Accordingly, the class defined above is equivalent to

    -
    class Student_info
    {
    std::string name;
    double midterm, final;
    std::vector<double> homework;

    public:
    double grade() const;
    std::istream & read(std::istream &);
    };
    -

    In general, if we don’t intend to restrict data access, we use struct. But if we intend to have private members, we use class.

    -

    Accessor functions

    Now we have successfully hidden data memebers but the problem next is that We fail to access name as well. We can set member name to public, however, what we really need is only the read access rather than write access. Alternatively, we can write another member function that returns the name but is not allowed to rewrite the name object.

    -
    class Student_info 
    {
    public:
    // interface goes here
    double grade() const;
    std::istream & read(std::istream &);
    std::string name() const { return n; }

    private:
    // implementation goes here
    std::string n;
    double midterm, final;
    std::vector<double> homework;
    };
    - -

    As mentioned above, member functions defined inside are implicitly inline to avoid function call overhead. In this class, we define member name inside the class to imply compiler that this member function should be expanded inline at anywhere it is called.

    -

    Functions such as name are often called accessor functions. It seems that such function breaks the encapsulation that we were trying to achieve. Therefore, such function is privided only when accessors are part of the abtract interface of the class. In this case, the abtraction is that of a student and a corresponding final grade. Obviously, name is part of the abstract interface.

    -

    Accordingly, the compare function changes to:

    -
    bool compare(const Student_info & x, Student_info & y)
    {
    return x.name() < y.name();
    }
    - -

    Testing for empty

    There is another problem we may concern when using such class object. For example, if we call the member grade without calling read first, we would get an exception due to homework is empty. A traditional solution is to catch the exception and let users know what leads to the exception. But again, this may require users knowing about the iternal structure of the class object. Alternatively, we can provide a public member function named valid:

    -
    class Student_info()
    {
    public:
    bool valid() const { return !homework.empty(); }
    // as before
    }
    -

    This member function tells the state of the object: if valid function returns true, it indicates that the object contains valid data, i.e. at least one homework grade; if valid returns false, it indicates the object is invalid for computing the final grades as there is no any homework grade. Users can check the state of the class object before the grade function call, thereby avoiding a potential exception.

    -

    Constructors

    It is known that when we create an object, it should be default initialized or assigned with an appropriate value. For example, when we define a string without an initializer, we get an empty string. So, what happens when we create a class object?

    -

    The class type supports defining how to initialize an class onject through constructors which are special member functions. There is no way to explicitly call constructors. Instead creating an class object automatically calls an appropriate constructor as a side effect.

    -

    If we do not define any constructors, the compiler will synthesize one for us. The synthesized constructor initializes the data member to a value depending on how the object is created. Specifically, if a class object is created as a local variable, then the data members will be default initialized. If a class object is used to initialize a container element, either as a side effect of adding new element to a map, or as the elements of container defined to have a given size, then the member will be value-initialized determined by a class type itself.

    -

    The rules below summarize how a class type initializes its data members:

    -
      -
    1. if an object is of a class type that defines one or more constructors, then the appropriate constructor determines how data memebers will be initialized.

      -
    2. -
    3. if an object is of built-in type, then value-initializing it sets it to zero, and default-initializing it gives it an undefined value.

      -
    4. -
    5. In the case that an object is of a class type that doesn’t define any constructor, value- or default-initializing the object value, or default-initializes each of its data members. This process will be recursive if any data member is of a class type with its own constructor.

      -
    6. -
    -

    In our example, the Student_info class type is the case 3. If we define a local variable that is of such class type, n and homework are default-initialized and concequently yields an empty string and vector respectively. However, default-initializing midterm and final leads to undefined values. To ensure all data members have sensible values at all times, we should define constructors for our class type. Let’s look at two constructors:

    -
    class Student_info 
    {
    public:
    Student_info (); // construct an empty Student_info object
    Student_info (std::istream); // construct one by reading from input stream
    // as before
    }
    -

    We add two public members functions that are both named Student_info , that is, the name of the class type itself, and both have no return type. These two features distinguish constructors from other member functions. It can also be observed that constructors has two different versions, the first constructor of which takes no argument while the second takes input stream object as argument. Corresponding, we can write our code like

    -
    Student_info s;          // an empty Student_info
    Student_info s2(cin); // initialize s2 by reading from cin
    - -

    The default constructor

    The first constructor

    -
    Student_info s;
    -

    is known as default constructor which takes no arguments and ensures that all data members are normally initialized through:

    -
    Student_info::Student_info(): midterm(0), final(0) {}
    -

    This definition uses new syntax: the contents between : and { are a sequence of initializers which initializes each given data member with the value that appears inside of the corresponding parenthese. This default constructor doesn’t explictly initialize n and homework members as they are initialized implicitly. In specific, n is initialized by the string default constructor and homework is initialized by the vector default constructor.

    -

    When we create a new class object, the implementation proceeds following steps:

    -
      -
    1. allocates memory to hold the object
    2. -
    3. initializes the object as the constructor defines
    4. -
    5. executes the constructor body
    6. -
    -

    The implementation initializes every data member of every object even if some members are not explicitly initialized with the constructor initializer list.

    -

    Constructors with arguments

    Student_info::Student_info(istream &is) { read(is); }
    -

    There is no initializer provided for each data member and hence n and homework will be initialized by the default constructors for string and vector, respectively. But midterm and fianl are undefined if the object is default-initialized otherwise are value-initialized to 0. Nevertheless, the function body gives new values to these data members by calling read function.

    -

    Class-based grading program

    Now we have successfully defined a class-based Student_info type. Apparently, using the class is different from using the original structure. Therefore, the last step is to rewrite the main function and organsize files.

    -

    mainfunction.cpp

    -
    #include <algorithm>		// to get the declaration of max, sort
    #include <iomanip> // to get the declaration of setprecision
    #include <iostream> // to get the declaration of streamsize
    #include <stdexcept> // to get the declatation of domain_error
    #include <string> // to get the declaration of string
    #include <vector> // to get the declaration of vector
    #include "Student_info.h" // to get the declaration of Student_info
    #include "grade.h" // to get the declatation of grading functions

    using std::cin; using std::setprecision;
    using std::cout; using std::sort;
    using std::endl; using std::streamsize;
    using std::domain_error; using std::string;
    using std::max; using std::vector;

    int main()
    {
    vector<Student_info> students;
    Student_info record;
    string::size_type maxlen = 0;

    // read and store all the records, and find the length of the longest name
    while(record.read(cin))
    {
    maxlen = max(maxlen, record.name().size());
    students.push_back(record);
    }

    // alphabetize the records
    sort(students.begin(), students.end(), compare);

    // write each line of outpurs
    for (vector<Student_info>::const_iterator it = students.begin();
    it != students.end(); ++it)
    {
    // write the name, blanks
    cout << (*it).name() << string(maxlen + 1 - (*it).name().size(), ' ');

    // compute and write the final grade
    try{
    double final_grade = (*it).grade();
    streamsize prec = cout.precision();
    cout << setprecision(3) << final_grade << setprecision(prec);
    } catch(domain_error e){
    cout << e.what();
    }
    cout << endl;
    }
    return 0;
    }
    - -

    Student_info.h

    -
    #ifndef GUARD_STUDENT_INFO
    #define GUARD_STUDENT_INFO

    #include <string>
    #include <iostream>
    #include <vector>

    class Student_info
    {
    public:
    Student_info (); // default constructor
    Student_info (std::istream &); // constructor with argument
    std::string name() const { return n; } // inline member function return name
    bool valid() const { return !homework.empty(); } // inline member function check state
    std::istream & read(std::istream &); // member function read in data
    double grade() const; // member function calculate final grade

    private:
    std::string n;
    double midterm, final;
    std::vector<double> homework;
    };

    std::istream & read_hw(std::istream &, std::vector<double> &); // nonmember function read data into a vector
    bool compare(const Student_info &, const Student_info &); // nonmember function compare two string

    #endif
    - -

    Student_info.cpp

    -
    #include "Student_info.h"
    #include "grade.h"

    using std::vector; using std::istream;

    // construct an empty Student_info object
    Student_info::Student_info (): midterm(0), final(0) { }

    // construct one by reading from input stream
    Student_info::Student_info (std::istream & in) { read(in); }

    // member function read data from input stream
    std::istream & Student_info::read(std::istream &in)
    {
    // reads and store the student's name, midterm and final exam grades
    in >> n >> midterm >> final;

    // reads and store all homework grades
    read_hw(in, homework);
    return in;
    }

    // member function grade
    double Student_info::grade() const
    {
    return ::grade(midterm, final, homework);
    }

    // nonmember function compare
    bool compare(const Student_info &x, const Student_info &y)
    {
    return x.name() < y.name();
    }

    // nonmember function read_hw
    istream & read_hw(istream &in, vector<double> &hw)
    {
    if (in){
    //get rid of previous contents
    hw.clear();

    // read homework grades
    double x;
    while(in >> x)
    hw.push_back(x);

    // clear the stream so that input will work for the next student
    in.clear();
    }
    return in;
    }
    - -

    grade.h

    -
    #ifndef GUARD_GRADE_H
    #define GUARD_GRADE_H

    #include<vector>

    double grade(double, double, const std::vector<double> &);
    double grade(double, double, double);
    double median(std::vector<double>);
    #endif
    - -

    grade.cpp

    -
    #include <algorithm>
    #include <stdexcept>
    #include <vector>
    #include "grade.h"

    using std::domain_error; using std::istream;
    using std::vector; using std::sort;

    // grade function 1
    double grade(double midterm, double final, const vector<double> &hw)
    {
    if (hw.empty())
    throw domain_error("student has done no homework");
    return grade(midterm, final, median(hw));
    }

    // grade function 2
    double grade(double midterm, double final, double homework)
    {
    return 0.2*midterm + 0.4*final + 0.4*homework;
    }

    // compute the median of vector<double>
    double median(vector<double> vec)
    {
    // check whether the vec is empty
    if (vec.begin() == vec.end())
    throw domain_error("median of an empty vector");

    // sort the grades
    sort(vec.begin(), vec.end());

    // compute the median homework grade
    vector<double>::difference_type size = vec.end() - vec.begin();
    vector<double>::const_iterator mid = vec.begin() + size/2;
    return size % 2 == 0 ? (*mid + *(mid - 1))/2 : *mid;
    }
    - -

    Test

    Inputs

    Robin 90 87 79 88 81 73 45
    Brendan 70 69 88 100 91 75 66
    Arsenii 99 87 89 88 74 90 70
    Liam 83 66 100 76 87 91 78

    Outputs

    Arsenii 89.8
    Brendan 76.8
    Liam 77.8
    Robin 84.4
    - -

    The program works as same as the original program and delivers same results.

    -]]>
    - - Programming - - - C++ - Notes - -
    - - Accelerated C++ Solutions to Exercises(Chapter 8 Part 2) - /2018/04/04/Accelerated-C-Solutions-to-Exercises-Chapter-8-Part-2/ - Exercise 8-3

    As we learned in §4.1.4/58, it can be expensive to return (or pass) a container by value. Yet the median function that we wrote in §8.1.1/140 passes the vector by value. Could we rewrite the median function to operate on iterators instead of passing the vector? If we did so, what would you expect the performance impact to be?

    -

    Solution & Results

    Yes, we can rewrite the median function without passing the vector. See the median funtion template below

    -
    // median function template
    template<class In>
    typename In::value_type median(In begin, In end)
    {
    if(begin == end)
    throw domain_error("median of an empty vector");

    sort(begin, end);

    typename In::difference_type size = end - begin;
    In mid = begin + size/2;
    return size % 2 == 0 ? (*mid + *(mid - 1))/2 : *mid;
    }
    -

    The type parameter of the function template is In which will be infered from two arguments, i.e. two iterators that denote the range of a sequence. In addition, The Iterator::value_type gives the type of the element the iterator can point to. Therefore, we can set the return type using

    -
    typename In::value_type
    -

    If the container is vector, this expression is equivalent to

    -
    vector<double>::iterator::value_type
    -

    In the original version, all computations are based on values of type size_type. But now we don’t have such information anymore. Instead we can do computations with iterators. Firstly, we checks whether the container is empty using begin == end. The sort function is applied as same as that in the original version. Then we get the “size” of the container using end - begin, which gives the distance between the first element and one past the last elements. The result of such computation has type difference_type which is in fact a signed integer value.

    -

    Once we have the “size”, we can get the position of the mid point. Finally we can compute the median by accessing elements via dereference operation on iterators. One might wonder that why we get the position of the mid point using begin + size/2 rather than (begin + end)/2? This is because iterators doesn’t support additive operation between two iterators, but they (random access iterators) do support additive operation between an iterator and an integer value.

    -

    Now let’s perform following test and check what happens:

    -

    test program

    -
    #include <iostream>	// to get the declaration of cout, endl
    #include <vector> // to get the declaration of vector
    #include <stdexcept> // to get the declaration of domain_error
    #include <algorithm> // to get the declaration of sort

    using std::cout; using std::vector;
    using std::endl; using std::domain_error;
    using std::sort;

    // print function template
    template<class T>
    void print(T &contianer)
    {
    cout << *(contianer.begin());
    for(typename T::const_iterator i = contianer.begin() + 1; i != contianer.end(); ++i)
    cout << ' ' << *i;
    cout << endl;
    }

    // median function template
    template<class In>
    typename In::value_type median(In begin, In end)
    {
    if(begin == end)
    throw domain_error("median of an empty vector");

    sort(begin, end);
    typename In::difference_type size = end - begin;
    In mid = begin + size/2;

    return size % 2 == 0 ? (*mid + *(mid - 1))/2 : *mid;
    }

    int main()
    {
    // print a sequence
    vector<double> vec{9, 6, 8, 3, 5, 7, 1, 2};

    cout << "Original sequence: ";
    print(vec);

    // get the median of the original sequence
    cout << "The median value is: " << median(vec.begin(), vec.end()) << endl;

    // print the new sequence
    cout << "Now the sequence becomes: ";
    print(vec);
    return 0;
    }
    - -

    results

    -
    Original sequence: 9 6 8 3 5 7 1 2
    The median value is: 5.5
    Now the sequence becomes: 1 2 3 5 6 7 8 9
    -

    The results show that we successfully compute the median value but change the original sequence due to operate on the original container directly. This is the cost that calling by reference instead of calling by value.

    -

    Exercise 8-4

    Implement the swap function that we used in §8.2.5/148. Why did we call swap rather than exchange the values of *beg and *end directly? Hint: Try it and see.

    -

    Solution & Results

    swap(*begin++, *end);
    -

    To swap two elements, generally we can write

    -
    T temp = *begin;
    *begin = *end;
    *end = x;
    ++begin;
    -

    where T is the type of the elements begin refers to.
    Clearly, the key to solution is how to determine T. One way is to deduce the type from the varaible automatically using auto (or decltype) specifier. Another way is to use value_type to get the type of the element the iterator can point to. I tried both two methods and the test program is shown below

    -
    #include <iostream>		// to get the declaration of cout, endl
    #include <algorithm> // to get the declaration of swap
    #include <vector> // to get the declaration of vector

    using std::cout; using std::endl;
    using std::vector; using std::swap;

    // print function template
    template <class T>
    void print(T &t)
    {
    for (typename T::const_iterator i = t.begin(); i != t.end(); ++i)
    cout << *i << endl;
    }

    // original reverse function template
    template<class Bi>
    void reverse_ori(Bi begin, Bi end)
    {
    while (begin != end)
    {
    --end;
    if (begin != end)
    {
    swap(*begin++, *end);
    }

    }
    }

    // revised reverse function version 1: using auto
    template<class Bi>
    void reverse_v1(Bi begin, Bi end)
    {
    while (begin != end)
    {
    --end;
    if (begin != end)
    {
    auto temp = *begin;
    *begin = *end;
    *end = temp;
    ++begin;
    }

    }
    }

    // revised reverse function version 2: using value_type
    template<class Bi>
    void reverse_v2(Bi begin, Bi end)
    {
    typedef typename Bi::value_type valueType;
    while (begin != end)
    {
    --end;
    if (begin != end)
    {
    valueType temp = *begin;
    *begin = *end;
    *end = temp;
    ++begin;
    }

    }
    }

    int main()
    {
    vector<int> vec{1, 2, 3, 4, 5, 6, 7, 8, 9, 10};

    // reverse
    reverse_ori(vec.begin(), vec.end());
    print(vec);

    // reverse again
    cout << endl;
    reverse_v1(vec.begin(), vec.end());
    print(vec);

    // reverse again
    cout << endl;
    reverse_v2(vec.begin(), vec.end());
    print(vec);

    return 0;
    }
    -

    This program gives following outputs

    -
    10
    9
    8
    7
    6
    5
    4
    3
    2
    1

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10

    10
    9
    8
    7
    6
    5
    4
    3
    2
    1
    -

    We have seen that both three method works fine, but why we use swap? The major limitation of other two methods is that they both copies objects to and from a variable, which may be not allowed for some highlevel objects. Also, it is not inefficient to do if we can avoid performing copies. The sandard swap algorithm is also an template and hence can determine the elements type automatically. As far as I know, it doesn’t rely on such temp variable as other two methods. I’ll give more explanations about swap once I know how exactly it works.

    -

    Exercise 8-5

    Reimplement the gen_sentence and xref functions from Chapter 7 to use output iterators rather than writing their output directly to a vector . Test these new versions by writing programs that attach the output iterator directly to the standard output,and by storing the results in a list and a vector.

    -

    Solution & Results

    To be updated.

    -

    Exercise 8-6

    Suppose that m has type map<int, string> , and that we encounter a call to copy(m.begin(), m.end(), back_inserter(x)). What can we say about the type of x ? What if the call were copy(x.begin(), x.end(), back_inserter(m)) instead?

    -

    Solution & Results

    copy(m.begin(), m.end(), back_inserter(x));
    -

    This statement copy all elements of m into the destination sequence x via back_inserter. It is known that x should support push_back member function. In addition, the elements contained in m have type pair<int, string>. Therefore, x should be a container that has type c<pair<int, string>>. Standard contaniers that meet the first condition include vector, list, deque, string. But string can only store string literals. In summary, I presume that x can be

    -
      -
    1. vector<pair<int, string>>
    2. -
    3. list<pair<int, string>>
    4. -
    5. deque<pair<int, string>>
    6. -
    -

    To verify my expectation, I write a test program shown as below:

    -
    #include <iostream>		// to get the declaration of cout, endl
    #include <vector> // to get the declaration of vector
    #include <string> // to get the declaration of string
    #include <list> // to get the declaration of list
    #include <deque> // to get the declaration of deque
    #include <map> // to get the declaration of map
    #include <algorithm> // to get the declaration of copy
    #include <iterator> // to get the declaration of back_inserter
    #include <utility> // to get the declaration of pair

    using std::cout; using std::copy;
    using std::endl; using std::vector;
    using std::map; using std::string;
    using std::list; using std::pair;
    using std::deque; using std::back_inserter;

    template <class T>
    void print(T &t)
    {
    for (typename T::const_iterator i = t.begin(); i != t.end(); ++i)
    cout << i->first << ' ' << i->second << endl;
    }

    int main()
    {
    typedef pair<int, string> element;
    vector<element> x;
    list<element> y;
    deque<element> z;
    map<int, string> m{{1, "abc"}, {2, "bcd"}, {3, "def"}, {4, "hig"}, {5, "klm"}};
    copy(m.begin(), m.end(), back_inserter(x));
    copy(m.begin(), m.end(), back_inserter(y));
    copy(m.begin(), m.end(), back_inserter(z));

    print(x);
    cout << endl;
    print(y);
    cout << endl;
    print(z);

    return 0;
    }
    -

    The results show that all these three types of container work fine for the copy algorithm.

    -
    1 abc
    2 bcd
    3 def
    4 hig
    5 klm

    1 abc
    2 bcd
    3 def
    4 hig
    5 klm

    1 abc
    2 bcd
    3 def
    4 hig
    5 klm
    - -

    For the second function call

    -
    copy(x.begin(), x.end(), back_inserter(m));
    -

    I presume it doesn’t work because map doesn’t have member function push_back. We can correct this function call as

    -
    copy(x.begin(), x.end(), inserter(m, m.end());
    -

    If we add follow statements to above program

    -

    vector<element> n{{10, "abcd"}, {20, "bcde"}, {30, "defh"}};
    map<int, string>::iterator it = m.end();
    copy(n.begin(), n.end(), inserter(m, it));
    print(m);
    -

    The modified program gives results as expected

    -
    1 abc
    2 bcd
    3 def
    4 hig
    5 klm
    10 abcd
    20 bcde
    30 defh
    -

    Noting that, though we insert at the position where m.end() denotes, the new element may not appear starting from the end of the map. This is because that the map automatically sorts elements according to the values of keys.

    -
    -

    Exercise 8-7

    Why doesn’t the max function use two template parameters, one for each argument type?

    -

    Solution & Results

    Let’s write the max function using two template parameters:

    -
    template<class T, class P>
    P max(const T& left, const P& right)
    {
    return left > right ? left : right;
    }
    -

    The biggest problem is how can we correctly set the return type. There are two types T and P. If we set return type as T, the value returned may has type P, and vice versa. Concequently, type coversion happens. However, the result of type conversion depends on the range of the values that the types permit. For example, we call the max function as follows

    -
    int main()
    {
    cout << max(3500.9, 'a');
    }
    -

    If we set return type P, what happens of this program is unknown.

    -

    Therefore, such function template is non-practical and may lead to fatal errors.

    -
    -

    Exercise 8-8

    n the binary_search function in §8.2.6/148, why didn’t we write (begin + end)/ 2 instead of the more complicated begin + (end - begin) /2 ?

    -

    Solution & Results

    This is because that iterators doesn’t support additive operation between two iterators. For random access iterators, they do support arithmetic operator + and - between an iterator and an ineger value, e.g.

    -
    iterator_i + integer_j
    integer_j + iterator_i
    iterator_i - integer_j
    -

    In addition, they support substracting an iterator from another, e.g.

    -
    iterator_i - iterator_j
    -

    Such operation yields an value of type difference_type, which in fact is an signed integer. Therefore, it is legal to do

    -
    begin + (end - begin) /2
    -

    as such expression is in fact the additive operation between an iterator and an iteger value.

    -

    But it makes no sence to do

    -
    (begin + end)/ 2
    - -
    -

    Reference

    Koenig, A. and Moo, B.E., 2000. Accelerated C++.

    -

    Lippman, S.B., Lajoie, J. and Moo, B.E., 2013. C++ Primer, 5th edt.

    -]]>
    - - Programming - - - Accelerated C++ Solutions - -
    - - Accelerated C++ Solutions to Exercises(Chapter 8 Part 1) - /2018/04/03/Accelerated-C-Solutions-to-Exercises-Chapter-8/ - Exercise 8-0

    Compile, execute, and test the programs in this chapter.

    -

    Solution & Results

    Please find the programs and analysis on Writing generic functions.

    -

    Exercise 8-1

    Note that the various analysis functions we wrote in §6.2/110 share the same behavior; they differ only in terms of the functions they call to calculate the final grade. Write a template function, parameterized by the type of the grading function, and use that function to evaluate the grading schemes.

    -

    Solution & Results

    To parameterize the type of the grading function, I define the type parameter of the analysis function template as Fun. Then what we need to do is replacing all revelant functions with f which is a variable that has type Fun. See the function template below:

    -

    New grades_analysis.h

    -
    #ifndef GUARD_GRADES_ANALYSIS_H
    #define GUARD_GRADES_ANALYSIS_H

    #include <vector>
    #include <algorithm>
    #include "Student_info.h"
    #include "gradingSchemes.h"

    template <class Fun>
    double analysis(const std::vector<Student_info> &students, Fun &f)
    {
    std::vector<double> grades;

    std::transform(students.begin(), students.end(), std::back_inserter(grades), f);
    return median(grades);
    }
    #endif /* GUARD_GRADES_ANALYSIS_H */
    -

    The next step is to rewrite the write_analysis function as the original one defines a parameter to take various analysis functions.

    -
    #ifndef GUARD_WRITE_ANALYSIS_H
    #define GUARD_WRITE_ANALYSIS_H

    #include <iostream>
    #include <string>
    #include <vector>
    #include "Student_info.h"
    #include "grades_analysis.h"

    template <class Fun>
    void write_analysis(std::ostream &out, const std::string &name, Fun &f,
    const std::vector<Student_info> &did,
    const std::vector<Student_info> &didnt)
    {
    out << name << ": median(did) = " << analysis(did, f)
    << ": median(didnt) = " << analysis(didnt, f) << std::endl;
    }
    #endif /* GUARD_WRITE_ANALYSIS_H */
    - -

    Its necessary to put both declaration and definition into the header file for both two function templates due to the fact that they are required to be visible to the compiler in the point of instantiation.

    -

    Now we can pass different grading functions directly to the write_analysis function to generate analysis reports.

    -

    mainfunction.cpp

    -
    #include <iostream>			// to get the declaration of cin, cout, endl
    #include <vector> // to get the declaration of vector
    #include "Student_info.h" // to get the declaration of Student_info
    #include "did_all_hw.h" // to get the declatation of the predicate on students' records
    #include "write_analysis.h" // to get the declaration of write_analysis function
    #include "gradingSchemes.h" // to get the declarations of various grading functions

    using std::vector; using std::cout;
    using std::cin; using std::endl;

    int main()
    {
    // students who did and didn't do all their homework
    vector<Student_info> did, didnt;

    // read the student records and partition time
    Student_info student;
    while(read(cin, student))
    {
    if(did_all_hw(student))
    did.push_back(student);
    else
    didnt.push_back(student);
    }
    // verify thatthe analyses will show us something
    if(did.empty())
    {
    cout << "No student did all the homework!" << endl;
    return 1;
    }
    if(didnt.empty())
    {
    cout << "Every student did all the homework!" << endl;
    return 1;
    }

    // do the analysis
    write_analysis(cout, "median", median_grade_aux, did, didnt);
    write_analysis(cout, "average", average_grade, did, didnt);
    write_analysis(cout, "median of homework turned in", optimistic_median, did, didnt);

    return 0;
    }
    -

    Other files are keep unchanged and can be found below.

    -

    did_all_hw.cpp

    -
    #include <algorithm>		// to get the declaration of find
    #include "Student_info.h" // to get the declaration of Student_info
    #include "did_all_hw.h" // to get the declaration of did_all_hw itself

    using std::find;

    bool did_all_hw(const Student_info &s)
    {
    return ((find(s.homework.begin(), s.homework.end(), 0)) == s.homework.end());
    }
    - -

    did_all_hw.h

    -
    #ifndef GUARD_DID_ALL_HW_H
    #define GUARD_DID_ALL_HW_H

    #include "Student_info.h"

    bool did_all_hw(const Student_info &s);

    #endif /* GUARD_DID_ALL_HW_H */
    -

    gradingSchemes.cpp

    -
    #include <algorithm>		// to get the declaration of remove_copy
    #include <numeric> // to get the declaration of accumulate
    #include <stdexcept> // to get the declaration of domain_error
    #include <vector> // to get the declaration of vector
    #include "Student_info.h" // to get the declaration of Student_info
    #include "gradingSchemes.h" // to get the declaration of all functions here to keep consistent

    using std::domain_error; using std::istream;
    using std::vector; using std::sort;
    using std::accumulate;

    // final grade function returns weighted average of midterm exam grade,
    // final exam grade, and homework grade which will be computed
    // using different methods depending on grading schemes
    double grade(double midterm, double final, double homework)
    {
    return 0.2*midterm + 0.4*final + 0.4*homework;
    }

    // fundermental functions 1: returns the median value of vector<double>
    double median(vector<double> vec)
    {
    // get the size of the vector
    typedef vector<double>::size_type vec_size;
    vec_size size = vec.size();

    // check whether the empty is empty
    if (size == 0)
    throw domain_error("median of an empty vector");

    // sort the grades
    sort(vec.begin(), vec.end());

    // compute the median homework grade
    vec_size mid = size/2;
    return size % 2 == 0 ? (vec[mid] + vec[mid - 1])/2 : vec[mid];
    }

    // fundermental functions 2: returns the average value of vector<double>
    double average(const vector<double> &v)
    {
    // check whether the empty is empty
    if (v.empty())
    { throw domain_error("average of an empty vector");}

    return accumulate(v.begin(), v.end(), 0.0) / v.size();
    }


    // grading scheme 1: final grade is based on the median homework grade
    double median_grade(const Student_info &s)
    {
    return median_grade(s.midterm, s.final, s.homework);
    }

    // grading scheme 1: overloaded median_grade function
    double median_grade(double midterm, double final, const vector<double> &hw)
    {
    if (hw.size() == 0)
    throw domain_error("student has done no homework");
    return grade(midterm, final, median(hw));
    }

    // grading scheme 1: auxiliary median_grade function
    double median_grade_aux(const Student_info &s)
    {
    try{
    return median_grade(s);
    }catch (domain_error){
    // students who did no homework at all, get 0 homework grade
    return grade(s.midterm, s.final, 0);
    }
    }

    // grading scheme 2: final grade is based on average homework grades
    double average_grade(const Student_info &s)
    {
    try{
    return grade(s.midterm, s.final, average(s.homework));
    }catch (domain_error){
    // students who did no homework at all, get 0 homework grade
    return grade(s.midterm, s.final, 0);
    }
    }

    // grading scheme 3: final grade is based on median of the completed homework grades,
    // and students who did no homework at all will get 0 homework grade
    double optimistic_median(const Student_info &s)
    {
    vector<double> nonzero;
    remove_copy(s.homework.begin(), s.homework.end(), back_inserter(nonzero), 0);

    if(nonzero.empty())
    return grade(s.midterm, s.final, 0);
    else
    return grade(s.midterm, s.final, median(nonzero));
    }
    - -

    gradingSchemes.h

    -
    #ifndef GUARD_GRADING_SCHEMES_H
    #define GUARD_GRADING_SCHEMES_H

    #include<vector>
    #include "Student_info.h"

    double grade(double midterm, double final, double homework);
    double median(std::vector<double> vec);
    double average(const std::vector<double> &v);
    double median_grade(const Student_info &s);
    double median_grade(double midterm, double final, const std::vector<double> &hw);
    double median_grade_aux(const Student_info &s);
    double average_grade(const Student_info &s);
    double optimistic_median(const Student_info &s);

    #endif /* GUARD_GRADING_SCHEMES_H */
    - -

    Student_info.cpp

    -
    #include "Student_info.h"
    using std::vector; using std::istream;

    // argument to the function sort
    bool compare(const Student_info &x, const Student_info &y)
    {
    return x.name < y.name;
    }

    // read the info
    istream & read(istream &is, Student_info &s)
    {
    // reads and store the student's name, midterm and final exam grades
    is >> s.name >> s.midterm >> s.final;

    // reads and store all homework grades
    read_hw(is, s.homework);
    return is;
    }

    // read all homework grades
    istream & read_hw(istream &in, vector<double> &hw)
    {
    if (in){
    //get rid of previous contents
    hw.clear();

    // read homework grades
    double x;
    while(in >> x)
    hw.push_back(x);

    // clear the stream so that input will work for the next student
    in.clear();
    }
    return in;
    }
    - -

    Student_info.h

    -
    #ifndef GUARD_Student_info
    #define GUARD_Student_info

    #include<iostream>
    #include<string>
    #include<vector>

    struct Student_info{
    std::string name;
    double midterm, final;
    std::vector<double> homework;
    };

    bool compare(const Student_info &, const Student_info &);
    std::istream & read(std::istream &, Student_info &);
    std::istream & read_hw(std::istream &, std::vector<double> &);
    #endif
    - -

    I tested this program using the same inputs as we did in A simple test. It gives the same results as the original progam

    -
    median: median(did) = 46.1475: median(didnt) = 42.9273
    average: median(did) = 45.4202: median(didnt) = 44.3273
    median of homework turned in: median(did) = 46.1475: median(didnt) = 52.1273
    ```

    # Exercise 8-2
    Implement the following library algorithms, which we used in Chapter 6 and describedin §6.5/121. Specify what kinds of iterators they require. Try to minimize the number of distinct iterator operations that each function requires. After you have finished your implementation, see §B.3/321 to see how well you did.
    -

    equal(b, e, d) search(b, e, b2, e2)
    find(b, e, t) find_if(b, e, p)
    copy(b, e, d) remove_copy(b, e, d, t)
    remove_copy_if(b, e, d, p) remove(b, e, t)
    transform(b, e, d, f) partition(b, e, p)
    accumulate(b, e, t)

    -
    
    -## Solution & Results
    -To be updated. 
    -
    -
    -
    ]]>
    - - Programming - - - Accelerated C++ Solutions - -
    - - C++ - Writing generic functions - /2018/04/02/C-Writing-generic-functions/ - Generic function - an example

    Generic functions are functions written in a way that is independent of any particular type. When we use a generic program, we supply the type(s) or value(s) on which that instance of the program will operate(Lippman etc. 2012). In C++, we can create generic functions by defining template functions, allowing writing a single definition for a family of functions or types that behave similarly but have different types of paratmters. The only difference can be summarized as parameters, namely, template parameters. Take the median function as an example, its template can be defined as follows.

    -
    template<class T>
    T median(vector<T> v)
    {
    typedef typename vector<T>::size_type vec_sz;

    vec_sz size = v.size();
    if(size == 0)
    throw domain_error("median of an empty vector");

    sort(v.begin(), v.end());

    vec_sz mid = size/2;

    return size % 2 ? (v[mid] + v[mid - 1])/2 : v[mid];
    }
    -

    From the definition, we observe

    -
      -
    1. A template starts with keyword template followed by type parameter(s) enclosed by a pair of angle brackets.
    2. -
    3. type parameter(s) define the names that can be used within the scope of the function. They refer to types, not variables. In other words, the types of template parameters can be regarded as variables that have types specified by type parameters(s).
    4. -
    5. type parameter(s) are specified following the keyword class or typename.
    6. -
    7. the template tells implementation that vector::size_type is the name of a type by adding keyword typename before.
    8. -
    -

    When we call median and pass a template argument vector, the implementation will create and compile an instance of the function as if the function median(vector). This is so called template instantiation. But how is the template compiled?

    -

    When the compiler encounters the definition of a template, it doesn’t generate code. It generates only when the template is instantiated. Remembering that when we call a function, we need to supply only its declaration. But if we want to call the template, we need to supply not only its declaration but also its definition in order to instantiate it. Therefore, the header file of a template generally includes the source file also via a #include or directly. I found an article How To Organize Template Source Code, where gives detailed explinations on how to organize template source code and three solutions.

    -

    Due to the feature of generating code during instantiation, compilation errors may occur during three stages:

    -
      -
    1. the first stage is when we compile the template itself. But the compiler can only detect some syntax errors like forgetting a semicolon, misspelling a variable name etc..
    2. -
    3. the second stage is when we use a template, before it is instantiated. The compiler typically will check that whether the number of the arguments is appropriate, whether two arguments that are supposed to have the same type do so.
    4. -
    5. the type-related errors mostly occurs during the third stage when the template is instantiated. Though implementations manage instantiation on their own ways, the type-related errors may be reported at link time due to that implementations assume the type is possibly defined in other unit and hence leave it to linker to resolve.
    6. -
    -

    In fact, we have applied many templates in previous programs, such as vector and list, and all standard library algorithms.

    -

    Algorithms and iterators

    As shown in above example, the template is type independent. However, it is also clear that the function to instantiate requires its argument supporting all operations included in the function. In above case, the types stored in the vectors that are passed to the median function must support addition and division.

    -

    The standard algorithms are not limited by the type of containers and obviously are data-structure independent function templates. It is known that the parameters taken by algorithms includes iterators and others. This implies that iterators to pass should support certain operations used inside of the algorithms. But we also know that some containers may support operations that others do not. For example, vector support random access elements via iterators while list doesn’t. Therefore, it is important to restrict the right of iterators depending on operations that an algorithm include and operations different containers support. For this reason, the library defines five iterator categories with specifying the collection of iterator operations for each category. By doing so, we know exactly what effect of an algorithm can have on an container and which container can use which algorithms.

    -

    Sequential read-only access

    Some algorithms only require iterators that can access elements sequentially. For example, the find algorithm:

    -
    template <class In, class X>
    In find(In beg, In end, const X &x)
    {
    while (begin != end && *begin != x)
    ++begin;
    return begin;
    }
    -

    The iterators of type In access elements sequentially via
    operations *, ++. Also, they can be compared using !=. Alternatively,

    -
    template <class In, class X>
    In find(In begin, In end, const X &x)
    {
    if (begin == end || *begin == x)
    return begin;
    begin++;
    return find(begin, end, x);
    }
    -

    This version of find uses another strategy, recursively calling itself. We have observed that the iterators may also need to support operations ++(postfix) and ==.

    -

    Furthermore, the iterators ought to support member access via ->, e.g. it->first. It has the same effect as (*it).first.

    -

    In summary, iterators that offers sequential read-only access to elements of a container should supports ++(both prefix and postfix), == and !=, * and ->. Such iterators are named as input iterators. All standard container meet the requirements of input iterator.

    -

    Sequential write-only access

    Some algorithms may require write elements of a sequence via iterators, for example

    -
    template <class In, class Out>
    Out copy(In begin, In end, Out dest)
    {
    while(begin != end)
    *dest++ = *begin++;
    return dest;
    }
    -

    The copy algorithm takes three iterators, the first two of which denote the range of a sequence to copy while the third iterator denotes the initial position of the destination. The iterators of type In offer sequential read-only access to elements and hence are input iterators. The type Out gives the third iterator the ability to write elements via *dest = and dest++. As with the find algorithm above, such iterators should also support ++dest.

    -

    The fact that iterators of type Out are used for output only also implies that

    -
      -
    1. each element pointed by the iterator is written a value only once and then the iterator is incremented.

      -
    2. -
    3. the iterator can not be incremented twice without assignments to the elements that it refers to.

      -
    4. -
    -

    In summary, such iterators are output iterators. All standard containers as well as back_inserter meet the requirements of output iterator. Noting that in this function, the left side deference operator is used to write to the underlying elements while the right side deference operator is used to read the underlying elements only.

    -

    Sequential read-write access

    There is another situation that we want to both read and write the elements of a sequence, but only sequentially. For example

    -
    template<class For, class X>
    void replace(For beg, For end, const X &x, const X &y)
    {
    while (beg != end)
    {
    if (*beg == x)
    *beg = y;
    ++beg;
    }
    }
    -

    The replace algorithm examines each element in the range [beg, end) and assigns value y to those elements that are equal to x. Apparently, iterators of type For should support all operations supported by an input operator as well as an output operator. Moreover, they can read and write the same elements multiple times. Such a type is a forward iterator. Some operations that such a type need to support are

    -
      -
    1. *it (for both reading and writing)
    2. -
    3. ++ (both prefix and postfix)
    4. -
    5. == and !=
    6. -
    7. ->
    8. -
    -

    All standard containers meet the requirements of forward iterator.

    -

    Reversible access

    All above iterators access elements in a container in a forward sequence. But some functions may require get elements in reverse order, for example

    -
    template <class Bi> void reverse(Bi begin, Bi end)
    {
    while (begin != end)
    {
    --end;
    if (begin != end)
    swap (*begin++, *end);
    }
    }
    -

    The iterators of type Bi moves backward from end to begin via . Then, the swap algorithm exchanges values of two elements. Such iterators meet all requirements of forward iterator and support (both prefix and postfix). They are bidirectional iterators. All standard containers meet the requirements of bidirectional iterators.

    -

    Random access

    All above iterators access elements in a forward or backward sequence, however, some functions need to access elements starting from arbitrary positions. For example

    -
    template<class Ran, class X>
    bool binary_search(Ran begin, Ran end, const X &x)
    {
    while (begin < end)
    {
    // find the midpoint of the range
    Ran mid = begin + (end - begin)/2;

    // see which part of the range contains x; keep looking only in that part
    if (x < *mid)
    end = mid;
    else if (*mid <x)
    begin = mid + 1;

    // if we got here, then *mid == x so we are done
    else
    return true;
    }
    return false;
    }
    -

    The binary search algorithm looks for a particular element in a sorted container. It always starts to search from the middle point of a sequence, which relies on the ability to do arithmetic on iterators. Such an iterator is called a random access iterator. Specifically, if p and q are random access iterators, they should support all operations that a bidirectional iterator supports, as well as following arithmetic operations

    -
      -
    1. p + n, p - n, n + p
    2. -
    3. p-q
    4. -
    5. p[n] (equivalent to *(p + n))
    6. -
    7. p > q, p < q, p <= q, p >= q
    8. -
    -

    Standard sort algorithm requires random-access iterators. Therefore, vector and string can use standard sort function as their iterators are random-access iterators. list iterators are not random-access iterators and hence list defines its own member sort instead of using the standard sort.

    -

    Summary

    From above analysis, we know that forward, bidirectional and random-access iterators are also valid input iterators as they all meet the requirements of input iterator. In addition, all forward, bidirectional and random-access iterators that are not constant iterators are also valid output iterators.

    -

    From the perspective of containers, different category of iterator makes some containers distinct, for example, list iterators are bidirectional iterators, forward_list iterators are forward_iterators, vector and string iterators are random access iterators.

    -

    But why we need input/output iterator? One reason is that not all iterators are assicoated with containers. For example, back_inserter() is an iterator that meet and only meet the requirements of output iterator.

    -

    Another typical example is that the standard library provides iterators that can be bound to input and output streams, namely, istream_iterator and ostream_iterator.
    Apparently, istream_iterator is input iterator that allows us to read successive elements from an input stream.
    ostream_iterator is output iterator that allows us to write sequentially to an output stream.

    -
    vector<int> v;
    // read ints from the standard input and append them to v
    copy(istream_iterator<int>(cin), istream_iterator<int>(), back_insert(v));
    -

    This example shows that the first istream_iterator is bound to cin and expects to read values of type int. But the second istream_iterator is not bound to any file.
    This is because istream_iterator type has a default value with a sepcial property such that any istream_iterator that has reached end-of-file or is an error state will appear to be equal to the default value. Therefore, we can use the default value of a istream_iterator together with the first input iterator to denote the sequence in the input stream.

    -

    Similarly, we can use ostream_iterator to write elements to an output stream.

    -
    // write the elements of v each separated from the other by a space
    copy(v.begin(), v.end(), ostream_iterator<int> (cout, " "));
    -

    This statement uses copy algorithm to copy the vector v onto the standard output. ostream_iterator is bound to cout with an additional argument, that is, a space in this case. This additional argument specifies a value to be written after each element and typically is a string literal.

    -

    Rewrite the split function

    Now we apply the new knowledge learned above to rewrite the split function as a template such that it is data-structure independent.

    -
    template <class Out>
    void split(const string &str, Out os)
    {
    typedef string::const_iterator iter;

    iter i = str.begin();
    while(i != str.end())
    {
    // ignore leading spaces
    i = find_if(i, str.end(), not_space);

    // find end of next word
    iter j = find_if(i, str.end(), space);

    // copy the characters in [i,j)
    if(i != str.end())
    *os++ = string(i, j);
    }
    }
    -

    This function has return type void, i.e. nothing to return. If we want to store each word contained in a line of inputs into a vector, we just need to pass an output iterator. This is also true for a list. For example

    -
    int main()
    {
    // list<string> words;
    vector<string> words;

    string line;
    while(getline(cin, line))
    {
    split(line, back_inserter(words));
    }

    for(vector<string>::const_iterator it = words.begin(); it != words.end(); ++it)
    cout << *it << endl;
    return 0;
    }
    -

    Alternatively, we may write all words onto the standard output directly. I take this as an exercise and present a complete program below.

    -

    mainfunction.cpp

    -
    #include <iostream>
    #include <iterator>
    #include <string>
    #include "split.h"

    using std::cin; using std::ostream_iterator;
    using std::cout; using std::string;
    using std::endl; using std::back_inserter;

    int main(){

    string line;
    while (getline(cin, line))
    {
    split(line, ostream_iterator<string>(cout, "\n"));
    }

    return 0;
    }
    - -

    split.h

    -
    #ifndef GUARD_SPLIT_H
    #define GUARD_SPLIT_H

    #include <string>
    #include <algorithm>

    // true if the argument is whitespace, false otherwise
    bool space(char c)
    {
    return isspace(c);
    }

    // false is the argument is whitespace, true otherwise
    bool not_space(char c)
    {
    return !isspace(c);
    }

    // template declaration and definition
    template <class Out>
    void split(const std::string &str, Out os)
    {
    typedef std::string::const_iterator iter;

    iter i = str.begin();
    while(i != str.end())
    {
    // ignore leading spaces
    i = std::find_if(i, str.end(), not_space);

    // find end of next word
    iter j = std::find_if(i, str.end(), space);

    // copy the characters in [i,j)
    if(i != str.end())
    *os++ = std::string(i, j);
    i = j;
    }
    }
    #endif /* GUARD_SPLIT_H */
    - -

    Noting that I didn’t separate the definition and declaration of the split template to make them be visible to compiler in the point of instantiation.

    -

    Let’s type some words

    -
    what 
    a
    beautiful
    name
    -

    The results are as expected

    -
    what
    what
    a
    a
    beautiful
    beautiful
    name
    name
    -

    Again type

    -
    what a beautiful name
    -

    The program gives

    -
    what
    a
    beautiful
    name
    ]]>
    - - Programming - - - C++ - Notes - -
    - - Accelerated C++ Solutions to Exercises(Chapter 7 Part 2) - /2018/03/31/Accelerated-C-Solutions-to-Exercises-Chapter-7-Part-2/ - Exercise 7-5

    Reimplement the grammar program using a list as the data structure in which webuild the sentence.

    -

    Solutions & Results

    There is no any other differences between the list-based version and the vector-based version except that we replace vectors with lists literally, and hence No further discussion about this exercise. The original program can be found here Example 3.

    -

    Exercise 7-6

    Reimplement the gen_sentence program using two vectors: One will hold the fullyunwound, generated sentence, and the other will hold the rules and will be used as a stack.Do not use any recursive calls.

    -

    Solution & Results

    To be updated.

    -

    Exercise 7-7

    Change the driver for the cross-reference program so that it writes line if there is only one line and lines otherwise.

    -

    Solution & Results

    The solution is to add and check a condition that whether the vector where holds line numbers only contain one line number. If there is only one line number, we use line else use lines.

    -
    if ((it->second).size() == 1)
    cout << "line:" << endl;
    else
    cout << "lines:" << endl;
    -

    I only present the revised file here and please find other file in Exercise 7-4.

    -

    mainfunction.cpp

    -
    #include <map>		// to get the declaration of map
    #include <string> // to get the declaration of string
    #include <vector> // to get the declaration of vector
    #include <iostream> // to get the declaration of cin, cout, endl;
    #include <sstream> // to get the declaration of ostringstream
    #include <cctype> // to get the declaration of isspace
    #include "split.h" // to get the declaration of function split
    #include "xref.h" // to get the declaration of xref

    using std::map; using std::cout;
    using std::cin; using std::endl;
    using std::string; using std::vector;
    using std:: ostringstream; using std::isspace;

    int main()
    {
    // call xret using split by default
    map<string, vector<int> > ret = xref(cin);

    // set the length for each line of outputs
    string::size_type line_length = 20;

    // write the result
    for(map<string, vector<int> >::const_iterator it = ret.begin(); it != ret.end(); ++it)
    {
    // write the word
    cout << it->first << " occurs on " << endl;

    if ((it->second).size() == 1)
    cout << "line:" << endl;
    else
    cout << "lines:" << endl;

    // followed by one or more line numbers
    vector<int>::const_iterator line_it = it->second.begin();
    cout << *line_it; // write the first line number

    // scan the rest line numbers
    ++line_it;
    ostringstream os;
    while(line_it != it->second.end())
    {
    // store line numbers into ostringstream object
    os << ", " << *line_it;
    ++line_it;
    }

    // get the contents from line_numbers
    string line_numbers = os.str();

    // write each line of outputs
    for(string::size_type i = 0; i != line_numbers.size(); ++i)
    {
    cout << line_numbers[i];
    if((i + 1) % line_length == 0)
    cout << endl;
    }
    // write a blank line to separate each words
    cout << endl;
    }
    return 0;
    }
    -

    I use the inputs as same as inputs used in exercise 7-4 and get following results, showing the effect of above changes.

    -
    ABC occurs on lines:
    1, 2, 3, 4, 5, 6, 7,
    8, 9, 10, 11, 12, 13
    , 14, 15, 16, 17, 18
    , 19, 20, 21
    DEF occurs on line:
    2
    - -

    Exercise 7-8

    Change the cross-reference program to find all the URLs in a file, and write all the lines
    on which each distinct URL occurs.

    -

    Solution & Results

    Please find the program and analysis in Example 2-Test 2.

    -

    Exercise 7-9

    (difficult) The implementation of nrand in §7.4.4/135 will not work for arguments greater than RAND_MAX. Usually, this restriction is no problem, because RAND_MAX is often the largest possible integer anyway. Nevertheless, there are implementations under which RAND_MAX is much smaller than the largest possible integer. For example, it is not uncommon for RAND_MAX to be 32767 (2^15 -1) and the largest possible integer to be 2147483647 (2^31 -1). Reimplement nrand so that it works well for all values of n.

    -

    Solution & Results

    Recalling nrand function

    -
    int nrand(int n)
    {
    if(n <= 0 || n > RAND_MAX)
    throw domain_error("Argument to nrand is out of range");

    const int bucket_size = RAND_MAX /n;
    int r;

    do r = rand() / bucket_size;
    while(r >= n);

    return r;
    }
    -

    nrand generates a random numbers in the range [0, n). The idea behind this function is that we divide the range[0, n(RAND_MAX/n)) into *n** pieces of equal size. Assuming RAND_MAX = 32767, n = 1000, random numbers r and random numbers generated from rand() have following relationships:

    -
    r (values)                   rand() (values of the range)

    0 [0, 32)
    1 [32, 64)
    2 [64, 96)
    ... ...
    999 [31968, 32000)
    -

    To be continued.

    -]]>
    - - Programming - - - Accelerated C++ Solutions - -
    - - Accelerated C++ Solutions to Exercises(Chapter 7 Part 1) - /2018/03/30/Accelerated-C-Solutions-to-Exercises-Chapter-7/ - Exercise 7-0

    Compile, execute, and test the programs in this chapter.

    -

    Solution & Results

    Please find the programs and detailed analysis in Example 1, 2 and Example 3.

    -

    Exercise 7-1

    Extend the program from §7.2/124 to produce its output sorted by occurrence count.That is, the output should group all the words that occur once, followed by those that occur twice, and so on.

    -

    Solution & Results

    The key to the solution is building a map from occurrence numbers to corresponding words. The original program builds a map from each distinct word to its occurrence numbers. Therefore, we can simply inverse the original map. But noting there may be more than one words have the same occurrence numbers. The revised program is shown below:

    -
    #include <iostream>		// to get the declaration of cin, cout, endl
    #include <vector> // to get the declaration of vector
    #include <string> // to get the declaration of string
    #include <map> // to get the declaration of map

    using std::cin; using std::cout;
    using std::endl; using std::string;
    using std::map; using std::vector;

    int main()
    {
    // store each word and an associated counter
    string s;
    map<string, int> counters;

    // read the input, keeping track of each word and how often we see it
    while (cin >> s)
    {
    ++counters[s];
    }

    // sort words stored in counters according to the occurence count
    map<int, vector<string> > sorted_counters;
    for (map<string, int>::const_iterator it = counters.begin(); it != counters.end(); ++it)
    {
    sorted_counters[it->second].push_back(it->first);
    }

    // write the words and associated counts
    cout << "Words and their associated counts:" << endl;
    for (map<string, int>::const_iterator it = counters.begin(); it != counters.end(); ++it)
    {
    cout << it->first << "\t" << it->second << endl;
    }

    // write a blank line to separate the outputs of two maps
    cout << endl;

    // write occurrence count followed by the corresponding words
    cout << "Occurrence count and the corresponding words:" << endl;
    for (map<int, vector<string> >::const_iterator it = sorted_counters.begin(); it != sorted_counters.end(); ++it)
    {
    cout << it->first;
    for (vector<string>::const_iterator i = (it->second).begin(); i != (it->second).end(); ++i)
    cout << ' ' << *i;
    cout << endl;
    }

    return 0;
    }
    -

    It can be observed that the values of the original map are stored into the new map as keys while the keys are stored as values in the new map. In addition, we specify vector to hold more words that have same occurrence numbers. Now let’s type some words and check the results.

    -
    Inputs:

    a dog and a cat
    cat is good dog is bad
    human is ugly

    Outputs:

    Words and their associated counts:
    a 2
    and 1
    bad 1
    cat 2
    dog 2
    good 1
    human 1
    is 3
    ugly 1

    Occurrence count and the corresponding words:
    1 and bad good human ugly
    2 a cat dog
    3 is
    -

    Yeah, it correctly sorts the words according to their occurrence numbers.

    -
    -

    Exercise 7-2

    Extend the program in §4.2.3/64 to assign letter grades by ranges:

    -
    A   90-100
    B 80-89.99...
    C 70-79.99...
    D 60-69.99...
    F < 60
    -

    The output should list how many students fall into each category.

    -

    Solution & Results

    The key to solution is building a map from the letter grades, A, B, C, D, F, to the number of students who have the corresponding final grades. Therefore, the map can be defined as:

    -
    map<string, int> grades_count;
    -

    The strategy can be divided into four steps:

    -
      -
    1. read students’ information
    2. -
    3. calculate final grade for each student
    4. -
    5. check the range of each final grade and get a letter grade (i.e. the key)
    6. -
    7. increment the value associated with the key returned in step 3
    8. -
    -

    Step 1 and step 2 are familar.

    -

    Step 3 needs a function on the final grade. I uses a simple if-else statement to complete it:

    -
    string letter_grade(double &grade)
    {
    if(grade < 0 || grade > 100)
    throw domain_error("grade is outside of[0, 100]");
    else if (grade >= 90)
    return "A";
    else if(grade >= 80 && grade < 90)
    return "B";
    else if(grade >= 70 && grade < 80)
    return "C";
    else if(grade >= 60 && grade < 70)
    return "D";
    else
    return "F";
    }
    -

    Step 4 is accomplished by the statement:

    -
    ++grades_count[letter_grade(final_grade)];
    -

    letter_grade(final_grade) returns a letter grade based on the function above. Then, the map grades_count stores (if it is new ) the letter as the key and returns the associated value. Finally, applies ++ operator to increment the associated value, showing the counting process.

    -

    The complete program

    The complete program is displayed below including files: mainfunction.cpp, grade.cpp, grade.h, Student_info.cpp and Student_info.h.

    -

    mainfunction.cpp

    -
    #include <iostream>		// to get the declaration of cin, cout, endl
    #include <stdexcept> // to get the declaration of domain_error
    #include <string> // to get the declaration of string
    #include <vector> // to get the declaration of vector
    #include <map> // to get the declaration of map
    #include "Student_info.h" // to get the declaration of Student_info
    #include "grade.h" // to get the declaration of grade

    using std::cin;
    using std::cout; using std::string;
    using std::endl; using std::vector;
    using std::domain_error; using std::map;

    string letter_grade(double &grade)
    {
    if(grade < 0 || grade > 100)
    throw domain_error("grade is outside of[0, 100]");
    else if (grade >= 90)
    return "A";
    else if(grade >= 80 && grade < 90)
    return "B";
    else if(grade >= 70 && grade < 80)
    return "C";
    else if(grade >= 60 && grade < 70)
    return "D";
    else
    return "F";
    }

    int main()
    {
    // read and store all the records
    vector<Student_info> students;
    Student_info record;
    while(read(cin, record))
    {
    students.push_back(record);
    }

    map<string, int> grades_count;
    for (vector<Student_info>::size_type i = 0; i != students.size(); ++i)
    {
    // compute each final grade letter grades and counting the letter grades
    try{
    double final_grade = grade(students[i]);
    ++grades_count[letter_grade(final_grade)];
    } catch(domain_error e){
    cout << e.what();
    }
    }

    for(map<string, int>::const_iterator it = grades_count.begin();
    it != grades_count.end(); ++it)
    cout << it->first << '\t' << it->second << endl;

    return 0;
    }
    - -

    grade.cpp

    -
    #include <algorithm>
    #include <stdexcept>
    #include <vector>
    #include "grade.h"
    #include "Student_info.h"

    using std::domain_error; using std::istream;
    using std::vector; using std::sort;

    // grade function 1
    double grade(const Student_info &s)
    {
    return grade(s.midterm, s.final, s.homework);
    }

    // grade function 2
    double grade(double midterm, double final, const vector<double> &hw)
    {
    if (hw.size() == 0)
    throw domain_error("student has done no homework");
    return grade(midterm, final, median(hw));
    }

    // grade function 3
    double grade(double midterm, double final, double homework)
    {
    return 0.2*midterm + 0.4*final + 0.4*homework;
    }

    // compute the median of vector<double>
    double median(vector<double> vec)
    {
    // get the size of the vector
    typedef vector<double>::size_type vec_size;
    vec_size size = vec.size();

    // check whether the empty is empty
    if (size == 0)
    throw domain_error("median of an empty vector");

    // sort the grades
    sort(vec.begin(), vec.end());

    // compute the median homework grade
    vec_size mid = size/2;
    return size % 2 == 0 ? (vec[mid] + vec[mid - 1])/2 : vec[mid];
    }
    - -

    grade.h

    -
    #ifndef GUARD_grade_h
    #define GUARD_grade_h

    // grade.h
    #include<vector>
    #include "Student_info.h"

    double grade(const Student_info &);
    double grade(double, double, const std::vector<double> &);
    double grade(double, double, double);
    double median(std::vector<double>);
    #endif
    - -

    Student.info

    -
    #include "Student_info.h"
    using std::vector; using std::istream;

    bool compare(const Student_info &x, const Student_info &y)
    {
    return x.name < y.name;
    }

    istream & read(istream &is, Student_info &s)
    {
    // reads and store the student's name, midterm and final exam grades
    is >> s.name >> s.midterm >> s.final;

    // reads and store all homework grades
    read_hw(is, s.homework);
    return is;
    }

    istream & read_hw(istream &in, vector<double> &hw)
    {
    if (in){
    //get rid of previous contents
    hw.clear();

    // read homework grades
    double x;
    while(in >> x)
    hw.push_back(x);

    // clear the stream so that input will work for the next student
    in.clear();
    }
    return in;
    }
    -

    Student_info.h

    -
    #ifndef GUARD_Student_info
    #define GUARD_Student_info

    #include<iostream>
    #include<string>
    #include<vector>

    struct Student_info{
    std::string name;
    double midterm, final;
    std::vector<double> homework;
    };

    bool compare(const Student_info &, const Student_info &);
    std::istream & read(std::istream &, Student_info &);
    std::istream & read_hw(std::istream &, std::vector<double> &);
    #endif
    - -

    Performance Test

    Inputs:

    Phqgh 24.7879 58.6263 64.0505
    Nlfdx 95.4242 27.3636 91.0404
    Cxggb 16.1818 95.4747 26.7172
    Uxwfn 35.9495 3.11111 22.3333
    Tkjpr 68.4747 44.6263 57.3737
    Pnrvy 16.3535 90.4242 88.0606
    Syycq 5.90909 29.7071 50.0606
    Ffmzn 84.5455 56.404 66.7677
    Vwsre 23.3737 38.1818 82.2929
    Fxtls 4.30303 77.0606 73.8687
    Dpooe 29.7778 73.9798 12.8687
    Ejuvp 55.7475 31.5253 50.5051
    Poeyl 91.0707 37.5758 87.5354
    Jvrvi 21.8889 22.4646 6.30303
    Hwqnq 55.101 59.2424 37.4848
    Jjloo 91.3636 74.202 96.2121
    Whmsn 34.5354 99.1818 38
    Sfzkv 48.8384 7.21212 10.1717
    Lyjyh 51 49.1919 56.9899
    Nkkuf 89.0202 95.8586 93.4343

    Outputs:

    A 1
    B 1
    C 1
    D 5
    F 12
    - -
    -

    Exercise 7-3

    The cross-reference program from §7.3/126 could be improved: As it stands, if a word occurs more than once on the same input line, the program will report that line multiple times. Change the code so that it detects multiple occurrences of the same line number and inserts the line number only once.

    -

    Solution & Results

    In the original program, we built a map from each distinct word to the line numbers in which the word appears.

    -
    map<string, vector<int> > xref(istream &in,
    vector<string> find_words(const string &))
    {
    string line;
    int line_number = 0;
    map<string, vector<int> > ret;

    // read the next line
    while(getline(in, line))
    {
    ++line_number;

    // break the input line into words
    vector<string> words = find_words(line);

    // remember that each word occurs on the current line
    for(vector<string>::const_iterator it = words.begin(); it != words.end();
    ++it)
    {
    ret[*it].push_back(line_number);
    }
    }
    return ret;
    }
    - -

    However, the line numbers for each word may repeatedly recorded. To avoid this problem, one solution is to check whether the line number has already been recorded. If the line number has been recorded, we ignore it, otherwise, we store it into the vector. We do not need to check all elements in the vector instead we only check the last stored line number. This is because that if a line number is repeatedly recorded, two elements (i.e. same line numbers) must be adjacent. Therefore, I add if statement as follows:

    -
    if(ret[*it].empty() || *(ret[*it].end() - 1) != line_number)
    ret[*it].push_back(line_number);
    -

    I’ll give the complete program as well as test results in next exercise.

    -

    Exercise 7-4

    The output produced by the cross-reference program will be ungainly if the input file is large. Rewrite the program to break up the output if the lines get too long.

    -

    Solution & Results

    The key to the solution is managing the length of each line of outputs. Theoretically, the strategy can be divided into three steps:

    -
      -
    1. convert all line numbers (except the first one) associated with a word into strings.
    2. -
    3. count the number of characters that have been written.
    4. -
    5. when the predetermined length of a line is reached, write a new line.
    6. -
    -

    The first two steps seems tedious to us. Fortunately, we can use stringstream objects to accomplish these easily.

    -

    stringstream is a stream class defined standard library. It provides IO facilities that operate on strings. For example, ostringstream object uses a string buffer to hold a sequences of characters for printing all outputs together. In addition, the sequence of characters can be accessed directly as a string object, using member function str. In this case, I define such an object os to hold all line numbers as well as additional spaces between two line numbers.

    -
    os << ", " << *line_it;
    -

    line_it is an iterator that refers to one of line numbers stored in a vector.

    -

    Once all line numbers have been stored into os, we can access all contents to be written as a string object.

    -
    string line_numbers = os.str();
    - -

    Now, the next is to print all line numbers in one or more lines depending on the predetermined length of one line.

    -
    for(string::size_type i = 0; i != line_numbers.size(); ++i)
    {
    cout << line_numbers[i];
    if((i + 1) % line_length == 0)
    cout << endl;
    }
    -

    When i+1th element (i.e. line_number[i])
    is written, the if statement check that if the number of characters that have been written equals to the length (or multiple lengths) of a line, a newline character is inserted into the output stream.

    -

    A complete program

    I integerated the changes described in exercise 7-3 and this exercise into a new program including files: mainfunction.cpp, xref.cpp, xref.h, split.cpp, split.h.

    -

    mainfunction.cpp

    -
    #include <map>		// to get the declaration of map
    #include <string> // to get the declaration of string
    #include <vector> // to get the declaration of vector
    #include <iostream> // to get the declaration of cin, cout, endl;
    #include <sstream> // to get the declaration of ostringstream
    #include <cctype> // to get the declaration of isspace
    #include "split.h" // to get the declaration of function split
    #include "xref.h" // to get the declaration of xref

    using std::map; using std::cout;
    using std::cin; using std::endl;
    using std::string; using std::vector;
    using std:: ostringstream; using std::isspace;

    int main()
    {
    // call xret using split by default
    map<string, vector<int> > ret = xref(cin);

    // set the length for each line of outputs
    string::size_type line_length = 20;

    // write the result
    for(map<string, vector<int> >::const_iterator it = ret.begin(); it != ret.end(); ++it)
    {
    // write the word
    cout << it->first << " occurs on line(s):" << endl;

    // followed by one or more line numbers
    vector<int>::const_iterator line_it = it->second.begin();
    cout << *line_it; // write the first line number

    // scan the rest line numbers
    ++line_it;
    ostringstream os;
    while(line_it != it->second.end())
    {
    // store line numbers into ostringstream object
    os << ", " << *line_it;
    ++line_it;
    }

    // get the contents from line_numbers
    string line_numbers = os.str();

    // write each line of outputs
    for(string::size_type i = 0; i != line_numbers.size(); ++i)
    {
    cout << line_numbers[i];
    if((i + 1) % line_length == 0)
    cout << endl;
    }
    // write a blank line to separate each words
    cout << endl;
    }
    return 0;
    }
    - -

    xref.cpp

    -
    #include <iostream>	// to get the decalration of istream
    #include <map> // to get the declaration of map
    #include <vector> // to get the declaration of vector
    #include <string> // to get the declaration of string
    #include "split.h" // to get the declaration of split
    #include "xref.h" // to get the declatation of xref

    using std::map; using std::vector;
    using std::string; using std::istream;

    map<string, vector<int> > xref(istream &in,
    vector<string> find_words(const string &))
    {
    string line;
    int line_number = 0;
    map<string, vector<int> > ret;

    // read the next line
    while(getline(in, line))
    {
    ++line_number;

    // break the input line into words
    vector<string> words = find_words(line);

    // remember that each word occurs on the current line
    for(vector<string>::const_iterator it = words.begin(); it != words.end();
    ++it)
    {
    if(ret[*it].empty() || *(ret[*it].end() - 1) != line_number)
    ret[*it].push_back(line_number);
    }
    }
    return ret;
    }
    - -

    xref.h

    -
    #ifndef GUARD_XREF_H
    #define GUARD_XREF_H

    #include <map>
    #include <vector>
    #include <string>
    #include <iostream>
    #include "split.h"

    std::map<std::string, std::vector<int> > xref(std::istream &,
    std::vector<std::string> find_words(const std::string &) = split);
    #endif /*GUARD_XREF_H */
    - -

    split.cpp

    -
    #include <vector>		// to get the declarartion of vector
    #include <string> // to get the declaration of string
    #include <algorithm> // to get the declaration of find_if
    #include "split.h" // to get the declaration of split

    using std::vector; using std::string;
    using std::find_if;

    // true if the argument is whitespace, false otherwise
    bool space(char c)
    {
    return isspace(c);
    }

    // false if the argument is whitesapce, true otherwise
    bool not_space(char c)
    {
    return !isspace(c);
    }

    // function extracts words from a line of input
    vector<string> split(const string &str)
    {
    typedef string::const_iterator iter;
    vector<string> ret;

    iter i = str.begin();
    while (i != str.end()){
    // ignore leading blanks
    i = find_if(i, str.end(), not_space);

    // find end of next word
    iter j = find_if(i, str.end(), space);

    // copy the characters in [i,j)
    if(i != str.end())
    ret.push_back(string(i, j));
    i = j;
    }
    return ret;
    }
    - -

    split.h

    -
    #ifndef GUARD_SPLIT_H
    #define GUARD_SPLIT_H

    #include <vector>
    #include <string>

    bool space(char);
    bool not_space(char);
    std::vector<std::string> split(const std::string &);

    #endif /* GUARD_SPLIT_H */
    - -

    Performance test

    Inputs:

    ABC
    ABC DEF DEF
    ABC
    ABC ABC
    ABC
    ABC
    ABC
    ABC
    ABC
    ABC
    ABC
    ABC
    ABC
    ABC
    ABC
    ABC
    ABC
    ABC
    ABC
    ABC
    ABC

    Outputs:

    ABC occurs on line(s):
    1, 2, 3, 4, 5, 6, 7,
    8, 9, 10, 11, 12, 13
    , 14, 15, 16, 17, 18
    , 19, 20, 21
    DEF occurs on line(s):
    2
    - -

    From this test, we observe that

    -
      -
    1. if one word appears more than one times in the same line, the line number is only recorded once. This shows the change described in exercise 7-3 works well.
    2. -
    3. once a line of outputs exceeds 20 characters, it wraps. This verifies the solution given above.
    4. -
    -

    Noting that I also change the program such that it always writes line numbers starting from a new line.

    -
    -

    To be continued.

    -]]>
    - - Programming - - - Accelerated C++ Solutions - -
    - - C++ - Using associative containers (Part 2) - /2018/03/28/C-Using-associative-containers-Part-2/ - Example 3 - Generating sentences

    This section introduces how to write a program that can randomly generate a sentence given certain grammar rules. For example, given following input

    -
    Categories        Rules

    <noun> cat
    <noun> dog
    <noun> table
    <noun-phrase> <noun>
    <noun-phrase> <adjective> <noun-phrase>
    <adjective> large
    <adjective> brown
    <adjective> absurd
    <verb> jumps
    <verb> sits
    <location> on the stairs
    <location> under the sky
    <location> wherever it wants
    <sentence> the <noun-phrase> <verb> <location>
    - -

    The program might generate such a sentence:

    -
    the cat sits on the stairs
    -

    Some stylized facts can be observed:

    -
      -
    1. There are two types of element in the inputs, one type contains a string enclosed by a pair of angle brackets and the other type contains one or more strings. We can always find direct or indirect mapping relations from each first type element to the second type elements.

      -
    2. -
    3. The first type represents categories of the components that constitute a sentence. One or more categories can construct compound categories. The second type represents the smallest building blocks of a sentence.

      -
    4. -
    5. The sentence structure is determined by the Rule associated with the category . A Rule may contain either categories or most basic building blocks or mixed.

      -
    6. -
    -

    Therefore, to construct a sentence, we need to

    -
      -
    1. find , and then find the associated Rule.
    2. -
    3. start to find each element of a sentence follow the instructions of the Rule.
    4. -
    5. if the element is already the most basic building block, then we just store it into a vector for the final output. If the element is a category (i.e. the first type of element), we’ll find the associated Rule recursively, until that we find any of the most basic building blocks (i.e. the second type of element).
    6. -
    -

    Now, the soluction strategy can be logically divided into three parts:

    - -

    Read the grammar

    Seen from above example, we can built a map from the first colunm to the second column, two elements of which in each row construct a key-value pair. The elements in the first column have data type string. But what’s the data type for the elements of the second column? We know that each rule may contains one or more strings and hence each rule can be stored into a vector:

    -
    vector<string> Rule;
    -

    Also, the mapping from the first column to the second column has one-to-many relations,for example, one maps to several rules such as cat, dog and table. Therefore, we can store each rule into a high-level vector:

    -
    vector<vector<string>> Rule_collection;
    -

    For the sake of brevity, we can use type alias in declaring such a map:

    -
    typedef vector<string> Rule;
    typedef vector<Rule> Rule_collection;
    typedef map<string, Rule_collection> Grammar;
    -

    Let’s see how to read the grammar into such a map:

    -
    Grammar read_grammar(istream &in)
    Grammar ret;
    string line;

    // read the input
    while (getline(in, line))
    {
    // split the input into words
    vector<string> entry = split(line);
    if(!entry.empty())
    // use the category to store the associated rule
    ret[entry[0]].push_back(Rule(entry.begin() + 1, entry.end()));
    }
    return ret;
    }
    -

    The function returns a map Grammar that contains all grammar rules to be applied to generate a sentence in the next step. There is only one argument, an input stream object, to be passed.

    -

    Inside of the function body, the first statement defines an empty map ret for holding the grammar, and the second statement defines an empty string for holding each row of input containing the key (i.e. one category) and the value (i.e. one rule). The next is a while loop to read the input repeatedly and read one line once. When the first line is read in, we need to extract all words contained in the line. Then, for the first word (i.e. the category represented by a string enclosed by a pair of angle brackets), we store it into the map as the key, while for the following words, we store them as the value that associates with the key. The core statement is

    -
    ret[entry[0]].push_back(Rule(entry.begin() + 1, entry.end()));
    -

    It seems complex but actually there is noting new in it. entry is the vector returned by the split function and hence contains all words extracted from the line of inputs. ret[entry[0]] stores the first word (if the word is new for the key), i.e. the category, and returns its associated value, i.e. Rule_collection. Then, we uses push_back to store the associated Rule which is a vector . The Rule is filled with values from the range [entry.begin() + 1, entry.end()) using

    -
    vector<string>(iterator_first, iterator_last);
    -

    The last statement is to return the Grammar ret.

    -

    Generate a sentence

    Let’s consider the function that generate a sentence.

    -
    // generating the sentence
    vector<string> gen_sentence(const Grammar &g)
    {
    vector<string> ret;
    gen_aux(g, "<sentence>", ret);
    return ret;
    }
    -

    Apparently, we need a vector to hold the generated sentence. The only argument to be passed is the value returned by the function read_grammar.

    -

    The function that really deals with generating a sentence is named as gen_aux. It has three parameters, the first one is the grammar produced by read_grammar, the second one is a keyword ““ to be searched, the third one is a vector to hold the final results.

    -

    The function is defined below:

    -
    void gen_aux(const Grammar &g, const string &word, vector<string> &ret)
    {
    if(!bracketed(word)){
    ret.push_back(word);
    }
    else
    {
    // locate the rule that corresponds to word
    Grammar::const_iterator it = g.find(word);
    if(it == g.end())
    throw logic_error("empty rule");

    // fetch the set of possible rules
    const Rule_collection &c = it->second;

    // from which we select one at random
    const Rule &r = c[nrand(c.size())];

    // recursively expand teh selected rule
    for (Rule::const_iterator i = r.begin(); i != r.end(); ++i)
    gen_aux(g, *i, ret);
    }
    }
    -

    The logic of this function is exactly as same as we described above. Ignoring the if-else statement first, the first step is to find the category :

    -
    Grammar::const_iterator it = g.find(word);
    -

    The member function find finds and returns an iterator that refers to the element with the key equivalent to the given k. If such element doesn’t exist in the map, the find function returns iterator g.end(). Therefore, if there exists such an iterator, getting the associated Rule_collection:

    -
    if(it == g.end())
    throw logic_error("empty rule");

    // fetch the set of possible rules
    const Rule_collection &c = it->second;
    -

    By now, there exists two problems to be solved:

    -
      -
    1. how to randomly pick one rule from Rule_collection
    2. -
    3. if one rule is picked, how to deal with the case that its elements are still categories.
    4. -
    -

    Let’s put question 1 last and solve question 2 first. Assuming we have picked one rule from the Rule_collection, we then scan each element of it and store the element if the element is the most basic building block. But if the element is still one of the categories, what we need to do is to find the lower level rule (i.e. its associated value). Therefore, we just repeat above processes until the element is not a category.
    The if-else statement controls the recursive process while the condition that ceases the recursion is a predicate which returns true if the element is not a category:

    -
    bool bracketed(const string &s)
    {
    return s.size() > 1 && s[0] == '<' && s[s.size() - 1] == '>';
    }
    - -

    The function gen_sentence is recursively called in the for loop:

    -
    // recursively expand teh selected rule
    for (Rule::const_iterator i = r.begin(); i != r.end(); ++i)
    gen_aux(g, *i, ret);
    - -

    Random drawing

    Now the last piece is to randomly pick a rule from Rule_collection.
    One possible solution is using rand() % n. rand() is an algorithm defined in standard header and gives a random integer in the range [0, RAND_MAX]. The upper bound is a large number defined in . rand() % n, computes the remainder when deviding the random number by n and hence gives a random integer in the range [0, n). If we set n = c.size(), we can then obtain an random index that yields a rule via c[rand() % c.size]. However, this solution is not a good choice due to(Koenig and Moo 2000):

    -
      -
    1. rand() returns pseudo-random numbers. Many C++ implementations’ pseudo-random numbers give remainders that aren’t very random when the quotients are small integers.

      -
    2. -
    3. if n is large and RAND_MAX is not evenly divisible by n,some remainder will appear more often than others.

      -
    4. -
    -

    To circumvent these issues, we can divide the range of available numbers into buckets of exactly equal size:

    -
    const int bucket_size = RAND_MAX /n;
    -

    Then, bucket 0 has values from [0, bucket_size), bucket 1 has values from [bucket_size, bucket_size*2)…..

    -

    Then we can random draw a number and get the bucket number where the random number is located in via:

    -
    int r = rand() / bucket_size;
    -

    But there exist situations that the random number does not fall into any bucket due to the fact that RAND_MAX may be not evenly divisible by n. Therefore, we uses a do while statement to repeat above statement until it finds a random number that locates in the range of one of buckets.

    -

    The function nrand is shown below:

    -
    // return a random integer in the range [0, n)
    int nrand(int n)
    {
    if(n <= 0 || n > RAND_MAX)
    throw domain_error("Argument to nrand is out of range");

    const int bucket_size = RAND_MAX /n;
    int r;

    do r = rand() / bucket_size;
    while(r >= n);

    return r;
    }
    -

    Noting that rand() uses a seed to generate the sequence, which should be initialized to some distinctive value using void srand(unsigned int seed). A common practice is to use distinctive runtime value, like the value returned by function time. For example

    -
    srand (time(NULL));
    -

    In addition, srand() is in fact has global effect on rand() and hence can be stated at the very begining of the main function.

    -

    A complete program

    Now I files all functions and code discussed above and present the compete program below. The main function easy to understand and hence no further analysis here.

    -

    mainfunction.cpp

    -
    #include <cstdlib>		// to get the declaration of srand
    #include <ctime> // to get the declaration of time
    #include <vector> // to get the declaration of vector
    #include <string> // to get the declaration of string
    #include "read_grammar.h" // to get the declaration of read_grammar
    #include "gen_sentence.h" // to get the declaration of gen_sentence

    using std::cin; using std::cout;
    using std::vector; using std::endl;
    using std::string; using std::srand;
    using std::time;

    int main()
    {
    // initialize random number generator with distinctive runtime value
    srand (time(NULL));

    // generate the sentence
    vector<string> sentence = gen_sentence(read_grammar(cin));

    // write the first word, if any
    vector<string>::const_iterator it = sentence.begin();
    if(!sentence.empty())
    {
    cout << *it;
    ++it;
    }

    // write the rest of the words, each preceded by a space
    while(it != sentence.end())
    {
    cout << " " << *it;
    ++it;
    }
    cout << endl;
    return 0;
    }
    - -

    type_alias.h

    -
    #ifndef GUARD_TYPE_ALIAS_H
    #define GUARD_TYPE_ALIAS_H

    #include <vector>
    #include <string>
    #include <map>

    typedef std::vector<std::string> Rule;
    typedef std::vector<Rule> Rule_collection;
    typedef std::map<std::string, Rule_collection> Grammar;

    #endif /* GUARD_TYPE_ALIAS_H */
    - -

    read_grammar.cpp

    -
    #include <iostream>		// to get the declaration of istream
    #include <vector> // to get the declaration of vector
    #include <string> // to get the declaration of string
    #include "split.h" // to get the declaration of split
    #include "type_alias.h" // to get the declaration of type alias
    #include "read_grammar.h" // to get the declaration of read_grammar

    using std::istream; using std::vector;
    using std::string;

    // read a grammar from a given input stream
    Grammar read_grammar(istream &in)
    {
    Grammar ret;
    string line;

    // read the input
    while (getline(in, line))
    {
    // split the input into words
    vector<string> entry = split(line);
    if(!entry.empty())
    // use the category to store the associated rule
    ret[entry[0]].push_back(Rule(entry.begin() + 1, entry.end()));
    }
    return ret;
    }
    - -

    read_grammar.h

    -
    #ifndef GUARD_READ_GRAMMAR_H
    #define GUARD_READ_GRAMMAR_H

    #include <iostream>
    #include "type_alias.h"

    Grammar read_grammar(std::istream &);

    #endif /* GUARD_READ_GRAMMAR_H */
    - -

    gen_sentence.cpp

    -
    #include <vector>		// to get the declaration of vector
    #include <string> // to get the declaration of string
    #include <stdexcept> // to get the declaration of domain_error, logic_error
    #include <cstdlib> // to get the declaration of rand
    #include "type_alias.h" // to get the declaration of type_alias
    #include "gen_sentence.h" // to get the declatation of gen_sentence

    using std::vector; using std::string;
    using std::domain_error; using std::logic_error;
    using std::rand;

    // generating the sentence
    vector<string> gen_sentence(const Grammar &g)
    {
    vector<string> ret;
    gen_aux(g, "<sentence>", ret);
    return ret;
    }

    // auxillary gen function
    void gen_aux(const Grammar &g, const string &word, vector<string> &ret)
    {
    if(!bracketed(word)){
    ret.push_back(word);
    }
    else
    {
    // locate the rule that corresponds to word
    Grammar::const_iterator it = g.find(word);
    if(it == g.end())
    throw logic_error("empty rule");

    // fetch the set of possible rules
    const Rule_collection &c = it->second;

    // from which we select one at random
    const Rule &r = c[nrand(c.size())];

    // recursively expand teh selected rule
    for (Rule::const_iterator i = r.begin(); i != r.end(); ++i)
    gen_aux(g, *i, ret);
    }
    }

    // the predicate
    bool bracketed(const string &s)
    {
    return s.size() > 1 && s[0] == '<' && s[s.size() - 1] == '>';
    }

    // return a random integer in the range [0, n)
    int nrand(int n)
    {
    if(n <= 0 || n > RAND_MAX)
    throw domain_error("Argument to nrand is out of range");

    const int bucket_size = RAND_MAX /n;
    int r;

    do r = rand() / bucket_size;
    while(r >= n);

    return r;
    }
    - -

    split.cpp

    -
    #include <vector>	// to get the declarartion of vector
    #include <string> // to get the declaration of string
    #include <algorithm> // to get the declaration of find_if
    #include "split.h" // to get the declaration of split

    using std::vector; using std::string;
    using std::find_if;

    // true if the argument is whitespace, false otherwise
    bool space(char c)
    {
    return isspace(c);
    }

    // false if the argument is whitesapce, true otherwise
    bool not_space(char c)
    {
    return !isspace(c);
    }

    // function extracts words from a line of input
    vector<string> split(const string &str)
    {
    typedef string::const_iterator iter;
    vector<string> ret;

    iter i = str.begin();
    while (i != str.end()){
    // ignore leading blanks
    i = find_if(i, str.end(), not_space);

    // find end of next word
    iter j = find_if(i, str.end(), space);

    // copy the characters in [i,j)
    if(i != str.end())
    ret.push_back(string(i, j));
    i = j;
    }
    return ret;
    }
    -

    split.h

    -
    #ifndef GUARD_SPLIT_H
    #define GUARD_SPLIT_H

    #include <vector>
    #include <string>

    bool space(char);
    bool not_space(char);
    std::vector<std::string> split(const std::string &);

    #endif /* GUARD_SPLIT_H */
    - -

    Test performance

    Inputs:

    <noun> cat
    <noun> dog
    <noun> table
    <noun-phrase> <noun>
    <noun-phrase> <adjective> <noun-phrase>
    <adjective> large
    <adjective> brown
    <adjective> absurd
    <verb> jumps
    <verb> sits
    <location> on the stairs
    <location> under the sky
    <location> wherever it wants
    <sentence> the <noun-phrase> <verb> <location>

    Outputs:

    the large brown dog sits wherever it wants
    -

    The program works as expected.

    -
    -]]>
    -
    - - C++ - Using associative containers (Part 1) - /2018/03/27/C-Using-associative-containers/ - Introduction

    Unlike sequential containers, associative containers store data into a sequence depending on the values of the elements themselves rather than the sequence where we inserted them. An associative container supports efficient lookup and retrieval by a key which is the part of each element. The most common kind of assiciative data structure, namely the associative array, is one that stores key-value pairs, associating a value with each each key. In C++, such an associative array is called a map.

    -

    The keys in fact plays a role as an index for the values, which are similar to the index of a vector. But the difference is that the keys can be int* type or string type or any other types that allows ordering.

    -

    Example 1 - counting words

    int main()
    {
    string s;
    map<string, int> counters; // store each word and an associated counter

    // read the input, keeping track of each word and how often we see it
    while (cin >> s)
    {
    ++counters[s];
    }
    // write the words and associated counts
    for (map<string, int>::const_iterator it = counters.begin(); it != counters.end(); ++it)
    {
    cout << it->first << "\t" << it->second << endl;
    }
    return 0;
    }
    -

    Key points

    -
      -
    1. when we define a map, we need to specify the types for both key and value, i.e. a key-value pair. In this case, the key has type of string while the value has type of int. Such a container can be described as a map from string to int.

      -
    2. -
    3. from above, we know that each element in a map has type pair which is a data structure that holds two elements named first and second. For a map that has a key of type K, and a value of type V, the asociated pair type is pair<const K, V>. In this case, the elements of counters have type of pair<const string, int>.

      -
    4. -
    5. the map counters is constructed with default initialization, leading to an empty map.

      -
    6. -
    7. when we start to store words, the element pair<string, int> is value-initialized, that is, the key is initialized as an empty string and the value is initialized as 0.

      -
    8. -
    9. map supports operations via subscripting.

      -
      c[k] returns the **k**-associated value; if **k** doesn't exist, it adds **k** to the container, initialize and returns its associated value.

      In this case, when a word appears for the first time, counters[s] returns the associated value which is initialized with value 0. Then we increment the value via ++counters[s].

      -
    10. -
    11. the for loop shows how to access elements in a map using iterators. The operations are similar to those on a vactor. When we deference a map<string, int> iterator, we get a pair<const string, int>. Therefore, it->first extracts the value of the first element in the pair, that is, the key, while it->second gives the value of the second element in the pair, that is, the value associated with the key.

      -
    12. -
    -

    We can also access the value via subscripting as described at key point 5. For example,

    -
    cout << counters[it->first];
    -

    has the same effect as

    -
    cout << it->second;
    - -

    Example 2 - Generating a cross-reference table

    This example extends above program such that the new program can generate a cross-reference table that indicates where each word occurs in the input. It requires:

    -
      -
    1. read a line at a time for the purpose of obtaining the associated line number.
    2. -
    3. To separate each word from a line, we need a function like split described in Chpter 6. But rather than calling the function independently, we will pass it as an argument to the cross-reference function (denoted by xref).
    4. -
    -
    // find all the lines that refer to each word in the input
    map<string, vector<int> > xref(istream & in,
    vector<string> find_words(const string &) = split)
    {
    string line;
    int line_number = 0;
    map<string, vector<int> > ret;

    // read the next line
    while(getline(in, line))
    {
    ++line_number;

    // break the input line into words
    vector<string> words = find_words(line);

    // remember that each word occurs on the current line
    for (vector<string>::const_iterator it = words.begin(); it != words.end(); ++it)
    {
    ret[*it].push_back(line_number);
    }
    return ret;
    }
    }
    -

    Let’s analyse this function:

    -
      -
    1. what xref should return is a map<string, vector > as in fact we aims to build a map from the word to its line number. The key, representing each distinct word, has type string. The value, representing line numbers associated with each key, has type vector due to the fact that one word may appears in different lines.

      -
    2. -
    3. xref takes two arguments, one is an input stream object; another one is a function to extract words from a line. We are familar with how to define a function parameter. But what’ new here is to use ** = split** in defining such a function parameter.This indicates that this parameter has a default argument, i.e. split. It means that if xref is called without passing this argument, it uses the default argument. If xref is called with passing a new argument, it uses the new argument. For example

      -
      xref(cin); // uses split to find words in the input stream
      xref(cin, find_urls); // uses the function named find_urls to find words
    4. -
    5. the function begins with defining three varaibles: the first is a string named line to hold each line of input; the second is a integer that denotes the line number; the third is a map<string, vector > to hold each pair of word and line numbers.

      -
    6. -
    7. every iteration of the while statement, one line of input is read and broken into words. Then, each word is accessed via iterator and stored into the map together its line number. The core statement is:

      -
      ret[*it].push_back(line_number);
      -

      As mentioned earlier, if *it, a word, doesn’t appear before, it will be stored and ret[it] returns an default initialized associated value, i.e. an empty vector. Then, we use *push_back** to append the line number to the end of the vector. If *it has already appeared before, ret[*it] returned the associated value (i.e. vector) and the new line number will be appended to the end of the vector.

      -
    8. -
    -

    A complete program

    Now, I add #include directives and present all files including: mainfunction.cpp, xref.cpp, xref.h, split.cpp, split.h. This program uses the default split function.

    -

    mainfunction.cpp

    -
    int main()
    {
    // call xref using split by default
    map<string, vector<int> > ret = xref(cin);

    // write the results
    for(map<string, vector<int> >::const_iterator it = ret.begin();
    it != ret.end(); ++it)
    {
    // write the word
    cout << it->first << " occurs on line(s): ";

    // followed by one or more line_numbers
    vector<int>::const_iterator line_it = it->second.begin();
    cout << *line_it; // write the first line number

    ++line_it;
    while (line_it != it->second.end())
    {
    cout << ", " << *line_it;
    ++line_it;
    }
    // write a new line to separate each word from the next
    cout << endl;
    }
    return 0;
    }
    -

    There is nothing new in above code. The program writes the first line number and the rest(if there exist) separately for the purpose of separating line numbers with a comma followed by a space.

    -

    xref.cpp

    -
    #include <iostream>	// to get the decalration of istream
    #include <map> // to get the declaration of map
    #include <vector> // to get the declaration of vector
    #include <string> // to get the declaration of string
    #include "split.h" // to get the declaration of split
    #include "xref.h" // to get the declatation of xref

    using std::map; using std::vector;
    using std::string; using std::istream;

    map<string, vector<int> > xref(istream &in,
    vector<string> find_words(const string &))
    {
    string line;
    int line_number = 0;
    map<string, vector<int> > ret;

    // read the next line
    while(getline(in, line))
    {
    ++line_number;

    // break the input line into words
    vector<string> words = find_words(line);

    // remember that each word occurs on the current line
    for(vector<string>::const_iterator it = words.begin(); it != words.end();
    ++it)
    {
    ret[*it].push_back(line_number);
    }
    }
    return ret;
    }
    -

    xref.h

    -
    #ifndef GUARD_XREF_H
    #define GUARD_XREF_H

    #include <map>
    #include <vector>
    #include <string>
    #include <iostream>
    #include "split.h"

    std::map<std::string, std::vector<int> > xref(std::istream &,
    std::vector<std::string> find_words(const std::string &) = split);

    #endif /*GUARD_XREF_H */
    -

    It is worth noting that if xref is separated from the main function file, the default argument for the parameter (i.e. split in this case) should be put into its header file only. This is becasue that the default argument for a given parameter can only be specified once. (more discussion can be found on this page Default value of function parameter -).

    -

    split.cpp

    -
    #include <vector>	// to get the declarartion of vector
    #include <string> // to get the declaration of string
    #include <algorithm> // to get the declaration of find_if
    #include "split.h" // to get the declaration of split

    using std::vector; using std::string;
    using std::find_if;

    // true if the argument is whitespace, false otherwise
    bool space(char c)
    {
    return isspace(c);
    }

    // false if the argument is whitesapce, true otherwise
    bool not_space(char c)
    {
    return !isspace(c);
    }

    // function extracts words from a line of input
    vector<string> split(const string &str)
    {
    typedef string::const_iterator iter;
    vector<string> ret;

    iter i = str.begin();
    while (i != str.end()){
    // ignore leading blanks
    i = find_if(i, str.end(), not_space);

    // find end of next word
    iter j = find_if(i, str.end(), space);

    // copy the characters in [i,j)
    if(i != str.end())
    ret.push_back(string(i, j));
    i = j;
    }
    return ret;
    }
    - -

    split.h

    -
    #ifndef GUARD_SPLIT_H
    #define GUARD_SPLIT_H

    #include <vector>
    #include <string>

    bool space(char);
    bool not_space(char);
    std::vector<std::string> split(const std::string &);

    #endif /* GUARD_SPLIT_H */
    - -

    Test 1

    From a simple test shown below, it can be seen that the program works as expected.

    -
    Inputs:

    do you like me
    no I do not like you

    Outputs:

    I occurs on line(s): 2
    do occurs on line(s): 1 2
    like occurs on line(s): 1 2
    me occurs on line(s): 1
    no occurs on line(s): 2
    not occurs on line(s): 2
    you occurs on line(s): 1 2
    - -

    Test 2

    Sometimes we may want to use another strategy to extract words, for example, when extracting URLs like we did in Finding URLs. I add revelent files first.

    -

    find_urls.cpp

    -
    // function that finds and returns an URL
    #include "find_urls.h"
    #include <vector>
    #include <string>
    #include "delimit.h"

    using std::vector;
    using std::string;

    vector<string> find_urls(const string &s)
    {
    vector<string> ret;
    typedef string::const_iterator iter;
    iter b = s.begin(), e = s.end();

    // look through the entire input
    while (b != e)
    {
    // look for one or more letters followed by ://
    b = url_beg(b, e);

    // if we found it
    if(b != e)
    {
    // get the rest of the URL
    iter after = url_end(b, e);

    // remember the URL
    ret.push_back(string(b, after));

    // advance b and check for more URLs on this line
    b = after;
    }
    }
    return ret;
    }
    - -

    find_urls.h

    -
    #ifndef GUARD_FINDINGURLS_H
    #define GUARD_FINDINGURLS_H

    #include <vector>
    #include <string>

    std::vector<std::string> find_urls(const std::string &);

    #endif /* GUARD_FINDINGURLS_H */
    - -

    dilimit.cpp

    -
    // contains three functions: not_url_char, url_beg, url_end
    #include <string>
    #include <algorithm>
    #include "delimit.h"

    using std::string; using std::find;
    using std::find_if; using std::search;

    // predicate on a char, check whether it is a char that can appear in a URL
    bool not_url_char(char c)
    {
    // characters, in addition to alphanumerics, that can appear in a URL
    static const string url_ch = "~;/?:@=&$-_.+!*'(),";

    // see whether c can appear in a URL and return the negative
    return !(isalnum(c) || find(url_ch.begin(), url_ch.end(), c) != url_ch.end());
    }

    // function that returns an iterator that refers to the first element of a URL
    string::const_iterator url_beg(string::const_iterator b, string::const_iterator e)
    {
    static const string sep = "://";
    typedef string::const_iterator iter;

    // i marks where the separator was found
    iter i = b;
    while((i = search(i, e, sep.begin(), sep.end())) != e)
    {
    // make sure the seperator isn't at the begining of the proticol-name
    if(i != b && i + sep.size() != e)
    {
    // beg marks the begining of the protocol-name
    iter beg = i;
    while(beg != b && isalpha(beg[-1]))
    --beg;

    // is there at least one appropriate character before and after the sep
    if (beg != i && !not_url_char(i[sep.size()]))
    return beg;
    }
    // the seperator we found wasn't part of a URL advance i past this separator
    i += sep.size();
    }
    return e;
    }

    // function that returns an iterator that denotes the postion one past the last element
    string::const_iterator url_end(string::const_iterator b, string::const_iterator e)
    {
    return find_if(b, e, not_url_char);
    }
    - -

    delimit.h

    -
    #ifndef GUARD_DELIMIT_H
    #define GUARD_DELIMIT_H

    #include <string>

    bool not_url_char(char);
    std::string::const_iterator url_beg(std::string::const_iterator, std::string::const_iterator);
    std::string::const_iterator url_end(std::string::const_iterator, std::string::const_iterator);

    #endif /* GUARD_DELIMIT_H */
    - -

    Now, let’s call xref with passing argument find_urls:

    -
    int main()
    {
    // call xref using split by default
    map<string, vector<int> > ret = xref(cin, find_urls);

    // write the results
    for(map<string, vector<int> >::const_iterator it = ret.begin();
    it != ret.end(); ++it)
    {
    // write the word
    cout << it->first << " occurs on line(s): ";

    // followed by one or more line_numbers
    vector<int>::const_iterator line_it = it->second.begin();
    cout << *line_it; // write the first line number

    ++line_it;
    while (line_it != it->second.end())
    {
    cout << ", " << *line_it;
    ++line_it;
    }
    // write a new line to separate each word from the next
    cout << endl;
    }
    return 0;
    }
    -

    Then, we type following inputs:

    -
    A typical URL could have the form https://en.wikipedia.org/wiki/URL, 
    which indicates a protocol (http), a hostname (www.example.com),
    and a file name (index.html). http://www.cplusplus.com/reference/algorithm/search/?kw=search
    - -

    The program give results as expected:

    -
    http://www.cplusplus.com/reference/algorithm/search/?kw=search occurs on line(s): 3
    https://en.wikipedia.org/wiki/URL, occurs on line(s): 1
    -
    -]]>
    - - Programming - - - C++ - Notes - -
    - - Accelerated C++ Solutions to Exercises(Chapter 6) - /2018/03/25/Accelerated-C-Solutions-to-Exercises-Chapter-6/ - Exercise 6-0

    Compile, execute, and test the programs in this chapter.

    -

    Solution & Results

    Please find all programs and detailed analysis on Using library algorithms (Part 1), Using library algorithms (Part 2) and Using library algorithms (Part 3).

    -

    Exercise 6-1

    Reimplement the frame and hcat operations from §5.8.1/93 and §5.8.3/94 to use iterators.

    -

    Solution & Results

    This exercise has been completed here Putting strings together.

    -

    Exercise 6-2

    Write a program to test the find_urls function.

    -

    Solution & Results

    Please find the program and analysis on Using library algorithms (Part 1).

    -

    Exercise 6-3, 6-4

    6-3: What does this program fragment do?

    -
    vector<int> u(10, 100);
    vector<int> v;
    copy(u.begin(), u.end(), v.begin());
    -

    Write a program that contains this fragment, and compile and execute it.

    -

    6-4: Correct the program you wrote in the previous exercise to copy from u into v. Thereare at least two possible ways to correct the program. Implement both, and describe the relative advantages and disadvantages of each approach.

    -

    Soltion & Results

    The first statement creates an object of vector and initializes it with 10 elements that all equals to 100. The second statement creates an empty (i.e. due to default initialization) vector v . The third statement calls an algorithm copy to copy values in the range [u.begin(), u.end()) into the destination denoted by the third argument v.begin(). However, the destination sequence should be at least as large as the input range. Therefore, the fragment will lead to compilation errors.

    -

    Method 1

    To correct this, we can initialize the v as an vector that has the same size as u. For example

    -
    vector<int> v(10);
    -

    This statement means that v contains 10 elements that all equals to 0. Then, the program should work fine.

    -

    Method 2

    Alternatively, we can use back_inserter to append the copied elements to v. It naturelly increases the size of v and hence ensures enough space for holding those elements. The third statement is replaced by:

    -
    copy(u.begin(), u.end(), back_inserter(v));
    - -

    Method 3

    There is also an iterator adaptor inserter that creates an insert iterator for successive insertion into a container. Therefore, we can replace the back_inserter with inserter shown as follows

    -
    copy(u.begin(), u.end(), inserter(v, v.begin()));
    -

    This statement means that the elements of u are copied and inserted into v starting from the begining position of v.

    -

    comparison

    All three methods can correct the original program. The first method is not practical as in most cases we probably don’t know the number of elements to copy. Contrarily, method 2 and method 3 ensures enough space for holding elements to copy. However, two iterator adaptors works differently:

    -
      -
    1. back_inserter constructs a back-insert iterator that inserts new elements at the end of a container. The container should have member function push_back.

      -
    2. -
    3. inserter constructs an insert iterator that inserts new elements into a container successively starting from a specified position. The container should have member function insert.

      -
    4. -
    -

    Apparently, inserter provides more flexibility in inserting elements while back_inserter has more limitations. But more research should be done on their performances when dealing with massive data. I present a simple program to show that all three methods work fine and lead to same results.

    -

    A complete test program

    Test Program

    -
    #include <iostream>
    #include <vector>
    #include <iterator>
    #include <algorithm>

    using std::cout; using std::endl;
    using std::copy; using std::vector;
    using std::inserter;using std::back_inserter;

    void print(vector<int> &vec)
    {
    for(vector<int>::const_iterator iter = vec.begin(); iter != vec.end(); ++iter)
    cout << *iter << endl;
    }

    int main()
    {
    vector<int> u(10, 100);

    // method 1: let v has the same size as u
    vector<int> v1(10);
    copy(u.begin(), u.end(), v1.begin());
    cout << "The results of method 1:" << endl;
    print(v1);

    cout << endl;

    // method 2: back_inserter
    vector<int> v2;
    copy(u.begin(), u.end(), back_inserter(v2));
    cout << "The results of method 2:" << endl;
    print(v1);

    cout << endl;

    // method 3: inserter
    vector<int> v3;
    copy(u.begin(), u.end(), inserter(v3, v3.begin()));
    cout << "The results of method 3:" << endl;
    print(v3);

    return 0;
    }
    - -

    Test Results

    -
    The results of method 1:
    100
    100
    100
    100
    100
    100
    100
    100
    100
    100

    The results of method 2:
    100
    100
    100
    100
    100
    100
    100
    100
    100
    100

    The results of method 3:
    100
    100
    100
    100
    100
    100
    100
    100
    100
    100
    - -

    Exercise 6-5

    Write an analysis function to call optimistic_median.

    -

    Solution & Results

    Please find the analysis function here Using library algorithms (Part 2).

    -

    Exercise 6-6

    Note that the function from the previous exercise and the functions from §6.2.2/113and §6.2.3/115 do the same task. Merge these three analysis functions into a singlefunction.

    -

    Solution & Results

    Please find the solution strategy and analysis here Using library algorithms (Part 2).

    -

    Exercise 6-7

    The portion of the grading analysis program from §6.2.1/110 that read and classified student records depending on whether they did (or did not) do all the homework is similar to the problem we solved in extract_fails. Write a function to handle this subproblem.

    -

    Solution & Results

    This exercise requires us to seperate the students’ records into two groups: one group contains records that did all homeworks while the other one contains records that did not complete all homeworks. Specifically, we rewrite the main function such that all records will be stored into a vector did first, and then we extract and store the other group records into didnt via a function named extract_didnt. This function can be written exactly the same as the extract_fails function developed using the single-pass solution (see Using library algorithms (Part 3)).

    -

    I revised the original program described on Using library algorithms (Part 2) by creating a file contains extract_didnt function and the predicate did_all_hw:

    -

    extract_didnt.cpp

    -
    #include <vector>		// to get the declaration of vector
    #include <algorithm> // to get the declaration of stable_partition
    #include "Student_info.h" // to get the declaration of Student_info
    #include "extract_didnt.h" // to get the declaration of extract_didnt

    using std::vector;
    using std::stable_partition;


    bool did_all_hw(const Student_info &s)
    {
    return ((find(s.homework.begin(), s.homework.end(), 0)) == s.homework.end());
    }

    vector<Student_info> extract_didnt(vector<Student_info> &did)
    {
    vector<Student_info>::iterator iter = stable_partition(did.begin(), did.end(), did_all_hw);
    vector<Student_info> didnt(iter, did.end());
    did.erase(iter, did.end());
    return didnt;
    }
    -

    extract_didnt.h

    -
    #ifndef GUARD_EXTRACT_DIDNT_H
    #define GUARD_EXTRACT_DIDNT_H

    #include <vector>
    #include "Student_info.h"

    bool did_all_hw(const Student_info &);
    std::vector<Student_info> extract_didnt(std::vector<Student_info> &);

    #endif /* GUARD_EXTRACT_DIDNT_H */
    - -

    Correspondingly, the main function becomes

    -

    mainfunction.cpp

    -
    #include <iostream>			// to get the declaration of cin, cout, endl
    #include <vector> // to get the declaration of vector
    #include "Student_info.h" // to get the declaration of Student_inf"
    #include "grades_analysis.h" // to get the declaration of three analysis function
    #include "write_analysis.h" // to get the declaration of write_analysis function
    #include "extract_didnt.h"

    using std::vector; using std::cout;
    using std::cin; using std::endl;

    int main()
    {
    vector<Student_info> did;

    // read the student records
    Student_info student;
    while(read(cin, student))
    {
    did.push_back(student);
    }

    // extract records that don't complete all the homeworks
    vector<Student_info> didnt = extract_didnt(did);

    // verify thatthe analyses will show us something
    if(did.empty())
    {
    cout << "No student did all the homework!" << endl;
    return 1;
    }
    if(didnt.empty())
    {
    cout << "Every student did all the homework!" << endl;
    return 1;
    }

    // do the analysis
    write_analysis(cout, "median", median_analysis, did, didnt);
    write_analysis(cout, "average", average_analysis, did, didnt);
    write_analysis(cout, "median of homework turned in", optimistic_median_analysis, did, didnt);

    return 0;
    }
    - -

    Exercise 6-8

    Write a single function that can be used to classify students based on criteria of your choice. Test this function by using it in place of the extract_fails program, and use it in the program to analyze student grades.

    -

    Solution & Results

    This exercise asks us to generalize the program in exercise 6-7 such that the program can deal with various kinds of criteria. A possible solution is to pass the criteria as arguments to the more generalized function classify which will classifies an input sequence according to the criteria. By doing so, we can pass different criteria to classify the students’ records based on our own preference.
    Let’s define such classify function and put it in a sparate file.

    -

    classify.cpp

    -
    #include <vector>
    #include <algorithm>
    #include "Student_info.h"

    using std::vector; using std::stable_partition;

    vector<Student_info> classify(vector<Student_info> &v, bool criteria(const Student_info &s))
    {
    vector<Student_info>::iterator iter = stable_partition(v.begin(), v.end(), criteria);
    vector<Student_info> criteria_false(iter, v.end());
    v.erase(iter, v.end());
    return criteria_false;
    }
    - -

    classify.h

    -
    #ifndef GUARD_CLASSIFY_H
    #define GUARD_CLASSIFY_H

    #include <vector>
    #include "Student_info.h"

    std::vector<Student_info> classify(std::vector<Student_info> &,
    bool criteria(const Student_info &));

    #endif /* GUARD_CLASSIFY_H */
    - -

    Simialr to the extract_didnt function defined in exercise 6-7, this function returns a vector that contains all records that do not satisfy the criteria. The input sequence has also been modified and consequently contains only the records that statisfy the criteria.

    -

    To test the new function, the test program is designed to classify the students’ records as three groups (if available):

    -
      -
    1. students who fail the final grade
    2. -
    3. students who pass the grade, and are awarded the first class honours
    4. -
    5. students who pass the grade, and are awarded the second class honours
    6. -
    -

    Firstly, we classify students according to criteria pgrade, as a result, the records are divided into two groups. By then, students contains only the passing records. Secondly, we further classify students according to another criterion which allowing us to extract students who are awarded the first class honours.

    -

    The test program includes following files:

    -
      -
    1. classify.cpp, classify.h(as shown above)
    2. -
    3. mainfunction.cpp
    4. -
    5. criteria.cpp, criteria.h
    6. -
    7. grade.cpp, grade.h
    8. -
    9. Student_info.cpp, Student_info.h
    10. -
    11. print.cpp, print.h
    12. -
    -

    I present ecah file according to above order in below followed by the test results.

    -

    A complete program

    mainfunction.cpp

    -
    #include <iostream>			// to get the declaration of cin, cout, endl
    #include <algorithm> // to get the declaration of max
    #include <vector> // to get the declaration of vector
    #include <string> // to get the declaration of string
    #include "Student_info.h" // to get the declaration of Student_info
    #include "criteria.h" // to get the declaration of criteria
    #include "classify.h" // to get the declaration of classify
    #include "print.h" // to get the declaration of print

    using std::vector; using std::cout;
    using std::cin; using std::endl;
    using std::string; using std::max;

    int main()
    {
    vector<Student_info> students;
    Student_info record;
    string::size_type maxlen = 0;

    // read and store all the records, and find the length of the longest name
    while(read(cin, record))
    {
    maxlen = max(maxlen, record.name.size());
    students.push_back(record);
    }

    // classfiy students according to the criteria: fail and pass
    vector<Student_info> fails = classify(students, pgrade);

    // verify that the analyses will show us something
    if(fails.empty())
    {
    cout << "All students pass the grade!" << endl;
    return 1;
    }
    if(students.empty())
    {
    cout << "All students fail the grade!" << endl;
    return 1;
    }

    // classfiy students according to the criteria: first class and second class
    vector<Student_info> first_class_honours = classify(students, second_class_honours);

    // write lines of outputs
    cout << "Students who fails the grade include:" << endl;
    print(fails, maxlen);

    cout << endl;
    if(!first_class_honours.empty())
    {
    cout << "Students who are awarded first class honours include:" << endl;
    print(first_class_honours, maxlen);
    }
    else
    cout << "None of students is awarded first class honours." << endl;

    cout << endl;
    if(!students.empty())
    {
    cout << "Students who are awarded second class honours include:" << endl;
    print(students, maxlen);
    }
    else
    cout << "congratulations, all the students who "
    "pass the grade are awarded first class honours." << endl;

    return 0;
    }
    - -

    criteria.cpp

    -
    #include "grade.h"
    #include "Student_info.h"

    bool fgrade(const Student_info &s)
    {
    return grade(s) < 60;
    }

    bool pgrade(const Student_info &s)
    {
    return !fgrade(s);
    }

    bool first_class_honours(const Student_info &s)
    {
    return grade(s) >= 85;
    }

    bool second_class_honours(const Student_info &s)
    {
    return (!fgrade(s) && !first_class_honours(s));
    }
    - -

    criteria.h

    -
    #ifndef GUARD_CRITERIA_H
    #define GUARD_CRITERIA_H

    #include "Student_info.h"

    bool fgrade(const Student_info &);
    bool pgrade(const Student_info &);
    bool first_class_honours(const Student_info &);
    bool second_class_honours(const Student_info &s);
    #endif /* GUARD_CRITERIA_H */
    - -

    grade.cpp

    -
    #include <algorithm>
    #include <stdexcept>
    #include <vector>
    #include "grade.h"
    #include "Student_info.h"

    using std::domain_error; using std::istream;
    using std::vector; using std::sort;

    // grade function 1
    double grade(const Student_info &s)
    {
    return grade(s.midterm, s.final, s.homework);
    }

    // grade function 2
    double grade(double midterm, double final, const vector<double> &hw)
    {
    if (hw.size() == 0)
    throw domain_error("student has done no homework");
    return grade(midterm, final, median(hw));
    }

    // grade function 3
    double grade(double midterm, double final, double homework)
    {
    return 0.2*midterm + 0.4*final + 0.4*homework;
    }

    // compute the median of vector<double>
    double median(vector<double> vec)
    {
    // get the size of the vector
    typedef vector<double>::size_type vec_size;
    vec_size size = vec.size();

    // check whether the empty is empty
    if (size == 0)
    throw domain_error("median of an empty vector");

    // sort the grades
    sort(vec.begin(), vec.end());

    // compute the median homework grade
    vec_size mid = size/2;
    return size % 2 == 0 ? (vec[mid] + vec[mid - 1])/2 : vec[mid];
    }
    -

    grade.h

    -
    #ifndef GUARD_grade_h
    #define GUARD_grade_h

    // grade.h
    #include<vector>
    #include "Student_info.h"

    double grade(const Student_info &);
    double grade(double, double, const std::vector<double> &);
    double grade(double, double, double);
    double median(std::vector<double>);
    #endif
    - -

    Student_info.cpp

    -
    #include "Student_info.h"
    using std::vector; using std::istream;

    // argument to the function sort
    bool compare(const Student_info &x, const Student_info &y)
    {
    return x.name < y.name;
    }

    // read the info
    istream & read(istream &is, Student_info &s)
    {
    // reads and store the student's name, midterm and final exam grades
    is >> s.name >> s.midterm >> s.final;

    // reads and store all homework grades
    read_hw(is, s.homework);
    return is;
    }

    // read all homework grades
    istream & read_hw(istream &in, vector<double> &hw)
    {
    if (in){
    //get rid of previous contents
    hw.clear();

    // read homework grades
    double x;
    while(in >> x)
    hw.push_back(x);

    // clear the stream so that input will work for the next student
    in.clear();
    }
    return in;
    }
    - -

    Student_info.h

    -
    #ifndef GUARD_Student_info
    #define GUARD_Student_info

    #include<iostream>
    #include<string>
    #include<vector>

    struct Student_info{
    std::string name;
    double midterm, final;
    std::vector<double> homework;
    };

    bool compare(const Student_info &, const Student_info &);
    std::istream & read(std::istream &, Student_info &);
    std::istream & read_hw(std::istream &, std::vector<double> &);
    #endif
    - -

    print.cpp

    -
    #include <iostream>
    #include <vector>
    #include <iomanip>
    #include <string>
    #include "grade.h"
    #include "print.h"
    #include "Student_info.h"

    using std::cout; using std::setprecision;
    using std::endl; using std::streamsize;
    using std::string; using std::vector;

    void print(const vector<Student_info> &records, const string::size_type &maxlen)
    {
    for (vector<Student_info>::const_iterator iter = records.begin(); iter != records.end(); ++iter)
    {
    // write the name, blanks
    cout << (*iter).name << string(maxlen + 1 - (*iter).name.size(), ' ');

    // compute and write the final grade
    double final_grade = grade(*iter);
    streamsize prec = cout.precision();
    cout << setprecision(3) << final_grade << setprecision(prec) << endl;
    }
    }
    - -

    print.h

    -
    #ifndef GUARD_PRINT_H
    #define GUARD_PRINT_H

    #include <string>
    #include <vector>
    #include "Student_info.h"

    void print(const std::vector<Student_info> &records, const std::string::size_type &maxlen);

    #endif /* GUARD_PRINT_H */
    - -

    Test results

    Inputs:

    Phqgh 24.7879 58.6263 64.0505
    Nlfdx 95.4242 27.3636 91.0404
    Cxggb 16.1818 95.4747 26.7172
    Uxwfn 35.9495 3.11111 22.3333
    Tkjpr 68.4747 44.6263 57.3737
    Pnrvy 16.3535 90.4242 88.0606
    Syycq 5.90909 29.7071 50.0606
    Ffmzn 84.5455 56.404 66.7677
    Vwsre 23.3737 38.1818 82.2929
    Fxtls 4.30303 77.0606 73.8687
    Dpooe 29.7778 73.9798 12.8687
    Ejuvp 55.7475 31.5253 50.5051
    Poeyl 91.0707 37.5758 87.5354
    Jvrvi 21.8889 22.4646 6.30303
    Hwqnq 55.101 59.2424 37.4848
    Jjloo 91.3636 74.202 96.2121
    Whmsn 34.5354 99.1818 38
    Sfzkv 48.8384 7.21212 10.1717
    Lyjyh 51 49.1919 56.9899
    Nkkuf 89.0202 95.8586 93.4343

    Outputs:

    Students who fails the grade include:
    Phqgh 54
    Cxggb 52.1
    Uxwfn 17.4
    Tkjpr 54.5
    Syycq 33.1
    Vwsre 52.9
    Dpooe 40.7
    Ejuvp 44
    Jvrvi 15.9
    Hwqnq 49.7
    Sfzkv 16.7
    Lyjyh 52.7

    Students who are awarded first class honours include:
    Jjloo 86.4
    Nkkuf 93.5

    Students who are awarded second class honours include:
    Nlfdx 66.4
    Pnrvy 74.7
    Ffmzn 66.2
    Fxtls 61.2
    Poeyl 68.3
    Whmsn 61.8
    - -

    The test program shows our function works perfectly.

    -
    -

    Exercise 6-9

    Use a library algorithm to concatenate all the elements of a vector.

    -

    Solution & Results

    The complete program below gives two possible solutions.

    -

    The logic of the first solution is that

    -
      -
    1. take each element in vector vec as an independent container (i.e. a string).
    2. -
    3. apply copy to each independent string and copy all characters from it into a new string to hold the final result.
    4. -
    5. loop thru the vector vec and repeat step 2.
    6. -
    -

    Finally, each character contained in each element of the vector vec is copied and stored into the new string, which has the same effect as concatenating all the elements of vec.

    -

    The first solution should works fine but is not a better solution. Because if we don’t use copy, we can simply concatenate each element of vec without accessing each character.

    -

    The second solution applies accumulate algorithm to concatenate each element from vec directly. accumulate returns the sum of all elements of a sequence denoted by its first two arguments. The third argument provides the initial value for the summation and determines the type of the returned value. In this case, the initial value is set to an empty string.

    -

    A complete program

    -
    #include <iostream>	// to get the declaration of cout
    #include <algorithm> // to get the declaration of copy
    #include <iterator> // to get the declaration of back_inserter
    #include <vector> // to get the declaration of vector
    #include <string> // to get the declaration of string
    #include <numeric> // to get the declaration of accumulate

    using std::vector; using std::string;
    using std::cout; using std::copy;
    using std::endl; using std::back_inserter;
    using std::accumulate;

    int main()
    {
    vector<string> vec{"Please", "write", "an", "analysis", "function"};

    // method 1
    string vecCopy1;
    for(vector<string>::const_iterator iter = vec.begin(); iter != vec.end(); ++iter)
    {
    copy((*iter).begin(), (*iter).end(), back_inserter(vecCopy1));
    }
    cout << vecCopy1 << endl;

    // method 2
    string vecCopy2;
    vecCopy2 = accumulate(vec.begin(), vec.end(), vecCopy2);
    cout << vecCopy2 << endl;

    return 0;
    }
    -

    The results below show that both two methods work fine and give correct results.

    -

    Results

    -
    Pleasewriteananalysisfunction
    Pleasewriteananalysisfunction
    - -
    -

    Reference

    Koenig, A. and Moo, B.E., 2000. Accelerated C++.

    -

    Lippman, S.B., Lajoie, J. and Moo, B.E., 2013. C++ Primer, 5th edt.

    -]]>
    - - Programming - - - Accelerated C++ Solutions - -
    - - C++ - Using library algorithms (Part 3) - /2018/03/25/C-Using-library-algorithms-Part-3/ - Revisit the classifying program

    Recalling the extract_fails function developed in last chapter:

    -
    vector<Student_info> extract_fails(vector<Student_info>& students)
    {
    vector<Student_info> fail;
    vector<Student_info>::size_type i = 0;
    // invariant:elements [0, i) of students represent passing grades
    while (i != students.size())
    {
    if (fgrade(students[i]))
    {
    fail.push_back(students[i]);
    students.erase(students.begin() + i);
    }
    else
    ++i;
    }
    return fail;
    }
    -

    Instead of using member functions like push_back and erase, we can also apply library algorithms to accomplish the task that extracts failing records. Following parts introduce two algorithmic solutions.

    -

    A two-pass solution

    vector<Student_info> extract_fails(vector<Student_info> &students)
    {
    vector<Student_info> fails;
    remove_copy_if(students.begin(), students.end(), back_inserter(fails), pgrade);

    students.erase(remove_if(students.begin(), students.end(), fgrade), students.end());
    return fails;
    }
    -

    This function uses remove_copy_if algorithm to copy all failing records from students into a new vector fails. remove_copy_if finds and “removes”(i.e. not copy) all values that satisfy the predicate pgrade. Apparently, pgrade is a predicate on grades and returns true if the final grade is equal or greater than 60. In other words, it is a predicate that reverts the results of calling fgrade.

    -
    bool pgrade(const Student_info &s)
    {
    return !fgrade(s);
    }
    -

    However, remove_copy_if works similar to remove_copy. Both two algorithms won’t change the input sequence instead they copy the remaining values into a new vector. Therefore, we need one step more to modify students:

    -
    students.erase(remove_if(students.begin(), students.end(), fgrade), students.end());
    -

    In this statements, remove_if finds and “removes” all values that satisfies the predicate fgrade. However, the tricky point is that how does it remove those faling records?

    -

    The removal is done by replacing the elements for which pred returns true by the next element for which it does not, and signaling the new size of the shortened range by returning an iterator to the element that should be considered its new past-the-end element. The relative order of the elements not removed is preserved, while the elements between the returned iterator and last are left in a valid but unspecified state (see remove_if).

    -

    For example, we have a sequence of final grades:

    -
    s0(pass), s1(fail), s2(fail), s3(pass), s4(pass), s5(fail)
    -

    After the execution of remove_if function like above, the sequence becomes

    -
    s0(pass), ns1(pass), ns2(pass), s3(pass), s4(pass), s5(fail)
    -

    It copies passing records, s3 and s4, and replacing the faling records, s1 and s2. Then, it returns an iterator that refers to s3. The elements beteen s3 and s.end() (i.e. one past s5) are still there. Therefore, we need to erase them using erase function, resulting that only passing grades are left. The erase function here takes two arguments which denote a range of elements to be erased. It returns the new students.end().

    -

    A single pass solution

    The two pass solution seems not very ideal as it computes each final grade twice. Another algorithm stable_partition solves this by seperating a sequence into two pieces using a predicate. For example,

    -
    stable_partition(students.begin(), students.end(), pgrade);
    -

    This algorithm rearranges the sequence and put all elements that satisfy pgrade ahead of the elements that do not satisfy pgrade. Then, it returns an iterator that denotes the first element of the second group (i.e. one past the last element of the first group). If all values do not satisfy the pgrade, the returned iterator refers to the first element of the sequence.

    -

    Now we can rewrite the extract_fail function applying stable_partition algorithm.

    -
    vector<Student_info> extract_fails(vector<Student_info> &students)
    {
    vector<Student_info>::iterator iter = stable_partition(students.begin(), students.end(), pgrade);
    vector<Student_info> fails(iter, students.end());
    students.erase(iter, students.end());
    return fails;
    }
    -

    A complete program

    I revised the program that developed in Exercise 5-6 by replacing the original extract_fails function with above two functions. The new program accomplishes a simple comparison of the two alternatives. All files and the test results can be found in the remainder of this post.

    -

    file list

    -
      -
    1. mainfunction.cpp.
    2. -
    3. extract_fails.cpp, extract_fails.h: declares and defines functions including fgrade, pgrade, extract_fails_m1, extract_fails_m2.
    4. -
    5. grade.cpp, grade.h: declares and defines functions including grade, median.
    6. -
    7. Student_info.cpp, Student_info.h: declares and defines functions including compare, read, read_hw, and Student_info type struct.
    8. -
    9. print.cpp, print.h: declares and defines functions including print.
    10. -
    -
    -

    mainfunction.cpp

    -
    // Accelerated C++ Solutions Exercises 6-0: the revised extract_fails
    #include <algorithm>
    #include <stdexcept>
    #include <iostream>
    #include <vector>
    #include <string>
    #include <iterator>
    #include <chrono>
    #include "Student_info.h"
    #include "grade.h"
    #include "print.h"
    #include "extract_fails.h"

    using std::cin; using std::sort; using std::max;
    using std::cout; using std::endl; using std::domain_error;
    using std::string; using std::max; using std::stable_partition;
    using std::vector; using std::remove_copy;
    using std::remove_copy_if; using std::back_inserter;

    int main()
    {
    vector<Student_info> students;
    Student_info record;
    string::size_type maxlen = 0;

    // read and store all the records, and find the length of the longest name
    while(read(cin, record))
    {
    maxlen = max(maxlen, record.name.size());
    students.push_back(record);
    }
    try{
    // measure the performance for two pass solution
    typedef std::chrono::high_resolution_clock Clock;
    Clock::time_point startTime = Clock::now(); // get current time
    vector<Student_info> fails = extract_fails_m1(students); // extract records for failing students
    Clock::time_point endTime = Clock::now(); // get current time

    cout << "The two pass solution took me "
    << std::chrono::duration_cast<std::chrono::duration<double>>(endTime - startTime).count() << " seconds" << endl;


    // measure the performance for one pass solution
    startTime = Clock::now(); // get current time
    fails = extract_fails_m2(students); // extract records for failing students
    endTime = Clock::now(); // get current time

    cout << "The one pass solution took me "
    << std::chrono::duration_cast<std::chrono::duration<double>>(endTime - startTime).count() << " seconds" << endl;

    // write each line of outputs for passing students
    if (!students.empty())
    {
    //alphabetize the records

    sort(students.begin(), students.end(), compare);
    cout << "Students who passed: " << endl;
    print(students, maxlen);
    }
    else
    cout << "What a pity! all students failed.";

    // write a blank line
    cout << endl;

    // // write each line of outputs for failing students
    if(!fails.empty())
    {
    //alphabetize the records

    sort(fails.begin(), fails.end(), compare);
    cout << "Students who failed: " << endl;
    print(fails, maxlen);
    }
    else
    cout << "Congratulations! all students passed.";

    }catch(domain_error e){
    cout << e.what();
    }

    return 0;
    }
    - -

    extract_fails.cpp

    -
    #include <vector>			
    #include <algorithm>
    #include <iterator>
    #include "Student_info.h"
    #include "grade.h"
    #include "extract_fails.h"

    using std::vector; using std::stable_partition;
    using std::remove_if; using std::remove_copy_if;
    using std::back_inserter;

    // predicate
    bool fgrade(const Student_info &s)
    {
    return grade(s) < 60;
    }
    // predicate
    bool pgrade(const Student_info &s)
    {
    return !fgrade(s);
    }

    // two-pass solution
    vector<Student_info> extract_fails_m1(vector<Student_info> &students)
    {
    vector<Student_info> fails;
    remove_copy_if(students.begin(), students.end(), back_inserter(fails), pgrade);

    students.erase(remove_if(students.begin(), students.end(), fgrade), students.end());
    return fails;
    }

    // single-pass solution
    vector<Student_info> extract_fails_m2(vector<Student_info> &students)
    {
    vector<Student_info>::iterator iter = stable_partition(students.begin(), students.end(), pgrade);
    vector<Student_info> fails(iter, students.end());
    students.erase(iter, students.end());
    return fails;
    }
    - -

    extract_fails.h

    -
    #ifndef GUARD_EXTRACT_FAILS_H
    #define GUARD_EXTRACT_FAILS_H

    #include <vector>
    #include "Student_info.h"

    bool fgrade(const Student_info &);
    bool pgrade(const Student_info &);
    std::vector<Student_info> extract_fails_m1(std::vector<Student_info> &);
    std::vector<Student_info> extract_fails_m2(std::vector<Student_info> &);

    #endif /* GUARD_EXTRACT_FAILS_H */
    - -

    grade.cpp

    -
    #include <algorithm>
    #include <stdexcept>
    #include <vector>
    #include "grade.h"
    #include "Student_info.h"

    using std::domain_error; using std::istream;
    using std::vector; using std::sort;

    // grade function 2
    double grade(const Student_info &s)
    {
    return grade(s.midterm, s.final, s.homework);
    }

    // grade function 2
    double grade(double midterm, double final, const vector<double> &hw)
    {
    if (hw.size() == 0)
    throw domain_error("student has done no homework");
    return grade(midterm, final, median(hw));
    }

    // grade function 3
    double grade(double midterm, double final, double homework)
    {
    return 0.2*midterm + 0.4*final + 0.4*homework;
    }

    // compute the median of vector<double>
    double median(vector<double> vec)
    {
    // get the size of the vector
    typedef vector<double>::size_type vec_size;
    vec_size size = vec.size();

    // check whether the empty is empty
    if (size == 0)
    throw domain_error("median of an empty vector");

    // sort the grades
    sort(vec.begin(), vec.end());

    // compute the median homework grade
    vec_size mid = size/2;
    return size % 2 == 0 ? (vec[mid] + vec[mid - 1])/2 : vec[mid];
    }
    ···

    **grade.h**
    ```c++
    #ifndef GUARD_grade_h
    #define GUARD_grade_h

    // grade.h
    #include<vector>
    #include "Student_info.h"

    double grade(const Student_info &);
    double grade(double, double, const std::vector<double> &);
    double grade(double, double, double);
    double median(std::vector<double>);
    #endif
    - -

    Student_info.cpp

    -
    #include <vector>
    #include <iostream>
    #include "Student_info.h"

    using std::vector; using std::istream;

    bool compare(const Student_info &x, const Student_info &y)
    {
    return x.name < y.name;
    }

    istream & read(istream &is, Student_info &s)
    {
    // reads and store the student's name, midterm and final exam grades
    is >> s.name >> s.midterm >> s.final;

    // reads and store all homework grades
    read_hw(is, s.homework);
    return is;
    }

    istream & read_hw(istream &in, vector<double> &hw)
    {
    if (in){
    //get rid of previous contents
    hw.clear();

    // read homework grades
    double x;
    while(in >> x)
    hw.push_back(x);

    // clear the stream so that input will work for the next student
    in.clear();
    }
    return in;
    }
    - -

    Student_info.h

    -
    #ifndef GUARD_Student_info
    #define GUARD_Student_info

    #include<iostream>
    #include<string>
    #include<vector>

    struct Student_info{
    std::string name;
    double midterm, final;
    std::vector<double> homework;
    };

    bool compare(const Student_info &, const Student_info &);
    std::istream & read(std::istream &, Student_info &);
    std::istream & read_hw(std::istream &, std::vector<double> &);
    #endif
    - -

    print.cpp

    -
    #include <iostream>
    #include <vector>
    #include <iomanip>
    #include <string>
    #include "grade.h"
    #include "print.h"
    #include "Student_info.h"

    using std::cout; using std::setprecision;
    using std::endl; using std::streamsize;
    using std::string; using std::vector;

    void print(const vector<Student_info> &records, const string::size_type &maxlen)
    {
    for (vector<Student_info>::const_iterator iter = records.begin(); iter != records.end(); ++iter)
    {
    // write the name, blanks
    cout << (*iter).name << string(maxlen + 1 - (*iter).name.size(), ' ');

    // compute and write the final grade
    double final_grade = grade(*iter);
    streamsize prec = cout.precision();
    cout << setprecision(3) << final_grade << setprecision(prec) << endl;
    }
    }
    - -

    print.h

    -
    #ifndef GUARD_PRINT_H
    #define GUARD_PRINT_H

    #include <string>
    #include <vector>
    #include "Student_info.h"

    void print(const std::vector<Student_info> &, const std::string::size_type &);

    #endif /* GUARD_PRINT_H */
    - -

    Performance comparison

    - - - - - - - - - - - - - - - - - - - - - - -
    Number of linesTwo-pass solutionSingle-pass solution
    100.001035 seconds0.000000 seconds
    10000.006006 seconds0.000993 seconds
    100000.017035 seconds0.003023 seconds
    -

    The results shows as expected that the single-pass solution has much better performance that the two-pass solution.

    -

    A simple summary

    Algorithm act on container elements but do not act on containers and do not change the size of a container.

    -]]>
    - - Programming - - - C++ - Notes - -
    - - C++ - Using library algorithms (Part 2) - /2018/03/22/C-Using-library-algorithms-Part-2/ - Revisit the grading program

    This section redesigns the grading program described in Organizing programs with data structures. The new program is required to include extra two grading schemes(Koenig and Moo 2000):

    -

    1. using the average instead of the median, and treating those assignments that the student failed to turn in as zero.
    2. using the median of only the assignments that the student actually submitted.

    -

    Further, the program should solve following problems based on these grading schemes:

    -

    1. reading all the student records, separating the students who did all the homework from the others.
    2. apply each of the grading schemes to all the students in each group, and report the median grade of each group.

    -

    Now, let’s follow the instructions and finish this program step by step.

    -

    Reading and Separating the students records

    According to the first problem, we need to read and separate the students records into two groups, one group of which includes students who did all homeworks, and another group includes students who didn’t submit all homeworks. The read function has been introduced before and hence no further analysis. What is the next is to use a predict on students’ records and store the records separately based on the check results.

    -
    bool did_all_hw(const Student_info &s)
    {
    return ((find(s.homework.begin(), s.homework.end(), 0)) == s.homework.end());
    }
    -

    The return statements in the function body means that if none of the homework grades equal to 0, the function returns true. The idea behind it is that overdue homeworks are given 0 grades.

    -

    Now this part can be accomplished with following code:

    -
    // to hold two groups of students records
    vector<Student_info> did, didnt;
    Student_info student;

    // read all the records and separating them based on whether all homework was done
    while(read(cin, student))
    {
    if(did_all_hw(student))
    did.push_back(student);
    else
    didnt.push_back(student);
    }

    // check that both groups contain dada
    if (did.empty())
    {
    cout << "No student did all the homework!" << endl;
    return 1;
    }
    if (didnt.empty())
    {
    cout << "Every student did all the homework!" << endl;
    return 1;
    }
    -

    Three grading schemes

    Before we go into the second problem, we need to solve three grading schemes first. All schemes compute the final grade as the weighted average of midterm exam grade, final exam grade, and homework grade. The grade function below returns the final grade if it is called.

    -
    double grade(double midterm, double final, double homework)
    {
    return 0.2*midterm + 0.4*final + 0.4*homework;
    }
    -

    Midterm grade and final exam grade are fixed values once the information are read in. However, the homework grade can be computed using different methods depending on the grading schemes. Specifically:

    -
      -
    1. scheme 1: computes the homework grade as the median value of homework grades.
    2. -
    3. scheme 2: computes the homework grade as the average value of homework grades.
    4. -
    5. scheme 3: computes the homework grade as the median value of homework grades excluding the zero grades (i.e. grades for overdue homeworks).
    6. -
    -

    In addition, if one did not do homework at all, his homework grade would be set to 0.

    -

    Fundamentally, there are two types of homework grade, one is the median value and another is the average value. Here are the two functions that return the median value and average value of a sequence of double values stored in a vector, respectively.

    -
    // fundermental functions 1: returns the median value of vector<double>
    double median(vector<double> vec)
    {
    // get the size of the vector
    typedef vector<double>::size_type vec_size;
    vec_size size = vec.size();

    // check whether the empty is empty
    if (size == 0)
    throw domain_error("median of an empty vector");

    // sort the grades
    sort(vec.begin(), vec.end());

    // compute the median homework grade
    vec_size mid = size/2;
    return size % 2 == 0 ? (vec[mid] + vec[mid - 1])/2 : vec[mid];
    }

    // fundermental functions 2: returns the average value of vector<double>
    double average(const vector<double> &v)
    {
    // check whether the empty is empty
    if (v.empty())
    throw domain_error("average of an empty vector");

    return accumulate(v.begin(), v.end(), 0.0) / v.size();
    }
    -

    What’s the new idea in above functions is that we compute the average using the accumulate algorithm. The accomulate is defined in the staandard header . It takes three arguments, the first two of which denotes a range of values to be summed while the third arguments gives the initial value for the summation. It is worth noting that we uses 0.0 rather than 0 due to the fact the type of the resulted sum is the type of the third argument.

    -

    Note that I slightly changes the average function provided in the book for the purpose of avoiding the case that v.size() == 0. As mentioned earlier, if one did not submit one or more homeworks, he would get 0 grades for the corresponding homeworks. But, the program may encounter the case that there is no any inputs for the homeworks, e.g. when someone did not do homeworks at all. To response this case, the homework grade is set to 0 directly. We need to catch the exception and handle the case in next step.

    -

    The median grading scheme

    We are familar with the first grading scheme, that is, the original scheme we used to compute the final grade, in previous chapters. For convenience sake, I renamed the original functions grade as median_grade (as shown below).

    -
    / grading scheme 1: final grade is based on the median homework grade
    double median_grade(const Student_info &s)
    {
    return median_grade(s.midterm, s.final, s.homework);
    }

    // grading scheme 1: overloaded median_grade function
    double median_grade(double midterm, double final, const vector<double> &hw)
    {
    if (hw.size() == 0)
    throw domain_error("student has done no homework");
    return grade(midterm, final, median(hw));
    }

    // grading scheme 1: auxiliary median_grade function
    double median_grade_aux(const Student_info &s)
    {
    try{
    return median_grade(s);
    }catch (domain_error){
    // students who did no homework at all, get 0 homework grade
    return grade(s.midterm, s.final, 0);
    }
    }
    -

    It can be observed that a new function, median_grade_aux ,is included for the median grading scheme. The reason is that we’ll pass the median_gade as an argument, however, we cann’t pass an overloaded function easily. Therefore, we define an auxiliary function and handle the exception in this function. As analysed above, the function return a 0 homework grade once catches an exception.

    -

    The average grading scheme

    The average based final grade is computed exactly the same as the median version as long as we replace the median value as the average value. The function handles the exception in the same manner.

    -
    // grading scheme 2: final grade is based on average homework grades
    double average_grade(const Student_info &s)
    {
    try{
    return grade(s.midterm, s.final, average(s.homework));
    }catch (domain_error){
    // students who did no homework at all, get 0 homework grade
    return grade(s.midterm, s.final, 0);
    }
    }
    -

    The optimistic_median grading scheme

    The third grading scheme is named as optimistic median as it ignores the zero homework grades and consequently improves students’ overall performances. Following code shows how this function works.

    -
    // grading scheme 3: final grade is based on median of the completed homework grades,
    // and students who did no homework at all will get 0 homework grade
    double optimistic_median(const Student_info &s)
    {
    vector<double> nonzero;
    remove_copy(s.homework.begin(), s.homework.end(), back_inserter(nonzero), 0);

    if(nonzero.empty())
    return grade(s.midterm, s.final, 0);
    else
    return grade(s.midterm, s.final, median(nonzero));
    }
    -

    remove_copy algorithm takes four arguments. It searches from the range denoted by first two iterators, and finds all values that match a value given by its fourth argument. Then, these values are “removed” and the remaining elements are copied into a new vector nonzero. It doesn’t change the input sequence. In addition, back_inserter is applied to append the copied values to the new vector, which ensures that the space is enough for holding all the elements.

    -

    Analyze the grades

    The next step is to apply each of above schemes to each group, that is, to compute the median of each group. Let’s have a look at the function that returns the median grade of a vector students based on the median homework grade.

    -
    double median_analysis(const vector<Student_info> &students)
    {
    vector<double> grades;

    transform(students.begin(), students.end(), back_inserter(grades), median_grade_aux);
    return median(grades);
    }
    -

    The function introduces a new algorithm transform which calls median_grade_aux, namely the transform function, and stores the result into a new vector. The first two iterators specify a range of elements to transform. back_inserter provides the destination and ensures that there is enough for the elements. Finally, the function returns the median value of the final grades.

    -

    Analogously, we can define another two functions that return average homework grade based median grade and optimistic median homework grade based median grade.

    -
    // average
    double average_analysis(const vector<Student_info> &students)
    {
    vector<double> grades;

    transform(students.begin(), students.end(), back_inserter(grades), average_grade);
    return median(grades);
    }

    // median of the completed homework
    double optimistic_median_analysis(const vector<Student_info> &students)
    {
    vector<double> grades;
    transform(students.begin(), students.end(), back_inserter(grades), optimistic_median);
    return median(grades);
    }
    -

    Report the analysis

    As described above, we need to compute three types of median grade for both two groups. In addition, we would better to generate a report such that we can compare two groups regarding to one specific type of median grade. The code below shows how we incorporate these driven factors into a analysis function:

    -
    void write_analysis(ostream &out, const string &name,
    double analysis(const vector<Student_info> &),
    const vector<Student_info> &did,
    const vector<Student_info> &didnt)
    {
    out << name << ": median(did) = " << analysis(did)
    << ": median(didnt) = " << analysis(didnt) << endl;
    }
    -

    This function takes four arguments, of which: the first is an object of the outstream; the second is a function; the third and fourth arguments are two objects of vector. What’ new here is that when passing a function as a parameter, we declare the parameter exactly as the same as ddeclare the function itself. Apparently, we’ll pass three analysis function defined above to this write analysis function.

    -

    A complete program

    Now, we can complete the main function:

    -

    mainfunction.cpp

    -
    #include <iostream>	    // to get the declaration of cin, cout, endl
    #include <vector> // to get the declaration of vector
    #include "Student_info.h" // to get the declaration of Student_info
    #include "did_all_hw.h" // to get the declatation of the predicate on students' records
    #include "grades_analysis.h"// to get the declaration of three analysis function
    #include "write_analysis.h" // to get the declaration of write_analysis function

    using std::vector; using std::cout;
    using std::cin; using std::endl;

    int main()
    {
    // students who did and didn't do all their homework
    vector<Student_info> did, didnt;

    // read the student records and partition time
    Student_info student;
    while(read(cin, student))
    {
    if(did_all_hw(student))
    did.push_back(student);
    else
    didnt.push_back(student);
    }
    // verify thatthe analyses will show us something
    if(did.empty())
    {
    cout << "No student did all the homework!" << endl;
    return 1;
    }
    if(didnt.empty())
    {
    cout << "Every student did all the homework!" << endl;
    return 1;
    }

    // do the analysis
    write_analysis(cout, "median", median_analysis, did, didnt);
    write_analysis(cout, "average", average_analysis, did, didnt);
    write_analysis(cout, "median of homework turned in", optimistic_median_analysis, did, didnt);

    return 0;
    }
    -

    I had seperate other functions into different files according to my own preference:

    -
      -
    1. mainfunction.cpp (as shown above).

      -
    2. -
    3. did_all_hw.cpp, did_all_hw.h: including the function did_all_hw.

      -
    4. -
    5. gradingSchemes.cpp, gradingSchemes.h: covering following functions

      -
        -
      • grade, median, average, median_grade, median_grade_aux, average_grade, optimistic_median.
      • -
      -
    6. -
    7. grades_analysis.cpp, grades_analysis.h: covering following functions

      -
        -
      • median_analysis, average_analysis, optimistic_grade_median.
      • -
      -
    8. -
    9. write_analysis.cpp, write_analysis.h: including the function write_analysis.

      -
    10. -
    11. Student_info.cpp, Stuent_info.h: covering following functions

      -
        -
      • compare, read, read_hw.
      • -
      -
    12. -
    -

    All profiles are presented below in order.

    -

    did_all_hw.cpp

    -
    #include <algorithm>		// to get the declaration of find
    #include "Student_info.h" // to get the declaration of Student_info
    #include "did_all_hw.h" // to get the declaration of did_all_hw itself

    using std::find;

    bool did_all_hw(const Student_info &s)
    {
    return ((find(s.homework.begin(), s.homework.end(), 0)) == s.homework.end());
    }
    -

    did_all_hw.h

    -
    #ifndef GUARD_DID_ALL_HW_H
    #define GUARD_DID_ALL_HW_H

    #include "Student_info.h"

    bool did_all_hw(const Student_info &s);

    #endif /* GUARD_DID_ALL_HW_H */
    - -

    gradingSchemes.cpp

    -
    #include <algorithm>	    // to get the declaration of remove_copy
    #include <numeric> // to get the declaration of accumulate
    #include <stdexcept> // to get the declaration of domain_error
    #include <vector> // to get the declaration of vector
    #include "Student_info.h" // to get the declaration of Student_info
    #include "gradingSchemes.h" // to get the declaration of all functions here to keep consistent

    using std::domain_error; using std::istream;
    using std::vector; using std::sort;
    using std::accumulate;

    // final grade function returns weighted average of midterm exam grade,
    // final exam grade, and homework grade which will be computed
    // using different methods depending on grading schemes
    double grade(double midterm, double final, double homework)
    {
    return 0.2*midterm + 0.4*final + 0.4*homework;
    }

    // fundermental functions 1: returns the median value of vector<double>
    double median(vector<double> vec)
    {
    // get the size of the vector
    typedef vector<double>::size_type vec_size;
    vec_size size = vec.size();

    // check whether the empty is empty
    if (size == 0)
    throw domain_error("median of an empty vector");

    // sort the grades
    sort(vec.begin(), vec.end());

    // compute the median homework grade
    vec_size mid = size/2;
    return size % 2 == 0 ? (vec[mid] + vec[mid - 1])/2 : vec[mid];
    }

    // fundermental functions 2: returns the average value of vector<double>
    double average(const vector<double> &v)
    {
    // check whether the empty is empty
    if (v.empty())
    { throw domain_error("average of an empty vector");}

    return accumulate(v.begin(), v.end(), 0.0) / v.size();
    }


    // grading scheme 1: final grade is based on the median homework grade
    double median_grade(const Student_info &s)
    {
    return median_grade(s.midterm, s.final, s.homework);
    }

    // grading scheme 1: overloaded median_grade function
    double median_grade(double midterm, double final, const vector<double> &hw)
    {
    if (hw.size() == 0)
    throw domain_error("student has done no homework");
    return grade(midterm, final, median(hw));
    }

    // grading scheme 1: auxiliary median_grade function
    double median_grade_aux(const Student_info &s)
    {
    try{
    return median_grade(s);
    }catch (domain_error){
    // students who did no homework at all, get 0 homework grade
    return grade(s.midterm, s.final, 0);
    }
    }

    // grading scheme 2: final grade is based on average homework grades
    double average_grade(const Student_info &s)
    {
    try{
    return grade(s.midterm, s.final, average(s.homework));
    }catch (domain_error){
    // students who did no homework at all, get 0 homework grade
    return grade(s.midterm, s.final, 0);
    }
    }

    // grading scheme 3: final grade is based on median of the completed homework grades,
    // and students who did no homework at all will get 0 homework grade
    double optimistic_median(const Student_info &s)
    {
    vector<double> nonzero;
    remove_copy(s.homework.begin(), s.homework.end(), back_inserter(nonzero), 0);

    if(nonzero.empty())
    return grade(s.midterm, s.final, 0);
    else
    return grade(s.midterm, s.final, median(nonzero));
    }
    - -

    gradingScheme.h

    -
    #ifndef GUARD_GRADING_SCHEMES_H
    #define GUARD_GRADING_SCHEMES_H

    #include<vector>
    #include "Student_info.h"

    double grade(double midterm, double final, double homework);
    double median(std::vector<double> vec);
    double average(const std::vector<double> &v);
    double median_grade(const Student_info &s);
    double median_grade(double midterm, double final, const std::vector<double> &hw);
    double median_grade_aux(const Student_info &s);
    double average_grade(const Student_info &s);
    double optimistic_median(const Student_info &s);

    #endif /* GUARD_GRADING_SCHEMES_H */
    - -

    grades_analysis.cpp

    -
    #include <vector>		// to get the declaration of vector
    #include <iterator> // to get the declaration of back_inserter
    #include <stdexcept> // to get the declaration of domain_error
    #include <algorithm> // to get the declaration of transform
    #include "Student_info.h" // to get the declaration of Student_info
    #include "grades_analysis.h" // to get the declaration of all functions here to keep consistent
    #include "gradingSchemes.h" // to get the declaration of median_grade_aux, average_grade, optimistic_median

    using std::vector; using std::transform;
    using std::domain_error; using std::back_inserter;

    // median
    double median_analysis(const vector<Student_info> &students)
    {
    vector<double> grades;

    transform(students.begin(), students.end(), back_inserter(grades), median_grade_aux);
    return median(grades);
    }

    // average
    double average_analysis(const vector<Student_info> &students)
    {
    vector<double> grades;

    transform(students.begin(), students.end(), back_inserter(grades), average_grade);
    return median(grades);
    }

    // median of the completed homework
    double optimistic_median_analysis(const vector<Student_info> &students)
    {
    vector<double> grades;
    transform(students.begin(), students.end(), back_inserter(grades), optimistic_median);
    return median(grades);
    }
    - -

    grades_analysis.h

    -
    #ifndef GUARD_GRADES_ANALYSIS_H
    #define GUARD_GRADES_ANALYSIS_H

    #include <vector>
    #include "Student_info.h"

    double median_analysis(const std::vector<Student_info> &students);
    double average_analysis(const std::vector<Student_info> &students);
    double optimistic_median_analysis(const std::vector<Student_info> &students);

    #endif /* GUARD_GRADES_ANALYSIS_H */
    - -

    write_analysis.cpp

    -
    #include <iostream>		// to get the declaration of cout, endl, ostream
    #include <string> // to get the declaration of string
    #include <vector> // to get the declaration of vector
    #include "Student_info.h" // to get the declatation of Student_info
    #include "write_analysis.h" // to get the declatation of write_analysis itself

    using std::cout; using std::endl;
    using std::string; using std::vector;
    using std::ostream;

    void write_analysis(ostream &out, const string &name,
    double analysis(const vector<Student_info> &),
    const vector<Student_info> &did,
    const vector<Student_info> &didnt)
    {
    out << name << ": median(did) = " << analysis(did)
    << ": median(didnt) = " << analysis(didnt) << endl;
    }
    -

    write_analysis.h

    -
    #ifndef GUARD_WRITE_ANALYSIS_H
    #define GUARD_WRITE_ANALYSIS_H

    #include <iostream>
    #include <string>
    #include <vector>
    #include "Student_info.h"

    void write_analysis(std::ostream &out, const std::string &name,
    double analysis(const std::vector<Student_info> &),
    const std::vector<Student_info> &did,
    const std::vector<Student_info> &didnt);


    #endif /* GUARD_WRITE_ANALYSIS_H */
    - -

    Student_info.cpp

    -
    #include "Student_info.h"
    using std::vector; using std::istream;

    // argument to the function sort
    bool compare(const Student_info &x, const Student_info &y)
    {
    return x.name < y.name;
    }

    // read the info
    istream & read(istream &is, Student_info &s)
    {
    // reads and store the student's name, midterm and final exam grades
    is >> s.name >> s.midterm >> s.final;

    // reads and store all homework grades
    read_hw(is, s.homework);
    return is;
    }

    // read all homework grades
    istream & read_hw(istream &in, vector<double> &hw)
    {
    if (in)
    {
    //get rid of previous contents
    hw.clear();

    // read homework grades
    double x;
    while(in >> x)
    hw.push_back(x);

    // clear the stream so that input will work for the next student
    in.clear();
    }
    return in;
    }
    - -

    Student_info.h

    -
    #ifndef GUARD_Student_info
    #define GUARD_Student_info

    #include<iostream>
    #include<string>
    #include<vector>

    struct Student_info{
    std::string name;
    double midterm, final;
    std::vector<double> homework;
    };

    bool compare(const Student_info &, const Student_info &);
    std::istream & read(std::istream &, Student_info &);
    std::istream & read_hw(std::istream &, std::vector<double> &);
    #endif
    -

    A simple Test

    Inputs:

    Xxzrz 87.1414 3.48485 18 58 0
    Oketl 98.0909 57.7273 92 38 22
    Gzqrc 91.1515 88.5657 34 2 11
    Ojway 86.7576 33.697 16 44 42
    Psajl 99.5758 76.9293 12 75 89
    Aovlz 88.0101 89.5556 85 2 23
    Cpwsr 57.3232 32.697 89 21 54
    Izcob 55.3434 49.4141 60 45 12
    Ijtvd 96.8788 29.4949 49 66 37
    Ldvgy 5.88889 82.5556 1 14 34
    Mborx 55.8586 53.1212 45 32 8
    Xohgm 82.8182 44.9697 61 29 22
    Xotog 59.9293 39.5354 10 54 24
    Nfetc 22.6869 18.8788 91 58 5
    Kljug 24.3434 74.7273 70 33 59
    Zjenp 70.6364 68.9293 80 2 85
    Pjsrd 25.4343 24.2323 81 61 72
    Lchhb 31.9293 42.2222 0 64 86
    Zobiw 70.3535 33.1111 67 96 60
    Fsksr 24.1919 25.7677 2 58 94
    Dcyzj 84.1818 64.1919 87 0 52
    Qcozi 15.7677 27.4343 9 64 58
    Eeddp 74.2525 27.2929 20 23 28
    Mmtat 61.9596 25.6465 16 2 60
    Muvnp 47.5354 20.9091 63 88 24
    Azuxm 13.5859 78.6566 0 77 7

    Outputs:
    median: median(did) = 46.1475: median(didnt) = 42.9273
    average: median(did) = 45.4202: median(didnt) = 44.3273
    median of homework turned in: median(did) = 46.1475: median(didnt) = 52.1273
    - -

    Merging three analysis functions into one single function

    It has been observed that three analysis functions are in fact defined in the same manner. The only difference between them is the statements:

    -
    transform(students.begin(), students.end(), back_inserter(grades), //transform function to be passed);
    -

    Therefore, we can redefine the analysis function such that the transform function can be passed to the analysis function as an argument. We have learned how to do this from the write_analysis function. Now let’s define it:

    -
    double analysis(const vector<Student_info> &students, double grading_scheme(const Student_info &s))
    {
    vector<double> grades;

    transform(students.begin(), students.end(), back_inserter(grades), grading_scheme);
    return median(grades);
    }
    -

    For example, if we want to compute the average based median grade, we would call this function like:

    -
    analysis(did, average_grade);
    -

    Correspondingly, we need to change the write_analysis function as well.

    -
    void write_analysis(ostream &out, const string &name,
    double grading_scheme(const Student_info &s),
    const vector<Student_info> &did,
    const vector<Student_info> &didnt)
    {
    out << name << ": median(did) = " << analysis(did, grading_scheme)
    << ": median(didnt) = " << analysis(didnt, grading_scheme) << endl;
    }
    -

    I define the new write_analysis function such that it takes the grading_scheme functions as its second arguments. To qualify the use of the analysis function, I add #include “grade_analysis.h” into write_analysis.cpp. In addition, I add #include “gradingSchemes.h” into mainfunction.cpp for qualifing the use of three grading_scheme functions. Please find the revised files shown in below.

    -

    modified file 1: grades_analysis.cpp

    -
    #include <vector>		// to get the declaration of vector
    #include <iterator> // to get the declaration of back_inserter
    #include <stdexcept> // to get the declaration of domain_error
    #include <algorithm> // to get the declaration of transform
    #include "Student_info.h" // to get the declaration of Student_info
    #include "grades_analysis.h" // to get the declaration of all functions here to keep consistent
    #include "gradingSchemes.h" // to get the declaration of the median function

    using std::vector; using std::transform;
    using std::domain_error; using std::back_inserter;

    double analysis(const vector<Student_info> &students, double grading_scheme(const Student_info &s))
    {
    vector<double> grades;

    transform(students.begin(), students.end(), back_inserter(grades), grading_scheme);
    return median(grades);
    }
    - -

    modified file 2: grades_analysis.h

    -
    #ifndef GUARD_GRADES_ANALYSIS_H
    #define GUARD_GRADES_ANALYSIS_H

    #include <vector>
    #include "Student_info.h"

    double analysis(const std::vector<Student_info> &students,
    double grading_scheme(const Student_info &s));

    #endif /* GUARD_GRADES_ANALYSIS_H */
    - -

    modified file 3: write_analysis.cpp

    -
    #include <iostream>		// to get the declaration of cout, endl, ostream
    #include <string> // to get the declaration of string
    #include <vector> // to get the declaration of vector
    #include "Student_info.h" // to get the declatation of Student_info
    #include "write_analysis.h" // to get the declatation of write_analysis itself
    #include "grades_analysis.h" // to get the declaration of analysis function

    using std::cout; using std::endl;
    using std::string; using std::vector;
    using std::ostream;

    void write_analysis(ostream &out, const string &name,
    double grading_scheme(const Student_info &s),
    const vector<Student_info> &did,
    const vector<Student_info> &didnt)
    {
    out << name << ": median(did) = " << analysis(did, grading_scheme)
    << ": median(didnt) = " << analysis(didnt, grading_scheme) << endl;
    }
    - -

    modified file 4: write_analysis.h

    -
    #ifndef GUARD_WRITE_ANALYSIS_H
    #define GUARD_WRITE_ANALYSIS_H

    #include <iostream>
    #include <string>
    #include <vector>
    #include "Student_info.h"

    void write_analysis(std::ostream &out, const std::string &name,
    double grading_scheme(const Student_info &s),
    const std::vector<Student_info> &did,
    const std::vector<Student_info> &didnt);


    #endif /* GUARD_WRITE_ANALYSIS_H */
    - -

    modified file 5: mainfunction.cpp

    -
    #include <iostream>		// to get the declaration of cin, cout, endl
    #include <vector> // to get the declaration of vector
    #include "Student_info.h" // to get the declaration of Student_info
    #include "did_all_hw.h" // to get the declatation of the predicate on students' records
    #include "write_analysis.h" // to get the declaration of write_analysis function
    #include "gradingSchemes.h" // to get the declaration of three grading functions

    using std::vector; using std::cout;
    using std::cin; using std::endl;

    int main()
    {
    // students who did and didn't do all their homework
    vector<Student_info> did, didnt;

    // read the student records and partition time
    Student_info student;
    while(read(cin, student))
    {
    if(did_all_hw(student))
    did.push_back(student);
    else
    didnt.push_back(student);
    }
    // verify thatthe analyses will show us something
    if(did.empty())
    {
    cout << "No student did all the homework!" << endl;
    return 1;
    }
    if(didnt.empty())
    {
    cout << "Every student did all the homework!" << endl;
    return 1;
    }

    // do the analysis
    write_analysis(cout, "median", median_grade_aux, did, didnt);
    write_analysis(cout, "average", average_grade, did, didnt);
    write_analysis(cout, "median of homework turned in", optimistic_median, did, didnt);

    return 0;
    }
    - -

    I tested this program using the same inputs as I used in the original program. They work exactly the same and yield same outputs.

    -
    -

    To be continued.

    -]]>
    - - Programming - - - C++ - Notes - -
    - - C++ - Using library algorithms (Part 1) - /2018/03/21/C-Using-library-algorithms/ - Generic algorithms for operations on strings

    copy

    Recalling the vcat function described in Vertical concatenation:

    -
    for (vector<string>::const_iterator i = bottom.begin(); i != bottom.end(); ++i)
    {
    ret.push_back((*i));
    }
    -

    Using a for loop, all elements of vector bottom are copied and appended to the end of the vector ret.
    An alternative method is to use the insert function:

    -
    ret.insert(ret.end(), bottom.begin(), bottom.end());
    -

    These two methods rely on the member functions of a specified container. A more general solution, using generic algorithm copy, to solve the same question,

    -
    copy(bottom.begin(), bottom.end(), back_inserter(ret));
    -

    The generic algorithms provided in the standard library implement classic algorithms via iterator operations. They do not depend on any specific type of container. The copy algorithm is an algorithm that writes elements to a container. It takes three iterators, of which, the first two iterators indicates the input range while the third iterator denotes the starting point of the destination sequence.

    -

    back_inserter() is a iterator adaptor which is a function that returns an appropriate iterator (has type of back_insert_iterator) according to the argument. All intertor adaptors are defined in header . In this case, back_inserter() takes a container as its arguments and yields an iterator that, when used as a destination, appends values to the container. But noting that

    -
    copy(bottom.begin(), bottom.end(), ret.end())
    -

    is not allowed as there is no element at ret.end.

    -

    After this function completes, the size of ret increases by bottom.size(). The copy function returns an iterator ((has type of back_insert_iterator) that refers to the next postion of the last element of ret.

    -

    find_if algorithm

    Now, using another generic algorithm find_if, we can simplify the split function described in Taking strings apart.

    -
    // true if the argument is whitespace, false otherwise
    bool space(char c)
    {
    return isspace(c);
    }

    // false is the argument is whitespace, true otherwise
    book not_space(char c)
    {
    return !isspace(c);
    }

    vector<string> split(const_string &str)
    {
    typedef string::const_iterator iter;
    vector<string> ret;

    iter i = str.begin();
    while(i != str.end())
    {
    // ignore leading blanks
    i = find_if(i, str.end(), not_space)

    // find end of next word
    iter j = find_if(i, str.end(), space);

    // copy the characters in [i, j)
    if(i != str.end())
    ret.push_back(string(i, j));
    i = j;
    }
    return ret;
    }
    -

    First, in this function, we use iterators instead of indices. The core algorithm is that use find_if to firstly find a nonwhiltespace character and a whitespace that closely follows, which determines the range of a word.

    -

    find_if algorithm takes three arguments, of which, first two are iterators that denotes a sequence while the third one is a predicate on characters. The algorithm calls the predicate to test each element starting from *i, and stops when the predicate returns true, i.e. the character is not whitespace in this case. It returns an iterator that refers to the first element that satisfies the predicate. If find_if failed to find an element that satifies the predicate, it returns its second arguments, i.e. str.end() in this case.

    -

    It has been observed that we didn’t use isspace() directly instead we write our own functions. This is because that isspace() is overloaded depending on arguments and can’t be passed as an argument to a template function.

    -

    Another new usage is string(i,j), which constructs a new string that copies the value from the range [i, j).

    -

    equal algorithm

    Now we introduce another algorithm equal, which can simplify the isPalindromes function described in Exercise 5-10.

    -
    bool is_palindrome(const string &s)
    {
    return equal(s.begin(), s.end(), s.rbegin());
    }
    -

    It is consise enough compared with the one I wrote before. The equal algorithm takes three iterators, of which first two indicate the range of the first sequence while the third iterator denotes the inital position of the second sequence. It compares these two sequence and returns true otherwise returns false.

    -

    It is known that begin() returns an iterator that refers to the first element in a container. On the contrary, rbegin() returns an reverse iterator that refers to the last element in a container. Similarly, rend() returns to an iterator that refers to the position that before the first element.

    -

    Finding URLs

    Considering that one or more URLs are embedded in a string, we are requested to write a function that finds each URL. A URL is a sequence of characters of the form:

    -
    protocol-name://resource-name
    -

    where protocol-name contains only letters, and resource-name may consist of letters, digits, and certain punctuation characters(Koenig and Moo 2000).

    -

    To some extent, this exercise is similar to the split function. Though determining the range of such a string is much complex than finding the range for a word, the idea is the same. The strategy can be divided into three steps:

    -
      -
    1. looking for the characters :// that might be a part of a URL.
    2. -
    3. if we find these characters, then it looks backward to find the protocol-name and determines the begining position of the URL; then, it looks forward to find the resource-name and determines the ending position of the URL.
    4. -
    5. finally, we store the URL according the two positions and continue searching for next URL until we finishes the whole document held in the single string.
    6. -
    -

    We starts from the last step, providing the first two steps have been completed.

    -

    function to find URLs

    -
    vector<string> find_urls(const string &s)
    {
    vector<string> ret;
    typedef string::const_iterator iter;
    iter b = s.begin(), e = s.end();

    // look through the entire input
    while (b != e)
    {
    // look for one or more letters followed by ://
    b = url_beg(b, e);

    // if we found it
    if(b != e)
    {
    // get the rest of the URL
    iter after = url_end(b, e);

    // remember the URL
    ret.push_back(string(b, after));

    // advance b and check for more URLs on this line
    b = after;
    }
    }
    return ret;
    }
    -

    Function url_beg responsibles for finding the :// and then finding the begining position of the URL accordingly. url_end responsibles for finding the end position of the URL based on the results of url_beg.

    -

    As mentioned earlier, string(b, after) constructs the URL with two iterators which denotes a range [b, after) of a sequence, i.e. the URL. In other words, b is the interator that denotes the first element of the URL while after denotes the position that one past the last element in the URL. After stores the URL into vector ret, we set the initial position for finding next URL as the end of the previous URL, with setting b = after.

    -

    Now we consider url_end function first, providing that the begining position of a URL has been found.

    -
    string::const_iterator  url_end(string::const_iterator b, string::const_iterator e)
    {
    return find_if(b, e, not_url_char);
    }
    -

    We have explained the find_if algorithm in above. It calls the precidate not_url_char and test each element starting from the position where b denotes, and stops until finds the element that makes the predicate returns true. It returns an iterator that refers to the first element that satifies the predicate, and returns its second arguments e if can’t find an element that satisfies the predicate. Now let’s write the predicate:

    -
    bool not_url_char(char c)
    {
    // characters, in addition to alphanumerics, that can appear in a URL
    static const string url_ch = "~;/?:@=&$-_.+!*'(),";

    // see whether c can appear in a URL and return the negative
    return !(isalnum(c) || find(url_ch.begin(), url_ch.end(), c) != url_ch.end());
    }
    -

    Yeah, the statements in the function body seems to be both compact and informative. The first statement defines a const string that contains all punctuation characters that can appear in a URL. What’s new here is the storage class specifier static. Local variables that declaraed with specifier static have static storage duration and are initialized only the first time. On all other calls, the declarations are skipped. In other words, the variable exists starting from the first time declaration to when the program finishes. By doing so, the const string url_ch avoids being declared each time when the predicate is called.

    -

    The return statement contains three expressions:

    - -

    In brief, if the scanned character is not a letter, not a digit, and not any punctuation character that can appear in a URL, it is regarded as not a URL character and it is the element that one past the last element of the URL.

    -

    Finally, we return to the first step, writing the function url_beg to find the initial position of the URL. One concern we have is that :// may not guaranntee a URL, for example, in the case that these characters appear at the end of a string. Therefore, we also need to make sure that there at least one or more letters before :// and at lease one character follows it.

    -
    string::const_iterator url_beg(string::const_iterator b, string::const_iterator e)
    {
    static const string sep = "://";
    typedef string::const_iterator iter;

    // i marks where the separator was found
    iter i = b;
    while((i = search(i, e, sep.begin(), sep.end())) != e)
    {
    // make sure the seperator isn't at the begining of the proticol-name
    if(i != b && i + sep.size() != e)
    {
    // beg marks the begining of the protocol-name
    iter beg = i;
    while(beg != b && isalpha(beg[-1]))
    --beg;

    // is there at least one appropriate character before and after the sep
    if (beg != i && !not_url_char(i[sep.size()]))
    return beg;
    }
    // the seperator we found wasn't part of a URL advance i past this separator
    i += sep.size();
    }
    return e;
    }
    -

    Let’s analyse from the while loop. There appears a new algorithm search in the condition:

    -
    (i == search(i, e, sep.begin(), sep.end())) != e
    -

    The search algorithm takes four iterators, of which the first two indicate the sequence while the last two denotes the initial and final positions of the sequence to be searched for. It returns an iterator the refers to the first element of [sep.begin(), sep.end()) if such sequence can be found in [i, e), otherwise it returns e. Now we know the condition is evaluated to true if :// can be found in the range of [i, e).

    -

    Assuming that :// is found in the first iteration, i is assigned the iterator that denotes :. To make sure :// reveals a valid URL, we need to make sure it is neither the start of s nor the ending of s. This is managed by the first if statement inside of the while loop. The next is to find the begining position of the URL using following statements.

    -
    iter beg = i;
    while(beg != b && isalpha(beg[-1]))
    --beg;
    -

    beg[-1] accesses the element that before the one denoted by beg, it has the same effect as

    -
    *(beg - 1)
    -

    If the element denoted by beg is a letter within the range of [b, i), the loop continues with taking one position back each iteration. After the loop finishes, we got the initial position of the URL. However, the while body may fail to be executed even once, if the condition is evaluated to false the first time. Therefore, we need to verify another condition to make sure there exists at least one appropriate character before and after the sep.

    -
    if (beg != i && !not_url_char(i[sep.size]))
    return beg;
    -

    The first expression beg != i ensures that there is at least one letter before :. In other words, the while loop above is executed at least once.

    -

    The sencond expression !not_url_char(i[sep.size]) ensures that there is at least one appropriate character follows ://. i[sep.size()] has the same effect as

    -
    *(i + sep.size())
    -

    which denotes the first character after sep (i.e. “://“).

    -

    At this phase, if the condition is evaluated to true, then the function returns the “qualified” iterator to the function caller, otherwise, the function keeps looking untill the last character.

    -

    A complete program

    The files below show the complete program. A simple test can also be found after the program.

    -

    mainfunction.cpp

    -
    #include <iostream>
    #include <vector>
    #include <string>
    #include "find_urls.h"

    using std::cout; using std::endl;
    using std::string; using std::vector;

    int main()
    {
    vector<string> urls;
    string doc{"A typical URL could have the form https://en.wikipedia.org/wiki/URL, "
    "which indicates a protocol (http), a hostname (www.example.com), "
    "and a file name (index.html). http://www.cplusplus.com/reference/algorithm/search/?kw=search"};
    urls = find_urls(doc);

    for (vector<string>::const_iterator iter = urls.begin(); iter != urls.end(); ++iter)
    cout << *iter << endl;
    }
    - -

    find_urls.cpp

    -
    // function that finds and returns an URL
    #include "find_urls.h"
    #include <vector>
    #include <string>
    #include "delimit.h"

    using std::vector;
    using std::string;

    vector<string> find_urls(const string &s)
    {
    vector<string> ret;
    typedef string::const_iterator iter;
    iter b = s.begin(), e = s.end();

    // look through the entire input
    while (b != e)
    {
    // look for one or more letters followed by ://
    b = url_beg(b, e);

    // if we found it
    if(b != e)
    {
    // get the rest of the URL
    iter after = url_end(b, e);

    // remember the URL
    ret.push_back(string(b, after));

    // advance b and check for more URLs on this line
    b = after;
    }
    }
    return ret;
    }
    -

    find_urls.h

    -
    #ifndef GUARD_FINDINGURLS_H
    #define GUARD_FINDINGURLS_H

    #include <vector>
    #include <string>

    std::vector<std::string> find_urls(const std::string &);

    #endif /* GUARD_FINDINGURLS_H */
    - -

    delimit.cpp

    -
    // contains three functions: not_url_char, url_beg, url_end
    #include <string>
    #include <algorithm>
    #include "delimit.h"

    using std::string; using std::find;
    using std::find_if; using std::search;

    // predicate on a char, check whether it is a char that can appear in a URL
    bool not_url_char(char c)
    {
    // characters, in addition to alphanumerics, that can appear in a URL
    static const string url_ch = "~;/?:@=&$-_.+!*'(),";

    // see whether c can appear in a URL and return the negative
    return !(isalnum(c) || find(url_ch.begin(), url_ch.end(), c) != url_ch.end());
    }

    // function that returns an iterator that refers to the first element of a URL
    string::const_iterator url_beg(string::const_iterator b, string::const_iterator e)
    {
    static const string sep = "://";
    typedef string::const_iterator iter;

    // i marks where the separator was found
    iter i = b;
    while((i = search(i, e, sep.begin(), sep.end())) != e)
    {
    // make sure the seperator isn't at the begining of the proticol-name
    if(i != b && i + sep.size() != e)
    {
    // beg marks the begining of the protocol-name
    iter beg = i;
    while(beg != b && isalpha(beg[-1]))
    --beg;

    // is there at least one appropriate character before and after the sep
    if (beg != i && !not_url_char(i[sep.size()]))
    return beg;
    }
    // the seperator we found wasn't part of a URL advance i past this separator
    i += sep.size();
    }
    return e;
    }

    // function that returns an iterator that denotes the postion one past the last element
    string::const_iterator url_end(string::const_iterator b, string::const_iterator e)
    {
    return find_if(b, e, not_url_char);
    }
    -

    delimit.h

    -
    #ifndef GUARD_DELIMIT_H
    #define GUARD_DELIMIT_H

    #include <string>

    bool not_url_char(char);
    std::string::const_iterator url_beg(std::string::const_iterator, std::string::const_iterator);
    std::string::const_iterator url_end(std::string::const_iterator, std::string::const_iterator);

    #endif /* GUARD_DELIMIT_H */
    - -

    Test results

    -
    https://en.wikipedia.org/wiki/URL,
    http://www.cplusplus.com/reference/algorithm/search/?kw=search
    -

    The program just has function to roughly grab the URLs, but works as expected.

    -
    -

    To be continued.

    -]]>
    - - Programming - - - C++ - Notes - -
    - - Accelerated C++ Solutions to Exercises(Chapter 5 Part 4) - /2018/03/20/Accelerated-C-Solutions-to-Exercise-Chapter-5-Part-4/ - Exercise 5-9

    Write a program to write the lowercase words in the input followed by the uppercase words.

    -

    Solution & Results

    The solution strategy is pretty straightforward: check each entered word to see whether it contains one or more uppercase letters, and store words into two containers according to the check results. I present the code and test performance in below.

    -

    A complete program

    -
    #include <iostream>
    #include <string>
    #include <vector>

    using std::cin; using std::cout;
    using std::endl; using std::string;
    using std::vector;

    // function declaration
    bool pureLowercaseWords(const string &word); // a predicate on words
    void print(const vector<string> &Words); // print elements in a vector one by one

    int main()
    {
    vector<string> lowercase_Words; // for holding words that contains only lower case letters
    vector<string> uppercase_Words; // for holding words that contains at lease one upper case letters
    string word;
    while(cin >> word)
    {
    if(!word.empty())
    {
    if(pureLowercaseWords(word))
    lowercase_Words.push_back(word);
    else
    uppercase_Words.push_back(word);
    }
    }
    cout << "Words that contain only lowercase letters: " << endl;
    print(lowercase_Words);
    cout << "Words that contain at lease one uppercase letters: " << endl;
    print(uppercase_Words);
    return 0;
    }

    bool pureLowercaseWords(const string &word)
    {
    for (string::const_iterator iter = word.begin(); iter != word.end(); ++iter)
    {
    if (isupper(*iter))
    return false;
    }
    return true;
    }

    void print(const vector<string> &Words);
    {
    for (vector<string>::const_iterator iter = Words.begin(); iter != Words.end(); ++iter)
    cout << *iter << endl;
    }
    - -

    Test
    with inputs: University of Oxford is one of best universities all over the world according to QS Ranking

    -

    The program works as expected:

    -
    Words that contain only lowercase letters: 
    of
    is
    one
    of
    best
    universities
    all
    over
    the
    world
    according
    to
    Words that contain at lease one uppercase letters:
    University
    Oxford
    QS
    Ranking
    - -
    -

    Exercise 5-10

    Palindromes are words that are spelled the same right to left as left to right. Write aprogram to find all the palindromes in a dictionary. Next, find the longest palindrome.

    -

    Solution & Results

    This project has two goals:

    -
      -
    1. telling whether a word is a palindrome.
    2. -
    3. find the longest palindrome.
    4. -
    -

    If a word is not a palindromes, we dicard it directly. On the contrary, if a word is a palindromes, we need to store it for generating a final report. To find the longest palindrome, we can compare the size of two words and keep the longer word, finally obtaining the longest palindrome(or the first observed longest one).

    -

    To determine whether a word is a palindrome, one key point is that whether the palindromes are case sensitive. I treat the palindromes as case insensitive in this program and hence the predicate on a word is based on the lowercase(or uppercase) version of a word. Another point is that whether a single character is a palindrome. I regard it as a palindrome and treat a single character as a one-letter word.

    -

    Apparently, the key is to find an algorithm that can identify a palindrome. According to the definition, palindromes are words that are spelled the same right to left as left to right. A possible solution is to divide the characters in a word into two groups, which are symmetrically located at two side of a mid point, and compare them one pair by one pair. If all pairs are same, the word is a palindromes.

    -

    Intuitively, if the number of characters in a word is odd, then, there exists one unique mid point, which splits two groups of characters. The graph below shows this case and illustrates the relationship of one pair of characters regarding to their corresponding iterators.

    -

    A word contains odd number characters

    -

    Clearly, this process can be translated to a while loop and the stopping point is the mid point (i.e. the position that iterator word.begin() + size/2 refers to).

    -

    Similarly, if the number of characters in a word is even, characters can be exactly splitted into two groups, i.e. word.size()/2 pairs.

    -

    A word contains even number characters

    -

    It can be seen from above graph, the stopping point is also the position that word.begin() + size/2 refers to. Before this point, the last pair of characters(i.e. two mid elements) are compared.

    -

    Now the predicate can be write as:

    -
    bool isPalindromes(const string &word)
    {
    typedef string::const_iterator iter;
    iter startPosition = word.begin();
    iter endPosition = word.end() - 1;
    iter midPosition = word.begin() + word.size()/2;
    while (startPosition != midPosition)
    {
    if(tolower(*startPosition) != tolower(*endPosition))
    return false;
    ++startPosition;
    --endPosition;
    }
    return true;
    }
    -

    Firstly, I choose to compare two endpoints and then move both points 1 position forward to the middle in the following iteration. Finally, the while loop stops when all pairs of characters have been compared.

    -

    Once finishes this function, we can write the main function directly. The code below shows the complete program. Note that I seperate the condition word.size() == 1 from isPalindromes(word) simply for the purpose of computational efficiency.

    -
    #include <algorithm>
    #include <iostream>
    #include <string>
    #include <vector>

    using std::cin; using std::cout;
    using std::endl; using std::string;
    using std::vector; using std::max;

    // function declarations
    bool isPalindromes(const string &word);
    void print(const vector<string> &palindromes);

    int main()
    {
    vector<string> palindromes; // hold all identified palindromes
    string longest; // hold the first observed longest palindrome

    // read words
    string word;
    while(cin >> word)
    {
    if(word.size() == 1 || isPalindromes(word))
    {
    if(longest.size() < word.size())
    longest = word;
    palindromes.push_back(word);
    }
    }

    // print all palindromes
    cout << "Following words are palindromes:" << endl;
    print(palindromes);

    // print the longest
    cout << "The longest palindrome(first observed) is:\n" << longest << endl;
    return 0;
    }

    void print(const vector<string> &palindromes)
    {
    for(vector<string>::const_iterator iter = palindromes.begin();
    iter != palindromes.end(); ++iter)
    cout << *iter << endl;
    }

    // please add the predicate here
    -

    Now let’s test how the program performs. I entered following words successively:

    -
    Rotator Anna Oxford Civic Sampling Kayak Level Madam Mom Noon shape racecar radar Redder Refer Repaper Please Rotor again Sagas impossible Solos distribution Stats Tenet Wow
    - -

    The results are displayed below:

    -
    Following words are palindromes:
    Rotator
    Anna
    Civic
    Kayak
    Level
    Madam
    Mom
    Noon
    racecar
    radar
    Redder
    Refer
    Repaper
    Rotor
    Sagas
    Solos
    Stats
    Tenet
    Wow
    The longest palindrome(first observed) is:
    Rotator
    -

    Yeah, the program works perfectly.

    -

    Exercise 5-11

    In text processing it is sometimes useful to know whether a word has any ascenders or descenders. Ascenders are the parts of lowercase letters that extend above the text line;in the English alphabet, the letters b, d, f, h, k, l, and t have ascenders. Similarly, the descenders are the parts of lowercase letters that descend below the line; In English,theletters g, j, p, q, and y have descenders. Write a program to determine whether a word has any ascenders or descenders. Extend that program to find the longest word in the dictionary that has neither ascenders nor descenders.

    -

    Solution & Results

    The idea to solve this exercise is similar to that described in above exercise. The first step is to check whether a word contains any ascenders or descenders, by means of comparing each character in the word to ascenders or descenders. The second step is to store the word into corresponding vector according to the check results in step 1. The longest word that has neither ascenders nor descenders can be found using the same method as above.

    -

    I present the complete program below followed by a performance test. Noting that I defined two global variables which can be accessed anywhere inside of the file. But they cannot be modified as I add const in defining them. The purpose is to avoid that these two objects will be created repeatedly if they are put into the precidate. This problem can be easily solved with specifier static, which will be introduced in chapter 6.

    -
    // Accelerated C++ Solutions Exercises 5-11
    #include <algorithm>
    #include <iostream>
    #include <string>
    #include <vector>

    using std::cin; using std::cout;
    using std::endl; using std::string;
    using std::vector; using std::max;

    // global variables
    const string ascenders = "bdfhlt";
    const string descenders = "gjpqy";

    // function declarations
    bool noAscDes(const string &word);
    void print(const vector<string> &words);

    int main()
    {
    vector<string> asc_des; // hold words that contain any ascenders or descenders
    vector<string> no_asc_des; // hold words that has neither ascenders nor descenders
    string longest_no; // find the longest word that has neither ascenders nor descenders

    // read words
    string word;
    while(cin >> word)
    {
    if(noAscDes(word))
    {
    if (longest_no.size() < word.size())
    longest_no = word;
    no_asc_des.push_back(word);
    }
    else
    asc_des.push_back(word);
    }

    cout << "Following words contain either ascenders or descenders:" << endl;
    print(asc_des);
    cout << "Following words contain neither ascenders nor descenders:" << endl;
    print(no_asc_des);
    cout << "The longest word that has neither ascenders nor descenders is:\n" << longest_no << endl;

    return 0;
    }

    bool noAscDes(const string &word)
    {
    typedef string::const_iterator iter;
    for (iter i = word.begin(); i != word.end(); ++i)
    {
    for(iter j = ascenders.begin(); j != ascenders.end(); ++j)
    {
    if (*i == *j)
    return false;
    }
    for (iter k = descenders.begin(); k != descenders.end(); ++k)
    {
    if (*i == *k)
    return false;
    }
    }
    return true;
    }

    void print(const vector<string> &words)
    {
    for(vector<string>::const_iterator iter = words.begin();
    iter != words.end(); ++iter)
    cout << *iter << endl;
    }
    - -

    Test results

    -
    Inputs:
    throughout the course of history there have been many famous speeches that changed the world from on the mount to the inaugural speeches of modern leaders their words have become an inspiration to millions of people

    Outputs:
    Following words contain either ascenders or descenders:
    throughout
    the
    of
    history
    there
    have
    been
    many
    famous
    speeches
    that
    changed
    the
    world
    from
    the
    mount
    to
    the
    inaugural
    speeches
    of
    modern
    leaders
    their
    words
    have
    become
    inspiration
    to
    millions
    of
    people
    Following words contain neither ascenders nor descenders:
    course
    on
    an
    The longest word that has neither ascenders nor descenders is:
    course
    -

    As above results show, It works fine.

    -
    -

    References

    Koenig, A. and Moo, B.E., 2000. Accelerated C++.

    -

    Lippman, S.B., Lajoie, J. and Moo, B.E., 2013. C++ Primer, 5th edt.

    -]]>
    - - Programming - - - Accelerated C++ Solutions - -
    - - Accelerated C++ Solutions to Exercises(Chapter 5 Part 3) - /2018/03/19/Accelerated-C-Solutions-to-Exercises-Chapter-5-Part-3/ - Exercise 5-5

    Write a function named center(const vector&) that returns a picture in which all the lines of the original picture are padded out to their full width, and the padding is as evenly divided as possible between the left and right sides of the picture. What are the properties of pictures for which such a function is useful? How can you tell whether a given picture has those properties?

    -

    Solution & Results

    The full width is the size of the longest string and can be obtained via a for loop.

    -
    for (vector<string>::const_iterator iter = p.begin();
    iter != p.end(); ++iter)
    {
    maxlen = max(maxlen, (*iter).size());
    }
    -

    The key is to compute the number of spaces (denoted by paddingLeft) needed for the left side padding. If we want that the padding is as evenly divided as possible, paddingLeft can be set as half of maxlen - (*iter).size(), which is the total number of spaces needed to pad out to the full width of a line.

    -

    If (maxlen - (*iter).size()) is even, the numbers of spaces on both sides of the original string are the same (i.e. the case of that padding is evenly divided).

    -

    If (maxlen - (*iter).size()) is odd, paddingLeft is in fact has the value of ((maxlen - (*iter).size()) - 1)/2, and hence is one space less that the right side padding.

    -

    In summary, the program logic is

    -
      -
    1. find maxlen.
    2. -
    3. compute paddingLeft.
    4. -
    5. construct a new line based on the string in the original picture and paddingLeft spaces.
    6. -
    7. store each new line into a vector and return it once finishes.
    8. -
    -

    The complete program and tests can be found below.

    -

    mainfunction.cpp

    -
    #include <iostream>
    #include <string>
    #include <vector>
    #include <algorithm>

    using std::cout; using std::string;
    using std::endl; using std::vector;
    using std::max;

    // function declaration
    vector<string> center(const vector<string> &p);

    int main()
    {
    // create an original picture
    vector<string> p;
    p.push_back("this is an");
    p.push_back("example");
    p.push_back("to");
    p.push_back("illustrate");
    p.push_back("framing");

    // generate the centered picture
    vector<string> np = center(p);
    for(vector<string>::const_iterator iter = np.begin();
    iter != np.end(); ++iter)
    cout << *iter << endl;

    return 0;
    }

    // define the function that returns a centered picture
    vector<string> center(const vector<string> &p)
    {
    vector<string> centeredPicture;
    string::size_type maxlen = 0;

    // get the length of the longest string
    for (vector<string>::const_iterator iter = p.begin();
    iter != p.end(); ++iter)
    {
    maxlen = max(maxlen, (*iter).size());
    }

    // pad out the left side
    for (vector<string>::const_iterator iter = p.begin();
    iter != p.end(); ++iter)
    {
    string::size_type paddingLeft = (maxlen - (*iter).size())/2;
    string s = string(paddingLeft, ' ') + (*iter);
    centeredPicture.push_back(s);
    }

    return centeredPicture;
    }
    - -

    Test Results

    -
    this is an
    example
    to
    illustrate
    framing
    - -
    -

    Exercise 5-6

    Rewrite the extract_fails function from §5.1.1/77 so that instead of erasing each failing student from the input vector v, it copies the records for the passing students to the beginning of v, and then uses the resize function to remove the extra elements from the end of v. How does the performance of this version compare with the one in §5.1.1/77?

    -

    Solution & Results

    The original function is

    -

    Method 1

    -
    vector<Student_info> extract_fails_method1(vector<Student_info>& students)
    {
    vector<Student_info> fail;
    vector<Student_info>::size_type i = 0;
    // invariant:elements [0, i) of students represent passing grades
    while (i != students.size())
    {
    if (fgrade(students[i]))
    {
    fail.push_back(students[i]);
    students.erase(students.begin() + i);
    }
    else
    ++i;
    }
    return fail;
    }
    - -

    The revised version is

    -

    Method 2

    -
    vector<Student_info> extract_fails_method2(vector<Student_info>& students)
    {

    vector<Student_info> fail;
    vector<Student_info>::size_type i = 0, j = students.size();
    // invariant:elements [0, i) of students represent passing grades
    while (i != students.size())
    {
    if (fgrade(students[i]))
    {
    fail.push_back(students[i]};
    }
    else
    {
    // the size increases by 1
    students.insert(students.begin(), students[i]);
    // the indice move forward by 1
    ++i;
    }
    ++i;
    }
    students.resize(j - fail.size());
    return fail;
    }
    -

    The next step is to measure the performance of these two function, using the same methodology as that applied in Exercise 5-4. I add above two methods into the file fails.cpp and fails.h as alternatives. To avoid redundancy, I only present the file that contains the main function in below.

    -
    // Accelerated C++ Solutions Exercises 5-2. 5-3, 5-4
    #include <algorithm> // to get declaration of max, sort
    #include <iostream> // to get declaration of cin, cout, endl
    #include <stdexcept> // to get declaration of domain_error
    #include <string> // to get declaration of string
    #include <chrono>
    #include "Student_info.h"
    #include "grade.h"
    #include "fails.h"
    #include "print.h"
    #include "info.h"


    using std::cin; using std::string;
    using std::cout; using std::max;
    using std::endl; using std::sort;
    using std::domain_error;

    int main()
    {
    info students;
    Student_info record;
    string::size_type maxlen = 0;

    // read and store all the records, and find the length of the longest name
    while(read(cin, record))
    {
    maxlen = max(maxlen, record.name.size());
    students.push_back(record);
    }

    try{
    info students_copy = students;

    // measure the performance for method1
    typedef std::chrono::high_resolution_clock Clock;
    Clock::time_point startTime = Clock::now(); // get current time
    info fails = extract_fails_method1(students); // extract records for failing students
    Clock::time_point endTime = Clock::now(); // get current time

    cout << "Method 1 took me "
    << std::chrono::duration_cast<std::chrono::duration<double>>(endTime - startTime).count()
    << " seconds" << endl;

    // measure the performance for method2
    startTime = Clock::now(); // get current time
    fails = extract_fails_method2(students_copy); // extract records for failing students
    endTime = Clock::now(); // get current time

    cout << "Method 2 took me "
    << std::chrono::duration_cast<std::chrono::duration<double>>(endTime - startTime).count()
    << " seconds" << endl;

    // write each line of outputs for passing students
    if (!students.empty())
    {
    //alphabetize the records

    sort(students.begin(), students.end(), compare);
    // students.sort(compare);
    cout << "Students who passed: " << endl;
    print(students, maxlen);
    }
    else
    cout << "What a pity! all students failed.";

    // write a blank line
    cout << endl;

    // write each line of outputs for failing students
    if(!fails.empty())
    {
    //alphabetize the records

    sort(fails.begin(), fails.end(), compare);
    //fails.sort(compare);
    cout << "Students who failed: " << endl;
    print(fails, maxlen);
    }
    else
    cout << "Congratulations! all students passed.";

    }catch(domain_error e){
    cout << e.what();
    }

    return 0;
    }
    - -

    The table below gives the comparison of these two methods, showing that method 2 performs slightly better than method 1. The result is in fact not reliable as it strongly depends on the number of passing students and failing students contained in the raw data. More experiments need to be done for more robust results.
    |Number of lines|Method 1-erase| Method 2-insert&resize|
    | :— | :— | :— |
    |10|0.000 seconds|0.000 seconds|
    |1000|0.147114 seconds|0.117083 seconds|
    |10000|0.848616 seconds|0.735503 seconds|

    -
    -

    Exercise 5-7

    Given the implementation of frame in §5.8.1/93, and the following code fragment

    -
    vector<string> v;
    frame(v);
    -

    describe what happens in this call. In particular, trace through how both the width functiona nd the frame function operate. Now, run this code. If the results differ from your expectations, first understand why your expectations and the program differ, and then change one to match the other.

    -

    Solution & Results

    Let’t recall the frame function

    -
    vector<string> frame(const vector<string> &v)
    {
    // to hold each rows of framed picture to be written
    vector<string> ret;
    // get the length of the longest string
    string::size_type maxlen = width(v);

    // first line of outputs
    string border(maxlen + 4, '*');
    ret.push_back(border);

    for (vector<string>::const_iterator iter = v.begin(); iter != v.end(); ++iter)
    {
    // new rows except two border lines
    string temp = "* " + (*iter) + string(maxlen - (*iter).size(), ' ') + " *";
    ret.push_back(temp);
    }

    // bottom line of outputs
    ret.push_back(border);
    return ret;
    }
    -

    Once calling this function with passing an empty vector v, I expected following procedures happen one after another.

    -
      -
    1. the argument is passed by const reference. v refers to the empty vector v.

      -
    2. -
    3. an empty vector ret is created.

      -
    4. -
    5. an object of type string::size_type is created and named as maxlen. maxlen is initialized with a value returned by width function.

      -
    6. -
    7. the computer enters into width function (as shown below). The arguments is also passed by const reference and hence parameter v refers to the empty vector v defined in the main function.

      -

      4.1. object maxlen is created and initialized with value of 0.

      -

      4.2. the computer enters into a for loop. The init-statement is evaluated and iter is initialized as v.begin(). Then then condition is evaluated and the result is false as v.begin() == v.end() due to the fact that no any elements in the container.

      -

      4.3 for loop quits and maxlen that equals to 0 is returned.

      -
      string::size_type width(const vector<string> &v)
      {
      string::size_type maxlen = 0;
      for (vector<string>::const_iterator iter = v.begin(); iter != v.end(); ++iter)
      {
      maxlen = max(maxlen, (*iter).size());
      }
      return maxlen;
      }
    8. -
    9. the computer goes back to the frame function and maxlen is initialized with value 0 (step 3 finishes).

      -
    10. -
    11. object border is created and initialized with 4 asterisks. Then, it is stored into vector ret.

      -
    12. -
    13. the next for loop is similar to the for loop inside the width function. It quits and has no any effects.

      -
    14. -
    15. object border is stored into vector ret again. By then, ret has two elements, both of which are strings filled by 4 asterisks.

      -
    16. -
    17. ret is returned. The computer goes back to the function caller.

      -
    18. -
    -

    According to my expection, the picture returned by frame only consist of two lines of strings each formed by 4 asterisks. Let’s verify this the program displayed below.

    -

    A complete program

    -
    #include <string>
    #include <vector>
    #include <iostream>
    #include <algorithm>

    using std::string; using std::cout;
    using std::endl; using std::max;
    using std::vector;

    // function declarations
    string::size_type width(const vector<string> &v);
    vector<string> frame(const vector<string> &v);

    int main()
    {
    vector<string> v;
    vector<string> p = frame(v);
    for (vector<string>::iterator iter = p.begin(); iter != p.end(); ++iter)
    cout << *iter <<endl;
    return 0;
    }

    // please fill this part with the width function described above
    // please fill this part with the frame function described above
    -

    As expected, the program produces following outputs

    -
    ****
    ****
    - -
    -

    Exercise 5-8

    In the hcat function from §5.8.3/95, what would happen if we defined s outside the scope of the while? Rewrite and execute the program to confirm your hypothesis.

    -

    Solution & Results

    Code analysis

    Recalling the hcat function:

    -
    vector<string> hcat(const vector<string> &left, const vector<string> &right)
    {
    // to hold the each line of outputs
    vector<string> ret;

    // add one space column between two pictures
    string::size_type width1 = width(left) + 1;

    // iterators to look at elements from left and right respectively
    vector<string>::const_iterator iter_i = left.begin(), iter_j = right.begin();

    // continue until we've see all rows from two pictures
    while (iter_i != left.end() || iter_j != right.end())
    {
    // construct new string to hold characters from two pictures
    string s;

    // copy a row from left side picture
    if (iter_i != left.end())
    {
    s = (*iter_i);
    ++iter_i;
    }
    // pad to full width
    s += string(width1 - s.size(), ' ');

    // copy a row from right picture
    if (iter_j != left.end())
    {
    s += (*iter_j);
    ++iter_j;
    }

    // store s into vector to form a new picture
    ret.push_back(s);
    }
    return ret;
    }
    -

    Let’s analyse what happens if we define s outside the scope of the while loop:

    -
      -
    1. s is an empty string before entering into the while loop.

      -
    2. -
    3. the first iteration:

      -

      2.1. s is assigned with the copy of the first element from the left picture, and then is padded out to the full width (left side).

      -

      2.2. s is concatenated with the first element from the right picture.

      -

      2.3. s is stored into vector ret to formatted the first line of the new picture.

      -
    4. -
    5. the nth interation:

      -

      case 1: the numbers of rows of both pictures are the same. The process of the rest iterations are similar to the first iteration until the while condition is evaluated to false. As a result, all rows of both pictures are copied into the new picture. The function has the same effect as the original one, where s is defined inside the while loop.

      -

      case 2: the left side picture has less rows than the right side picture. When iter_i == left.end() but iter_j != right.end(), the first if statements are ignored but the next statement (shown below) will result in compilation errors.

      -
      s += string(width1 - s.size(), ' ');
      -

      remembering that s.size() is the summation of widths of both sides, i.e. width1 plus the width of the right side picture. Therefore, width1 - s.size() is negative.

      -

      case 3: if left side picture has more rows than the right side picture. The process of the rest iterations are similar to case 1. The function has same effect as the original function, where s is defined inside of the while loop.

      -
    6. -
    -

    Rewrite the original program

    Now, let’s verify above expectations using following program, covering three cases and two functions(i.e. the original one and the modified one). The files of the program includes mainfunction.cpp, pics.cpp, pics.h, width.cpp. width.h, print.cpp and print.h. More details about the original program can be found in putting strings together.

    -

    mainfunction.cpp

    -
    #include <iostream>
    #include <string>
    #include <vector>
    #include "pics.h"
    #include "print.h"

    using std::cout; using std::endl;
    using std::vector; using std::string;

    int main()
    {
    vector<string> pic1;
    pic1.push_back("aaaaaa");
    pic1.push_back("bbbbbbbbbbbbb");
    pic1.push_back("ccc");

    vector<string> pic2;
    pic2.push_back("ddddddddddddd");
    pic2.push_back("eeeeeee");
    pic2.push_back("fffffffffff");
    pic2.push_back("ggggg");

    vector<string> left, right;
    // Test case 1
    left = right = pic1;

    // Test case 2
    // left = pic1; right = pic2;

    // Test case 3
    // left = pic2; right = pic1;

    vector<string> hcatPicture1 = hcat_function1(left, right);
    vector<string> hcatPicture2 = hcat_function2(left, right);

    cout << "The original function produces a picture shown as follows: " << endl;
    print(hcatPicture1);
    cout << endl;
    cout << "The modified function produces a picture shown as follows: " << endl;
    print(hcatPicture2);

    return 0;
    }
    - -

    pics.cpp

    -
    // functions that generate different pictures
    #include <string> // to get declaration of string
    #include <vector> // to get declaration of vector
    #include "width.h"
    #include "pics.h"

    using std::string; using std::vector;

    // to horizontally concatenate two pictures: original function
    vector<string> hcat_function1(const vector<string> &left, const vector<string> &right)
    {
    // to hold the each line of outputs
    vector<string> ret;

    // add one space column between two pictures
    string::size_type width1 = width(left) + 1;

    // iterators to look at elements from left and right respectively
    vector<string>::const_iterator iter_i = left.begin(), iter_j = right.begin();

    // continue until we've see all rows from two pictures
    while (iter_i != left.end() || iter_j != right.end())
    {
    // construct new string to hold characters from two pictures
    string s;

    // copy a row from left side picture
    if (iter_i != left.end())
    {
    s = (*iter_i);
    ++iter_i;
    }
    // pad to full width
    s += string(width1 - s.size(), ' ');

    // copy a row from right picture
    if (iter_j != left.end())
    {
    s += (*iter_j);
    ++iter_j;
    }

    // store s into vector to form a new picture
    ret.push_back(s);
    }
    return ret;
    }

    // to horizontally concatenate two pictures: modified function
    vector<string> hcat_function2(const vector<string> &left, const vector<string> &right)
    {
    // to hold the each line of outputs
    vector<string> ret;

    // add one space column between two pictures
    string::size_type width1 = width(left) + 1;

    // iterators to look at elements from left and right respectively
    vector<string>::const_iterator iter_i = left.begin(), iter_j = right.begin();

    // construct new string to hold characters from two pictures
    string s;
    // continue until we've see all rows from two pictures
    while (iter_i != left.end() || iter_j != right.end())
    {
    // copy a row from left side picture
    if (iter_i != left.end())
    {
    s = (*iter_i);
    ++iter_i;
    }
    // pad to full width
    s += string(width1 - s.size(), ' ');

    // copy a row from right picture
    if (iter_j != right.end())
    {
    s += (*iter_j);
    ++iter_j;
    }

    // store s into vector to form a new picture
    ret.push_back(s);
    }
    return ret;
    }
    - -

    pics.h

    -
    #ifndef GUARD_PICS_H
    #define GUARD_PICS_H

    #include <string>
    #include <vector>

    std::vector<std::string> hcat_function1(const std::vector<std::string> &left,
    const std::vector<std::string> &right);

    std::vector<std::string> hcat_function2(const std::vector<std::string> &left,
    const std::vector<std::string> &right);

    #endif /* GUARD_PICS_H */
    - -

    width.cpp

    -
    // function returns the size of the longest string in a vector<string>
    #include <string> // to get declaration of string
    #include <vector> // to get declaration of vector
    #include <algorithm> // to get declaration of max
    #include "width.h" // to get declaration of the function itself

    using std::string; using std::vector; using std::max;


    string::size_type width(const vector<string> &v)
    {
    string::size_type maxlen = 0;
    for (vector<string>::const_iterator iter = v.begin(); iter != v.end(); ++iter)
    {
    maxlen = max(maxlen, (*iter).size());
    }
    return maxlen;
    }
    - -

    width.h

    -
    #ifndef GUARD_WIDTH_H
    #define GUARD_WIDTH_H

    #include <string>
    #include <vector>

    std::string::size_type width(const std::vector<std::string> &v);

    #endif /* GUARD_WIDTH_H */
    - -

    print.cpp

    -
    // function to write each elements from a vector<string>
    #include "print.h"
    #include <iostream> // to get declaration of cout and endl
    #include <string> // to get declaration of string
    #include <vector> // to get declaration of vector

    using std::cout; using std::string;
    using std::endl; using std::vector;

    void print(const vector<string> &pics)
    {
    // loop thru the vector and write elements one by one
    for (vector<string>::const_iterator iter = pics.begin(); iter != pics.end(); ++iter)
    {
    cout << (*iter) << endl;
    }
    }
    - -

    print.h

    -
    #ifndef GUARD_OUTPUT_H
    #define GUARD_OUTPUT_H

    #include <string>
    #include <vector>

    void print(const std::vector<std::string> &pics);

    #endif /* GUARD_OUTPUT_H */
    -

    Test results

    Case 1

    -
    The original function produces a picture shown as follows: 
    aaaaaa aaaaaa
    bbbbbbbbbbbbb bbbbbbbbbbbbb
    ccc ccc

    The modified function produces a picture shown as follows:
    aaaaaa aaaaaa
    bbbbbbbbbbbbb bbbbbbbbbbbbb
    ccc ccc
    - -

    Case 2

    -
    This application has requested the Runtime to terminate it in an unusual way. 
    Please contact the application's support team for more information.
    terminate called after throwing an instance of 'std::length_error'
    what(): basic_string::_M_create
    - -

    Case 3

    -
    The original function produces a picture shown as follows: 
    ddddddddddddd aaaaaa
    eeeeeee bbbbbbbbbbbbb
    fffffffffff ccc
    ggggg

    The modified function produces a picture shown as follows:
    ddddddddddddd aaaaaa
    eeeeeee bbbbbbbbbbbbb
    fffffffffff ccc
    ggggg
    - -

    The results of all three cases are exactly as I expected and analysed in last section.

    -
    -

    To be continued.

    -]]>
    - - Programming - - - Accelerated C++ Solutions - -
    - - Accelerated C++ Solutions to Exercises(Chapter 5 Part 2) - /2018/03/17/Accelerated-C-Solutions-to-Exercises-Chapter-5-Part-2/ - Exercise 5-2, 5-3, 5-4

    5-2: Write the complete new version of the student-grading program, which extracts records for failing students, using vectors. Write another that uses lists. Measure the performance difference on input files of ten lines, 1,000 lines, and 10,000 lines.

    -

    5-3: By using a typedef, we can write one version of the program that implements either a vector-based solution or a list-based one. Write and test this version of the program.

    -

    5-4: Look again at the driver functions you wrote in the previous exercise. Note that it ispossible to write a driver that differs only in the declaration of the type for the data structure that holds the input file. If your vector and list test drivers differ in any other way, rewrite them so that they differ only in this declaration.

    -

    Solution

    I’ll give solution to 5-4 directly as these three exercises are closely connected. The strategy can be divided into three steps:

    -
      -
    1. write drivers for using list or vector to hold input files.
    2. -
    3. add the function to extract records for failing students, and add the drivers for files where the declaration of specified container is needed.
    4. -
    5. change indices to iterators if necessary because list doesn’t support access elements via indices.
    6. -
    7. measure the performance difference of two containers on input files of 10 lines, 1000 lines and 10000 lines.
    8. -
    -

    Step 1

    To switch the use of list and vector, I follows the suggestion of 5-3;

    -
    // typedef list<Student_info> info;
    typedef vector<Student_info> info;
    -

    The alias info can represent either the type list or vector and hence allows us switch from one version to another simply via modifying this declaration.

    -

    However, the type declared above is not only used in the file that contains the main function, but may also needed in other files. Therefore, I separate the declaration from the main function and create a header for this reason.
    info.h

    -
    #ifndef GUARD_INFO_H
    #define GUARD_INFO_H

    #include <list>
    #include <vector>
    #include "Student_info.h"

    //typedef std::vector<Student_info> info;
    typedef std::list<Student_info> info;

    #endif /* GUARD_INFO_H */
    -

    All files that want to use the name info include the header where declares the name info.

    -

    Step 2

    This step adds two functions: one is to extract the records for failing students; another one is a predicate on failing grades.
    It can be observed that the header “info.h” is included for the purpose of defining the container fail to holding information of the failing students.
    fails.cpp

    -
    #include "Student_info.h"
    #include "grade.h"
    #include "fails.h"
    #include "info.h"

    // the predicate for students who failed
    bool fgrade(const Student_info &s)
    {
    return grade(s) < 60;
    }

    // function to extract the failed student records
    info extract_fails(info &students)
    {
    info fail;
    info::iterator iter = students.begin();

    while(iter != students.end())
    {
    if(fgrade(*iter))
    {
    fail.push_back(*iter);
    iter = students.erase(iter);
    }
    else
    {
    ++iter;
    }
    }
    return fail;
    }
    - -

    fails.h

    -
    #ifndef GUARD_FAILS_H
    #define GUARD_FAILS_H

    #include "Student_info.h"
    #include "info.h"

    bool fgrade(const Student_info &s);
    info extract_fails(info &students);

    #endif /* GUARD_FAILS_H*/
    - -

    Step 3

    The last change needs to be done is using iterators instead of indices. To avoid messy, I rewrite the output section as a function named print.

    -

    print.cpp

    -
    #include <iostream>
    #include <iomanip>
    #include <string>
    #include "grade.h"
    #include "print.h"
    #include "info.h"

    using std::cout; using std::setprecision;
    using std::endl; using std::streamsize;
    using std::string;

    void print(const info &records, const string::size_type &maxlen)
    {
    for (info::const_iterator iter = records.begin(); iter != records.end(); ++iter)
    {
    // write the name, blanks
    cout << (*iter).name << string(maxlen + 1 - (*iter).name.size(), ' ');

    // compute and write the final grade
    double final_grade = grade(*iter);
    streamsize prec = cout.precision();
    cout << setprecision(3) << final_grade << setprecision(prec) << endl;
    }
    }
    -

    print.h

    -
    #ifndef GUARD_PRINT_H
    #define GUARD_PRINT_H

    #include <string>
    #include "info.h"

    void print(const info &records, const std::string::size_type &maxlen);

    #endif /* GUARD_PRINT_H */
    - -

    Note that I didn’t add try block here as the exceptions may be thrown early in the process of extracting the failing records.

    -

    Step 4

    To measure the performance of the vector based program and list based program, I uses members of the time library . Specifically:

    -
    typedef std::chrono::high_resolution_clock Clock;
    Clock::time_point startTime = Clock::now(); // get current time
    info fails = extract_fails(students); // extract records for failing students
    Clock::time_point endTime = Clock::now(); // get current time

    cout << "It took me "
    << std::chrono::duration_cast<std::chrono::seconds>(endTime - startTime).count()
    << " seconds" << endl;
    -

    The usage of std::chrono::high_resolution_clock refers to
    high_resolution_clock.

    -

    A complete program

    By now, I have introduced the main changes relative to the original program. Therefore, the complete program includes following files

    - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
    header filessource files
    info.h
    fails.hfails.cpp
    print.hprint.cpp
    Student_info.hStudent_info.cpp
    grade.hgrade.cpp
    mainfunction.cpp
    -

    I present the rest files in below. Noting that there are two major differences between the vector-based version and the list-based version. First is the declaration metioned above. The second is that the usage of sort function for two types of containers are different, for vectors, the statement is

    -
    sort(students.begin(), students.end(), compare);
    -

    while for lists,

    -
    students.sort(compare);
    -

    Strictly speaking, my solution doesn’t meet the requirements of 5-4. But I haven’t find a better one.

    -

    mainfunction.cpp

    -
    #include <algorithm>		// to get declaration of max, sort
    #include <iostream> // to get declaration of cin, cout, endl
    #include <stdexcept> // to get declaration of domain_error
    #include <string> // to get declaration of string
    #include <chrono>
    #include "Student_info.h"
    #include "grade.h"
    #include "fails.h"
    #include "print.h"
    #include "info.h"


    using std::cin; using std::string;
    using std::cout; using std::max;
    using std::endl; using std::sort;
    using std::domain_error;

    int main()
    {
    info students;
    Student_info record;
    string::size_type maxlen = 0;

    // read and store all the records, and find the length of the longest name
    while(read(cin, record))
    {
    maxlen = max(maxlen, record.name.size());
    students.push_back(record);
    }

    try{
    // measure the performance
    typedef std::chrono::high_resolution_clock Clock;
    Clock::time_point startTime = Clock::now(); // get current time
    info fails = extract_fails(students); // extract records for failing students
    Clock::time_point endTime = Clock::now(); // get current time

    cout << "It took me "
    << std::chrono::duration_cast<std::chrono::seconds>(endTime - startTime).count()
    << " seconds" << endl;

    // write each line of outputs for passing students
    if (!students.empty())
    {
    //alphabetize the records

    //sort(students.begin(), students.end(), compare);
    students.sort(compare);
    cout << "Students who passed: " << endl;
    print(students, maxlen);
    }
    else
    cout << "What a pity! all students failed.";

    // write a blank line
    cout << endl;

    // // write each line of outputs for failing students
    if(!fails.empty())
    {
    //alphabetize the records

    //sort(fails.begin(), fails.end(), compare);
    fails.sort(compare);
    cout << "Students who failed: " << endl;
    print(fails, maxlen);
    }
    else
    cout << "Congratulations! all students passed.";

    }catch(domain_error e){
    cout << e.what();
    }

    return 0;
    }
    - -

    Student_info.cpp

    -
    #include <vector>
    #include <iostream>
    #include "Student_info.h"

    using std::vector; using std::istream;

    bool compare(const Student_info &x, const Student_info &y)
    {
    return x.name < y.name;
    }

    istream & read(istream &is, Student_info &s)
    {
    // reads and store the student's name, midterm and final exam grades
    is >> s.name >> s.midterm >> s.final;

    // reads and store all homework grades
    read_hw(is, s.homework);
    return is;
    }

    istream & read_hw(istream &in, vector<double> &hw)
    {
    if (in){
    //get rid of previous contents
    hw.clear();

    // read homework grades
    double x;
    while(in >> x)
    hw.push_back(x);

    // clear the stream so that input will work for the next student
    in.clear();
    }
    return in;
    }
    - -

    Student_info.h

    -
    #ifndef GUARD_Student_info
    #define GUARD_Student_info

    // Student_info.header file
    #include<iostream>
    #include<string>
    #include<vector>

    struct Student_info{
    std::string name;
    double midterm, final;
    std::vector<double> homework;
    };

    bool compare(const Student_info &, const Student_info &);
    std::istream & read(std::istream &, Student_info &);
    std::istream & read_hw(std::istream &, std::vector<double> &);
    #endif
    - -

    grade.cpp

    -
    #include <algorithm>
    #include <stdexcept>
    #include <vector>
    #include "grade.h"
    #include "Student_info.h"

    using std::domain_error; using std::istream;
    using std::vector; using std::sort;

    // grade function 1
    double grade(const Student_info &s)
    {
    return grade(s.midterm, s.final, s.homework);
    }

    // grade function 2
    double grade(double midterm, double final, const vector<double> &hw)
    {
    if (hw.size() == 0)
    throw domain_error("student has done no homework");
    return grade(midterm, final, median(hw));
    }

    // grade function 3
    double grade(double midterm, double final, double homework)
    {
    return 0.2*midterm + 0.4*final + 0.4*homework;
    }

    // compute the median of vector<double>
    double median(vector<double> vec)
    {
    // get the size of the vector
    typedef vector<double>::size_type vec_size;
    vec_size size = vec.size();

    // check whether the empty is empty
    if (size == 0)
    throw domain_error("median of an empty vector");

    // sort the grades
    sort(vec.begin(), vec.end());

    // compute the median homework grade
    vec_size mid = size/2;
    return size % 2 == 0 ? (vec[mid] + vec[mid - 1])/2 : vec[mid];
    }
    - -

    grade.h

    -
    #ifndef GUARD_grade_h
    #define GUARD_grade_h

    // grade.h
    #include<vector>
    #include "Student_info.h"

    double grade(const Student_info &);
    double grade(double, double, const std::vector<double> &);
    double grade(double, double, double);
    double median(std::vector<double>);
    #endif
    - -

    Results

    I also wrote a naive program that “randomly” generates thousands of names and grades.

    -
    #include <cstdlib>
    #include <iostream>
    #include <string>

    using std::cout; using std::endl;
    using std::string;

    int main()
    {
    string initials{"ABCDEFGHIJKLMNOPQRSTUVWXYZ"};
    string letters{"abcdefghijklmnopqrstuvwxyz"};

    for (int i = 0; i != 10000; ++i)
    {
    int x = rand() % 26;
    int y = rand() % 26;
    int z = rand() % 26;
    int p = rand() % 26;
    int q = rand() % 26;
    double midterm = rand() % 100 + (rand() % 100)/99.0;
    double final = rand() % 100 + (rand() % 100)/99.0;
    double homework = rand() % 100 + (rand() % 100)/99.0;

    string name{initials[x], letters[y], letters[z], letters[p], letters[q]};
    cout << name << ' ' << midterm << ' ' << final << ' ' << homework << endl;
    }

    return 0;
    }
    -

    The table below gives the performance difference of two version pograms on input files of ten lines, 1,000 lines, and 10,000 lines.

    - - - - - - - - - - - - - - - - - - - - - - - -
    Number of linesvectorlist
    100.00107 seconds0.00007 seconds
    10000.136711 seconds0.003036 seconds
    100000.826406 seconds0.015598 seconds
    -

    Apparently, the list based program has much better performance than the vector based program, with using much less time in extracting the failing students’ records regardless of the file size. In addition, along with the increase of file size, the time taken by the list version program increases much slower than that taken by the vector version.

    -
    -

    To be continued.

    -]]>
    - - Programming - - - Accelerated C++ Solutions - -
    - - Accelerated C++ Solutions to Exercises(Chapter 5 Part 1) - /2018/03/14/Accelerated-C-Solutions-to-Exercises-Chapter-5/ - Exercise 5-0

    Compile, execute, and test the programs in this chapter

    -

    Solution & Results

    Please find solutions and detailed analysis in Chapter 5 Sequential Containers .

    -

    Exercise 5-1

    Design and implement a program to produce a permuted index. A permuted index is one in which each phrase is indexed by every word in the phrase. So, given the following input,

    -
    The quick brown fox 
    jumped over the fence
    -

    the output would be

    -
          The quick     brown fox 
    jumped over the fence
    The quick brown fox
    jumped over the fence
    jumped over the fence
    The quick brown fox
    jumped over the fence
    The quick brown fox
    - -

    A good algorithm is suggested in The AWK Programming Language by Aho, Kernighan, and Weinberger (Addison-Wesley, 1988). That solution divides the problem into three steps:

    -
      -
    1. Read each line of the input and generate a set of rotations of that line. Each rotation puts the next word of the input in the first position and rotates the previous first word to the end of the phrase. So the output of this phase for the first line of our input would be

      -
      The quick brown fox
      quick brown fox The
      brown fox The quick
      fox The quick brown
      -

      Of course, it will be important to know where the original phrase ends and where the rotated beginning begins.

      -
    2. -
    3. Sort the rotations.

      -
    4. -
    5. Unrotate and write the permuted index, which involves finding the separator, putting the phrase back together, and writing it properly formatted.

      -
    6. -
    -

    Solution & Results

    program logic

    The solution strategy provided above is quite straightforward. But before implementing such strategy, several stylized facts observed from the example should be listed.

    -
      -
    1. the output can be divided into two groups.
    2. -
    3. the right group of lines contains the key words (i.e. the alphabetically indexed words). The key word can be any word of a phrase. Therefore, when the first word of a phrase is indexed, the right part contains a complete phrase.
    4. -
    5. the left part and right part of the same line are complementary each other, which leads to a complete phrase. When the right part contains a complete phrase, the left part contains nothing but spaces.
    6. -
    -

    As described above, we can split the first phrase as follows

    -
                        The quick brown fox
    The quick brown fox
    The quick brown fox
    The quick brown fox
    -

    Rather than generating rotations, I would like to split one phrase into all possible combinations. Each combination will be stored into a data structure defined as below.

    -
    struct line{
    std::string left; // contains the left part of a phrase
    std::string right;// contains the right part of a phrase
    };
    -

    In addition, we can define a vector to hold all combinations (i.e. objects of type line), covering all phrases to be entered.

    -
    vector<line> combinations;
    -

    Once all combinations have been stored into the vector, we can sort the vector according to the value of the right part of each combination.

    -
    sort(combinations.begin(), combinations.end(), compare);
    - -
    bool compare(const line &x, const line &y)
    {
    return x.right < y.right;
    }
    -

    By doing so, we have alphabetized all key words. The next is to format the left parts. The content of left part of each line has been fixed as the left part is bound with the right part. All left parts are lined up vertically on the right side while their left side are padded out with certain amount of spaces depending on the longest string of the left parts.

    -

    Assuming the length of the longest string is obtained and stored in maxlen, the left part of outputs for each line will be

    -
    string(maxlen - combinations[z].left.size(), ' ') + combinations[z].left
    - -

    It is worth noting that the permuted index is case-insensitive. Therefore, the strings that compared in the compare function should be converted to lower-case letters. In addition, a line of phrase is easily to be splitted if there is no extra spaces before or after the the line, or between words. However, a common case is that users enter more spaces than needed. Therefore, it is necessary to clean needless spaces before we split one line.

    -

    In summary, the program can be divided into five logic parts

    -
      -
    1. read in multiple lines of inputs
    2. -
    3. clean each line for removing needless spaces
    4. -
    5. split each line into all possible combinations
    6. -
    7. store all combinations for all lines and get maxlen.
    8. -
    9. sort and print each line
    10. -
    -

    files

    The main function is shown below

    -

    mainfunction.cpp

    -
    #include<iostream>      // to get declaration of cin, cout and endl
    #include<string> // to get declaration of string and getline
    #include<vector> // to get declaration of vector
    #include<algorithm> // to get declaration of sort
    #include "cleaning.h"
    #include "line.h"

    using std::cin; using std::vector;
    using std::cout; using std::string;
    using std::endl; using std::getline;
    using std::sort;


    int main()
    {
    // hold combinations
    vector<line> combinations;

    // hold the length of the longest string
    string::size_type maxlen = 0;

    // read in
    string words;
    while(getline(cin, words))
    {
    // split each line, store all combinations, record maxlen
    if(!words.empty())
    split(cleaning(words), combinations, maxlen);
    }

    // format the outputs
    cout << endl;
    sort(combinations.begin(), combinations.end(), compare);

    // write each line of outputs: left part + 4 spaces + right part
    for (vector<line>::size_type z = 0; z != combinations.size(); ++z)
    cout << (string(maxlen - combinations[z].left.size(), ' ') + combinations[z].left)
    << " " << combinations[z].right << endl;
    return 0;
    }
    - -

    I put the split function and compare funtion into one file named line.

    -

    line.cpp

    -
    #include <algorithm>
    #include <string>
    #include <vector>
    #include <cctype>
    #include "line.h"

    using std::string; using std::tolower;
    using std::vector; using std::isupper;
    using std::max;

    // function to get rotations of a line and store all rotations into vector<line>
    void split(string words, vector<line> &combs, string::size_type &maxlen)
    {
    // store the original lines
    line originLine;
    originLine.right = words;
    originLine.left = "";
    combs.push_back(originLine);
    maxlen = max(maxlen, originLine.left.size());

    // store the splitted lines
    string::size_type i = 0;
    while (i != words.size())
    {
    line rotateLine;
    if (isspace(words[i]))
    {
    rotateLine.right = words.substr(i+1, words.size() - i);
    rotateLine.left = words.substr(0, i);
    combs.push_back(rotateLine);
    maxlen = max(maxlen, rotateLine.left.size());
    }
    ++i;
    }
    }

    // change characters to lowercase letters
    string tolowerString(string c)
    {
    for (string::iterator iter = c.begin(); iter != c.end(); ++iter)
    {
    if (isupper(*iter))
    *iter = tolower(*iter);
    }
    return c;
    }

    // define the arguments for sort function
    bool compare(const line &x, const line &y)
    {
    return tolowerString(x.right) < tolowerString(y.right);
    }
    - -

    line.h

    -
    #ifndef GUARD_LINE_H
    #define GUARD_LINE_H

    #include <string>
    #include <vector>

    struct line{
    std::string left;
    std::string right;
    };

    void split(std::string words, std::vector<line> &combs, std::string::size_type &maxlen);
    std::string tolowerString(std::string x);
    bool compare(const line &x, const line &y);

    #endif /* GUARD_LINE_H */
    - -

    The final part is the cleaning function that is used to remove extra spaces.

    -

    cleaning.cpp

    -
    // this function removes needless spaces for a sentence
    #include <string>
    #include <cctype>
    #include "cleaning.h"

    using std::string;
    using std::isspace;

    string cleaning(string words)
    {
    string::iterator iter = words.begin();

    // remove spaces before the real sentence begins
    while(isspace(*iter))
    iter = words.erase(iter);

    // remove extra spaces between two words
    while((iter+1) != words.end())
    {
    if (isspace(*iter) && isspace(*(iter + 1)))
    iter = words.erase(iter);
    else
    ++iter;
    }

    // remove the trailing space
    if(isspace(*iter))
    words.erase(iter);
    return words;
    }
    - -

    cleaning.h

    -
    #ifndef GUARD_CLEANING_H
    #define GUARD_CLEANING_H

    #include <string>

    std::string cleaning(std::string words);

    #endif /* GUARD_CLEANING_H */
    -

    Test performance

    Let’s test the required inputs

    -
    The quick brown fox 
    jumped over the fence

    The quick brown fox
    jumped over the fence
    The quick brown fox
    jumped over the fence
    jumped over the fence
    The quick brown fox
    jumped over the fence
    The quick brown fox
    -

    To show the robustness of this program, I choose a text fragment from The Declaration of Independence as the inputs:

    -
    He has refused for a long time, 
    after such dissolution,
    to cause others to be elected;
    whereby the legislative powers,
    incapable of annihilation,
    have returned to the people at large for their exercise;
    the State remaining in the meantime exposed
    to all the dangers of invasion from
    without and convulsion within.
    - -

    The resulted output perfectly follows the requirement of the permuted index

    -
                                He has refused for    a long time,
    after such dissolution,
    to all the dangers of invasion from
    without and convulsion within.
    incapable of annihilation,
    have returned to the people at large for their exercise;
    to cause others to be elected;
    to cause others to be elected;
    without and convulsion within.
    to all the dangers of invasion from
    after such dissolution,
    to cause others to be elected;
    have returned to the people at large for their exercise;
    the State remaining in the meantime exposed
    He has refused for a long time,
    have returned to the people at large for their exercise;
    to all the dangers of invasion from
    He has refused for a long time,
    have returned to the people at large for their exercise;
    He has refused for a long time,
    the State remaining in the meantime exposed
    incapable of annihilation,
    to all the dangers of invasion from
    have returned to the people at large for their exercise;
    whereby the legislative powers,
    He has refused for a long time,
    the State remaining in the meantime exposed
    incapable of annihilation,
    to all the dangers of invasion from
    to cause others to be elected;
    have returned to the people at large for their exercise;
    whereby the legislative powers,
    He has refused for a long time,
    the State remaining in the meantime exposed
    have returned to the people at large for their exercise;
    the State remaining in the meantime exposed
    after such dissolution,
    to all the dangers of invasion from
    whereby the legislative powers,
    the State remaining in the meantime exposed
    have returned to the people at large for their exercise;
    the State remaining in the meantime exposed
    have returned to the people at large for their exercise;
    He has refused for a long time,
    to all the dangers of invasion from
    to cause others to be elected;
    to cause others to be elected;
    have returned to the people at large for their exercise;
    whereby the legislative powers,
    without and convulsion within.
    without and convulsion within.
    - -
    -

    To be continued.

    -]]>
    - - Programming - - - Accelerated C++ Solutions - -
    - - C++ - Sequential Containers (Part 2) - /2018/03/14/C-Sequential-Containers-Part-2/ - Putting strings together

    This part provides a series of exercises about various concatenations of strings. The topic is similar to that in Chpater 2 Looping and counting.

    -

    Exercise 1

    Briefly speaking, the first exercise requires us to design a program that can add a frame for a picture (shown as below).

    -

    The original picture

    -
    this is an
    example
    to
    illustrate
    framing
    -

    The framed picture

    -
    **************
    * this is an *
    * example *
    * to *
    * illustrate *
    * framing *
    * ************
    -

    The original picture is in fact formed by several lines of strings, of which each string is an element stored in the vector named p. The framed picture adds four edges for the original picture with asterisks. There is one space between the left-edge and the initial character of the strings, and one space between the right-edge and the end of the longest string.

    -

    Exercise 2

    Once we have the framed picture, we can vertically concatenate it with the original one. The program should generate a final picture like this

    -
    this is an
    example
    to
    illustrate
    framing
    **************
    * this is an *
    * example *
    * to *
    * illustrate *
    * framing *
    **************
    -

    Two pictures are lined up along the left-hand border.

    -

    Exercise 3

    We can also perform horizontal concatenation on two pictures like the below picture shows

    -
    this is an **************
    example * this is an *
    to * example *
    illustrate * to *
    framing * illustrate *
    * framing *
    **************
    -

    Two pictures are lined up along the top border. In addition, there is a blank column that seperates two pictures.

    -

    Solutions

    The book provides solutions to each exercise listed above, I’ll put all together and write a program that can generate above pictures once.

    -

    The framed picture

    Framing a picture is an old question. The solution strategy can be divided into three steps

    -
      -
    1. obtain the length (denoted by maxlen) of the longest string in p as the width of the framed picture depends on maxlen.
    2. -
    3. create a string object that filled by asterisks for generating the top line and bottom line. The number of the astersiks is maxlen + 4.
    4. -
    5. the middle lines are formed by 1 space character and the corresponding string in p, and a certain number of spaces. The number of spaces is the difference between the maxlen and the size of the corresponding string to be concatenated.
    6. -
    -

    Below shows the function that gives the length of the longest string in a object of vector. I uses iterators instead of indices for all functions.

    -
    string::size_type width(const vector<string> &v)
    {
    string::size_type maxlen = 0;
    for (vector<string>::const_iterator iter = v.begin(); iter != v.end(); ++iter)
    {
    maxlen = max(maxlen, (*iter).size());
    }
    return maxlen;
    }
    -

    The function below generates the framed picture while keeping the original picture unchanged.

    -
    vector<string> frame(const vector<string> &v)
    {
    // to hold each rows of framed picture to be written
    vector<string> ret;
    // get the length of the longest string
    string::size_type maxlen = width(v);

    // first line of outputs
    string border(maxlen + 4, '*');
    ret.push_back(border);

    for (vector<string>::const_iterator iter = v.begin(); iter != v.end(); ++iter)
    {
    // new rows except two border lines
    string temp = "* " + (*iter) + string(maxlen - (*iter).size(), ' ') + " *";
    ret.push_back(temp);
    }

    // bottom line of outputs
    ret.push_back(border);
    return ret;
    }
    -

    Vertical concatenation

    In fact we can generate the required picture without concatenation. The first step is to generate the original picture and then the second step is to generate the framed picture. They are closely connect if no blank line between two outputs. This implies that we can create a new object of verctor and copy all strings contained in two pictures into it. The function is shown below.

    -
    vector<string> vcat(const vector<string> &top, const vector<string> &bottom)
    {
    // copy the top picture
    vector<string> ret = top;

    // copy the bottom picture one line by one line
    ret.insert(ret.end(), bottom.begin(), bottom.end());
    return ret;
    }
    -

    What’s new here is the insert function. vector, string and list all support insert function. The first parameter of this insert function means that the new elements will be inserted into a position that before the parameter specifies. The second and the third parameters indicate that the elements in the range of [bottom.begin(), bottom.end()) will be copied and inserted. This increases the length of vector. It has the same effect as the for loops shown below.

    -
    for (vector<string>::const_iterator i = bottom.begin(); i != bottom.end(); ++i)
    {
    ret.push_back((*i));
    }
    -

    Horizontal concatenation

    Similar to vertical voncatenation, we can generate the required picture without concatenation through alternately write rows of the original picture and the framed picture until all rows from two pictures are written. If the number of rows of two pictures are different, the one that has less number of rows will be replenished with blank strings.

    -

    Therefore, the key to the solution is to concatenate two strings, of which one from the original picture and another from the framed picture. As we did in the fisrt exercise, we need to pad enough spaces for each row of the original picture. Now, the number of spaces will be the difference between maxlen + 1 and the size of each string itself as we want one space column between two pictures. The code below show the function.

    -
    vector<string> hcat(const vector<string> &left, const vector<string> &right)
    {
    // to hold the each line of outputs
    vector<string> ret;

    // add one space column between two pictures
    string::size_type width1 = width(left) + 1;

    // iterators to look at elements from left and right respectively
    vector<string>::const_iterator iter_i = left.begin(), iter_j = right.begin();

    // continue until we've see all rows from two pictures
    while (iter_i != left.end() || iter_j != right.end())
    {
    // construct new string to hold characters from two pictures
    string s;

    // copy a row from left side picture
    if (iter_i != left.end())
    {
    s = (*iter_i);
    ++iter_i;
    }
    // pad to full width
    s += string(width1 - s.size(), ' ');

    // copy a row from right picture
    if (iter_j != left.end())
    {
    s += (*iter_j);
    ++iter_j;
    }

    // store s into vector to form a new picture
    ret.push_back(s);
    }
    return ret;
    }
    - -

    A complete program

    I package all three pictures into one file and the width function into another file. In addition, I define a function more to deal with output. All files are displayed below with detailed comments for each step.
    output.cpp

    -
    // function to write each elements from a vector<string>
    #include <iostream> // to get declaration of cout and endl
    #include <string> // to get declaration of string
    #include <vector> // to get declaration of vector
    #include "output.h"

    using std::cout; using std::string;
    using std::endl; using std::vector;

    void output(const vector<string> &pics)
    {
    // loop thru the vector and write elements one by one
    for (vector<string>::const_iterator iter = pics.begin(); iter != pics.end(); ++iter)
    {
    cout << (*iter) << endl;
    }
    }
    -

    output.h

    -
    #ifndef GUARD_OUTPUT_H
    #define GUARD_OUTPUT_H

    #include <string>
    #include <vector>

    void output(const std::vector<std::string> &pics);

    #endif /* GUARD_OUTPUT_H */
    - -

    width.cpp

    -
    // function returns the size of the longest string in a vector<string>
    #include <string> // to get declaration of string
    #include <vector> // to get declaration of vector
    #include <algorithm> // to get declaration of max
    #include "width.h" // to get declaration of the function itself

    using std::string; using std::vector; using std::max;

    string::size_type width(const vector<string> &v)
    {
    string::size_type maxlen = 0;
    for (vector<string>::const_iterator iter = v.begin(); iter != v.end(); ++iter)
    {
    maxlen = max(maxlen, (*iter).size());
    }
    return maxlen;
    }
    -

    width.h

    -
    #ifndef GUARD_WIDTH_H
    #define GUARD_WIDTH_H

    #include <string>
    #include <vector>

    std::string::size_type width(const std::vector<std::string> &v);

    #endif /* GUARD_WIDTH_H */
    - -

    pics.cpp

    -
    // functions that generate different pictures
    #include <string> // to get declaration of string
    #include <vector> // to get declaration of vector
    #include "width.h"
    #include "pics.h"

    using std::string; using std::vector;

    // function to generate a framed picture
    vector<string> frame(const vector<string> &v)
    {
    // to hold each rows of framed picture to be written
    vector<string> ret;
    // get the length of the longest string
    string::size_type maxlen = width(v);

    // first line of outputs
    string border(maxlen + 4, '*');
    ret.push_back(border);

    for (vector<string>::const_iterator iter = v.begin(); iter != v.end(); ++iter)
    {
    // new rows except two border lines
    string temp = "* " + (*iter) + string(maxlen - (*iter).size(), ' ') + " *";
    ret.push_back(temp);
    }

    // bottom line of outputs
    ret.push_back(border);
    return ret;
    }

    // function to vertically concatenate two pictures
    vector<string> vcat(const vector<string> &top, const vector<string> &bottom)
    {
    // copy the top picture
    vector<string> ret = top;

    // copy the bottom picture one line by one line
    ret.insert(ret.end(), bottom.begin(), bottom.end());
    return ret;
    }

    // function to horizontally concatenate two pictures
    vector<string> hcat(const vector<string> &left, const vector<string> &right)
    {
    // to hold the each line of outputs
    vector<string> ret;

    // add one space column between two pictures
    string::size_type width1 = width(left) + 1;

    // iterators to look at elements from left and right respectively
    vector<string>::const_iterator iter_i = left.begin(), iter_j = right.begin();

    // continue until we've see all rows from two pictures
    while (iter_i != left.end() || iter_j != right.end())
    {
    // construct new string to hold characters from two pictures
    string s;

    // copy a row from left side picture
    if (iter_i != left.end())
    {
    s = (*iter_i);
    ++iter_i;
    }
    // pad to full width
    s += string(width1 - s.size(), ' ');

    // copy a row from right picture
    if (iter_j != right.end())
    {
    s += (*iter_j);
    ++iter_j;
    }

    // store s into vector to form a new picture
    ret.push_back(s);
    }
    return ret;
    }
    -

    pics.h

    -
    #ifndef GUARD_PICS_H
    #define GUARD_PICS_H

    #include <string>
    #include <vector>

    std::vector<std::string> frame(const std::vector<std::string> &v);

    std::vector<std::string> vcat(const std::vector<std::string> &top,
    const std::vector<std::string> &bottom);

    std::vector<std::string> hcat(const std::vector<std::string> &left,
    const std::vector<std::string> &right);

    #endif /* GUARD_PICS_H */
    - -

    mainfunction.cpp

    -
    #include <iostream>		// to get declaration of cout and endl
    #include <string> // to get declaration of string
    #include <vector> // to get declaration of vector
    #include "width.h"
    #include "pics.h"
    #include "output.h"

    using std::cout; using std::vector;
    using std::endl; using std::string;

    int main()
    {
    // to hold the original picture
    vector<string> p;
    p.push_back("this is an");
    p.push_back("example");
    p.push_back("to");
    p.push_back("illustrate");
    p.push_back("framing");

    vector<string> p1 = frame(p);
    vector<string> p2 = vcat(p, p1);
    vector<string> p3 = hcat(p, p1);


    output(p); // print the original picture
    cout << endl; // write a blank line to seperate two pictures
    output(p1); // print the framed picture
    cout << endl;
    output(p2); // print the vertically concatenated picture of p and p1
    cout << endl;
    output(p3); // print the horizontally concatenated picture of p and p1

    return 0;
    }
    -

    Test performance

    this is an
    example
    to
    illustrate
    framing

    **************
    * this is an *
    * example *
    * to *
    * illustrate *
    * framing *
    **************

    this is an
    example
    to
    illustrate
    framing
    **************
    * this is an *
    * example *
    * to *
    * illustrate *
    * framing *
    **************

    this is an **************
    example * this is an *
    to * example *
    illustrate * to *
    framing * illustrate *
    * framing *
    **************
    -

    The program works perfectly.

    -]]>
    - - Programming - - - C++ - Notes - -
    - - C++ - Sequential Containers (Part 1) - /2018/03/13/C-Sequential-Containers/ - Recalling the student-grading program in Organizing programs with data structures, and thinking about how to separate students into two categories, passed and failed according to their final grades. Let’s define pass/fail criteria in final grades.

    -
    // predicate to determine whether a student failed
    bool fgrade(const Student_info &s)
    {
    return grade(s) < 60;
    }
    -

    If the computed grade is less than 60, it is a failing grade and the predicate yields a true value.

    -

    vector

    An indices approach

    The next is to examine each element in students based on the function fgrade, and store the records for the students who failed and who passed seperately.

    -
    vector<Student_info> extract_fails(vector<Student_info> &students)
    {
    vector<Student_info> pass, fail;

    for (vector<Student_info>::size_type i = 0;
    i != students.size(); ++i)
    {
    if (fgrade(students[i]))
    fail.push_back(students[i]);
    else
    pass.push_back(students[i]);
    }
    students = pass;
    return fail;
    }
    -

    This function returns a vector named fail which contains the records for students who failed, and changes the original students to a vector which contains only the records for students who passed. It can be seen that the vector pass exists temporarily and seems to be superfluous from the perspective of the limited memory.

    -

    Alternatively, we can remove the pass and operate on the original student directly. The member function erase defined in the class template vector allows users to remove elements, either a single or a range, from a vector. Now let’s see how to change above code using erase.

    -
    vector<Student_info> extract_fails(vector<Student_info> &students)
    {
    vector<Student_info> fail;
    vector<Student_info>::size_type i = 0;

    // loop invariant: [0, i) of students represent passing grades
    while(i != students.size())
    {
    if(fgrade(students[i]))
    {
    fail.pushback(students[i]);
    students.erase(students.begin() + i);
    }
    else
    ++i;
    }
    return fail;
    }
    -

    This function only creates one vector to hold the records for students who failed. The logic is quite straightforward:

    -
      -
    1. when the first iteration starts, i is 0 and students.size() yields the original length of the vector.
    2. -
    3. then, the while body is executed. If the ith student failed, the record will be stored into fail, and also be erased from the original vector. Noting that there are two side effects: first, the length of the vector decreases by 1; the index of all reminder elements move forward by 1. By then,the element students[1] in the original vector now becomes students[0] in the new vector as the original studnets[0] has been removed. Therefore, the examination starts from i = 0 again.
    4. -
    5. if the ith student passed, the record will be kept and the next loop examines the following element, i.e. students[i+1]. Therefore, i increases by 1 before the end of the current loop preparing for next loop.
    6. -
    -

    What’s new here is the arguments of erase function.

    -
    students.erase(students.begin() + i);
    -

    We have mentioned in previous chapters that students.begin() denotes the initial element (i.e. corresponding to index 0) in the vector students. Increasing by i, the arguments denotes the ith element. The reason to use students.begin() is that the parameter type defined in erase is const_iterator (this is discussed laster).

    -

    Due to the length of the vector is not fixed, the students.size() should be used in the condition as it always yields the current length when it is evaluated. If a precomputed size is used, e.g. using size defined as below to replace students.size(), students[i] may refer to nonexist elements or the loop becomes endless once any of elements is removed.

    -
    vector<Student_info>::size_type size = students.size();
    - -

    An iterators approach

    It has been observed that each function defined above access elements from the vector in sequential order. Also it is known that indices allows us to access at random. From the perspective of the library, using indices has the same effect as that we request permissions to access elements randomly. In other words, indices privide the ability that we don’t want to use or we don’t need at all.

    -

    Beyond indices, C++ supports another mechanism iterators that we can use to access elements in a vector. Not all standard containers support indices, but all standard containers supports iterators.

    -

    iterators provide more flexibility in supporting certain operations depending on different types of containers.

    -

    In specific, an iterator is a value that

    - -

    the syntax

    If we use indices for the iteration, for example,

    -
    for (vector<Student_info>::size_type i = 0; 
    i != students.size(); ++i)
    cout << students[i].name << endl;
    -

    alternatively, we can use iterators also

    -
    for (vector<Student_info>::const_iterator iter = students.begin(); iter != students.end(); ++iter)
    cout << (*iter).name << endl;
    -

    From above code, we know that the type of the iter is
    vector::const_iterator. More general, vector defines two associated itertor types:

    -
    vector<type>::const_iterator
    vector<type>::iterator
    -

    The difference is that iterators of type const_iterator have only read access. If we want to use an iterator to change values in a vector, we should define it using iterator type.

    -

    In addition, iter is initialized with the value of students.begin(). begin and end function return values that denotes the begining or one past the end of a container, respectively. Noting that students.begin() returns a value of type iterator but is converted to the type const_iterator in above code.

    -

    The next is to increase the value of iter using ++iter. The effect of the increment operator on iter is up to how the iterator type defines. As a result, the iterator denotes the next element in the container after this expression is executed.

    -

    The statement in the for body shows how to indirect access to the elements in students.

    -
    cout << (*iter).name;
    -

    The dereference operator () applies to *iter** and returns an lvalue (i.e. the element) to which the iterator refers.

    -

    Alternatively, we can dereference an iterator and fetch the element via

    -
    iter->name
    -

    It has same effect as (*iter).name.

    -

    rewrite functions using iterators

    Now let’s use iterators to rewrite the extract_fails function.

    -
    vector<Student_info> extract_fails(vector<Student_info> &students)
    {
    vector<Student_info> fail;
    vector<Student_info>::const_iterator iter = students.begin();

    // loop invariant: [0, i) of students represent passing grades
    while(iter != students.end())
    {
    if(fgrade(*iter))
    {
    fail.pushback(*iter);
    iter = students.erase(iter);
    }
    else
    ++iter;
    }
    return fail;
    }
    -

    As mentioned earlier, we pass iter directly to the erase function as the parameter has type of const_iterator.

    -

    Noting that calling erase function invalidates all iterators that refer to elements after the one that was just removed. But the erase function will return an iterator that is positioned on the element that follows the erased one. Therefore, the statement becomes

    -
    iter = students.erase(iter);
    -

    Iterators also support equility (=) and inequaility (!=) operations. Two iterators are equal if they denote the same element or they are off-the-end iterator for the same container.

    -

    Iterator arithmetic

    All standard containers support above iterator operations. But iterators for vector and string also support additional operations as shown below.

    -
      -
    1. iter +/n. Adding/substracting an integer n to an iterator yields an iterator that denotes the nth emelemt (if available) after/before the original element, or one past the end of the container.

      -
    2. -
    3. iter1 - iter2. Substracting two iterators yields an number when added to the right-hand operand yields the left operand. The iterators must denote elements within the container or one past the end of the container.

      -
    4. -
    5. >, >=, <, <=. One iterator is less than another if it denotes an element that appears before the element that denoted by the other iterator. Both iterators must denote elements within the container or one past the end of the container.

      -
    6. -
    -

    list

    vector allows us to arbitrarily access elements efficiently. Moreover, it performs well when adding or deleting the element at the end of the vector. However, vector is not a good choice when it come to inserting or removing elements from the interior of the vector, such as functions illustrated above.

    -

    To deal with such problems, the standard library provides another data structure list for us to efficiently insert or delete elements anywhere in the container. On the other hand, list doesn’t support random access via indices. It only supports bidirectional sequential access via iterators.

    -

    The list type is defined in the standard header . Same as the vector, list is also a class template. In addition, list and vector share many operations. For example, we can use list to rewrite the function extract_fails.

    -
    // use list instead of vector
    list<Student_info> extract_fails(list<Student_info> &students)
    {
    list<Student_info> fails;
    list<Student_info>::iterator iter = students.begin();

    while(iter != students.end())
    {
    if (fgrade(*iter))
    {
    fail.push_back(*iter);
    iter = students.erase(iter);
    }
    else
    ++iter;
    }
    return fail;
    }
    -

    The code above has no distinct difference comparing with the vector version. However, the important difference between lists and vectors is that how they affect on iterators.

    -
      -
    1. For vectors, the erase operation invalidates all iterators that refer to elements including the one was just removed and the subsequent elements. It is because that when removing one element, all following elements move one position towards the one erased. The push_back operation invalidates all iterators due to the fact the entirely vector might be reallocated to a new area for holding the new element.

      -
    2. -
    3. For lists, the erase and push_back operations do not invalidate other iterators except the one that was erased.

      -
    4. -
    -

    Another difference is that the list type does’t support the standard sort function. It has its own member funciton sort which can be called as follows

    -
    list <Student_info> students;
    students.sort(compare);
    -

    Taking strings apart

    A string can be regarded as a special container that only contains characters. It supports many operations, such as random access through indices and iterators, similar to a vector. Therefore, we can access and operate on a specific character. In addition, the standard header defines a bunch of functions that can be used to deal with single character in a string. The table below gives the details of each function

    - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
    cctype functionsSource: Lippman etc. 2012, p.82
    isalnum(c)true if c is a letter or a digit
    isalpha(c)true if c is a letter
    iscntrl(c)true if c is a control character
    isdigit(c)true if c is a digit
    isgraph(c)true if c is not a space but is printable
    islower(c)true if c is a lowercase letter
    isprint(c)true if c is a printable character
    ispunct(c)true if c is a punctuation character
    isspace(c)true if c is whitespace
    isupper(c)true if c is an uppercase letter
    isxdigit(c)true if c is a hexademical digit
    tolower(c)if c is an uppercase letter, it returns the lowercase letter; otherwise returns c unchanged
    toupper(c)if c is a lowercase letter, it returns the uppercase letter; otherwise returns c unchanged
    -

    For example, we can write a funciton to extract each word from a string like

    -
    The wealth of the mind is the only wealth
    -

    We can use a string member function substr to construct a new string object with its value initialized to a copy of a substring of the original string. substr defines two parameters, of which one is the inital position of the substring in the original string and the other one is the length of the substring. Therefore, the key to solve this project is to find the index of the begining of each word and the length. It can be observed that each word starts from a nonwhitespace character and ends with a character followed by whitespace. Assuming the first character of a word is positioned at indice i and the whitespace that closely follows the end of the word has indice j, the length of the word will be j - i (imagine the half-open range).

    -

    The solution is provided in the book and shown as below

    -
    vector<string> split(const string &s)
    {
    vector<string> ret;
    typedef string::size_type string_size;
    string_size i = 0;

    // invariant: we have processed characters [original value of i, i)
    while(i != s.size())
    {
    // ignore leading blanks
    // invariant: characters in range [original i, current i) are all spaces
    while(i != s.size() && isspace(s[i]))
    ++i;

    // find end of the next word
    string_size j = i;

    // invariant: none of the characters in range [original j, current j)
    while(j != s.size() && !isspace(s[j]))
    ++j;
    // if we found some nonwhitespace characters
    if (i != j)
    {
    // copy from s starting at i and taking j - i characters
    ret.push_back(s.substr(i, j-i));
    i = j;
    }
    }
    return ret;
    }
    -

    Noting that, when the function try to recognize the last word, the third while loop will stop because of j == s.size() even if !isspace(s[j]) is still true in the case that no whitespaces follows the last word. I add #include directives and using declarations, and test the program using the example string described above.

    -
    #include<iostream>
    #include<vector>
    #include<string>

    using std::cout; using std::vector;
    using std::endl; using std::string;

    // function declaration
    vector<string> split(const string &s);

    int main()
    {
    // define a string contains words separated by whitespaces
    string s{"The wealth of the mind is the only wealth"};

    // define a vector to hold splitted words
    vector<string> words = split(s);

    // write each line that contains one word
    for (vector<string>::size_type i = 0; i != words.size(); ++i)
    cout << words[i] << endl;

    return 0;
    }

    // Please define the split function here
    -

    Test results

    -
    The
    wealth
    of
    the
    mind
    is
    the
    only
    wealth
    -
    -

    To be continued.

    -]]>
    - - Programming - - - C++ - Notes - -
    - - Accelerated C++ Solutions to Exercises(Chapter 4 Part 2) - /2018/03/11/Accelerated-C-Solutions-to-Exercises-Chapter-4-Part-2/ - Exercise 4-6

    Rewrite the Student_info structure to calculate the grades immediately and store only the final grade.

    -

    Solution & Results

    I didn’t understand very well about the original program when I went through it first time. Now I try to rewrite it in this program with detailed explinations on each step. The strategy can be divided into three parts:

    -
      -
    1. analyse the requirements
    2. -
    3. break the ultimate goal into smaller ones that are logically connected
    4. -
    5. design algorithms to accomplish small goals
    6. -
    7. logically verify the correctness of each part and put all together
    8. -
    9. test the performance
    10. -
    -

    Requirements interpretation

    What we have?

    The program asks to enter a file that contains multiple students’ information including name, midterm exam grade, final exam grades and a sequence of homework grades. The example below shows an input sample

    -
    Robin 90 87 79 88 81 73 45 
    Brendan 70 69 88 100 91 75 66
    Arsenii 99 87 89 88 74 90 70
    Liam 83 66 100 76 87 91 78
    -

    Note that the number of students is unkown and the number of homework grades is unknown as well.

    -

    What we need to do?

    The program is required to generate a final grade report for a class. Specifically

    -
      -
    1. each line of the report contains the information of one student, including name and a final grade.
    2. -
    3. the final grade is the weighted average of midterm, final and homework grades, of which the mediter exam grade counts for 20%, the final exam grade counts for 40%, and the median homework grade counts for 40%.
    4. -
    5. each line of outputs follows an alphabetical order according to the names.
    6. -
    7. the final grades are vertically aligned.
    8. -
    -

    An output sample can be found here

    -
    Arsenii 89.8
    Brendan 76.8
    Liam 77.8
    Robin 84.4
    -

    others info

    We are required to define a data structure to hold each student’s information including name and his final grade. This can be finished immediately.

    -
    // a structure contains name and final grade
    struct Student_info{
    string name;
    double grade;
    };
    -

    It can be seen that Student_info in fact have all information of outputs. Therefore, once it is filled with correct information, the rest is to format the report to be written.

    -

    Decompose the program

    Overall structure

    Assuming that we have filled the information for one student(i.e. one object of Student_info), we need to store it for making preparations for generating the report. As the number of student is unknown, vector is flexible enough to hold each Student_info object. Thus, the overall structure of this program can be

    -
    int main()
    {
    // to hold the information for each student
    vector<Student_info> students;

    // one Student_info object to be filled
    Student_info record;

    // loop invariant: we have stored students.size() students already
    while (// condition statement)
    {
    // put the recorded student into the vector students
    students.push_back(record);
    }

    // write statements to format the report
    }
    -

    What the condition for this while loop should be? The condition for us to store record is that it has been filled(i.e. both name and grade contains correct information). Therefore, the condition shoule be true if record is successfully filled, and should be false if the attempt is failure.

    -

    There are two members in an object of type Student_info, one is name and the other one is grade. name comes from supplied information while grade can be computed using the information. It is necessary to write a function that not only can read and deal with supplied information but also meet the requirments of being the condition of above while loop. Let’s declare this function

    -
    istream &read(istream &is, Student_info &s);
    -

    The first parameter is a reference that refers to the object of input stream (i.e. istream). The second parameter is also a reference that refers to record. The reason to pass arguments by reference rather than value is that:

    -
      -
    1. the function needs to return a value that shows whether the reading attempt is successful. It is the pre-condition for filling record.
    2. -
    3. the istream objects can’t be copied.
    4. -
    5. the function needs to return the filled record.
    6. -
    7. By passing by reference, we can avoid seting two return values.
    8. -
    -

    As the condition of the while loop, read function returns an istream type object which is evaluated to be true if it is valid and otherwise it is evaluted to be false. Therefore, read function should return a valid istream object after finishing filling the record, and return an invalid istream object after finishing filling all records.

    -

    Fill in the information

    Let’s firstly consider how to fill the information for on student. The name can be stored directly into s.name. To hold midterm and final grades, we define two double type variables while to hold homework grades we define a variable of type vector.

    -
    istream & read(istream &is, Student_info &s)
    {
    // reads and store the student's name, midterm and final exam grades
    double midterm, final;
    is >> s.name >> midterm >> final;

    // reads and store all homework grades
    vector<double> hw;

    // call functions that store all homework grades
    // call functions that compute the final grade

    // return a valid is if both name and grade contain correct information
    return is;
    }
    - -

    To compute the final grade, what we lack is the median value of homework grades. But before we can compute the median grade, we need to store all possible homework grades using a function similar to read.

    -
    istream & read_hw(istream &in, vector<double> &hw)
    {
    // read homework grades
    double x;

    // loop invariant: we have read hw.size() homework grades
    while(in >> x)
    hw.push_back(x);

    return in;
    }
    -

    This function is ok if there is only one student but doesn’t work for the input file shown above. When it finishes reading all homework grades of the first student, it terminates because that it encounters next student’s name rather than a double value, leading to the input stream being failure state. Therefore, we can add in.clear() before return in to reset the error state to good.

    -
    // clear the stream so that input will work for the next student
    in.clear();
    - -

    Computations

    Once we obtained the homework grades, we can compute the median value and then the final grade. The algorithms for these computations are not complicated. The function below will be called in the read function.

    -
    double grade(double midterm, double final, const vector<double> &hw)
    {
    if (hw.size() == 0)
    throw domain_error("student has done no homework");
    return grade(midterm, final, median(hw));
    }
    -

    In this function, two functions are called. One is the median(hw) function which returns the median value, and anther one is overloaded grade function that returns the final grade which is again returned to the read function. Two functions are presented as follows

    -
    // function to calculate median
    double median(vector<double> vec)
    {
    // get the size of the vector
    typedef vector<double>::size_type vec_size;
    vec_size size = vec.size();

    // check whether the empty is empty
    if (size == 0)
    throw domain_error("median of an empty vector");

    // sort the grades
    sort(vec.begin(), vec.end());

    // compute the median homework grade
    vec_size mid = size/2;
    return size % 2 == 0 ? (vec[mid] + vec[mid - 1])/2 : vec[mid];
    }
    -
    // function to calculate final grade
    double grade(double midterm, double final, double homework)
    {
    return 0.2*midterm + 0.4*final + 0.4*homework;
    }
    - -

    How does eof works?

    Now, we can complete the read function and the while loop. Noting that I add throw statement after finishing reading the homework grades. Correspondingly, I add the try block to catch any exception that might be thrown when reading students’ information.

    -
    istream & read(istream &is, Student_info &s)
    {
    // reads and store the student's name, midterm and final exam grades
    double midterm, final;
    is >> s.name >> midterm >> final;

    // reads and store all homework grades
    vector<double> hw;
    read_hw(is, hw);
    if(hw.empty())
    throw domain_error("student has done no homework");

    // get the value of final grade
    s.grade = grade(midterm, final, hw);

    // return a valid is if both name and grade contain correct information
    return is;
    }
    -
    int main()
    {
    // to hold the information for each student
    vector<Student_info> students;

    // one Student_info object to be filled
    Student_info record;

    // loop invariant: we have stored students.size() students already
    try{
    while(read(cin, record))
    {
    maxlen = max(maxlen, record.name.size());
    students.push_back(record);
    }
    }catch(domain_error e){
    cout << e.what() << endl;
    }

    // write statements to format the report
    }
    -

    Theoretically, we have finished most parts of the program. The next step is to format outputs if above functions work fine. Let’s logically verify this part with following steps

    -
      -
    1. when the computer executes the while loop, the condition is evaluated.
    2. -
    3. the computer enter into the read function. The function stores name, midterm and final, and create an empty object hw.
    4. -
    5. then the computer enters into the read_hw function. This function creates an object x and starts another while loop.
    6. -
    7. the condition in >> x is evaluated. In most cases, the condition is true until it encounters next student’s name. As a result, the input stream is changed to failure state. By then, it has stored all homework grades of the first student into the vector hw.
    8. -
    9. the state of in is reset to be good by using in.clear().
    10. -
    11. in is returned and the implementation goes back to the function read.
    12. -
    13. grade function is called. Inside this function, another grade function is called and median function is called.
    14. -
    15. s.grade is obtained, and is is returned. By then, record contains correct information of the first student.
    16. -
    17. step 1 continues, is is evaluated to be true which indicates that record is filled successfully.
    18. -
    19. the computer moves to the while body and record is stored into students. By then, both the name and final grade of the first student is available for us.
    20. -
    21. the while loop starts over again from step 1 - 10.
    22. -
    -

    Yeah, it works fine by now. However, how to stop it? Typically we can stop a while loop by sending a signal eof to set the input stream being a failure state. Let’s type Ctrl+Z/D and see how it works.

    -
      -
    1. same as above
    2. -
    3. same as above. Though name, midterm are uninitialized, the implementation would’t stop at this position.
    4. -
    5. same as above
    6. -
    7. the condition in >> x is evaluated to be false due to the eof
    8. -
    9. same as above. By then, hw is still empty.
    10. -
    11. same as above
    12. -
    13. grade function is called. An exception is thrown due to the fact of step 5.
    14. -
    15. the program fails
    16. -
    -

    So, what’s the problem? The immediate cause is the empty hw. We actually even don’t want to create the hw since we want to terminate the while loop immediately. Therefore, we need to add a condition like follow code shows

    -
    istream & read(istream &is, Student_info &s)
    {
    // reads and store the student's name, midterm and final exam grades
    double midterm, final;
    is >> s.name >> midterm >> final;

    // to check whether eof is sent
    if(is)
    {
    // reads and store all homework grades
    vector<double> hw;
    read_hw(is, hw);
    if(hw.empty())
    throw domain_error("student has done no homework");

    // get the value of final grade
    s.grade = grade(midterm, final, hw);

    // return a valid is if both name and grade contain correct information
    }
    return is;
    }
    -

    Why don’t add the if condition at the very begining of the read function? It is because that the input stream hasn’t start to read anything at that position. Now we recheck each step after sending eof signal.

    -
      -
    1. same as above
    2. -
    3. midterm and final is created.
    4. -
    5. eof is read and the state of is is set to be failure.
    6. -
    7. condition is is evaluated to be false
    8. -
    9. return is. The computer goes back the step 1.
    10. -
    11. the condition in step 1 is evaluated to be false, while loop stops.
    12. -
    -

    Now, this part of code should work as expected.

    -

    Formatting the outputs

    The next is to format each line of outputs according to requirements analysed above.
    To alphabetize the outputs, we can use following code

    -
    sort(students.begin(), students.end(), compare);
    -

    where compare is defined as follows

    -
    bool compare(const Student_info &x, const Student_info &y)
    {
    return x.name < y.name;
    }
    - -

    To line up the final grades, we need to find the longest name and hence add one statement in the first while loop. maxlen will finally record the length of the longest name.

    -
    maxlen = max(maxlen, record.name.size());
    -

    The final grade can be obtained via

    -
    double final_grade = students[i].grade;
    - -

    ####

    -

    Put all together

    Following gives each file of the program. No more discussion here.
    mainfunction.cpp

    -
    #include <algorithm>
    #include <iomanip>
    #include <ios>
    #include <iostream>
    #include <stdexcept>
    #include <string>
    #include <vector>
    #include "Student_info.h"
    #include "grade.h"

    using std::cin; using std::setprecision;
    using std::cout; using std::sort;
    using std::endl; using std::streamsize;
    using std::domain_error; using std::string;
    using std::max; using std::vector;

    int main()
    {
    vector<Student_info> students;
    Student_info record;
    string::size_type maxlen = 0;

    // read and store all the records, and find the length of the longest name
    try{
    while(read(cin, record))
    {
    maxlen = max(maxlen, record.name.size());
    students.push_back(record);
    }
    }catch(domain_error e){
    cout << e.what() << endl;
    }

    // alphabetize the records
    sort(students.begin(), students.end(), compare);

    // write each line of outpurs
    for (vector<Student_info>::size_type i = 0; i != students.size(); ++i)
    {
    // write the name, blanks
    cout << students[i].name << string(maxlen + 1 - students[i].name.size(), ' ');

    // compute and write the final grade
    double final_grade = students[i].grade;
    streamsize prec = cout.precision();
    cout << setprecision(3) << final_grade << setprecision(prec) << endl;
    }
    return 0;
    }
    -

    Student_info.cpp

    -
    #include "Student_info.h"
    #include "grade.h"
    using std::vector; using std::istream; using std::cin; using std::cout;

    // function to define the additional arguments in max
    bool compare(const Student_info &x, const Student_info &y)
    {
    return x.name < y.name;
    }

    // function to read data for inputs and fill information
    istream & read(istream &is, Student_info &s)
    {
    // reads and store the student's name, midterm and final exam grades
    double midterm, final;
    is >> s.name >> midterm >> final;
    if (is)
    {
    // reads and store all homework grades
    vector<double> hw;
    read_hw(is, hw);
    if(hw.empty())
    throw domain_error("student has done no homework");

    // get the final grade
    s.grade = grade(midterm, final, hw);
    }
    return is;
    }

    // function to read homework grades
    istream & read_hw(istream &in, vector<double> &hw)
    {
    // read homework grades
    double x;
    while(in >> x)
    hw.push_back(x);

    // clear the stream so that input will work for the next student
    in.clear();

    return in;
    }
    -

    Student_info.h

    -
    #ifndef GUARD_Student_info
    #define GUARD_Student_info

    // Student_info.header file
    #include<iostream>
    #include<string>
    #include<vector>

    struct Student_info{
    std::string name;
    double grade;
    };

    bool compare(const Student_info &, const Student_info &);
    std::istream & read(std::istream &, Student_info &);
    std::istream & read_hw(std::istream &, std::vector<double> &);
    #endif
    -

    grade.cpp

    -
    #include <algorithm>
    #include <stdexcept>
    #include <vector>
    #include "grade.h"

    using std::domain_error; using std::istream;
    using std::vector; using std::sort;

    // grade function 1
    double grade(double midterm, double final, const vector<double> &hw)
    {
    if (hw.size() == 0)
    throw domain_error("student has done no homework");
    return grade(midterm, final, median(hw));
    }

    // grade function 2
    double grade(double midterm, double final, double homework)
    {
    return 0.2*midterm + 0.4*final + 0.4*homework;
    }

    // compute the median of vector<double>
    double median(vector<double> vec)
    {
    // get the size of the vector
    typedef vector<double>::size_type vec_size;
    vec_size size = vec.size();

    // check whether the empty is empty
    if (size == 0)
    throw domain_error("median of an empty vector");

    // sort the grades
    sort(vec.begin(), vec.end());

    // compute the median homework grade
    vec_size mid = size/2;
    return size % 2 == 0 ? (vec[mid] + vec[mid - 1])/2 : vec[mid];
    }
    -

    grade.h

    -
    #ifndef GUARD_grade_h
    #define GUARD_grade_h

    // grade.h
    #include<vector>

    double grade(double, double, const std::vector<double> &);
    double grade(double, double, double);
    double median(std::vector<double>);
    #endif
    - -

    Performance test

    Inputs

    Robin 90 87 79 88 81 73 45
    Brendan 70 69 88 100 91 75 66
    Arsenii 99 87 89 88 74 90 70
    Liam 83 66 100 76 87 91 78

    Outputs:

    Arsenii 89.8
    Brendan 76.8
    Liam 77.8
    Robin 84.4
    -

    It performs as same as the original program provided in Chapter 4.

    -
    -

    Exercise 4-7

    Write a program to calculate the average of the numbers stored in a vector.

    -

    Solution & Results

    It’s a simple project and the solution can be divided into three steps

    -
      -
    1. define a function to read and store data into a vector.
    2. -
    3. define a function to compute the average value.
    4. -
    5. set precision to format the double value of outputs.
    6. -
    -

    All details can be found in other exercises and hence no more explinations here.

    -

    Below shos the only file including both functions and main function.

    -
    #include <iostream>
    #include <vector>
    #include <stdexcept>
    #include <iomanip>

    using std::cin; using std::cout;
    using std::endl; using std::domain_error;
    using std::vector; using std::istream;
    using std::streamsize; using std::setprecision;

    // define function to read data into a vector
    istream &read(istream &is, vector<double> &doubleValues)
    {
    double number;
    while(is >> number)
    doubleValues.push_back(number);
    return is;

    }

    // define function to compute the average of a data sequence
    double average(const vector<double> &doubleValues)
    {
    if (doubleValues.size() == 0)
    throw domain_error("An empty vector");
    typedef vector<double>::size_type vec_size;
    double sum = 0;
    for (vec_size i = 0; i != doubleValues.size(); ++i)
    sum += doubleValues[i];
    return sum/doubleValues.size();
    }

    int main()
    {
    vector<double> doubleValues;
    read(cin, doubleValues);
    try{
    double averageValue = average(doubleValues);
    streamsize prec = cout.precision();
    cout << setprecision(3) << averageValue
    << setprecision (prec) << endl;
    }catch(domain_error){
    cout << "You must enter at least 1 number. Pleast try again.";
    }
    return 0;
    }
    -

    Test

    -
    Input: 
    1.2 5.89 9.33 12.7 7.8 4

    Output:
    6.82
    - -
    -

    Exercise 4-8

    If the following code is legal, what can we infer about the return type of f?

    -
    double d = f()[n];
    -

    Solution & Results

    From the code, we can infer

    -
      -
    1. f()[n] is possible a value of type int or float or double or bool.
    2. -
    3. f() supports accessing the element using subscript and hence it could be a container.
    4. -
    5. f() has the form of a function.
    6. -
    -

    According to above analysis, f() could be a function that returns a container which can hold values of types listed above. As far as I know, the container is vector and returns a type of vector(inside types including int, float, double and bool).

    -

    To verify my analysis, I wrote a simple program shown as below

    -
    #include<iostream>
    #include <vector>

    using std::cout;
    using std::cin;
    using std::vector;
    using std::endl;

    // funtion to return a vector<double>
    vector<double> f(vector<double> number)
    {
    number.push_back(11);
    number.push_back(20.5);
    return number;
    }

    int main(){
    vector<double> number;
    double x = f(number)[0];
    double y = f(number)[1];
    cout << x << endl;
    cout << y << endl;
    return 0;
    }
    -

    It works as expected and gives results

    -
    11
    20.5
    -

    If I change the type double to any of types listed above, the program works fine.

    -
    -

    Reference

    Koenig, A. and Moo, B.E., 2000. Accelerated C++.

    -

    Lippman, S.B., Lajoie, J. and Moo, B.E., 2013. C++ Primer, 5th edt.

    -]]>
    - - Programming - - - Accelerated C++ Solutions - -
    - - Accelerated C++ Solutions to Exercises(Chapter 4 Part 1) - /2018/03/09/Accelerated-C-Solutions-to-Exercises-Chapter-4/ - Exercise 4-0

    Compile, execute, and test the programs in this chapter

    -

    Solution & Results

    This exercise has been done presented in Organizing programs with functions and Organizing programs with data structures.

    -
    -

    Exercise 4-1

    We noted in §4.2.3/65 that it is essential that the argument types in a call to maxmatch exactly. Will the following code work? If there is a problem, how would you fix it?

    -
    int maxlen;
    Student_info s;
    max(s.name.size(), maxlen);
    - -

    Solution & Results

    It doesn’t work. There exist two problems here.

    -

    First, the max function defined in standard header requires that both arguments must have the same type. But in this piece of code, s.name.size() has the type of string::size_type while maxlen is a int type. To fix this, we need to define maxlen as a variable of type string::size_type.

    -

    Second, it should be initialized as a variable of built-in type(assuming no problem with type int) is undefined if it is not initialized explicitly. For a variable of other types, the default value (if available, otherwise the variable is undefined) depends on how each type defines. I did a simple experiment as follows

    -
    #include<iostream>
    #include<string>

    using std::cout;
    using std::string;

    int main(){
    string::size_type y;
    cout << y;
    return 0;
    }
    -

    The result is

    -
    4200939
    -

    with warning

    -
    'y' is used uninitialized in this function
    -

    The experiment shows that the type of string::size_type doesn’t support default initialization. To fixed this, we need to explicitly initialize it.

    -

    The correct code should be:

    -
    string::size_type maxlen = 0;
    Student_info s;
    max(s.name.size(), maxlen);
    -
    -

    Exercise 4-2, 4-3

    4-2: Write a program to calculate the squares of int values up to 100. The program should write two columns: The first lists the value; the second contains the square of that value.Use setw to manage the output so that the values line up in columns.

    -

    4-3: What happens if we rewrite the previous program to allow values up to but not including 1000 but neglect to change the arguments to setw? Rewrite the program to be more robust in the face of changes that allow i to grow without adjusting the setw arguments.

    -

    Solution & Result

    algorithms

    Exercise 4-3 is a generalized version of 4-2. Specifically, the program is required to write two colomns as follows

    -
      0        0
    1 1
    2 4
    3 9
    ... ...
    99 9801
    100 10000
    ... ...
    n n*n
    -

    Each line contains an integer value followed by the square of the integer. The range of integers in the first column starts from 0 to 1000 (excluded). Most importantly, each line should be formated such that the values line up in columns.

    -

    The key to the solution is to find the longest number in each column. Then, we can set the width of the first column as the number of digits of the corresponding longest number. Analogously, the width of the second column will be set as the number of digits of the longest number in it, with an additional space to seperate from the first column.

    -

    For an ascending sequence, the longest numbers in both columns depend on the largest number. For exercise 4-2, the largest number is 100 and 10000 (square of 100) in the first and second column, respectively. We can write and format each line as below shows

    -
    for (int i = 0; i != 101; ++i)
    {
    // 100 has three digits and 10000 has 5 digits, 1 additional space for seperate two columns
    cout << setw(3) << i << setw(6) << i*i << endl;
    }
    -

    Exercise 4-3 requires more flexibility such that no needs to change setw arguments when the largest number changes. Naturally, the key is to compute the number of digits of a user-defined largest number, e,g. defined as maxNum.

    -
    // hold the number of digits of maxNum and its square
    int n = 0;
    int m = 0;

    // hold the values of the largest number and its square
    int j = maxNum;
    int k = maxNum*maxNum;
    // bounds check
    if (j >= 1000 || j < 0)
    {
    cout << "Enter a number greater than or equal to 0 and less than 1000. Please try again ";

    return 1;
    }

    // computations
    if (j == 0)
    {
    m = n = 1;
    }
    else
    {
    // loop invariant: we have counted n digits of the value j
    while (j != 0)
    {
    // each operation j reduces one digit
    j /= 10;

    // maintain the loop invariant
    ++n;
    }

    // loop invariant: we have counted n digits of the value j
    while (k != 0)
    {
    k /= 10;
    ++m;
    }
    }
    -

    organize the program with functions

    Obviously, above code is partly repeated. I’ll rewrite the computations as a function which returns the number of digits of an entered value.
    width.cpp

    -
    // function to compute the number of digits of an integer value
    #include "width.h"

    int width(int num)
    {
    if (num == 0)
    return 1;
    else
    {
    int n = 0;
    while(num != 0)
    {
    num /= 10;
    ++n;
    }
    return n;
    }
    }
    -

    width.h

    -
    #ifndef GUARD_width_h
    #define GUARD_width_h

    int width(int);
    #endif
    -

    mainfunction.cpp

    -
    #include <iostream>
    #include <iomanip>
    #include "width.h"

    using std::cin; using std::cout;
    using std::endl; using std::setw;

    int main()
    {
    // asks to enter the upper bound
    cout << "Enter a number greater than or equal to 0 and less than 1000: ";

    // read the largest number
    int maxNum;
    cin >> maxNum;

    // bounds check
    if (maxNum >= 1000 || maxNum < 0)
    {
    cout << "The entered value is beyond the allowed value range. Please try again.";
    return 1;
    }

    // write the outputs
    for (int i = 0; i != maxNum + 1; ++i)
    {
    cout << setw(width(maxNum)) << i
    << setw(width(maxNum*maxNum) + 1) << i*i << endl;
    }
    return 0;
    }
    -

    Test performance

    Test 1: the upper limit is 10

    -
    Enter a number greater than or equal to 0 and less than 1000: 10
    0 0
    1 1
    2 4
    3 9
    4 16
    5 25
    6 36
    7 49
    8 64
    9 81
    10 100
    -

    Test 2: the upper limit is 101

    -
    Enter a number greater than or equal to 0 and less than 1000: 101
    0 0
    1 1
    2 4
    3 9
    .. ..
    90 8100
    91 8281
    92 8464
    93 8649
    94 8836
    95 9025
    96 9216
    97 9409
    98 9604
    99 9801
    100 10000
    101 10201
    -

    Test 3: the upper limit is 1000

    -
    Enter a number greater than or equal to 0 and less than 1000: 1000
    The entered value is beyond the allowed value range. Please try again.
    -

    It can be seen from these tests that the program perform as expected.

    -
    -

    Exercise 4-4

    Now change your squares program to use double values instead of ints. Use manipulators to manage the output so that the values line up in columns

    -

    Solution & Results

    The difference between this exercise and last exercise is that the function width defined above can not compute the number of digits of a double value. Admittedly, I didn’t found very good strategy to complete this project. I circument the problem applying the type conversion technique. Specifically

    -
      -
    1. divide the maxNum/maxNum*maxNum into integer part and fractional part.
    2. -
    3. compute the number of digits of the integer part using same function as shown above.
    4. -
    5. control the fractional part with an additional user-defined variable places which represents the number of places after the dot point.
    6. -
    -

    The arguments of *setw for the first column is:

    -
    width(maxNum) + 1 + places
    -

    The number 1 leaves room for the decimal point.

    -

    The arguments of *setw for the first column is:

    -
    width(maxNum*maxNum) + 1 + 1 + places
    -

    One of two values of 1 is added for the decimal point while the other one is added for seperating from the first column. I didn’t change anything in the width function described above and hence the arguments to be passed are converted to int type, leading to that the returned value of width is the width of the part before the decimal point.

    -

    Beyond this, the program asks to enter an initial value and an value of the increment for numbers of the first column. For example, we set the initial value as 0.0 and increment as 0.5, the first column becomes

    -
    0.0
    0.5
    1.0
    ...
    n
    -

    where n is the largest number (i.e. maxNum) that available for outputs in the range of [0.0, 1000). It will be computed in the program.

    -

    To format outputs, I uses function fixed together with setprecision to fixed number of decimal places and showpoint to enable the display of trailing 0. A detailed comparison between these three can be found in C++ - Working with batches of data.

    -

    Please find the midth.cpp and midth.h in exercise 4-2/3. The main function file is shown below.

    -
    // Accelerated C++ Solutions Exercises 4-4
    #include <iostream>
    #include <iomanip>
    #include "width.h"

    using std::cin; using std::endl;
    using std::cout; using std::setw;
    using std::setprecision; using std::fixed;
    using std::streamsize; using std::showpoint;
    using std::noshowpoint;

    int main()
    {
    // asks toset the range for outputs
    double minNum, incrementByValue;
    cout << "Enter an intial value greater than or equal to 0.0 and less than 1000.0: ";
    cin >> minNum;
    cout <<"Enter the increment for each line of outputs: ";
    cin >> incrementByValue;

    // bounds check
    if (minNum >= 1000.0 || minNum < 0.0)
    {
    cout << "The entered value is beyond the allowed value range. Please try again.";
    return 1;
    }

    // asks to enter a value that determines decimal places
    cout << "How many places you want to keep after decimal point?\n"
    "Enter an integer to determine decimal places: ";
    int places;
    cin >> places;

    // get the largest value that available for outputs
    double maxNum = minNum;
    while ((maxNum + incrementByValue) < 1000.0)
    {
    maxNum += incrementByValue;
    }

    // write the outputs
    for (double i = minNum; i < 1000.0; i += incrementByValue)
    {
    streamsize prec = cout.precision();
    cout << showpoint << fixed << setprecision(places)
    << setw(width(maxNum) + places + 1) << i
    << setw(width(maxNum*maxNum) + places + 2) << i*i
    << setprecision(prec) << noshowpoint << endl;
    }
    return 0;
    }
    -

    It is worth noting that we cannot use equality and inequality operators in the floating point value conditions as floating values cannot be precisely represented in the computer world. Due to this limitation, I change the condition i != 1000 to i < 1000.0. I did several tests to show how is the performance.

    -

    Test 1

    -
    Enter an intial value greater than or equal to 0.0 and less than 1000.0: 0
    Enter the increment for each line of outputs: 100
    How many places you want to keep after decimal point?
    Enter an integer to determine decimal places: 0
    0. 0.
    100. 10000.
    200. 40000.
    300. 90000.
    400. 160000.
    500. 250000.
    600. 360000.
    700. 490000.
    800. 640000.
    900. 810000.
    -

    Test 2

    -
    Enter an intial value greater than or equal to 0.0 and less than 1000.0: 0.5234
    Enter the increment for each line of outputs: 0.5
    How many places you want to keep after decimal point?
    Enter an integer to determine decimal places: 4
    0.5234 0.2739
    1.0234 1.0473
    1.5234 2.3207
    2.0234 4.0941
    2.5234 6.3675
    3.0234 9.1409
    3.5234 12.4143
    4.0234 16.1877
    4.5234 20.4611
    5.0234 25.2345
    5.5234 30.5079
    6.0234 36.2813
    6.5234 42.5547
    .... ....
    996.0234 992062.6133
    996.5234 993058.8867
    997.0234 994055.6601
    997.5234 995052.9335
    998.0234 996050.7069
    998.5234 997048.9803
    999.0234 998047.7537
    999.5234 999047.0271
    - -

    Test 3

    -
    Enter an intial value greater than or equal to 0.0 and less than 1000.0: 999
    Enter the increment for each line of outputs: 1
    How many places you want to keep after decimal point?
    Enter an integer to determine decimal places: 1
    999.0 998001.0
    ```
    - -
    -

    Exercise 4-5

    Write a function that reads words from an input stream and stores them in a vector. Use that function both to write programs that count the number of words in the input, and to count how many times each word occurred.

    -

    Solution & Results

    This exercise is a variant of Chapter 3 Exercise 3-3. I uses exactly the same solution strategy in this project. Therefore, no more discussion here. The code and tests can be found below.

    -

    mainfunction.cpp

    -
    #include <iostream>
    #include <string>
    #include <vector>
    #include <iomanip>
    #include <stdexcept>
    #include "wordsRead.h"

    using std::cin; using std::string;
    using std::cout; using std::vector;
    using std::endl; using std::setw;
    using std::domain_error;

    int main()
    {
    // hold the information of each words
    vector<words_info> words;

    // type alias
    typedef vector<words_info>::size_type vec_size;
    typedef string::size_type str_size;
    str_size word_size = 0;

    // count the number of words
    int totalNum = 0;

    // asks to enter words
    cout << "Please enter words: ";


    try{
    // read, count and store words
    wordsRead(cin, words, totalNum, word_size);

    // write the total number of inputs
    cout << "The total number of words is: " << totalNum << endl;

    // write each distinct word and its occurrence number
    for (vec_size i = 0; i != words.size(); ++i)
    {
    // format each line
    int n = 9 + word_size - words[i].wordName.size();
    cout << words[i].wordName << setw(n) << " appears "
    << words[i].count << " times"<< endl;
    }

    }catch(domain_error){
    cout << "You must enter at least one word. Please try again.";
    }
    return 0;
    }
    - -

    wordsRead.cpp

    -
    // function to read, count and store words
    #include <iostream>
    #include <string>
    #include <vector>
    #include <stdexcept>
    #include "wordsRead.h"

    using std::istream; using std::domain_error;
    using std::string; using std::vector;

    istream & wordsRead(istream &is, vector<words_info> &words, int &totalNum, string::size_type &word_size)
    {
    words_info word;

    // loop invariant: we have read totalNum words now
    while(is >> word.wordName)
    {
    // maintain the loop invariant
    ++totalNum;

    // find the size of the longest word
    if (word_size < word.wordName.size())
    word_size = word.wordName.size();

    // set flag to find each distinct word
    // flag == true: the word is distinct
    // flag == false: the word exists
    bool flag = true;
    for (vector<words_info>::size_type i = 0; i != words.size(); ++i)
    {
    // compare with previous distinct words
    if (word.wordName == words[i].wordName)
    {
    ++words[i].count;
    flag = false;
    }
    }

    // if the word has not been entered, store
    if (flag == true)
    {
    word.count = 1;
    words.push_back(word);
    }

    }

    if (totalNum == 0)
    throw domain_error("No input");
    return is;
    }
    -

    wordsRead.h

    -
    #ifndef GUARD_wordsRead_h
    #define GUARD_wordsRead_h

    #include <iostream>
    #include <string>
    #include <vector>

    struct words_info{
    std::string wordName;
    int count;
    };

    std::istream & wordsRead(std::istream &, std::vector<words_info> &words, int &, std::string::size_type &);


    #endif /* GUARD_wordsRead_h */
    - -

    Test Performance

    Test 1

    -
    Please enter words: I am a good teacher and you are a good student
    The total number of words is: 11
    I appears 1 times
    am appears 1 times
    a appears 2 times
    good appears 2 times
    teacher appears 1 times
    and appears 1 times
    you appears 1 times
    are appears 1 times
    student appears 1 times
    -

    Test 2

    -
    Please enter words: what happens depends on the range of the values that the types permit
    The total number of words is: 13
    what appears 1 times
    happens appears 1 times
    depends appears 1 times
    on appears 1 times
    the appears 3 times
    range appears 1 times
    of appears 1 times
    values appears 1 times
    that appears 1 times
    types appears 1 times
    permit appears 1 times
    - -
    -

    To be continued.

    -]]>
    - - Programming - - - Accelerated C++ Solutions - -
    - - C++ - Organizing programs with data structures - /2018/03/08/Organizing-programs-and-data-Part-2/ - The program we have accomplished in last chapter is good enough for computing one students’ final grade, however, is unpractical in reality when it comes to generating a final grade report for a class. Assuming that we have a file that records all students’ information including their names, midterm and final exam grades, and homework grades. For example

    -
    Robin 90 87 79 88 81 73 45
    Brendan 70 69 88 100 91 75 66
    ...
    -

    The program is required to compute the final grade for each student and generate a report like

    -
    Bredan 76.8
    Robin 84.4
    -

    In specific, there are three requirements

    -
      -
    1. in the final grade, the mediter exam grade counts for 20%, the final exam grade counts for 40%, and the median homework grade counts for 40%.
    2. -
    3. the output follows an alphabetical order according to the names.
    4. -
    5. the final grades are vertically aligned.
    6. -
    -

    A similar program has been done in Exercise 3-5, which can keep track of grades for several students at once though it uses the average homework grade rather than median value. The whole structure is simply a while loop. The program in last chapter teaches us how to fullfill the first requirement with functions. Now we focus on how to meet the second the third requirements.

    -

    Data struct

    Last chapter mainly introdues how to write functions to deal with computations as well as data reading. However, the information such as name, medterm and final exam grades are still left there. If more information such as age, weight and grade need to be added, the program would be bloated. In fact, all these information can be integrated as a user-defined data structures as follows

    -
    struct Student_info {
    string name;
    double midterm, final;
    vector<double> homework;
    } objectName;
    -

    The code defines a struct that contains a group of data members. Student_info is the name of this type. Each data member is declared with a type and a name. objectName is an object of such type. Another way to declare an object is

    -
    Student_info objectName;
    -

    Note that there must be a semicolon at the end of the curly braces when defining a struct type.

    -

    It has been observed that each object of such type holds information for one student. We can store all students’ information into a vector, e.g. vector record.

    -

    reading data

    The function that an object of Student_info reads data is similar to that for a vector.

    -
    Student_info record;
    read(cin, record);
    -

    The read function

    -
    istream & read(istream &is, Student_info &s)
    {
    // reads and store the student's name, midterm and final exam grades
    is >> s.name >> s.midterm >> s.final;

    // reads and store all homework grades
    read_hw(is, s.homework);
    return is;
    }
    -

    Since the function is similar to the read_hw function defined in this page, no more discussed here.

    -

    grade function

    Now the data have been stored into a sturct and concequently the grade function becomes
    overloaded function 1

    -
    double grade(const Student_info &s)
    {
    return grade(s.meterm, s.final, s.homework)
    }
    -

    overloaded function 2

    -
    double grade(double midterm, double final, const vector<double> &hw)
    {
    if (hw.size() == 0)
    throw domain_error("student has done no homework");
    return grade(midterm, final, median(hw));
    }
    -

    overloaded function 3

    -
    double grade(double midterm, double final, double homework)
    {
    return 0.2*midterm + 0.4*final + 0.4*homework;
    }
    -

    stores all structs into a vector

    Once the record finishes reading data for inputs, we can store it into a vector.

    -
    vector<Student_info> students;
    Student_info record;

    while(read(cin, record))
    {
    students.push_back(record);
    }
    -

    alphabetize students

    Up to now, we have finished the code for all computations and data reading. The next step is to sort the students in an alphabetical order according to students’ names. In previous chapters, we uses the standard algorithm sort to accomplish sorting the homework. It can also be used to sort the students, but before we apply it we need to learn how it works on the homework.

    -

    homework is a vector that contains all values of homework grades. The sort function compares objects in the vector using <. It is clear when using < to compare two numerical values but doesn’t works for the element type of a struct. Regarding to this case, the sort function provides an optional argument, a predicate, for us to define the ways to compare elements.

    -

    A predicate is a function that typically yields a true value of type bool. Let’s see how is it defined

    -
    bool compare(const Student_info &x, const Student_info &y)
    {
    return x.name < y.name;
    }
    -

    The usage of the sort function is

    -
    sort(students.begin(), students.end, compare);
    -

    It means that the sort function will compare elements in the students only according to its member name rather than using < directly. As for the effect of < on strings, the expression is evaluated to be true if x.name is alphabetically ahead of y.name. Specifically, when compare two strings

    -
      -
    1. the result is the result of comparing the first character at which the strings differ.
    2. -
    3. if all characters of one string equal to the corresponding characters of another string, then the shorter one is less than the longer one.
    4. -
    -

    align the final grade vertically

    Now we deal with the third requirement. Each line of outputs is formed by s.name, a blank string, and the grade. The key to solve this problem is to write a blank string with appropriate length such that all lines have same total length ahead of the final grades while the total length depends on the longest name. The minimum number of spaces between a name and a grade is one. The process can be logically divided into three steps

    -
      -
    1. find the longest name
    2. -
    3. calculate the total length ahead of the grade: the size of the longest name plus one(space).
    4. -
    5. Create a blank string for each line with length: the total length minus the size of each name.
    6. -
    -

    To find the longest name, we use another the max function defined in the header . The syntax is

    -
    max(el, e2);
    -

    It returns the larger one of two expressions which yield values of the same type. The comparison is similar to Exercise 3-4 strategy 2.

    -

    A complete program

    Above steps show the core technicals that deals with three requirements mentioned at the begining. The new program built on struct and functions is presented as below

    -
    int main()
    {
    vector<Student_info> students;
    Student_info record;
    string::size_type maxlen = 0;

    // read and store all the records, and find the length of the longest name
    while(read(cin, record))
    {
    maxlen = max(maxlen, record.name.size());
    students.push_back(record);
    }

    // alphabetize the records
    sort(students.begin(), students.end(), compare);

    // write each line of outpurs
    for (vector<Student_info>::size_type i = 0; i != students.size(); ++i)
    {
    // write the name, blanks
    cout << students[i].name << string(maxlen + 1 - students[i].name.size(), ' ');

    // compute and write the final grade
    try{
    double final_grade = grade(students[i]);
    streamsize prec = cout.precision();
    cout << setprecision(3) << final_grade << setprecision(prec);
    } catch(domain_error e){
    cout << e.what();
    }
    cout << endl;
    }
    return 0;
    }
    -

    In this program, we uses a new function what to write the diagnostic message if an exception is thrown. The catch clause named the diagnostic message as e, i.e. the object that contains the message. The message can be obtained from what().

    -

    Seperate compilation

    Strictly speaking, above program is not a complete program as it doesn’t work when it is executed. Because we haven’t add the functions to be called in this program. It can be done by putting all stuff into a single file, which however may increase complexity and reduce readability. Alternatively, we can seperate the program into several files and compile these files seperately. In fact, we uses seperate compilation since the first program. For example, we can use IO class objects by means of including the header and declarations rather than defining the type in our programs. How this is done? Let’s write our own header files!

    -

    header file and source file

    To support seperate compilation, C++ distinguishes declarations and definitions which allows muliple files sharing one definition. For example, if we want to seperate the median function from above program, we need to put its definition into a source file named median.cpp (depending on your c++ implementations), and put its declarations into a header file named median.h. By doing so, the median function is allowed to be accessed in programs as long as we include its header file like

    -
    #include "median.h"
    -

    The header file is enclosed by double quotes rather than angle brackets, which makes it distinct from the standard library headers. The source file is created as follows

    -
    // source file for median function 
    #include <algorithm> // to get declaration of sort
    #include <stdexcept> // to get declaration of domain_error
    #include <vector> // to get declaration of vector
    #include <median.h>

    // declarations for names
    using std::domain_error;
    using std::vector;
    using std::sort;

    // compute the median of vector<double>
    double median(vector<double> vec)
    {
    // get the size of the vector
    typedef vector<double>::size_type vec_size;
    vec_size size = vec.size();

    // check whether the empty is empty
    if (size == 0)
    throw domain_error("median of an empty vector");

    // sort the grades
    sort(vec.begin(), vec.end());

    // compute the median homework grade
    vec_size mid = size/2;
    return size % 2 == 0 ? (vec[mid] + vec[mid - 1])/2 : vec[mid];
    }
    -

    Note this file includes all needed headers for the median function itself. It contains both the function declarations and definitions, which allows the compiler to check the consistency between the declarations and definitions. The header file can be written as

    -
    #ifndef GUARD_median_h
    #define GUARD_median_h

    #include<vector>
    double median(std::vector<double>);

    #endif
    -

    There are several new points here. First, the file include the needed header , and use std::vector instead of using std::vector. This is because we are not sure whether a user want a using declaration in their program as once we add using declaration, all programs that include this header file get a using std::vector. This has been also emphasized in C++ - Getting Started.
    Second, #ifndef directive responsible for checking whether GUARD_median_h is defined. GUARD_median_h (aka. header guard) is the name of a preprocessor variable that has two status: defined or not defined. The #define directive takes the name and defines it as a preprocessor variable. ifndef is true if the preprocessor variable is undefined and the preprocessor will process following contents until encounter endif. If ifndef is false, subsquent attempts to include median.h will be overlooked to avoid multiple inclusion.

    -

    Reorganize the final grade program

    The reminder of this post aims to reorganize the final grade program applying the technical, seperate compilation,introduced in this post. Let’s list all functions and data structures needed in this program.

    -
      -
    1. read function to read students’ information.
    2. -
    3. read_hw function to read homework grades for each student.
    4. -
    5. compare function as an optional argument in sort.
    6. -
    7. three grade functions(overloaded) to compute the final grade.
    8. -
    9. median function to compute median of homework grades.
    10. -
    11. Beyond above functions, we also defined a data structure, Student_info to hold student’s information.
    12. -
    -

    Logically speaking, these entities can be divided into two groups:

    - -

    Therefore, we can package two groups into two independent files seperately.

    -

    Group 1

    Student_info.h

    -
    #ifndef GUARD_Student_info
    #define GUARD_Student_info

    // Student_info.header file
    #include<iostream>
    #include<string>
    #include<vector>

    struct Student_info{
    std::string name;
    double midterm, final;
    std::vector<double> homework;
    };

    bool compare(const Student_info &, const Student_info &);
    std::istream & read(std::istream &, Student_info &);
    std::istream & read_hw(std::istream &, std::vector<double> &);
    #endif
    -

    Student_info.cpp

    -
    // source file for Student_info related functions
    #include "Student_info.h"
    using std::vector; using std::istream;

    bool compare(const Student_info &x, const Student_info &y)
    {
    return x.name < y.name;
    }

    istream & read(istream &is, Student_info &s)
    {
    // reads and store the student's name, midterm and final exam grades
    is >> s.name >> s.midterm >> s.final;

    // reads and store all homework grades
    read_hw(is, s.homework);
    return is;
    }

    istream & read_hw(istream &in, vector<double> &hw)
    {
    if (in){
    //get rid of previous contents
    hw.clear();

    // read homework grades
    double x;
    while(in >> x)
    hw.push_back(x);

    // clear the stream so that input will work for the next student
    in.clear();
    }
    return in;
    }
    -

    Group 2

    grade.h

    -
    #ifndef GUARD_grade_h
    #define GUARD_grade_h

    // grade.h
    #include<vector>
    #include "Student_info.h"

    double grade(const Student_info &);
    double grade(double, double, const std::vector<double> &);
    double grade(double, double, double);
    double median(std::vector<double>);
    #endif

    **grade.cpp**
    ```c++
    #include <algorithm>
    #include <stdexcept>
    #include <vector>
    #include "grade.h"
    #include "student_info.h"

    using std::domain_error; using std::istream;
    using std::vector; using std::sort;

    // grade function 1
    double grade(const Student_info &s)
    {
    return grade(s.midterm, s.final, s.homework);
    }

    // grade function 2
    double grade(double midterm, double final, const vector<double> &hw)
    {
    if (hw.size() == 0)
    throw domain_error("student has done no homework");
    return grade(midterm, final, median(hw));
    }

    // grade function 3
    double grade(double midterm, double final, double homework)
    {
    return 0.2*midterm + 0.4*final + 0.4*homework;
    }

    // compute the median of vector<double>
    double median(vector<double> vec)
    {
    // get the size of the vector
    typedef vector<double>::size_type vec_size;
    vec_size size = vec.size();

    // check whether the empty is empty
    if (size == 0)
    throw domain_error("median of an empty vector");

    // sort the grades
    sort(vec.begin(), vec.end());

    // compute the median homework grade
    vec_size mid = size/2;
    return size % 2 == 0 ? (vec[mid] + vec[mid - 1])/2 : vec[mid];
    }
    -

    Test

    Inputs:

    Robin 90 87 79 88 81 73 45
    Brendan 70 69 88 100 91 75 66
    Arsenii 99 87 89 88 74 90 70
    Liam 83 66 100 76 87 91 78

    Outputs:

    Arsenii 89.8
    Brendan 76.8
    Liam 77.8
    Robin 84.4
    ]]>
    - - Programming - - - C++ - Notes - -
    - - C++ - Organizing programs with functions - /2018/03/06/C-Organizing-programs-and-data/ - Previous chapters mainly covers topics including

    -
      -
    1. main function structure
    2. -
    3. statements such as expression statements and flow-of-control statements.
    4. -
    5. built-in types, such as int, float, double, bool and char.
    6. -
    7. standard library IO mechanism.
    8. -
    9. standard library string.
    10. -
    11. standard library vector.
    12. -
    -

    We have achieved several goals through certain statements and operations on objects of different types. However, the program becomes unmanageable along with increasingly complex functions and growing information. For this reason, this chapter introduces how to organize programs and data.

    -

    Functions

    Basics

    writing a function

    If we break a program(e.g. A complete program) into pieces, we found that it is in fact constituted by data information, homework grade computation and final grade computation. Both computations can be organized as a function, which is a named block of code. The functions will be called When the computation results are needed. Let’s start with writing a function to compute the final grade, assuming that the homework grade has been computed.

    -
    // compute the final grade of a student
    double grade(double midterm, double final, double homework)
    {
    return 0.2*midterm + 0.4*final + 0.4*homework;
    }
    -

    Basically, it has the same structure as the main function except that we use empty parameter list in previous main function.

    -

    In general, a function includes four parts:

    -
      -
    1. return type. In this case, it has return type of double.
    2. -
    3. function name. In this case, the function is names as grade.
    4. -
    5. parameter list enclosed in parentheses (). In this case, there are three parameters seperated by commas. All three parameters have type of double. They are defined like variables but only be created when the function is called.
    6. -
    7. function body enclosed in curly braces {}. The return statements returns the result to function caller.
    8. -
    -

    calling a function

    When calling the function, the excution of function caller is suspended and execution of the called function begins. We must supply corresponding arguments for the purpose of initializing the parameters. In other words, arguments are the initializers for a function’s parameters. Arguments can be variables or expressions or even values. But they must be provided in the same order as well as the same type as the parameters. If we replace the computation in the original program, it would be like

    -
    cout << "You final grade is " << setprecision(3)
    << grade(midterm, final, sum/count) << setprecision(prec) << endl;
    -

    The first parameter midterm will be initialized by copying the value of argument midterm into it. So do the other parameters. This is what so called call by value. Essentially, these parameters are created in an area independent from the variables in the calling function though they have the same values. Therefore, if the function manipulate these parameters, it wouldn’t change values of the calling function variables. In addition, the parameters are local to the function and only exist start from calling the function to returning from the function. Therefore, it doesn’t matter that we use same name as the variable in the calling function.

    -

    Once the execution encounters the return statement in the function body, the execution of the function ends and back to the calling function.

    -

    Writing a median function

    Now we consider writing a median function that computes the median value of the homework grades. Let’s list four parts of a median function:

    -
      -
    1. the return type should be double.
    2. -
    3. it is named as median for clarity.
    4. -
    5. what we need for computation is only a vector of double type (assuming that we have read all grades).
    6. -
    7. computing algorithms.
    8. -
    -

    It’s pretty straightforward

    -
    double median(vector<double> vec)
    {
    // algorithms to be written
    }
    -

    The algorithm in the original program is

    -
    // get the size of the vector
    typedef vector<double>::size_type vec_size;
    vec_size size = homework.size();

    // check special case
    if (size == 0)
    {
    cout << endl << "You must enter your grades. "
    "Please try again." << endl;
    return 1;
    }

    // sort the grades
    sort(homework.begin(), homework.end());

    // compute the median homework grade
    vec_size mid = size/2;
    double median;
    median = size % 2 == 0 ? (homework[mid] + homework[mid - 1])/2 : homework[mid];
    -

    To write this piece of code into the function, we need to first change the variable name homework to vec as this function suites for more general cases. Nevertheless, you don’t have to do it if you dislike. The second step is to remove the variable median and add return because what this function need to do to return the median value. The last step is to change the code that deals with the case of empty vector.

    -
    if (size == 0)
    throw domain_error("median of an empty vector");
    -

    This is because the original code can not be used here due to it returns another value 1 (unless we change the function structure). In real word programming, throw an exception is a more general way to complain. The usage is explained in next part. Now the function is accomplished as shown below

    -
    // function to compute the median of a vector<double>
    double median(vector<double> vec)
    {
    // get the size of the vector
    typedef vector<double>::size_type vec_size;
    vec_size size = vec.size();

    // check whether the empty is empty
    if (size == 0)
    throw domain_error("median of an empty vector");

    // sort the grades
    sort(vec.begin(), vec.end());

    // compute the median homework grade
    vec_size mid = size/2;
    return size % 2 == 0 ? (vec[mid] + vec[mid - 1])/2 : vec[mid];
    }
    -

    try blocks and Exception handling

    “Exceptions are run-time anomalies—such as losing a database connection or encountering unexpected input—that exist outside the normal functioning of a program.” - Lippman etc. 2012

    -

    throw expressions

    throw expressions is used to detect the exceptions, which is followed by an exception onject that describes the problems that it encounters. It stops the execution of the current function and passes an exception object to the caller for handling it.

    -
    // the detecting part
    if (size == 0)
    // throw raises exceptions
    throw domain_error("median of an empty vector");
    -

    For example, the exception object domian_error contains the information of that the caller can use to act on the exception. It is a type that the standard library defines in header for use in reporting the logic error: argument is out side the values that the function can accept. What closely follows is a string enclosed by parentheses to describe the problem.

    -

    the try block

    The try block is the handling part uses to deal with an exception. Once the exception is thrown, it catches the exception and handle it according to the type of the exception object. The general syntax is

    -
    try{
    // statements including the detecting part
    } catches(exception object1){
    // handler-statements
    } catches(exception object2){
    // handler-statements
    } ...
    -

    The catch clause handles the exception and hence is termed as “exception handler”. If the statements between try and catch don’t throw any exceptions during execution, the program ignores the handler-statements and continue to next part.

    -

    It is worthing noting that each pair of curly braces forms a name scope. The application of the try block will be finished at the end of this post.

    -

    Finish the grade function

    Now we can embed the median function in the grade function.

    -
    // function to compute the final grade which is the weighted average grade of medterm exam grade, final exam grade and the median homework grade
    double grade(double midterm, double final, const vector<double> &hw)
    {
    if (hw.size() == 0)
    throw domain_error("student has done no homework");
    return grade(midterm, final, median(hw));
    }
    -

    reference and call by reference

    It has been noted that this grade function differs from the previous one mentioned at the beigining in the part of parameter list. The third parameter here has a compound type with modifier reference. Recalling that to declare a variable needs a type and a name. More generally, a declaration is a base type followed by a list of declarators including a name and an optional type modifier.

    -
        base type modifier name 
    ```
    A variable declared in above form has a type named **compound type** which is built from the base type. In this case, the third parameter has a type of **vector<double>** with modifier **reference** which indicates that the onject named **hw** refers to its **initializer**. In other words, a reference is a **alias** and **hw** is simply another name for the argument to be passed. In addition, a reference to a reference is in fact that both references refer to the original object. For example
    ```c++
    vector<double> &hw1 = hw; // hw1 is another name for the vector homework
    -

    In contrast to call by value, this is termed as call by reference. When we operate on a reference, we actually operate on the object that the reference refers. For the purpose of computational effiency, passing argument by reference can avoid copies, particularly for objects of large containers or class types. But, it is not a good habit to modify the value of the object that the reference refers. It’s complete ok in this case as the object is passed by copy in the median function where we will operate on the homework grades. Beyond this, there is a const qualifier before the reference, which restrict the values of the object to be changed when operating on the reference.

    -

    overloaded function

    Recalling the previous grade function

    -
    ```c++
    // compute the final grade of a student
    double grade(double midterm, double final, double homework)
    {
    return 0.2*midterm + 0.4*final + 0.4*homework;
    }
    -

    These two functions have the same name but different parameter list. This is termed as function overloading with either different types or numbers of parameters. If two function only differs in return type, functions can not be overloaded. When calling the overloaded function, the complier determines which function to call according to the supplied arguments and the defined parameters in each funciton.

    -

    Writing a reading function

    Finally, we need to solve the problem that how to read home work grades into a vector. The oringal code is

    -
    double x;
    vector<double> homework;

    // enter homework grades followed by end-of-file
    while(cin >> x)
    homework.push_back(x);
    -

    So, what’s this function should return?
    Obviously, the purpose is to fill the vector homework and therefore it should return a filled vector. Beyond this, the function is required to return another value to the stream to indicate whether the attempted input was successful.
    Intuitively, it works like this:

    -

    Funtion work flow

    -

    But it is hard to deal with two returns in one function and alternatively we can define a parameter as a reference type for the purpose of changing the values in homework directly. See the code below

    -
    // read the homework grades from an input stream into a vector homework
    istream & read_hw(istream &in, vector<double> &hw)
    {
    // statements to be filled
    return in;
    }
    -

    lvalue

    We are familar with the second parameter which refers to its initializer to be passed, i.e. the vector homework. Since we intend to modify the passed arguments, the const qualifier has been dropped. There is an important difference between a const reference and a nonconst reference. For a const reference, the arguments to be passed can be any value while a non const reference can only refer to a lvalue object (i.e. a nontemporary object). Any expressions that generate arithmetic values are not lvalue. For example

    -
    int i = 10;
    int &j = i; // correct: j is bound to i
    int &m = 10; // error: initializer must be an nontemporary object
    const int &n = 10; // correct: a const reference
    -

    member function clear

    The first parameter is also a type of non-const reference which refers to the object cin. This is because we hope to change its internal state. As a result, the return type is also a reference as in is a reference. Another reason is that there is no copy or assign for IO objects.

    -

    Now we consider read entered grades into homework. Remember that We propose to write a program that can deal with multiple students’ records. One problem is that the vector might contain the grades of the last student. To keep the vector empty, we use hw.clear() to discard any contents the vector might have had.

    -

    Similarly, we also need to keep the cin be valid for each student. In previous chapter, We have explained that once we finishes typing in the homework grades a signal end-of-file needs to be sent for terminating the loop. The signal will change the internal state of the cin to be false. In addition, end-of-file is not the only input that can stop the loop. If we enter values of an improper type, the library would mark the input stream as being in failure state as well. For this reason, we use in.clear() to clear the error state of cin after finishing the input for one student. Note that both the vector and the object of istream have member function clear but the effects are completely different. The function is shown below

    -
    // read homework grades from an input stream inti a vector
    istream & read_hw(istream &in, vector<double> &hw)
    {
    if (in){
    //get rid of previous contents
    hw.clear();

    // read homework grades
    double x;
    while(in >> x)
    hw.push_back(x);

    // clear the stream so that input will work for the next student
    in.clear();
    }
    return in;
    }
    -

    A complete program

    Up to now, we have changed the computations in the original program to functions including a function to homework grades, a function to calculate the median of homework grades and a function to calculate the final grade. A complete program is presented below

    -
    // include directives
    #include<iostream> // to get declaration of cin, cout, endl,
    #include<istream> // to get declaration of istream
    #include<string> // to get declaration of string
    #include<vector> // to get declaration of vector
    #include<algorithm>// to get declaration of sort
    #include<ios> // to get declaration of streamsize
    #include<stdexcept>// to get declaration of domain_error
    #include<iomanip> // to get declaration of setprecision

    // add using declarations
    using std::cin; using std::setprecision;
    using std::cout; using std::streamsize;
    using std::endl; using std::domain_error;
    using std::string; using std::istream;
    using std::vector; using std::sort;

    // declare functions
    istream & read_hw(istream &in, vector<double> &hw);
    double median(vector<double> vec);
    double grade(double midterm, double final, double homework);
    double grade(double midterm, double grade, const vector<double> &hw);

    // main function
    int main()
    {
    // ask for and read the student's name
    cout << "Please enter your first name: ";
    string name;
    cin >> name;
    cout << "Hello, " << name << "!" << endl;

    // ask for and read the midterm and final grades
    cout << "Please enter your midterm and final exam grades: ";
    double midterm, final;
    cin >> midterm >> final;

    // ask for the homework grades
    cout << "Enter all your homework grades, followed by end-of-file: ";

    // read the homework grades
    vector<double> homework;
    read_hw(cin, homework);

    // compute and generate the final grade, if possible
    try {
    double final_grade = grade(midterm, final, homework);
    streamsize prec = cout.precision();
    cout << "Your final grade is " << setprecision(3)
    << final_grade << setprecision(prec) <<endl;
    } catch(domain_error) {
    cout << endl << "You must enter your grade. Please trt again." << endl;
    return 1;
    }
    return 0;
    }

    // define function to read homework grade
    istream & read_hw(istream &in, vector<double> &hw)
    {
    if (in){
    //get rid of previous contents
    hw.clear();

    // read homework grades
    double x;
    while(in >> x)
    hw.push_back(x);

    // clear the stream so that input will work for the next student
    in.clear();
    }
    return in;
    }

    // define function to calculate median value
    double median(vector<double> vec)
    {
    // get the size of the vector
    typedef vector<double>::size_type vec_size;
    vec_size size = vec.size();

    // check whether the empty is empty
    if (size == 0)
    throw domain_error("median of an empty vector");

    // sort the grades
    sort(vec.begin(), vec.end());

    // compute the median homework grade
    vec_size mid = size/2;
    return size % 2 == 0 ? (vec[mid] + vec[mid - 1])/2 : vec[mid];
    }

    // define a function to calculate final grade
    double grade(double midterm, double final, const vector<double> &hw)
    {
    if (hw.size() == 0)
    throw domain_error("student has done no homework");
    return grade(midterm, final, median(hw));
    }

    //define a function to calculate final grade (function overloading)
    double grade(double midterm, double final, double homework)
    {
    return 0.2*midterm + 0.4*final + 0.4*homework;
    }
    - -

    See from above program, why the statements inside the try block are not organized as such form

    -
    try {
    streamsize_prec = cout.precision();
    cout << "Your final grade is " << setprecision(3)
    << grade(midterm, final, homework)
    << setprecision(prec) <<endl;
    } catch...
    ...
    -

    In doing so, we probably can’t control the outputs as the grade function may be called after or before the string literals depending on the implementation. Also, if any exception is thrown, the precision may not be reset back to the original value as expected.

    -
    -

    To be continued.

    -]]>
    - - Programming - - - C++ - Notes - -
    - - Accelerated C++ Solutions to Exercises(Chapter 3 Part 2) - /2018/03/05/Accelerated-C-Solutions-to-Exercises-Chapter-3-Part-2/ - Exercise 3-5

    Write a program that will keep track of grades for several students at once. The program could keep two vectors in sync: The first should hold the student’s names, and the second the final grades that can be computed as input is read. For now, you should assume a fixed number of homework grades.

    -

    Solution & Results

    The exercise tries to make the original program (see the first program) more practical. There are two more requirements compared with the original one: first, it requires computing the final grades for several students at once; second, it requires keep tracking both the students’ name and their final grades.

    -

    To meet the first requirements, we can add a while loop which allows us compute the final grades multiple times for different student. The condition will be an variable whose value depends on users. Let’s finish this first

    -
    // set initial status
    bool running = true;
    while(running)
    {
    // compound statements


    char flag;
    cout << "Do you want to check more students? Please input Y/N: ";
    cin >> flag;

    if (flag == 'Y') ;
    else running = false;
    }
    -

    To meet the second requirement, we can store both names and the final grades into two vectors.

    -
    vector<string> names;
    vector<double> finalGrades;
    -

    Each time we store a student’s name, we will store his final grade after computation. When needs writing the outputs, we can use same index for both vectors as there is a one-to-one correspondence.

    -

    Below is the modified program

    -
    #include <iomanip>
    #include <ios>
    #include <iostream>
    #include <string>
    #include <vector>

    using std::cin; using std::setprecision;
    using std::cout; using std::string;
    using std::endl; using std::streamsize;
    using std::vector;

    int main()
    {
    vector<string> names;
    vector<double> finalGrades;
    bool running = true;
    while(running)
    {
    // ask for and read the student's name
    cout << "Please enter your first name: ";
    string name;
    cin >> name;

    // store the name into names
    names.push_back(name);
    cout << "Hello, " << name << "!" << endl;

    // ask for and read the midterm and final grades
    cout << "Please enter you midterm and final exam grades: ";
    double midterm, final;
    cin >> midterm >> final;

    // ask for the number of homework grades
    cout << "Please enter the number of your homeworks: ";
    int numofHomeworks;
    cin >> numofHomeworks;

    // ask for all homework grades
    cout << "Enter all your homework grades: ";

    // the number and the sum of grades read so far
    int count = 0;
    double sum = 0;

    // a variable into which to read
    double x;

    // invariant: we have read count grades so far, and sum is the sum of the first count grades
    while (count < numofHomeworks)
    {
    ++count;
    cin >> x;
    sum += x;
    }

    // compute the fianl grade and store into finalGrades
    double finalGrade = 0.2 * midterm + 0.4 * final + 0.4 * sum/count;
    finalGrades.push_back(finalGrade);

    // check condition
    char flag;
    cout << "Do you want to check more students? Please input Y/N: ";
    cin >> flag;
    if (flag == 'Y') ;
    else running = false;
    }

    // prepare for writing outputs
    typedef vector<string>::size_type vec_size;


    for (vec_size i = 0; i != names.size(); ++i)
    {
    streamsize prec = cout.precision();
    cout << names[i] << "'s final grade is: " << setprecision(3) << finalGrades[i]
    << setprecision(prec) << endl;
    }
    return 0;
    }
    -

    It’s not complex once figure out the original program. I test the program and it works well (see below).
    Test

    -
    Please enter your first name: Conor
    Hello, Conor!
    Please enter you midterm and final exam grades: 88 70
    Please enter the number of your homeworks: 3
    Enter all your homework grades: 65 76 81
    Do you want to check more students? Please input Y/N: Y
    Please enter your first name: Brendan
    Hello, Brendan!
    Please enter you midterm and final exam grades: 70 80
    Please enter the number of your homeworks: 2
    Enter all your homework grades: 90 85
    Do you want to check more students? Please input Y/N: Y
    Please enter your first name: Robin
    Hello, Robin!
    Please enter you midterm and final exam grades: 90 90
    Please enter the number of your homeworks: 5
    Enter all your homework grades: 80 70 60 50 40
    Do you want to check more students? Please input Y/N: N

    Conor's final grade is: 75.2
    Brendan's final grade is: 81
    Robin's final grade is: 78
    -

    Note that the final grades don’t retain the tail zeros and the dot point though we set precision arguments as 3. Please find more experiments about setprecision at Working with batches of data .

    -
    -

    Exercise 3-6

    The average-grade computation in the first program might divide by zero if the student didn’t enter any grades. Division by zero is undefined in C++, which means that the implementation is permitted to do anything it likes. What does your C++ implementation do in this case? Rewrite the program so that its behavior does not depend on how the implementation treats division by zero.

    -

    Solution & Results

    To figure out how c++ implementation deals with division by 0, I did several experiments.
    Experiment 1

    -
    #include <iostream>
    using std::cout;
    using std::endl;

    int main(){
    int i = 10;
    int j = 0;
    cout << i/j << endl;
    returo 0;
    }
    -

    The result shows that disivion by 0 with both integers makes the program crash.

    -

    Experiment 2

    -
    #include <iostream>
    using std::cout;
    using std::endl;

    int main(){
    int i = 10;
    double j = 0;
    cout << i/j << endl;

    double x = 10;
    int y = 0;
    cout << x/y << endl;

    double m = 10;
    double n = 0;
    cout << m/n << endl;

    return 0;
    }
    -

    This program works and returns three inf which means infinity.

    -

    Exerpriment 3

    -
    #include <iostream>
    using std::cout;
    using std::endl;

    int main(){

    double h = 0;
    double k1 = 0;
    int k2 = 0;

    cout << h/k1 << endl;
    cout << h/k2 << endl;

    return 0;
    }
    -

    The third program is the case of the original program and it returns nan which means Not-A-Number.

    -

    There is one question on quora Why does division by zero return INF (infinite) with floats, but makes the program crash with integers in C++?. Many answers can be found there but I can’t understand the mechanism well.

    -

    Anyway, division by 0 is a special case and should be treated seperately. For the original program, one way is to check the count before the division. For example, we can add below piece of code after the while loop finishes

    -
       if (count == 0)
    {
    cout << "No homework grades entered, please try again.";
    return 1;
    }
    -

    Alternatively, we can set a default value for the average homework grade.

    -
    double homeworkGrade;
    if (count == 0)
    homeworkGrade = 0;
    else
    homeworkGrade = sum/count;
    -

    Now I apply the second method and present the modified program below.

    -
    #include <iomanip>
    #include <ios>
    #include <iostream>
    #include <string>

    using std::cin; using std::setprecision;
    using std::cout; using std::string;
    using std::endl; using std::streamsize;

    int main()
    {
    // ask for and read the student's name
    cout << "Please enter your first name: ";
    string name;
    cin >> name;
    cout << "Hello, " << name << "!" << endl;

    // ask for and read the midterm and final grades
    cout << "Please enter you midterm and final exam grades: ";
    double midterm, final;
    cin >> midterm >> final;

    // ask for the homework grades
    cout << "Enter all your homework grades, "
    "followed by end-of-file: ";

    // the number and the sum of grades read so far
    int count = 0;
    double sum = 0;

    // a variable into which to read
    double x;

    // invariant: we have read count grades so far, and sum is the sum of the first count grades
    while (cin >> x)
    {
    ++count;
    sum += x;
    }

    // special case treatment
    double homeworkGrade;
    if (count == 0)
    homeworkGrade = 0;
    else
    homeworkGrade = sum/count;

    // write a blank line to seperate outputs
    cout << endl;

    // write the result
    streamsize prec = cout.precision();
    cout << "Your final grade is " << setprecision(3) <<
    0.2 * midterm + 0.4 * final + 0.4 * homeworkGrade
    <<setprecision(prec) << endl;
    return 0;
    }
    -

    Test

    -
    Please enter your first name: Brendan
    Hello, Brendan!
    Please enter you midterm and final exam grades: 95 77
    Enter all your homework grades, followed by end-of-file:

    Your final grade is 49.8
    -

    Now, it works better than the original one.

    -
    -

    Reference

    Koenig, A. and Moo, B.E., 2000. Accelerated C++.

    -

    Lippman, S.B., Lajoie, J. and Moo, B.E., 2013. C++ Primer, 5th edt.

    -]]>
    - - Programming - - - Accelerated C++ Solutions - -
    - - Accelerated C++ Solutions to Exercises(Chapter 3 Part 1) - /2018/03/04/Accelerated-C-Solutions-to-Exercise-Chapter-3/ - Exercise 3-0

    Compile, execute, and test the programs in this chapter

    -

    Solution & Results

    This exercise has been accomplished in C++ - Working with batches of data with detailed explination.

    -
    -

    Exercise 3-1

    Suppose we wish to find the median of a collection of values. Assume that we have read some of the values so far, and that we have no idea how many values remain to be read. Prove that we cannot afford to discard any of the values that we have read. Hint: One proof strategy is to assume that we can discard a value, and then find values for the unread—and therefore unknown—part of our collection that would cause the median to be the value that we discarded.

    -

    Solution & Results

    It is known that the median value of a data sample is sensitive to the number of elements. If the number is an odd number, the mid element is unique and the median value is the value of this mid element. However, if the number is an even number, there exist two mid elements and the median value is the average value of these two elements. Consider that, once a value is discarded, the number of the elements changes from odd (or even) to even (odd), and hence the median value would be inaccurate.

    -

    For example, follow sequence of values is part of a data sample,

    -
    2 8 3 4 9 7 0
    -

    the unknown part is

    -
    11 6 13 9
    -

    The ture median value is 7. If 0 is discarded, the median value becomes 7.5. If 7 is discarded, the median value is still 7. If 9 is discarded, the median value is 6.5. This indicates that the median value would be an unreliable measure for a data sample if any of values is discarded.

    -
    -

    Exercise 3-2

    Write a program to compute and print the quartiles (that is, the quarter of the numbers with the largest values, the next highest quarter, and so on) of a set of integers.

    -

    Solutions & Results

    Quartiles of an ordered dataset are values that divide the data set into four equal parts, i.e. quarters. An intuitive strategy is to find the median value of the whole data set, and then find the median values of the divided two parts respecitively. As a result, there will exist three median values which are first quartile, second quartile and third quartile relative to the whole data set. Therefore, once we know how to compute the median value, we know how to compute quartiles. Now I’ll enter into the details of how to implement such a computing algorithm.

    -

    data preparation

    At the very beiging stage, we need to store and sort all values

    -
     cout << "Please enter a sequence of integers, "
    "followed by end-of-file: ";
    int x;
    vector<int> integers; // to hold all values
    while(cin >> x)
    integers.push_back(x);
    sort(integers.begin(), integers.end());
    -

    find the median value

    Now we have a sorted data set. Due to the calculation of a median value depends on the number of elements in the data set, we need to consider several cases. The number of elements is the size of integers.

    -
    typedef vector<int>::size_type vec_size;
    vec_size size = integers.size();
    -
      -
    1. if there is no elements(i.e. size == 0), it is impossible to compute the median value.

      -
      if (size == 0)
      {
      cout << "You must enter at least one integer. "
      "Please try again.";
      return 0;
      }
      -
    2. -
    3. if size is an even number, the median value is the average value of two mid elements. In addition, size/2 is exactly devided and represent the number of each side elements.
      Due to the index of a vector starts from 0, the mid elements should be integers[size/2 - 1] and integers[size/2]. Therefore, the median value is the average of the corresponding two values. As shown in above graph, the number of elements of both sides equals to size/2 because

      -
      left side:  number = size/2 - 1 - 0 + 1 = size/2
      right side: number = size - 1 - size/2 + 1 = size/2
      -

      If size is an even number

      -
    4. -
    5. if size is an odd number, the median value is the value of the unique mid element. size/2 yields the same value as (size-1)/2 in c++. Then the mid element is exact the integers[(size-1)/2] as both sides of this element has same number of elements(as shown in below graph). This can also be verified mathematically

      -
      left side:  number = (size-1)/2 - 1 - 0 + 1 = (size-1)/2
      right side: number = size - 1 - {(size-1)/2 + 1} + 1 = (size-1)/2;
      -

      If size is an odd number

      -
    6. -
    -

    Now let’s translate the algorithm into real code

    -
    vec_size mid = size/2;
    double Q2; // the median is in fact the second quartile denoted by Q2
    Q2 = size % 2 == 0 ? (integers[mid - 1] + integers[mid])/2.0
    : integers[mid];
    -

    Note that when calculate the average of two mid elements, I divide by 2.0 rather than 2 for the purpose of avoiding the loss of precision.This is because the quotient of two integers will be an integer.

    -

    find the first quartile

    From above analysis, we have known how to find a median, i.e. the second quartile of the dataset. The median has divided the data into two equal groups: first group is from the smallest value to the median and the other one is from the median value to the largest value. Therefore, the middle value of the first group is in fact the first quartile(also known as lower quartile) and the middle value of the second group is the third quartile (also known as upper quartile). We can apply the same method to find both middle values. Similarly, the first step is to find the size (denoted by half_size) for both groups and discuss different cases.

    -

    From above analysis, we know that

    -
      -
    1. if size is an even number, half_size == size/2.
    2. -
    3. if size is an odd number, half_size == (size-1)/2. See wikipedia-Quartile Method 1
    4. -
    -
    vec_size half_size; // defind a variable to represent the size of two equal groups.
    half_size = size % 2 == 0 ? size/2 : (size-1)/2;
    -

    Now let’s find the middle value of the first group values.

    -
      -
    1. if half_size == 0, then size == 1. In this case, the single element is all we have and hence all quartiles equals to the value of the single element.
    2. -
    -
    // variables to hold the first quartile and third quartile
    double Q1, Q3;
    if (half_size == 0)
    Q1 = Q3 = integers[0];
    -
      -
    1. if half_size is an even number, half_size/2 is exactly divided and the mid elements are integers[half_size/2] and integers[half_size/2 - 1].

      -
    2. -
    3. if half_size is an odd number, half_size/2 gives the value of (half_size - 1)/2. The middle value is integers[(half_size-1)/2].

      -
    4. -
    -

    Both two cases are exactly the same as finding the median for the whole dataset.

    -
    vec_size mid_first = half_size/2;
    Q1 = half_size % 2 == 0 ? (integers[mid_first - 1] + integers[mid_first])/2.0 : integers[mid_first];
    - -

    find the third quartile

    The upper quartile is computed as same as the lower quartile except the index to be applied. If size is even, then the starting point for the second half is mid, while if size is odd, the starting point is mid+1. Therefore

    -
    vec_size mid_second = size % 2 == 0 ? (half_size/2 + mid) : (half_size/2 + mid + 1);
    Q3 = half_size % 2 ? (integers[mid_second - 1] + integers[mid_second])/2.0
    : integers[mid_second];
    - -

    A complete program

    Now let’s put all pieces of code togther and add appropriate headers as well s using declarations.

    -
    #include <algorithm>
    #include <iomanip>
    #include <ios>
    #include <iostream>
    #include <string>
    #include <vector>

    using std::cin; using std::sort;
    using std::cout; using std::streamsize;
    using std::endl; using std::string;
    using std::setprecision; using std::vector;

    int main()
    {
    // ask for and read integers
    cout << "Please enter a sequence of integers, "
    "followed by end-of-file: ";
    int x;
    vector<int> integers; // to hold all values

    while(cin >> x)
    integers.push_back(x);
    sort(integers.begin(), integers.end());

    // get the size of the dataset
    typedef vector<int>::size_type vec_size;
    vec_size size = integers.size();

    if (size == 0)
    {
    cout << "You must enter at least one integer. "
    "Please try again.";
    return 0;
    }

    // find the median value which in fact is the second quartile denoted by Q2
    vec_size mid = size/2;
    double Q2;
    Q2 = size % 2 == 0 ? (integers[mid - 1] + integers[mid])/2.0 : integers[mid];

    // get the size of two equal groups.
    vec_size half_size;
    half_size = size % 2 == 0 ? size/2 : (size-1)/2;

    // find the first quartile and third quartile denoted by Q1 and Q3 respectively
    double Q1, Q3;
    if (half_size == 0)
    {
    Q1 = Q3 = integers[0];
    }
    else
    {
    vec_size mid_first = half_size/2;
    Q1 = half_size % 2 == 0 ? (integers[mid_first - 1] + integers[mid_first])/2.0 : integers[mid_first];

    vec_size mid_second = size % 2 == 0 ? (half_size/2 + mid) : (half_size/2 + mid + 1);
    Q3 = half_size % 2 == 0 ? (integers[mid_second - 1] + integers[mid_second])/2.0 : integers[mid_second];
    }

    streamsize prec = cout.precision();
    cout << setprecision(3) << "The first quartile is: " << Q1 << endl;
    cout << "The second quartile is: " << Q2 << endl;
    cout << "The second quartile is: " << Q3 << setprecision(prec) << endl;
    return 0;
    }
    - -

    Test performance

    Test sequence 1: 1 2

    -
    Please enter a sequence of integers, followed by end-of-file: 1 2
    The first quartile is: 1
    The second quartile is: 1.5
    The second quartile is: 2
    -

    Test sequence 2: 1 2 3

    -
    Please enter a sequence of integers, followed by end-of-file: 1 2 3
    The first quartile is: 1
    The second quartile is: 2
    The second quartile is: 3
    -

    Test sequence 3: 1 2 3 4

    -
    Please enter a sequence of integers, followed by end-of-file: 1 2 3 4
    The first quartile is: 1.5
    The second quartile is: 2.5
    The second quartile is: 3.5
    -

    Test sequence 3: 1 3 5 6 9 0 3 2 5 3 8

    -
    Please enter a sequence of integers, followed by end-of-file: 1 3 5 6 9 0 3 2 5 3 8 
    The first quartile is: 2
    The second quartile is: 3
    The second quartile is: 6
    -

    Yeah, it works perfectly.

    -
    -

    Exercise 3-3

    Write a program to count how many times each distinct word appears in its input.

    -

    Solution & Results

    Intuitive explanations

    The purposes of this program is to write out each distinct word followed by its occurrence numbers in the input. And what we have is only unknown amount of words to be entered. Imagine that there is only one word(e.g. word1) in total, then the output will be:

    -
    word1 appears 1 times
    -

    Now in the case of two words, e.g. word1 and word2, if word1 is the same as word2, then the output will be:

    -
    word1 appears 2 times
    -

    otherwise

    -
    word1 appears 1 times
    word2 appears 1 times
    -

    From these two cases, we have seen that

    -
      -
    1. word1 needs to be stored and its occurrence needs to be recorded
    2. -
    3. word2 needs to be compared with word1. As a result, it will be discarded if they are the same and the occurrence number of word1 is increased by 1, and it will be stored if they are different and its occurrence number increases by 1.
    4. -
    -

    By analogy, following entered words will be compared with each stored word, and will be discarded if there already exist one same word otherwise will be stored, meanwhile, the corresponding occurrence numbers are adjusted. Finally, all stored words are distinct with eachother and their occurrence numbers have been clearly recorded. Now I enter the details of this program.

    -

    vectors and the structure

    To hold each distinct word and the associated number of occurrence, I define two vectors whis have types of int and string respectively.

    -
    vector<int> counter;
    vector<string> words;
    -

    The next step is to ask for enterring word by word and store the distinct word. This can be accomplished with a while statement. To write all distinct words as well as the occurrence numbers, we can loop through words and counter using index from 0 to words.size() - 1.
    Therefore, the whole structure is

    -
    int main()
    {
    vector<int> counter;
    vector<string> words;
    typedef vector<string>::size_type vec_size;

    string word;
    while(cin >> word)
    {
    /* pseudocode
    * if the word is a new distinct word
    * words.push_back(word);
    * counter.push_back(1);
    * if the word already exists
    * adjust the number of occurrence for
    * the existed distinct word
    */
    }

    // if there is no inputs, send warning
    if (words.empty())
    {
    cout << "You must input at least one word. Please try again.";
    return 1;
    }

    for (vec_size i = 0; i != words.size(); ++i)
    {
    cout << words[i] << " appears " << counter[i] << " times" << endl;
    }
    return 0;
    }
    -

    loop invariants

    The only part that is needed is write the while body. The loop invariant is that words contains each distinct word entered and counter contains the associated number of occurrence. Therefore, two goals need to be accomplished inside the while loop:

    -
      -
    1. check whether the current is distinct from all existed distinct words
    2. -
    3. adjust word and counter to maintain the loop invariant
    4. -
    -

    The first goal can be accomplished by comparing the current word with each word stored in words. In addition, a flag is set to indicate the status of the outcomes, that is, a distinct word or an existed word. Let’s see the code below

    -
    int flag = 0; // initial status
    for (vec_size i = 0; i != words.size(); ++i)
    {
    if (word == words[i])
    {
    // if the word is already existed, discard the word but change the counter
    ++counter[i];

    // change status show this word is not a distinct word
    flag = 1;
    }
    }
    -

    The second goal is partly accomplished by the code above and the rest case is when the current word is a distinct word. Accordingly, the word should be stored into words and the initial value for occurrence number is 1.

    -
    if (flag == 0)
    {
    words.push_back(word);
    counter.push_back(1);
    }
    - -

    Now the program is finished, and please find the complete version in the following part. I also did several tests and it works as expected. Note that I omitted the process that verify the correctness of the loop invariants here.

    -

    A complete program

    #include <string>
    #include <vector>
    #include <iostream>

    using std::cin; using std::string;
    using std::cout; using std::vector;
    using std::endl;

    int main()
    {
    // vectors for holding each distinct word and its occurrence numbers
    vector<int> counter;
    vector<string> words;

    // using type alias for convenience
    typedef vector<string>::size_type vec_size;

    // ask for and read words one by one
    cout << "Please enter a sequence of words, and followed by end-of-file: ";
    string word;

    // while loop invariant: words and counter contains each distinct word and the associated times
    while(cin >> word)
    {
    // set flags to indicate whether the current word already exists
    // flag == 0: the word is distinct from each existed distinct word
    // flag == 1: the word is already exist
    int flag = 0;

    // to maintain the outer loop invariant, compare the current word
    // with each existed distinct word using following for loops

    // for loop invariant: the current word has been compared with the ith distinct word
    for (vec_size i = 0; i != words.size(); ++i)
    {
    // adjust the number of occurrence by 1 for existed distinct words
    if (word == words[i])
    {
    ++counter[i];
    flag = 1; // change flag value to indicate that this word is distinct
    }
    }

    // maintain the outer loop invariant: store the new distinct word and its occurrence number
    if (flag == 0)
    {
    words.push_back(word);
    counter.push_back(1);
    }
    }

    // send warning if there is no any words entered
    if (words.empty())
    {
    cout << "You must input at least one word. Please try again.";
    return 1;
    }

    // write a blank line to seperate the outputs
    cout << endl;
    for (vec_size i = 0; i != words.size(); ++i)
    {
    cout << words[i] << " appears " << counter[i] << " times" << endl;
    }
    return 0;
    }
    -

    Test performance

    Test 1: house

    -
    Please enter a sequence of words followed by end-of-file: house

    house appears 1 times
    -

    Test 2: house number one and number two

    -
    Please enter a sequence of words followed by end-of-file: house number one and number two

    house appears 1 times
    number appears 2 times
    one appears 1 times
    and appears 1 times
    two appears 1 times
    - -
    -

    Exercise 3-4

    Write a program to report the length of the longest and shortest string in its input.

    -

    Solution & Results

    Strategy 1

    A very simple solution strategy to this exercise is that store the length of each string into a vector and then implement a library sort algorithm. I won’t go into details as it is simple. Please find the code and tests below.

    -
    #include <algorithm>
    #include <string>
    #include <vector>
    #include <iostream>

    using std::cin; using std::string;
    using std::cout; using std::vector;
    using std::endl;

    int main()
    {
    // ask for words
    cout << "Please enter words followed by end-of-file: ";
    string word;

    // creat a vector for holding the length of each string
    typedef string::size_type word_size;
    vector<word_size> length;

    // read word and store the length
    while(cin >> word)
    {
    length.push_back(word.size());
    }

    if (length.empty())
    {
    cout << "You must input at least one word."
    "Please try again.";
    return 1;
    }

    // sort all lengths in an nondecreasing order
    sort(length.begin(), length.end());
    cout << "The length of the shortest string is: " << length[0] << endl;
    cout << "The length of the longest string is: " << length[length.size() - 1] << endl;
    return 0;
    }
    -

    Test 1: I am a good teacher

    -
    Please enter words followed by end-of-file: I am a good teacher 
    The length of the shortest string is: 1
    The length of the longest string is: 7
    -

    Test 2: what are you going to do

    -
    Please enter words followed by end-of-file: what are you going to do
    The length of the shortest string is: 2
    The length of the longest string is: 5
    -

    Strategy 2

    Above strategy is computational inefficient due to the fact that it sorts all length values while we only need two extremes. An alternative strategy is to use insert sort algorithm but only sort one round for each of extremes. For example, now there is a sequence of integers

    -
    n1 n2 n3 n4 ... nx ny nz
    -

    Step 1: assumming the largest number is n1.

    -

    Step 2: compare n1 and n2, if n1 >= n2, we exchange their positions with eachother. But if n1 < n2, we keep their order and assume n2 is the largest number.

    -

    Step 3: compare the larger number of step 2 with n3. Deal with the comparison result as same as that in step 2.

    -

    Step 4: continue comparison until the last number. Now the number in the rightest position is the final result, i.e. the largest number.

    -

    By analogy, we can find the smallest number. In this case, the precedure is simpler as we don’t need to exchange their positions. For finding the largest number, we simply discard the smaller value in each comparison. It is pretty easy to understand and no more detailed description here. Please see the complete program below.

    -
    #include <iostream>
    #include <string>

    using std::cin; using std::endl;
    using std::cout; using std::string;


    int main()
    {
    // ask for inputs
    cout << "Please enter words followed by end-of-file: ";
    string word;
    typedef string::size_type str_size;

    // variables for holding the length values of the longest and shortest strings
    str_size longestLength = 0;
    str_size shortestLength = 0;

    // read words one by one
    while(cin >> word)
    {
    // insert sort to get the largest value and samllest value only
    if (longestLength == 0 || longestLength < word.size())
    longestLength = word.size();
    if (shortestLength == 0 || shortestLength > word.size())
    shortestLength = word.size();
    }

    // if there is no inputs, send warning
    if (longestLength == 0)
    {
    cout << "You must enter at least one word. Please try again.";
    return 1;
    }

    cout << "The length of the shortest string is: " << shortestLength << endl;
    cout << "The length of the longest string is: " << longestLength << endl;
    return 0;
    }
    -

    Test 1: I am a good teacher

    -
    Please enter words followed by end-of-file: I am a good teacher 
    The length of the shortest string is: 1
    The length of the longest string is: 7
    -

    Test 2: what are you going to do

    -
    Please enter words followed by end-of-file: what are you going to do
    The length of the shortest string is: 2
    The length of the longest string is: 5
    -

    The results of above two programs are exactly the same.

    -
    -

    To be continued.

    -]]>
    - - Programming - - - Accelerated C++ Solutions - -
    - - C++ - Working with batches of data - /2018/03/02/C-Working-with-batches-of-data/ - Imagine a course in which each student’s final exam counts for 40% of the final grade, the midterm exam counts for 20%, and the average homework grade makes up the remaining 40%. Now we are asked to write a program that reads a student’s exam and homework grades and computes a final grade.

    -
    #include <iomanip>
    #include <ios>
    #include <iostream>
    #include <string>

    using std::cin; using std::setprecision;
    using std::cout; using std::string;
    using std::endl; using std::streamsize;

    int main()
    {
    // ask for and read the student's name
    cout << "Please enter your first name: ";
    string name;
    cin >> name;
    cout << "Hello, " << name << "!" << endl;

    // ask for and read the midterm and final grades
    cout << "Please enter you midterm and final exam grades: ";
    double midterm, final;
    cin >> midterm >> final;

    // ask for the homework grades
    cout << "Enter all your homework grades, "
    "followed by end-of-file: ";

    // the number and the sum of grades read so far
    int count = 0;
    double sum = 0;

    // a variable into which to read
    double x;

    // invariant: we have read count grades so far, and sum is the sum of the first count grades
    while (cin >> x)
    {
    ++count;
    sum += x;
    }

    // write the result
    streamsize prec = cout.precision();
    cout << "Your final grade is " << setprecision(3)
    << 0.2 * midterm + 0.4 * final + 0.4 * sum/count
    << setprecision(prec) << endl;
    return 0;
    }
    -

    More about IO system

    The goal of above program is to compute a final grade, which is a simple math question-computing the weighted average. Let’s start from the #include directives

    -
    #include <iomanip>
    #include <ios>
    #include <iostream>
    #include <string>
    -

    We are familar with which is the header that defines the standard input/output stream objects, and which is the header that defines string type objects. Correspondingly, objects cin , cout and endl are defined in the iostream library, and string is defined in the string class. These names are defined in the namespace std, and need to be declared in the form of std::name or using std::name before we can use them.

    -

    Similarly, the is a header that defines the type streamsize which represents sizes. The defines the manipulator setprecision which sets the decimal precision. Both names streamsize and setprecision are defined in the namespace std.

    -

    setprecision, shownpoint & fixed

    setprecision is used to format floating-point values, such as float and double type values. It manipulates the stream by causing the subsequent output on that stream to be written with a given number of digits. The syntax is

    -
    << setprecision(int n) <<
    -

    The parameter n determines the number of digits to be written. In this case, setprecision(3) means the output will remain three digits. Let’s do some experiments to explore more about setprecision

    -

    Experiment 1

    -
    #include <iomanip>
    #include <ios>
    #include <iostream>

    using std::cin; using std::setprecision;
    using std::cout; using std::endl;


    int main()
    {
    int i = 12345;
    float f1 = 12345;
    float f2 = 3.148;;
    float f3 = 0.03148;
    float f4 = 0.03108;
    float f5 = 0.03100;
    float f6 = 1000.435;

    cout << setprecision(3) << i << endl;
    cout << f1 << endl;
    cout << f2 << endl;
    cout << f3 << endl;
    cout << f4 << endl;
    cout << f5 << endl;
    cout << f6 << endl;

    return 0;
    }
    -

    The result is shown below

    -
    12345
    1.23e+004
    3.15
    0.0315
    0.0311
    0.031
    1e+003
    -

    It can be seen from this experiment that

    -
      -
    1. setprecision doesn’t work on int type values
    2. -
    3. the parameter n (in this case is 3) controls the number of significant digits (i.e. count from the first non-zero number).
    4. -
    5. it follows the rounding principles.
    6. -
    7. it omits the trailing 0s.
    8. -
    -

    If one want to keep the trailing zero, the manipulator showpoint can be used. showpoint is declared in the ios library and its name is also in the namespace std. It sets the format flag and always includes the decimal point as well as the tail 0 for matching the precision. The showpoint flag can be unset with the noshowpoint manipulator.

    -

    If we want control the precision of the decimal part, we can use fixed manipulator to fixed the decimal part. Together with the setprecision(n), the number of digits in the fractional part will be fixed at n. If there is no enough numbers after the decimal point, zero will be added to match the precision.

    -

    To veryfy the usages of showpoint and fixed, I did tests as follows.

    -

    Experiment 2

    -
    #include <iomanip>
    #include <ios>
    #include <iostream>

    using std::cin; using std::setprecision;
    using std::cout; using std::fixed;
    using std::endl; using std::showpoint;
    using std::noshowpoint;


    int main()
    {
    float f1 = 1000.00;
    float f2 = 3.14800;
    float f3 = 0.03148;


    cout << showpoint << setprecision(4) << f1 << endl;
    cout << f2 << endl;
    cout << f3 << noshowpoint << endl;

    // write a bank line to sepearate outputs
    cout << endl;

    cout << fixed << setprecision(6) << f1 << endl;
    cout << f2 << endl;
    cout << f3 << endl;

    return 0;
    }
    -

    The result is as analysed above

    -
    1000.
    3.148
    0.03148

    1000.000000
    3.148000
    0.031480
    -

    streamsize

    Once we changed the precision, the subsequent output would be formated to match the precision. If we want to change back, we can reset the precision to the original setting if we know the precision value. If we don’t know the previous setting of the cout, the cout.presicion returns us the value and its type is streamsize.

    -

    Without using setprecision, we could use cout.precision(n) to set the precision. The usage is shown as below

    -

    Experiment 3

    -
    #include <iomanip>
    #include <ios>
    #include <iostream>

    using std::cin; using std::setprecision;
    using std::cout; using std::fixed;
    using std::endl; using std::streamsize;


    int main()
    {
    float f1 = 1000.00;
    float f2 = 3.14800;
    float f3 = 0.03148;

    // return the current precision
    streamsize prec1 = cout.precision();

    // set precision to 2
    cout << setprecision(2);

    // set precision to 3, return previous value
    streamsize prec2 = cout.precision(3);

    // the outputs should have three decimal digits
    cout << fixed << f1 << endl;
    cout << f2 << endl;
    cout << f3 << endl;

    // reset precision value to its previous value
    cout.precision(prec2);

    // the outputs should have two decimal digits
    cout << endl;
    cout << fixed << f1 << endl;
    cout << f2 << endl;
    cout << f3 << endl;

    // reset the the initial precision value
    cout.precision(prec1);

    cout << endl;
    cout << fixed << f1 << endl;
    cout << f2 << endl;
    cout << f3 << endl;

    // check the original value of precision
    cout << endl;
    cout << "The origin precision value is: " << prec1 << endl;

    return 0;
    }
    -

    The comments show the expected results according to that

    -
      -
    1. streamsize precision() returns the the value of the current floating-point precision.
    2. -
    3. streamsize precision(int n) sets the precision to a new value.
    4. -
    -

    The program gives result as I expected

    -
    1000.000
    3.148
    0.031

    1000.00
    3.15
    0.03

    1000.000000
    3.148000
    0.031480

    The origin precision value is: 6
    -

    Return to while statement

    Let’s get back to the example. The statements in the function body begin with writing a greeting as illustrated in previous chapters. Then, it reads two values into two variables midterm and final. The input operations can be chained as

    -
    cin >> midterm >> final;
    -

    This statement is equivalent to

    -
    cin >> midterm;
    cin >> final;
    -

    The next statement shows a new form of writing string literals.

    -
    cout << "Enter all your homework grades, "
    "followed by end-of-file: ";
    -

    It seems that two string lterals will be written, but in fact it has the same effect as the following statement

    -
    cout << "Enter all your homework grades, followed by end-of-file: ";
    -

    This is because two or more string literals separated only by whitespace are concatenated automatically.

    -

    What closely follow is the while loop.

    -
    int count = 0;
    double sum = 0;

    // a variable into which to read
    double x;

    // invariant: we have read count grades so far, and sum is the sum of the first count grades
    while (cin >> x)
    {
    ++count;
    sum += x;
    }
    -

    count is defined for counting the number of inputs as well as describing the loops. sum is defined for holding the summation of homework grades. Note that it is initialized with an int value 0 though it is a double type variable. Therefore, the int value will be converted automatically to double type with the fractional part of 0.

    -

    Since the number of homework grades is unkonwn, we could not use the while loop as before as we don’t know the number of loops. The while loop here makes it available to input multiple times continuously. As we know, the condition in a while loop should yield a value of bool, that is, true or false, otherwise the value (if available) will be converted to the type of bool. According to Koenig and Moo (2000), the istream class provides a conversion that can be used to convert cin to a value that can be used in a condition. In addition, the value depends on the iternal state of the istream object, which will remember whether the last attempt to read worked. Hence, the cin >> x will theoretically always yield a true value as long as we keep inputing right type values. In this example, it reminds us to send “EOF” signal, which will change the value to false, to stop the loop.

    -

    vector

    The example above shows how to compute an average value of a batch of data. In reality, the goal is probably to compute the median, or other statistics in the data. However, the inputted data are not stored in above program and hence are unavailable to access. Naturally, the first step is to store the data and the second step is to access or compute the target value via algorithms. Now we learn how to deal with these problems with vector.

    -

    Defining and Initializing vectors

    The vector is a class template. Before using the vector in a program, we need to include the header and qualify the name either explicitly or using using declaration. For example

    -
    #include <vector>
    using std::vector
    -

    A vector is a container that holds a sequence of objects of the same type. The syntax of creating a vector is

    -
    vector<T> name; // default initialization
    -

    The name is the template name. The T insides the angle brackets represents the type of the contained objects. It could be built-in types like int, char, or class types such as string, or even vector which means that the element contained in this vector is also a vector.

    -

    The vector template defines how to initialze vectors. When a vector is defalut initialized, it has no elements, which creats an empty vector. We can also initialize vectors using copy initialization or direct initialization or list initialization (see the table below).

    - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
    Ways to initialize a vectorreference: Lippman etc. 2012
    vector v1default initialization and creat an empty vector
    vector v2(v1)copy all elements in v1 to v2
    vector v2 = v1has same effect as the last one
    vector v3(n, val)direct initialization with n elements of the same value val
    vector v4(n)v4 has n copies of value-initialized object
    vector v5{a, b, c}v5 has three elements initialized with initializers a, b, c respectively
    vector v5 = {a, b, c}has same effect as the last one
    -

    What new usage here is the value-initialization

    -
    vector<T> v4(n)
    -

    The n here only provide the number of elements that will be contained in the vector. But there is no initializers provided. In this case, all elements will be initialized following the principle of default initialization determined by the type of the elements. For example

    -
    vector<int> v1(10); // 10 elements will be initialized with 0
    vector<string> v2(10); // 10 elements will be initialized to empty string type objects
    -

    If the type of the contained elements does’t support default initialization, the initialization of the vector would be failure.

    -

    In addition, it is worth noting the difference between

    -
    (1)    vector<int> v1(10);
    -

    and

    -
    (2)    vector<int> v2{10};
    -
    (3)    vector<int> v3(10, 1);
    -

    and

    -
    (4)    vector<int> v4{10, 1};
    -

    The first one is value-initialization as explained above while the second is list initialization with initializer 10. The third one is direct initialization with value 1 and the total number of elements is 10. The fourth one is list initialization with initializers 10 and 1, and the total number of elements is 2.

    -

    However, there exist some special cases

    -
    (5)    vector<string> v5("hello");

    (6) vector<string> v6{10}; // 10 value-initialized elements
    -

    Example (5) is incorrect as we can not copy the string lterals to a vector. Example (6) is correct but has the same effect as example (1) rather than list initialization. This is because 10 can not be used to initialize an element of string type, instead it can be used to initialize the vector with 10 value-initialized strings.

    -

    Now returing to the beigining of this post, I’ll define a vector for the purpose of holding all homework grades.

    -
    double x;
    vector<double> homework;
    -

    Operations on vectors

    Reading the elements

    One feature of a vector is that it has variable size, which is particularly helpful for us when the number of elements is unknown. The member function push_back can add a new element at the end of the vector after the current last element. The usage is shown below

    -
    while(cin >> x)
    {
    homework.push_back(x);
    }
    -

    The x is passed as an argument and its value is copied to the new element. As a result, the size of the vector homework is increased by 1.

    -

    Implementing algorithms

    Now all data have been stored into a vector. Assuming the goal of the program is to compute the median of the data set. It is known that the median value depends on the number of the stored elements (i.e. the size of the vector).

    -
      -
    1. if there exist an odd number of numbers, the median value is the value of the middle number.
    2. -
    3. if there exist an even number of numbers, there is no single middle number and the median value is the average value of two middle numbers.
    4. -
    -

    For a program, we also need to consider the case of no elements.

    -
      -
    1. if there is no elements at present, throwing a warning and asking to input again.
    2. -
    -

    Similar as a string, the size of a vector can be obtained through its member function size. For example, homework.size() returns the size of the vector. The returned value has a type of vector::size_type. Now we can translate above conditions to real code

    -
    // obtain the size of homework
    typedef vector<double>::size_type vec_size;
    vecsize size = homework.size();

    if (size == 0)
    { cout << "You must enter your grades. "
    "Please try again." << endl;

    // return 1 instead of 0 to indicate failure
    return 1;
    }
    else
    {
    if (size % 2 == 0)
    // compute median
    else
    // compute median
    }
    -

    Note that an alternative for the condition size == 0 is the empty function which returns true if vector is empty else returns false. The usage is the same as that for strings.

    -

    Type Aliases

    For convenience, we uses type alias instead of using vector::size_type directly in defining variables of such type. A type alias defines the name vec_size as a synonym for vector::size_type. There are two ways to define type alias

    -
    (1)    typedef double length; // length is a synonym for double

    (2) using length = double; // length is a synonmy for double
    -

    The second method is the new feature of new standards c++2011.

    -

    sort function

    Now the rest of the work is to find the median and basically speaking, is to sort the data set. This can be done by using a library algoritm.

    -
    #include <algorithm>
    ... // other code
    sort(homework.begin(), homework.end());
    -

    The sort function is defined in the library algorithm and therefore the header is added. It sorts the values in a container in an nondecreasing order. The arguments to sort specify the range of the data to be sorted. begin and end are member functions of the vector and represents the first element and (one past)the last element in homework respectively.
    They are iterators and will further discussed in chapter 6.

    -

    After we obtained a ordered sequence of values, now I illustrate how to determine the median value.
    If size is an even number

    -

    Similar as a string, we can access individual elements using subscript operator([]) and the index uses an asymmetrical range from 0 to the size of homework (excluded). If the size of homework is an even number, then size is exactly devided by 2. Due to the index starts from 0 ranther than 1, the mid elements should be homework[size/2 - 1] and homework[size/1]. Therefore, the median value is the average of the corresponding two values. As shown in above graph, the number of elements of both sides equals to size/2 because

    -
    left side:  number = size/2 - 1 - 0 + 1 = size/2
    right side: number = size - 1 - size/2 + 1 = size/2
    -

    If size is an odd number, the result of size/2 is in fact the value of (size-1)/2. Then the mid element is exact the homework[(size-1)/2] as both sides of this element has same number of elements(as shown in below graph). This can also be verified mathematically

    -
    left side:  number = (size-1)/2 - 1 - 0 + 1 = (size-1)/2
    right side: number = size - 1 - {(size-1)/2 + 1} + 1 = (size-1)/2;
    -

    If size is an odd number

    -

    Now let’s translate the algorithms to real code

    -
    { 
    vec_sz mid = size/2;
    double median;

    if (size % 2 == 0)
    median = (homework[mid] + homework[mid-1])/2.0;
    else
    median = homework[mid]
    }
    -

    Note that when calculate the average of two mid elements, I divide by 2.0 rather than 2 for the purpose of avoiding the loss of precision.This is because the quotient of two integers will be an integer.

    -

    The conditional (or ternary) operator

    An alternative statements for above if-else clause is to use the conditional (or ternary) operator (?:). The syntax is

    -
    (condition 1) ? expression 1 : expression 2

    It means that if condition 1 evaluates to true, then expression 2 is evaluated, and if condition 1 evaluates to false, then expression 3 is evaluated instead.

    -

    Therefore, we can change above code as

    -
    { 
    vec_sz mid = size/2;
    double median;

    median = size % 2 == 0) ? (homework[mid] + homework[mid-1])/2.0 : median = homework[mid];
    }
    - -

    A complete program

    Finally, I put all pieces of code together and obtained the complete program. Also, it works well when I test it.

    -
    #include <algorithm>
    #include <iomanip>
    #include <ios>
    #include <iostream>
    #include <string>
    #include <vector>

    using std::cin; using std::sort;
    using std::cout; using std::streamsize;
    using std::endl; using std::string;
    using std::setprecision; using std::vector;

    int main()
    {
    // ask for and read the student's name
    cout << "Please enter your first name: ";
    string name;
    cin >> name;
    cout << "Hello, " << name << "!" << endl;

    // ask for and read the midterm and final grades
    cout << "Please enter your midterm and final grades: ";
    double midterm, final;
    cin >> midterm >> final;

    // ask for the student entered some homework grades
    cout << "Enter all your homework grades, "
    "followed by end-of-file: ";
    double x;
    vector<double> homework;

    while(cin >> x)
    homework.push_back(x);

    // check that the student entered homework grades
    typedef vector<double>::size_type vec_size;
    vec_size size = homework.size();
    if (size == 0)
    {
    cout << endl << "You must enter your grades. "
    "Please try again." << endl;
    return 1;
    }

    // sort the grades
    sort(homework.begin(), homework.end());

    // compute the median homework grade
    vec_size mid = size/2;
    double median;
    median = size % 2 == 0 ? (homework[mid] + homework[mid - 1])/2
    : homework[mid];

    // compute and write the final grade
    streamsize prec = cout.precision();
    cout << "Your final grade is " << setprecision(3)
    << 0.2 * midterm + 0.4 * final + 0.4 * median
    << setprecision(prec) << endl;

    return 0;
    }
    -

    Test and results:

    -
    Please enter your first name: Bruce
    Hello, Bruce!
    Please enter your midterm and final grades: 80 90
    Enter all your homework grades, followed by end-of-file: 50 60 70 80 90
    Your final grade is 80
    ]]>
    - - Programming - - - C++ - Notes - -
    - - C++ - Built-in types and expressions - /2018/03/02/C-Built-in-types-and-expressions/ - Arithmetic types - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
    Arithmetic types in C++
    TypeMeaningMinimum size
    boolbooleanNA
    charcharacter8bits
    wchar_twide character16bits
    char16_tUnicode character16bits
    char32_tUnicode character32bits
    shortshort integer16bits
    intinteger16bits
    longlong integer32bits
    long longlong integer64bits
    floatsingle-precision floating-point6 significant digits
    doubledouble-precision floating-point10 significant digits
    long doubleextended-precision floating-point10 significant digits
    -]]>
    - - Programming - - - C++ - Notes - -
    - - Accelerated C++ Solutions to Exercises (Chapter 2 Part3) - /2018/03/01/Accelerated-C-Solutions-to-Exercises-Chapter-2-Part3/ - Exercise 2-6

    What does the following code do?

    -
    int i = 0;
    while (i < 10)
    {
    i += 1;
    std::cout << i << std::endl;
    }
    -

    Solution & Results

    The program writes 10 rows of numbers, starting from 1 to 10.

    -

    The while statement starts from testing the condition and then executes the while body if the condition is true. It stops executing the while body until the condition becomes false. Let’s analyse the first iteration

    - -

    From above steps we have seen that

    - -

    Accordingly, the final iteration can be deducted

    - -

    Now I complete the program and test it

    -
    #include <iostream>

    int main()
    {
    int i = 0;
    while (i < 10)
    {
    i += 1;
    std::cout << i << std::endl;
    }
    return 0;
    }
    -

    As expected, it writes 10 rows of outputs from 1 to 10 with one number in each row.

    -
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    -

    Note that the cursor appears on the next line of the final number 10 due to the following manipulator endl.

    -

    We can also explain the program from the perspective of its goal. Condiser that, we want the program to print out 10 rows, each of which contains a number, starting from 1 to 10 orderly. The loop invariant can be expressed as: we have written i rows now and the number in current row is i. To verify the loop invariant, we need to verify it at two specific points:

    -
      -
    1. the first point is before the first time that the condition is evaluated. In this case, it is correct as there is 0 output at current position.
    2. -
    3. the second point is before the end of the while body. Once the first statement is executed, i is increased by 1. To maintain the loop invariant, it needs writing a row which contains the number i. Therefore, the loop statement works as expected in each iteration.
    4. -
    -

    For clarity, I add comments for the program

    -
    #include <iostream>

    int main()
    {
    // loop invariant: we have written i rows now and the number in current row is i

    int i = 0;

    while (i < 10)
    {
    // i changes with increment of 1
    i += 1;

    // to maintain the loop invariant, write 1 row
    std::cout << i << std::endl;
    }
    return 0;
    }
    -

    Analysis

    See deatiled analysis in C++ - Looping and counting.

    -
    -

    Exercise 2-7

    Write a program to count down from 10 to -5.

    -

    Solution & Results

    This exercise is similar to the program in the last exercise. There are 16 numbers in total and hence there should be overal 16 loop times. Naturally, we use the range [0, 16) to describe the loop statements. The loop invariant can be expressed as we have written i rows and the number that written in this row is j.

    -
    #include <iostream>

    int main()
    {
    // The loop invariant can be expressed as we have
    // written i rows and the number written in this row is j.

    int i = 0;

    while (i < 16)
    {
    // write a row of outputs
    cout << endl;

    // to maintain the loop invariant, increase the value of i by adding 1
    ++i;
    }
    return 0;
    }
    -

    What is the value of j? As mentioned above, the loop invariant needs to be verified at two specific points. First, before the first time that the condition is evaluated, we have written 0 rows. Second, before the end of the while body, we have written 1 row and the number should be 10. Therefore, the inital value of j should be 10. It is still not clear now. I’ll further verify the loop invariant in the second and third iteration.

    -

    The second iteration:

    - -

    The third iteration:

    - -

    It has been seen from above descriptions, each iteration i changes with increment by 1 while j changes with decrement by 1. Therefore, the sum of i and j should be constant, and hence j = 10 - i as j has initial value 10. In other words, the loop invariant is i + j = 10, of which the i represents the row number and j represents the number contained in the i row of outputs.

    -

    Accordingly, the complete program is

    -
    #include <iostream>

    int main()
    {
    // loop invariant: we have written i rows and the number
    // written in this row is j = 10-i.

    int i = 0;
    int j = 10;

    while (i < 16)
    {
    // write a row of outputs
    std::cout << j << std::endl;

    // to maintain the loop invariant, increase the value
    // of i by adding 1, change the value o j to 10 - i
    ++i;
    j = 10 -i;
    }
    return 0;
    }
    -

    The program works as expected with following outputs

    -
    10
    9
    8
    7
    6
    5
    4
    3
    2
    1
    0
    -1
    -2
    -3
    -4
    -5
    - -
    -

    Exercise 2-8

    Write a program to generate the product of the numbers in the range [1, 10).

    -

    Solution & Results

    The product of the numbers in the range [1, 10) is

    -
    1 x 2 x 3 x 4 x 5 x 6 x 7 x 8 x 9
    -

    Essentially, it is a factorial of number 9. I can transform above expression as

    -
    9! = 1 x 2 x 3 x ... x 8 x 9
    -

    or

    -
    9! = 9 x 8!
    -

    Therefore, the calculation can be designed as a loop statement containing 8 times loops. In each iteration, we calculate the factorial of a number from 2 till 9. The loop invariant is that we have calculated the factorial i times and the number f. I’ll use the range [0, 8) to count the loops. The complete program is shown as below

    -
    #include <iostream>

    int main()
    {
    int f = 1;
    int product = 1;

    // we have calculated the factorial i times and the number is f
    for (int i = 0; i != 8; ++i)
    {
    // increase the value of f by 1
    ++f;

    // to maintain the loop invariant, calculate the factorial of number f
    product *= f;
    }
    std::cout << product << std::endl;

    return 0;
    }
    -

    The results is

    -
    362880
    - -
    -

    Exercise 2-9

    Write a program that asks the user to enter two numbers and tells the user which number is larger than the other.

    -

    Solution & Results

    It’s a simple exercise, and I’ll skip analysis and present the program directly

    -
    #include <iostream>
    using std::cout;
    using std::cin;
    using std::endl;

    int main()
    {
    cout << "Please enter two integers: ";
    // read two numbers
    int a, b;
    cin >> a >> b;

    if (a == b)
    cout << a << " equals to " << b << endl;
    else if (a > b)
    cout << a << " is greater than " << b << endl;
    else if (a < b)
    cout << b << " is greater than " << a << endl;
    return 0;
    }
    -

    Test 1

    -
    Please enter two numbers: 5 8
    8 is greater than 5
    -

    Test 2

    -
    Please enter two numbers: 4 2
    4 is greater than 2
    -

    Test 3

    -
    Please enter two numbers: 100 100
    100 equals to 100
    -

    Exercise 2-10

    Explain each of the uses of std:: in the following program:

    -
    #include <iostream>

    int main()
    {
    int k = 0;
    while (k != 5)
    {
    // invariant: we have written k asterisks so far
    using std::cout;
    cout << "*";
    ++k;
    }
    std::cout << std::endl;
    // std:: is required here
    return 0;
    }
    -

    Solution & Results

    The first std:: in line 9

    -
    using std::cout;
    -

    The using declaration qualify us to use the name cout, which is defined in the namespace std, directly instead of std::cout. However, the declaration is only valid within the block of the while statement as the curly braces form a name scope.

    -

    This also explains the requirements of the std:: in line 13. If one want to use the unqualified cout, endl inside the main function body, he should write the using declarations for each different name before the start of the main function. For example

    -
    #include <iostream>
    using std::cout;
    using std::endl;

    int main()
    {
    int k = 0;
    while (k != 5)
    {
    // invariant: we have written k asterisks so far
    // using std::cout - is not required now
    cout << "*";
    ++k;
    }
    std::cout << std::endl;
    // std:: is not required now
    return 0;
    }
    -

    Now, both programs work well and give the results

    -
    *****
    -

    Analysis

    See C++ - Getting Started.

    -
    -

    References

    Koenig, A. and Moo, B.E., 2000. Accelerated C++.

    -

    Lippman, S.B., Lajoie, J. and Moo, B.E., 2013. C++ Primer, 5th edt.

    -]]>
    - - Programming - - - Accelerated C++ Solutions - -
    - - Accelerated C++ Solutions to Exercises (Chapter 2 Part2) - /2018/02/25/Accelerated-C-Solutions-to-Exercises-Chapter-2-Part2/ - Exercise 2-4

    The framing program writes the mostly blank lines that seperate the borders from the greeting one character at a time. Change the program so that it writes all the spaces needed in a single output expression.

    -

    Solution & Results

    Without considering the frame itself, there only exist two types of row. The first type is the rows that full of spaces. The second type is the row that contains the greeting and spaces symmetricly located on two sides of the greeting. Therefore, one of the possible solutions is to write one row directly each time instead of writing one character. First, I defines two variables representing two blank strings

    -
       // two blank strings containing different number of spaces
    const string blankStringTopBottom(cols - 2, ' ');
    const string blankStringLeftRight(pad, ' ');
    -

    The first string is in fact the first type of row and can be written directly given certain conditions. The second row can be obtained by means of catenate operations

    -
       // the string that contains both the greeting and blanks
    const string greetingRow = blankStringLeftRight + greeting + blankStringLeftRight;
    -

    To maintain the loop invariant, the c need to be adjusted after each output. Due to one row is written each time (ignoring the borders), c increases cols - 1 every time.

    -
    c += cols - 2;
    -

    The modified program is

    -
    #include <iostream>
    #include <string>

    // using namespace std and names
    using std::cout; using std::cin;
    using std::endl; using std::string;

    int main()
    {
    // ask for the person's name
    cout << "Please enter your first name: ";

    // read the name
    string name;
    cin >> name;

    // build the message that we intend to write
    const string greeting = "Hello, " + name + "!";

    // the number of blanks surrounding the greeting
    const int pad = 1;

    // the number of rows and columns
    const int rows = pad * 2 + 3;
    const string::size_type cols = greeting.size() + pad * 2 + 2;

    // two blank strings containing different number of spaces
    const string blankStringTopBottom(cols - 2, ' ');
    const string blankStringLeftRight(pad, ' ');

    // the string that contains both the greeting and blanks
    const string greetingRow = blankStringLeftRight + greeting + blankStringLeftRight;


    // write a blank line to separate the output from the input
    cout << endl;

    // write rows rows of output
    // invariant: we have written r rows so far
    for (int r = 0; r != rows; ++r)
    {
    string::size_type c = 0;
    // we have written c characters so far in the current row
    while (c != cols)
    {
    // are we on the border?
    if (r == 0 || r == rows - 1 ||
    c == 0 || c == cols - 1)
    {
    cout << "*";
    ++c;
    }
    else
    { // is it time to write the greeting row?
    if (r == pad + 1)
    cout << greetingRow;
    // is it time to write the non-greeting rows?
    else
    cout << blankStringTopBottom;
    c += cols - 2;
    }
    }
    cout << endl;
    }
    return 0;
    }
    -

    A simple test has been done and the outputs are written on the console window as below

    -
    Please enter your first name: Bruce

    *****************
    * *
    * Hello, Bruce! *
    * *
    *****************
    -

    Analysis

    See deatiled analysis in C++ - Looping and counting.

    -
    -

    Exercise 2-5

    Write a set of “*” characters so that they form a square, a rectangle, and a triangle.

    -

    Solution & Results

    The design ideas

    The correct program for this exercise is non-unique as the requirements are loose. It doesn’t specify the details of each shape. I tried to design a program that is flexible enough to meet more needs and preferences. For example, users may ask the program to write various rectangles (or squares/triangles) with different lengths or different appearance such as solid or hollow shapes. In summary, I intend to design a program with following properties

    -
      -
    1. provide choices for three types of shapes, i.e. square, rectangle and triangle.
    2. -
      • -
      • for a square, the length of the side is customizable
      • -
      • for a rectangle, the length and the width are customizable, and the base can be the long side or the short side.
      • -
      • for a triangle, the height is customizable and the triangle is a equilateral triangle.
      • -
      -
    3. -
    4. for all shapes, there are two types of appearance: solid and hollow.
    5. -
    -

    The Primary Structure

    Due to there are three types of shape, we can use if else statement as one of the choices of the primary structure and provide three banches for the three shapes. First, flags are needed for indicating what shape type is to be written. For example:

    -
    /* flags for what shape to print
    * shapeType == 1, print a square shape
    * shapeType == 2, print a rectangle shape
    * shapeType == 3, print a triangle shape
    */
    -

    In addition, each type of shape can be a solid shape or hollow shape. Therefore, a choice for the appearance will be provided for users before the computer enters into each branch. For other properties, such as lengths of sides and heights, are not the same for each types of shape and will be solved in the branches. Therefore, the whole structure of the program is

    -
    int main()
    {
    // select a shape
    cout << "Select a shape type\n";
    cout << "1. A Square\n2. A Rectangle\n3. A Triangle\n";
    cout << "Please enter 1 or 2 or 3: ";

    // read the shape type
    int shapeType;
    cin >> shapeType;

    // write a blank line for clarity
    cout << endl;

    // select appearance
    cout << "Do you want a solid shape?\n";
    cout << "yes: a solid shape\nno: a hollow shape\n";
    cout << "Please enter y or n: ";

    // read the shape appearance
    char shapeAppearance;
    cin >> shapeAppearance;

    // write a blank line for clarity
    cout << endl;

    if (shapeType == 1)
    {
    // statements for generating a square
    }
    else if (shapeType == 2)
    {
    // statements for generating a rectangle
    }
    else if (shapeType == 3)
    {
    // statements for generating a triangles
    }
    return 0;
    }
    - -

    Algorithms

    A square shape

    The core part of the program is how to impelment the algorithms that can generate different shapes. I starts with the implementation of writing a square. Assuming the edge length of the square is 10, the program aims to generate two different squares as follows

    -
    **********          **********
    * * **********
    * * **********
    * * **********
    * * and **********
    * * **********
    * * **********
    * * **********
    * * **********
    ********** **********
    -

    Note that for all the shapes, the length or height means the number of columns or rows formed by asterisks (and spaces).

    -

    The algorithm is similar as that in the framed greeting program except that it doesn’t need to write the greeting itself. Essentially, there are two types of string:one is full filled with asterisks and another one is filled by blanks and two asterisks located at both ends of the blanks. The structure could be a for loop or while loop, which is up to your preference. Firstly, I define the variables that are needed.

    -
    // read in edge length for a square
    int edgeLength;
    cin >> edgeLength;

    // string variable type 1: full of asterisks
    string asteriskString(edgeLength, '*');

    // string variable type 2: blanks and asterisks
    string blankString(edgeLength - 2, ' ');
    string mixedString = "*" + blankString + "*";
    -

    The solid square shape is simple and only needs one type of the string type vairable. If we choose one of the loop statements, the loop invariant can be expressed as: we have written r rows of output, of which r is the counting variable.

    -
    // invariant: we have written r rows of output
    for (int r = 0; r != edgeLength; ++r)
    {
    // write a row whose length is the edge length
    cout << asteriskString << endl;
    }
    -

    To maintaint the loop invariant, r is increased by 1 after each iteration. Once the for loop finishes, the condition r != edgeLength is false and r = edgeLength. Then, the loop invariant becomes: we have written edgeLength rows of outputs, which keeps the correctness of our loop statement.

    -

    For a hollow square shape, the output of each row is conditional on its position.

    -
    // is it the first row or last row of outputs?
    if (r == 0 || r == edgeLength - 1)
    cout << asteriskString << endl;
    else
    cout << mixedString << endl;
    -

    Now, we can combine two cases together

    -
       if (shapeType == 1)
    {
    cout << "Please enter the edge length: ";

    // read the edge length
    int edgeLength;
    cin >> edgeLength;

    // write a blank line to separate inputs and outputs
    cout << endl;

    // string type 1: full of asterisks
    string asteriskString(edgeLength, '*');

    // string type 2: blanks and asterisks
    string blankString(edgeLength - 2, ' ');
    string mixedString = "*" + blankString + "*";

    for (int r = 0; r != edgeLength; ++r)
    {
    // is it rows of a solid shape or
    // the first/ last row of a hollow shape ?

    if (shapeAppearance == 'y' || r == 0 || r == edgeLength - 1)
    // write a row whose length is the edge length
    cout << asteriskString << endl;
    else
    cout << mixedString << endl;
    }
    }
    -

    A rectangle shape

    Square shapes can be regarded as a special case of rectangles. We can simply modify the above algorithm to generate a rectangle. I use base lenght and height instead of the length and width for convenience.

    -
    else if (shapeType == 2)
    {
    cout << "Please enter the base length and height\n";
    cout << "The base lenght = ";

    // read the base length
    int baseLength;
    cin >> baseLength;

    cout << "The height = ";

    // read the height
    int height;
    cin >> height;

    // write a blank line to separate inputs and outputs
    cout << endl;

    // string type 1: full of asterisks
    string asteriskString(baseLength, '*');

    // string type 2: blanks and asterisks
    string blankString(baseLength - 2, ' ');
    string mixedString = "*" + blankString + "*";

    for (int r = 0; r != height; ++r)
    {
    // is it rows of a solid shape or
    // the first/ last row of a hollow shape ?

    if (shapeAppearance == 'y' || r == 0 || r == height - 1)
    cout << asteriskString << endl;
    else
    cout << mixedString<< endl;
    }
    }
    - -

    A triangle shape

    For a triangle, the ideal result is to print out follow two shapes, providing the height is 4.

    -
       *                   *
    *** and * *
    ***** * *
    ******* *******
    -

    The solid trangle shape is formed only by asterisks ignoring the outer blanks. However, the number of asterisks of each row is different with eachother. From the table below, we can see a pattern in the numbers of asterisks of the rows.

    -

    | The rth Row| The number of columns of each row |
    | :—: | :—: | :—: |
    |0|1|
    |1|3|
    |2|5|
    |3|7|
    |4|9|
    |…|…|
    |r|(r*2 + 1)|
    |Height|loop finishes|

    -

    The table shows that when the program writes the rth row, it needs to write asterisks in a total number of r*2 + 1.
    It also shows that the number of columns is height*2 - 1.

    -

    For the hollow triangle shapes, there are two types of rows: the first type is rows filled only by asterisks such as the first row and the last row; another type is rows formed by blanks with two asterisks located at both ends of the blanks. The table gives the pattern in the numbers of blanks of the rows.

    -

    | The rth Row| The number of blanks in each row |
    | :—: | :—: | :—: |
    |0|0|
    |1|1|
    |2|3|
    |3|5|
    |4|7|
    |…|…|
    |r|((r-1)*2 + 1)|
    |Height|loop finishes|

    -

    Correspondingly, I define variables inside a for loop as below

    -
    for (int r = 0; r != height; ++r)
    {
    // is it the row of a solid shape, or the first or the last row of the hollow shape
    if (shapeAppearance == 'y' || r == 0 || r == height - 1)
    {
    // string type 1: full of asterisks
    string asteriskString(r*2 + 1, '*');
    }
    else
    {
    // string type 2: blanks and asterisks
    string blankString(((r-1)*2 + 1), ' ');
    string mixedString = "*" + blankString + "*";
    }
    }
    -

    What we need next is to find the condition for writing each row as each row has different initial position. Before the real start of each row, we only need to output spaces. The table below gives the pattern of the initial column position of each row.

    -

    | The rth Row| The column number of the initial position |
    | :—: | :—: | :—: |
    |height | loop finishes|
    |height - 1| 0|
    |height - 2|1|
    |…|…|
    |r|height - r - 1|
    |…|…|
    |0|height - 1|

    -

    The table shows that when the loop processes the column number height - r - 1, it starts to write the strings that I defined above. When we incorporate the column loops into the row loops, the program becomes

    -
    else if (shapeType == 3)
    {
    // read the height
    cout << "Please enter the height: ";
    int height;
    cin >> height;

    // write a blank line to separate inputs and outputs
    cout << endl;

    // loop invariant: we have written r rows now
    for (int r = 0; r != height; ++r)
    {
    // loop invariant: we have written c characters so far in the current line
    int c = 0;

    while(c != (height*2 - 1))
    { // // is it the row of a solid shape, or the first or the last row of the hollow shape
    if (shapeAppearance == 'y'
    || r == 0
    || r == height - 1)
    {
    // is it time to write the real row?
    if (c == height - r - 1)
    {
    // string type 1: full of asterisks
    string asteriskString(r*2 + 1, '*');
    cout << asteriskString;

    // maintain the loop invariant
    c += (r*2 + 1);
    }
    else
    {
    cout << ' ';
    // maintain the loop invariant
    ++c;
    }
    }
    else
    {
    // is it time to write the real row?
    if (c == height - r - 1)
    {
    // string type 2: blanks and asterisks
    string blankString(((r-1)*2 + 1), ' ');
    string mixedString = "*" + blankString + "*";

    cout << mixedString;
    // maintain the loop invariant
    c += ((r-1)*2 + 1 + 2);
    }
    else
    {
    cout << ' ';
    ++c;
    }
    }
    }
    cout << endl;
    }
    }
    -

    A complete program and tests

    Let’s put all pieces together

    -
    //Accelerated C++ Solutions Exercises 2-5
    #include <iostream>
    #include <string>

    // using namespace std and names
    using std::cout; using std::cin;
    using std::endl; using std::string;

    int main()
    {
    // select a shape
    cout << "Select a shape type\n";
    cout << "1. A Square\n2. A Rectangle\n3. A Triangle\n";
    cout << "Please enter 1 or 2 or 3: ";

    // read the shape type
    int shapeType;
    cin >> shapeType;

    // write a blank line for clarity
    cout << endl;

    // select appearance
    cout << "Do you want a solid shape?\n";
    cout << "yes: a solid shape\nno: a hollow shape\n";
    cout << "Please enter y or n: ";

    // read the shape appearance
    char shapeAppearance;
    cin >> shapeAppearance;

    // write a blank line for clarity
    cout << endl;

    if (shapeType == 1)
    {
    cout << "Please enter the edge length: ";
    int edgeLength;
    cin >> edgeLength;

    // write a blank line to separate inputs and outputs
    cout << endl;

    // string type 1: full of asterisks
    string asteriskString(edgeLength, '*');

    // string type 2: blanks and asterisks
    string blankString(edgeLength - 2, ' ');
    string mixedString = "*" + blankString + "*";

    for (int r = 0; r != edgeLength; ++r)
    {
    if (shapeAppearance == 'y' || r == 0 || r == edgeLength - 1)
    // write a row whose length is the edge length
    cout << asteriskString << endl;
    else
    cout << mixedString << endl;
    }
    }
    else if (shapeType == 2)
    {
    cout << "Please enter the base length and height\n";
    cout << "The base lenght = ";

    // read the base length
    int baseLength;
    cin >> baseLength;

    cout << "The height = ";

    // read the height
    int height;
    cin >> height;

    // write a blank line to separate inputs and outputs
    cout << endl;

    // string type 1: full of asterisks
    string asteriskString(baseLength, '*');

    // string type 2: blanks and asterisks
    string blankString(baseLength - 2, ' ');
    string mixedString = "*" + blankString + "*";

    for (int r = 0; r != height; ++r)
    {
    if (shapeAppearance == 'y' || r == 0 || r == height - 1)
    cout << asteriskString << endl;
    else
    cout << mixedString<< endl;
    }
    }
    else if (shapeType == 3)
    {
    cout << "Please enter the height: ";
    int height;
    cin >> height;

    // write a blank line to separate inputs and outputs
    cout << endl;

    // loop invariant: we have written r rows now
    for (int r = 0; r != height; ++r)
    {
    // loop invariant: we have written c characters so far in the current line
    int c = 0;

    while(c != (height*2 - 1))
    {
    if (shapeAppearance == 'y' || r == 0 || r == height - 1)
    {
    if (c == height - r - 1)
    {
    // string type 1: full of asterisks
    string asteriskString(r*2 + 1, '*');
    cout << asteriskString;

    // maintain the loop invariant
    c += (r*2 + 1);
    }
    else
    {
    cout << ' ';
    // maintain the loop invariant
    ++c;
    }
    }
    else
    {
    if (c == height - r - 1)
    {
    // string type 2: blanks and asterisks
    string blankString(((r-1)*2 + 1), ' ');
    string mixedString = "*" + blankString + "*";

    cout << mixedString;
    // maintain the loop invariant
    c += ((r-1)*2 + 1 + 2);
    }
    else
    {
    cout << ' ';
    ++c;
    }
    }
    }
    cout << endl;
    }
    }
    return 0;
    }
    -

    Note that I used the same variables names asteriskString, blankString and mixedString in all three branches, but it does not matter the correctness as each branch is an independent block. I did three tests and the program works as expected. Please see the results below

    -

    Test 1

    -
    Select a shape type
    1. A Square
    2. A Rectangle
    3. A Triangle
    Please enter 1 or 2 or 3: 1

    Do you want a solid shape?
    yes: a solid shape
    no: a hollow shape
    Please enter y or n: y

    Please enter the edge length: 5

    *****
    *****
    *****
    *****
    *****
    -

    Test 2

    -
    Select a shape type
    1. A Square
    2. A Rectangle
    3. A Triangle
    Please enter 1 or 2 or 3: 2

    Do you want a solid shape?
    yes: a solid shape
    no: a hollow shape
    Please enter y or n: n

    Please enter the base length and height
    The base lenght = 8
    The height = 4

    ********
    * *
    * *
    ********
    -

    Test 3

    -
    Select a shape type
    1. A Square
    2. A Rectangle
    3. A Triangle
    Please enter 1 or 2 or 3: 3

    Do you want a solid shape?
    yes: a solid shape
    no: a hollow shape
    Please enter y or n: n

    Please enter the height: 6

    *
    * *
    * *
    * *
    * *
    ***********
    - -
    -

    To be continued.

    -]]>
    - - Programming - - - Accelerated C++ Solutions - -
    - - C++ - Looping and counting - /2018/02/23/C-Looping-and-counting/ - In last chapter, the authors Koenig and Moo presents how to write a program that can produce a framed greeting. This chapter is to make the program more flexible so that we can change the size of the frame easily. Let’s have a look at the framed greeting again:

    -
    ********************
    * *
    * Hello, Estragon! *
    * *
    ********************
    -

    It has been observed that the greeting work has following characteristics:

    - -

    Clearly, the size of the frame is determined by the length of the greeting itself and the number of blank lines(i.e. spaces in this case). Now, we can define a variable named pad that represents the length of spaces (e.g. 1):

    -
    const int pad = 1;
    -

    Then, we can indicate the number of rows with defining another variable:

    -
    const int rows = 1 + 2 + 2 * pad;
    -

    The number 1 means the row of the greeting itself and the number 2 means two edges formed by asterisks. Once we know the number of rows, we can write the program similar as the the original one which defines several variables for each row and prints one by one. However, we need to change the code every time we change the frame size. This problem can be tackled with using iterative statements which will fully utilize the information listed above including the position of each type of elements.

    -

    While statement & for statement

    A while statement repeatedly executes a given statement as long as a pre-dertimined condition is true. The syntax of a while statement is

    -
    while (condition)
    statement
    -

    The processing of while statement starts from testing the condition and then executes the statement (aka, the while body) if the condition is true, and it starts all over again until the condition becomes false. Our purpose is to write each row, totalling rows times. Hence, what the program need here is a counting variable which is used to control the outputs.

    -
    int r = 0; // counting variable with initial value 0
    while (r != rows)
    {
    // write a row of output
    std::cout << std::endl;
    ++r;
    }
    -

    The while loop requires a single statement but the logic of our program need more. Hence, we can use a compound statement which refers to a block formed by a pair of curly braces. As shown above, We can write a sequence of statements and declarations or empty statement in the block. Note that the block forms a scope and the right brace (}) indicates the end of the statement.

    -

    The condition is an expression that returns bool type value (true or false). When the condition yields an arithmetic type value, the value will be converted to a value of bool type:

    -
      -
    1. zero values convert to false
    2. -
    3. non-zero values convert to true
    4. -
    -

    The inequality operator (!=) test the inequality between r and rows. The table below gives the logical and rational operators (all returns a value of bool type):

    - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
    AssociativityOperatorFunction
    Right!Logical NOT
    Left<less than
    Left<=less than or equal
    Left>greater than
    Left>=greater than or equal
    Left==equality
    Left!=inequality
    Left&&Logical AND
    Left||Logical OR
    Source: C++ Primer 2012
    -

    The ++() is the increment (decrement) operator which adds (subtract) 1 to the variable r. The expression is equivalent to

    -
    r = r + 1;
    -

    If we want to add another variable with varing values each time, we can use compound assignment operators (refer to arithmetic operators)

    - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
    +=-=*=/=%/
    For example, x += y; is equivalent to
    x = x + y;
    Comparing with the ordinary assignment, compound assignment only evaluate the left operand once while the ordinary assignment evaluate twice.
    -

    Let’s get back to the while statement. According to the condition, the counting process will starts from 0 till rows-1 and the statement is expected to be executed rows times. In this case, an alternative loop structure can also be considered, a for statement

    -
    for (init-statement; condition; expression) // for header
    statement // for body
    -

    The for header controls the for body which contains the statement that needs to be executed. The init-statement can only be one of three statements: declaration statement, an expression statement and anull statement(using a semicolon).
    The processing order of the for statement is

    -
      -
    1. at the begining of the loop, init-statement will be executed.
    2. -
    3. then, condition will be evaluated and the for body would be executed if the condition returns true. Otherwise, the loop terminates.
    4. -
    5. if condition is true, the last step of the loop is to execute expression.
    6. -
    -

    In this case, the for statement can be write as

    -
    for (int r; r != rows; ++r)
    {
    // write a row of output
    std::cout << std::endl;
    }
    -

    Loop invariant

    To verify the correctness of the loops, the book introduces two techniques. The first is that the condition must be false when the loop finishes. In this case, when the while statement finishes, r != rows is false. Another one is to use loop invariant which is a property that is true before and after each loop. In this example, the loop invariant is not the core language but a comment before the while statement:

    -
    // invariant: we have written r rows so far
    -

    The loop invariant shows that the while statement works as expected, and in turn the program should meet the requirements that invariant is true in each iteration. There are two specific points for verify this:

    -
      -
    1. the first point is before the first time that the condition is evaluated. In this example, we know it is correct as there is no output yet.
    2. -
    3. the second point is before the end of the while body. Once the while body is executed, one row will be written. To meet the requirement, we increase the value of r by adding 1 each time.
    4. -
    -

    When the while statement finishes, there will be rows lines are written as r = rows. The below piece of code shows how the loop invariant plays its role.

    -
    // invariant: we have written r rows so far
    int r = 0;
    // setting r to 0 makes the invariant true
    while (r != rows)
    {
    // we can assume the invariant is true here

    // writting a row of output makes the invariant false
    std::cout << std::endl;

    // incrementing r makes the invariant true again
    ++r;
    }
    // we can conclude that the invariant is true here
    -

    Up to now, we have figured how to control the loops for producing right number of rows. Similarly, we can use while loop or for loop to produce every character of each specific row. By the analogy, the first step is to define a variable to representing the number of columns. But the difference is that the number of columns is not only determined by blanks but also by the size of the greeting itself.

    -
    const std::string::size_type cols = greeting.size() + pad * 2 + 2;
    -

    The variable cols is defined as a std::string::size_type to keep consistent with the type of greeting.size(). This knowledge has been introduced in my last notes. We can also use type specifiers auto or decltype. For example

    -
    const auto cols = greeting.size() + pad*2 + 2;
    -

    What if we use int type directly? Then the variable might be insufficient if one input a name that is long enough. Now we can write the structure for writing a specific row.

    -
    std::string::size_type c = 0;

    // invariant: we have written c characters so far in the current row
    while (c != cols)
    {
    // write one or more characters
    // adjust the value of c to maintain the invariant
    }
    -

    if statements

    Now We already have the whole structure of the program. What we need next is to determine what character is needed to be written for each row and column. As listed at the begining of this notes, we can utilize the characteristic of the target. I’ll further transform the information to plain english for clarify the logic of the program. There are three components in the outputs: asterisk, spaces and the greeting. The asterisks form the border of the frame and will be written when the loop is processing

    - -

    These conditions can be expressed by means of the logical operators and relational operators

    -
    r == 0 || r == rows - 1 || c == 0 || cols - 1
    -

    The relational operators == have lower precedence than the arithmetic operators. Therefore, r == rows - 1 is equivalent to r == (rows - 1).

    -

    The logical OR operator has lower precedence than the relational operators. Therefore, above expression means

    -
    (r == 0) || (r == rows - 1) || (c == 0) || (cols - 1)
    -

    Moreover, the logical OR (as well as the logical AND) is left associative and the expression is evluated using the short-circuit strategy, that is, “the right side of an || is evaluated if and only if the left side is false (true for the logical AND)” (Lippman ect. 2012, p.154).

    -

    To evaluate above condition, we use the if statement

    -
    if (condition) 
    statement
    -

    or if else statement

    -
    if (condition)
    statement1
    else
    statement2
    -

    or nested if statement

    -
    if (condition1)
    statement1
    else if (condition2)
    statement2
    -

    Now we have got a complete structure for printing out the border

    -
    if (r == 0 || r == rows - 1 || c == 0 || cols - 1)
    {
    std::cout << "*";
    ++c;
    }
    else
    {
    if (other condition)
    // write one or more noborder characters
    // adjust the value of c to maintain the invariant
    }
    -

    By the analogy, we can determine the condition for outputing the greeting

    -
    r == pad + 1 && c == pad + 1
    -

    Note that we don’t need to write the condition of the each character of the greeting as once we find the row and the initial position we can print out the variable directly. Therefore, the value of the counting variable c will add a value of greeting.size() to maintain the invairant. Finally, it is not necessary to find the condition for the space characters as the remainder of the frame can only be spaces. Let’s combine three situations together

    -
    if (r == 0 || r == rows - 1 || c == 0 || cols - 1)
    {
    std::cout << "*";
    ++c;
    }
    else
    {
    if (r == pad + 1 && c == pad + 1)
    {
    std::cout << greeting;
    c += greeting.size();
    }
    else
    {
    std::cout <<" ";
    ++c;
    }
    }
    -

    One common mistake in using if statement is that missing the curly braces after if or else when there are multiple statements. We should pay much attention on this in the case of nested if statements as there may exist more if branches than else branches. It is confusing how each else match with the if branches, which is commonly termed as dangling else. In the default seeting, C++ specifies that each else is matched with the closest preceding unmatched if. To avoid the ambiguity, it is recommended to control execution with curly braces.

    -

    The complete framing program

    Now we can put all pieces together to get the complete framing program. Note there are several ways to organize the if statements and both while loop and for loop are available. Therefore, the program below is only one of the correct programs.

    -
    #include <iostream>
    #include <string>

    // using namespace std and names
    using std::cout; using std::cin;
    using std::endl; using std::string;

    int main()
    {
    // ask for the person's name
    cout << "Please enter your first name: ";

    // read the name
    string name;
    cin >> name;

    // build the message that we intend to write
    const string greeting = "Hello, " + name + "!";

    // the number of blanks surrounding the greeting
    const int pad = 2;

    // the number of rows and columns
    const int rows = pad * 2 + 3;
    const string::size_type cols = greeting.size() + pad * 2 + 2;

    // write a blank line to separate the output from the input
    cout << endl;

    // write rows rows of output
    // invariant: we have written r rows so far
    for (int r = 0; r != rows; ++r)
    {
    // we have written c characters so far in the current row
    string::size_type c = 0;
    while (c != cols)
    {
    // are we on the border?
    if (r == 0 || r == rows - 1 ||
    c == 0 || c == cols - 1)
    {
    cout << "*";
    ++c;
    }
    else
    { // is it time to write the greeting?
    if (r == pad + 1 && c == pad + 1)
    {
    cout << greeting;
    c += greeting.size();
    }
    else
    {
    cout <<" ";
    ++c;
    }
    }
    }
    cout << endl;
    }
    return 0;
    }
    -

    Now we can test the program with changing the number of blanks to 2, and type Bruce after the input prompt. It gives the outputs as we expected.

    -
    Please enter your first name: Bruce

    *******************
    * *
    * *
    * Hello, Bruce! *
    * *
    * *
    *******************
    ]]>
    - - Programming - - - C++ - Notes - -
    - - Accelerated C++ Solutions to Exercises(Chapter 2 Part 1) - /2018/02/22/Accelerated-C-Solutions-to-Exercises-Chapter-2/ - Exercise 2-0

    Compile and run the program presented in this chapter

    -

    Solution & Results

    This exercise has been accomplished in C++ - Looping and counting with detailed explination.

    -
    -

    Exercise 2-1

    Change the framing program so that it writes its greeting with no separation from the frame.

    -

    Solution & Results

    This can be easily accomplished by changing the number of the blanks to 0.

    -
    #include <iostream>
    #include <string>

    // using namespace std and names
    using std::cout; using std::cin;
    using std::endl; using std::string;

    int main()
    {
    // ask for the person's name
    cout << "Please enter your first name: ";

    // read the name
    string name;
    cin >> name;

    // build the message that we intend to write
    const string greeting = "Hello, " + name + "!";

    // the number of blanks surrounding the greeting
    // change to 0 to meet the requirement
    const int pad = 0;

    // the number of rows and columns
    const int rows = pad * 2 + 3;
    const string::size_type cols = greeting.size() + pad * 2 + 2;

    // write a blank line to separate the output from the input
    cout << endl;

    // write rows rows of output
    // invariant: we have written r rows so far
    for (int r = 0; r != rows; ++r)
    {
    string::size_type c = 0;
    // we have written c characters so far in the current row
    while (c != cols)
    {
    // are we on the border?
    if (r == 0 || r == rows - 1 ||
    c == 0 || c == cols - 1)
    {
    cout << "*";
    ++c;
    }
    else
    { // is it time to write the greeting?
    if (r == pad + 1 && c == pad + 1)
    {
    cout << greeting;
    c += greeting.size();
    }
    else
    {
    cout <<" ";
    ++c;
    }
    }
    }
    cout << endl;
    }
    return 0;
    }
    -

    Again, once we typed Bruce after the input prompt, the program writes below outputs on the console window

    -
    Please enter your first name: Bruce

    ***************
    *Hello, Bruce!*
    ***************
    -

    Analysis

    See deatiled analysis in C++ - Looping and counting.

    -
    -

    Exercise 2-2, 2-3

    2-2. Change the framing program so that it uses a different amount of space to seperate sides from the greeting than it uses to seperate the top and botton borders from the greeting.

    -

    2-3. Rewrite the framing program to ask the user to supply the amount of spacing to leave between the frame and the greeting.

    -

    These two exercises will be answered together.

    -

    Solution & Results

    It has been seen from the program (as shown in Exercise 2-1), the variable pad controls the spaces. Therefore, it is necessary to replace the pad with two seperate variables for controling the spaces on the leftside and rightside, and the spaces on the upside and downside, respectively. For example, I define two variables with initializers 2 and 3 seperately

    -
    const int lr_sides_pad  = 2; // controls left and right blanks
    const int tb_sides_pad = 3; // controls top and bottom blanks
    -

    Correspondingly, the size of rows becomes

    -
    const int rows = tb_sides_pad * 2 + 3
    -

    and the size of cols becomes

    -
    const string::size_type cols = greeting.size() + lr_sides_pad * 2 + 2;
    -

    In addition, the condition for determining the position of the greeting changes to

    -
    r == tb_sides_pad + 1 && c == lr_sides_pad + 1
    -

    Up to now, I have replaced all pad with above two new variables. However, the frame size is still predetermined in the program and not very flexible. We further change it so that the size can meet each user’s preference. We need to redifine the variables and assign user-defined values to them.

    -
    // ask for the number of blanks that on the left or right side
    cout << "Please enter the number of spaces between the greeting and the left border (or the right boder): ";

    // read the number of blanks that on the left or right side
    int lr_sides_pad;
    cin >> lr_sides_pad;

    // ask for the number of blanks that on the upper or below side
    cout << "Please enter the number of spaces between the greeting and the top border (or the bottom boder): ";

    // read the number of blanks that on the left or right side
    int tb_sides_pad;
    cin >> tb_sides_pad;
    - -

    Now the program becomes

    -
    #include <iostream>
    #include <string>

    // using namespace std and names
    using std::cout; using std::cin;
    using std::endl; using std::string;

    int main()
    {
    // ask for the person's name
    cout << "Please enter your first name: ";

    // read the name
    string name;
    cin >> name;

    // build the message that we intend to write
    const string greeting = "Hello, " + name + "!";

    // ask for the number of blanks that on the left or right side
    cout << "Please enter the number of spaces between the greeting and the left border (or the right boder): ";

    // read the number of blanks that on the left or right side
    int lr_sides_pad;
    cin >> lr_sides_pad;

    // ask for the number of blanks that on the upper or below side
    cout << "Please enter the number of spaces between the greeting and the top border (or the bottom boder): ";

    // read the number of blanks that on the left or right side
    int tb_sides_pad;
    cin >> tb_sides_pad;

    // the number of rows and columns
    const int rows = tb_sides_pad * 2 + 3;
    const string::size_type cols = greeting.size() + lr_sides_pad * 2 + 2;

    // write a blank line to separate the output from the input
    cout << endl;

    // write rows rows of output
    // invariant: we have written r rows so far
    for (int r = 0; r != rows; ++r)
    {
    string::size_type c = 0;
    // we have written c characters so far in the current row
    while (c != cols)
    {
    // are we on the border?
    if (r == 0 || r == rows - 1 ||
    c == 0 || c == cols - 1)
    {
    cout << "*";
    ++c;
    }
    else
    { // is it time to write the greeting?
    if (r == tb_sides_pad + 1 &&
    c == lr_sides_pad + 1)
    {
    cout << greeting;
    c += greeting.size();
    }
    else
    {
    cout <<" ";
    ++c;
    }
    }
    }
    cout << endl;
    }
    return 0;
    }
    -

    I performed three tests and present the results here

    -

    Test 1: input: Bruce 2 3

    Please enter your first name: Bruce
    Please enter the number of spaces between the greeting and the left border (or the right border): 2
    Please enter the number of spaces between the greeting and the top border (or the bottom border): 3

    *******************
    * *
    * *
    * *
    * Hello, Bruce! *
    * *
    * *
    * *
    *******************
    -

    Test 2: input: Bruce 4 0

    Please enter your first name: Bruce
    Please enter the number of spaces between the greeting and the left border (or the right border): 4
    Please enter the number of spaces between the greeting and the top border (or the bottom border): 0

    ***********************
    * Hello, Bruce! *
    ***********************
    -

    Test 3: input: Bruce 5 5

    Please enter your first name: Bruce
    Please enter the number of spaces between the greeting and the left border (or the right border): 5
    Please enter the number of spaces between the greeting and the top border (or the bottom border): 5

    *************************
    * *
    * *
    * *
    * *
    * *
    * Hello, Bruce! *
    * *
    * *
    * *
    * *
    * *
    *************************
    -

    Analysis

    See deatiled analysis in C++ - Looping and counting.

    -
    -

    To be continued.

    -]]>
    - - Programming - - - Accelerated C++ Solutions - -
    - - Accelerated C++ Solutions to Exercises(Chapter 1) - /2018/02/22/Accelerated-C-Solutions-to-Exercises-Chapter-1/ - Exercise 1-0

    Compile, execute, and test the programs in this chapter.

    -

    Solution & Results

    The first program:

    -
    // ask for a person's name, and greet the person
    #include <iostream>
    #include <string>

    int main()
    {
    // ask for the person's name
    std::cout << "Please enter your first name: ";

    // read the name
    std::string name; // define name
    std::cin >> name; // read into

    // write a greeting
    std::cout << "Hello, " << name << "!" << std:: endl;
    return 0;
    }
    -

    Test: type Liam according to the prompt and the console window displays as follows:

    -
    Please enter your first name: Liam
    Hello, Liam!
    -

    The second program:

    -
    // ask for a person's name, and generate a framed greeting
    #include <iostream>
    #include <string>

    int main()
    {
    std::cout << "Please enter your first name: ";
    std::string name;
    std::cin >> name;

    // build the message that we intend to write
    const std::string greeting = "Hello, " + name + "!";

    // build the second and forth lines of the output
    const std::string spaces(greeting.size(), ' ');
    const std::string second = "* " + spaces + " *";

    // build the first and fifth lines of the output
    const std::string first(second.size(), '*');

    // write it all
    std::cout << std::endl;
    std::cout << first << std::endl;
    std::cout << second << std::endl;
    std::cout << "* " << greeting << " *" << std::endl;
    std::cout << second << std::endl;
    std::cout << first << std::endl;

    return 0;
    }
    -

    Test: type Liam according to the prompt and the console window displays as follows:

    -
    Please enter your first name: Liam

    ****************
    * *
    * Hello, Liam! *
    * *
    ****************
    - -

    Analysis

    See C++ - Working with strings.

    -
    -

    Exercise 1-1

    Are the following definitions valid? Why or why not?

    -
    const std::string hello = "Hello";
    const std::string message = hello + ", world" + "!";
    -

    Solution & Results

    Yes, above definitions are valid.

    -

    The first statement defines a string type variable named hello and initialize the variable with copying string literals Hello into it. The second statement defines a variable named message and copy the value of the right side of the = into it. The right side is an expression that the concatenation operator + operates on the object hello and two string literals. First, the operator is left-associative and hence:

    -
    const std::string message = hello + ", world" + "!";
    = (hello + ", world") + "!";
    -

    Due to the fact that the result of (hello + “, world”) is also a string type object, the whole expression is simply to concatenate a string and a string literals, which is legal and valid. The test is shown below:

    -
    #include <iostream>
    #include <string>
    using std::cout; using std::endl; using std::string;

    int main()
    {
    const std::string hello = "Hello";
    const std::string message = hello + ", world" + "!";
    cout << hello << endl;
    cout << message << endl;
    return 0;

    }
    -

    The program runs ok and display results on the console window as below:

    -
    Hello
    Hello, world!
    -

    Analysis

    As stated by Lippman ect. (2012): “When we mix strings and string or character literals, at least one operand to each + operator must be of string type”. For more analysis, please see C++ - Working with strings.

    -
    -

    Exercise 1-2

    Are the following definitions valid? Why or why not?

    -
    const std::string exclam = "!";
    const std::string message = "Hello" + ", world" + exclam;
    -

    Solution & Results

    The definition of exclam is valid but the definition of message is illegal and hence invalid. This is because that the operator + is left-associative and cannot concatenate two string literals. Follow program shows my test:

    -
    #include <iostream>
    #include <string>
    using std::cout; using std::endl; using std::string;

    int main()
    {
    const std::string exclam = "!";
    const std::string message = "Hello" + ", world" + exclam;
    cout << exclam << endl;
    cout << message << endl;
    return 0;

    }
    -

    As expected, the compilation reports errors as below:

    -
    invalid operands of types 'const char [6]' and 'const char [8]' to binary 'operator+'.
    -

    Analysis

    See Exercise 1-1 and
    C++ - Working with strings.

    -
    -

    Exercise 1-3

    Is the following program valid? If so, what does it do? If not, why not?

    -
    #include <iostream>
    #include <string>

    int main()
    {
    { const std::string s = "a string";
    std::cout << s << std::endl; }
    { const std::string s = "another string";
    std::cout << s << std::endl; }
    return 0;
    }
    -

    Solution & Results

    Yes, it is a valid program.
    The program intends to print two different strings, both of which have the same name s within the main function body.
    I firstly present the result when running the program:

    -
    a string
    another string
    -

    These two varibles are not conflict because their names have different scopes that formed by two pairs of curly braces. Specifically:

    - -

    From the perspective of memory management, the first variable exists only when the part of the program within the first nested braces is executing, and disappears and returns the memory it occupied once the computer reaches the end of the braces. It has limited lifetime and so does the second variable.

    -

    Analysis

    See C++ - Working with strings.

    -
    -

    Exercise 1-4

    Question 1

    Is the following program valid?

    -
    #include <iostream>
    #include <string>

    int main()
    {
    { const std::string s = "a string"; //outer scope
    std::cout << s << std::endl;
    { const std::string s = "another string"; // inner scope
    std::cout << s << std::endl; }}
    return 0;
    }
    -

    Solution & Results

    Yes, the program is valid.
    In constrast with the last program, the scopes of two names are not independent with eachother but are nested:

    - -

    Therefore, the logic of this program can be described as:

    -
      -
    1. the first variable is defined and initialized with string literals “a string” and is printed out in the following statement.
    2. -
    3. “the variable” is redefined in the inner scope and initialized with string literals another string. Then, it is printed out in the following step.
    4. -
    5. the second s is destroyed at the end of its scope, that is, the first right brace (}).
    6. -
    7. the first s is still available after the first right brace, but is destroyed once the computer reaches the second right brace.
    8. -
    -

    Note that two variables are different and the second variable doesn’t overwriting the first one though they both use the name s. To confirm this, I simply add one statement between the first right curly brace and second right curly brace:

    -
    std::cout << s <<std::endl;
    -

    Then, I run the program and it yields:

    -
    a string
    another string
    a string
    -

    Question 2

    what if we change } } to };} in the third line from the end?

    -
    #include <iostream>
    #include <string>

    int main()
    {
    { const std::string s = "a string";
    std::cout << s << std::endl;
    { const std::string s = "another string";
    std::cout << s << std::endl; };}
    return 0;
    }
    -

    Solution & Results

    The program is still valid after adding an semicolon betwween the first and second right curly brace. The semicolon typically working as a statement terminator in C++. In this case, it does nothing except forming a null statement. This program leads to the same results as the program that without adding a semicolon.

    -
    a string
    another string
    -

    Analysis

    I didn’t found very good interpretations about the role of the semicolon in C++. For more analysis, please move to What is the semicolon in C++? and What is the function of semicolon in C++? , where some good answers have been provided by the forum users.

    -
    -

    Exercise 1-5

    Is this program valid? If so, what does it do? If not, say why not, and rewrite it to be valid.

    -
    #include <iostream>
    #include <string>

    int main()
    {
    { std::string s = "a string";
    { std::string x = s + ", really";
    std::cout << s << std::endl;
    }
    std::cout << x << std::endl;
    }
    return 0;
    }
    -

    Solution & Results

    No, this program is invalid. S
    imilarly as last two exercises, this question tries to test my understanding on the scope of a name. The scope of name s is outter scope and can be accessed in the inner scope where the variable x is defined. Therefore, it is correct that initializing x with a expression which oncatenates a string and string literals. It is also ok to output s inside the nested scope. However, the statement std::cout << x << std::endl; would be invalid due to the fact that x doesn’t exist anymore once the computer reaches the end of its scope, that is, the first right curly brace. As expected, when running this program, error occurs:

    -
    'x' was not declared in this scope
    -

    To correct it, we can simply put the statement:std::cout << x << std::endl; into the inner scope, or directly remove the nested curly braces as long as the name is not redefined in a same scope. Both corrections work well and following results can be seen on the console window:

    -
    a string
    a string, really
    -

    Analysis

    See Exercise 1-3, Exercise 1-4 and
    C++ - Working with strings.

    -
    -

    Exercise 1-6

    What does the following program do if, when it asks you for input, you type two names(for example, Samuel Beckett)? Predict the behavior before running the program, then try it.

    -
    #include <iostream>
    #include <string>

    int main()
    { // prompt that asks for input
    std::cout << "What is your name? ";

    // define an string type variable named **name** which is empty initially.
    std::string name;

    // read the contents into **name**
    std::cin >> name;

    // output greetings as well as asking for input
    std::cout << "Hello, " << name
    << std::endl << "And what is yours?";

    // read new contents into the same variable
    std::cin >> name;

    // output greeting
    std::cout << "Hello, " << name
    << "; nice to meet you too!" << std::endl;
    return 0;
    }
    -

    Solution & Results

    The program intends to create a greeting conversation between two people. For For clarity, I added comments for each statement first. Let’s analyse this program step by step:

    -
      -
    1. once click the running button, the computer reads the first statement and stores the contents *What is your name? * into buffer.
    2. -
    3. the cin statement triggers the flush of cout, and user can type two names Samuel Beckett according to the prompt. I predict following contents would be written on the output device:
      What is your name? Samuel Beckett
    4. -
    5. then, the cin reads from the first character untill it encounters the whitespace. Therefore, only Samuel is stored into name.
    6. -
    7. the fouth statement would flush the cout because of the manipulator endl. Then, the console windows should have following outputs:
      Hello, Samuel
    8. -
    9. the fifth statement cin flushes buffer and following sentence will be printed out straight after above contents.
      And what is yours?
    10. -
    11. then, cin starts reading the rest content Beckett and stores into name. The new name Beckett rewrites this variable.
    12. -
    13. finally, the last buffer flush happens once the computer reads std::endl. Following contents are expected to appear on the console window.
      Hello, Beckett; nice to meet you too!
    14. -
    15. finished.
    16. -
    -

    As ecpected, the final results of this program is shown as follows:

    -
    What is your name? Samuel Beckett
    Hello, Samuel
    And what is yours?Hello, Beckett; nice to meet you too!
    -

    Analysis

    I am not entirly sure about my analysis, and may update this in the futuer if I get new ideas.

    -
    -

    References

    Koenig, A. and Moo, B.E., 2000. Accelerated C++.

    -

    Lippman, S.B., Lajoie, J. and Moo, B.E., 2013. C++ Primer, 5th edt.

    -]]>
    - - Programming - - - Accelerated C++ Solutions - -
    - - C++ - Working with strings - /2018/02/11/C-Working-with-strings/ - Variables, Initialization & Declarations

    Conventionally, I present the program that provided in Accelerated C++: Practical Programming by Example here:

    -
    // ask for a person's name, and greet the person
    #include <iostream>
    #include <string>

    int main()
    {
    // ask for the person's name
    std::cout << "Please enter your first name: ";

    // read the name
    std::string name; // define name
    std::cin >> name; // read into

    // write a greeting
    std::cout << "Hello, " << name << "!" << std::endl;
    return 0;
    }
    -

    The program is based on Hello, world! and is improved to say Hello to anyone you specified. It asks you to input a name (e.g. Batman) and then output as follows:

    -
    Hello, Batman!
    -

    Variables

    To realize this function, a variable is defined to hold input. According to Koenig (2000), “a variable is an object that has a name” while “an object is a part of the computer’s memory that has a type”. Therefore, a variable should have:

    -
      -
    1. name: for identifying the object that is needed to be manipulated.
    2. -
    3. type: determines the size and layout of the variable’s memory, range of values that can be stored within the memory, and operations that can be applied to the variable.
    4. -
    -

    In this case, the variable is named name and its type is std::string. The std:: means that the string type is defined in namespace std. Therefore, we need to include the standard header string as same as include the header iostream at the begining of this program. This statement also indicates the syntax of defining variables: a type specifier followed by one name (or more names sepreated by commas), and ends with a semicolon.

    -

    As this program shows, the variable name is defined within the function body and hence it is a local variable, i.e. the variable is only valid when the function body is executed. Once the computer reachers the }, it destroys the variable name and returns the memory that the variable occupied during its lifetime.

    -

    Initialization

    An object is initialized and gets a specified value (i.e. initializer) when it is created. C++ defines several types of initializations. The example provided above uses default initialization in defining the variable name.

    -

    Default initialization

    The default initialization means that no initializer is applied to a variable when it is defined. In the case of default initialization, the variable gets a default value which depends on its type and may also depends on where the variable is defined. Note that the default value of an object of built-in type, e.g. arithmetic type, depends on where it is defined:

    - -

    For example, I write a program as below for testing:

    -
    #include<iostream>
    using std::cout;
    using std::endl;

    int i; bool h; char m;

    int main()
    {
    int j; bool k; char n;
    cout << i << ' ' << j << endl;
    cout << h << ' ' << k << endl;
    cout << m << ' ' << n << endl;;
    return 0;
    }
    -

    The results is:

    -
    0 4201179
    0 0
    a
    - -

    with warnings:

    -
    'j' is used uninitialized in this function [-Wuninitialized]
    'k' is used uninitialized in this function [-Wuninitialized]
    'n' is used uninitialized in this function [-Wuninitialized]
    -

    Clearly, it will result severe errors if we try to access the value or copy the value of such uninitialized variables.

    -

    Each class defines different ways to initialize objects of the class type. If default initialization is allowed, the default value of an object is determined by the class. For example, the default initialization of a string leads to an empty string (i.e. no characters). In the first program, the variable name is the empty string.

    -

    Copy & Direct initialization

    Another way to initialize variables is using =, that is copy initialize by copying the initializer on the right side into the created object. For example:

    -
    int i = 10;
    char j = 'a';
    string s1 = "Hello";
    -

    The values used to initialize objects can also be expressions:

    -
    int i = 10;
    int j = i*10;
    -

    The copy initialization seems like assignment, however, is completely different. As Lippman etc. (2012) point out: “Initialization happens when a variable is given a value when it is created. Assignment obliterates an object’s current value and replaces that value with a new one.”

    -

    We can also initialize above objects without using “=”:

    -
    int i(10);
    char j('a');
    string s1("Hello");
    -

    This way is named direct initialization. The string type variables can also be initialized using an alternative form:

    -
    // direct initialization, s1 is aaaaa
    string s1(5, 'a');
    // copy initialization, s2 is aaaaa
    string s2 = string(5, 'a');

    // decomposition of s2
    string s0(5, 'a'); // s0 is aaaaa
    string s2 = s0; // copy s0 into s2
    -

    The differences between copy initialization and direct initialization will not be disscussed until chapter 9;

    -

    List initialization

    The C++ 11 standards supports an alternative form for the copy initialization and direct initialization using curly braces. For example:

    -
    int i = {10}; // same as _int i = 10;_
    int j{10}; // same as _int j(10);_
    string s1 = {"Hello"}; // same as _string s1 = "Hello";_
    string s2 {"Hello"}; // same as _string s2("Hello");_
    -

    This form is known as list initialization. The major difference between list initialization and above methods is that list initialization doesn’t allow narrowing for the conversion of built-in type variables. To show this property, I performed an experiment using following codes:

    -
    #include<iostream>
    using std::cout;
    using std::endl;

    int main()
    {
    double i = 10.9876;
    int num1 = i;
    int num2(i);
    int num3{i};

    cout << num1 << endl; // ok but result is truncated: 10
    cout << num2 << endl; // ok but result is truncated: 10
    cout << num3 << endl; // warning: narrowing conversion of 'i' from 'double' to 'int' inside { }
    return 0;
    }
    -

    Hence, list initialization is a more reliable approach compared with other alternatives.

    -

    Declarations

    As analysed above, the definition of a variable requires type, name and initializers. For convenience, C++ supports separate compilation, that is, allows a program to be split into several files and compiled independently. In fact, we have already used the separate compilation in previous examples, such as the usage of IO system. The std::cout is defined in the header iostream but can be used in our programs through simply including the header file. To realize this function, a variable needs declaration which requires stating the type and name. The difference between declaration and definition is that the variable may be explicitly initialized in its definition. To declare a a variable only, we need add extern and don’t explicitly initialize it:

    -
    extern int i; // declares but not defines i
    int i; // declares and defines i
    extern int i = 10; // declares and defines i
    -

    But it should be noted that(Lippman etc. 2012):

    - -

    Operations on strings

    Reading & Writing strings

    To read the inputs into name, this program uses std::cin and operator >>. It will discard whitespace characters such as space, tab and backspace, and then reads chars into name until it encounters another whitespace. For example, following three piece of inputs yield same outputs when we run the program:

    -
    Bruce
    [a tab space]Bruce
    Bruce Lee
    -

    Results:

    -
    Hello, Bruce!
    Hello, Bruce!
    Hello, Bruce!
    -

    The IO library accumulates and stores the characters using an internal data structure named buffer, and flushes the buffer by writing the contents to the output device only when necessary. There are three events that cause the system to flush the buffer:

    - -

    The input operator >> is also left-associative and returns the left-hand operands as their results. Therefore, multiple reads can be done like below codes :

    -
    string s1, s2; // define two string type variables
    cin >> s1 >> s2; // reads from cin into s1 and s2 seprately
    cout << s1 << s2 << endl; // writes two strings
    -

    In some cases, we would like to read chars as well as whitespaces. To fulfill this, we need to use getline instead of >> with the syntax:

    -
    std::getline(std::cin, name)
    -

    The getline function will read chars into variable name until it encounters line feed (note that the line feed will also be read but not stored into the variable). I modified the program using getline:

    -
    #include <iostream>
    #include <string>

    int main()
    {
    // ask for the person's name
    std::cout << "Please enter your full name: ";

    // read the name
    std::string name; // define name
    std::getline(std::cin, name); // read into

    // write a greeting
    std::cout << "Hello, " << name << "!" << std::endl;
    return 0;
    }
    -

    When I run the program and input “Bruce Lee”, it results:

    -
    Please enter your first name: Bruce Lee
    Hello, Bruce Lee!
    - -

    The string size and concatenate operations

    Now we go into a little bit complex operations and aim to produce a framed greeting like below:

    -
    Please enter your full name: Bruce Lee 
    *********************
    * *
    * Hello, Bruce Lee! *
    * *
    *********************
    -

    Let’s provide the program first:

    -
    // ask for a person's name, and generate a framed greeting
    #include <iostream>
    #include <string>

    int main()
    {
    std::cout << "Please enter your full name: ";
    std::string name;
    std::getline(std::cin, name);

    // build the message that we intend to write
    const std::string greeting = "Hello, " + name + "!";

    // build the second and forth lines of the output
    const std::string spaces(greeting.size(), ' ');
    const std::string second = "* " + spaces + " *";

    // build the first and fifth lines of the output
    const std::string first(second.size(), '*');

    // write it all
    std::cout << std::endl;
    std::cout << first << std::endl;
    std::cout << second << std::endl;
    std::cout << "* " << greeting << " *" << std::endl;
    std::cout << second << std::endl;
    std::cout << first << std::endl;

    return 0;
    }
    -

    The first three statements are exactly the same as those in the last program. However, we define a new string type variable named greeting and initialize it with the message that we will write using opetator +:

    -
    const std::string greeting = "Hello, " + name + "!";
    -

    The keyword const means that the value of the variable keeps constant since the first time read in. The + concatenate two strings (may also be one string and one string literals but cannot be two string literals) into a single string. As mentioned in previous chapter, operators have different effect on operands depending on the types of operands, which is commonly termed as overloaded. Same as operators >> and <<, + is also left-associative.

    -

    The remainder parts of the frame is simply constituted by asterisks and spaces while the numbers of asterisks and spaces are determined by the size of greeting. Recalling the ways to initialize a string type variable filled by same chars(e.g. ‘a’):

    -
    string s0(5, 'a'); // s0 is aaaaa
    -

    What we need here is to figure out the size of greeting. greeting.size() is a member function of the object. By calling this member function, we will obtain an integer that means the number of chars in greeting. The returned value is in fact not a int type value, but a string::size_type. In this case, we know that this value is unsigned and hence cann’t compare this value with other signed values for avoiding errors. If we don’t know what exactly the type is, we can use type deduction with specifiers auto and decltype. For example:

    -
    auto len = greeting.size();
    -

    The variable len is string::size_type. Similarly:

    -
    decltype(greeting.size) len;
    -

    The differences of these two specifiers are:

    - -

    Random access using a subscript

    If there needs to access some characters of the string, we can use the subscript operator([]) to access and sequentially operate on individual characters. Inside the operator, an index value is required to denote the position of the character to be accessed. The operation returns a reference to the character of the given position.

    -

    The index value should be a value of type string::size_type, and its range should be >= 0 and < size(). In other words, it use asymmetric range [0, size()). To show more about the random access, I did a experiment as below

    -
    #include <iostream>
    #include <vector>

    using std::cin; using std::string;
    using std::cout; using std::endl;

    int main()
    {
    string str = {"abcdef"};

    // obtain the size of str1
    string::size_type strSize = str.size();

    // define an signed type variable
    int x = 0;

    // write the string one character by one character
    cout << str[x] << str[1] << str[2] << str[3]
    <<str[4] << str[strSize-1] << endl;

    // access the character beyond the range of [0, strSize-1)
    cout << str[strSize] << str[strSize + 1] << str[10] << endl;

    return 0;
    }
    -

    Results:

    -
    abcdef
    b
    -

    The example above shows that it leads to unknown results if we use a subscript that beyond the range of [0, size()). In addition, it works if we use a integer value has a signed type (in contrast, the string::size_type is unsigned integeral value).
    This is because the index value of the signed type can be converted to the type of string::size_type.

    -

    Other operations

    empty function returns a bool type value that indicates whether the string type variable is empty.

    -
    greeting.empty(); // if greeting is empty, it returns 1, otherwise returns 0;
    -

    <, <=, >, >= “test whether one string is less than, less than or equal to, greater than, or greater than or equal to another. These operators use the same strategy as a (case-sensitive) dictionary” (Lippman etc. 2012, p80).

    -
    -

    Next: C++ - Looping and counting.

    -]]>
    - - Programming - - - C++ - Notes - -
    - - Accelerated C++ Solutions to Exercises(Chapter 0) - /2018/02/06/Accelerated-C-Solution-to-Exercise-Chapter%200/ - All the programs were compiled and excuted within the setting of Eclipse CDT and MinGW64

    -
    -

    Exercise 0-0

    Compile and run the Hello, world! program

    -

    Solution & Results

    Compile and run Hello, world! program with MinGW + Eclipse CDT:

    -

    Hello, world!

    -

    Analysis

    See C++ - Getting Started.

    -
    -

    Exercise 0-1

    What does the following statement do?

    -
    3 + 4;
    -

    Solution & Results

    The expression statement yields 7 as its results because it contains two int type operands (i.e. 3 and 4), and one operator (i.e. addition). However, it has no side effects on the state of the program and the implementation. Hence, there should be nothing displayed on the console when the program is excuated. As expected, the graph below shows the result along with a warning description statement has no effect ‘3+4’.

    -

    Expression Statement: 3+4;

    -

    Analysis

    See C++ - Getting Started.

    -
    -

    Exercise 0-2

    Write a program that, when run, writes

    -
    This (") is a quote, and this (\) is a backslash.
    -

    Solution & Results

    It seems that this program is exactly the same as the Hello, world! program in Exercise 0-0 once we replace the string literals. In doing so, however, the compiler reports errors as below graph shows.

    -

    Compilation errors

    -

    The the occurrence of errors is due to the facts:

    -
      -
    1. Characters should be enclosed in double quotes and the string literals is not allowed to span lines.
    2. -
    3. Some characters such as backslash or single/double quotes have special meaning in C++ language.
    4. -
    5. Some characters such as backspace or control character are nonprintable.
    6. -
    -

    Therefore, it is impossible to use such characters directly. Instead, the language provides escape sequence to represent such characters. The escape sequence begins with a backslash, i.e. in the form of \+character. The follow graph shows the right solution to this question.

    -

    String literals output

    -

    Analysis

    The below table gives the escape sequences that are defined in C++ language.

    - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
    newline\nhorizontal tab\talert bell\a
    vertical\vbackspace\bdouble quote\“
    backslash\\question mark?single quote\‘
    carriage return\rformfeed\f
    -

    One can choose another syntax for using Escape sequence: a backslash followed by by hexadecimal or octal digits while the value represents the numerical value of the character. For example, the linefeed command in C++ can be written in three ways:

    - - - - - - - - - - - - - - - - - - - -
    character\n
    octal digits\12
    hexadecimal digits\x0a
    -

    If a backslash followed by more than three octal digits, the escape sequence is only valid for the first three digits while the rest digits will be read as normal character. For example, ‘\12345’ is equivalent to ‘S’ followed by ‘4’ and ‘5’. But if a backslash followed by the hex digits, the escape sequence uses all the hex digits.

    -
    -

    Exercise 0-3

    The string literal “\t” represents a tab character; different C++ implementations display tabs in different ways. Experiment with your implementation to learn how it treats tabs.

    -

    Solution & Results

    In my view, the different implementations could be interpretated as: different C++ compilers or different versions, which may have different interpretation of C++ standards or different support for certain C++ features. Different IDEs or editors might also have an impact on the final displays of results due to various settings. Actually, I am not very sure about my understanding on this.

    -

    To test how different implementations display tabs, I wrote an output statement in which the Horizontal tab ‘\t’ is inserted into a string literals in a random manner. Then, I run the program in four different environments. First I present the codes here:

    -
    // Accelerated C++ Solution to Exercises 0-3
    #include <iostream>
    using std::cout;
    using std::endl;

    int main()
    {
    // for loops for generating 10 groups' string literals.
    for (int j = 3; j < 13; j++)
    { // for loops for generating each character of the string literals
    for (char i = 'A'; i < 'z'; i++)
    {
    cout << i;
    // the insertation of the horizontal tab is
    // conditional on the bool expression: (int)i % j == 0
    if ((int)i % j == 0)
    cout << '\t';
    }
    cout << endl;
    }
    return 0;
    }
    -

    The following content displays four environment settings and the results.

    -

    Linux GNU(G++) + Vim Editor

    Linux GNU + Vim

    -

    Linux GNU(G++) + Eclipse

    Linux GNU + Eclipse

    -

    Windows MinGW + Vim Editor

    MinGW + Vim

    -

    Windows MinGW + Eclipse

    MinGW + Eclipse

    -

    Unfortunately, the experiments failed to response the question correctly as there is no difference between these implementations regarding to displaying tabs. But it reveals that the tab stops every 8 spaces and always aligns the followed text to the next stop, regardless of the position where the tab is inserted into. I will keep focus on this question and try to find a better solution to this question.

    -

    Analysis

    To be continued.

    -
    -

    Exercise 0-4

    Write a program that, when run, writes the Hello, world! program as its out put.

    -

    Solution & Results

    The question is not complicated. But, be carefull about the output of the nexted double quotes.

    -

    Hello, world! program print

    -

    Analysis

    See Solution to Exercise 0-2.

    -
    -

    Exercise 0-5

    Is this a valid program? Why or why not?

    -
    #include <iostream> 
    int main() std::cout << "Hello, world!" << std::endl;
    -

    Solution & Results

    The program is invalid as shown below:

    -

    A invalid program

    -

    It is clear that curly braces are missing for a complete main function in this program. A correct main function should include a function body which is a block of statements enclosed by a pair of curly braces.

    -

    Analysis

    If one has no any programming knowledge, he probabily can’t figure out the error only from the error reports:

    -
    expected initializer before 'std'
    -

    Why the compile error shows like this? A possible reason is that the compiler treats codes outside the function as declarations of variables. To define a variable, one should firstly specify the type specifier and then declare the name of the variable, and finally add a semicolon. Once the variable is created, it is initialized. Consider that the compiler treats int main() as a declaration of a variable, it will find that the initialization of this variable is failed due to lacking of a semicolon.

    -
    -

    Exercise 0-6

    Is this a valid program? Why or why not?

    -
    #include <iostream>
    int main() {{{{{{std::cout << "Hello, world! << std::endl;}}}}}}
    -

    Solution & Results

    In contrast to the program in Exercise 0-5, this program is valid because it has a complete structure of a main function. Beyond the outmost curly bracces, the rest curly braces form independent blocks and can be nested.

    -

    A valid program

    -

    Analysis

    See C++ - Getting Started and Solution to Exercise 0-5.

    -
    -

    Exercise 0-7

    Is this a valid program? Why or why not?

    -
    #include <iostream>
    int main()
    {
    /*This is a comment that extends over several lines
    because it uses /* and */ as its starting and ending delimiters */
    std::cout << "Does this work?" << std::endl;
    return 0;
    }
    -

    Solution & Results

    The program is invalid:

    -

    A invalid program

    -

    As mentioned in C++ - Getting Started, comment pairs cannot nest. The warning also confirms this restriction.

    -
    ..\Exercise0_7.cpp:7:25: warning: "/*" within comment [-Wcomment]
    -

    To Solve the problem, we can add single line comments before each lines of comments. Below graph gives the correct implementation:

    -

    A valid program

    -

    Analysis

    See C++ - Getting Started.

    -
    -

    Exercise 0-8

    Is this a valid program? Why or why not?

    -
    #include <iostream>
    int main()
    {
    // This is a comment that extends over several lines
    // by using // at the begining of each line instead of
    // using /* or */ to delimit comments.
    std::cout << "Does this work?" << std::endl;
    return 0;
    -

    Solution & Results

    It is a valid program which successfully corrects the program in Exercise0-7

    -

    A valid program

    -

    Analysis

    See C++ - Getting Started and Solution to Exercise 0-7.

    -
    -

    Exercise 0-9

    What is the shortest valid program?

    -

    Solution & Results

    As I mentioned in C++ - Getting Started, a valid program must contain a main function while a main function includes four elements: return type, function name, parameter list and function body.

    -

    Therefore, the shortest valid program works like this:

    -

    The shortest valid program

    -

    Andrew Koenig who is the author of Accelerated C++: Practical Programming by Example states:

    -
      -
    1. generally, functions must include at least one return statement.
    2. -
    3. the main function is special and may omit the return. In this case, the implementation will assume a return value of zero.
    4. -
    -

    The shortest valid program I present here omits the return statement. But as pointed out by Andrew Koenig, explicitly including a return from main function is a good practice.

    -

    Analysis

    See C++ - Getting Started.

    -
    -

    Exercise 0-10

    Rewrite the Hello, world! program so that a newline occurs everywhere that whitespace is allowed in the program.

    -

    Solution & Results

    Recalling the Hello, world! program:

    -
    #include <iostream>
    int main()
    {
    std::cout << "Hello, world!" << std::endl;
    return 0;
    }
    -

    There exits one space in the string literals Hello, world!”. I would replace the space with a newline character \n. The graph below shows my implementation and result:

    -

    New Hello, world! program

    -

    Analysis

    See C++ - Getting Started.

    -

    References

    Koenig, A. and Moo, B.E., 2000. Accelerated C++.

    -

    Lippman, S.B., Lajoie, J. and Moo, B.E., 2013. C++ Primer, 5th edt.

    -]]>
    - - Programming - - - Accelerated C++ Solutions - -
    - - C++ - Getting Started - /2018/02/02/C++%20-%20Getting%20Started/ - Introduction

    To have a better understanding on the C++ programming language, I decided to learn it from the very begining and take detailed notes from the textbooks Accelerated C++: Practical Programming by Example and C++ Primer (5th Edition), and some online C++ tutorials. In addition, I plan to complete the exercises of each chapter in the Accelerated C++ with providing detailed codes analysis and implementation processes.
    Please check the solutions to the first chapter exercises here: Accelerated C++ Solutions to Exercises Chapter 0

    -

    Hello, world!

    Let’s start by writing a simple program, Hello, world!, and then analyse its structure.

    -
    /*
    * A simple C++ Program
    * Author: Mr. Nobody
    */
    #include <iostream>

    int main()
    {
    // output Hello, world!
    std::cout << "Hello, world!" << std::endl;
    return 0;
    }
    -

    0.1 Comments

    As shown above, there are two types of comments in C++:

    -
      -
    1. Single line comment indicated by two slash signs //.
    2. -
    3. Comment pairs using two delimiters, beigining with a /* and ending with a */.
    4. -
    -

    0.2 #, #include

    Lines begining with # are preprocessor directives which are preprocessed before actual compilation, i.e. the compilation of the program itself. These preprocessor directives are not part of the program statement, and hence semicolons ;, marking the end of most statements in C++, are not required.

    -

    The directive #include gives preprocessor instructions to include a header file, i.e. copy the entire content of the specified header or file and insert. There are two ways to use **#include#:

    -
    #include <header>
    #include "file"
    -

    The angle-brackets <> are generally used to include a header of the standard library, e.g. iostream, string…, while the quotes “” include a file that is typically programmer-defined. For example, the program above includes a standard header named iostream to accomplish Input-Output (IO) as the C++ language doesn’t define any statements to do IO.

    -

    0.3 The main function

    Every C++ program must contain a main function. The main function is being called when the operating system runs a program. As same as other functions, the main function also includes four elements: return type, function name, parameter list and function body.

    -

    The main function requires an int (i.e. integers) type return value. A return value of zero tells the implementation that the program ran successfully while a non-zero value indicates errors. The example above has an empty parameter list, showing that there is nothing between the parentheses (()).

    -

    The function body begins with an open curly brace ({) and ends with a close curly brace (}). The braces indicate that all the statements inside are part of the same function, of which the return statement terminates the execution of the function by returning a value to the function’s caller. Of course, the value that returned should has the same type as the defined return type of the function. In the case of main function, if the return statement is omitted, the implementation would assume a return value of zero (not recommended).

    -

    0.4 IO & using declarations

    Before the return statement inside the braces, the first statement (as shown below) achieves the goal of this program.

    -
    std::cout << "Hello, world!" << std::endl;
    -

    As mentioned above, the statement writes Hello, World! on the standard output. The std:: indicates the followed name is part of the namespace named std. The names std::cout and std::cin refer to the objects of the standard output stream (ostream) and input stream (istream), which had already been defined in the iostream library. Another two objects that are defined in the iostream library are cerr and clog. The cerr, representing standard error, is used to output warning or error messages. The clog is used to output the general information about the execution of the program. The name std::endl, which is a manipulator, ends the current line of output. The output operator (<<) and input operator (>>) indicate that what follows is inserted to std::cout and std::cin. To use these objects, firstly we need to include the associated standard header, namely iostream.

    -

    To simplify the usage of library names from namesapce std, one could use unqualified names cout or cin instead of std::cout or std::cin by the means of using declarations. For example, above program is exactly as same as the below program:

    -
    /*
    * A simple C++ Program
    * Author: Mr. Nobody
    */
    #include <iostream>
    using std::cout;
    using std::endl;
    int main()
    {
    // output Hello, world!
    cout << "Hello, world!" << endl;
    return 0;
    }f
    -

    It should be noted that Headers should not include using declarations for the purpose of avoiding potential name confilits.

    -

    0.5 Expressions & Scope

    An Expression typically contains operators and operands. It becomes an expression statement when it is followed by a semicolon. The expression statement asks the implementation to compute or evaluate the expression but discards the results. However, the computation may lead to side effects such as printing a result. For example:

    -
    3 + 4;
    std::cout << "Hello, world!" << std::endl;
    -

    The first statement yields a result 7 but has no side effects. In other words, it is useless because the result is discarded. The second statement is useful because of its side effects, that is, the string is inserted into the standard output stream and hence is printed out.

    -

    In this case, << symbols are operators while std::cout, std::endl and Hello, world! are operands. Every operand has a type that determines the effect of the operators. As mentioned above, std::cout has type std::ostream. Then, the operator << writes the given value (i.e. its right-hand operand) on the given ostream (i.e. the left-hand operand)

    -

    The operator << is left-associative, that is, the operator returns its left-hand operands. The second << has following relationships with its left and right operands, connecting the output requests.

    -
    (std::cout << "Hello, world!") << std::endl;
    -

    The scope of a name is the part of a program, in which the name is valid. The above program shows two kinds of scopes: one kind is namespace scope, i.e. scope name std and scope operator ::; another kind is block scope formed by curly braces.

    -

    0.6 Others

    Another aspect is that C++ programs do not have strict requirements on indentation and spaces, allowing improving readability based on personal preference. However, three entities are not allowed to be used in a free-form.

    -
      -
    1. string literals which enclosed in double quotes are generally not span lines.
    2. -
    3. preprocessor directive should be put on its own line.
    4. -
    5. // type comments should ends at the end of the current line, and /***/ type comments cannot nest.
    6. -
    -
    -

    Next: C++ - Working with strings.

    -]]>
    - - Programming - - - C++ - Notes - -
    - - Hello World - /2018/02/01/hello-world/ - Welcome to Hexo! This is your very first post. Check documentation for more info. If you get any problems when using Hexo, you can find the answer in troubleshooting or you can ask me on GitHub.

    -

    Quick Start

    Create a new post

    $ hexo new "My New Post"
    - -

    More info: Writing

    -

    Run server

    $ hexo server
    - -

    More info: Server

    -

    Generate static files

    $ hexo generate
    - -

    More info: Generating

    -

    Deploy to remote sites

    $ hexo deploy
    - -

    More info: Deployment

    -]]>
    -
    - diff --git a/tags/Accelerated-C-Solutions/index.html b/tags/Accelerated-C-Solutions/index.html index 6a6e330e..7217b480 100644 --- a/tags/Accelerated-C-Solutions/index.html +++ b/tags/Accelerated-C-Solutions/index.html @@ -1,47 +1,128 @@ - + + + + + + + - - + + + - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + - + - - - + + + + + + + + + + + + - - Tag: Accelerated C++ Solutions | Liam's Blog @@ -51,363 +132,415 @@ - - -
    -
    + -
    -
    - + + + + + +
    -
    +
    - - -
    +
    -
    -
    +
    + +
    -

    Accelerated C++ Solutions - Tag +

    Accelerated C++ SolutionsTag

    -
    -

    2018

    -
    + -
    + -
    + + + + + -
    + + + + + -
    + + + + + -
    + + + + + -
    + + + + + -
    + + + + + -
    + + + + + -
    +
    @@ -415,7 +548,7 @@

    @@ -423,202 +556,304 @@

    - + +
    + -
    -
    - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + - + + + + + + + + - + - + + + + + + + + + + - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/tags/Accelerated-C-Solutions/page/2/index.html b/tags/Accelerated-C-Solutions/page/2/index.html index f1bd47cf..da691593 100644 --- a/tags/Accelerated-C-Solutions/page/2/index.html +++ b/tags/Accelerated-C-Solutions/page/2/index.html @@ -1,47 +1,128 @@ - + + + + + + + - - + + + - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + - + - - - + + + + + + + + + + + + - - Tag: Accelerated C++ Solutions | Liam's Blog @@ -51,363 +132,415 @@ - - -
    -
    + -
    -
    - + + + + + +
    -
    +
    - - -
    +
    -
    -
    +
    + +
    -

    Accelerated C++ Solutions - Tag +

    Accelerated C++ SolutionsTag

    -
    -

    2018

    -
    + -
    + -
    + + + + + -
    + + + + + -
    + + + + + -
    + + + + + -
    + + + + + -
    + + + + + -
    + + + + + -
    +
    @@ -415,7 +548,7 @@

    @@ -423,202 +556,304 @@

    - + +
    + -
    -
    - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + - + + + + + + + + - + - + + + + + + + + + + - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/tags/Accelerated-C-Solutions/page/3/index.html b/tags/Accelerated-C-Solutions/page/3/index.html index 55b81b8f..a505f367 100644 --- a/tags/Accelerated-C-Solutions/page/3/index.html +++ b/tags/Accelerated-C-Solutions/page/3/index.html @@ -1,47 +1,128 @@ - + + + + + + + - - + + + - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + - + - - - + + + + + + + + + + + + - - Tag: Accelerated C++ Solutions | Liam's Blog @@ -51,183 +132,163 @@ - - -
    -
    + -
    -
    - + + + + + +
    -
    +
    - - -
    +
    -
    -
    +
    + +
    -

    Accelerated C++ Solutions - Tag +

    Accelerated C++ SolutionsTag

    -
    -

    2018

    -
    + -
    +
    @@ -235,7 +296,7 @@

    @@ -243,203 +304,305 @@

    - + +
    + -
    -
    - - - - + + + + + + + + + + + + + - - - + + + + + + + + + + + + + + + + - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/tags/Algorithms/index.html b/tags/Algorithms/index.html index c630f529..f56c4c73 100644 --- a/tags/Algorithms/index.html +++ b/tags/Algorithms/index.html @@ -1,47 +1,128 @@ - + + + + + + + - - + + + - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + - + - - - + + + + + + + + + + + + - - Tag: Algorithms | Liam's Blog @@ -51,363 +132,415 @@ - - -
    -
    + -
    -
    - + + + + + +
    -
    +
    - - -
    +
    -
    -
    +
    + +
    -

    Algorithms - Tag +

    AlgorithmsTag

    -
    -

    2018

    -
    + -
    + -
    + + + + + -
    + + + + + -
    + + + + + -
    + + + + + -
    + + + + + -
    + + + + + -
    + + + + + -
    +
    @@ -415,7 +548,7 @@

    @@ -423,202 +556,304 @@

    - + +
    + -
    -
    - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + - + + + + + + + + - + - + + + + + + + + + + - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/tags/Algorithms/page/2/index.html b/tags/Algorithms/page/2/index.html index 8d8b94c5..dbd4a22c 100644 --- a/tags/Algorithms/page/2/index.html +++ b/tags/Algorithms/page/2/index.html @@ -1,47 +1,128 @@ - + + + + + + + - - + + + - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + - + - - - + + + + + + + + + + + + - - Tag: Algorithms | Liam's Blog @@ -51,203 +132,191 @@ - - -
    -
    + -
    -
    - + + + + -
    + +
    -
    +
    - - -
    +
    -
    -
    +
    + +
    -

    Algorithms - Tag +

    AlgorithmsTag

    -
    -

    2018

    -
    + -
    +
    @@ -255,7 +324,7 @@

    @@ -263,179 +332,265 @@

    - + +
    + -
    -
    - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + - - + - @@ -445,8 +600,8 @@

    + - @@ -459,6 +614,22 @@

    + + + + + + + + + + + + + + + + diff --git a/tags/C/index.html b/tags/C/index.html index 2cf2b324..b6221606 100644 --- a/tags/C/index.html +++ b/tags/C/index.html @@ -1,47 +1,128 @@ - + + + + + + + - - + + + - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + - + - - - + + + + + + + + + + + + - - Tag: C++ | Liam's Blog @@ -51,363 +132,415 @@ - - -
    -
    + -
    -
    - + + + + + +
    -
    +
    - - -
    +
    -
    -
    +
    + +
    -

    C++ - Tag +

    C++Tag

    -
    -

    2018

    -
    + -
    + -
    + + + + + -
    + + + + + -
    + + + + + -
    + + + + + -
    + + + + + -
    + + + + + -
    + + + + + -
    +
    @@ -415,7 +548,7 @@

    @@ -423,202 +556,304 @@

    - + +
    + -
    -
    +
    + + +
    + + +
    + + + +
    - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + - + + + + + + + + - + - + + + + + + + + + + - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/tags/C/page/2/index.html b/tags/C/page/2/index.html index 4dfd94b2..99b6bbce 100644 --- a/tags/C/page/2/index.html +++ b/tags/C/page/2/index.html @@ -1,47 +1,128 @@ - + + + + + + + - - + + + - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + - + - - - + + + + + + + + + + + + - - Tag: C++ | Liam's Blog @@ -51,363 +132,415 @@ - - -
    -
    + -
    -
    - + + + + + +
    -
    +
    - - -
    +
    -
    -
    +
    + +
    -

    C++ - Tag +

    C++Tag

    -
    -

    2018

    -
    + -
    + -
    + + + + + -
    + + + + + -
    + + + + + -
    + + + + + -
    + + + + + -
    + + + + + -
    + + + + + -
    +
    @@ -415,7 +548,7 @@

    @@ -423,202 +556,304 @@

    - + +
    + -
    -
    +
    + + +
    + + +
    + + + +
    - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + - + + + + + + + + - + - + + + + + + + + + + - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/tags/C/page/3/index.html b/tags/C/page/3/index.html index 815ae8b6..2422975f 100644 --- a/tags/C/page/3/index.html +++ b/tags/C/page/3/index.html @@ -1,47 +1,128 @@ - + + + + + + + - - + + + - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + - + - - - + + + + + + + + + + + + - - Tag: C++ | Liam's Blog @@ -51,363 +132,415 @@ - - -
    -
    + -
    -
    - + + + + + +
    -
    +
    - - -
    +
    -
    -
    +
    + +
    -

    C++ - Tag +

    C++Tag

    -
    -

    2018

    -
    + -
    + -
    + + + + + -
    + + + + + -
    + + + + + -
    + + + + + -
    + + + + + -
    + + + + + -
    +
    @@ -415,7 +548,7 @@

    @@ -423,202 +556,304 @@

    - + +
    + -
    -
    +
    + + +
    + + +
    + + + +
    - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + - + + + + + + + + - + - + + + + + + + + + + - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/tags/C/page/4/index.html b/tags/C/page/4/index.html index f6074d39..ba272930 100644 --- a/tags/C/page/4/index.html +++ b/tags/C/page/4/index.html @@ -1,47 +1,128 @@ - + + + + + + + - - + + + - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + - + - - - + + + + + + + + + + + + - - Tag: C++ | Liam's Blog @@ -51,263 +132,275 @@ - - -
    -
    + -
    -
    - + + + + + +
    -
    +
    - - -
    +
    -
    -
    +
    + +
    -

    C++ - Tag +

    C++Tag

    -
    -

    2018

    -
    + -
    + -
    + + + + + -
    +
    @@ -315,7 +408,7 @@

    @@ -323,154 +416,152 @@

    - + +
    + -
    -
    +
    + + +
    + + +
    + + + +
    - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + - + - + + - + + + + + + + + - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/tags/Data-Structures/index.html b/tags/Data-Structures/index.html index 44197db3..2108f574 100644 --- a/tags/Data-Structures/index.html +++ b/tags/Data-Structures/index.html @@ -1,47 +1,128 @@ - + + + + + + + - - + + + - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + - + - - - + + + + + + + + + + + + - - Tag: Data Structures | Liam's Blog @@ -51,283 +132,303 @@ - - -
    -
    + -
    -
    - + + + + + +
    -
    +
    - - -
    +
    -
    -
    +
    + + +
    @@ -336,183 +437,189 @@

    -

    - + +
    + -
    -
    +
    + + +
    + + +
    + + + +
    - - - - + - - - @@ -520,23 +627,118 @@

    + + + + + + + + - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/tags/Notes/index.html b/tags/Notes/index.html index c06bd125..f9d24a08 100644 --- a/tags/Notes/index.html +++ b/tags/Notes/index.html @@ -1,47 +1,128 @@ - + + + + + + + - - + + + - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + - + - - - + + + + + + + + + + + + - - Tag: Notes | Liam's Blog @@ -51,363 +132,415 @@ - - -
    -
    + -
    -
    - + + + + + +
    -
    +
    - - -
    +
    -
    -
    +
    + +
    -

    Notes - Tag +

    NotesTag

    -
    -

    2018

    -
    + -
    + - - -
    + + + + + -
    + + + + + -
    + + + + + -
    + + + + + -
    + + + + + -
    + + + + + -
    +
    @@ -415,7 +548,7 @@

    @@ -423,179 +556,186 @@

    - + +
    + -
    -
    +
    + + +
    + + +
    + + + +
    - - - - + - - - @@ -603,22 +743,117 @@

    + + + + + + + + - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/tags/Notes/page/2/index.html b/tags/Notes/page/2/index.html index 2393c30a..a74d343e 100644 --- a/tags/Notes/page/2/index.html +++ b/tags/Notes/page/2/index.html @@ -1,47 +1,128 @@ - + + + + + + + - - + + + - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + - + - - - + + + + + + + + + + + + - - Tag: Notes | Liam's Blog @@ -51,363 +132,415 @@ - - -
    -
    + -
    -
    - + + + + + +
    -
    +
    - - -
    +
    -
    -
    +
    + +
    -

    Notes - Tag +

    NotesTag

    -
    -

    2018

    -
    + -
    + - - -
    + + + + + -
    + + + + + -
    + + + + + -
    + + + + + -
    + + + + + -
    + + + + + -
    +
    @@ -415,7 +548,7 @@

    @@ -423,179 +556,186 @@

    - + +
    + -
    -
    +
    + + +
    + + +
    + + + +
    - - - - + - - - @@ -603,22 +743,117 @@

    + + + + + + + + - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/tags/Notes/page/3/index.html b/tags/Notes/page/3/index.html index 5369cfe5..dbc21d32 100644 --- a/tags/Notes/page/3/index.html +++ b/tags/Notes/page/3/index.html @@ -1,47 +1,128 @@ - + + + + + + + - - + + + - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + - + - - - + + + + + + + + + + + + - - Tag: Notes | Liam's Blog @@ -51,223 +132,191 @@ - - -
    -
    + -
    -
    - + + + + + +
    -
    +
    - - -
    +
    -
    -
    +
    + +
    -

    Notes - Tag +

    NotesTag

    -
    -

    2018

    -
    + -
    +
    @@ -275,7 +324,7 @@

    @@ -283,202 +332,304 @@

    - + +
    + -
    -
    +
    + + +
    + + +
    + + + +
    - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + - + - + + - + + + + + - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/tags/Python/index.html b/tags/Python/index.html deleted file mode 100644 index a5dc03c1..00000000 --- a/tags/Python/index.html +++ /dev/null @@ -1,445 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - Tag: Python | Liam's Blog - - - - - - - - - - - - -
    -
    - -
    -
    - - -
    - - -

    If history repeats itself, and the unexpected always happens, how incapable must Man be of learning from experience?—George Bernard Shaw.

    -
    - - -
    - - - - -
    -
    - - -
    - - 0% -
    - - - - -
    -
    -
    - - -
    - - - - - -
    -
    -
    -

    Python - Tag -

    -
    - - -
    -

    2018

    -
    - - - -
    -
    - - - - - - - - -
    - - - - -
    - - - - - - - - -
    -
    - -
    - -
    -
    - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/tags/STL/index.html b/tags/STL/index.html index 1b9950c4..9bea124c 100644 --- a/tags/STL/index.html +++ b/tags/STL/index.html @@ -1,47 +1,128 @@ - + + + + + + + - - + + + - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + - + - - - + + + + + + + + + + + + - - Tag: STL | Liam's Blog @@ -51,183 +132,163 @@ - - -
    -
    + -
    -
    - + + + + + +
    -
    +
    - - -
    +
    -
    -
    +
    + + +
    @@ -236,207 +297,308 @@

    -

    - + +
    + -
    -
    +
    + + +
    + + +
    + + + +
    - - - - + + + + + + + + + + + + + - - - + + + + + + + + + + + + + + + + - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/tags/financial-modelling/index.html b/tags/financial-modelling/index.html index b9eed303..efa1eab1 100644 --- a/tags/financial-modelling/index.html +++ b/tags/financial-modelling/index.html @@ -1,47 +1,128 @@ - + + + + + + + - - + + + - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + - + - - - + + + + + + + + + + + + - - Tag: financial modelling | Liam's Blog @@ -51,183 +132,163 @@ - - -
    -
    + -
    -
    - + + + + + +
    -
    +
    - - -
    +
    -
    -
    +
    + +
    -

    financial modelling - Tag +

    financial modellingTag

    -
    -

    2018

    -
    + -
    +
    @@ -236,207 +297,308 @@

    -

    - + +
    + -
    -
    +
    + + +
    + + +
    + + + +
    - - - - + + + + + + + + + + + + + - - - + + + + + + + + + + + + + + + + - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/tags/index.html b/tags/index.html index ea428158..dd96037a 100644 --- a/tags/index.html +++ b/tags/index.html @@ -1,215 +1,263 @@ - + + + + + + + - - + + + - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + - + - - - - - + + + + + + + + - - + + - tags | Liam's Blog - - + tags | Liam's Blog + + + - - -
    -
    + -
    -
    - + + + + + +
    +
    -
    + - - -
    - - 0% -
    - - +
    +
    -
    +
    - - - - -
    +
    -
    +
    -
    +
    -

    tags -

    +

    tags

    -
    @@ -217,12 +265,14 @@

    tags
    + + @@ -232,8 +282,6 @@

    tags

    - -

    @@ -242,179 +290,190 @@

    tags

    - + + + + + +
    + -
    -
    +
    + + +
    + + +
    + + + +
    - - - - + - - - @@ -424,21 +483,120 @@

    tags + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +