Skip to content

Commit ecd560f

Browse files
authored
Merge pull request kodecocodes#328 from lostatseajoshua/bug-fix-palindromes
Bug fix palindromes
2 parents 1d7ae6b + f618414 commit ecd560f

File tree

10 files changed

+647
-71
lines changed

10 files changed

+647
-71
lines changed

.travis.yml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -41,3 +41,4 @@ script:
4141
- xcodebuild test -project ./Stack/Tests/Tests.xcodeproj -scheme Tests
4242
- xcodebuild test -project ./Topological\ Sort/Tests/Tests.xcodeproj -scheme Tests
4343
- xcodebuild test -project ./Treap/Treap/Treap.xcodeproj -scheme Tests
44+
- xcodebuild test -project ./Palindromes/Test/Test.xcodeproj -scheme Test
Lines changed: 54 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -1,34 +1,61 @@
1-
import Cocoa
1+
//: Playground - noun: a place where people can play
22

3-
public func palindromeCheck(text: String?) -> Bool {
4-
if let text = text {
5-
let mutableText = text.trimmingCharacters(in: NSCharacterSet.whitespaces).lowercased()
6-
let length: Int = mutableText.characters.count
3+
import Foundation
4+
5+
/**
6+
Validate that a string is a plaindrome
7+
- parameter str: The string to validate
8+
- returns: `true` if string is plaindrome, `false` if string is not
9+
*/
10+
func isPalindrome(_ str: String) -> Bool {
11+
let strippedString = str.replacingOccurrences(of: "\\W", with: "", options: .regularExpression, range: nil)
12+
let length = strippedString.characters.count
713

8-
if length == 1 || length == 0 {
9-
return true
10-
} else if mutableText[mutableText.startIndex] == mutableText[mutableText.index(mutableText.endIndex, offsetBy: -1)] {
11-
let range = Range<String.Index>(mutableText.index(mutableText.startIndex, offsetBy: 1)..<mutableText.index(mutableText.endIndex, offsetBy: -1))
12-
return palindromeCheck(text: mutableText.substring(with: range))
14+
if length > 1 {
15+
return palindrome(strippedString.lowercased(), left: 0, right: length - 1)
1316
}
14-
}
15-
16-
return false
17+
return false
1718
}
1819

19-
// Test to check that non-palindromes are handled correctly:
20-
palindromeCheck(text: "owls")
21-
22-
// Test to check that palindromes are accurately found (regardless of case and whitespace:
23-
palindromeCheck(text: "lol")
24-
palindromeCheck(text: "race car")
25-
palindromeCheck(text: "Race fast Safe car")
26-
27-
// Test to check that palindromes are found regardless of case:
28-
palindromeCheck(text: "HelloLLEH")
20+
/**
21+
Compares a strings left side character against right side character following
22+
- parameter str: The string to compare characters of
23+
- parameter left: Index of left side to compare, must be less than or equal to right
24+
- parameter right: Index of right side to compare, must be greater than or equal to left
25+
- returns: `true` if left side and right side have all been compared and they all match, `false` if a left and right aren't equal
26+
*/
27+
private func palindrome(_ str: String, left: Int, right: Int) -> Bool {
28+
if left >= right {
29+
return true
30+
}
31+
32+
let lhs = str[str.index(str.startIndex, offsetBy: left)]
33+
let rhs = str[str.index(str.startIndex, offsetBy: right)]
34+
35+
if lhs != rhs {
36+
return false
37+
}
38+
39+
return palindrome(str, left: left + 1, right: right - 1)
40+
}
2941

30-
palindromeCheck(text: "moom")
42+
//true
43+
isPalindrome("A man, a plan, a canal, Panama!")
44+
isPalindrome("abbcbba")
45+
isPalindrome("racecar")
46+
isPalindrome("Madam, I'm Adam")
47+
isPalindrome("Madam in Eden, I'm Adam")
48+
isPalindrome("Never odd or even")
49+
isPalindrome("5885")
50+
isPalindrome("5 8 8 5")
51+
isPalindrome("58 85")
52+
isPalindrome("৯৯")
53+
isPalindrome("In girum imus nocte et consumimur igni")
3154

32-
// Test that nil and empty Strings return false:
33-
palindromeCheck(text: "")
34-
palindromeCheck(text: nil)
55+
// false
56+
isPalindrome("\\\\")
57+
isPalindrome("desserts")
58+
isPalindrome("😀😀")
59+
isPalindrome("")
60+
isPalindrome("a")
61+
isPalindrome("power")

Palindromes/Palindromes.swift

Lines changed: 22 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -1,17 +1,26 @@
1-
import Cocoa
1+
import Foundation
22

3-
public func palindromeCheck(text: String?) -> Bool {
4-
if let text = text {
5-
let mutableText = text.trimmingCharacters(in: NSCharacterSet.whitespaces).lowercased()
6-
let length: Int = mutableText.characters.count
3+
func isPalindrome(_ str: String) -> Bool {
4+
let strippedString = str.replacingOccurrences(of: "\\W", with: "", options: .regularExpression, range: nil)
5+
let length = strippedString.characters.count
76

8-
if length == 1 || length == 0 {
9-
return true
10-
} else if mutableText[mutableText.startIndex] == mutableText[mutableText.index(mutableText.endIndex, offsetBy: -1)] {
11-
let range = Range<String.Index>(mutableText.index(mutableText.startIndex, offsetBy: 1)..<mutableText.index(mutableText.endIndex, offsetBy: -1))
12-
return palindromeCheck(text: mutableText.substring(with: range))
7+
if length > 1 {
8+
return palindrome(strippedString.lowercased(), left: 0, right: length - 1)
139
}
14-
}
15-
16-
return false
10+
return false
11+
}
12+
13+
private func palindrome(_ str: String, left: Int, right: Int) -> Bool {
14+
if left >= right {
15+
return true
16+
}
17+
18+
let lhs = str[str.index(str.startIndex, offsetBy: left)]
19+
let rhs = str[str.index(str.startIndex, offsetBy: right)]
20+
21+
if lhs != rhs {
22+
return false
23+
}
24+
25+
return palindrome(str, left: left + 1, right: right - 1)
1726
}

Palindromes/README.markdown

Lines changed: 74 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -1,64 +1,107 @@
11
# Palindromes
22

3-
A palindrome is a word or phrase that is spelled the exact same when read forwards or backwards.
4-
For this example puzzle, palindromes will not take case into account, meaning that uppercase and
5-
lowercase text will be treated the same. In addition, spaces will not be considered, allowing
6-
for multi-word phrases to constitute palindromes too.
3+
A palindrome is a word or phrase that is spelled the exact same when reading it forwards or backward. Palindromes are allowed to be lowercase or uppercase, contain spaces, punctuation, and word dividers.
74

85
Algorithms that check for palindromes are a common programming interview question.
96

107
## Example
118

12-
The word "radar" is spelled the exact same both forwards and backwards, and as a result is a palindrome.
13-
The phrase "race car" is another common palindrome that is spelled the same forwards and backwards.
9+
The word racecar is a valid palindrome, as it is a word spelled the same when backgrounds and forwards. The examples below shows valid cases of the palindrome `racecar`.
1410

15-
## Algorithm
11+
```
12+
raceCar
13+
r a c e c a r
14+
r?a?c?e?c?a?r?
15+
RACEcar
16+
```
1617

17-
To check for palindromes, the first and last characters of a String must be compared for equality.
18-
When the first and last characters are the same, they are removed from the String, resulting in a
19-
substring starting with the second character and ending at the second to last character.
18+
## Algorithm
2019

21-
In this implementation of a palindrome checker, recursion is used to check each substring of the
22-
original String.
20+
To check for palindromes, a string's characters are compared starting from the beginning and end then moving inward toward the middle of the string while maintaining the same distance apart. In this implementation of a palindrome algorithm, recursion is used to check each of the characters on the left-hand side and right-hand side moving inward.
2321

2422
## The code
2523

2624
Here is a recursive implementation of this in Swift:
2725

2826
```swift
29-
func palindromeCheck (text: String?) -> Bool {
30-
if let text = text {
31-
let mutableText = text.trimmingCharacters(in: NSCharacterSet.whitespaces()).lowercased()
32-
let length: Int = mutableText.characters.count
33-
34-
guard length >= 1 else {
35-
return false
36-
}
37-
38-
if length == 1 {
39-
return true
40-
} else if mutableText[mutableText.startIndex] == mutableText[mutableText.index(mutableText.endIndex, offsetBy: -1)] {
41-
let range = Range<String.Index>(mutableText.index(mutableText.startIndex, offsetBy: 1)..<mutableText.index(mutableText.endIndex, offsetBy: -1))
42-
return palindromeCheck(text: mutableText.substring(with: range))
43-
}
27+
func isPalindrome(_ str: String) -> Bool {
28+
let strippedString = str.replacingOccurrences(of: "\\W", with: "", options: .regularExpression, range: nil)
29+
let length = strippedString.characters.count
30+
31+
if length > 1 {
32+
return palindrome(strippedString.lowercased(), left: 0, right: length - 1)
4433
}
4534

4635
return false
4736
}
37+
38+
private func palindrome(_ str: String, left: Int, right: Int) -> Bool {
39+
if left >= right {
40+
return true
41+
}
42+
43+
let lhs = str[str.index(str.startIndex, offsetBy: left)]
44+
let rhs = str[str.index(str.startIndex, offsetBy: right)]
45+
46+
if lhs != rhs {
47+
return false
48+
}
49+
50+
return palindrome(str, left: left + 1, right: right - 1)
51+
}
52+
```
53+
54+
This algorithm has a two-step process.
55+
56+
1. The first step is to pass the string to validate as a palindrome into the `isPalindrome` method. This method first removes occurrences of non-word pattern matches `\W` [Regex reference](http://regexr.com). It is written with two \\ to escape the \ in the String literal.
57+
58+
```swift
59+
let strippedString = str.replacingOccurrences(of: "\\W", with: "", options: .regularExpression, range: nil)
4860
```
4961

62+
The length of the string is then checked to make sure that the string after being stripped of non-word characters is still in a valid length. It is then passed into the next step after being lowercased.
5063

51-
This code can be tested in a playground using the following:
64+
2. The second step is to pass the string in a recursive method. This method takes a string, a left index, and a right index. The method checks the characters of the string using the indexes to compare each character on both sides. The method checks if the left is greater or equal to the right if so the entire string has been run through without returning false so the string is equal on both sides thus returning true.
65+
```swift
66+
if left >= right {
67+
return true
68+
}
69+
```
70+
If the check doesn't pass it continues to get the characters at the specified indexes and compare each. If they are not the same the method returns false and exits.
71+
```swift
72+
let lhs = str[str.index(str.startIndex, offsetBy: left)]
73+
let rhs = str[str.index(str.startIndex, offsetBy: right)]
5274

75+
if lhs != rhs {
76+
return false
77+
}
78+
```
79+
If they are the same the method calls itself again and updates the indexes accordingly to continue to check the rest of the string.
5380
```swift
54-
palindromeCheck(text: "Race car")
81+
return palindrome(str, left: left + 1, right: right - 1)
82+
```
83+
84+
Step 1:
85+
`race?C ar -> raceCar -> racecar`
86+
87+
Step 2:
5588
```
89+
| |
90+
racecar -> r == r
5691
57-
Since the phrase "Race car" is a palindrome, this will return true.
92+
| |
93+
racecar -> a == a
94+
95+
| |
96+
racecar -> c == c
97+
98+
|
99+
racecar -> left index == right index -> return true
100+
```
58101

59102
## Additional Resources
60103

61104
[Palindrome Wikipedia](https://en.wikipedia.org/wiki/Palindrome)
62105

63106

64-
*Written by [Stephen Rutstein](https://github.com/srutstein21)*
107+
*Written by [Joshua Alvarado](https://github.com/https://github.com/lostatseajoshua)*

Palindromes/Test/Palindromes.swift

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
import Foundation
2+
3+
func isPalindrome(_ str: String) -> Bool {
4+
let strippedString = str.replacingOccurrences(of: "\\W", with: "", options: .regularExpression, range: nil)
5+
let length = strippedString.characters.count
6+
7+
if length > 1 {
8+
return palindrome(strippedString.lowercased(), left: 0, right: length - 1)
9+
}
10+
return false
11+
}
12+
13+
private func palindrome(_ str: String, left: Int, right: Int) -> Bool {
14+
if left >= right {
15+
return true
16+
}
17+
18+
let lhs = str[str.index(str.startIndex, offsetBy: left)]
19+
let rhs = str[str.index(str.startIndex, offsetBy: right)]
20+
21+
if lhs != rhs {
22+
return false
23+
}
24+
25+
return palindrome(str, left: left + 1, right: right - 1)
26+
}

0 commit comments

Comments
 (0)