7

I like many of the features in Swift, but using manipulating strings are still a big pain in the ass.

func checkPalindrome(word: String) -> Bool {
    print(word)
    if word == "" {
        return true
    } else {
        if word.characters.first == word.characters.last {
            return checkPalindrome(word.substringWithRange(word.startIndex.successor() ..< word.endIndex.predecessor()))
        } else {
            return false
        }
    }
}

This code fails miserably whenever the string's length is an odd number. Of course I could make it so the first line of the block would be if word.characters.count < 2, but is there a way in Swift to get substrings and check easily?

Update I like many of the suggestions, but I guess the original question could be misleading a little, since it's a question about String more than getting the right results for the function.

For instance, in Python, checkPalindrome(word[1:-1]) would work fine for the recursive definition, whereas Swift code is much less graceful since it needs other bells and whistles.

funct7
  • 3,407
  • 2
  • 27
  • 33
  • you can check String(characters.reverse()) == self – Leo Dabus Dec 28 '15 at 07:45
  • check if word.characters.count % 2 == 1 and delete the character at the center if true – Leo Dabus Dec 28 '15 at 08:04
  • Manipulating strings in Swift is NOT a "pain in the ass"! – Eric Mar 06 '17 at 17:24
  • It "fails miserably" for length==1 because Swift does not allow to create a range with startIndex>endIndex. – Martin R Nov 23 '17 at 09:16
  • Actually what I learned the other day is that you can easily convert a string into an array like this. `var stringArray = Array(string)`. that will easily convert any string into an array. This is using swift of course but you get my drift. Then to change around the string just use `stringArray[0] = stringArray[1]` that will interchange both of those letters. and wallah you now have the characters switched – Esko918 Jan 19 '18 at 22:18

13 Answers13

19
return word == String(word.reversed())
PrynsTag
  • 191
  • 1
  • 10
Gigi
  • 616
  • 6
  • 7
6
func isPalindrome(myString:String) -> Bool {
    let reverseString = String(myString.characters.reversed())
    if(myString != "" && myString == reverseString) {
        return true
    } else {
        return false
    }
}

print(isPalindrome("madam"))
Lukas Würzburger
  • 6,543
  • 7
  • 41
  • 75
4

I have used the below extension to find whether the number is Palindrome or Not.

extension String {
    var isPalindrome: Bool {
        return self == String(self.reversed())
    } 
 }
ssowri1
  • 1,299
  • 2
  • 14
  • 31
3

Sometimes having a front end for a recursion can simplify life. I sometimes do this when the arguments which are most convenient to use are not what I want in the user interface.

Would the following meet your needs?

func checkPalindrome(str: String) -> Bool {
  func recursiveTest(var charSet: String.CharacterView) -> Bool {
    if charSet.count < 2 {
      return true
    } else {
      if charSet.popFirst() != charSet.popLast() {
        return false
      } else {
        return recursiveTest(charSet)
      }
    }
  }
  return recursiveTest(str.characters)
}
pjs
  • 18,696
  • 4
  • 27
  • 56
3

Convert the string into an Array. When the loop is executed get the first index and compare with the last index.

func palindrome(string: String)-> Bool{
    let char = Array(string)
    for i in 0..<char.count / 2 {
        if char[i] != char[char.count - 1 - i] {
            return false
        }
    }
    return true
}
Israel Manzo
  • 217
  • 3
  • 6
2
extension StringProtocol where Self: RangeReplaceableCollection {
    var letters: Self { filter(\.isLetter) }
    var isPalindrome: Bool {
        let letters = self.letters
        return String(letters.reversed()).caseInsensitiveCompare(letters) == .orderedSame
    }
}

"Dammit I'm Mad".isPalindrome    // true
"Socorram-me subi no onibus em marrocos".isPalindrome   // true

You can also break your string into an array of characters and iterate through them until its half comparing one by one with its counterpart:


func checkPalindrome(_ word: String) -> Bool {
    let chars = Array(word.letters.lowercased())
    for index in 0..<chars.count/2 {
        if chars[index] != chars[chars.count - 1 - index] {
            return false
        }
    }
    return true
}

And the recursive version fixing the range issue where can't form a range with endIndex < startIndex:


func checkPalindrome<T: StringProtocol>(_ word: T) -> Bool {
    let word = word.lowercased()
        .components(separatedBy: .punctuationCharacters).joined()
        .components(separatedBy: .whitespacesAndNewlines).joined()
    if word == "" || word.count == 1 {
        return true
    } else {
        if word.first == word.last {
            let start = word.index(word.startIndex,offsetBy: 1, limitedBy: word.endIndex) ?? word.startIndex
            let end = word.index(word.endIndex,offsetBy: -1, limitedBy: word.startIndex) ?? word.endIndex
            return checkPalindrome(word[start..<end])
        } else {
            return false
        }
    }
}

checkPalindrome("Dammit I'm Mad")
Leo Dabus
  • 229,809
  • 59
  • 489
  • 571
  • 2
    Great answer, but OP seems to want a recursive solution, perhaps ad an exercise. – Flying_Banana Dec 28 '15 at 07:47
  • The question is more about how to get substrings than how to check if string is palindrome – funct7 Dec 28 '15 at 07:53
  • @WoominJoshPark "but is there a way in Swift to get substrings and check easily?" also you should take a look at this link http://stackoverflow.com/a/34281720/2303865 – Leo Dabus Dec 28 '15 at 08:12
2

just add on more condition in if

        func checkPalindrome(word: String) -> Bool {
        print(word)
    if (word == "" || word.characters.count == 1){
            return true

        }
    else {
            if word.characters.first == word.characters.last {
                return checkPalindrome(word.substringWithRange(word.startIndex.successor() ..< word.endIndex.predecessor()))
            } else {
                return false
            }
        }
    }
Jaydeep Patel
  • 1,699
  • 17
  • 30
1

I think if you make an extension to String like this one then it will make your life easier:

extension String {
    var length: Int { return characters.count }

    subscript(index: Int) -> Character {
        return self[startIndex.advancedBy(index)]
    }

    subscript(range: Range<Int>) -> String {
        return self[Range<Index>(start: startIndex.advancedBy(range.startIndex), end: startIndex.advancedBy(range.endIndex))]
    }
}

With it in place, you can change your function to this:

func checkPalindrome(word: String) -> Bool {
    if word.length < 2 {
        return true
    } 

    if word.characters.first != word.characters.last {
        return false
    }

    return checkPalindrome(word[1..<word.length - 1])
}

Quick test:

print(checkPalindrome("aba")) // Prints "true"
print(checkPalindrome("abc")) // Prints "false"
0x416e746f6e
  • 9,872
  • 5
  • 40
  • 68
  • Good answer, but we're still checking for word length. It doesn't work as gracefully as word[1:-1] in Python... – funct7 Dec 29 '15 at 03:07
1
extension String {

    func trimmingFirstAndLastCharacters() -> String {
        guard let startIndex = index(self.startIndex, offsetBy: 1, limitedBy: self.endIndex) else {
            return self
        }

        guard let endIndex = index(self.endIndex, offsetBy: -1, limitedBy: self.startIndex) else {
            return self
        }

        guard endIndex >= startIndex else {
            return self
        }

        return String(self[startIndex..<endIndex])
    }

    var isPalindrome: Bool {
        guard count > 1 else {
            return true
        }

        return first == last && trimmingFirstAndLastCharacters().isPalindrome
    }
}

We first declare a function that removes first and last characters from a string.

Next we declare a computer property which will contain the actual recursive code that checks if a string is palindrome.

If string's size is less than or equal 1 we immediately return true (strings composed by one character like "a" or the empty string "" are considered palindrome), otherwise we check if first and last characters of the string are the same and we recursively call isPalindrome on the current string deprived of the first and last characters.

lucamegh
  • 527
  • 6
  • 16
1

This solution is not recursive, but it is a O(n) pure index based solution without filtering anything and without creating new objects. Non-letter characters are ignored as well.

It uses two indexes and walks outside in from both sides.

I admit that the extension type and property name is stolen from Leo, I apologize.

extension StringProtocol where Self: RangeReplaceableCollection {
    var isPalindrome : Bool {
        if isEmpty { return false }
        if index(after: startIndex) == endIndex { return true }
        var forward = startIndex
        var backward = endIndex
        while forward < backward {
            repeat { formIndex(before: &backward) } while !self[backward].isLetter
            if self[forward].lowercased() != self[backward].lowercased() { return false }
            repeat { formIndex(after: &forward) } while !self[forward].isLetter
        }
        return true
    }
}
vadian
  • 274,689
  • 30
  • 353
  • 361
1

A simple solution in Swift:

func isPalindrome(word: String) -> Bool {
    
    // If no string found, return false
    if word.count == 0 { return false }
    
    var index = 0
    var characters = Array(word) // make array of characters
    while index < characters.count / 2 { // repeat loop only for half length of given string
        if characters[index] != characters[(characters.count - 1) - index] {
            return false
        }
        index += 1
    }
    
    return true
}
nitin.agam
  • 1,949
  • 1
  • 17
  • 24
0

Wasn't really thinking of this, but I think I came up with a pretty cool extension, and thought I'd share.

extension String {
    var subString: (Int?) -> (Int?) -> String {
        return { (start) in
            { (end) in
                let startIndex = start ?? 0 < 0 ? self.endIndex.advancedBy(start!) : self.startIndex.advancedBy(start ?? 0)
                let endIndex = end ?? self.characters.count < 0 ? self.endIndex.advancedBy(end!) : self.startIndex.advancedBy(end ?? self.characters.count)

                return startIndex > endIndex ? "" : self.substringWithRange(startIndex ..< endIndex)
            }
        }
    }
}


let test = ["Eye", "Pop", "Noon", "Level", "Radar", "Kayak", "Rotator", "Redivider", "Detartrated", "Tattarrattat", "Aibohphobia", "Eve", "Bob", "Otto", "Anna", "Hannah", "Evil olive", "Mirror rim", "Stack cats", "Doom mood", "Rise to vote sir", "Step on no pets", "Never odd or even", "A nut for a jar of tuna", "No lemon, no melon", "Some men interpret nine memos", "Gateman sees name, garageman sees nametag"]

func checkPalindrome(word: String) -> Bool {
    if word.isEmpty { return true }
    else {
        if word.subString(nil)(1) == word.subString(-1)(nil) {
            return checkPalindrome(word.subString(1)(-1))
        } else {
            return false
        }
    }
}

for item in test.map({ $0.lowercaseString.stringByReplacingOccurrencesOfString(",", withString: "").stringByReplacingOccurrencesOfString(" ", withString: "") }) {
    if !checkPalindrome(item) {
        print(item)
    }
}
funct7
  • 3,407
  • 2
  • 27
  • 33
-1
func checkPalindrome(_ inputString: String) -> Bool {
    if inputString.count % 2 == 0 {
        return false
    } else if inputString.count == 1 {
        return true
    } else {
        var stringCount = inputString.count
        while stringCount != 1 {
            if inputString.first == inputString.last {
                stringCount -= 2
            } else {
                continue
            }
        }
        if stringCount == 1 {
            return true
        } else {
            return false
        }
    }
}
Nubok
  • 3,502
  • 7
  • 27
  • 47
Bola Ibrahim
  • 720
  • 9
  • 8
  • let inputString0 = "aabaa" checkPalindrome(inputString0) let inputString1 = "abcuabc" checkPalindrome(inputString1) let inputString2 = "abac" checkPalindrome(inputString2) – Bola Ibrahim Jan 16 '18 at 17:30
  • 2
    Please try including an explanation with your code, as it makes for a better answer. – Antimony Jan 16 '18 at 17:54
  • 1
    Your function returns `false` for the string "abba" (which is a palindrome), it returns `true` for the string "abcda" (which is not a palindrome), and it never terminates for the string "abcde". – Also the question was about recursion and substrings, and code-only answers without any explanation are useless for future readers. – Martin R Sep 06 '18 at 06:12