英文:
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:
a
is always discardedb
andc
are 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))
通过集体智慧和协作来改善编程学习和解决问题的方式。致力于成为全球开发者共同参与的知识库,让每个人都能够通过互相帮助和分享经验来进步。
评论