1

I'm trying to wrap my head around map and reduce operations in Kotlin. At least, I guess it's reduce what I'm trying to do.

Let's say that I have a class called Car that takes any number (varargs constructor) of CarPart. Then, I have a list of CarPart which I'll do a map operation and from the result of the operation I need to build one Car using each subelement, something along these lines:

class CarPart(val description: String)
class Car(vararg val carPart: CarPart)

val carParts = listOf(CarPart("Engine"), CarPart("Steering Wheel")))

carParts.map { it.description.toUpperCase() }
    .map { CarPart(it) }
    .reduce { acc, carPart -> Car(carPart) }  <--- I'm struggling here, is reduce what I should be doing 
                                                   to construct one car from all the subelement?

PS.1: I know that the class design could be better and not take a varargs, this is just an example of a legacy application I'm refactoring and originally that's a Java class taking varargs which I can't change now.

PS.2: The example of mapping to a String and then creating an object out of that String is just for the sake of the example. The actual code grabs an object within the list.

Allan
  • 45
  • 1
  • 3
  • Does this answer your question? [Kotlin convert List to vararg](https://stackoverflow.com/questions/51161558/kotlin-convert-list-to-vararg) – Sweeper Jan 18 '22 at 16:13
  • FYI `fold` and `reduce` both run on a collection and build up a result (usually called the *accumulator*) by running a function on each element of the collection, updating the accumulator each time. So that pattern doesn't really fit here (since you need to call the constructor once, with all the items, instead of multiple times, once for each item). `fold` takes an initial result state (like an empty list), so that can be any type. `reduce` just combines the first 2 items, and the result of that is combined with item 3 etc - so the result is always the same type as the items in the collection – cactustictacs Jan 18 '22 at 19:32

2 Answers2

5

You can simply use a the spread operator (*) over an array:

val mappedCarParts = carParts
    .map { it.description.toUpperCase() }
    .map { CarPart(it) }
    .toTypedArray()

val car = Car(*mappedCarParts)

// Or even:

val car = carParts
    .map { it.description.toUpperCase() }
    .map { CarPart(it) }
    .toTypedArray()
    .let{ Car(*it) }
Some random IT boy
  • 7,569
  • 2
  • 21
  • 47
  • That's fantastic! Such a clever solution and I haven't even thought of the spread operator, I was stuck thinking that reduce is what I was looking for. Thanks! I liked what you did in the second example. You converted it to an array and used let to take the resulting array, Car takes an array as it accepts a vargargs and then we spread it straight away! Cheers! – Allan Jan 18 '22 at 16:38
  • Very glad it suits your needs! – Some random IT boy Jan 18 '22 at 16:39
2

You could just extract the constructor of the Car outside of the creation of the list. I don't see any reason as to why you'd want it inside.

val car = Car(
        *carParts
            .map { CarPart(it.description.uppercase(Locale.getDefault())) } //keep the .toUpperCase() if you are using an old version of Kotlin
            .toTypedArray()
    )

We need the spread operator there in order for the vararg to know that we are passing it the elements of the list and not the list itself.

AlexT
  • 2,524
  • 1
  • 11
  • 23
  • Thanks! That's a valid solution as well :) I ended up doing what has been suggested before as I think that splitting the creation of the object from the list makes it more readable, bu thanks anyways. – Allan Jan 18 '22 at 16:41