Skip to content

Commit 6c43b4c

Browse files
authored
Merge pull request kodecocodes#336 from rzaccone/serialize-with-array
Serialize with array
2 parents f0c7f8a + 1160658 commit 6c43b4c

File tree

6 files changed

+184
-64
lines changed

6 files changed

+184
-64
lines changed

Trie/Trie/Trie.xcodeproj/project.pbxproj

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -51,6 +51,7 @@
5151
EB798E1C1DFEF79900F0628D /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = "<group>"; };
5252
EB798E281DFEF81400F0628D /* Trie.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Trie.swift; sourceTree = "<group>"; };
5353
EB798E2B1DFEF90B00F0628D /* dictionary.txt */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; path = dictionary.txt; sourceTree = "<group>"; };
54+
EB8369AC1E15C5710003A7F8 /* ReadMe.md */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = net.daringfireball.markdown; path = ReadMe.md; sourceTree = "<group>"; };
5455
/* End PBXFileReference section */
5556

5657
/* Begin PBXFrameworksBuildPhase section */
@@ -101,6 +102,7 @@
101102
EB798DFC1DFEF79900F0628D /* Trie */ = {
102103
isa = PBXGroup;
103104
children = (
105+
EB8369AC1E15C5710003A7F8 /* ReadMe.md */,
104106
EB798DFD1DFEF79900F0628D /* AppDelegate.swift */,
105107
EB798DFF1DFEF79900F0628D /* ViewController.swift */,
106108
EB798E011DFEF79900F0628D /* Assets.xcassets */,
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
<?xml version="1.0" encoding="UTF-8"?>
2+
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
3+
<plist version="1.0">
4+
<dict>
5+
<key>classNames</key>
6+
<dict>
7+
<key>TrieTests</key>
8+
<dict>
9+
<key>testInsertPerformance()</key>
10+
<dict>
11+
<key>com.apple.XCTPerformanceMetric_WallClockTime</key>
12+
<dict>
13+
<key>baselineAverage</key>
14+
<real>3.407</real>
15+
<key>baselineIntegrationDisplayName</key>
16+
<string>Local Baseline</string>
17+
</dict>
18+
</dict>
19+
</dict>
20+
</dict>
21+
</dict>
22+
</plist>
Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
<?xml version="1.0" encoding="UTF-8"?>
2+
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
3+
<plist version="1.0">
4+
<dict>
5+
<key>runDestinationsByUUID</key>
6+
<dict>
7+
<key>6ABF2F62-9363-4450-8DE1-D20F57026950</key>
8+
<dict>
9+
<key>localComputer</key>
10+
<dict>
11+
<key>busSpeedInMHz</key>
12+
<integer>100</integer>
13+
<key>cpuCount</key>
14+
<integer>1</integer>
15+
<key>cpuKind</key>
16+
<string>Intel Core i7</string>
17+
<key>cpuSpeedInMHz</key>
18+
<integer>3300</integer>
19+
<key>logicalCPUCoresPerPackage</key>
20+
<integer>4</integer>
21+
<key>modelCode</key>
22+
<string>MacBookPro13,2</string>
23+
<key>physicalCPUCoresPerPackage</key>
24+
<integer>2</integer>
25+
<key>platformIdentifier</key>
26+
<string>com.apple.platform.macosx</string>
27+
</dict>
28+
<key>targetArchitecture</key>
29+
<string>x86_64</string>
30+
</dict>
31+
</dict>
32+
</dict>
33+
</plist>

Trie/Trie/Trie/ReadMe.md

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
# Changes
2+
3+
- Corrected a spelling error in a comment.
4+
5+
- Got rid of the `get` syntax for a read only property as the coding guidelines suggest.
6+
7+
- Changed several tests so that they are more Swift-like. That is, they now feel like they are using the features of the language better.
8+
9+
- Fixed a problem in the test method `testRemovePerformance`. The `measure` method runs the block of code 10 times. Previously, all words would get removed after the first run and the next 9 runs were very fast because there was nothing to do! That is corrected now.
10+
11+
- Made the `Trie` class a subclass of `NSObject` and had it conform to `NSCoding`. Added a test to verify that this works.
12+
13+
---
14+
15+
I wasn't able to figure out how to recursively archive the trie. Instead, I tried Kelvin's suggestion to use the `words` property to create an array of words in the trie, then archiving the array.
16+
17+
There are a couple of nice aspects to this approach.
18+
19+
- The `TrieNode` class can remain generic since it doesn't need to conform to `NSCoding`.
20+
21+
- It doesn't require much new code.
22+
23+
There are several downsides though.
24+
25+
- The size of the archived words is three times the size of the original file of words! Did I do this right? The tests pass, so it seems correct. I question whether archiving is worth the effort if the resulting archive is so much larger than the original file.
26+
27+
- I would expect that archiving the trie would result in a file that was smaller than the original since a trie doesn't repeat leading character sequences when they are the same.
28+
29+
- This requires that the trie get reconstructed when it is unarchived.
30+
31+
- My gut tells me that it would be faster to archive and unarchive the trie itself, but I don't have any hard data to support this.
32+
33+
I would like to see code that recursively archives the trie so we can compare the performance.

Trie/Trie/Trie/Trie.swift

Lines changed: 50 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -11,13 +11,12 @@ import Foundation
1111

1212
/// A node in the trie
1313
class TrieNode<T: Hashable> {
14-
var value: T?
15-
weak var parentNode: TrieNode?
16-
var children: [T: TrieNode] = [:]
17-
var isTerminating = false
18-
var isLeaf: Bool {
19-
get {
20-
return children.count == 0
14+
var value: T?
15+
weak var parentNode: TrieNode?
16+
var children: [T: TrieNode] = [:]
17+
var isTerminating = false
18+
var isLeaf: Bool {
19+
return children.count == 0
2120
}
2221
}
2322

@@ -45,28 +44,50 @@ class TrieNode<T: Hashable> {
4544

4645
/// A trie data structure containing words. Each node is a single
4746
/// character of a word.
48-
class Trie {
49-
typealias Node = TrieNode<Character>
50-
/// The number of words in the trie
51-
public var count: Int {
52-
return wordCount
53-
}
54-
/// Is the trie empty?
55-
public var isEmpty: Bool {
56-
return wordCount == 0
57-
}
58-
/// All words currently in the trie
59-
public var words: [String] {
60-
return wordsInSubtrie(rootNode: root, partialWord: "")
61-
}
62-
fileprivate let root: Node
63-
fileprivate var wordCount: Int
64-
65-
/// Creats an empty trie.
66-
init() {
67-
root = Node()
68-
wordCount = 0
69-
}
47+
class Trie: NSObject, NSCoding {
48+
typealias Node = TrieNode<Character>
49+
/// The number of words in the trie
50+
public var count: Int {
51+
return wordCount
52+
}
53+
/// Is the trie empty?
54+
public var isEmpty: Bool {
55+
return wordCount == 0
56+
}
57+
/// All words currently in the trie
58+
public var words: [String] {
59+
return wordsInSubtrie(rootNode: root, partialWord: "")
60+
}
61+
fileprivate let root: Node
62+
fileprivate var wordCount: Int
63+
64+
/// Creates an empty trie.
65+
override init() {
66+
root = Node()
67+
wordCount = 0
68+
super.init()
69+
}
70+
71+
// MARK: NSCoding
72+
73+
/// Initializes the trie with words from an archive
74+
///
75+
/// - Parameter decoder: Decodes the archive
76+
required convenience init?(coder decoder: NSCoder) {
77+
self.init()
78+
let words = decoder.decodeObject(forKey: "words") as? [String]
79+
for word in words! {
80+
self.insert(word: word)
81+
}
82+
}
83+
84+
/// Encodes the words in the trie by putting them in an array then encoding
85+
/// the array.
86+
///
87+
/// - Parameter coder: The object that will encode the array
88+
func encode(with coder: NSCoder) {
89+
coder.encode(self.words, forKey: "words")
90+
}
7091
}
7192

7293
// MARK: - Adds methods: insert, remove, contains

Trie/Trie/TrieTests/TrieTests.swift

Lines changed: 44 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -12,20 +12,20 @@ import XCTest
1212
class TrieTests: XCTestCase {
1313
var wordArray: [String]?
1414
var trie = Trie()
15-
15+
1616
/// Makes sure that the wordArray and trie are initialized before each test.
1717
override func setUp() {
1818
super.setUp()
1919
createWordArray()
2020
insertWordsIntoTrie()
2121
}
22-
23-
/// Don't need to do anything here because the wordArrayu and trie should
22+
23+
/// Don't need to do anything here because the wordArray and trie should
2424
/// stay around.
2525
override func tearDown() {
2626
super.tearDown()
2727
}
28-
28+
2929
/// Reads words from the dictionary file and inserts them into an array. If
3030
/// the word array already has words, do nothing. This allows running all
3131
/// tests without repeatedly filling the array with the same values.
@@ -36,7 +36,7 @@ class TrieTests: XCTestCase {
3636
let resourcePath = Bundle.main.resourcePath! as NSString
3737
let fileName = "dictionary.txt"
3838
let filePath = resourcePath.appendingPathComponent(fileName)
39-
39+
4040
var data: String?
4141
do {
4242
data = try String(contentsOfFile: filePath, encoding: String.Encoding.utf8)
@@ -48,24 +48,21 @@ class TrieTests: XCTestCase {
4848
wordArray = data!.components(separatedBy: "\n")
4949
XCTAssertEqual(wordArray!.count, dictionarySize)
5050
}
51-
51+
5252
/// Inserts words into a trie. If the trie is non-empty, don't do anything.
5353
func insertWordsIntoTrie() {
54-
guard trie.count == 0 else {
55-
return
56-
}
57-
let numberOfWordsToInsert = wordArray!.count
58-
for i in 0..<numberOfWordsToInsert {
59-
trie.insert(word: wordArray![i])
54+
guard let wordArray = wordArray, trie.count == 0 else { return }
55+
for word in wordArray {
56+
trie.insert(word: word)
6057
}
6158
}
62-
59+
6360
/// Tests that a newly created trie has zero words.
6461
func testCreate() {
6562
let trie = Trie()
6663
XCTAssertEqual(trie.count, 0)
6764
}
68-
65+
6966
/// Tests the insert method
7067
func testInsert() {
7168
let trie = Trie()
@@ -78,7 +75,7 @@ class TrieTests: XCTestCase {
7875
XCTAssertTrue(trie.contains(word: "cut"))
7976
XCTAssertEqual(trie.count, 4)
8077
}
81-
78+
8279
/// Tests the remove method
8380
func testRemove() {
8481
let trie = Trie()
@@ -90,7 +87,7 @@ class TrieTests: XCTestCase {
9087
XCTAssertFalse(trie.contains(word: "cute"))
9188
XCTAssertEqual(trie.count, 1)
9289
}
93-
90+
9491
/// Tests the words property
9592
func testWords() {
9693
let trie = Trie()
@@ -101,52 +98,53 @@ class TrieTests: XCTestCase {
10198
XCTAssertEqual(words[0], "foobar")
10299
XCTAssertEqual(words.count, 1)
103100
}
104-
101+
105102
/// Tests the performance of the insert method.
106103
func testInsertPerformance() {
107-
let trie = Trie()
108104
self.measure() {
109-
let numberOfWordsToInsert = self.wordArray!.count
110-
for i in 0..<numberOfWordsToInsert {
111-
trie.insert(word: self.wordArray![i])
105+
let trie = Trie()
106+
for word in self.wordArray! {
107+
trie.insert(word: word)
112108
}
113109
}
114110
XCTAssertGreaterThan(trie.count, 0)
115111
XCTAssertEqual(trie.count, wordArray?.count)
116112
}
117-
113+
118114
/// Tests the performance of the insert method when the words are already
119115
/// present.
120116
func testInsertAgainPerformance() {
121117
self.measure() {
122-
let numberOfWordsToInsert = self.wordArray!.count
123-
for i in 0..<numberOfWordsToInsert {
124-
self.trie.insert(word: self.wordArray![i])
118+
for word in self.wordArray! {
119+
self.trie.insert(word: word)
125120
}
126121
}
127122
}
128-
123+
129124
/// Tests the performance of the contains method.
130125
func testContainsPerformance() {
131126
self.measure() {
132-
for i in 0..<self.wordArray!.count {
133-
XCTAssertTrue(self.trie.contains(word: self.wordArray![i]))
127+
for word in self.wordArray! {
128+
XCTAssertTrue(self.trie.contains(word: word))
134129
}
135-
136130
}
137131
}
138-
139-
/// Tests the performance of the remove method.
132+
133+
/// Tests the performance of the remove method. Since setup has already put
134+
/// words into the trie, remove them before measuring performance.
140135
func testRemovePerformance() {
136+
for word in self.wordArray! {
137+
self.trie.remove(word: word)
138+
}
141139
self.measure() {
142-
let numberOfWordsToRemove = self.wordArray!.count
143-
for i in 0..<numberOfWordsToRemove {
144-
self.trie.remove(word: self.wordArray![i])
140+
self.insertWordsIntoTrie()
141+
for word in self.wordArray! {
142+
self.trie.remove(word: word)
145143
}
146144
}
147145
XCTAssertEqual(trie.count, 0)
148146
}
149-
147+
150148
/// Tests the performance of the words computed property. Also tests to see
151149
/// if it worked properly.
152150
func testWordsPerformance() {
@@ -159,4 +157,15 @@ class TrieTests: XCTestCase {
159157
XCTAssertTrue(self.trie.contains(word: word))
160158
}
161159
}
160+
161+
/// Tests the archiving and unarchiving of the trie.
162+
func testArchiveAndUnarchive() {
163+
let resourcePath = Bundle.main.resourcePath! as NSString
164+
let fileName = "dictionary-archive"
165+
let filePath = resourcePath.appendingPathComponent(fileName)
166+
NSKeyedArchiver.archiveRootObject(trie, toFile: filePath)
167+
let trieCopy = NSKeyedUnarchiver.unarchiveObject(withFile: filePath) as! Trie
168+
XCTAssertEqual(trieCopy.count, trie.count)
169+
170+
}
162171
}

0 commit comments

Comments
 (0)