- Declare methods in the
.hheader file using proper syntax. - Define methods in the
.mimplementation file using proper syntax. - Engage a problem to be solved in code by breaking the approach down into smaller steps.
- Solve each step sequentially in increasing levels of complexity.
- Check your code's behavior periodically with
NSLog()as you progress in your solution. - Call another method in the same file by using the
selfkeyword.
- Memorize some palindromes that you can use to impress your friends.
A palindrome is a word, phrase, or sentence whose letters are exactly mirrored around the center letter. Some examples include:
racecar
Bob
Kanakanak (a city in Alaska)
Aibohphobia (the fear of palindromes)
never odd or even
I prefer pi
Flee to me, remote elf.
Norma is as selfless as I am, Ron.
No sir! Away! A papaya war is on.
The qualification of a palindrome typically ignores spaces, punctuation, and capitalization, judging only the order of the letters themselves. Essentially, when a palindrome is reversed, its letters fall into the same order.
In this code-along we're going to write a method that can judge whether or not a particular string qualifies as a palindrome. We're going to start with judging a single all-lowercase word, and then add complexity to allow the method to handle capitalization, spaces, and punctuation. We'll call this method stringIsPalindrome:.
The easiest way to judge a palindrome is simply to compare it to its reverse. However, there is no string reversal method built into the Objective-C language so we'll have to write this functionality ourselves. We'll accomplish this by writing another method called stringByReversingString: that we can call from within the stringIsPalindrome method by using the self keyword as the recipient object.
Fork and clone this lab.
Open the objc-palindrome-detector.xcodeproj file and navigate to the FISAppDelegate.h header file. You should see the following code:
// FISAppDelegate.h
#import <UIKit/UIKit.h>
@interface FISAppDelegate : UIResponder <UIApplicationDelegate>
@property (strong, nonatomic) UIWindow *window;
/**
* Declare your methods here.
*/
@endDeclare two methods inside the @interface section, between the @property statement and the @end statement:
stringIsPalindrome:which takes oneNSStringargument calledstringand returns aBOOL, andstringByReversingString:which takes oneNSStringargument calledstringand returns anNSString.
After declaring these methods, your FISAppDelegate.h header file should look something like this:
// FISAppDelegate.h
#import <UIKit/UIKit.h>
@interface FISAppDelegate : UIResponder <UIApplicationDelegate>
@property (strong, nonatomic) UIWindow *window;
- (BOOL)stringIsPalindrome:(NSString *)string;
- (NSString *)stringByReversingString:(NSString *)string;
@endGreat!
Now, navigate to the FISAppDelegate.m implementation file. Below the ending curly brace of the application:didFinishLaunchingWithOptions: method, but above the @end statement, use autocomplete to define both methods with default returns:
stringIsPalindrome:shouldreturn NO;, andstringByReversingString:shouldreturn nil;.
Your FISAppDelegate.m file should now look something like this:
// FISAppDelegate.m
#import "FISAppDelegate.h"
@implementation FISAppDelegate
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
// Override point for customization after application launch.
/**
* Write your check code here.
*/
// do not alter
return YES; //
} ///////////////
/**
* Implement your methods here.
*/
- (BOOL)stringIsPalindrome:(NSString *)string {
return NO;
}
- (NSString *)stringByReversingString:(NSString *)string {
return nil;
}
@endBecause the logic of our stringIsPalindrome: method depends on our ability to reverse a string, let's start by writing out the stringByReversingString: method that we declared. Our goal is take whatever string variable is passed into the string argument (also "parameter") and return its exact reverse at the end of the method.
How would we approach this goal with a word or sentence written on paper? Couldn't we simply start at the end of the word or sentence, moving backwards while copying each letter onto a new line? Of course we could!
How could we represent this process in code? Well, we already know that strings are made up of individual characters that are arranged by index. So, could we simply start with the character at the string's highest index and work backwards, copying each character into a new string? Of course we could!
Advanced: There are more efficient ways to reverse a string, but this is perhaps the simplest one to understand.
One possible implementation of this process in Objective-C is written below:
- (NSString *)stringByReversingString:(NSString *)string {
NSString *result = @"";
for (NSUInteger i = [string length]; i > 0; i--) {
NSUInteger index = i - 1;
unichar c = [string characterAtIndex:index];
result = [result stringByAppendingFormat:@"%C", c];
}
return result;
}Let's walk through what this method is doing:
- Creating a new
NSStringvariable calledresultthat will be the container for the reversed string that will get assembled one character at a time. - Declaring a
forloop whose counter begins at the size of the string's length and counts down (decrements) to1(remember that the length will always be one more than the maximum index). - Defines an integer called
indexthat is one less than the current value ofi. This is to solve the off-by-one difference between the value of the string'slengthand the value of its highest index. - Finds the next character (
unichar c) to be copied by using theindexinteger to call thecharacterAtIndex:method on the argumentstring. - Appends that character (
c) to the end of theresultstring. This uses theunicharformat specifier (%C) to interpolate the character variablecinto a string that can be appended normally. - After the loop counts down to
1, theresultstring should be complete, and it is returned at the end of the method.
Once you have a conceptual understanding of this implementation, copy it into your code.
Let's check that our new stringByReversingString: method works by calling it from the application:didFinishLaunchingWithOptions: method and NSLog()ing the return! We can call our new method from within the same file by sending a message to the self keyword.
- Inside
application:didFinishLaunchingWithOptions:, create anNSStringvariable calledpalindromeand set it to the string literal@"palindrome":
NSString *palindrome = @"palindrome";
- Now create another
NSStringvariable calledreversedand use it to capture the return of callingstringByReversingString:onselfwithpalindromesubmitted as the method argument:
NSString *reversed = [self stringByReversingString:palindrome];
- Finally, print
palindromeandreversedto the console to inspect the method:
NSLog(@"%@ : %@", palindrome, reversed);
This should print: palindrome : emordnilap.
Hooray! Our first method works!
Now the we've verified that our string reversal method works, we can get to work on the implementation of our stringIsPalindrome: method.
In our first pass at this method, let's use the simplest case for our first example: an all-lowercase single word such as "racecar". We'll add complexity to our method later. For now, let's just make sure we can get the core of the logic down.
Let's conceptualize the steps we need to take:
- We need to get the reverse of the string we're testing,
- then we can compare the original string to its reverse,
- then we can return the result of that comparison, which will be a
BOOL.
That sounds like a solid plan! Now let's write the implementation:
- Create a new
NSStringvariable calledreverseand use it to capture the return of callingstringByReversingString:onselfwith thestringargument variable submitted as the method argument:
NSString *reverse = [self stringByReversingString:string];
- Create a new
BOOLvariable calledstringIsEqualToReverseand use it to capture the return of calling theisEqualToString:method onstringwithreversesubmitted as the argument:
BOOL stringIsEqualToReverse = [string isEqualToString:reverse];
- Return the
stringIsEqualToReversevariable to end the method implementation:
return stringIsEqualToReverse;
Your method implementation should look something like this:
- (BOOL)stringIsPalindrome:(NSString *)string {
NSString *reverse = [self stringByReversingString:string];
BOOL stringIsEqualToReverse = [string isEqualToString:reverse];
return stringIsEqualToReverse;
}Just like we checked the stringByReversingString: method before, let's use local variables and an NSLog() within the application:didFinishLaunchingWithOptions: method to check that our method can handle the case of "racecar".
- Move up to the
application:didFinishLaunchingWithOptions:method. Create a newNSStringvariable calledracecarand assign it to the string literal@"racecar":
NSString *racecar = @"racecar";
- Now, create a new
BOOLvariable calledracecarIsPalindromeand use it to capture the return of callingstringIsPalindrome:onselfwithracecarsubmitted as the method argument:
BOOL racecarIsPalindrome = [self stringIsPalindrome:racecar];
- Finally,
NSLog()the variables together to view the result:
NSLog(@"%d : %@", racecarIsPalindrome, racecar);
This should print: 1 : racecar, meaning that "racecar" is a palindrome.
Now, let's check a string that isn't a palindrome. Reuse the palindrome string to verify that stringIsPalindrome: returns "NO" for a string that doesn't match its reverse.
- Create a new
BOOLvariable calledpalindromeIsPalindromeand use it to capture the return of callingstringIsPalindrome:onselfwithpalindromesubmitted as the method argument:
BOOL palindromeIsPalindrome = [self stringIsPalindrome:palindrome];
- Now,
NSLog()these variables together to view the result:
NSLog(@"%d : %@", palindromeIsPalindrome, palindrome);
This should print: 0 : palindrome, meaning that "palindrome" is not a palindrome (BOOL ironic = YES;).
Let's improve our stringIsPalindrome: method to allow it to handle uppercase letters in strings. Right now, evaluating the actual palindromes "Bob", "Kanakanak", and "Aibohphobia" will all return "NO" because they contain a capital letter. Verify this by adding these checks into the application:didFinishLaunchingWithOptions: method in the same style as evaluating "racecar":
Note: We suggest you get the practice of typing these lines out manually. Use the code block below as a guide without copy/pasting from it.
// within application:didFinishLaunchingWithOptions:
// single word, with uppercase
NSString *bob = @"Bob";
BOOL bobIsPalindrome = [self stringIsPalindrome:bob];
NSLog(@"%d : %@", bobIsPalindrome, bob);
NSString *kanakanak = @"Kanakanak";
BOOL kanakanakIsPalindrome = [self stringIsPalindrome:kanakanak];
NSLog(@"%d : %@", kanakanakIsPalindrome, kanakanak);
NSString *aibohphobia = @"Aibohphobia";
BOOL aibohphobiaIsPalindrome = [self stringIsPalindrome:aibohphobia];
NSLog(@"%d : %@", aibohphobiaIsPalindrome, aibohphobia);Running the program now should print these three lines with zeroes:
0 : Bob
0 : Kanakanak
0 : Aibohphobia
The implementation of handling these additional cases in stringIsPalindrome: is relatively simple: we just need to lowercase the string argument before we evaluate it, right? Let's try it:
1 — Insert a statement that creates a new NSString variable called lowercase use to capture the return of calling the lowercase method on the string argument. Then refactor the string comparison to evaluate lowercase against reverse:
- (BOOL)stringIsPalindrome:(NSString *)string {
NSString *reverse = [self stringByReversingString:string];
NSString *lowercase = [string lowercaseString];
BOOL stringIsEqualToReverse = [lowercase isEqualToString:reverse];
return stringIsEqualToReverse;
}Now, run the program again. It will still print our three palindromes with zeroes:
0 : Bob
0 : Kanakanak
0 : Aibohphobia
What did we do wrong? Let's add an NSLog() right before the comparison to inspect the strings:
- (BOOL)stringIsPalindrome:(NSString *)string {
NSString *reverse = [self stringByReversingString:string];
NSString *lowercase = [string lowercaseString];
NSLog(@"lowercase: %@, reverse: %@", lowercase, reverse);
BOOL stringIsEqualToReverse = [lowercase isEqualToString:reverse];
return stringIsEqualToReverse;
}Running the program will cause this to print:
lowercase: bob, reverse: boB
0 : Bob
lowercase: kanakanak, reverse: kanakanaK
0 : Kanakanak
lowercase: aibohphobia, reverse: aibohphobiA
0 : Aibohphobia
It looks like we reversed the string before we lowercased it, which means that we're comparing the lowercased version to a reversed version that contains an uppercase letter in it. This causes the comparison to fail.
To fix this, we should be move the statement creating lowercase to above the statement creating the reverse string, and refactor reverse's assignment to use lowercase as its method argument instead of string:
- (BOOL)stringIsPalindrome:(NSString *)string {
NSString *lowercase = [string lowercaseString];
NSString *reverse = [self stringByReversingString:lowercase];
NSLog(@"lowercase: %@, reverse: %@", lowercase, reverse);
BOOL stringIsEqualToReverse = [lowercase isEqualToString:reverse];
return stringIsEqualToReverse;
}Now running the program should be able to handle the checks with our capitalized strings, and print them with 1s:
lowercase: bob, reverse: bob
1 : Bob
lowercase: kanakanak, reverse: kanakanak
1 : Kanakanak
lowercase: aibohphobia, reverse: aibohphobia
1 : Aibohphobia
Awesome! Now we can handle proper nouns properly! Delete the NSLog() from the method before moving on; we don't need it anymore.
Since phrases can qualify as a palindromes, let's improve our stringIsPalindrome: method to evaluate strings that contain spaces. Let's start by adding some new checks to the application:didFinishLaunchingWithOptions: method for the phrases "this is not a palindrome" (as a control check), "never odd or even", and "I prefer pi":
Note: We suggest you get the practice of typing these lines out manually. Use the code block below as a guide without copy/pasting from it.
//within application:didFinishLaunchingWithOptions:
NSString *notAPalindrome = @"this is not a palindrome";
BOOL notAPalindromeIsPalindrome = [self stringIsPalindrome:notAPalindrome];
NSLog(@"%d : %@", notAPalindromeIsPalindrome, notAPalindrome);
NSString *neverOdd = @"never odd or even";
BOOL neverOddIsPalindrome = [self stringIsPalindrome:neverOdd];
NSLog(@"%d : %@", neverOddIsPalindrome, neverOdd);
NSString *iPreferPi = @"I prefer pi";
BOOL iPreferPiIsPalindrome = [self stringIsPalindrome:iPreferPi];
NSLog(@"%d : %@", iPreferPiIsPalindrome, iPreferPi);Running the program should print all three phrases with zeroes:
0 : this is not a palindrome
0 : never odd or even
0 : I prefer pi
Now let's edit our stringIsPalindrome: method again by adding logic at the top that removes the spaces. We can accomplish this by using the stringByReplacingOccurrencesOfString:withString: (say that 10 times fast).
Create a new NSString variable called spaceless and use it to capture the return of calling stringByReplacingOccurrencesOfString:withString: on the string argument variable; the first method argument should be a string literal containing a single space (@" ") and the second method argument should be an empty string literal (@"").
Refactor the rest of the method implementation to use spaceless instead of string (this should only affect one statement):
- (BOOL)stringIsPalindrome:(NSString *)string {
NSString *spaceless = [string stringByReplacingOccurrencesOfString:@" " withString:@""];
NSString *lowercase = [spaceless lowercaseString];
NSString *reverse = [self stringByReversingString:lowercase];
BOOL stringIsEqualToReverse = [lowercase isEqualToString:reverse];
return stringIsEqualToReverse;
}Now let's run the program again. Our palindrome phrases should now print with 1s:
0 : this is not a palindrome
1 : never odd or even
1 : I prefer pi
For our final trick, let's add the functionality to our stringIsPalindrome: method so that can accommodate palindromes that are full sentences. (That means punctuation!) Let's start by adding some checks by utilizing the palindrome sentences "Flee to me, remote elf.", "Norma is as selfless as I am, Ron.", and "No sir! Away! A papaya war is on.":
Note: We suggest you get the practice of typing these lines out manually. Use the code block below as a guide without copy/pasting from it.
//within application:didFinishLaunchingWithOptions:
// full sentences with punctuation and capitalization
NSString *fleeToMe = @"Flee to me, remote elf.";
BOOL fleeToMeIsPalindrome = [self stringIsPalindrome:fleeToMe];
NSLog(@"%d : %@", fleeToMeIsPalindrome, fleeToMe);
NSString *norma = @"Norma is as selfless as I am, Ron.";
BOOL normaIsPalindrome = [self stringIsPalindrome:norma];
NSLog(@"%d : %@", normaIsPalindrome, norma);
NSString *papayaWar = @"No sir! Away! A papaya war is on.";
BOOL papayaWarIsPalindrome = [self stringIsPalindrome:papayaWar];
NSLog(@"%d : %@", papayaWarIsPalindrome, papayaWar);Running the program now should print these sentences with zeroes:
0 : Flee to me, remote elf.
0 : Norma is as selfless as I am, Ron.
0 : No sir! Away! A papaya war is on.
We can remove the punctuation characters from the string argument variable by using stringByReplacingOccurrencesOfString:withString: again. If we collect our punctuation characters into an array, we can iterate over each punctuation string with a for loop, replacing every punctuation character in the string with an empty string instead. Let's do this now at the top of the stringIsPalindrome: method implementation:
- Create a new
NSArraycalledpunctuationsand assign it to an array literal containing the string literals for the six common punctuation characters.,,,!,?,:, and;(period, comma, exclamation point, question mark, colon, and semicolon):
NSArray *punctuations = @[ @".", @",", @"!", @"?", @":", @";" ];
- Create a new
NSStringvariable calledwithoutPunctuationand assign to a copy of thestringargument variable:
NSString *withoutPunctuation = [string copy];
- Start a
forloop that iterates over thepunctuationsarray:
for (NSUInteger i = 0; i < [punctuations count]; i++) {...}
- Within the
forloop, create newNSStringvariable calledpunctuationand assign it to the subscript of thepunctuationsarray using the current value ofi:
NSString *punctuation = punctuations[i];
- Still within the
forloop, reassign thewithoutPunctuationvariable to capture a call of thestringByReplacingOccurrencesOfString:withString:method on thewithoutPunctuationvariable (itself). The first argument should be thepunctuationstring variable, and the second argument should be an empty string literal (@""):
withoutPunctuation = [withoutPunctuation stringByReplacingOccurrencesOfString:punctuation withString:@""];
- Refactor the creation of the
spacelessvariable to usewithoutPunctuationinstead of thestringargument variable:
NSString *spaceless = [withoutPunctuation stringByReplacingOccurrencesOfString:@" " withString:@""];
The stringIsPalindrome: should now look something like this:
- (BOOL)stringIsPalindrome:(NSString *)string {
NSArray *punctuations = @[ @".", @",", @"!", @"?", @":", @";" ];
NSString *withoutPunctuation = [string copy];
for (NSUInteger i = 0; i < [punctuations count]; i++) {
NSString *punctuation = punctuations[i];
withoutPunctuation = [withoutPunctuation stringByReplacingOccurrencesOfString:punctuation withString:@""];
}
NSString *spaceless = [withoutPunctuation stringByReplacingOccurrencesOfString:@" " withString:@""];
NSString *lowercase = [spaceless lowercaseString];
NSString *reverse = [self stringByReversingString:lowercase];
BOOL stringIsEqualToReverse = [lowercase isEqualToString:reverse];
return stringIsEqualToReverse;
}Run the program to see how well our method handles the palindrome sentences. It should print all three of them now with 1s:
1 : Flee to me, remote elf.
1 : Norma is as selfless as I am, Ron.
1 : No sir! Away! A papaya war is on.
Because palindromes are cool (like bowties).