Home > Software engineering >  trying to avoid using data classes with copy method to assign a new object
trying to avoid using data classes with copy method to assign a new object

Time:01-21

I have the following data class

data class Passport(
    val byr: String = "",
    val iyr: String = "",
    val eyr: String = "",
    val hgt: String = "",
    val hcl: String = "",
    val ecl: String = "",
    val pid: String = "",
    val cid: String = ""
)

And I have this enum class

enum class PassportFields(val field: String) {
    BYR("byr"),
    IYR("iyr"),
    EYR("eyr"),
    HGT("hgt"),
    HCL("hcl"),
    ECL("ecl"),
    PID("pid"),
    CID("cid")
}

I have this method that will create the individual passports but I have to use a when statement.

What I would like to avoid is having the mutable passportData I would really like to have something like this val passportData = .... However, if I would do something like that I can't assign using the .copy(...)

I would also like to avoid using the copy to assign a new object each time.

passportData = passportData.copy(byr = field)

This is my method

fun List<String>.parsePassports(): List<Passport> {
    return this.map { passportField ->
        val passport = passportField.split("\n", " ")
        
        var passportData = Passport()

        passport.map { field ->
            when(field) {
                PassportFields.BYR.field -> {
                    passportData = passportData.copy(byr = field)
                }
                PassportFields.IYR.field -> {
                    passportData = passportData.copy(iyr = field)
                }
                PassportFields.EYR.field -> {
                    passportData = passportData.copy(eyr = field)
                }
                PassportFields.HGT.field -> {
                    passportData = passportData.copy(hgt = field)
                }
                PassportFields.HCL.field -> {
                    passportData = passportData.copy(hcl = field)
                }
                PassportFields.ECL.field -> {
                    passportData = passportData.copy(ecl = field)
                }
                PassportFields.PID.field -> {
                    passportData = passportData.copy(pid = field)
                }
                PassportFields.CID.field -> {
                    passportData = passportData.copy(cid = field)
                }
            }
        }
        passportData
    }
}

CodePudding user response:

You don't want to create any extra objects, but also want the Passport properties to be vals. This means that you must get all the values for the fields ready, before you call the constructor for Passport. You can't create a half-created passport, and then gradually assign things to it as you loop over the list passport.

Luckily, the value for each passport field f can be easily computed: if the list passport contains f, then the value is f, otherwise it is "".

Hence:

fun List<String>.parsePassports(): List<Passport> {
    return this.map { passportField ->
        val passport = passportField.split("\n", " ").toSet()
        Passport(
            PassportFields.BYR.field.takeIf(passport::contains) ?: "",
            PassportFields.IYR.field.takeIf(passport::contains) ?: "",
            PassportFields.EYR.field.takeIf(passport::contains) ?: "",
            PassportFields.HGT.field.takeIf(passport::contains) ?: "",
            PassportFields.HCL.field.takeIf(passport::contains) ?: "",
            PassportFields.ECL.field.takeIf(passport::contains) ?: "",
            PassportFields.PID.field.takeIf(passport::contains) ?: "",
            PassportFields.CID.field.takeIf(passport::contains) ?: "",
        )
    }
}

You could put this in a loop, but I actually find this less readable:

val arguments = PassportFields.values().map { 
    it.field.takeIf(passport::contains) ?: ""
}
Passport(
    arguments[0],
    arguments[1],
    arguments[2],
    arguments[3],
    arguments[4],
    arguments[5],
    arguments[6],
    arguments[7],
)

CodePudding user response:

data class Passport(
  val byr: String = "",
  val iyr: String = "",
  val eyr: String = "",
  val hgt: String = "",
  val hcl: String = "",
  val ecl: String = "",
  val pid: String = "",
  val cid: String = ""
)

fun List<String>.parsePassports(): List<Passport> {
  return this.map { passportString ->
    val parts = passportString
      .split("\n", ":")
      .chunked(2)
      .groupBy { it.first() }
      .mapValues { (_, value) -> value[0][1] }
    Passport(
      byr = parts["byr"]?: "",
      iyr = parts["iyr"]?: "",
      eyr = parts["eyr"]?: "",
      hgt = parts["hgt"]?: "",
      hcl = parts["hcl"]?: "",
      ecl = parts["ecl"]?: "",
      pid = parts["pid"]?: "",
      cid = parts["cid"]?: ""
    )
  }
}

val data = listOf(
  "byr:2001\niyr:2019\neyr:2029\nhgt:185\nhcl:#cfa07d\necl:blu\npid:000123456\ncid:147",
  "byr:1965\niyr:2015\neyr:2025\nhgt:177\nhcl:#afb90b\necl:amb\npid:123456789\ncid:78",
)

val result = data.parsePassports()

println(result)
  •  Tags:  
  • Related