英文:
scala: Nested String Map Builder
问题
I understand your request to translate the provided code and explanation. Here's the translated code snippet without any additional content:
我想创建一个名为NestedStrMap的类,其签名如下:
final class NestedStrMap[A](list: List[A], first: A => String, rest: (A => String)*)
我想在其中编写一个名为`asMap`的函数,该函数可以使用`first`和`rest`构建嵌套映射。但是,我无法确定如何定义此函数的返回类型。
def asMap = {
rest.toList.foldLeft(list.groupBy(first)) { (acc, i) =>
acc.view.mapValues(l => l.groupBy(i)).toMap // 失败,因为返回类型不匹配
}
}
以下是我希望如何使用它的示例:
```scala
case class TestResult(name: String, testType: String, score: Int)
val testList = List(
TestResult("A", "math", 75),
TestResult("B", "math", 80),
TestResult("B", "bio", 90),
TestResult("C", "history", 50)
)
val nestedMap = NestedStrMap(testList, _.name, _.testType)
val someMap: Map[String, Map[String, List[TestResult]] = nestedMap.asMap
println(someMap)
/*
Map(
"A" -> Map("math" -> List(TestResult("A", "math", 75)))
"B" -> Map(
"math" -> List(TestResult("B", "math", 80)),
"bio" -> List(TestResult("B", "bio", 90))
),
"C" -> Map("history" -> List(TestResult("C", "history", 50)))
)
*/
这在Scala中是否可行?
请 let me know if you need anything else.
<details>
<summary>英文:</summary>
I want to create a class called NestedStrMap where it has a signature as such:
final class NestedStrMap[A](list: List[A], first: A => String, rest: (A => String)*)
I want to write a function `asMap` inside of it where I can take `first` and `rest` to build a nested map. However, I can't figure out how to define the return type of this function.
def asMap = {
rest.toList.foldLeft(list.groupBy(first)) { (acc, i) =>
acc.view.mapValues(l => l.groupBy(i)).toMap // fails because the return type doesn't match
}
}
Here's an example of how I'd like to use it:
case class TestResult(name: String, testType: String, score: Int)
val testList = List(
TestResult("A", "math", 75),
TestResult("B", "math", 80),
TestResult("B", "bio", 90),
TestResult("C", "history", 50)
)
val nestedMap = NestedStrMap(testList, _.name, _.testType)
val someMap: Map[String, Map[String, List[TestResult]] = nestedMap.asMap
println(someMap)
/*
Map(
"A" -> Map("math" -> List(TestResult("A", "math", 75)))
"B" -> Map(
"math" -> List(TestResult("B", "math", 80)),
"bio" -> List(TestResult("B", "bio", 90))
),
"C" -> Map("history" -> List(TestResult("C", "history", 50)))
)
*/
Is this doable in scala?
</details>
# 答案1
**得分**: 2
你想要返回 `Map[String, Map[String, ... Map[String, List[A]]]]`。这个类型必须在编译时知道 `rest: (A => String)*` 的长度。你可以使用 Shapeless 的 `Sized` 来引入一个类型类,具体信息可以在[这里](https://github.com/milessabin/shapeless/wiki/Feature-overview:-shapeless-2.0.0#collections-with-statically-known-sizes)找到。
在 Scala 2.13 中,如果你想使用 `import scala.collection.Seq`,以便 `Seq` 引用到标准的 `scala.Seq` 或 `scala.collection.immutable.Seq`,你可以定义以下隐式:
```scala
implicit def immutableSeqAdditiveCollection[T]: shapeless.AdditiveCollection[collection.immutable.Seq[T]] = null
如果你不想手动指定 N
,你可以定义一个宏:
import scala.language.experimental.macros
import scala.reflect.macros.whitebox
object NestedStrMap {
def apply[A](list: List[A])(selectors: (A => String)*): NestedStrMap[A, _ <: Nat] = macro applyImpl[A]
def applyImpl[A: c.WeakTypeTag](c: whitebox.Context)(list: c.Tree)(selectors: c.Tree*): c.Tree = {
import c.universe._
val A = weakTypeOf[A]
val len = selectors.length
q"new NestedStrMap[$A, _root_.shapeless.nat.${TypeName(s"_$len")}]($list, ..$selectors)"
}
}
这将不适用于:
val sels = Seq[TestResult => String](_.name, _.testType)
val nestedMap = NestedStrMap(testList)(sels: _*)
因为 sels
是运行时值。
另一种方法是在一开始就使用宏(与你想要的 foldRight
/foldLeft
一样):
import scala.language.experimental.macros
import scala.reflect.macros.whitebox
final class NestedStrMap[A](list: List[A])(selectors: (A => String)*) {
def asMap: Any = macro NestedStrMapMacro.asMapImpl[A]
}
object NestedStrMapMacro {
def asMapImpl[A: c.WeakTypeTag](c: whitebox.Context): c.Tree = {
import c.universe._
val A = weakTypeOf[A]
val ListA = weakTypeOf[List[A]]
c.prefix.tree match {
case q"new NestedStrMap[..$_]($list)(..$selectors)" =>
val func = selectors.foldRight(q"_root_.scala.Predef.identity[$ListA]")((sel, acc) =>
q"(_: $ListA).groupBy($sel).view.mapValues($acc).toMap"
)
q"$func.apply($list)"
}
}
}
英文:
You want to return Map[String, Map[String, ... Map[String, List[A]]]]
. The type must be known at compile time. So the length of rest: (A => String)*
must be known at compile time. You can introduce a type class using Shapeless Sized
// libraryDependencies += "com.chuusai" %% "shapeless" % "2.3.10"
import shapeless.nat.{_0, _2}
import shapeless.{Nat, Sized, Succ}
import scala.collection.Seq // Scala 2.13
// type class
trait AsMap[A, N <: Nat] {
type Out
def apply(list: List[A], selectors: Sized[Seq[A => String], N]): Out
}
object AsMap {
type Aux[A, N <: Nat, Out0] = AsMap[A, N] {type Out = Out0}
def instance[A, N <: Nat, Out0](f: (List[A], Sized[Seq[A => String], N]) => Out0): Aux[A, N, Out0] = new AsMap[A, N] {
type Out = Out0
override def apply(list: List[A], selectors: Sized[Seq[A => String], N]): Out = f(list, selectors)
}
implicit def zero[A]: Aux[A, _0, List[A]] = instance((l, _) => l)
implicit def succ[A, N <: Nat](implicit
asMap: AsMap[A, N]
): Aux[A, Succ[N], Map[String, asMap.Out]] =
instance((l, sels) => l.groupBy(sels.head).view.mapValues(asMap(_, sels.tail)).toMap)
}
final class NestedStrMap[A, N <: Nat](list: List[A], selectors: (A => String)*){
def asMap(implicit asMap: AsMap[A, N]): asMap.Out =
asMap(list, Sized.wrap[Seq[A => String], N](selectors))
}
object NestedStrMap {
def apply[N <: Nat] = new PartiallyApplied[N]
class PartiallyApplied[N <: Nat] {
def apply[A](list: List[A])(selectors: (A => String)*) = new NestedStrMap[A, N](list, selectors: _*)
}
}
case class TestResult(name: String, testType: String, score: Int)
val testList: List[TestResult] = List(
TestResult("A", "math", 75),
TestResult("B", "math", 80),
TestResult("B", "bio", 90),
TestResult("C", "history", 50)
)
val nestedMap = NestedStrMap[_2](testList)(_.name, _.testType)
val someMap = nestedMap.asMap
someMap: Map[String, Map[String, List[TestResult]]]
//Map(A -> Map(math -> List(TestResult(A,math,75))), B -> Map(bio -> List(TestResult(B,bio,90)), math -> List(TestResult(B,math,80))), C -> Map(history -> List(TestResult(C,history,50))))
In Scala 2.13, instead of import scala.collection.Seq
(i.e. if you want Seq
to refer to scala.Seq
aka scala.collection.immutable.Seq
, which is standard for Scala 2.13, rather than to scala.collection.Seq
) then you can define
implicit def immutableSeqAdditiveCollection[T]:
shapeless.AdditiveCollection[collection.immutable.Seq[T]] = null
(Not sure why this implicit isn't defined, I guess it should.)
https://stackoverflow.com/questions/69591709/cats-auto-derived-with-seq
If you don't want to specify N
manually, you can define a macro
import scala.language.experimental.macros
import scala.reflect.macros.whitebox // libraryDependencies += scalaOrganization.value % "scala-reflect" % scalaVersion.value
object NestedStrMap {
def apply[A](list: List[A])(selectors: (A => String)*): NestedStrMap[A, _ <: Nat] = macro applyImpl[A]
def applyImpl[A: c.WeakTypeTag](c: whitebox.Context)(list: c.Tree)(selectors: c.Tree*): c.Tree = {
import c.universe._
val A = weakTypeOf[A]
val len = selectors.length
q"new NestedStrMap[$A, _root_.shapeless.nat.${TypeName(s"_$len")}]($list, ..$selectors)"
}
}
// in a different subproject
val nestedMap = NestedStrMap(testList)(_.name, _.testType)
val someMap = nestedMap.asMap
someMap: Map[String, Map[String, List[TestResult]]]
//Map(A -> Map(math -> List(TestResult(A,math,75))), B -> Map(bio -> List(TestResult(B,bio,90)), math -> List(TestResult(B,math,80))), C -> Map(history -> List(TestResult(C,history,50))))
This will not work with
val sels = Seq[TestResult => String](_.name, _.testType)
val nestedMap = NestedStrMap(testList)(sels: _*)
because sels
is a runtime value.
Alternatively to Shapeless, you can apply macros from the very beginning (with foldRight
/foldLeft
as you wanted)
import scala.language.experimental.macros
import scala.reflect.macros.whitebox
final class NestedStrMap[A](list: List[A])(selectors: (A => String)*) {
def asMap: Any = macro NestedStrMapMacro.asMapImpl[A]
}
object NestedStrMapMacro {
def asMapImpl[A: c.WeakTypeTag](c: whitebox.Context): c.Tree = {
import c.universe._
val A = weakTypeOf[A]
val ListA = weakTypeOf[List[A]]
c.prefix.tree match {
case q"new NestedStrMap[..$_]($list)(..$selectors)" =>
val func = selectors.foldRight(q"_root_.scala.Predef.identity[$ListA]")((sel, acc) =>
q"(_: $ListA).groupBy($sel).view.mapValues($acc).toMap"
)
q"$func.apply($list)"
}
}
}
// in a different subproject
val someMap = new NestedStrMap(testList)(_.name, _.testType).asMap
someMap: Map[String, Map[String, List[TestResult]]]
//Map(A -> Map(math -> List(TestResult(A,math,75))), B -> Map(bio -> List(TestResult(B,bio,90)), math -> List(TestResult(B,math,80))), C -> Map(history -> List(TestResult(C,history,50))))
通过集体智慧和协作来改善编程学习和解决问题的方式。致力于成为全球开发者共同参与的知识库,让每个人都能够通过互相帮助和分享经验来进步。
评论