DEV Community

loading...

Advent of Code 2020 in Kotlin: Day 04

heylucas profile image Lucas Originally published at heylucas.net on ・3 min read

Day 04 was Advent of Code’s version of Papers, Please. You had to go over a list of passports and validate them. I’m not too happy with how the code turned out, as it’s a bit verbose for my taste. Alas, it’s not terrible.

Data representation

A passport must contain a set of fields and we need to ensure they are present. I created a Passport class with an entry for each of those fields:

data class Passport(
    val byr: Int?,
    val iyr: Int?,
    val eyr: Int?,
    val hgt: String?,
    val hcl: String?,
    val ecl: String?,
    val pid: String?,
    val cid: String?) { }
Enter fullscreen mode Exit fullscreen mode

You’ll see I mark each field as nullable (the ? after the type). I do this is because each passport maybe have missing fields, making them invalid.

To help me construct the Passport objects, I defined a Builder class:

data class Passport(
  ...
) {
    class Builder {
      var byr: Int? = null
      var iyr: Int? = null
      var eyr: Int? = null
      var hgt: String? = null
      var hcl: String? = null
      var ecl: String? = null
      var pid: String? = null
      var cid: String? = null

      fun setField(fieldName: String, fieldValue: String) {
        when (fieldName) {
          "byr" -> byr = fieldValue.toInt()
          "iyr" -> iyr = fieldValue.toInt()
          "eyr" -> eyr = fieldValue.toInt()
          "hgt" -> hgt = fieldValue
          "hcl" -> hcl = fieldValue
          "ecl" -> ecl = fieldValue
          "pid" -> pid = fieldValue
      "cid" -> cid = fieldValue
      }
    }

    fun build() = Passport(
      byr, iyr, eyr,
      hgt, hcl, ecl,
      pid, cid)
    }
}
Enter fullscreen mode Exit fullscreen mode

Reading the data

Like on Day 03, I defined an extension method on the File class. This method returns a nullable list of Passports.

fun File.toPassportListOrNull() : List<Passport>? {
  val result = ArrayList<Passport>()
  var passportBuilder = Passport.Builder()

  forEachLine {
    if (it.isEmpty()) {
      result.add(passportBuilder.build())
      passportBuilder = Passport.Builder()
    } else {
      it.split(" ").forEach {
        val parts = it.split(":")
        passportBuilder.setField(parts[0], parts[1])
      }
    }
  }

  result.add(passportBuilder.build())

  return result
}
Enter fullscreen mode Exit fullscreen mode

Parsing the passports is a bit tricky. The fields might span multiple lines. An empty line marks the end of a passport record. That’s why in the code above I check if the current line is empty to insert the passport in the list.

Counting the valid passports

To check if a passport is valid or not, I just added a new method to the Passport class:

    fun isValid(): Boolean {
    return (
        byr != null &&
        iyr != null &&
        eyr != null &&
        hgt != null &&
        hcl != null &&
        ecl != null &&
        pid != null
    )
    }
Enter fullscreen mode Exit fullscreen mode

The code to actually count the passports is trivial:

fun main() {
  val count = File("input.txt").toPassportListOrNull()?.count { it.isValid() }
  print(count)
}

Enter fullscreen mode Exit fullscreen mode

Part 2

The second part of the problem is nothing special. Before we just had to check if the required fields were present, now we must check if the fields have correct values. That’s easy enough, we just extend the isValidmethod.

    val validEyeColors = setOf("amb", "blu", "brn", "gry", "grn", "hzl", "oth")
    val hairColorRegex = Regex("""#[a-f0-9]{6}""")

    fun validHeight(height: String): Boolean {
    if (height.contains("in")) {
        val value = height.substring(0..height.length - 3).toInt()
        return 50 <= value && value <= 76
    } else if (height.contains("cm")) {
        val value = height.substring(0..height.length - 3).toInt()
        return 150 <= value && value <= 193
    } else {
        return false
    }
    }

    fun isValid(): Boolean {
    return (
        byr != null && byr in 1920..2002 &&
        iyr != null && iyr in 2010..2020 &&
        eyr != null && eyr in 2020..2030 &&
        hgt != null && validHeight(hgt) &&
        hcl != null && hairColorRegex.matches(hcl) &&
        ecl != null && validEyeColors.contains(ecl) &&
        pid != null && pid.length == 9
    )
    }
Enter fullscreen mode Exit fullscreen mode

The validation code is pretty ugly but it gets the job done. The first 3 fields are years, so we just check if they are within a given range.

To validate the height, I created a separate method. Heights can be given either in centimeters or inches. It made sense to have a separate method for that.

Hair color was just a matter of using a regular expression for a HEX color code. Eye color is a set membership check. And the last field, Passport ID (PID), we just check its length.

The end

That’s it for day 4. I’m not exactly happy with this code. I feel like I wrote too much to accomplish too little. I’m sure Kotlin has some facilities that would make writing this code a lot shorter, but I haven’t explored that yet.

Now on to day 05!

Discussion (0)

pic
Editor guide