-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathPassportScanner.java
223 lines (196 loc) · 7.15 KB
/
PassportScanner.java
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
/**
* ___ __ _ _
* | \ __ _ _ _ / \| | |
* | |) / _` | || | | () |_ _|
* |___/\__,_|\_, | \__/ |_|
* |__/
*
* "Passport Processing"
*
* Challenge:
*
* The input file contains objects: each with several key:value pairs.
* Spaces and/or a single newline separate fields. Empty lines separate
* objects. For narrative reasons, the `cid` field is optional, but otherwise
* valid objects require the following keys:
*
* - byr
* - iyr
* - eyr
* - hgt
* - hcl
* - ecl
* - pid
* - cid (optional)
*
* Count objects from the input file which meet these requirements.
*
* Part One answer:
* Batch contains 260 passport objects.
* Batch contains 222 valid passports.
*
* ___ _ ___
* | _ \__ _ _ _| |_ |_ )
* | _/ _` | '_| _| / /
* |_| \__,_|_| \__| /___|
*
* Field-level validation within each object. ("range" is inclusive)
* - cid remains optional
* - byr: /\d{4}/ range 1920 - 2002
* - iyr: /\d{4}/ range 2010 - 2020
* - eyr: /\d{4}/ range 2020 - 2030
* - hgt: /\d{2,3}(cm|in)/
* for "cm" suffix: range 150 - 193
* for "in" suffix: range 59 - 76
* - hcl: /#[0-9a-f]{6}/
* - ecl: /(amb|blu|brn|gry|grn|hzl|oth)
* - pid: /\d{9}/
*
* Count objects from the input file which have required fields with valid values.
*
* Part Two answer:
* Batch contains 140 valid passports.
*/
import java.io.File;
import java.io.FileNotFoundException;
import java.util.Scanner;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import java.util.ArrayList;
import java.util.Arrays;
class PassportScanner {
public static String[] fieldsRequired = {"byr", "iyr", "eyr", "hgt", "hcl", "ecl", "pid"};
public static String[] eyeColorOptions = {"amb", "blu", "brn", "gry", "grn", "hzl", "oth"};
public static Pattern heightParser = Pattern.compile("(\\d{2,3})(cm|in)");
public static void main(String[] args) throws FileNotFoundException {
// Set up the input file
File passportBatchFile = new File("./passport_batch.txt");
Scanner passportBatch = new Scanner(passportBatchFile);
passportBatch.useDelimiter("\n{2,}");
// Set up array of passport "objects"
ArrayList<String> passportObjects = new ArrayList<String>();
// Step through the batch file and pull out individual records
while(passportBatch.hasNext()) {
passportObjects.add(passportBatch.next());
}
// Close the scanner once we've captured everything
passportBatch.close();
System.out.println("Batch contains " + passportObjects.size() + " passport objects.");
// Validate each passport object (String)
int validPassports = 0;
for (int i = 0; i < passportObjects.size(); i++) {
System.out.println("-- Checking Passport #" + i);
if (checkPassport(passportObjects.get(i))) {
System.out.println("-- PASS\n");
validPassports++;
} else {
System.out.println("-- FAIL\n");
}
}
// Report on the total
System.out.println("Batch contains " + validPassports + " valid passports.");
}
/**
* Take the passport string and figure out if it has all the required fields.
* @param passport
* @return boolean is passport valid?
*/
public static Boolean checkPassport(String passport) {
// Fields are either space or newline separated
String[] fields = passport.split("\\s");
// Sort the fields so that the output is easier to read
Arrays.sort(fields);
// Assume true because that sure seems like a great idea :upside_down_face:
Boolean valid = true;
// Keeping logic from Part 1, ensure all required fields are present.
for (String field : PassportScanner.fieldsRequired) {
if (!passport.contains(field)) {
System.out.println("Passport is missing required fields.");
return false;
}
}
for (String field : fields) {
String[] kv = field.split(":");
switch (kv[0]) {
case "byr":
valid = checkRange(1920, Integer.parseInt(kv[1]), 2002);
System.out.println("Birth Year: " + kv[1] + " -- " + valid.toString());
break;
case "iyr":
valid = checkRange(2010, Integer.parseInt(kv[1]), 2020);
System.out.println("Issue Year: " + kv[1] + " -- " + valid.toString());
break;
case "eyr":
valid = checkRange(2020, Integer.parseInt(kv[1]), 2030);
System.out.println("Expiration Year: " + kv[1] + " -- " + valid.toString());
break;
case "hgt":
// Height could be in inches or cm, match and capture components
Matcher heightMatches = heightParser.matcher(kv[1]);
// Was height specified correctly?
if (!heightMatches.matches()) {
System.out.println("Height: Invalid format or unit " + kv[1] + " -- false");
valid = false;
break;
}
// For the provided unit, is the height acceptable?
switch (heightMatches.group(2)) {
case "in":
valid = checkRange(59, Integer.parseInt(heightMatches.group(1)), 76);
System.out.println("Height: " + heightMatches.group(1) + " inches -- " + valid.toString());
break;
case "cm":
valid = checkRange(150, Integer.parseInt(heightMatches.group(1)), 193);
System.out.println("Height: " + heightMatches.group(1) + " centimeters -- " + valid.toString());
break;
default:
valid = false;
System.out.println("Height: Invalid unit " + kv[1] + " -- false");
break;
}
break;
case "hcl":
valid = (kv[1].toUpperCase().matches("#[0-9A-F]{6}"));
System.out.println("Hair color: " + kv[1].toUpperCase() + " -- " + valid);
break;
case "ecl":
valid = (java.util.Arrays.asList(eyeColorOptions).indexOf(kv[1]) > -1);
System.out.println("Eye color: " + kv[1].toUpperCase() + " -- " + valid);
break;
case "pid":
valid = (kv[1].matches("\\d{9}"));
System.out.println("Passport Number: " + kv[1] + " -- " + valid);
break;
case "cid":
// _
// __ __ _| |_ ___ ___ ___
// \ V V / ' \/ -_) -_) -_)
// \_/\_/|_||_\___\___\___|
//
break;
default:
valid = false;
System.out.println("Unexpected Field Found: " + field + " -- " + valid);
break;
}
// @TODO: Test cases are currently all written as (valid = (test)), so we
// need to bail if we ever get a fail. Otherwise the validity of the
// passport will always be the validity of the last field. How could I
// check everything but keep a failure once it is registered?
if (!valid) {
break;
}
}
return valid;
}
/**
* Determine if a number is within an acceptable range, inclusively
* @param low int lower limit
* @param check int number to check
* @param high int upper limit
* @return boolean is check number between the other two?
*/
public static Boolean checkRange(int low, int check, int high) {
return ((low - 1) < check) && (check < (high + 1));
}
}