Skip to content
This repository was archived by the owner on Aug 17, 2022. It is now read-only.

Commit e19f03e

Browse files
authored
Merge pull request #20 from juliand665/master
More Abstraction and Additional Clock Test Cases
2 parents 06c387d + b2e3f7b commit e19f03e

File tree

14 files changed

+569
-113
lines changed

14 files changed

+569
-113
lines changed

CodeChallenge.xcodeproj/project.pbxproj

Lines changed: 8 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,6 @@
1212
1B49C0501E0850D90094121E /* BugKrushaBulletMatchEntry.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1B49C04F1E0850D90094121E /* BugKrushaBulletMatchEntry.swift */; };
1313
1B49C0531E0855C90094121E /* BulletMatchTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1B49C0521E0855C90094121E /* BulletMatchTests.swift */; };
1414
1B4ECDA51E08267B00767EBD /* BulletMatch.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1B4ECDA41E08267B00767EBD /* BulletMatch.swift */; };
15-
1B5C1BC51E1EB242002E9254 /* markings_short.json in Resources */ = {isa = PBXBuildFile; fileRef = 1B5C1BC41E1EB242002E9254 /* markings_short.json */; };
1615
1B9E113F1E005A7C00B9FA5A /* Clock.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1B9E113E1E005A7C00B9FA5A /* Clock.swift */; };
1716
1B9E11411E006A2B00B9FA5A /* BugKrushaClockEntry.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1B9E11401E006A2B00B9FA5A /* BugKrushaClockEntry.swift */; };
1817
1B9E11461E006F0800B9FA5A /* ClockTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1B9E11451E006F0800B9FA5A /* ClockTests.swift */; };
@@ -40,6 +39,8 @@
4039
9489866A1BE5CFB000D34976 /* CodeChallengeType.swift in Sources */ = {isa = PBXBuildFile; fileRef = 948986691BE5CFB000D34976 /* CodeChallengeType.swift */; };
4140
948986701BE5D28500D34976 /* ExampleCodeChallenge.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9489866F1BE5D28500D34976 /* ExampleCodeChallenge.swift */; };
4241
94FB98CA1BE1B5D800228845 /* TwoSumChallenge.swift in Sources */ = {isa = PBXBuildFile; fileRef = 94FB98C91BE1B5D800228845 /* TwoSumChallenge.swift */; };
42+
953445F81E21ED03008AC0B3 /* BulletMatch_dataset.json in Resources */ = {isa = PBXBuildFile; fileRef = 953445F71E21ED03008AC0B3 /* BulletMatch_dataset.json */; };
43+
953445FA1E21ED07008AC0B3 /* Clock_dataset.json in Resources */ = {isa = PBXBuildFile; fileRef = 953445F91E21ED07008AC0B3 /* Clock_dataset.json */; };
4344
9572008E1E1F19F9006BB4D6 /* juliand665BulletMatchEntry.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9572008D1E1F19F9006BB4D6 /* juliand665BulletMatchEntry.swift */; };
4445
957200991E206A35006BB4D6 /* juliand665LetterCombinationOfPhoneNumberEntry.swift in Sources */ = {isa = PBXBuildFile; fileRef = 957200981E206A35006BB4D6 /* juliand665LetterCombinationOfPhoneNumberEntry.swift */; };
4546
9572009C1E206A4B006BB4D6 /* juliand665LSWRCEntry.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9572009B1E206A4B006BB4D6 /* juliand665LSWRCEntry.swift */; };
@@ -65,7 +66,6 @@
6566
1B49C04F1E0850D90094121E /* BugKrushaBulletMatchEntry.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = BugKrushaBulletMatchEntry.swift; path = BulletMatch/Entries/BugKrushaBulletMatchEntry.swift; sourceTree = "<group>"; };
6667
1B49C0521E0855C90094121E /* BulletMatchTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = BulletMatchTests.swift; sourceTree = "<group>"; };
6768
1B4ECDA41E08267B00767EBD /* BulletMatch.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = BulletMatch.swift; path = BulletMatch/BulletMatch.swift; sourceTree = "<group>"; };
68-
1B5C1BC41E1EB242002E9254 /* markings_short.json */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.json; path = markings_short.json; sourceTree = "<group>"; };
6969
1B9E113E1E005A7C00B9FA5A /* Clock.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Clock.swift; sourceTree = "<group>"; };
7070
1B9E11401E006A2B00B9FA5A /* BugKrushaClockEntry.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = BugKrushaClockEntry.swift; sourceTree = "<group>"; };
7171
1B9E11451E006F0800B9FA5A /* ClockTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ClockTests.swift; sourceTree = "<group>"; };
@@ -96,6 +96,8 @@
9696
948986691BE5CFB000D34976 /* CodeChallengeType.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CodeChallengeType.swift; sourceTree = "<group>"; };
9797
9489866F1BE5D28500D34976 /* ExampleCodeChallenge.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ExampleCodeChallenge.swift; sourceTree = "<group>"; };
9898
94FB98C91BE1B5D800228845 /* TwoSumChallenge.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TwoSumChallenge.swift; sourceTree = "<group>"; };
99+
953445F71E21ED03008AC0B3 /* BulletMatch_dataset.json */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.json; name = BulletMatch_dataset.json; path = BulletMatch/BulletMatch_dataset.json; sourceTree = "<group>"; };
100+
953445F91E21ED07008AC0B3 /* Clock_dataset.json */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.json; path = Clock_dataset.json; sourceTree = "<group>"; };
99101
9572008D1E1F19F9006BB4D6 /* juliand665BulletMatchEntry.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = juliand665BulletMatchEntry.swift; path = BulletMatch/Entries/juliand665BulletMatchEntry.swift; sourceTree = "<group>"; };
100102
957200981E206A35006BB4D6 /* juliand665LetterCombinationOfPhoneNumberEntry.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = juliand665LetterCombinationOfPhoneNumberEntry.swift; sourceTree = "<group>"; };
101103
9572009B1E206A4B006BB4D6 /* juliand665LSWRCEntry.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = juliand665LSWRCEntry.swift; sourceTree = "<group>"; };
@@ -150,6 +152,7 @@
150152
children = (
151153
1B49C0511E0850DD0094121E /* Entries */,
152154
1B4ECDA41E08267B00767EBD /* BulletMatch.swift */,
155+
953445F71E21ED03008AC0B3 /* BulletMatch_dataset.json */,
153156
);
154157
name = BulletMatch;
155158
sourceTree = "<group>";
@@ -173,17 +176,11 @@
173176
children = (
174177
1B9E11421E006A2F00B9FA5A /* Entries */,
175178
1B9E113E1E005A7C00B9FA5A /* Clock.swift */,
179+
953445F91E21ED07008AC0B3 /* Clock_dataset.json */,
176180
);
177181
path = Clock;
178182
sourceTree = "<group>";
179183
};
180-
1BB33E9E1E15BA5200C83E3F /* Resources */ = {
181-
isa = PBXGroup;
182-
children = (
183-
);
184-
path = Resources;
185-
sourceTree = "<group>";
186-
};
187184
9272E19A1BD7F6560030B403 = {
188185
isa = PBXGroup;
189186
children = (
@@ -225,8 +222,6 @@
225222
94350F591BE5D5C1003592FB /* Challenges */ = {
226223
isa = PBXGroup;
227224
children = (
228-
1B5C1BC41E1EB242002E9254 /* markings_short.json */,
229-
1BB33E9E1E15BA5200C83E3F /* Resources */,
230225
1B9E11471E010A3500B9FA5A /* Clock */,
231226
94350F661BE5EB21003592FB /* TwoSum */,
232227
944CFCD61BE72AEB00FD2E41 /* LetterCombinationsOfPhoneNumber */,
@@ -410,7 +405,8 @@
410405
isa = PBXResourcesBuildPhase;
411406
buildActionMask = 2147483647;
412407
files = (
413-
1B5C1BC51E1EB242002E9254 /* markings_short.json in Resources */,
408+
953445F81E21ED03008AC0B3 /* BulletMatch_dataset.json in Resources */,
409+
953445FA1E21ED07008AC0B3 /* Clock_dataset.json in Resources */,
414410
);
415411
runOnlyForDeploymentPostprocessing = 0;
416412
};

CodeChallenge/Base/CodeChallengeType.swift

Lines changed: 96 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -8,11 +8,13 @@
88

99
import Foundation
1010

11+
// MARK: - Challenge Definition
12+
1113
/**
1214
Protocol for defining a new Challenge.
1315

1416
This protocol defines the Input/Output types for the Entry's `block` property. HINT: Use a tuple for multiple inputs! It also defines the interface that whatever will run the challenges (currently our tests) uses to run the entries and verify their output.
15-
*/
17+
*/
1618
protocol CodeChallengeType {
1719
/// The type for the input(s) for the challenge.
1820
associatedtype InputType
@@ -27,17 +29,104 @@ protocol CodeChallengeType {
2729

2830
/**
2931
A function to generate the dataset that the entries will be run against. Each `InputType` in the returned array will be run by each entry multiple times and then averaged in the results.
30-
*/
32+
*/
3133
func generateDataset() -> [InputType]
3234

3335
/**
3436
A function which verifies the output from running an entry's block with the given input.
3537

3638
- Returns: True if the output is valid, false if not.
37-
*/
39+
*/
3840
func verifyOutput(_ output: OutputType, forInput input: InputType) -> Bool
3941
}
4042

43+
/**
44+
Convenient protocol for defining challenges that verify entries using a JSON file.
45+
46+
Fields required by `JSONBasedChallenge`:
47+
- `fileName`
48+
- `verificationData`
49+
50+
Fields required by `CodeChallengeType`:
51+
- `title`
52+
- `entries`
53+
- `InputType`
54+
- `OutputType`
55+
56+
In most cases, this should take over `generateDataset` and `verifyOutput` for you.
57+
The more your types are simple `String`s, the less methods you'll have to implement yourself.
58+
*/
59+
protocol JSONBasedChallenge: class, CodeChallengeType {
60+
61+
/// The type the output is encoded as in the JSON file. This can usually be inferred from methods you define; you shouldn't have to add it manually.
62+
associatedtype RawOutputType
63+
64+
/// The name of the JSON file. You shouldn't need to add any path components in here.
65+
/// Recommended placement of the JSON file is in your challenge folder.
66+
var fileName: String { get }
67+
68+
/// You have to implement this yourself, since protocols can't store anything for you. The types should match those of the JSON file.
69+
var verificationData: [String: RawOutputType] { get set }
70+
71+
72+
/// Converts the JSON key into an input for the challenge. (Used for dataset generation.)
73+
///
74+
/// If your `InputType` is `String`, you don't have to implement this.
75+
///
76+
/// - Parameter raw: Raw input from the JSON file
77+
/// - Returns: Input to feed into entries
78+
func input(from raw: String) -> InputType
79+
80+
/// Converts the challenge input back into raw JSON. (Used for output verification.)
81+
///
82+
/// If your `InputType` is `String`, you don't have to implement this.
83+
///
84+
/// - Parameter input: Input used for the entry
85+
/// - Returns: Raw input, as in the JSON file, to use for accessing `verificationData`
86+
func raw(from input: InputType) -> String
87+
}
88+
89+
// Default Implementations
90+
91+
extension JSONBasedChallenge {
92+
93+
func generateDataset() -> [InputType] {
94+
// Extract data from JSON file
95+
let bundle = Bundle(for: Self.self)
96+
guard
97+
let dataUrl = bundle.url(forResource: fileName, withExtension: "json"),
98+
let data = try? Data(contentsOf: dataUrl),
99+
let jsonObjects = try? JSONSerialization.jsonObject(with: data, options: .allowFragments),
100+
let vd = jsonObjects as? [String: RawOutputType] else { fatalError() }
101+
102+
verificationData = vd
103+
104+
// Map key `String`s to `InputType`
105+
return vd.keys.map { input(from: $0) }
106+
}
107+
}
108+
109+
extension JSONBasedChallenge where OutputType: Equatable, RawOutputType == OutputType {
110+
111+
func verifyOutput(_ output: OutputType, forInput input: InputType) -> Bool {
112+
guard let expected = verificationData[raw(from: input)] else { return false }
113+
return expected == output
114+
}
115+
}
116+
117+
extension JSONBasedChallenge where InputType == String {
118+
119+
func input(from raw: String) -> InputType {
120+
return raw
121+
}
122+
123+
func raw(from input: InputType) -> String {
124+
return input
125+
}
126+
}
127+
128+
// MARK: - Entry Definition
129+
41130
/// An entry for a `CodeChallengeType`.
42131
struct CodeChallengeEntry<ChallengeType: CodeChallengeType> {
43132
/// This entry's author's name.
@@ -47,6 +136,8 @@ struct CodeChallengeEntry<ChallengeType: CodeChallengeType> {
47136
let block: (ChallengeType.InputType) -> ChallengeType.OutputType
48137
}
49138

139+
// MARK: - Test Evaluation
140+
50141
/// A structure to hold the result of a single run of an Entry's `block`.
51142
struct CodeChallengeResult<ChallengeType: CodeChallengeType> {
52143
/// The name of the Entry's participant
@@ -103,7 +194,7 @@ struct AccumulatedChallengeResult<ChallengeType: CodeChallengeType> {
103194

104195
/**
105196
Create a new AccumulatedChallengeResult from the given set of results. The time properties will be calculated using the given parameters to this init.
106-
*/
197+
*/
107198
init(name: String, results: [CodeChallengeResult<ChallengeType>], successes: Int, failures: Int) {
108199
self.name = name
109200
self.results = results
@@ -133,7 +224,7 @@ extension CodeChallengeType {
133224
This function calls `generateDataset()` and then runs each input in the dataset through each entry in the `entries` multiple times, concurrently. It then processes all of the `CodeChallengeResult`s into `AcummulatedChallengeResult`s for each participant and returns them sorted by `totalTime`.
134225

135226
- Returns: An array of `AcummulatedChallengeResult`s for each participant sorted by `averageTime`.
136-
*/
227+
*/
137228
func runAll() -> [AccumulatedResultType] {
138229

139230
// Generate the dataset, everybody gets the same one

CodeChallenge/Challenges/BulletMatch/BulletMatch.swift

Lines changed: 40 additions & 54 deletions
Original file line numberDiff line numberDiff line change
@@ -7,77 +7,63 @@
77
//
88

99
import Foundation
10-
/**
11-
Forensic specialists examine spent cartridges for unique
12-
markings left by parts of a gun. They can then compare
13-
these markings to the ones in any gun to determine if there
14-
is a match. A match means that the bullet was fired from
15-
that gun.
16-
17-
Given two markings, one from a spent cartridge and the other
18-
from a suspected weapon, both as Strings, return true if they match
19-
and false if they don't.
20-
21-
EXAMPLES:
22-
Match, returns true
23-
"| ||| | |" and
24-
"| ||| | |"
2510

26-
Match, returns true though one is rotated. Bullets can be rotated.
27-
Your solution should account for the possible rotation of bullets.
28-
"|| ||| | " and
29-
" | || |||"
30-
31-
Doesn't match, returns false
32-
"| ||| | |" and
33-
"||| | | |"
11+
/**
12+
Forensic specialists examine spent cartridges for unique
13+
markings left by parts of a gun. They can then compare
14+
these markings to the ones in any gun to determine if there
15+
is a match. A match means that the bullet was fired from
16+
that gun.
17+
18+
Given two markings, one from a spent cartridge and the other
19+
from a suspected weapon, both as Strings, return true if they match
20+
and false if they don't.
21+
22+
EXAMPLES:
23+
Match, returns true
24+
"| ||| | |" and
25+
"| ||| | |"
26+
27+
Match, returns true though one is rotated. Bullets can be rotated.
28+
Your solution should account for the possible rotation of bullets.
29+
"|| ||| | " and
30+
" | || |||"
31+
32+
Doesn't match, returns false
33+
"| ||| | |" and
34+
"||| | | |"
3435

3536
NOTE: Markings cannot be flipped.
3637
"| ||" is not a match for
3738
"|| |"
38-
39-
Problem adapted from http://bit.ly/2h57Wxe
40-
*/
41-
42-
final class BulletChallenge: CodeChallengeType {
39+
40+
Problem adapted from http://bit.ly/2h57Wxe
41+
*/
42+
final class BulletChallenge: JSONBasedChallenge {
4343
typealias InputType = (bulletMarkings: String, gunMarkings: String)
4444
typealias OutputType = Bool
4545

4646
var title = "Bullet Challenge"
47-
private let separator = "#"
47+
48+
// no `protected` access level (darn you, swift gods!)
49+
let fileName = "BulletMatch_dataset"
50+
var verificationData: [String : OutputType] = [:]
4851

4952
var entries: [CodeChallengeEntry<BulletChallenge>] = [
5053
bugKrushaBulletMatchEntry,
5154
codesmanBulletMatchEntry,
5255
juliand665BulletMatchEntry,
53-
matthijsBulletMatchEntry,
56+
matthijsBulletMatchEntry
5457
]
5558

56-
func generateDataset() -> [InputType] {
57-
let bundle = Bundle(for: BulletChallenge.self)
58-
guard
59-
let dataUrl = bundle.url(forResource: "markings_short", withExtension: "json"),
60-
let data = try? Data(contentsOf: dataUrl),
61-
let jsonObjects = try? JSONSerialization.jsonObject(with: data, options: .allowFragments),
62-
let vd = jsonObjects as? [String: Bool]
63-
else { fatalError() }
64-
65-
verificationData = vd
66-
let markings = verificationData.keys.map { key -> (String, String) in
67-
let components = key.components(separatedBy: self.separator)
68-
let gunMarkings = components[1]
69-
let bulletMarkings = components[0]
70-
return (bulletMarkings: bulletMarkings, gunMarkings: gunMarkings)
71-
}
72-
73-
return Array(markings)
59+
func input(from raw: String) -> InputType {
60+
let components = raw.components(separatedBy: "#")
61+
let gunMarkings = components[1]
62+
let bulletMarkings = components[0]
63+
return (bulletMarkings: bulletMarkings, gunMarkings: gunMarkings)
7464
}
7565

76-
func verifyOutput(_ output: Bool, forInput input: (bulletMarkings: String, gunMarkings: String)) -> Bool {
77-
return verificationData[input.bulletMarkings + separator + input.gunMarkings] == output
66+
func raw(from input: InputType) -> String {
67+
return "\(input.bulletMarkings)#\(input.gunMarkings)"
7868
}
7969
}
80-
81-
private var verificationData = [String: Bool]()
82-
83-

CodeChallenge/Challenges/BulletMatch/Entries/juliand665BulletMatchEntry.swift

Lines changed: 15 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -8,17 +8,19 @@
88

99
import Foundation
1010

11-
let juliand665BulletMatchEntry = CodeChallengeEntry<BulletChallenge>(name: "juliand665") { (bullet, gun) in
12-
var bul = bullet
13-
14-
for _ in 1 ... bul.characters.count {
15-
// equate
16-
if bul == gun {
17-
return true
18-
}
19-
// rotate
20-
bul.characters.append(bul.characters.popFirst()!)
21-
}
22-
23-
return false
11+
let juliand665BulletMatchEntry = CodeChallengeEntry<BulletChallenge>(name: "juliand665", block: compare)
12+
13+
private func compare(bullet: String, gun: String) -> Bool {
14+
var bul = bullet
15+
16+
for _ in 1 ... bul.characters.count {
17+
// equate
18+
if bul == gun {
19+
return true
20+
}
21+
// rotate
22+
bul.characters.append(bul.characters.popFirst()!)
23+
}
24+
25+
return false
2426
}

0 commit comments

Comments
 (0)