forked from aholub/SwiftInDepth
-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathREADME.TXT
439 lines (319 loc) · 16.1 KB
/
README.TXT
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
======================================================================
Exercises for Allen Holub's Pluralsight class: Swift In Depth
http://www.pluralsight.com/courses/swift-in-depth
======================================================================
Allen Holub
http://www.holub.com
@allenholub
This file and all associated solutions (c)2015, Allen I. Holub.
All rights reserved.
======================================================================
These are the same exercises that I use when I teach a hands-on version
of my swift class in house. If you'd like me to do that for you, please
contact me at [email protected]. The main advantage of having me come
in instead of going the Pluralsight route is that I can answer questions
as you work.
======================================================================
Preliminaries:
If you haven't yet, download XCode 7 from the App Store. It's no
longer in Beta, and most (but not all, unfortunately) of the bugs
I was running up against in my video have been fixed. The testing
frameworks work well in the release version. All of my solutions
to the following problems are written in Swift-2, so they won't
compile with earlier versions of XCode. I recommend that you build
your solutions as XCode projects rather than using the Playground.
You should also build test classes for these assignments and use your
tests to verify that everything works. If you haven't done that before,
Apple describes the testing system at: http://apple.co/1Mr3XFE .
My solutions all come with tests that use the XCT framework.
======================================================================
Getting the code and running it
My solutions are all available at
http://github.com/aholub/SwiftInDepth
(I suppose you know that or you wouldn't be reading this file :-)
You can just read them on gitHub if all you want to do is read them.
To install the solutions on your own machine, clone the git repository
to your own hard drive. The following git request puts it in the
SwiftInDepth subdirectory of the current directory on your disk:
git clone http://github.com/aholub/SwiftInDepth SwiftInDepth
The cloned repository is an XCode project, so after issuing the
above request, you should be able to run it in Xcode from the
command line with:
cd SwiftInDepth
open SwiftExercises.xcodeproj
If that doesn't work for some reason, you can import the source-code
files into a new, but empty, XCode project using
File->Add Files To "MyProject" (or whatever your project is named).
Once you're in XCode, the code for the solutions in the SwiftExercises
group, and all the tests are in the SwiftExercisesTest group.
The groups form separate "targets" (modules). The tests, since
they're in a different target, must use the following import
statement to access the classes they're testing:
@testable import SwiftExercises
(The @testable makes all of the internal-access methods available
to your test code. Without it, you'll only be able to access public
stuff.)
If you want to set up a similar structure for your own tests,
you'll need to tell XCode that the SwiftExercises
group defines a "module." To do that, go into the "Project Navigator"
(View->Navigators->Show Project Navigator) and highlight the project
itself (at the very top of the window with the blue icon to its left)
On the Right, Select "Swift Exercises" under "TARGETS." Scroll down
to the "Packaging" category and set "Defines Module" to "Yes."
======================================================================
Asking Questions:
Please address any questions to the Discussion group associated with
my class:
http://www.pluralsight.com/courses/discussion/swift-in-depth
That way we can all benefit. If you send me email, I'll most likely just
copy it to the discussion group and post my answer there. (I'll write
back to you when I post the reply). If you can't get at the Discussions
because you're using a pirated version of my class, I have no sympathy
for your plight.
*********************************************************************
EXERCISES:
*********************************************************************
=====================================================================
After Finishing Module 3
======================================================================
1: Build a simple binary tree that holds String values.
If you've never worked with a binary tree, they're described in
Robert Horvick's Pluralsight Class: "Algorithms and Data
Structures---Part 1" (http://www.pluralsight.com/courses/ads-part1).
A simple binary tree is not an ideal data structure (AVL trees are better,
for example), but this is a great exercise for understanding how optionals
work. (The tree root and child pointers in the node have to be options so that
you can use nil to represent no descendants.) Don't complicate things by
implementing an AVL or Red/Black Tree unless you really relish the
challenge. The point, after all, is to learn Swift, not build a Tree class.
Use the following typealias for the stored-element type. Don't hard code
String into your implementation:
class StringTree {
typealias T = String
//...
}
Both of the following statements should create trees:
var t1 = StringTree()
var t2 = StringTree( ["a", "b", "c"] )
You should support the following methods and computed properties:
isEmpty (read-only property)
count (read-only property. Number of elements in the tree)
clear() (remove all elements)
smallest() -> T? (return the smallest element, nil if the tree is empty)
largest() -> T? (return the largest element, nil if the tree is empty)
add(element:T) (add the specified string to the tree)
findMatchOf(lookingFor:T ) -> T? (return the contained string that matches
the lookingFor argument. Use == for
comparison)
contains( lookingFor: T) -> Bool (return true if the tree contains an
element that matches lookingFor. Use == for
comparison)
The last two methods should call the following "workhorse" method to do the
actual searching (I'm assuming that this method is recursive). This method
will be handy should you implement remove, and is a good exercise in optionals
even if you don't:
func doFind( lookingFor: T, // the value you're looking for
current: Node?, // with the search starting here
parent: Node? ) // and this is the parent of the "current" node.
// (nil if "current" is the root node)
-> (found: Node, // Return an optional tuple that holds the matching
parent: Node?)? // node and its parent (or nil if the matching
// node was the root node) Return nil if you didn't
// find the value you were looking for.
For example, to find the node "x," starting the search at the root node of the tree,
you'd call:
doFind("x", current: root, parent: nil)
My solution is in:
StringTree.swift
StringTreeTests.swift
----------------------------------------------------------------------
2: (optional. This is hard, so take it as a challenge). Add:
remove( lookingFor: T ) -> T?
My solution is in:
StringTreeWithRemove.swift
StringTreeRemoveTests.swift
----------------------------------------------------------------------
3: First, create an enum that represents a US, Canadian, or UK postal code:
let usCode = PostalCode.US(12345,6789)
let caCode = PostalCode.CA("A0A 0A0" )
let ukCode = PostalCode.UK("SW1A 1AA")
Implement these with an asString() method that returns a string
representation of the code. For example, given the earlier definitions,
usCode.asString() evaluates to "12345-6789"
Next, Create a second enum that represents countries. These must
use string raw values that work as follows:
Country.USA.rawValue == "United States"
Country.UK.rawValue == "United Kingdom"
Country.CA.rawValue == "Canada"
The Country should be able to produce a postal code as follows:
Country.USA.getPostalCode(12345,6789)
Country.CA.getPostalCode("A0A 0A0")
Country.UK.getPostalCode("SW1A 1AA")
These methods either return an appropriate PostalCode object, or
they return nil if the arguments to the methods do not form
a legitimate postal code for the specified country.
In particular, if A is a letter and d is a digit:
Canadian postal codes take the form
AdA dAd
UK postal codes take one of these forms:
AA0A dAA
AdA dAA
Ad dAA
Add dAA
AAd dAA
AAdd dAA
US postal codes are two numbers. The first must be in the
range 0-99999; the second in the range 0-9999
My solution is in:
PostalCode.swift PostalCodeTests.swift
======================================================================
After finishing module 4
======================================================================
4: Add:
_verifyChildren( parent: T, expectedLeft: T?, expectedRight: T? ) -> Bool
to your tree. This mehod verifies that the two children of the node with the given
key (parent) have the expected values. expectedLeft and expectedRight can
both be nil (in the case of a leaf node).
For example, given a balanced tree created by inserting the nodes "b" "a"
and "c" in that order, the following should all evaluate true:
_verifyChildren( "b", "a", "c", )
_verifyChildren( "a", nil, nil )
_verifyChildren( "c", nil, nil )
if you then add the node "d" to the tree, the following will hold
_verifyChildren( "b", "a", "c", )
_verifyChildren( "a", nil, nil )
_verifyChildren( "c", nil, "d" )
_verifyChildren( "d", nil, nil )
You must do all checking for these conditions with a switch with four
(and only four) case statements in it, one for each of the following
situations:
left right
nil nil
not Nil nil
nil not Nil
not Nil not Nil
That same case should compare values when necessary using a "where" clause.
In other words, a single case statement is triggered when both
expectedRight and expectedLeft are nil, and that same case statement
should verify (with a where clause) that both children of the specified parent
are also nil.
Similarly, if exectedLeft is nil and expected Right isn't, a single
case statement should be trigged to handle that situation, and the
same case statement (in its where clause) should verify that the
expectedRight value matches the acctual value of the right child of
the indicated parent.
Modify your tests to verify that a tree to which you add the following
values in the indicated order is structured correctly:
"d" "b" "f" "a" "c" "e" "g"
----------------------------------------------------------------------
If you didn't implement remove, copy my implementation into your code.
Modify remove() to throw an exception if you try to remove an item
from an empty tree or if you try to remove an item not in the tree.
----------------------------------------------------------------------
My solution to both of the above is in:
StringTreeWithVerify.swift
VeryifyAndTreeEmptyTests.swift
======================================================================
After finishing module 5 (Functions and CLosures)
======================================================================
5:
Add the following methods to your Tree
t.traverse { print("\($0)"); return true }
t.forEveryElement{
print("\($0)")
}
public func filter( okay: (T)->Bool ) -> Tree<T>
public func map( transform: (T)->T ) -> Tree<T>
public func reduce<U>(first: U, combine: (U, T) -> U) -> U
t.asString ( delim: String = " " )
My Solution:
StringTreeWithClosures.swift
StringTreeClosuresTests.swift
======================================================================
After Modules 6 and 7 (Classes)
======================================================================
6. Get rid of the T typealias and make the tree class generic.
That is, create Tree<T> where T must implement the Comparable
Protocol (String implements Comparable). Also add public and private
where appropriate. The class itself should be public.
We haven't covered protocols at this point in the class, but the syntax is
class Tree<T: Comparable> {
}
That will allow the relational operators ( < > == etc ) to work correctly
on node values.
Also, extract the filter() map() and reduce() methods into an extension
rather than defining them in the tree class itself.
MySolution: SimpleGenericTree.swift
SimpleGenericTreeTests.swift
======================================================================
After Module 9 (Protocols)
======================================================================
7. Extract the key operations (add, remove, traverse, contains, findMatchOf,
forEveryElement, and the count computed property) into a Collection protocol
and modify your generic tree implementation to implement that protocol.
My Solution:
Collection.swift (and comment in the :Collection in SimpleGenericTree.swift)
8. Extend the Collection protocol to add the method median(), which finds
the "median" element (e.g. if you turned the tree into an array with
indexes 0 to i, the median element would be at tree[i/2]. That's not
the same thing as the root element. Do not turn to tree into an array
to solve this problem, however.) Return nil if the tree is empty.
My Solution:
Median.swift
MedianTests.swift
----------------------------------------------------------------------
9. Implement an UndoableTree class into which you can insert any Comparable
object. This class should adopt Collection, and should also adopt the
Undoable protocol, which has the members undo() and redo().
Calling undo() should reverse the last modification (add or remove).
Subsequently calling redo() should reverse the most recent undo().
It must be possible to call undo() multiple times and then reverse
the set of undo operations by calling redo() as many times as you called
undo().
Implemented this mechanism using a stack of tuples that contain two closures.
One of these closures implements a "do" operation and the other
implements an "undo" operation.
To undo, pop a tuple off the stack, execute its "undo" closure,
and then push the tuple onto a redo stack. To redo, pop a tuple off the
redo stack, execute it's "redo" closure, and then push the tuple
onto the undo stack.
(As an aside, this is a classic example of the Gang Of Four "Command"
design pattern, but with the Command object defined with a tuple rather
than a class)
My Solution:
UndoableTree.swift
UndoableTreeTests.swift
======================================================================
After finishing module 10 (Customizing Swift)
======================================================================
10. One problem with the tree is that, if you change the value of the
"key" after you've inserted an item into a tree, then the entire
tree structure becomes invalid. Solve that problem by introducing a Lockable
protocol with two members, lock() and unlock(). Then implement a
SafeTree subclass of our generic tree whose members must adopt
both Comparable (as is the case with our original tree) and Lockable.
The add() method locks the inserted item, and the remove() method unlocks it.
Any attempt to modify a locked item should result in a exception toss.
(You may not recycle any of the standard exception types for this purpose---make
up one of your own).
Test your Safe Tree using a class of your own creation that implements
both Lockable and Comparable.
My Solution:
SafeTree.swift
SafeTreeTests.swift
----------------------------------------------------------------------
11. Modify your tree so that it supports the following operations. (Use
the Extension mechanism when possible)
let t: Tree<String> = [ "a", "b", "c" ]
t += "d"
t -= "a"
if( t <> "a" ) {...} // if t contains "a"
if( t !<> "a" ) {...} // if t doesn't contain "a"
let x = t[1] (read only. t[1] = "x" shouldn't work)
for element in t {
print("\(t)" )
}
My Solution
Tree.swift
TreeTests.swift