Добавил:
Опубликованный материал нарушает ваши авторские права? Сообщите нам.
Вуз: Предмет: Файл:

C++ For Mathematicians (2006) [eng]

.pdf
Скачиваний:
211
Добавлен:
16.08.2013
Размер:
31.64 Mб
Скачать

116

C++ for Mathematicians

7.2Designing a primitive Pythagorean triple class

We want to design a C++ class to represent primitive Pythagorean triples. The first question is: What shall we call this class? The most descriptive name is the verbose PrimitivePythagoreanTriple but it would be painful to have to type that repeatedly. On the other extreme, we could call the class PPT, but that would be too cryptic. We settle on a compromise solution: PTriple.

How shall a PTriple store its data? The easiest solution is to save the triple (a,b,c). Although we could omit c because it can be calculated from a and b, the small bit of extra memory used by keeping c also is not a problem. We don’t want to consider (3,4,5) different from any permutation of (3,4,5), so let us agree that a PTriple should hold the three integers in nondecreasing order. The three integers are held in long variables named a, b, and c.

Every class needs a constructor. Based on our discussion, the constructor should process a pair of integers (m,n) to produce the triple (m2 − n2,2mn,m2 + n2). Unfortunately, life is not as simple as assigning

a ← m2 −n2, b ← 2mn, and c ← m2 + n2.

To begin, this may result in a or b negative. Even if both are positive, they may be in the wrong order (we want 0 ≤ a ≤ b). Finally, the three values might not be relatively prime. The only thing of which we can be confident is that m2 + n2 is nonnegative and the largest of the three.

To handle these issues the constructor needs to correct the signs of a and b, put them in their proper order, and divide through by gcd(a,b).

One last worry. In case m = n = 0, the triple constructed would be (0,0,0). Technically, this is not primitive because the entries are not relatively prime. We have two choices: we can allow (0,0,0) to be a valid PTriple or we can forbid it. In either case, the constructor needs to treat this possibility as a special case. In this book, we decide to allow (0,0,0) as a valid PTriple.

We also need to provide a constructor with no arguments. This is what is invoked by a declaration of the form PTriple P;. What Pythagorean triple should this create? In other words, what should be the default primitive Pythagorean triple? There are three natural choices: (0,0,0), (0,1,1), or (3,4,5). The last is the smallest triple in which none of the elements is zero. For better or worse, we decide that (0,0,0) is the default. If you are unhappy with any of these decisions, you can certainly choose to proceed differently.

What methods do we need? We need to know the values held in a, b, and c, so we plan for methods getA(), getB(), and getC() to report those values. We do not want methods that can alter these values because we do not want to allow a user to put a PTriple into an invalid state. If we had a setA method, then P.setA(2); would result in a PTriple that cannot possibly be a Pythagorean triple.

We also want operators to compare PTriples. In addition to the == and != operators, we define a < operator. We need a less-than operator so we can sort PTriples

Pythagorean Triples

117

in an array and remove duplicates. (Remember, our goal is to find all primitive Pythagorean triples with 0 ≤ a ≤ b ≤ c ≤ 1000.) We have some choice as to how to define < for PTriples; we just need to ensure that our definition of < results in

a total order. One simple solution is to order the triples lexicographically. We put (a,b,c) < (a0,b0,c0) provided c < c0, or else c = c0 and b < b0. Of course, if c = c0

and b = b0, then a = a0 and the triples are identical.

Finally, it is useful to define the << operator so we can print PTriples to the computer’s screen.

We are now ready to implement the PTriple class.

7.3Implementation of the PTriple class

As planned, the PTriple class has three private data elements of type long named a, b, and c. The constructors ensure that the following conditions are always met.

0 ≤ a ≤ b ≤ c, gcd(a,b) = 1, a2 + b2 = c2.

( )

The only exception is that we allow (a,b,c) = (0,0,0).

We create the class with two constructors: one that takes no arguments and one that takes two long arguments. The first constructs (0,0,0) and the latter uses the method described at the beginning of the chapter. It is the constructor’s responsibility to make sure that the conditions in ( ) are met. We relegate that to a private helper method named reduce. (Strictly speaking, we do not need a separate procedure for these steps, but we want to illustrate an instance of a private method for a class.)

The public methods for PTriple are getA, getB, getC, and the operators ==, !=, and <. Finally, we have an operator<< procedure that is not a member of the

PTriple class.

We now present the header file PTriple.h. In this case, we have not removed the comments. This header file introduces some new ideas that we discuss after we present the file.

Program 7.1: Header file for the PTriple class.

1 #ifndef PTRIPLE_H

2#define PTRIPLE_H

3 #include <iostream>

4using namespace std;

5

6/**

7* A PTriple represents a reduced Pythagorean triple. That is, a

8* sequence of three nonnegative integers (a,b,c) in nondecreasing

9* order for which aˆ2+bˆ2=cˆ2 and (a,b,c) are relatively prime. The

10* relatively prime requirement means that we only deal with primitive

11* Pythagorean triples. We allow (0,0,0).

12*/

118

C++ for Mathematicians

13class PTriple {

14private:

15/// Shorter leg of the triple

16long a;

17/// Longer leg of the triple

18long b;

19/// Hypotenuse

20long c;

21

22/**

23* This private method makes sure the triple elements are

24* nonnegative, in nondecreasing order, and relatively prime.

25*/

26void reduce();

27

28public:

29/**

30* Default constructor. Makes the triple (0,0,0).

31*/

32PTriple() {

33a = b = c = 0;

34}

35

36/**

37* Construct from a pair of integers. Given integers m and n, we

38* make a Pythagorean triple by taking the legs to be 2mn and

39* mˆ2-nˆ2 and the hypotenuse to be mˆ2+nˆ2. We then make sure the

40* three numbers are nonnegative, in nondecreasing order, and then

41* divide out by their gcd. For example PTriple(2,1) creates the

42* famous (3,4,5) triple.

43*/

44PTriple(long m, long n);

45

46/// What is the shorter leg of this triple?

47long getA() const {

48return a;

49}

50

51/// What is the longer leg of this triple?

52long getB() const {

53return b;

54}

55

56/// What is the hypotenuse of this triple?

57long getC() const {

58return c;

59}

60

61/**

62* Check if this PTriple is less than another. The ordering is

63* lexicographic starting with c, then b. That is, we first compare

64* hypotenuses. If these are equal, then we compare the longer leg.

65* @param that Another PTriple

66* @return true if this PTriple is lexicographically less than that.

67*/

68bool operator<(const PTriple& that) const;

Pythagorean Triples

119

69

70/**

71* Check if this PTriple is equal to another.

72*/

73bool operator==(const PTriple& that) const {

74return ( (a==that.a) && (b==that.b) && (c==that.c) );

75}

76

77/**

78* Check if this PTriple is not equal to another.

79*/

80bool operator!=(const PTriple& that) const {

81return !( (*this) == that );

82}

83};

84

85/// Send a PTriple to an output stream

86ostream& operator<<(ostream& os, const PTriple& PT);

87

88 #endif

The class declaration begins on line 13. Within the private section we see the three long variables that hold the triple’s data. Thinking of a right triangle with side lengths a,b,c, we call a the shorter leg, b the longer leg, and c the hypotenuse.

Also within the private section is the reduce() method. This is a method invoked by the two-argument constructor PTriple(m,n); we discuss this in more detail below.

The public section begins on line 28 beginning with the two constructors. The zero-argument constructor PTriple() is not declared in the way we expect. Normally, a procedure declaration does not include the procedure’s code. All we expect is this:

PTriple();

Instead, we see what we would normally expect to find in the .cc file.

PTriple() {

a = b = c = 0;

}

This is a special feature of C++. Procedures may be defined in header files and are known as inline procedures. An inline procedure combines the declaration and definition of a procedure at a single location in the header file.

It is possible to make any procedure into an inline procedure. The keyword inline is used to announce this fact. However, for procedures that are members of classes (i.e., methods), the keyword is optional. For all other procedures (i.e., ordinary procedures that are not class members), the inline keyword is mandatory.

When should you use inline procedures as opposed to the usual technique of a declaration in the .h file and definition in the .cc file? If the method’s code is only a line or two, then it is a good idea to write it as an inline procedure. Several of PTriple’s methods are extremely short and we write those as inline procedures as

120

C++ for Mathematicians

well. Others (such as the two-argument constructor or the operator<) are more involved, so we use the usual technique for those.

There are advantages and disadvantages to writing inline code. It is convenient to write the code for a method in the same file where it is defined. The C++ compiler produces more efficient code from procedures that are written inline. (However, smart C++ compilers can figure out when it is worthwhile to convert an ordinary procedure into an inline procedure.) However, the object code for programs that use inline procedures takes up a bit more disk space. Also, it takes a bit more time to compile programs with inline procedures.

Sometimes it is mandatory to write methods as inline procedures. We explain that when the time comes (Chapter 12).

The procedures that are not specified by inline code in the header file are defined in the file PTriple.cc that we present next.

Program 7.2: Program file for the PTriple class.

1 #include "PTriple.h"

2#include "gcd.h"

3

4 PTriple::PTriple(long m, long n) {

5a = 2*m*n;

6 b = m*m - n*n; 7 c = m*m + n*n;

8

9 reduce();

10 }

11

12void PTriple::reduce() {

13// Nothing to do if a=b=c=0

14if ((a==0) && (b==0) && (c==0)) return;

15

16// Make sure a,b are nonnegative (c must be)

17if (a<0) a = -a;

18if (b<0) b = -b;

19

20// Make sure a <= b

21if (a>b) {

22long tmp = a;

23a = b;

24b = tmp;

25}

26

27// Make sure a,b,c are relatively prime

28long d = gcd(a,b);

29a /= d;

30b /= d;

31c /= d;

32}

33

34bool PTriple::operator<(const PTriple& that) const {

35if (c < that.c) return true;

36if (c > that.c) return false;

Pythagorean Triples

121

37

38if (b < that.b) return true;

39return false;

40}

41

42ostream& operator<<(ostream& os, const PTriple& PT) {

43os << "(" << PT.getA() << "," << PT.getB() << ","

44<< PT.getC() << ")";

45return os;

46}

The code for the constructor PTriple(m,n) is broken into two parts. Given the input integers m,n we set

a ← 2mn, b ← m2 −n2, and c ← m2 + n2.

Then we need to fix a few things up. We need to make sure that a and b are nonnegative, that a ≤ b, and that gcd(a,b,c) = 1. We relegate these chores to the private reduce method (which is invoked by the PTriple constructor on line 9).

The code for the < operator follows. This operator is used to compare a given PTriple with another. If R and P are variables of type PTriple, the expression R<P causes the operator< procedure belonging to R to execute.

We first compare the c values of R and P; this happens on lines 35–36. The first comparison is c < that.c. The first c is the hypotenuse of the left-hand argument of < (i.e., R). The unadorned c refers to the c for the object on which the method was invoked. The c of the right-hand argument needs to be specified, so we must refer to it as that.c because the right-hand argument is named that. Because P is passed by reference (required for operators) to <, that.c is exactly the same variable as

P.c.

In short: When R<P is encountered, the unadorned c on line 35 is the c data member of R and the that.c on line 35 is the c data member of P.

7.4Finding and sorting the triples

Our goal is to find all primitive Pythagorean triples (a,b,c) with 0 ≤ a ≤ b ≤ c ≤ 1000. Our strategy is this.

First, we create an array of PTriples by calling PTriple(m,n) over a suitably large range of m and n. Because every primitive Pythagorean triple can be

obtained from (m2 −n2,2mn,m2 + n2) we do not need to consider values of m or n greater than 1000.

The upper bound of 1000 is, of course, arbitrary. Our design permits an arbitrary upper bound of N.

122

C++ for Mathematicians

As the Pythagorean triples are created, we save them in an array. Because the upper bound on a,b,c is arbitrary (N is specified by user input), the array we create needs to be dynamically allocated.

How big should this array be? In the worst-case scenario, all of the Pythagorean triples we create might be primitive and different from one another. (This is

actually a gross overestimate, but serves our purposes.) Because we iterate

over all m and n with 1 ≤ m,n ≤ N, an array that can accommodate N values is large enough.

Once the array is populated, we sort it. It was in anticipation of the need to sort the array that we defined the < operator.

Finally, we read through the array printing out unique elements.

Here is the program that does all those steps; we explain the key features after we present the code.

Program 7.3: A program to find Pythagorean triples.

1 #include "PTriple.h"

2 #include <iostream>

3#include <cmath>

4using namespace std;

5/**

6* Find all primitive Pythagorean triples (a,b,c) with 0 <= a <= b <=

7* c <= N where N is specified by the user.

8

*/

 

9

 

 

10

int main() {

 

11

PTriple* table;

// table to hold the triples

12

long N;

// upper bound on triples.

13

 

 

14// Ask the user for N

15cout << "Please enter N (upper bound on triples) --> ";

16cin >> N;

17if (N <= 0) return 0; // nothing to do when N isn’t positive

18

19// Allocate space for the table

20table = new PTriple[N];

21

22// Populate the table with all possible PTriples

23long idx = 0; // index into the table

24long rootN = long( sqrt(double(N)) );

25

26for (long m=1; m<=rootN; m++) {

27for (long n=1; n<=rootN; n++) {

28PTriple P = PTriple(m,n);

29if (P.getC() <= N) {

30table[idx] = P;

31idx++;

32}

33}

34}

Pythagorean Triples

123

35

36// Sort the table

37sort(table, table+idx);

38

39// Print out nonduplicate elements of the table

40cout << table[0] << endl;

41for (int k=1; k<idx; k++) {

42if (table[k] != table[k-1]) {

43cout << table[k] << endl;

44}

45}

46

47// Release memory held by the table

48delete[] table;

49return 0;

50}

Note: Some compilers may require #include <algorithm> in order to use the sort procedure (line 37).

Now for the analysis of the code.

We need a table to hold the PTriples and so we declare a variable table of type PTriple* (pointer to a PTriple, i.e., the head of an array). Because we do not know how large this array needs to be, we cannot declare it with a statement of the form PTriple table[1000];.

Lines 15–17 ask the user to give us an integer N. If the user gives us an integer that is less than 1, we end the program.

Line 20 allocates space for table.

The variable idx, declared on line 23, is used to access elements in the array table.

The variable rootN is set to N . The syntax, unfortunately, is awkward. The C++ sqrt procedure (which may require #include <cmath>) takes and returns values of type double. So we first need to cast the value of N into a double, calculate its square root, and then cast back to type long. When a double is converted into a long, the digits to the right of the decimal place

are discarded. Therefore, line 24 computes the desired N .

• Lines 26–34 run through all possible values of m,n with 1 ≤ m,n ≤ N. For each pair, we generate a primitive Pythagorean triple. If its c-value is no larger than N (line 29) we insert its value in the table and increment idx. Therefore, once we finish this double for-loop, the variable idx holds the number of entries we made into the array table.

Sorting of the table takes place on line 37. The C++ sort procedure can be used on any array of elements provided the type of those elements can be

124

C++ for Mathematicians

compared with a < operator. Thus, sort can be used to sort an array of longs or doubles. In order to sort an array of PTriples, we just need to provide a < operator.

The sort procedure may require the algorithm header.

The sorting is invoked with an unusual syntax: sort(table, table+idx);. The first argument, table, makes sense. The sort procedure needs to know what array to sort. It’s the second argument that is difficult to understand.

The second argument to sort needs to be a pointer to the location of the first element beyond the end of the array. In other words, if the array holds five elements (indexed 0 through 4), then the second argument to sort needs to be a pointer to the nonexistent sixth element of the array (index 5).

More generally, sort can be used to sort a contiguous block of elements of an array. The first argument to sort should be a pointer to the start of the block and the second argument should be a pointer to the first element after the end of the block.

We know that table is a pointer to the first element of the array table. That is, table holds the memory address of table[0]. In C++, table+1 evaluates to the address in memory that holds table[1]. So table+idx is the memory location holding table[idx]. However, at this point in our program, idx holds a number one greater than the location where we last placed a PTriple. This is what sort wants and so this is what we do.

Bottom line: To sort an array named, say, table that contains, say, 25 elements, the statement we use is this: sort(table, table+25);. It’s OK to forget all this discussion about pointers and just remember this:

sort( table_name, table_name + number_of_elements);

Lines 40–45 step through the elements of table. If the current element is different from the previous one, we print it.

Finally, we release the memory allocated to table on line 48. Strictly speaking, this isn’t necessary because we are at the end of the program; the memory would be reclaimed automatically. Nevertheless, it is a good habit to be sure every new is matched with a delete[].

Here is what we see when the program is run (for N = 100).

 

Please enter N (upper bound on triples) --> 100 (0,1,1)

(3,4,5)

(5,12,13)

(8,15,17)

(7,24,25)

(20,21,29)

(12,35,37)

(9,40,41)

 

Pythagorean Triples

125

 

 

 

 

 

 

(28,45,53)

 

 

 

(11,60,61)

 

 

 

(33,56,65)

 

 

 

(16,63,65)

 

 

 

(48,55,73)

 

 

 

(36,77,85)

 

 

 

(13,84,85)

 

 

 

(39,80,89)

 

 

 

(65,72,97)

 

 

 

 

We see that there are 17 primitive Pythagorean triples with 0 ≤ a ≤ b ≤ c ≤ 100. This implies that the array we created, table, used more memory than we really needed.

Let’s complain about this program.

It wastes memory. This is not a terrible problem because holding hundreds, or even hundreds of thousands of Pythagorean triples is well within the capacity of even the most modest computers. Still, we may encounter other situations in which we need to be careful about the amount of memory we use.

It was annoying that we needed to figure out how much memory to set aside in table. It would be much easier if the table could adjust its size to suit our needs, rather than requiring us to figure out how big to make it.

The call to sort is still bugging me. Adding an object of type PTriple* and an object of type long just seems wrong. (It isn’t wrong, but it is confusing.)

We deal with all these complaints in the next chapter.

7.5Exercises

7.1Create an Interval class to represent closed intervals on the real line: [a,b] = {x R : a ≤ x ≤ b}. Implement this class entirely within an Interval.h file and without any code file. To do this, you will need to make all methods and procedures inline.

The class should include the following features.

(a)Two constructors: a zero-argument constructor that creates a default interval (say, [0,1]) and a two-argument interval that creates the interval with the specified end points. In response to either Interval(3,4) or Interval(4,3), the interval [3,4] should be constructed.

(b)Get methods to reveal the end points of the interval.

(c)Comparison operators for equality (==) and inequality (!=).