Erik W
Erik W

Reputation: 63

Kotlin/Java functional and immutable way of collecting a map in a map

Currently i'm reading a file through the Java API and adding items to a map through the method foreach which forces me to make use of the mutablemap. Is there a way of collecting the items without the mutablemap? I know there is a method collect but I couldn't get it working.

the current way:

    val result = mutableMapOf<Int, MutableMap<Int, Double>>()
    Files.lines(Paths.get(folderPath))
                    .map { line -> line.split(",") }
                    .map { items -> Triple(items[0].toInt(), items[1].toInt(), items[2].toDouble()) }
                    .forEach { (id, article, rating) ->
                        if (result.containsKey(id))
                            result[id]!!.put(article, rating)
                        else
                            result.put(id, mutableMapOf(Pair(article, rating)))
                    }

EDIT:

my goal is to merge the triple objects based on the first value of the triple. So a scenario would be two triple objects(1, 2, 5.5) and (1,3, 5.5). The first value of the triple is the user id, the second is the article id and the third is the rating of the article. after merging there would be one single entry in the map containing the first key = the user id, the first value of the triple, and the value would be a map containing the articles which the user rated.

It is currently working how I want it to work, but I was curious if there was a more functional way of solving this.

Upvotes: 4

Views: 1300

Answers (2)

Jorn Vernee
Jorn Vernee

Reputation: 33895

You could do this:

val result: Map<Int, Map<Int, Double>> = Files.lines(Paths.get(folderPath))
        .map { it.split(",") }
        .collect(groupingBy({ it[0].toInt() },
                toMap<List<String>, Int, Double>({ it[1].toInt() }) { it[2].toDouble() }))

The compiler didn't seem to be able to infer the types for toMap, so I added a type hint there.

I will point out that Kotlin doesn't have an immutable Map implementation. The default returned by mapOf is java.util.LinkedhashMap which is mutable. It's just that the Map interface doesn't expose the mutating methods (like put, putAll). And you can freely assign a MutableMap<Int, MutableMap<Int, Double>> to a Map<Int, Map<Int, Double>>, which is also what I'm doing here with the type annotation on result, I'm forcing the static type to be the immutable Map.

Upvotes: 3

Eugene
Eugene

Reputation: 120988

I am not comfortable enough with Kotlin, but it seems that you should be using a simple : Collectors.collectingAndThen. Something like:

  System.out.println(Stream.of(1, 2, 3, 1)
             .collect(Collectors.collectingAndThen(
                  Collectors.groupingBy(Function.identity(), Collectors.counting()),
                       Collections::unmodifiableMap)));

The idea is that you collect to a mutable map and then wrap that into an ImmutableMap.

If by the way you have guava on the class path - it has build collectors that collect to an ImmutableMap.

Upvotes: 3

Related Questions