英文:
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的操作。
根据提供的代码,只有在b和c字段均为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时,b和c才会被保留
如果查看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以及value的Some[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("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]
}
// 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("a" -> a) ++ List(("b" -> b), ("c" -> 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("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}")
the output will be
foo1: Map()
foo2: Map(b -> 2)
foo3: Map(c -> 3)
foo4: Map(b -> 4, c -> 4)
we can observe the following:
ais always discardedbandcare only kept when they areSome
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 >= 0 => 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 => value you are applying something named Pattern Matching. When you write
case (key, Some(value)) => key -> 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("a" -> a, "b" -> b, "c" -> c).collect {
case (key, Some(value)) => key -> 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)) => key -> value
}
def toMap: Map[String, Any] =
Map("a" -> a, "b" -> b, "c" -> 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(("someInt",Some(1234)))
partialFunction.isDefinedAt(("someString",Some("some string")))
partialFunction.isDefinedAt(("someBoolean",Some(false)))
// these will return false
partialFunction.isDefinedAt(("none",None))
partialFunction.isDefinedAt(("string","string"))
partialFunction.isDefinedAt(("boolean",true))
partialFunction.isDefinedAt(("int",5))
partialFunction.isDefinedAt(("double",1.5))
通过集体智慧和协作来改善编程学习和解决问题的方式。致力于成为全球开发者共同参与的知识库,让每个人都能够通过互相帮助和分享经验来进步。


评论