I’ve updated some of my programming tutorial exercises with Swift code samples. See the original exercise for complete problem description. Swift 2.1 code tested in Xcode 7.2 Playground.

The anagram problem

A string is an anagram of another string if re-arranging the letters results in the other string. “Astronomer” and “moon-starer!” are anagrams.

In the original problem we stripped all punctuation since symbols and spaces may be ignored. Let’s say we wanted to count “Ray.adverb” as a single word (for whatever reason) including the period, but “ray-adverb” should still ignore the hyphen as normal. In the code below we strip by word boundaries.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
import Cocoa

var str1 = "Astronomer!"
var str2 = " Moon starer?"
var str3 = "ray-adverb"
var str4 = "barry-dave."
var str5 = "Ray.Adverb"
var str6 = "Tom Marvolo-Riddle"
var str7 = "I am Lord Voldemort!"

func areAnagrams(phrase1: String, phrase2: String) -> Bool
{
    if phrase1.isEmpty { return false }
    if phrase2.isEmpty { return false }
    
    //var trimmedPhrase1 = phrase1.stringByReplacingOccurrencesOfString(" ", withString: "")
    //var trimmedPrhase2 = phrase2.stringByReplacingOccurrencesOfString(" ", withString: "")

    
    var trimmedPhrase1 = stripWordBoundaries(phrase1)
    var trimmedPrhase2 = stripWordBoundaries(phrase2)
    
    trimmedPhrase1 = trimmedPhrase1.lowercaseString
    trimmedPrhase2 = trimmedPrhase2.lowercaseString
    
    if trimmedPrhase2 == trimmedPhrase1 {
        return false
    }
    if trimmedPrhase2.characters.count != trimmedPhrase1.characters.count {
        return false
    }
    let chars1 = trimmedPhrase1.characters.sort()
    let chars2 = trimmedPrhase2.characters.sort()
    
    if chars1 == chars2 {
        return true
    }
    return false
}

// let's treat "Ray.adverb" as a word so period would not be stripped, but "Adverb-Ray?" would become AdverbRay
func stripWordBoundaries(string: String) -> String
{
    var words : [String] = []
    string.enumerateSubstringsInRange(string.characters.indices,
        options: .ByWords) {
            (substring, _, _, _) -> () in
            words.append(substring!)
    }
    return words.joinWithSeparator("")
}

func anagramTest(phrase1 ph1: String, phrase2 ph2: String)
{
    if areAnagrams(ph1, phrase2: ph2) {
        print("'\(ph1)' and '\(ph2)' are anagrams")
    }
    else {
        print("'\(ph1)' and '\(ph2)' are NOT anagrams")
    }
}

anagramTest(phrase1: str1, phrase2: str2)
anagramTest(phrase1: str3, phrase2: str4)
anagramTest(phrase1: str5, phrase2: str4)
anagramTest(phrase1: str6, phrase2: str7)

The sum-zero triplet problem

We want to check if a set of integers contains three integers that sum to zero. See original problem for complete description and alternate solutions.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
import Cocoa

// test numbers
let numArray1 = [ 0, 5, -2, 2, -3, 42, 10 ];
let numArray2 = [ 1, 4, 0, 7, 3 ];
let numArray3 = [ 10, 4, -4, 8, 0 ];
let testArray = [ -2, -1, -3, 4, -5, 6 ];

// return an optional tupple; returns nil of none exist
//
func findTripletSumZero(inIntegers intArray: [Int]) -> (Int, Int, Int)?
{
    if intArray.count < 3 {
        return nil
    }
    let sortedArray = intArray.sort()
    
    // all positives never sum zero
    if sortedArray[0] > 0 {
        return nil
    }
    
    // check from each end and adjust according to sorted order
    for i in 0 ..< sortedArray.count
    {
        var left = i + 1;
        var right = sortedArray.count - 1
        
        // squeeze until right crosses paths with left
        while right > left
        {
            let sum = sortedArray[i] + sortedArray[left] + sortedArray[right]
            if sum == 0
            {
                return (sortedArray[i], sortedArray[left], sortedArray[right])
            }
            else if sum > 0 {
                right -= 1
            }
            else {
                left += 1
            }
        }
    }
    
    return nil
}

func performTest(inIntegers intArray: [Int])
{
    let test = findTripletSumZero(inIntegers: intArray)
    if (test == nil) {
        print("Array \(intArray) has no triplet which sum zero.")
    }
    else {
        let firstInt = test!.0
        let secondInt = test!.1
        let thirdInt = test!.2
        print("Array \(intArray) has zero-sum triplet: (\(firstInt),\(secondInt),\(thirdInt)).")
    }
}

performTest(inIntegers: testArray)
performTest(inIntegers: numArray1)
performTest(inIntegers: numArray2)
performTest(inIntegers: numArray3)

Palindrome check

In Swift 2, a String is not a sequence type so we use String.CharacterView (astring.characters). There are a number of ways to loop through a CharacterView, but since we want to compare characters at two indices then let’s convert to an Array. See the original problem for details.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
import Cocoa

func phraseIsPalindrome(phrase: String) -> Bool
{
    let regex = try! NSRegularExpression(pattern: "[\\s\\p{P}]", options: [.CaseInsensitive])
    
    let trimmedPhrase = regex.stringByReplacingMatchesInString(phrase, options:[], range: NSMakeRange(0, phrase.characters.count), withTemplate: "")
    
    if trimmedPhrase.characters.count > 1 {
        let charArray = Array(trimmedPhrase.lowercaseString.characters)

        // go to mid-point while checking for same character on other end
        for i in 0 ..< len / 2 + 1 {
            if charArray[i] != charArray[charArray.count - i - 1] {
                return false
            }
        }
        return true
    }
	else if trimmedPhrase.characters.count == 1 {
	    // allow single-character phrase
	    return true
	}
    return false
}

phraseIsPalindrome("Never odd or even.")  // true
phraseIsPalindrome("Never odd or evan")   // false
phraseIsPalindrome("Madam, I'm Adam!")    // true