PDA

View Full Version : Kochan 2.0 Exercise 11-3




mdeh
Jan 25, 2009, 07:44 PM
I thought this was quite difficult, but not sure if I did what I was supposed to do.

Anyway, here is my solution.


Your (kind) feedback would be appreciated.

Comparison.h
//
// Comparison.h
// Exercise_2.0_11_2

// Copyright 2009 __MyCompanyName__. All rights reserved.
//

#import "Fraction.h"


@interface Fraction (Comparison)

/*-(BOOL) isEqualTo : (Fraction *) f; */
-(int) compare: (Fraction *) f;

@end



Comparison.m

//
// Comparison.m
// Exercise_2.0_11_2
//

// Copyright 2009 __MyCompanyName__. All rights reserved.
//

#import "Comparison.h"


@implementation Fraction (Comparison)

/*-(BOOL) isEqualTo : (id) f
{

Fraction *a = [ [ Fraction alloc] initWith: numerator over: denominator];
Fraction *b = [ [ Fraction alloc] initWith: [f numerator] over: [f denominator] ];
BOOL sameNess = NO;

[a reduce];
[b reduce];



if ( a.numerator == b.numerator && a.denominator == b.denominator)
sameNess = YES;

[a release];
[b release];

return sameNess;
}
*/
-(int) compare: (Fraction *) f
{
Fraction *a = [ [ Fraction alloc] initWith: numerator over: denominator];
double f1, f2;

f1= [a convertToNum];
f2 = [ f convertToNum];

[a release];

if ( f1 < f2)
return -1;
else if (f1 > f2)
return 1;
else
return 0;


}



@end


Fraction.h

//
// Fraction.h
// Chap 7 Prgm 7.1

// Copyright 2009 __MyCompanyName__. All rights reserved.
//

#import <Foundation/Foundation.h>


@interface Fraction : NSObject {
int numerator;
int denominator;
}

@property int numerator, denominator;

/* implement informal protocol */

- (BOOL)isEqualTo:(id)object;
- (BOOL)isLessThanOrEqualTo:(id)object;
- (BOOL)isLessThan:(id)object;
- (BOOL)isGreaterThanOrEqualTo:(id)object;
- (BOOL)isGreaterThan:(id)object;
- (BOOL)isNotEqualTo:(id)object;




+(Fraction *) allocF;
+ (int) count;
+ (int) gAddMethodUsageCounter;
static int gCount;
static int gAddMethodUsageCounter;

/** class initialization **/

- initWith: (int) n over: (int) d;

-(void) reduce;

/* modified accessor*/
-(void) setTo: (int) n over: (int) d;


/*utilities*/

-(double) convertToNum;

/* basic utility */

/*********SPECIFIC TO EXERCISE 7-5 ***************/
-(void) print;

/*********SPECIFIC TO EXERCISE 7-5 ***************/
-(void) printReduced: (BOOL) reduce;


@end



Fraction.m

//
// Fraction.m
// Chap 7 Prgm 7.1

// Copyright 2009 __MyCompanyName__. All rights reserved.
//

#import "Fraction.h"
#import "Comparison.h"


@implementation Fraction;

@synthesize denominator, numerator;

+ (int) gAddMethodUsageCounter
{
return gAddMethodUsageCounter;
}


+(Fraction *) allocF{
extern int gCount;
++gCount;

return [Fraction alloc];
}

+ (int) count
{
extern int gCount;
return gCount;
}

- (BOOL)isEqualTo:(id) f
{

if ( ! self)
return NO;
Fraction *a = [ [ Fraction alloc] initWith: numerator over: denominator];
Fraction *b = [ [ Fraction alloc] initWith: [f numerator] over: [f denominator] ];
BOOL sameNess = NO;

[a reduce];
[b reduce];



if ( a.numerator == b.numerator && a.denominator == b.denominator)
sameNess = YES;

[a release];
[b release];

return sameNess;

}

// Implemented using isEqual:. Returns NO if receiver is nil.
- (BOOL)isLessThanOrEqualTo:(id) f
{
if ( !self)
return NO;

if ( [ self compare: f] == -1 || [ self isEqualTo: f])
return YES;
else
return NO;
}

// Implemented using compare. Returns NO if receiver is nil.
- (BOOL)isLessThan:(id) f
{
if ( !self)
return NO;

if ( [ self compare: f] == -1 )
return YES;
else
return NO;
}

// Implemented using compare. Returns NO if receiver is nil.
- (BOOL)isGreaterThanOrEqualTo:(id) f
{
if ( !self)
return NO;

if ( [ self compare: f] == 1 || [ self isEqualTo: f] )
return YES;
else
return NO;
}

// Implemented using compare. Returns NO if receiver is nil.
- (BOOL)isGreaterThan:(id) f
{
if ( !self)
return NO;

if ( [ self compare: f] == 1 )
return YES;
else
return NO;
}

// Implemented using compare. Returns NO if receiver is nil.
- (BOOL)isNotEqualTo:(id) f
{
if ( !self)
return NO;

if ( ! [ self isEqualTo: f] )
return YES;
else
return NO;

}

// Implemented using compare. Returns NO if receiver is nil.

-(void) print
{

NSLog(@"%i/%i ", numerator, denominator);
}

- initWith: (int) n over: (int) d
{
if ( ! ( self = [super init]))
return nil;

if (self)
[self setTo: n over: d];

return self;

}

-(double) convertToNum
{
if (denominator != 0)
return (double) numerator / denominator;
else
return 1.0;
}



/*********SPECIFIC TO EXERCISE 7-5 ***************

-(void) print
{
float f = 0.00;

if ( (f = (float) numerator / denominator) > 1)

NSLog(@"%i %i/%i", numerator/denominator, numerator % denominator, denominator);

else

NSLog(@"%i/%i ", numerator, denominator);
}

*********SPECIFIC TO EXERCISE 7-5 ***************/

-(void) printReduced: (BOOL) reduce
{
if (reduce == YES){
Fraction *myF = [ [ Fraction alloc] init];
myF.numerator = numerator;
myF.denominator = denominator;
[myF reduce];
NSLog(@"%i/%i", myF.numerator, myF.denominator);
[myF release];
}
else
NSLog(@"%i/%i ", numerator, denominator);


}



-(void) setTo: (int) n over: (int) d
{
numerator = n;
denominator = d;
}



/*
-(Fraction *) add: (Fraction *) f
{
int resultNum, resultDenom;
Fraction* result = [[Fraction alloc] init];

resultNum = numerator * f.denominator + denominator * f.numerator;
resultDenom = denominator * f.denominator;

[result setTo: resultNum over: resultDenom];
[result reduce];
return result;
}

*/

-(void) reduce
{
int u, v, temp;
BOOL isNegativeNumerator = NO;
BOOL isNegativeDenominator = NO;

if ( numerator < 0)
{
isNegativeNumerator = YES;
numerator = -numerator;
}


if ( denominator < 0)
{
isNegativeDenominator = YES;
denominator = -denominator;
}

u = numerator;
v = denominator;


while (v != 0) {
temp = u % v;
u = v;
v = temp;
}



numerator /= u;
denominator /= u;



if ( isNegativeNumerator == YES || isNegativeDenominator == YES)
numerator = -numerator;

}

@end



main.h

//
// Prefix header for all source files of the 'Exercise_2.0_11_2' target in the 'Exercise_2.0_11_2' project.
//

#ifdef __OBJC__
#import <Foundation/Foundation.h>
#endif



main.m
/*#import "MathOps.h"*/
#import "Fraction.h"
#import "Comparison.h"

int main (int argc, const char * argv[]) {
NSAutoreleasePool * pool = [[NSAutoreleasePool alloc] init];
Fraction *a = [[Fraction alloc] initWith: 7 over: 8];
Fraction *b = [ [ Fraction alloc] initWith: 14 over: 16];
Fraction *c = [ [ Fraction alloc] initWith: 3 over: 4];

if ( [ a isEqualTo: b] == YES)
{
[a print];
NSLog(@"is Equal to");
[b print];
}

NSLog(@"\n");

if ( [ a isLessThanOrEqualTo: b] == YES)
{
[a print];
NSLog(@"is Less than or Equal to");
[b print];
}

NSLog(@"\n");

if ( [ c isLessThanOrEqualTo: a] == YES)
{
[c print];
NSLog(@"is Less than or Equal to");
[a print];
}

NSLog(@"\n");

if ( [ c isGreaterThanOrEqualTo: a] == YES)
{
[c print];
NSLog(@"is greater than or Equal to");
[a print];
}

NSLog(@"\n");

if ( [ c isLessThan: a] == YES)
{
[c print];
NSLog(@"is Less than");
[a print];
}

NSLog(@"\n");

if ( [ a isGreaterThanOrEqualTo: c] == YES)
{
[a print];
NSLog(@"is greater than or Equal to");
[c print];
}

NSLog(@"\n");

if ( [ a isGreaterThan: c] == YES)
{
[a print];
NSLog(@"is greater than");
[c print];
}

NSLog(@"\n");

if ( [ a isNotEqualTo: c] == YES)
{
[a print];
NSLog(@"is not equal to");
[c print];
}

[a release];
[b release];
[c release];
[pool drain];
return 0;
}



lee1210
Jan 25, 2009, 09:27 PM
I have a few questions and suggestions. One question is that isEqualTo: seems to be implemented twice, in the same way. Why?

Second is that compare should be the only thing that needs to "compute". isEqualTo: can easily be implemented using compare: == 0. All of the other functions can be very easily performed using a single compare: call. For example, greaterThanOrEqualTo: can be done by checking the return of compare: != -1.

Third, why do you return 1.0 in convertToNum when the denominator is 0? The denominator being 0 means that the value is undefined. The proper return would be NaN, a macro for which is available in math.h when using GCC.
http://www.delorie.com/gnu/docs/glibc/libc_407.html

Fourth, while it is not critical, expensive operations are being performed in compare, and it will be used a lot. As such, it might be better to do some integer math instead of double precision floating point math. Currently you're doing a single double FP divide in convertToNum, so compare: requires 2 of these. You can accomplish the same thing with two integer operations... something like:
int numA = numerator * [f denominator];
int numB = [f numerator] * denominator;


Now you have the numerators if you were to multiply through by the denominators to get a common denominator. you've done two integer multiplications instead of 2 double precision FP divides, which is much cheaper. Now you can compare numA and numB like you compare f1 and f2 now. If you want to, you can cast this to long int so you're sure not to overflow, depending on the requirements. This also avoids instantiating a new Fraction object, which i really wasn't sure about the reasoning for. It seems like you instantiate one to call convertToNum... which, if that was your desire, you should be able to just invoke on self instead.

Otherwise I am a bit confused about the use of the category on fraction to add functionality, but then using that functionality in Fraction.m. It seems that either everything should go in main.m, or all comparison functions should be in the category. It seems like the category not being there should not result in things implemented in the main class definition no longer working. It should simply extend functionality. I would move all of the greaterThan, lessThan, etc. functions into the category if use of a category was needed.

I don't know exactly what the exercise is, but those are the things that I noticed on a read-through.

-Lee

mdeh
Jan 26, 2009, 07:18 AM
Firstly, thanks for looking over it.
The actual exercise says:
Extend the Fraction class by adding methods that conform to the informal protocol NSComparisonMethods....and implement the first 6 methods...(is=), (is<||==),(is<), (is>||==), (is>),(is!=) and test them. {Excuse the short cuts}


I have a few questions and suggestions. One question is that isEqualTo: seems to be implemented twice, in the same way. Why?

Hmmm..... Not sure if I can find that.

Second is that compare should be the only thing that needs to "compute". isEqualTo: can easily be implemented using compare: == 0. All of the other functions can be very easily performed using a single compare: call. For example, greaterThanOrEqualTo: can be done by checking the return of compare: != -1.

These are great suggestions. The reason I implemented isEqualTo: so directly was that it was part of the protocol...but your point of using "Compare" makes much greater sense.

Third, why do you return 1.0 in convertToNum when the denominator is 0? The denominator being 0 means that the value is undefined. The proper return would be NaN, a macro for which is available in math.h when using GCC.
http://www.delorie.com/gnu/docs/glibc/libc_407.html

That was from an earlier exercise, demonstrating, AFAIR, the if-else statement. Your point about NAN is well taken.

Fourth, while it is not critical, expensive operations are being performed in compare, and it will be used a lot. As such, it might be better to do some integer math instead of double precision floating point math. Currently you're doing a single double FP divide in convertToNum, so compare: requires 2 of these. You can accomplish the same thing with two integer operations... something like:
int numA = numerator * [f denominator];
int numB = [f numerator] * denominator;


Now you have the numerators if you were to multiply through by the denominators to get a common denominator. you've done two integer multiplications instead of 2 double precision FP divides, which is much cheaper. Now you can compare numA and numB like you compare f1 and f2 now. If you want to, you can cast this to long int so you're sure not to overflow, depending on the requirements. This also avoids instantiating a new Fraction object, which i really wasn't sure about the reasoning for. It seems like you instantiate one to call convertToNum... which, if that was your desire, you should be able to just invoke on self instead.

Otherwise I am a bit confused about the use of the category on fraction

The use of categories was just an exercise to demonstrate it's use, I believe. As to the advice about the "expense" of the calculations, thank you. I will implement those changes.

Now, one last question. I am assuming that the general gist of the answer is correct...ie...an informal protocol expects, me, the programmer to implement the actual code. The reason I ask this is that if one "Jump(s) to definition" for the definition of, for example, "isEqualTo:", one is directed to
this line:

@interface NSObject (NSComparisonMethods)
- (BOOL)isEqualTo:(id)object;
// Implemented using isEqual:. Returns NO if receiver is nil.

The confusing thing to me is the comment. It seems to imply that one should use a method called "isEqual:". Now again, is the programmer expected to implement "isEqual:" or does this exist somewhere? Searching for isEqual: yields this
@protocol NSObject

- (BOOL)isEqual:(id)object;

Now, the book has not gotten to the use of the libraries yet, so I am assuming not, but, I would be interested in hearing your views on this.
Once again...thanks



Changes as per your suggestion:

- (BOOL)isLessThanOrEqualTo:(id) f
{
if ( !self)
return NO;

if ( [ self compare: f] != 1 )
return YES;
else
return NO;
}


and compare


-(int) compare: (Fraction *) f
{

int recNumerator, argNumerator;

recNumerator = numerator * f.denominator;
argNumerator = f.numerator * denominator;

if ( recNumerator < argNumerator)
return -1;
else if (recNumerator > argNumerator)
return 1;
else
return 0;
}

lee1210
Jan 26, 2009, 01:57 PM
Firstly, thanks for looking over it.


No problem.


Hmmm..... Not sure if I can find that.

My mistake, the copy in Comparison.m is commented out. Block comments + no syntax highlighting = confusion.


<snip>
Now, one last question. I am assuming that the general gist of the answer is correct...ie...an informal protocol expects, me, the programmer to implement the actual code. The reason I ask this is that if one "Jump(s) to definition" for the definition of, for example, "isEqualTo:", one is directed to
this line:

@interface NSObject (NSComparisonMethods)
- (BOOL)isEqualTo:(id)object;
// Implemented using isEqual:. Returns NO if receiver is nil.

The confusing thing to me is the comment. It seems to imply that one should use a method called "isEqual:". Now again, is the programmer expected to implement "isEqual:" or does this exist somewhere? Searching for isEqual: yields this
@protocol NSObject

- (BOOL)isEqual:(id)object;

Now, the book has not gotten to the use of the libraries yet, so I am assuming not, but, I would be interested in hearing your views on this.
Once again...thanks


Those are just showing you the headers where these are defined for NSObject. It has a basic implementation of these methods in case you choose not to implement them yourself. In this case, NSObject has a supporting method that is used in the implementation of isEqualTo:, but isEqual: is not part of the protocol, so you do not have to implement this.


Changes as per your suggestion:

- (BOOL)isLessThanOrEqualTo:(id) f
{
if ( !self)
return NO;

if ( [ self compare: f] != 1 )
return YES;
else
return NO;
}


and compare


-(int) compare: (Fraction *) f
{

int recNumerator, argNumerator;

recNumerator = numerator * f.denominator;
argNumerator = f.numerator * denominator;

if ( recNumerator < argNumerator)
return -1;
else if (recNumerator > argNumerator)
return 1;
else
return 0;
}


Those look good to me. I'm not sure how much error checking you're supposed to do, and if you've gotten to reflection/introspection yet. In general it's good to check non-primitive arguments for nullity/nil-ity, and if the type is id, ensuring that the argument isMemberOfClass: that you are expecting. That's to say:
if(![f isMemberOfClass: [Fraction class]]) return NO;

One other thing i noticed is it seems that numerator and denominator can be signed. It seems like some of your routines would be simpler if you always figured out the sign of the whole number, and choose either the numerator or denominator will carry the sign (I would pick the numerator). It's pretty easy to check this when the numerator and denominator are set, just something like:
if(numerator < 0 ^ denominator < 0) {
numerator = -1 * abs(numerator);
denominator = abs(denominator);
} else {
numerator = abs(numerator);
denominator = abs(denominator);
}

This might over-compute a bit, but it's a fairly even tradeoff in my mind to do simple computations unconditionally vs. adding a bunch of conditions and perhaps doing the computations anyway. This should simplify reduce, which i found an error in. You should take the xor (^) of the two isNegative... values if you are going to do it this way... because currently if both are negative the result should be positive, but it will be negative. Unless there is something elsewhere i missed, it doesn't seem like these are mutually exclusive.

-Lee

mdeh
Jan 26, 2009, 02:22 PM
Lee,
thank you again for all the time spent on this. Much appreciated.