Loom
Loom

Reputation: 9986

Check if all keys are present in Map

Let there is a map config: Map[String, String].

And there are several keys: "foo", "bar", ...

I need to make sure that all keys are present in config. And if they are present, I need to call a function with values for these keys in the config map:

fun(config("foo"), config("bar"), config(...), ...)

The following is a solution:

val res = Option.when(config.contains("foo") & config.contains("bar") & config.contains(...) & ...)
    ( fun(config("foo"), config("bar"), config(...), ...) )

Or maybe:

val set = Set("foo", "bar", ...)
val res = Option.when(config.view.filterKeys(set).size == set.size)
    ( fun(config("foo"), config("bar"), config(...), ...) )

Both approaches look ugly and ineffective. Is there more concise way to implement the same behavior?

Upvotes: 0

Views: 496

Answers (5)

Loom
Loom

Reputation: 9986

The following is a flatMap way:

config.get("key1").flatMap(key1 =>
config.get("key2").flatMap(key2 =>
...
config.get("keyN").flatMap(keyN =>
fun(key1, key2, ..., keyN)
))...) 

Upvotes: 0

Mario Galic
Mario Galic

Reputation: 48400

Consider forall in combination with contains

val requiredKeys = List("foo", "bar")

if (requiredKeys forall config.contains) {
  // work with config
} else {
  // handler error
}

or based on Tom Crockett consider keySet approach

val requiredKeys = Set("foo", "bar")

if (requiredKeys subsetOf config.keySet) {
  // work with config
} else {
  // handler error
}

Upvotes: 1

V-Lamp
V-Lamp

Reputation: 1678

This is using cats, a very common FP library:

import cats.implicits._

def fun(a: String, b: String, c: String): MyConfigClass = ???

val parsedOpt: Option[MyConfigClass] = 
  (config.get("a"), config.get("b"), config.get("c"))
  .mapN(fun)

The mapN method does what you want, it will extact all values if they exist and provide them to fun. For the curious, it relies on the fact that Option is an Applicative.

To show its power, you could also get back a list of missing keys, to know where the issue was:

import cats.data._
def getConfig(key: String): Either[NonEmptyList[String], String] =
config.get(key).toRightNel(s"Key $key not found")

val parsedValidated: Either[NonEmptyList[String], MyConfigClass] =
  (getConfig("a"), getConfig("b"), getConfig("c"))
 .parMapN(fun)

Upvotes: 0

Since the set of keys is static, you do not need to do anything too complex.
You just need to attempt to get each key and if all are in call the function and wrap the result in a Some, if not then return a None.

for {
  value1 <- config.get("key1")
  // ...
  valueN <- config.get("keyN")
} yield fun(value1, ..., valueN)

If you have cats, you can do it like.

(
  config.get("key1"),
  // ...
  config.get("keyN")
).mapN(fun(_).tupled)

Upvotes: 2

Bal&#225;zs N&#233;meth
Bal&#225;zs N&#233;meth

Reputation: 6637

if you have your keys as ("foo", "bar", ...).map(config) returns the values, right? then if it contains a None, then not all keys are found. I would start to think along this idea.

Passing elements of a List as parameters to a function with variable arguments helps here, so val args = list.map(config); then the condition to check if all values are present, and finally fun(args:_*).

how about that?

Upvotes: 0

Related Questions