【问题标题】:Scala: How to "map" an Array[Int] to a Map[String, Int] using the "map" method?Scala:如何使用“map”方法将 Array[Int] 映射到 Map[String, Int]?
【发布时间】:2018-01-19 08:16:56
【问题描述】:

我有以下Array[Int]val array = Array(1, 2, 3),为此我有以下Int和String之间的映射关系:

val a1 = array.map{
  case 1 => "A"
  case 2 => "B"
  case 3 => "C"
}

要创建一个Map 来包含上述映射关系,我知道我可以使用foldLeft 方法:

val a2 = array.foldLeft(Map[String, Int]()) { (m, e) =>
  m + (e match {
    case 1 => ("A", 1)
    case 2 => "B" -> 2
    case 3 => "C" -> 3
  })
}

哪个输出:

a2: scala.collection.immutable.Map[String,Int] = Map(A -> 1, B -> 2, C -> 3)

这就是我想要的结果。 但是我可以通过map 方法获得相同的结果吗?

以下代码不起作用:

val a3 = array.map[(String, Int), Map[String, Int]] {
  case 1 => ("A", 1)
  case 2 => ("B", 2)
  case 3 => ("C", 3)
}

map的签名是

def map[B, That](f: A => B)
(implicit bf: CanBuildFrom[Repr, B, That]): That

这是什么CanBuildFrom[Repr, B, That]?我试图阅读Tribulations of CanBuildFrom,但并不真正理解它。那篇文章提到 Scala 2.12+ 为 map 提供了两个实现。但是为什么我在使用 Scala 2.12.4 的时候没有找到呢?

我主要使用 Scala 2.11.12。

【问题讨论】:

    标签: scala functional-programming


    【解决方案1】:

    在您的表达式末尾调用toMap

    val a3 = array.map {
      case 1 => ("A", 1)
      case 2 => ("B", 2)
      case 3 => ("C", 3)
    }.toMap
    

    【讨论】:

    • 这不起作用。我仍然遇到同样的错误:Error:(36, 126) Cannot construct a collection of type Map[String,Int] with elements of type (String, Int) based on a collection of type Array[Int]. def get$$instance$$a3 = a3;/* ###worksheet### generated $$end$$ */ lazy val a3 = array.map[(String, Int), Map[String, Int]] { ^
    • 还有Error:(36, 126) not enough arguments for method map: (implicit bf: scala.collection.generic.CanBuildFrom[Array[Int],(String, Int),Map[String,Int]])Map[String,Int]. Unspecified value parameter bf. def get$$instance$$a3 = a3;/* ###worksheet### generated $$end$$ */ lazy val a3 = array.map[(String, Int), Map[String, Int]] { ^
    【解决方案2】:

    为了后面解释的简洁,我先在这里定义你的函数:

    // worth noting that this function is effectively partial
    // i.e. will throw a `MatchError` if n is not in (1, 2, 3)
    def toPairs(n: Int): (String, Int) =
      n match {
        case 1 => "a" -> 1
        case 2 => "b" -> 2
        case 3 => "c" -> 3
      }
    

    一种可能的方法(如在另一个答案中已突出显示)是使用toMap,它仅适用于对集合:

    val ns = Array(1, 2, 3)
    
    ns.toMap // doesn't compile
    
    ns.map(toPairs).toMap // does what you want
    

    值得注意的是,除非您使用惰性表示(如 IteratorStream),否则这将导致两次遍历集合并创建不必要的中间集合:第一次将toPairs 映射到集合上,然后将整个集合从对集合转换为Map(使用toMap)。

    你可以在the implementation of toMap看清楚。

    正如您已经在答案中链接的阅读中所建议的那样(特别是here),您可以通过两种方式避免这种双重通过:

    • 您可以利用scala.collection.breakOut,这是CanBuildFrom 的实现,您可以提供map(以及其他)来更改目标集合,前提是您为编译器显式提供类型提示:

    val resultMap: Map[String, Int] = ns.map(toPairs)(collection.breakOut)
    val resultSet: Set[(String, Int)] = ns.map(toPairs)(collection.breakOut)
    

    • 否则,您可以在集合上创建一个视图,将其放入您需要的惰性包装器中,以使操作不会导致双重传递

    ns.view.map(toPairs).toMap
    

    您可以在this Q&A 中阅读有关隐式构建器提供程序和视图的更多信息。

    【讨论】:

      【解决方案3】:

      基本上toMap(归功于Sergey Lagutin)是正确答案。

      你实际上可以让代码更紧凑一点:

      val a1 = array.map { i => ((i + 64).toChar, i) }.toMap
      

      如果你运行这段代码:

      val array = Array(1, 2, 3, 4, 5, 6, 7, 8, 10, 11, 12, 0)
      val a1 = array.map { i => ((i + 64).toChar, i) }.toMap
      println(a1)
      

      你会在控制台上看到这个:

      Map(E -> 5, J -> 10, F -> 6, A -> 1, @ -> 0, G -> 7, L -> 12, B -> 2, C -> 3, H -> 8, K -> 11, D -> 4)
      

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 2019-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2023-03-17
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多