- Declare methods in the
.h
header file using proper syntax. - Define methods in the
.m
implementation 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
self
keyword.
- 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.
*/
@end
Declare two methods inside the @interface
section, between the @property
statement and the @end
statement:
stringIsPalindrome:
which takes oneNSString
argument calledstring
and returns aBOOL
, andstringByReversingString:
which takes oneNSString
argument calledstring
and 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;
@end
Great!
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;
}
@end
Because 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
NSString
variable calledresult
that will be the container for the reversed string that will get assembled one character at a time. - Declaring a
for
loop 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
index
that is one less than the current value ofi
. This is to solve the off-by-one difference between the value of the string'slength
and the value of its highest index. - Finds the next character (
unichar c
) to be copied by using theindex
integer to call thecharacterAtIndex:
method on the argumentstring
. - Appends that character (
c
) to the end of theresult
string. This uses theunichar
format specifier (%C
) to interpolate the character variablec
into a string that can be appended normally. - After the loop counts down to
1
, theresult
string 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 anNSString
variable calledpalindrome
and set it to the string literal@"palindrome"
:
NSString *palindrome = @"palindrome";
- Now create another
NSString
variable calledreversed
and use it to capture the return of callingstringByReversingString:
onself
withpalindrome
submitted as the method argument:
NSString *reversed = [self stringByReversingString:palindrome];
- Finally, print
palindrome
andreversed
to the console to inspect the method:
NSLog(@"%@ : %@", palindrome, reversed);
This should print: palindrome : emordnilap
.
Hooray! Our first method works!
Now that 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
NSString
variable calledreverse
and use it to capture the return of callingstringByReversingString:
onself
with thestring
argument variable submitted as the method argument:
NSString *reverse = [self stringByReversingString:string];
- Create a new
BOOL
variable calledstringIsEqualToReverse
and use it to capture the return of calling theisEqualToString:
method onstring
withreverse
submitted as the argument:
BOOL stringIsEqualToReverse = [string isEqualToString:reverse];
- Return the
stringIsEqualToReverse
variable 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 newNSString
variable calledracecar
and assign it to the string literal@"racecar"
:
NSString *racecar = @"racecar";
- Now, create a new
BOOL
variable calledracecarIsPalindrome
and use it to capture the return of callingstringIsPalindrome:
onself
withracecar
submitted 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
BOOL
variable calledpalindromeIsPalindrome
and use it to capture the return of callingstringIsPalindrome:
onself
withpalindrome
submitted 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 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 1
s:
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 1
s:
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
NSArray
calledpunctuations
and 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
NSString
variable calledwithoutPunctuation
and assign to a copy of thestring
argument variable:
NSString *withoutPunctuation = [string copy];
- Start a
for
loop that iterates over thepunctuations
array:
for (NSUInteger i = 0; i < [punctuations count]; i++) {...}
- Within the
for
loop, create newNSString
variable calledpunctuation
and assign it to the subscript of thepunctuations
array using the current value ofi
:
NSString *punctuation = punctuations[i];
- Still within the
for
loop, reassign thewithoutPunctuation
variable to capture a call of thestringByReplacingOccurrencesOfString:withString:
method on thewithoutPunctuation
variable (itself). The first argument should be thepunctuation
string variable, and the second argument should be an empty string literal (@""
):
withoutPunctuation = [withoutPunctuation stringByReplacingOccurrencesOfString:punctuation withString:@""];
- Refactor the creation of the
spaceless
variable to usewithoutPunctuation
instead of thestring
argument 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 1
s:
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).
View Code-Along: Palindrome Detector on Learn.co and start learning to code for free.
View Code-Along: Palindrome Detector on Learn.co and start learning to code for free.