Reputation: 1142
I wrote the following function:
private[this] def extractAerospikeBinsFromKafkaConnectorResource(connectors: List[KafkaConnector]): Map[AerospikeNamespace, Map[AerospikeSet, RecoverBins]] = {
connectors.foldLeft(Map.empty[AerospikeNamespace, Map[AerospikeSet, RecoverBins]]) {
case (acc, connector) =>
acc ++
TopicsConfig
.parseYaml(connector.getSpec.getConfig.get("config-yaml").toString)
.topics
.values
.map(topicConfig => topicConfig.mapping.namespace.value -> Map(topicConfig.mapping.set.value -> RecoverBins(topicConfig.mapping.bins)))
}
}
acc
is Map[AerospikeNamespace, Map[AerospikeSet, RecoverBins]], for example:Map(namespace -> Map(test-recovery-set-single -> SingleBin))
TopicsConfig
contains namespace
, set
and List[Bins]
which is logically Map[Namespace, Map[Set, List[Bins]]
I want to find a nice way to merge two maps of type Map[AerospikeNamespace, Map[AerospikeSet, RecoverBins]]
into one, such that if they have same AerospikeNamespace, its will be contacted and if they have same AerospikeNamespace and AerospikeSet its will be contacted, for example:
input:
output:
Map (
namespace -> Map(
test-recovery-set-double -> List[MultiBin, SingleBin]),
test-recovery-set-multi -> List[MultiBin]),
),
namespace2 -> Map(test-recovery-set-single -> SingleBin)
)
Upvotes: 1
Views: 302
Reputation: 15086
The cats library provides this functionality virtually out of the box.
val maps = List(
Map("namespace" -> Map("test-recovery-set-double" -> "SingleBin")),
Map("namespace" -> Map("test-recovery-set-double" -> "MultiBin")),
Map("namespace" -> Map("test-recovery-set-multi" -> "MultiBin")),
Map("namespace2" -> Map("test-recovery-set-other" -> "SingleBin"))
)
import cats.implicits._
maps.foldMap(_.view.mapValues(_.view.mapValues(_ :: Nil).toMap).toMap)
/*
Map(
namespace2 -> Map(
test-recovery-set-other -> List(SingleBin)
),
namespace -> Map(
test-recovery-set-multi -> List(MultiBin),
test-recovery-set-double -> List(SingleBin, MultiBin)
)
)
*/
foldMap
works by wrapping the inner values of your maps in a List
and then folding them into a single Map
by using the Monoid
instance of Map[A, Map[B, List[C]]]
.
The only less elegant part of this code comes from mapValues
in scala 2.13 being awkward to use.
Instead of using foldMap
, it's not hard to implement yourself:
import cats.implicits._
val listMaps = maps.map(_.view.mapValues(_.view.mapValues(_ :: Nil).toMap).toMap)
listMaps.reduce(_ |+| _) // |+| is an alias for Monoid.combine
As pointed out in the comments by @LuisMiguelMejíaSuárez, you can also clean up the mapValues
a bit with some more cats magic.
maps.foldMap(_.fmap(_.fmap(_ :: Nil)))
// or
maps.foldMap(_.nested.map(_ :: Nil).value)
Upvotes: 2
Reputation: 27421
Assuming Scala 2.13 then this helper function merges a list of Maps
to a single Map
:
def mergeMaps[T, U](maps: List[Map[T, U]]): Map[T, List[U]] =
maps
.flatMap(_.toList) // Convert each map to list and concatenate
.groupMap(_._1)(_._2) // Group by key to new Map
It converts each map to a list of pairs, flattens the list, and then creates a new map by grouping on the first value in the pair and extracting the second value.
Now you just need to apply this to the outer map and then each inner map:
val maps = List(
Map("namespace" -> Map("test-recovery-set-double" -> "SingleBin")),
Map("namespace" -> Map("test-recovery-set-double" -> "MultiBin")),
Map("namespace" -> Map("test-recovery-set-multi" -> "MultiBin")),
Map("namespace2" -> Map("test-recovery-set-other" -> "SingleBin"))
)
mergeMaps(maps).map{ case (k, v) => k -> mergeMaps(v) }
The result is
Map(namespace2 -> Map(test-recovery-set-other -> List(SingleBin)), namespace -> Map(test-recovery-set-multi -> List(MultiBin), test-recovery-set-double -> List(SingleBin, MultiBin)))
Upvotes: 1