Nested String Map Builder

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

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&#39;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&#39;s an example of how I&#39;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 =&gt; 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 =&gt; String)*): NestedStrMap[A, _ &lt;: 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&quot;new NestedStrMap[$A, _root_.shapeless.nat.${TypeName(s&quot;_$len&quot;)}]($list, ..$selectors)&quot;
  }
}

这将不适用于:

val sels = Seq[TestResult =&gt; 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 =&gt; 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&quot;new NestedStrMap[..$_]($list)(..$selectors)&quot; =&gt;
        val func = selectors.foldRight(q&quot;_root_.scala.Predef.identity[$ListA]&quot;)((sel, acc) =&gt;
          q&quot;(_: $ListA).groupBy($sel).view.mapValues($acc).toMap&quot;
        )
        q&quot;$func.apply($list)&quot;
    }
  }
}
英文:

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 =&gt; String)* must be known at compile time. You can introduce a type class using Shapeless Sized

// libraryDependencies += &quot;com.chuusai&quot; %% &quot;shapeless&quot; % &quot;2.3.10&quot;
import shapeless.nat.{_0, _2}
import shapeless.{Nat, Sized, Succ}
import scala.collection.Seq // Scala 2.13

// type class
trait AsMap[A, N &lt;: Nat] {
  type Out
  def apply(list: List[A], selectors: Sized[Seq[A =&gt; String], N]): Out
}
object AsMap {
  type Aux[A, N &lt;: Nat, Out0] = AsMap[A, N] {type Out = Out0}
  def instance[A, N &lt;: Nat, Out0](f: (List[A], Sized[Seq[A =&gt; String], N]) =&gt; Out0): Aux[A, N, Out0] = new AsMap[A, N] {
    type Out = Out0
    override def apply(list: List[A], selectors: Sized[Seq[A =&gt; String], N]): Out = f(list, selectors)
  }

  implicit def zero[A]: Aux[A, _0, List[A]] = instance((l, _) =&gt; l)
  implicit def succ[A, N &lt;: Nat](implicit
    asMap: AsMap[A, N]
  ): Aux[A, Succ[N], Map[String, asMap.Out]] =
    instance((l, sels) =&gt; l.groupBy(sels.head).view.mapValues(asMap(_, sels.tail)).toMap)
}

final class NestedStrMap[A, N &lt;: Nat](list: List[A], selectors: (A =&gt; String)*){
  def asMap(implicit asMap: AsMap[A, N]): asMap.Out =
    asMap(list, Sized.wrap[Seq[A =&gt; String], N](selectors))
}
object NestedStrMap {
  def apply[N &lt;: Nat] = new PartiallyApplied[N]
  class PartiallyApplied[N &lt;: Nat] {
    def apply[A](list: List[A])(selectors: (A =&gt; String)*) = new NestedStrMap[A, N](list, selectors: _*)
  }
}

case class TestResult(name: String, testType: String, score: Int)

val testList: List[TestResult] = List(
  TestResult(&quot;A&quot;, &quot;math&quot;, 75),
  TestResult(&quot;B&quot;, &quot;math&quot;, 80),
  TestResult(&quot;B&quot;, &quot;bio&quot;, 90),
  TestResult(&quot;C&quot;, &quot;history&quot;, 50)
)
val nestedMap = NestedStrMap[_2](testList)(_.name, _.testType)
val someMap = nestedMap.asMap
someMap: Map[String, Map[String, List[TestResult]]]
//Map(A -&gt; Map(math -&gt; List(TestResult(A,math,75))), B -&gt; Map(bio -&gt; List(TestResult(B,bio,90)), math -&gt; List(TestResult(B,math,80))), C -&gt; Map(history -&gt; 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 % &quot;scala-reflect&quot; % scalaVersion.value

object NestedStrMap {
  def apply[A](list: List[A])(selectors: (A =&gt; String)*): NestedStrMap[A, _ &lt;: 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&quot;new NestedStrMap[$A, _root_.shapeless.nat.${TypeName(s&quot;_$len&quot;)}]($list, ..$selectors)&quot;
  }
}
// in a different subproject

val nestedMap = NestedStrMap(testList)(_.name, _.testType)
val someMap = nestedMap.asMap
someMap: Map[String, Map[String, List[TestResult]]]
//Map(A -&gt; Map(math -&gt; List(TestResult(A,math,75))), B -&gt; Map(bio -&gt; List(TestResult(B,bio,90)), math -&gt; List(TestResult(B,math,80))), C -&gt; Map(history -&gt; List(TestResult(C,history,50))))

This will not work with

val sels = Seq[TestResult =&gt; 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 =&gt; 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&quot;new NestedStrMap[..$_]($list)(..$selectors)&quot; =&gt;
        val func = selectors.foldRight(q&quot;_root_.scala.Predef.identity[$ListA]&quot;)((sel, acc) =&gt;
          q&quot;(_: $ListA).groupBy($sel).view.mapValues($acc).toMap&quot;
        )
        q&quot;$func.apply($list)&quot;
    }
  }
}
// in a different subproject

val someMap = new NestedStrMap(testList)(_.name, _.testType).asMap
someMap: Map[String, Map[String, List[TestResult]]]
//Map(A -&gt; Map(math -&gt; List(TestResult(A,math,75))), B -&gt; Map(bio -&gt; List(TestResult(B,bio,90)), math -&gt; List(TestResult(B,math,80))), C -&gt; Map(history -&gt; List(TestResult(C,history,50))))

huangapple
  • 本文由 发表于 2023年4月11日 03:52:03
  • 转载请务必保留本文链接:https://go.coder-hub.com/75980244.html
匿名

发表评论

匿名网友

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

确定