Main
Swift Coding Challenges
Swift Coding Challenges
Paul Hudson
0 /
0
How much do you like this book?
What’s the quality of the file?
Download the book for quality assessment
What’s the quality of the downloaded files?
Categories:
Year:
2018
Edition:
1st Edition
Language:
english
Pages:
300
Series:
Hacking With Swift
File:
PDF, 7.11 MB
Your tags:
Download (pdf, 7.11 MB)
- Open in Browser
- Checking other formats...
- Convert to EPUB
- Convert to FB2
- Convert to MOBI
- Convert to TXT
- Convert to RTF
- Converted file can differ from the original. If possible, download the file in its original format.
The file will be sent to your email address. It may take up to 1-5 minutes before you receive it.
The file will be sent to your Kindle account. It may takes up to 1-5 minutes before you received it.
Please note: you need to verify every book you want to send to your Kindle. Check your mailbox for the verification email from Amazon Kindle.
Please note: you need to verify every book you want to send to your Kindle. Check your mailbox for the verification email from Amazon Kindle.
Conversion to is in progress
Conversion to is failed
You may be interested in Powered by Rec2Me
Most frequently terms
array323
compile210
output203
input198
int192
var183
loop164
func154
nil137
swift110
node109
taylor94
strings67
binary59
integer52
bool42
sorted42
tricky40
arrays39
queen39
files35
pivot34
integers34
grid32
operator32
tortoise26
reversed26
nodes26
convert25
lowest25
optional23
serenity23
hare23
bracket22
taxing22
queens21
coding19
buffer19
tuple19
filename18
digits18
tracker17
url17
Related Booklists
0 comments
You can write a book review and share your experiences. Other readers will always be interested in your opinion of the books you've read. Whether you've loved the book or not, if you give your honest and detailed thoughts then people will find new books that are right for them.
1
|
2
|
Paul Hudson CODING CHALLENGES SWIFT CODING CHALLENGES SWIFT HACKING WITH SWIFT REAL PROBLEMS, REAL SOLUTIONS Prepare for iOS interviews, test yourself against friends, and level up your skills. Contents Introduction 4 Welcome Strings 11 Challenge 1: Are the letters unique? Challenge 2: Is a string a palindrome? Challenge 3: Do two strings contain the same characters? Challenge 4: Does one string contain another? Challenge 5: Count the characters Challenge 6: Remove duplicate letters from a string Challenge 7: Condense whitespace Challenge 8: String is rotated Challenge 9: Find pangrams Challenge 10: Vowels and consonants Challenge 11: Three different letters Challenge 12: Find longest prefix Challenge 13: Run-length encoding Challenge 14: String permutations Challenge 15: Reverse the words in a string Numbers 54 Challenge 16: Fizz Buzz Challenge 17: Generate a random number in a range Challenge 18: Recreate the pow() function Challenge 19: Swap two numbers Challenge 20: Number is prime Challenge 21: Counting binary ones Challenge 22: Binary reverse Challenge 23: Integer disguised as string Challenge 24: Add numbers inside a string Challenge 25: Calculate a square root by hand Challenge 26: Subtract without subtract Files 87 Challenge 27: Print last lines Challenge 28: Log a message Challenge 29: Documents directory www.hackingwithswift.com 2 Challenge 30: New JPEGs Challenge 31: Copy recursively Challenge 32: Word frequency Challenge 33: Find duplicate filenames Challenge 34: Find executables Challenge 35: Convert images Challenge 36: Print error lines Collections 119 Challenge 37: Count the numbers Challenge 38: Find N smallest Challenge 39: Sort a string array by length Challenge 40: Missing numbers in array Challenge 41: Find the median Challenge 42: Recreate index(of:) Challenge 43: Linked lists Challenge 44: Linked list mid-point Challenge 45: Traversing the tree Challenge 46: Recreate map() Challenge 47: Recreate min() Challenge 48: Implement a deque data structure Challenge 49: Sum ; the even repeats Challenge 50: Count the largest range Challenge 51: Reversing linked lists Challenge 52: Sum an array of numbers Challenge 53: Linked lists with a loop Challenge 54: Binary search trees Algorithms 185 Challenge 55: Bubble sort Challenge 56: Insertion sort Challenge 57: Isomorphic values Challenge 58: Balanced brackets Challenge 59: Quicksort Challenge 60: Tic-Tac-Toe winner Challenge 61: Find prime numbers Challenge 62: Points to angles Challenge 63: Flood fill Challenge 64: N Queens Appendix: Be the Compiler 236 www.hackingwithswift.com 3 Introduction About this book www.hackingwithswift.com 4 Welcome This is not your average book about Swift coding. This is a book where I expect you to do most of the work. Sure, I’ll be helping you along with hints, and I’ll also be providing my own solutions and explanations along the way, but if you haven’t already put the work in then you’ll be missing out. You see, this book is called Swift Coding Challenges because I really want to challenge you. There is no learning without struggle, so if you don’t take the time to read each challenge and try it for yourself in Xcode, you’ll never know how you would have fared. So, please follow these instructions to the letter if you want to reap the full benefit of what this book offers. It’s not a cheatsheet, a guide book, or even a tutorial like I would normally write. Instead, this is a book designed to make you think, to make you work, and to make you learn by doing. Note: this book is not for Swift beginners, so you should have at least six months of Swift behind you. If you’ve completed Hacking with Swift you ought to be able to handle all the Easy and some of the Tricky problems. If you’ve completed Pro Swift you ought to be able to handle most of the Taxing problems too. If you consistently struggle with challenges, or if you’ve never seen the rethrows keyword before, you should definitely watch my Pro Swift videos. Where I thought it was useful, I have used Big O notation to describe the computational complexity of an algorithm. The order of a function (the “O”) tells you how fast it grows, and provides a worst-case scenario of how much work must be done to run some code. An O(n) function means “the function takes as correspondingly longer as you add items.” So, if it takes one second with one item, it would take 10 seconds with ten items. In comparison, an O(1) function runs in constant time, which means a function that takes 1 second with one item would take 1 second with 100 items. How to use this book I’ve organized this book into chapters so that it’s roughly grouped by different kinds of problem. There is, inevitably, some crossover between chapters, but I’ve tried to place things where I thought best. Inside each chapter are individual challenges sorted by difficulty, so you www.hackingwithswift.com 5 ought to be able to flip to a chapter that interests you and just start working your way through from the beginning. Each challenge is broken down into three parts. First, I state the problem as clearly as possible in one sentence. When necessary I’ll provide some extra clarification. When working with strings, I’ve made it clear whether you should ignore letter case (so “CAT” is equal to “cat”) or whether you should take letter case into account (so “CAT” is not equal to “cat”). Second, I provide some example input and output so you should be able to write test cases to validate that your code is correct. I’m not saying that you need to use test-driven development, but I would suggest that if you’re going to a job interview TDD is a good skill to be able to show. After these two steps, I encourage you to fire up Xcode and start coding. How long it takes to solve each challenge depends on your skill level, but I would suggest the following as a rough guide: 1. Novice developers, i.e. under one year of coding experience, Swift or otherwise: 15 minutes for an Easy challenge, 30 minutes for a Tricky challenge, and 1 hour for a Taxing challenge. 2. Intermediate developers, i.e. under three years of coding experience: 10 minutes for an Easy challenge, 20 minutes for a Tricky challenge, and 30 minutes for a Taxing challenge. 3. Senior developers, i.e. five years or more of coding experience: 5 minutes for an Easy challenge, 10 minutes for a Tricky challenge, and 15 minutes for a Taxing challenge. If you fall somewhere between those groups, I’m sure you’re smart enough to extrapolate a rough goal for yourself. If you’re way beyond five years of experience then I would expect the times might come down further – perhaps as low as five minutes for a taxing challenge if you’re confident. Obvious warning: the groupings are very broad, so don’t worry if you go over the suggested times. I personally would count writing tests and commenting your code in those times, but that’s down to you. www.hackingwithswift.com 6 As you work, you might find you hit problems – and that’s OK. Remember the whole “no learning without struggle” thing? If you hit a brick wall and you’re not sure how to continue, every challenge comes with hints from me to try to point you in the right direction. You should read these only if you’ve tried and failed to complete the solution, and even then read only one hint at a time – you might find just reading the first one is enough to help you advance. Once you have completed the challenge – which might be when you’ve written a solution that passes your tests, or might be when your self-allotted time target has lapsed – then it’s time for you to read my solution. I’ve tried to make sure every solution is as clear as possible, so sometimes I’ve used three lines of code rather than one to allow me to add more comments or discussion. Remember, interview environments are stressful enough without you striving for the perfect answer to a question – get something that works then improve on it, rather than trying to get it into a magic one liner in your first pass! Many challenges come with more than one solution. This is sometimes to help you compare performance characteristics, sometimes because I can never resist the opportunity to teach new things, but sometimes also because I want to encourage you to be open-minded – there are lots of ways of making things work, and I hope you can appreciate the variety! Passing a challenge I’m afraid there’s no certificate when you complete all the challenges, but if you tweet me @twostraws I’ll high five you over the internet. Since I’m not on hand to mark your answers, it’s down to you to self-regulate – you’re only cheating yourself, after all. I suggest you write tests for your challenges; something like this ought to work, replacing the “X” with your current challenge number: func yourFunctionName(input: String) -> Bool { return true } assert(yourFunctionName(input: "Hacking with Swift") == true, "Challenge X did not return true") www.hackingwithswift.com 7 I frequently use challengeX() for the names of my functions and methods, but only because it makes it easier for you to remember where they came from if you copy them into your own playgrounds. You should be able to complete most of the challenges in a Swift playground. The Files chapter requires you to work with external files on your computer, so for those challenges you should use a macOS Command Line Tool project. Note: sometimes it’s possible you’ll fly through a challenge graded as taxing, or struggle with a problem that’s graded easy. When this happens, please don’t send me angry emails: 1. Things that are easy for you aren’t always easy for others, and things that are hard for you aren’t always hard for others. 2. The nature of strings make them easier to work with than collections, so you’ll find many more easy problems with strings than with collections. 3. What seems easy over a glass of wine on a Sunday evening can seem insurmountable when faced with three interviewers staring at you as you pick up a whiteboard pen! Often I refer to a solution as being naïve. This is not an insult! If your solution is the same as my naïve solution, it means you totally solved the problem and deserve a pat on the back. It does, however, mean that there are more efficient or cleaner solutions available, and I hope everyone will learn at least a little bit while reading this book. A note on algorithms Some of you reading this won’t have had formal computer science education, and perhaps might not have had the chance to fill any gaps on the job. If this is you, it’s very likely you might find the algorithms chapter particularly difficult – if you can’t tell a bubble sort from an insertion sort, then it’s going to be hard to answer interview questions about them. Take heart! I do my best to explain algorithms in particular detail, and hope my explanations will help you understand how they work. If you’re able to try these challenges that’s awesome, but don’t feel discouraged if you struggle and end up relying on my solutions particularly heavily. We all have things we don’t know – even me. www.hackingwithswift.com 8 Frequent Flyer Club You can buy Swift tutorials from anywhere, but I'm pleased, proud, and very grateful that you chose mine. I want to say thank you, and the best way I have of doing that is by giving you bonus content above and beyond what you paid for – you deserve it! Every book contains a word that unlocks bonus content for Frequent Flyer Club members. The word for this book is COBALT. Enter that word, along with words from any other Hacking with Swift books, here: https://www.hackingwithswift.com/frequent-flyer Dedication This book is dedicated to Taylor Smith, who frequently reads my Taylor Swift-inspired coding examples and imagines they are about him. Well, this time it really is about you: import Foundation let TAYLOR = { (t: Int) in return -(~t) } let tayloR = { return TAYLOR(TAYLOR($0)) } let taylOr = { return tayloR(tayloR($0)) } let tayLor = { return taylOr(taylOr($0)) } let taYlor = { return tayLor(tayLor($0)) } let tAylor = { return taYlor(taYlor($0)) } let Taylor = { return tAylor(tAylor($0)) } let taylor = { (t: Int) -> Int in print(UnicodeScalar(t)!, terminator: ""); return 0 } taylor (tayloR (tayLor (taylor (TAYLOR (tAylor (taylor (taYlor (tAylor (taylor (tayloR (taYlor (tAylor (taylor (taylOr (tAylor (taylor (tAylor (taylor (TAYLOR (taylOr (tAylor (Taylor (taylor (TAYLOR (taylOr (tayLor (tAylor (Taylor (taylor (tAylor (taylor (TAYLOR (taylOr (tAylor (Taylor (taylor (TAYLOR (tayloR (taylOr (taYlor (tAylor (Taylor (taylor (TAYLOR (tayloR (taylOr (tayLor (tAylor (Taylor (taylor (tAylor (taylor (TAYLOR (taylOr (taYlor (tAylor (Taylor (taylor (TAYLOR (tayloR (taylOr (tayLor (tAylor www.hackingwithswift.com 9 (Taylor (taylor (TAYLOR (tayLor (taYlor (Taylor (0) ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) ) Copyright Swift, the Swift logo, Xcode, Instruments, Cocoa Touch, Touch ID, AirDrop, iBeacon, iPhone, iPad, Safari, App Store, Mac and macOS are trademarks of Apple Inc., registered in the U.S. and other countries. Swift Coding Challenges, Practical iOS 10, Pro Swift, and Hacking with Swift are copyright Paul Hudson. All rights reserved. No part of this book or corresponding materials (such as text, images, or source code) may be reproduced or distributed by any means without prior written permission of the copyright owner. www.hackingwithswift.com 10 Chapter 1 Strings www.hackingwithswift.com 11 Challenge 1: Are the letters unique? Difficulty: Easy Write a function that accepts a String as its only parameter, and returns true if the string has only unique letters, taking letter case into account. Sample input and output • The string “No duplicates” should return true. • The string “abcdefghijklmnopqrstuvwxyz” should return true. • The string “AaBbCc” should return true because the challenge is case-sensitive. • The string “Hello, world” should return false because of the double Ls and double Os. For this initial challenge I’ll write some test cases for you, so that you have something to use in the future. These four assert() statements should all evaluate to true, and therefore not trigger an error: assert(challenge1(input: "No duplicates") == true, "Challenge 1 failed") assert(challenge1(input: "abcdefghijklmnopqrstuvwxyz") == true, "Challenge 1 failed") assert(challenge1(input: "AaBbCc") == true, "Challenge 1 failed") assert(challenge1(input: "Hello, world") == false, "Challenge 1 failed") Hints Remember, read as few hints as you can to help you solve the challenge, and only read them if you’ve tried and failed. (This reminder won’t be repeated again.) Hint #1: You should treat the input string like an array that contains Character elements. Hint #2: You could use a temporary array to store characters that have been checked, but it’s www.hackingwithswift.com 12 not necessary. Hint #3: Sets are like arrays, except they can’t contain duplicate elements. Hint #4: You can create sets from arrays and arrays from sets. Both have a count property. Solution There are two ways to solve this, both of which are perfectly fine given our test cases. First, the brute force approach: create an array of checked characters, then loop through every letter in the input string and append the latter to the list of checked characters, returning false as soon as a call to contains() fails. Here’s how that code would look: func challenge1a(input: String) -> Bool { var usedLetters = [Character]() for letter in input { if usedLetters.contains(letter) { return false } usedLetters.append(letter) } return true } That solution is correct with the example input and output I provided, but you should be prepared to discuss that it doesn’t scale well: calling contains() on an array is an O(n) operation, which means it gets slower as more items are added to the array. If our text were in a language with very few duplicate characters, such as Chinese, this might cause performance issues. www.hackingwithswift.com 13 issues. The smart solution is to use Set, which can be created directly from the input string. Sets cannot contain duplicate items, so if we create a set from the input string then the count of the set will equal the count of the input if there are no duplicates. In code you would write this: func challenge1b(input: String) -> Bool { return Set(input).count == input.count } www.hackingwithswift.com 14 Challenge 2: Is a string a palindrome? Difficulty: Easy Write a function that accepts a String as its only parameter, and returns true if the string reads the same when reversed, ignoring case. Sample input and output • The string “rotator” should return true. • The string “Rats live on no evil star” should return true. • The string “Never odd or even” should return false; even though the letters are the same in reverse the spaces are in different places. • The string “Hello, world” should return false because it reads “dlrow ,olleH” backwards. Hints Hint #1: You can reverse arrays using their reversed() method. Hint #2: Two arrays compare as equal if they contain the same items in the same order. They are value types in Swift, so it doesn’t matter how they were created, as long as their values are the same. Hint #3: String aren’t really arrays, but you can make them one using Array(). Hint #4: You need to ignore case, so consider forcing the string to either lowercase or uppercase before comparing. Solution This is one of the most common interview questions you’ll come across, and it has a particular quirk in Swift that might have caught you out: strings might look like arrays, but trying to index into them is hard. www.hackingwithswift.com 15 index into them is hard. Fortunately, you can create an array from it like this: let characterArray = Array(input) Once you figure that out, you’ll just need to use reversed() to reverse the string’s characters, then compare the reversed array with the character view-as-array, like this: func challenge2(input: String) -> Bool { return input.reversed() == Array(input) } Remember, strings are value types in Swift, which means they compare as equal as long as their contents are identical - it doesn’t matter how they are created. As an analogy, we all know that 2 times 2 is equal to 2 + 2, even though the number 4 was created using different methods. The same is true of Swift’s string: even though one is reversed, the == operator just compares the current value. Finally, make sure you remember that your comparison should ignore string case. This can be done with the lowercased() method on the input string, like this: func challenge2(input: String) -> Bool { let lowercase = input.lowercased() return lowercase.reversed() == Array(lowercase) } Done! www.hackingwithswift.com 16 Challenge 3: Do two strings contain the same characters? Difficulty: Easy Write a function that accepts two String parameters, and returns true if they contain the same characters in any order taking into account letter case. Sample input and output • The strings “abca” and “abca” should return true. • The strings “abc” and “cba” should return true. • The strings “ a1 b2 ” and “b1 a2” should return true. • The strings “abc” and “abca” should return false. • The strings “abc” and “Abc” should return false. • The strings “abc” and “cbAa” should return false. Hints Hint #1: This task requires you to handle duplicate characters. Hint #2: The naive way to check this is to loop over the characters in one and check it exists in the other, removing matches as you go. Hint #3: A faster solution is to convert both strings to character arrays. Hint #4: If you sort two character arrays, then you will have something that is the same length and identical character for character. Solution You could write a naïve solution to this problem by taking a variable copy of the second input string, then looping over the first string and checking each letter exists in the second. If it does, remove it so it won’t be counted again; if not, return false. If you get to the end of the first www.hackingwithswift.com 17 string, then return true if the second string copy is now empty, otherwise return false. For example: func challenge3a(string1: String, string2: String) -> Bool { var checkString = string2 for letter in string1 { if let index = checkString.index(of: letter) { checkString.remove(at: index) } else { return false } } return checkString.count == 0 } That solution works, but is less than ideal because you’re having to look up letter positions repeatedly using index(of:), which is O(n). Worse, the remove(at:) call is also O(n), because it needs to move other elements down in the array once the item is removed. A more efficient solution is to make arrays out of both sets of string characters, then sort them. Once that’s done, you can do a direct comparison using ==. This ends up not only being faster to run, but also involving much less code: func challenge3b(string1: String, string2: String) -> Bool { let array1 = Array(string1) let array2 = Array(string2) return array1.sorted() == array2.sorted() } For bonus interview points, you might be tempted to write something like this: return array1.count == array2.count && array1.sorted() == www.hackingwithswift.com 18 array2.sorted() However, there’s no need – the == operator for arrays does that before doing its item-by-item comparison, so doing it yourself is just redundant. www.hackingwithswift.com 19 Challenge 4: Does one string contain another? Difficulty: Easy Write your own version of the contains() method on String that ignores letter case, and without using the existing contains() method. Sample input and output • The code "Hello, world".fuzzyContains("Hello") should return true. • The code "Hello, world".fuzzyContains("WORLD") should return true. • The code "Hello, world".fuzzyContains("Goodbye") should return false. Hints Hint #1: You should write this as an extension to String. Hint #2: You can’t use contains(), but there are other methods that do similar things. Hint #3: Try the range(of:) method. Hint #4: To ignore case, you can either uppercase both strings, or try the second parameter to range(of:). Solution If you were already familiar with the range(of:) method, this one should have proved straightforward. If not, you were probably wondering why I gave it an easy grade! The range(of:) method returns the position of one string inside another. As it’s possible the substring might not exist in the other, the return value is optional. This is perfect for us: if we call range(of:) and get back nil, it means the substring isn’t contained inside the check string. www.hackingwithswift.com 20 Ignoring letter case adds a little complexity, but can be solved either by collapsing the case before you do your check, or by using the .caseInsensitive option for range(of:). The former looks like this: extension String { func fuzzyContains(_ string: String) -> Bool { return self.uppercased().range(of: string.uppercased()) ! = nil } } And the latter like this: extension String { func fuzzyContains(_ string: String) -> Bool { return range(of: string, options: .caseInsensitive) != nil } } In this instance the two are identical, but there’s a benefit to collapsing the case if you had to check through lots of items. www.hackingwithswift.com 21 Challenge 5: Count the characters Difficulty: Easy Write a function that accepts a string, and returns how many times a specific character appears, taking case into account. Tip: If you can solve this without using a for-in loop, you can consider it a Tricky challenge. Sample input and output • The letter “a” appears twice in “The rain in Spain”. • The letter “i” appears four times in “Mississippi”. • The letter “i” appears three times in “Hacking with Swift”. Hints Hint #1: Remember that String and Character are different data types. Hint #2: Don’t be afraid to go down the brute force route: looping over characters using a for-in loop. Hint #3: You could solve this functionally using reduce(), but tread carefully. Hint #4: You could solve this using NSCountedSet, but I’d be suspicious unless you could justify the extra overhead. Solution You might be surprised to hear me saying this, but: this is a great interview question. It’s simple to explain, it’s simple to code, and it has enough possible solutions that it’s likely to generate some interesting discussion – which is gold dust in interviews. This question is also interesting, because it’s another good example where the simple brute www.hackingwithswift.com 22 force approach is both among the most readable and most efficient. I suggested two alternatives in the hints, and I think it’s an interesting code challenge for you to try all three. First, the easy solution: loop over the characters by hand, comparing against the check character. In code, it would be this: func challenge5a(input: String, count: Character) -> Int { var letterCount = 0 for letter in input { if letter == count { letterCount += 1 } } return letterCount } There’s nothing complicated there, but do make sure you accept the check character as a Character to make the equality operation smooth. The second option is to solve this problem functionally using reduce(). This has the advantage of making for very clear, expressive, and concise code, particularly when combined with the ternary operator: func challenge5b(input: String, count: Character) -> Int { return input.reduce(0) { $1 == count ? $0 + 1 : $0 } } So, that will start with 0, then go over every character in the string. If a given letter matches the input character, then it will add 1 to the reduce counter, otherwise it will return the current reduce counter. Functional programming does make for shorter code, and the intent here is nice and clear, however this is not quite as performant – it runs about 10% slower than the first www.hackingwithswift.com 23 solution. A third solution is to use NSCountedSet, but that’s wasteful unless you intend to count several characters. It’s also complicated because Swift bridges String to NSObject well, but doesn’t bring Character, so NSCountedSet won’t play nicely unless you convert the characters yourself. So, your code would end up being something like this: func challenge5c(input: String, count: String) -> Int { let array = input.map { String($0) } let counted = NSCountedSet(array: array) return counted.count(for: count) } That creates an array of strings by converting each character in the input string, then creates a counted set from the string array, and finally returns the count – for a single letter. Wasteful, for sure, and inefficient too – a massive ten times slower than the original. There’s actually a fourth option you might have chosen. It’s the shortest option, however it requires a little lateral thinking: you can calculate how many times a letter appears in a string by removing it, then comparing the lengths of the original and modified strings. Here it is in Swift: func challenge5d(input: String, count: String) -> Int { let modified = input.replacingOccurrences(of: count, with: "") return input.count - modified.count } www.hackingwithswift.com 24 Challenge 6: Remove duplicate letters from a string Difficulty: Easy Write a function that accepts a string as its input, and returns the same string just with duplicate letters removed. Tip: If you can solve this challenge without a for-in loop, you can consider it “tricky” rather than “easy”. Sample input and output • The string “wombat” should print “wombat”. • The string “hello” should print “helo”. • The string “Mississippi” should print “Misp”. Hints Hint #1: Sets are great at removing duplicates, but bad at retaining order. Hint #2: Foundation does have a way of forcing sets to retain their order, but you need to handle the typecasting. Hint #3: You can create strings out of character arrays. Hint #4: You can solve this functionally using filter(). Solution There are three interesting ways this can be solved, and I’m going to present you with all three so you can see which suits you best. Remember: “fastest” isn’t always “best”, not least because readability is important, but also particularly because “memorizability” is important too – the perfect solution is often easily forgotten when you’re being tested. Let’s look at a slow but interesting solution first: using sets. Swift’s standard library has a www.hackingwithswift.com 25 built-in Set type, but it does not preserve the order of its elements. This is a shame, because otherwise the solution would have been as simple as this: let string = "wombat" let set = Set(string) print(String(set)) However, Foundation has a specialized set type called NSOrderedSet. This also removes duplicates, but now ensures items stay in the order they were added. Sadly, it’s not bridged to Swift in any pleasing way, which means to use it you must add typecasting: once from Character to String before creating the set, then once from Array<Any> to Array<String>. This function does just that: func challenge6a(string: String) -> String { let array = string.map { String($0) } let set = NSOrderedSet(array: array) let letters = Array(set) as! Array<String> return letters.joined() } That passes all tests, but I think you’ll agree it’s a bit ugly. I suspect Swift might see a native OrderedSet type in the future. A second solution is to take a brute-force approach: create an array of used characters, then loop through every letter in the string and check if it’s already in the used array. If it isn’t, add it, then finally return a stringified form of the used array. This is nice and easy to write, as long as you know that you can create a String directly from a Character array: func challenge6b(string: String) -> String { var used = [Character]() for letter in string { www.hackingwithswift.com 26 if !used.contains(letter) { used.append(letter) } } return String(used) } There is a third solution, and I think it’s guaranteed to generate some interesting discussion in an interview or book group! As you know, dictionaries hold a value attached to a key, and only one value can be attached to a specific key at any time. You can change the value attached to a key just by assigning it again, but you can also call the updateValue() method – it does the same thing, but also returns either the original value or nil if there wasn’t one. So, if you call updateValue() and get back nil it means “that wasn’t already in the dictionary, but it is now.” We can use this method in combination with the filter() method on our input string’s character property: filter the characters so that only those that return nil for updateValue() are used in the return array. So, the third solution to this challenge looks like this: func challenge6c(string: String) -> String { var used = [Character: Bool]() let result = string.filter { used.updateValue(true, forKey: $0) == nil } return String(result) } As long as you know about the updateValue() method, that code is brilliantly readable – the use of filter() means it’s clear what the loop is trying to do. However, it’s about 3x www.hackingwithswift.com 27 slower than the second solution when using our sample input and output data, so although it gets full marks for cleverness it falls short on performance. www.hackingwithswift.com 28 Challenge 7: Condense whitespace Difficulty: Easy Write a function that returns a string with any consecutive spaces replaced with a single space. Sample input and output I’ve marked spaces using “[space]” below for visual purposes: • The string “a[space][space][space]b[space][space][space]c” should return “a[space]b[space]c”. • The string “[space][space][space][space]a” should return “[space]a”. • The string “abc” should return “abc”. Hints Hint #1: You might think it a good idea to use components(separatedBy:) then joined(), but that will struggle with leading and trailing spaces. Hint #2: You could loop over each character, keeping track of a seenSpace boolean that gets set to true when the previous character was a space. Hint #3: You could use regular expressions. Hint #4: Try using replacingOccurrences(of:) Solution As is the case for many other string challenges, we can write a naïve solution or a clever one, but here the clever one is dramatically simpler – and it uses regular expressions. (Yes, you did just read “simpler” and “regular expressions” in the same sentence.) But first, let’s look at something you might have tried: www.hackingwithswift.com 29 func challenge7(input: String) -> String { let components = input.components(separatedBy: .whitespacesAndNewlines) return components.filter { !$0.isEmpty }.joined(separator: " ") } That splits a string up by its spaces, then removes any empty items, and joins the remainder using a space, and is the ideal solution – if your goal is to remove any duplicate whitespace while also removing leading and trailing whitespace. However, it fails the requirement that “[space][space][space][space]a” should return “[space]a“, so you should have rejected it. Instead, you might have written a loop over the characters in the input string. If the current letter was a space and you had already seen one in this run, continue to the next letter. Otherwise, mark that you’ve seen a space. If it wasn’t a space, clear the space flag. Regardless of whether it was the first space or a letter, append it to an output string. Transform that into Swift and you get this: func challenge7a(input: String) -> String { var seenSpace = false var returnValue = "" for letter in input { if letter == " " { if seenSpace { continue } seenSpace = true } else { seenSpace = false } returnValue.append(letter) } return returnValue www.hackingwithswift.com 30 } This is a clear solution, and it works great. However, for once, this is a place where regular expressions can help: they turn all that into a single line of code: func challenge7b(input: String) -> String { return input.replacingOccurrences(of: " +", with: " ", options: .regularExpression, range: nil) } If you’re not familiar with regular expressions, “[space]+” means “match one or more spaces”, so that will cause all multiple spaces to be replaced with a single space. Running regular expressions isn’t cheap, so that code runs about 50% the speed of the manual solution, but you would have to be doing a heck of a lot of work in order for it to be noticeable. www.hackingwithswift.com 31 Challenge 8: String is rotated Difficulty: Tricky Write a function that accepts two strings, and returns true if one string is rotation of the other, taking letter case into account. Tip: A string rotation is when you take a string, remove some letters from its end, then append them to the front. For example, “swift” rotated by two characters would be “ftswi”. Sample input and output • The string “abcde” and “eabcd” should return true. • The string “abcde” and “cdeab” should return true. • The string “abcde” and “abced” should return false; this is not a string rotation. • The string “abc” and “a” should return false; this is not a string rotation. Hints Hint #1: This is easier than you think. Hint #2: A string is only considered a rotation if is identical to the original once you factor in the letter movement. That is, “tswi” is not a rotation of “swift” because it is missing the F. Hint #3: If you write a string twice, it must encapsulate all possible rotations, e.g. “catcat” contains “cat”, “tca”, and “atc”. Solution This question appears in coding interviews far more than it deserves, because it’s a problem that seems tricky the first time you face it but is staring-you-in-the-face obvious once someone has told you the solution. I wonder how many times this question appears on interviews just so the interviewer can feel smug about knowing the answer! www.hackingwithswift.com 32 Anyway, let’s talk about the solution. As I said in hint #3, if you write a string twice it must always encapsulate all possible rotations. So if your string was “abc” then you would double it to “abcabc”, which contains all possible rotations: “abc”, “cab”, and “bca”. So, an initial solution might look like this: func challenge8(input: String, rotated: String) -> Bool { let combined = input + input return combined.contains(rotated) } However, that’s imperfect – the final example input and output was that “abc” should return false when given the test string “a”. Using the code above, the input string would be double to “abcabc”, which clearly contains the test string “a”. To fix this, we need to check not only that the test string exists in the doubled input, but also that both strings are the same size. So, the correct solution is this: func challenge8(input: String, rotated: String) -> Bool { guard input.count == rotated.count else { return false } let combined = input + input return combined.contains(rotated) } Like I said, it’s easier than you think, but is it a test of coding knowledge? Not really. If anything, you get a brief “aha!” flash when someone explains the solution to you, but apart from scoring you some interview brownie points I doubt this would be useful in real life. www.hackingwithswift.com 33 Challenge 9: Find pangrams Difficulty: Tricky Write a function that returns true if it is given a string that is an English pangram, ignoring letter case. Tip: A pangram is a string that contains every letter of the alphabet at least once. Sample input and output • The string “The quick brown fox jumps over the lazy dog” should return true. • The string “The quick brown fox jumped over the lazy dog” should return false, because it’s missing the S. Hints Hint #1: Make sure you start by collapsing case using something like lowercased(). Hint #2: You can compare letters using >, >=, and so on. Hint #3: If you remove duplicates and non-alphabetic characters, the remaining string should add up to 26 letters. Solution You could try and solve this using character sets, but it’s really not needed: Swift’s characters conform to Comparable, so you can compare them against “a” and “z” directly to ensure they are alphabetical. Once you know how to ensure a letter is alphabetical, all that remains is removing duplicates (easy using a set) and collapsing case (lowercased() is fine), then comparing the count of the result against 26. So, here’s an example solution in just three lines of code: www.hackingwithswift.com 34 So, here’s an example solution in just three lines of code: func challenge9(input: String) -> Bool { let set = Set(input.lowercased()) let letters = set.filter { $0 >= "a" && $0 <= "z" } return letters.count == 26 } www.hackingwithswift.com 35 Challenge 10: Vowels and consonants Difficulty: Tricky Given a string in English, return a tuple containing the number of vowels and consonants. Tip: Vowels are the letters, A, E, I, O, and U; consonants are the letters B, C, D, F, G, H, J, K, L, M, N, P, Q, R, S, T, V, W, X, Y, Z. Sample input and output • The input “Swift Coding Challenges” should return 6 vowels and 15 consonants. • The input “Mississippi” should return 4 vowels and 7 consonants. Hints Hint #1: Just because a letter is not a vowel, it doesn’t mean it’s a consonant – think punctuation, for example. Hint #2: You’ll need to differentiate carefully between the String and Character types. Hint #3: You could use CharacterSet here, but is it really needed? Hint #4: Your return type should be (vowels: Int, consonants: Int). Hint #5: Watch out for uppercase and lowercase letters – an “A” is a vowel regardless of its case. Solution There are three interesting ways to solve this challenge, and I’m going to present them to you slowest first – although realistically you’ll only see significant performance differences if these functions are being called run a hundred times or more. First, you could use CharacterSet here, but it has a fatal flaw: even though “Character” is www.hackingwithswift.com 36 right there in the name, you can’t ask a character set whether it contains a single character because it works on a different type called UnicodeScalar. Instead, you need to convert the Character to a String, then use its rangeOfCharacter(from:) method, like this: func challenge10a(input: String) -> (vowels: Int, consonants: Int) { let vowels = CharacterSet(charactersIn: "aeiou") let consonants = CharacterSet(charactersIn: "bcdfghjklmnpqrstvwxyz") var vowelCount = 0 var consonantCount = 0 for letter in input.lowercased() { let stringLetter = String(letter) if stringLetter.rangeOfCharacter(from: vowels) != nil { vowelCount += 1 } else if stringLetter.rangeOfCharacter(from: consonants) != nil { consonantCount += 1 } } return (vowelCount, consonantCount) } The second solution builds on the first: if you’re going to work with string methods, why not just skip the character set entirely? There’s a perfectly good contains() method on strings that works just as well, so you can avoid allocating a character set at all: func challenge10b(input: String) -> (vowels: Int, consonants: Int) { www.hackingwithswift.com 37 let vowels = "aeiou" let consonants = "bcdfghjklmnpqrstvwxyz" var vowelCount = 0 var consonantCount = 0 for letter in input.lowercased() { let stringLetter = String(letter) if vowels.contains(stringLetter) { vowelCount += 1 } else if consonants.contains(stringLetter) { consonantCount += 1 } } return (vowelCount, consonantCount) } The third option is to do away with strings entirely: create arrays of vowel characters and consonant characters, then use the contains() method on the arrays to see if each letter matches. In code, you’d get this: func challenge10c(input: String) -> (vowels: Int, consonants: Int) { let vowels = "aeiou" let consonants = "bcdfghjklmnpqrstvwxyz" var vowelCount = 0 var consonantCount = 0 for letter in input.lowercased() { if vowels.contains(letter) { vowelCount += 1 www.hackingwithswift.com 38 } else if consonants.contains(letter) { consonantCount += 1 } } return (vowelCount, consonantCount) } There’s very little to separate each of these three answers in terms of performance, so go with whichever is most familiar to you. www.hackingwithswift.com 39 Challenge 11: Three different letters Difficulty: Tricky Write a function that accepts two strings, and returns true if they are identical in length but have no more than three different letters, taking case and string order into account. Sample input and output • The strings “Clamp” and “Cramp” would return true, because there is one letter difference. • The strings “Clamp” and “Crams” would return true, because there are two letter differences. • The strings “Clamp” and “Grams” would return true, because there are three letter differences. • The strings “Clamp” and “Grans” would return false, because there are four letter differences. • The strings “Clamp” and “Clam” would return false, because they are different lengths. • The strings “clamp” and “maple” should return false. Although they differ by only one letter, the letters that match are in different positions. Hints Hint #1: If you value your sanity, get both strings into arrays as quickly as possible. Hint #2: You probably want to use the enumerated() method on one array, to get the index and character at the same time. Hint #3: Your function should return false as soon as it reaches four differences; there’s no point continuing to check characters. Hint #4: Make sure you check the strings are the same size first, preferably using guard. www.hackingwithswift.com 40 Solution This problem isn’t hard as long as you convert your strings into character arrays – if you don’t, you need to advance through string indices, which is never pleasant and certainly hard to do during an interview. The simplest, clearest way to solve this challenge is like so: 1. Start with an early return in case the two strings have different lengths. 2. Create arrays out of both strings. 3. Initialize a differences counter to 0. 4. Loop over the first array, using enumerated() so we get the current index as well as each character. 5. Compare that character against the character at the same index in the other string array. 6. If they are different, add one to differences. 7. If as a result of that differences is now 4, return false. 8. On the other hand, if we get to the end of the array, it means we can return true. Something like this ought to do the trick: func challenge11(first: String, second: String) -> Bool { guard first.count == second.count else { return false } let firstArray = Array(first) let secondArray = Array(second) var differences = 0 for (index, letter) in firstArray.enumerated() { if secondArray[index] != letter { differences += 1 if differences == 4 { return false } } } www.hackingwithswift.com 41 return true } www.hackingwithswift.com 42 Challenge 12: Find longest prefix Difficulty: Tricky Write a function that accepts a string of words with a similar prefix, separated by spaces, and returns the longest substring that prefixes all words. Sample input and output • The string “swift switch swill swim” should return “swi”. • The string “flip flap flop” should return “fl”. Hints Hint #1: Start with components(separatedBy:) so you can check words with a loop. Hint #2: You’ll need a property for the prefix you’re currently checking as well as for the best prefix you’ve found so far. Hint #3: Make sure you use the hasPrefix() method. Solution I’ve watched some people blast through this code in a minute, and others struggle to finish in 30 minutes as they get into a mess of recursion. The key to a simple solution is the hasPrefix() method, which avoids the mess of string slicing: start with an empty string, then continue adding more letters from the first word until hasPrefix() fails for any of the other words. So: rather than trying to write a recursive function, you can solve this problem using an inner loop, like this: func challenge12(input: String) -> String { let parts = input.components(separatedBy: " ") www.hackingwithswift.com 43 guard let first = parts.first else { return "" } var currentPrefix = "" var bestPrefix = "" for letter in first { currentPrefix.append(letter) for word in parts { if !word.hasPrefix(currentPrefix) { return bestPrefix } } bestPrefix = currentPrefix } return bestPrefix } www.hackingwithswift.com 44 Challenge 13: Run-length encoding Difficulty: Taxing Write a function that accepts a string as input, then returns how often each letter is repeated in a single run, taking case into account. Tip: This approach is used in a simple lossless compression technique called run-length encoding. Sample input and output • The string “aabbcc” should return “a2b2c2”. • The strings “aaabaaabaaa” should return “a3b1a3b1a3” • The string “aaAAaa” should return “a2A2a2” Hints Hint #1: This would be quite straightforward in other languages using character lookahead, but that’s expensive in Swift thanks to grapheme clusters – String isn’t an array. Hint #2: To use the “Swifty” approach of looping over your string as-is, make sure you keep track of the current character, and its count, as you loop over the string. Hint #3: Alternatively, consider converting your string to a proper array that you can index into freely, so you can look ahead to compare letters. Solution There are two ways you could solve this, but in a stressful interview environment realistically there’s only one you’ll reach for: the “dumb”, brute force approach. I put dumb in quotes for a reason – more on that later. This approach would use an algorithm like this: www.hackingwithswift.com 45 • Create a currentLetter variable that contains an optional Character, as well as a counter integer and a return value string. • Loop through every letter in the input string. • If the letter is equal to our current letter, add one to the counter. • Otherwise, if currentLetter has a value it means we already had a letter and it’s about to change, so append it to the return string. • Regardless, update currentLetter to be the new letter, and reset the counter to 1. • Once the loop finishes, append currentLetter to the return string along with the counter, then return it. That last step is easily forgotten – because the return string is only modified when the letter changes, the last letter sequence won’t be added unless we do it by hand. Putting that into code: func challenge13a(input: String) -> String { var currentLetter: Character? var returnValue = "" var letterCounter = 0 for letter in input { if letter == currentLetter { letterCounter += 1 } else { if let current = currentLetter { returnValue.append("\(current)\(letterCounter)") } currentLetter = letter letterCounter = 1 } } if let current = currentLetter { www.hackingwithswift.com 46 returnValue.append("\(current)\(letterCounter)") } return returnValue } An alternative solution, which is probably one you would be more likely to use if you had come to Swift after learning a different language, would be to use character look ahead: if the next character is different to the current one, or if we’re about to hit the end of the array, then modify the return value and reset the counter. This alternative solution doesn’t come naturally to Swift developers, because indexing into strings is expensive in Swift thanks to the grapheme cluster system. However, you could convert the Swift character index into an array, then use that. This would give you the following solution: func challenge13b(input: String) -> String { var returnValue = "" var letterCounter = 0 var letterArray = Array(input) for i in 0 ..< letterArray.count { letterCounter += 1 if i + 1 == letterArray.count || letterArray[i] != letterArray[i + 1] { returnValue += "\(letterArray[i])\(letterCounter)" letterCounter = 0 } } return returnValue } www.hackingwithswift.com 47 That’s a valid solution, but does it beat the “dumb” one? Well, it depends what you mean by “beat”: it’s certainly less code, and it avoids the if let repetition, but it runs slower – about 15% so, in my tests, despite containing much less code. So, I think the “dumb” solution is superior: not only does it run faster, but it’s also easier to figure out on the fly while you’re being tested! www.hackingwithswift.com 48 Challenge 14: String permutations Difficulty: Taxing Write a function that prints all possible permutations of a given input string. Tip: A string permutation is any given rearrangement of its letters, for example “boamtw” is a permutation of “wombat”. Sample input and output • The string “a” should print “a”. • The string “ab” should “ab”, “ba”. • The string “abc” should print “abc”, “acb”, “bac”, “bca”, “cab”, “cba”. • The string “wombat” should print 720 permutations. Hints Hint #1: Your function will need to call itself. Hint #2: The number of lines printed should be the factorial of the length of your string, e.g. “wombat” has six characters, so will have 6! permutations: 6 x 5 x 4 x 3 x 2 x 1, or 720. Hint #3: You’ll find it easiest to convert the string to a character array for easier indexing. Hint #4: Each time your function is called, it should loop through all letters in the string so that all combinations are generated. Hint #5: You can slice arrays using strArray[0...3]. Hint #6: You can convert string array slices into strings just by using an initializer: String(strArray[0...3]). Solution www.hackingwithswift.com 49 This is a recursive function with lots of looping, but the nature of factorials is that the loops get smaller each time. Given the input string “wombat”, the first time the function is called you’ll need to loop from 0 up to 5, calling the recursive function each time. So, initially you’ll pick “w” as it’s the first letter, and in you go to the recursive function – but this time there are only five letters to loop over, so you choose “o”, and go into the function again, etc. Eventually you spell out “wombat”, which is the result of choosing the first remaining letter each time. But now that you’ve reached the deepest point of the recursion, you back up a level: when you had “womb” it chose the first letter in the remainder (“at”) to make “wombat”, but now that path has been explored it should choose the second remaining letter (“t”) to make “wombt”, at which point the only remaining letter now is “a” to make “wombta”. Again the recursion has maxed out, so now it will need to go back one level further: when it was at “wom” the remainder was “bat” so it chose the first letter, but now it should choose the second, to make “woma”, with “bt” as remainder. On the first pass it will choose “b” first then “t” (making “womabt”), and on the second it will choose “t” then “b” (making “womatb”). That subset of the path is now maxed out, so it will wind back to “wom” and “bat” and choose the third letter, to make “womt” with “ba” as remainder. So it will get “womtba” then “womtab”, and so on, and so on. That’s the algorithm. It sounds clunky when explained step by step, but trust me: a CPU flies through this. Here it is in code: func challenge14(string: String, current: String = "") { let length = string.count let strArray = Array(string) if (length == 0) { // there's nothing left to re-arrange; print the result print(current) print("******") } else { print(current) www.hackingwithswift.com 50 // loop through every character for i in 0 ..< length { // get the letters before me let left = String(strArray[0 ..< i]) // get the letters after me let right = String(strArray[i+1 ..< length]) // put those two together and carry on challenge14(string: left + right, current: current + String(strArray[i])) } } } Note: the print("******") and second print(current) call are there to help you see how the function works; they serve no functional purpose. www.hackingwithswift.com 51 Challenge 15: Reverse the words in a string Difficulty: Taxing Write a function that returns a string with each of its words reversed but in the original order, without using a loop. Sample input and output • The string “Swift Coding Challenges” should return “tfiwS gnidoC segnellahC”. • The string “The quick brown fox” should return “ehT kciuq nworb xof”. Hints Hint #1: You should start by converting the string into an array by separating on spaces. Hint #2: You can reverse the characters in a string by calling reversed() on it. Hint #3: You can create a string from a character array. Hint #4: You want to convert an array of left to right strings into an array of right to left strings, all without using a loop - this is a perfect use for map(). Hint #5: Once you have an array of reversed strings, you can create a single string using joined(). Solution The requirement not to use a loop is what makes this a taxing challenge; if you could just use for-in then it would be easy or tricky depending on who spoke to. The only sensible way to solve this is using functional programming: the need to convert from one kind of array (“words written left to right”) to another (“words written right to left”) is the perfect use case for map(), so most of what remains is creating the array by splitting on spaces then recreating the string once you’ve reversed the words. www.hackingwithswift.com 52 spaces then recreating the string once you’ve reversed the words. Here’s my solution: func challenge15(input: String) -> String { let parts = input.components(separatedBy: " ") let reversed = parts.map { String($0.reversed()) } return reversed.joined(separator: " ") } www.hackingwithswift.com 53 Chapter 2 Numbers www.hackingwithswift.com 54 Challenge 16: Fizz Buzz Difficulty: Easy Write a function that counts from 1 through 100, and prints “Fizz” if the counter is evenly divisible by 3, “Buzz” if it’s evenly divisible by 5, “Fizz Buzz” if it’s even divisible by three and five, or the counter number for all other cases. Sample input and output • 1 should print “1” • 2 should print “2” • 3 should print “Fizz” • 4 should print “4” • 5 should print “Buzz” • 15 should print “Fizz Buzz” Hints Hint #1: You’ll need to use modulus: %. Hint #2: Check for the “Fizz Buzz” case first, because that’s most specific. Hint #3: Remember to use the closed range operator to include the number 100 at the end. Solution This is the holotype of interview questions, so it was inevitable it would be in here somewhere. Sadly, people really do fail it – I’ve seen it myself – which ought to be impossible, so I hope you didn’t just skip by blithely! A simple solution looks like this: func challenge16a() { www.hackingwithswift.com 55 for i in 1...100 { if i % 3 == 0 && i % 5 == 0 { print("Fizz Buzz") } else if i % 3 == 0 { print("Fizz") } else if i % 5 == 0 { print("Buzz") } else { print(i) } } } You could make it slightly more efficient by nesting the “Fizz Buzz” case inside two if statements rather than one: func challenge16b() { for i in 1...100 { if i % 3 == 0 { if i % 5 == 0 { print("Fizz Buzz") } else { print("Fizz") } } else if i % 5 == 0 { print("Buzz") } else { print(i) } } } Using this approach, you don’t end up evaluating i % 3 twice. www.hackingwithswift.com 56 You could be a bit more fancy using forEach and ternary operators, but you’re basically sacrificing readability for smugness: func challenge16c() { (1...100).forEach { print($0 % 3 == 0 ? $0 % 5 == 0 ? "Fizz Buzz" : "Fizz" : $0 % 5 == 0 ? "Buzz" : "\($0)") } } Note that I used "\($0)" rather than String($0) – this is a pet hate of mine, but that code is so complex that Swift actually fails to compile if I used String($0)! www.hackingwithswift.com 57 Challenge 17: Generate a random number in a range Difficulty: Easy Write a function that accepts positive minimum and maximum integers, and returns a random number between those two bounds, inclusive. Sample input and output • Given minimum 1 and maximum 5, the return values 1, 2, 3, 4, 5 are valid. • Given minimum 8 and maximum 10, the return values 8, 9, 10 are valid. • Given minimum 12 and maximum 12, the return value 12 is valid. • Given minimum 12 and maximum 18, the return value 7 is invalid. Hints Hint #1: There are lots of ways to generate random numbers in Swift; you’ll be judged – silently or openly – on your choice. Hint #2: Keep in mind that lots of random number generators generate from 0 up to a certain point so you’ll need to write code to count from an arbitrary number upwards. Hint #3: Also remember that lots of random number generators generate up to but excluding the maximum, so you should add 1 to make sure your tests pass. Hint #4: Take a look at arc4random_uniform(). Solution There are several choices for random number generators, and your choice says a lot about your skill level. Very roughly: • If you used rand() you probably came from a C background, or don’t generally care about randomness. www.hackingwithswift.com 58 • If you used GameplayKit, you’re either fairly new to iOS development, or particularly interested in the random number generation shaping GameplayKit offers. • If you used arc4random() you’re showing some awareness of half-decent random number generator, but are unaware of – or uninterested in – modulo bias. • If you used arc4random_uniform(), then you’re showing some serious chops. Of the four options, arc4random_uniform() is preferred amongst developers, because it generates suitably random numbers for most purposes, it doesn’t require seeding, it isn’t prone to modulo bias, and it isn’t restricted to Apple platforms – it’s a commonly used C function that is well understood. Using arc4random_uniform() has three hiccups in Swift: it generates numbers from 0 up to an upper bound, it excludes the upper bound rather than including it, and it uses UInt32 rather than Int, so you need some typecasting. We can fix all those problems and still write just one line of code to solve the challenge: func challenge17(min: Int, max: Int) -> Int { return Int(arc4random_uniform(UInt32(max - min + 1))) + min } www.hackingwithswift.com 59 Challenge 18: Recreate the pow() function Difficulty: Easy Create a function that accepts positive two integers, and raises the first to the power of the second. Tip: If you name your function myPow() or challenge18(), you’ll be able to use the built-in pow() for your tests. The built-in pow() uses doubles, so you’ll need to typecast. Sample input and output • The inputs 4 and 3 should return 64, i.e. 4 multiplied by itself 3 times. • The inputs 2 and 8 should return 256, i.e. 2 multiplied by itself 8 times. Hints Hint #1: You don’t need any hints to solve this one. Hint #2: Oh, alright: here’s a hint: you can either use a loop or, if you’re feeling fancy, use a recursive function. Hint #3: Here’s another: you could use guard or precondition() to ensure both numbers are positive. Solution This ought to have been the easiest of easy challenges, because all you’re doing is multiplying a number against itself a fixed number of times. In its most simple form, you can solve the challenge like this: func challenge18a(number: Int, power: Int) -> Int { guard number > 0, power > 0 else { return 0 } var returnValue = number www.hackingwithswift.com 60 for _ in 1..<power { returnValue *= number } return returnValue } Like I said in the hints, you could also solve this challenge using a recursive function. To do that, make the function multiply its input number by the return value of calling itself, subtracting 1 from the power parameter each time, like this: func challenge18b(number: Int, power: Int) -> Int { guard number > 0, power > 0 else { return 0 } if power == 1 { return number } return number * challenge18b(number: number, power: power - 1) } www.hackingwithswift.com 61 Challenge 19: Swap two numbers Difficulty: Easy Swap two positive variable integers, a and b, without using a temporary variable. Sample input and output • Before running your code a should be 1 and b should be 2; afterwards, b should be 1 and a should be 2. Hints Hint #1: There are lots of ways to solve this, but probably the easiest to remember is using tuples. Hint #2: Alternatively, try using the global Swift function swap(). Hint #3: If you’re feeling fancy, you can solve this problem with arithmetic. Hint #4: If you’re feeling fancy and want to demonstrate your bit manipulation skills, you can also solve this problem using bitwise XOR. Solution This is a favorite question of lazy interviewers – people who don’t want to spend an hour of life coming up with genuinely interesting, useful questions that explore real Swift knowledge. It used to be important in Ye Olde Days when every byte mattered, but in a world where Slack on macOS happily chews through 400MB of RAM just idling this test is more a curiosity than anything else. Still, this question does one have benefit, which is that are idiomatic solutions for Swift developers – i.e., Swifty ways to solve it. Let’s look at the basic solution first, which looks like this: www.hackingwithswift.com 62 Let’s look at the basic solution first, which looks like this: a = a + b b = a - b a = a - b That’s the solution you’d use in most languages, and it works fine. If you’re feeling smart, you can also use XOR like this: a = a ^ b b = a ^ b a = a ^ b I think that solution is fractionally easier to remember, because it uses the same operator all three times. So, those are the standard solutions, but Swift gives us two alternatives. First, there’s a global swap() function that swaps two values of the same type, like this: swap(&a, &b) The swap() function is micro-optimized to be as fast as possible, so you’ll see it used extensively in sorting algorithms. A second idiomatic solution is to use tuples. This delivers a beautifully solution to the challenge that is also undeniably Swifty: (a, b) = (b, a) Marvelous. www.hackingwithswift.com 63 Challenge 20: Number is prime Difficulty: Tricky Write a function that accepts an integer as its parameter and returns true if the number is prime. Tip: A number is considered prime if it is greater than one and has no positive divisors other than 1 and itself. Sample input and output • The number 11 should return true. • The number 13 should return true. • The number 4 should return false. • The number 9 should return false. • The number 16777259 should return true. Hints Hint #1: You should start with a brute force approach: loop through every number from 2 up to one less than the input number, and check whether the input number divides into it. Hint #2: You can shrink the search space by searching up to a smaller number – what’s the highest it could be? Hint #3: There’s no point searching higher than the square root of your input number, rounding up. Solution There’s a naïve solution to this problem, but it has terrible performance characteristics – there’s a reason I included 16,777,259 in the list of sample input and output. The naïve solution looks like this: www.hackingwithswift.com 64 func challenge20a(number: Int) -> Bool { guard number >= 2 else { return false } for i in 2 ..< number { if number % i == 0 { return false } } return true } That counts from 2 up to one less than the input number, and returns false if the input number divides equally into i. That works correctly. It has a guard statement at the front because the numbers 1 and lower are not prime by definition. The problem with this function is that it’s computationally expensive: 16,777,259 is a prime number, so this solution will divide from 2 up to 16,777,258 and find that none of them work before deciding that the number is prime. Consider this: if the number n is not prime, it means it can be reached by multiplying two factors, x and y. If both of those numbers were greater than the square root of n, then x * y would be greater than n, which is not possible. So, we can be sure that at least one of x or y is less than or equal to the square root of n. As a result of this, we can dramatically reduce our search space: rather than counting from 2 up to 16,777,259, we can square root the number and round up, to get 4097, then search up to there and no further. Remember, we don’t have to find both numbers that multiply to make n, just one of them, because if we find one – and it isn’t 1 or itself – it means n is not prime. So, we could write a second solution like this: func challenge20b(number: Int) -> Bool { guard number >= 2 else { return false } guard number != 2 else { return true } www.hackingwithswift.com 65 let max = Int(ceil(sqrt(Double(number)))) for i in 2 ... max { if number % i == 0 { return false } } return true } Because this second solution uses the closed range operator, ..., rather than the half-open range operator, ..<, it’s important to add the second guard check at the top so that 2 doesn’t evaluate incorrectly. This second solution performs significantly faster for primes such as 16,777,259, because rather than around 16 million searches you’re now doing around 4000. www.hackingwithswift.com 66 Challenge 21: Counting binary ones Difficulty: Tricky Create a function that accepts any positive integer and returns the next highest and next lowest number that has the same number of ones in its binary representation. If either number is not possible, return nil for it. Sample input and output • The number 12 is 1100 in binary, so it has two 1s. The next highest number with that many 1s is 17, which is 10001. The next lowest is 10, which is 1010. • The number 28 is 11100 in binary, so it has three 1s. The next highest number with that many 1s is 35, which is 100011. The next lowest is 26, which is 11010. Hints Hint #1: You can find the binary representation of an integer by converting it to a string – look for a “radix” initializer. Hint #2: You should be using radix 2, which is binary. Hint #3: Your return value ought to be (nextHighest: Int?, nextLowest: Int?). Hint #4: You can count the 1s in a stringified number by using filter() on its letters property. Hint #5: Don’t be afraid to duplicate code while you’re working – you need to search up and down for the same thing, so start with duplication then refactor. Hint #6: You can’t create ranges where the end is higher than the start. Instead, create a forwards range then reverse it. www.hackingwithswift.com 67 Solution This is a classic computing science problem, although I have to admit Swift makes it quite easy thanks to its range of String initializers. First and most importantly, you get the binary representation of an integer like this: let binaryString = String(someNumber, radix: 2) With that done, you can count the 1s by filtering by character and counting the resulting array: let numberOfOnes = binaryString.filter { (char: Character) -> Bool in char == "1" }.count All that leaves is finding the next highest and lowest, which can be done by counting upwards or downwards until the you back the same number of ones. Here’s the complete solution: func challenge21a(number: Int) -> (nextHighest: Int?, nextLowest: Int?) { let targetBinary = String(number, radix: 2) let targetOnes = targetBinary.filter { (char: Character) -> Bool in char == "1" }.count var nextHighest: Int? = nil var nextLowest: Int? = nil for i in number + 1...Int.max { let currentBinary = String(i, radix: 2) let currentOnes = currentBinary.filter { (char: Character) -> Bool in char == "1" }.count if targetOnes == currentOnes { nextHighest = i break www.hackingwithswift.com 68 } } for i in (0 ..< number).reversed() { let currentBinary = String(i, radix: 2) let currentOnes = currentBinary.filter { (char: Character) -> Bool in char == "1" }.count if targetOnes == currentOnes { nextLowest = i break } } return (nextHighest, nextLowest) } Looking at that code, the duplication of binary counting does rather stick out. We can refactor it into a nested function to remove the duplication, although it only reduces the overall size of the solution by a little: func challenge21b(number: Int) -> (nextHighest: Int?, nextLowest: Int?) { func ones(in number: Int) -> Int { let currentBinary = String(number, radix: 2) return currentBinary.filter { (char: Character) -> Bool in char == "1" }.count } let targetOnes = ones(in: number) var nextHighest: Int? = nil var nextLowest: Int? = nil for i in number + 1...Int.max { www.hackingwithswift.com 69 if ones(in: i) == targetOnes { nextHighest = i break } } for i in (0 ..< number).reversed() { if ones(in: i) == targetOnes { nextLowest = i break } } return (nextHighest, nextLowest) } www.hackingwithswift.com 70 Challenge 22: Binary reverse Difficulty: Tricky Create a function that accepts an unsigned 8-bit integer and returns its binary reverse, padded so that it holds precisely eight binary digits. Tip: When you get the binary representation of a number, Swift will always use as few bits as possible – make sure you pad to eight binary digits before reversing. Sample input and output • The number 32 is 100000 in binary, and padded to eight binary digits that’s 00100000. Reversing that binary sequence gives 00000100, which is 4. So, when given the input 32 your function should return 4. • The number 41 is 101001 in binary, and padded to eight binary digits that 00101001. Reversing that binary sequence gives 10010100, which is 148. So, when given the input 41 your function should return 148. • It should go without saying that your function should be symmetrical: when fed 4 it should return 32, and when fed 148 it should return 41. Hints Hint #1: You can get the binary representation of an integer using String(someNumber, radix: 2). Hint #2: You can get the decimal integer equivalent of a string containing binary using Int(someString, radix: 2) – but be warned that will given you an optional integer. Hint #3: To pad the input number’s binary representation so that it holds eight digits, use the String(repeating:count:) initializer. Hint #4: You can reverse a character array then create a new string from it. www.hackingwithswift.com 71 Solution As long as you’re comfortable converting to and from binary, this challenge is not likely to pose any problems for you. In fact, apart from converting between binary and decimal, the only interesting parts of the problem are calculating how much zero padding to apply and reversing the binary representation. Calculating and adding the padding can be done by subtracting the current character count from 8, like this: let paddingAmount = 8 - binary.count let paddedBinary = String(repeating: "0", count: paddingAmount) + binary Reversing the padding binary representation is as easy as calling reversed() on the binary string, then creating a new string out of it, like this: let reversedBinary = String(paddedBinary.reversed()) With all that in mind, here’s a complete solution: func challenge22(number: UInt) -> UInt { let binary = String(number, radix: 2) let paddingAmount = 8 - binary.count let paddedBinary = String(repeating: "0", count: paddingAmount) + binary let reversedBinary = String(paddedBinary.reversed()) return UInt(reversedBinary, radix: 2)! } www.hackingwithswift.com 72 Challenge 23: Integer disguised as string Difficulty: Tricky Write a function that accepts a string and returns true if it contains only numbers, i.e. the digits 0 through 9. Sample input and output • The input “01010101” should return true. • The input “123456789” should return true. • The letter “9223372036854775808” should return true. • The letter “1.01” should return false; “.” is not a number. Hints Hint #1: You can create integers from strings, and Swift will return nil if the conversion failed. Hint #2: The number “9223372036854775808” is a precise choice, not just a random string of numbers. Hint #3: Chances are Swift’s integer type will be 64-bit for you, and it’s signed, which means its maximum value is 2 to the power of 63, minus 1, i.e. 9223372036854775807 – that’s one less than the test case you’ve been given. Hint #4: You should look into inverted character sets. Hint #5: Some languages write numbers differently from English. Solution There are lots of ways of solving this challenge, but if you want to solve it clearly, concisely, and correctly then your options are more limited. There are two big gotchas when dealing with numbers. First, integers have a ceiling, beyond www.hackingwithswift.com 73 which they refuse to work. In Swift, the ceiling is 9,223,372,036,854,775,807, which is the largest number that can be represented by a signed 64-bit integer. The third test case I gave you was one higher than the maximum signed 64-bit integer, which was intentional. However, if you recognized that, you could have switched an unsigned integer and passed the challenge by writing code like this: func challenge23a(input: String) -> Bool { return UInt(input) != nil } I can’t think of a faster, simpler way to solve this challenge. However, it’s also a bit of a fudge – all unsigned integers do is double the largest number you can address, so it would still fail if you added a 0 to the end of the existing big number. From there you might have migrated to using Int() to compare each individual letter in the string, like this: func challenge23b(input: String) -> Bool { for letter in input { if Int(String(letter)) == nil { return false } } return true } As you can see, you can create integers from strings, and strings from characters, but you can’t create integers from characters – d’oh. Still, though, this code runs fast: it bails out as soon as any non-matching letter is found, and only returns true if all digits converted to an integer successfully. An alternative solution is to use the rangeOfCharacter(from:) method, which lets you provide a character set and returns the location – if any – of those characters in the search www.hackingwithswift.com 74 string. In our case we know the numbers we want (digits), so we just need to get the inverse of that set using something like this: func challenge23c(input: String) -> Bool { return input.rangeOfCharacter(from: CharacterSet.decimalDigits.inverted) == nil } This solution is interesting because it highlights another curiosity of numbers: Apple, being the smart company it is, considers “decimal digits” to include numerals from other languages. I’m not sure how well this will print on your computer, but “٢” is the Arabic-Indic numeral 2. If you use the decimalDigits character set, it will include Arabic-Indic numerals as well as the numbers 0 through 9. While none of the test cases used Arabic-Indic numerals, if you wanted to conform strictly to the requirements for this challenge then you could use something like this: func challenge23d(input: String) -> Bool { return input.rangeOfCharacter(from: CharacterSet(charactersIn: "0123456789").inverted) == nil } Perhaps now you understand why I graded this challenge as tricky rather than easy! www.hackingwithswift.com 75 Challenge 24: Add numbers inside a string Difficulty: Tricky Given a string that contains both letters and numbers, write a function that pulls out all the numbers then returns their sum. Sample input and output • The string “a1b2c3” should return 6 (1 + 2 + 3). • The string “a10b20c30” should return 60 (10 + 20 + 30). • The string “h8ers” should return “8”. Hints Hint #1: Creating an integer from a string returns Int? – nil if it was a number, or an Int otherwise. Hint #2: Use the nil coalescing operator, ??, to strip out any unwanted optionality. Hint #3: Don’t forget to catch trailing numbers, i.e. where the string ends with a number. Hint #4: You could solve this using regular expressions, in which case I’d grade it as taxing rather than tricky. Solution This is a personal favorite problem of mine – not because of its complexity, but more because of its usefulness for weeding out developers who have exaggerated a little on their résumé. You see, you can take a naïve approach to this challenge and get a solution that works efficiently with very little code. However, to do that you need to know how to convert characters to strings, and strings to integers, as well as how to use nil coalescing if you want to make the code neat. As a result, someone who has puffed up their résumé will find this harder www.hackingwithswift.com 76 than it ought to be, and their code will usually show them up. On the flip side, an experienced Swift developer who is out to impress might try and solve this using regular expressions, in which case now they have two challenges: summing the numbers, and making regular expressions in Swift not suck. Let’s take a look at the simple solution first, which is hopefully similar to the one you came up with. The algorithm is this: 1. Create an empty string that represents the current number being read. 2. Create a sum value that contains the total of all numbers so far, initialized to 0. 3. Loop through every letter in the input string, converting the character to a String. 4. If we can convert that string to an integer, add it to the current number string. 5. Otherwise it’s not a number, so convert the current number string to an integer, or 0 if it’s an invalid integer, add it to the sum and clear the current number string. 6. Finally, convert any remaining value in the current number string to an integer, and add it to sum. 7. Return sum. Here’s that list translated into code: func challenge24a(string: String) -> Int { var currentNumber = "" var sum = 0 for letter in string { let strLetter = String(letter) if Int(strLetter) != nil { currentNumber += strLetter } else { sum += Int(currentNumber) ?? 0 currentNumber = "" } } www.hackingwithswift.com 77 sum += Int(currentNumber) ?? 0 return sum } Solving the same problem using regular expressions is a real nightmare, and highlights the worst of Swift’s string handling problems. You see, creating a regex requires a Swift string going in, but works entirely using NSRange rather than Swift’s string ranges. This means you need to use string.utf16.count to calculate the size of the range, and using string.count will introduce subtle bugs in your code. However, it gets really grim when trying to read the contents of each match. NSRegularExpression returns an array of NSTextCheckingResult, which contains the NSRange of each match but not the contents, so you need to read the substring using that range… which isn’t possible in Swift, because it uses string ranges rather than NSRange. *Sigh*… Anyway, if you want to prove you have more time than sense, here’s how to solve it using regular expressions: 1. Create the regex (\\d+). 2. Use its matches() method to pull out an array of NSTextCheckingResult objects. 3. Typecast your input string as NSString to get a substring() method that works with the NSRange provided by each NSTextCheckingResult. 4. Send that substring into Int() to get an optional integer. 5. Strip out the optionality, then sum the integers. You can use flatMap() and reduce() for steps 4 and 5 if you really have something to prove, giving code like this: func challenge24b(string: String) -> Int { let regex = try! NSRegularExpression(pattern: "(\\d+)", options: []) let matches = regex.matches(in: string, options: [], range: www.hackingwithswift.com 78 NSRange(location: 0, length: string.utf16.count)) let allNumbers = matches.flatMap { Int((string as NSString).substring(with: $0.range)) } return allNumbers.reduce(0, +) } That code runs significantly slower than the previous solution, largely because of the cost of creating the regex. If you were to run this method many times you should move the NSRegularExpression creation outside the method, at which point it runs “only” about half the speed of the previous solution. www.hackingwithswift.com 79 Challenge 25: Calculate a square root by hand Difficulty: Taxing Write a function that returns the square root of a positive integer, rounded down to the nearest integer, without using sqrt(). Sample input and output • The number 9 should return 3. • The number 16777216 should return 4096. • The number 16 should return 4. • The number 15 should return 3. Hints Hint #1: You can brute force this using a loop count from 0 up to the input number. Hint #2: A more efficient solution is using a binary search. Hint #3: A rounded-down integer square root will never be more than half its square. Hint #4: If you consider half your input number to be your upper bound, then calculate the mid-point between that and a lower bound that’s initially 0 (i.e., input number / 4), you can check whether that mid-point squared gives your input. Hint #5: If the mid-point is too low, make it the new lower bound then repeat. If the mid-point is too high, make it the new higher bound then repeat. Hint #6: Using this technique, you should be able to loop until you find the best answer. Solution Like most coding interview problems, this one has a naïve solution, a smart solution, and a sneaky solution. www.hackingwithswift.com 80 sneaky solution. The naïve solution is trivial: loop from 1 up to one higher than half the test number, checking to see whether that number squared is greater than the test number. If it is, return the number directly below. We need to add a special case for 1, because otherwise the code returns 0. Here’s the code: func challenge25a(input: Int) -> Int { guard input != 1 else { return 1 } for i in 0 ... input / 2 + 1 { if i * i > input { return i - 1 } } return 0 } However, consider the second test case, which is calculating the square root of 16,777,216. That’s going to require 4097 multiplications before returning the correct response, which is massively wasteful. A smarter solution is to use a binary search, which massively reduces the search space. Given the input number 9 it works like this: • Start by specifying a high bound of half the input number plus one, rounding down so for the input number 10 that gives us a high bound of 6. • Now specify a low bound, which will be zero to begin with. • Calculate the mid-point of the two, which is equal to half of upper - lower, plus the lower. So that’s lower + ((upper - lower) / 2), which is 0 + ((6 - 0) / 2), which is 0 + (6 / 2), which is 0 + 3 – so our mid-point is 3. • You then square your mid-point (3 x 3 is 9) and compare it against the input number. • If the square is less than the input it means that all the numbers from your low bound up to the mid-point are also too low, and so don’t need to be checked, so you can set www.hackingwithswift.com 81 the new low bound to be equal to your mid-point, then repeat. • If the square is higher than the input it means that all the numbers from the mid-point up to the upper bound are also too high, and so don’t need need to be checked, so you can set the new high bound to be equal to your mid-point, then repeat. • If the square is equal to the input, then you have your answer. • If you find that your lower bound + 1 is greater than or equal to your upper bound it means you’ve overshot the mark, so you should return the lower bound. I realize that sounds complicated, but it’s actually remarkably simple: “select the range it must be in, then try the middle of it. If you were too high you can eliminate the upper half of the range; if you were too low you can eliminate the lower half of the range. So, eliminate one half of the range then split the remaining half… and repeat.” This search technique is sometimes called a binary chop, because you halve your search space with each check. So to get 16,777,216 you halve to a range of 8,388,608, then halve that to a range of 4,194,304, then halve to a range of 2,097,152, then 1,048,576, then 524,288, then 262,144, then 131,072, then 65,536, and so on. Using this approach takes only 11 loops to figure out the square root of 16,777,216, compared to 4097 loops using the naïve method, so it runs a great deal faster. Nice! Here’s the code: func challenge19b(input: Int) -> Int { guard input != 1 else { return 1 } var lowerBound = 0 var upperBound = 1 + input / 2 while lowerBound + 1 < upperBound { let middle = lowerBound + ((upperBound - lowerBound) / 2) let middleSquared = middle * middle if middleSquared == input { return middle } else if middleSquared < input { www.hackingwithswift.com 82 lowerBound = middle } else { upperBound = middle } } return lowerBound } So, that’s the naïve approach and the smart approach, but there’s also a sneaky approach: the challenge is to calculate the square root of an integer without using sqrt(), but it didn’t say not to use the pow() function. If you request a number raised to the power of 0.5, you get its square root. The calculation isn’t precisely the same – a true square root will yield ever so slightly different results, and will be optimized for that task – but given that we’re working with integers the two will be identical. So, here’s how to solve the challenge using pow(), which will run so fast it’s hard to benchmark meaningfully: func challenge25c(input: Int) -> Int { return Int(floor(pow(Double(input), 0.5))) } Note the extensive typecasting, which is unavoidable I’m afraid – pow() works with doubles, not integers, so we need to typecast and floor before converting to an integer for the return value. www.hackingwithswift.com 83 Challenge 26: Subtract without subtract Difficulty: Taxing Create a function that subtracts one positive integer from another, without using -. Sample input and output • The code challenge26(subtract: 5, from: 9) should return 4. • The code challenge26(subtract: 10, from: 30) should return 20. Hints Hint #1: In your code you can use any other operator, or any other number, positive or negative, just not the - operator. Hint #2: Swift has a full set of bitwise operators – operators that manipulate the binary digits of a number. Hint #3: You could try using bitwise NOT, which is ~. Solution This question is looking for a basic grasp of mathematics: if you can’t subtract one number from another using -, how can you do it? The answer is, of course, to flipping the sign on the number then adding it – i.e., rather than subtracting 10, you add -10. You could make an argument that flipping the sign on a number using - is different to using - for subtraction. That would give you a solution like this: func challenge26a(subtract: Int, from: Int) -> Int { return from + -subtract } Technically, though, - in that instance is the unary minus operator, so it’s the same thing, www.hackingwithswift.com 84 really. A solution that fits the spirit of the challenge a bit better is multiplying by -1, like this: func challenge26b(subtract: Int, from: Int) -> Int { return from + -1 * subtract } That’s a negative number, not the unary minus operator, so that fits both the letter and spirit of the challenge. However, for maximum effect, you can solve this challenge without typing - at all – well, excluding the -> used in declaring the function’s return value. This technique depends on the ~ operator (a tilde), which is bitwise NOT. It causes all the binary digits in a number to be flipped. If this is not new to you, skip ahead – I’m going to take a brief tangent into what it does behind the scenes. 8-bit integers are stored using eight binary digits, where the right-most digit stores 1, the second right-most stores 2, then 4, 8, 16, 32, and 64. Depending on whether each of those binary digits are 1 or 0, Swift can represent numbers from 0 (all zeros) to 127 (all 1s). However, notice there are only seven of them – 1, 2, 4, 8, 16, 32, and 64 – and we’re talking about 8-bit integers, so there ought to be 8, right? Right. That eighth digit is how we track whether a number is positive or negative. If the left-most digit is 0, all the digits on the right add up to a positive number. If the left-most digit is 1, all the digits on the right represent a negative number. Here are some examples of positive numbers: • 00000000 is 0 • 00000001 is 1 • 00000010 is 2 • 00000011 is 3 • 00000100 is 4 • 00000101 is 5 www.hackingwithswift.com 85 • 01000000 is 64 • 01111111 is 127 So, as long as that first digit is 0, the rest of the digits form a positive number. Things are a little tricksier when you use negative numbers. Whereas the highest positive number starts with a zero and is followed by all ones, the lowest negative number starts with a one and is followed by all zeros – it’s the exact opposite. So: • 00000000 is still 0 • 10000000 is -128 • 10000001 is -127 • 10000010 is -126 • 10000011 is -125 • 10000100 is -124 • 10000101 is -123 • 11000000 is -64 • 11111111 is -1 Now, all this becomes important when you flip the 1s and 0s. For example, 01000000 is 64, but if you make the 0s into 1s and the 1s into 0s you get 10111111, which is -65. Similarly, if you take 01010101, which is 85, and flip the bits, you get 10101010, which is -86. Both times the negative number is identical to add one to the number and flipping its sign, i.e. making positive negative. And that’s where our solution comes in – check this out: func challenge26c(subtract: Int, from: Int) -> Int { return from + (~subtract + 1) } So, to subtract one number from another, we flip the bits (64 becomes -65) then add one (to make -64), and add that to our input number to make subtraction. Done! www.hackingwithswift.com 86 Chapter 3 Files www.hackingwithswift.com 87 Challenge 27: Print last lines Difficulty: Easy Write a function that accepts a filename on disk, then prints its last N lines in reverse order, all on a single line separated by commas. Sample input and output Here is your test input file: Antony And Cleopatra Coriolanus Cymbeline Hamlet Julius Caesar King Lear Macbeth Othello Twelfth Night • If asked to print the last 3 lines, your code should output “Twelfth Night, Othello, Macbeth”. • If asked to print the last 100 lines, your code should output “Twelfth Night, Othello, Macbeth, King Lear, Julius Caesar, Hamlet, Cymbeline, Coriolanus, Antony and Cleopatra”. • If asked to print the last 0 lines, your could should print nothing. Hints Hint #1: Use the contentsOfFile initializer to pull in the text, then components(separatedBy:) to create an array of lines. Hint #2: Arrays have a built-in reverse() method that flip them around in-place. www.hackingwithswift.com 88 Hint #3: You need to print the last N lines, but of course you don’t want to read beyond the size of the array. Make sure you use the min() function to choose the lesser of the two. Solution This is a nice and simple challenge, and is the kind of thing you’ll hit in real-world coding all the time. Hopefully, then, you found it a breeze: use contentsOfFile to get a multiline String of text, use components(separatedBy: "\n") to split it into an array of lines, then reverse the array and loop over N items. In order to fully satisfy the challenge’s requirements – and to add safety to make sure we don’t read outside the array! – we also need to add a couple of guard statements to eliminate bad input, and use min() to pick the lowest number between the requested line count and the number of lines in the array. Here’s the code: func challenge27(filename: String, lineCount: Int) { guard let input = try? String(contentsOfFile: filename) else { return } var lines = input.components(separatedBy: "\n") guard lines.count > 0 else { return } lines.reverse() for i in 0 ..< min(lines.count, lineCount) { print(lines[i]) } } www.hackingwithswift.com 89 Challenge 28: Log a message Difficulty: Easy Write a logging function that accepts accepts a path to a log file on disk as well as a new log message. Your function should open the log file (or create it if it does not already exist), then append the new message to the log along with the current time and date. Tip: It’s important that you add line breaks along with each message, otherwise the log will just become jumbled. Sample input and output • If the file does not exist, running your function should create it and save the new message. • If it does exist, running your function should load the existing content and append the message to the end, along with suitable line breaking. Hints Hint #1: I think it would be reasonable to use contentsOfFile to load the existing log file into a string. Hint #2: You can use try? and nil coalescing to provide a default value if the existing log file doesn’t exist. Hint #3: How you format the date and time for each message will be interesting, but don’t forget KISS: Keep It Simple, Stupid. Hint #4: What should happen if you can’t write the log file? This wasn’t specified in the challenge description, so you get to use some initiative. Solution www.hackingwithswift.com 90 This is a small and contained challenge, but gives you just enough scope to demonstrate your Swift skills. Specifically, there are three areas where your solution is open for discussion: 1. How you load the log file, or provide a default value if the log hasn’t been created yet. 2. How you format dates to include in each log message. 3. How you write the updated log file back to disk, handling any errors that might occur. The easiest way to solve the first is using one of my favorite Swift tips: combining try? with nil coalescing to provide a default value in cases where a thrown exception just means “missing value”. In code it looks like this: var existingLog = (try? String(contentsOfFile: logFile)) ?? "" After that line has run, existingLog will either be an empty string or the contents of the log file. There are lots of ways to solve the second, but honestly the smartest way is also the easiest: just using Date() inside string interpolation. That will cause the current date and time to be printed out as “Year-Month-Day Hour:Minute:Second”, which is ideal. So, you would write this: existingLog.append("\(Date()): \(message)\n") Finally, writing data to disk is a throwing function, so you need to decide what to do with any errors. You could ignore them, like this: _ = try? existingLog.write(toFile: logFile, atomically: true, encoding: .utf8) …but that’s probably not smart: failure to write log messages seems serious to me, so at the very least you will want to print a warning, like this: do { try existingLog.write(toFile: logFile, atomically: true, www.hackingwithswift.com 91 encoding: .utf8) } catch { print("Failed to write to log: \ (error.localizedDescription)") } Another reasonable approach would be to do without do/catch, just use try by itself, then mark the whole function with throws and let the call site deal with it. Regardless of what you choose, it leaves some scope for interesting discussion, which is always a good thing. Here’s my complete solution to this challenge: func challenge28(log message: String, to logFile: String) { var existingLog = (try? String(contentsOfFile: logFile)) ?? "" existingLog.append("\(Date()): \(message)\n") do { try existingLog.write(toFile: logFile, atomically: true, encoding: .utf8) } catch { print("Failed to write to log: \ (error.localizedDescription)") } } www.hackingwithswift.com 92 Challenge 29: Documents directory Difficulty: Easy Write a function that returns a URL to the user’s documents directory. Sample input and output • Your function should need no input, and return a URL pointing to /Users/ yourUserName/Documents on macOS, and /path/to/container/Documents on iOS. Hints Hint #1: This is one you either know or you don’t. I’d be tempted to answer “that’s something I could find on Google” if the answer fled from my brain in an interview. Hint #2: You should investigate the urls(for:in) method of FileManager. Hint #3: The user has only one documents directory. Solution This is the kind of question that gives coding interviews a bad rap: there’s no logic that you can figure out yourself, or any algorithm you might have learned in a class once. Instead, you either know the answer or you don’t, which is testing nothing other than your ability to memorize vast swathes of Cocoa Touch. There are three reasons I’m including it here. First, if you didn’t know how to do this, you do know now so you’ve learned something new. Second, it’s actually pretty easy to do once you know how, so you might as well get it under your belt in case you get hit with this question in the future. Third, this is useful code to have at hand: reading and writing to the user’s documents directory is very common on iOS, so you’ll almost certainly use this code in real projects. www.hackingwithswift.com 93 Here’s my solution: func challenge29() -> URL { let paths = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask) return paths[0] } Like I said in the hints, though: if you get asked this during an interview, I’d be tempted to answer that it’s something trivial enough to be looked up online. I’m no fan of coding by Stack Overflow, and if someone said “I don’t know much about UITableView but I could look it up” then I’d not think highly of their skills, but this particular problem is something so simple that it really is just a Google search away if you need it. www.hackingwithswift.com 94 Challenge 30: New JPEGs Difficulty: Easy Write a function that accepts a path to a directory and returns an array of all JPEGs that have been created in the last 48 hours. Tip #1: For the purpose of this task, just looking fo