条件对象到映射的转换

huangapple go评论62阅读模式
英文:

Conditional object to Map conversion

问题

case class Foo(a: String, b: Option[Int] = None, c: Option[String] = None)

def toMap: Map[String, Any] = {
   Map("a" -> a, "b" -> b, "c" -> c).collect {
     case (key, Some(value)) => key -> value
   }
}
val foo = Foo("bar")
val extFoo = Foo("ext", b = Some(12))

val fooMap = foo.toMap

println(fooMap)
// Map(a -> bar)

val extMap = extFoo.toMap

println(extMap)
// Map(a -> ext, b -> 12)

希望这个翻译对您有所帮助。如果您有任何其他问题,请随时提出。

英文:

I have a Scala case class, where some fields are Option. Like this:

case class Foo(a: String, b: Option[Int] = None, c: Option[String] = None)

I would like to convert this object to a Map[String, Any].

I have the following method:

def toMap: Map[String, Any] = {
   Map("a" -> a, "b" -> b, "c" -> c).collect {
     case (key, Some(value)) => key -> value
}

However, this method returns empty Map.

How should it be done?

//EDIT

Here's the example object of the Foo class:

val foo = Foo("bar")
val extFoo = Foo("ext", b = Some(12))

when calling toMap on this object, I want receive the following:

val fooMap = foo.toMap

println(fooMap)
// Map(a -> bar)

val extMap = extFoo.toMap

println(extMap)
// Map(a -> bar, b -> 12)

Basically, I want to remove key-value pairs, if the Option is equal to None, but still be able to map required fields.

答案1

得分: 1

为了解决您的问题,您需要在您正在应用于collect方法中的模式匹配中添加另一个case,以捕获其他情况。

case class Foo(a: String, b: Option[Int] = None, c: Option[String] = None) {
  def toMapCollect: Map[String, Any] =
    Map("a" -> a, "b" -> b, "c" -> c).collect {
      case (key, value) if !value.isInstanceOf[None.type]  => key -> value
    }

  def toMapFilter: Map[String, Any] =
    Map("a" -> a, "b" -> b, "c" -> c).filter { keyValue =>
      val (_, value) = keyValue
      !value.isInstanceOf[None.type]
    }

  def toMapFilterNot: Map[String, Any] =
    Map("a" -> a, "b" -> b, "c" -> c).filterNot { keyValue =>
      val (_, value) = keyValue
      value.isInstanceOf[None.type]
    }

  // 还有一种选项是手动构建Map,将`Option`放入`List[(String,Option[_])]`中,并检查它们是否不为空
  def anotherToMap: Map[String,Any] = {
    Map("a" -> a) ++ List(("b" -> b), ("c" -> c)).filter(_._2.nonEmpty)
  }
}

collect方法类似于应用过滤器然后应用map的操作。

根据提供的代码,只有在bc字段均为None时,toMap才会返回空Map。如果我写了以下代码:

val foo1 = Foo("1", None, None)
val foo2 = Foo("2", Some(2), None)
val foo3 = Foo("3", None, Some("3"))
val foo4 = Foo("4", Some(4), Some("4"))

println(s"foo1: ${foo1.toMap}")
println(s"foo2: ${foo2.toMap}")
println(s"foo3: ${foo3.toMap}")
println(s"foo4: ${foo4.toMap}")

输出将是:

foo1: Map()
foo2: Map(b -> 2)
foo3: Map(c -> 3)
foo4: Map(b -> 4, c -> 4)

我们可以观察到以下内容:

  • a始终被丢弃
  • 只有当它们为Some时,bc才会被保留

如果查看Map.collect的文档,我们可以看到scaladoc和该函数的签名如下:

/** 通过将部分函数应用于此`map`的所有定义域上的所有元素来构建新集合。
  *
  * @param pf     过滤和映射`map`的部分函数。
  * @tparam K2    返回的`map`的键类型。
  * @tparam V2    返回的`map`的值类型。
  * @return       通过将给定的部分函数`pf`应用于其定义域上的每个元素并收集结果而得到的新`map`。
  *               保留元素的顺序。
  */
def collect[K2, V2](pf: PartialFunction[(K, V), (K2, V2)]): Map[K2, V2]

这意味着collect接收一个名为pf的参数,其类型为PartialFunction函数具有其定义域,在定义域内定义了一个部分函数,它也是一个函数,但其定义域是一个子域(可以是完整的定义域、只是子集,也可以为空)。

对于函数平方根,其定义域(仅为实数)为0和正数。在Scala中,您可以像这样定义平方根的PartialFunction

val squareRoot: PartialFunction[Double, Double] = {
  case x if x >= 0 => Math.sqrt(x)
}

PartialFunction提供了isDefinedAt函数,可以让您知道函数是否在特定值上定义。

您所做的每个case x if condition => value都是在应用名为模式匹配的东西。当您编写以下代码时:

case (key, Some(value)) => key -> value

您在告诉系统只考虑key的任何String以及valueSome[Any]用于Map。

回到您定义的函数:

case class Foo(a: String, b: Option[Int] = None, c: Option[String] = None) {
  def toMap: Map[String, Any] =
    Map("a" -> a, "b" -> b, "c" -> c).collect {
      case (key, Some(value)) => key -> value
    }
}

我们可以像这样编写相同的代码:

case class Foo(a: String, b: Option[Int] = None, c: Option[String] = None) {
  val partialFunction: PartialFunction[(String,Any),(String,Any)] = {
    case (key, Some(value)) => key -> value
  }
  def toMap: Map[String, Any] =
    Map("a" -> a, "b" -> b, "c" -> c).collect(partialFunction)
}

然后,我们可以询问partialFunction是否在不同的值上定义。

// 基于PartialFunction被类型化为[(String,Any),(String,Any)],
// 我们只能传递类型为(String, Any)的元组给`isDefinedAt`

// 这些将返回true
partialFunction.isDefinedAt(("someInt",Some(1234)))
partialFunction.isDefinedAt(("someString",Some("some string")))
partialFunction.isDefinedAt(("someBoolean",Some(false)))

// 这些将返回false
partialFunction.isDefinedAt(("

<details>
<summary>英文:</summary>

**Edit: TL;DR**

to solve the issue you have, you would need to add another `case` to the [pattern matching](https://docs.scala-lang.org/tour/pattern-matching.html) you are applying in the `collect` method where you catch the rest of the cases.

```scala
case class Foo(a: String, b: Option[Int] = None, c: Option[String] = None) {
  def toMapCollect: Map[String, Any] =
    Map(&quot;a&quot; -&gt; a, &quot;b&quot; -&gt; b, &quot;c&quot; -&gt; c).collect {
      case (key, value) if !value.isInstanceOf[None.type]  =&gt; key -&gt; value
    }

  def toMapFilter: Map[String, Any] =
    Map(&quot;a&quot; -&gt; a, &quot;b&quot; -&gt; b, &quot;c&quot; -&gt; c).filter { keyValue =&gt;
      val (_, value) = keyValue
      !value.isInstanceOf[None.type]
    }

  def toMapFilterNot: Map[String, Any] =
    Map(&quot;a&quot; -&gt; a, &quot;b&quot; -&gt; b, &quot;c&quot; -&gt; c).filterNot { keyValue =&gt;
      val (_, value) = keyValue
      value.isInstanceOf[None.type]
    }

  // one more option is to build the Map manually, put the `Option`s in a `List[(String,Option[_])]` and check if they are not `None`
  def anotherToMap: Map[String,Any] = {
    Map(&quot;a&quot; -&gt; a) ++ List((&quot;b&quot; -&gt; b), (&quot;c&quot; -&gt; c)).filter(_._2.nonEmpty)
  }
}

The collect method is similar to apply a filter and then a map.

Not sure exactly what you are trying to solve, but this doesn't look like a good approach. Using Any could force you to make type casting later.


Based on the provided code, the only case where toMap returns an empty Map, is when b and c fields are None. If I wrote the following

val foo1 = Foo(&quot;1&quot;, None, None)
val foo2 = Foo(&quot;2&quot;, Some(2), None)
val foo3 = Foo(&quot;3&quot;, None, Some(&quot;3&quot;))
val foo4 = Foo(&quot;4&quot;, Some(4), Some(&quot;4&quot;))

println(s&quot;foo1: ${foo1.toMap}&quot;)
println(s&quot;foo2: ${foo2.toMap}&quot;)
println(s&quot;foo3: ${foo3.toMap}&quot;)
println(s&quot;foo4: ${foo4.toMap}&quot;)

the output will be

foo1: Map()
foo2: Map(b -&gt; 2)
foo3: Map(c -&gt; 3)
foo4: Map(b -&gt; 4, c -&gt; 4)

we can observe the following:

  • a is always discarded
  • b and c are only kept when they are Some

If we check the documentation of Map.collect, we can see that the scaladoc and the signature of that function is the following

  /** Builds a new collection by applying a partial function to all elements of this `map`
    *  on which the function is defined.
    *
    *  @param pf     the partial function which filters and maps the `map`.
    *  @tparam K2    the key type of the returned `map`.
    *  @tparam V2    the value type of the returned `map`.
    *  @return       a new `map` resulting from applying the given partial function
    *                `pf` to each element on which it is defined and collecting the results.
    *                The order of the elements is preserved.
    */
  def collect[K2, V2](pf: PartialFunction[(K, V), (K2, V2)]): Map[K2, V2]

This means that collect receives a parameter named pf of type PartialFunction. A function has a domain where is defined, a partial function is also a function but the domain is a subdomain (it could the complete domain, just a subset or it could be empty).


If we consider the function square root, the domain (only real numbers) is 0 and positive numbers. In scala, you can define a PartialFunction for square root like this

val squareRoot: PartialFunction[Double, Double] = {
  case x if x &gt;= 0 =&gt; Math.sqrt(x)
}

The trait PartialFunction provides the function isDefinedAt that lets you know if the function is defined at an specific value.


What you are doing for each case x if condition =&gt; value you are applying something named Pattern Matching. When you write

case (key, Some(value)) =&gt; key -&gt; value

you are saying that you will only consider any String for the key and only Some[Any] for the value of the Map.


Backing to the function you defined

case class Foo(a: String, b: Option[Int] = None, c: Option[String] = None) {
  def toMap: Map[String, Any] =
    Map(&quot;a&quot; -&gt; a, &quot;b&quot; -&gt; b, &quot;c&quot; -&gt; c).collect {
      case (key, Some(value)) =&gt; key -&gt; value
    }
}

we could write the same code like this

case class Foo(a: String, b: Option[Int] = None, c: Option[String] = None) {
  val partialFunction: PartialFunction[(String,Any),(String,Any)] = {
    case (key, Some(value)) =&gt; key -&gt; value
  }
  def toMap: Map[String, Any] =
    Map(&quot;a&quot; -&gt; a, &quot;b&quot; -&gt; b, &quot;c&quot; -&gt; c).collect(partialFunction)
}

Then we could ask to the partialFunction if it is defined at different values.

// based that the PartialFunction is typed as [(String,Any),(String,Any)],
// we can only pass a tuple of type (String, Any) to `isDefinedAt`

// these will return true
partialFunction.isDefinedAt((&quot;someInt&quot;,Some(1234)))
partialFunction.isDefinedAt((&quot;someString&quot;,Some(&quot;some string&quot;)))
partialFunction.isDefinedAt((&quot;someBoolean&quot;,Some(false)))

// these will return false
partialFunction.isDefinedAt((&quot;none&quot;,None))
partialFunction.isDefinedAt((&quot;string&quot;,&quot;string&quot;))
partialFunction.isDefinedAt((&quot;boolean&quot;,true))
partialFunction.isDefinedAt((&quot;int&quot;,5))
partialFunction.isDefinedAt((&quot;double&quot;,1.5))

huangapple
  • 本文由 发表于 2023年5月24日 20:22:37
  • 转载请务必保留本文链接:https://go.coder-hub.com/76323494.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定