Home > Software engineering >  Create list from two uneven lists
Create list from two uneven lists

Time:06-10

I'm new to Kotlin and it's intricacies, but have two lists of unknown sizes and contents that could look something like this

codes = ["or", "or", "or", "parks", "parks", "wa", "wa", "wa", "id"]
types = ["STATE", "NATIONAL", "STATE", "STATE"]

Each type relates to a non-distinct item within codes (e.g. parks->NATIONAL, wa->STATE), but the total number of STATEs are needed. In this case, 7 STATEs i=are expected.

My initial thought was to do something like this

var typesIdx = 0
var prevCode = ""

val totalList = mutableListOf<String>()
    
for (currCode in codes) {
    if (currCode != prevCode) {
        prevCode = currCode
        typesIdx =1
    }    
    totalList  = types.get(typesIdx).toString()
} 

But I feel like there's a better and smarter way to do this that implements more of Kotlin's built in functions rather than simply for looping and creating the list bit-by-bit

CodePudding user response:

If I have understood what you want to do correctly, you can use the distinct() method on a list to help here. It returns a list containing only distinct elements from the original list, preserving the order of appearance.

val codes = listOf("or", "or", "or", "parks", "parks", "wa", "wa", "wa", "id")
val types = listOf("STATE", "NATIONAL", "STATE", "STATE")

// First, condense the "codes" list down to its distinct entries - which
// should make it the same size as "Types"
val condensedCodes = codes.distinct()
println(condensedCodes) // ["or","parks","wa","id"]

// Then create a map from code to type
val typeMap = condensedCodes.zip(types).toMap()
println(typeMap) // {or=STATE, parks=NATIONAL, wa=STATE, id=STATE}

// Then use that map to count the original codes list based on type
val numStates = codes.count { typeMap[it] == "STATE" }
println(numStates) // prints 7

// or if you want the list of states
val states = codes.filter { typeMap[it] == "STATE" }
println(states) // [or, or, or, wa, wa, wa, id]

// or if you want to transform the codes list to a list of types
val typeOfCodes = codes.map { typeMap[it] }
println(typeOfCodes) // [STATE, STATE, STATE, NATIONAL, NATIONAL, STATE, STATE, STATE, STATE]

The approach above will not work if the same group of codes appears in multiple places in your list. You can't use distinct any more, but it's still possible with the following approach:

val codes = listOf("or", "or", "or", "parks", "parks", "wa", "wa", "id", "or", "or")
val types = listOf("STATE", "NATIONAL", "STATE", "STATE", "STATE")

val condensedCodes = codes.zipWithNext()
                          .filter { it.first != it.second }
                          .map { it.first }   codes.last()

How does this work? The zipWithNext() creates a list like this

[(or, or), (or, or), (or, parks), ...

then it gets filtered down to only the first elements from the mis-matched pairs, essentially selecting the last element of each set of repeats. The last group is missed this way, so then we add codes.last() on the end.

["or", "or", "or", "parks", "parks", "wa", "wa", "wa", "id"]
              ^              ^                    ^          
[            "or",          "parks",             "wa"      ]   "id"

If you were going to use this in a lot of places you could define an extension function (a neat feature of Kotlin) for lists

fun <T> List<T>.condense() = when(isEmpty()) {
    true -> listOf()
    else -> zipWithNext().filter { it.first != it.second }.map { it.first }   last()
}

to let you just use

val condensedCodes = codes.condense()

CodePudding user response:

If my understanding of your question is correct, you just want to replace your for loop with kotlin standard library functions and get the output of totalList.

You can do so in this fashion

val totalList = codes
    .groupBy { it } // Create a map -> {or=[or, or, or], parks=[parks, parks], wa=[wa, wa, wa], id=[id]}
    .values // Get a list of values from the previous map -> [[or, or, or], [parks, parks], [wa, wa, wa], [id]]
    .flatMapIndexed { index, v -> // flatten inner and outer lists while transforming each inner list
        v.map { types[index] } // In inner lists, replace [codes] element with [types] element
    }

In short

val totalList = codes.groupBy { it }.values.flatMapIndexed { index, v -> v.map { types[index] } }

Final output is

[STATE, STATE, STATE, NATIONAL, NATIONAL, STATE, STATE, STATE, STATE]

Point to be noted: This is less performant than your original code because of repeated iterations, as well as allocation of more collections.

  • Related