【问题标题】:How does map() on 'zipped' Lists work?“压缩”列表上的 map() 如何工作?
【发布时间】:2016-03-02 20:01:09
【问题描述】:

我希望计算两个列表的标量积。假设我们有两个 List,l1 = List(1,2,3)l2 = List(4,5,6),结果应该是 List(4,10,18)

下面的代码有效:

def scalarProduct(l1 : List[Int], l2 : List[Int]):List[Int] = {
  val l3 = l1 zip(l2); l3 map(xy => xy._1*xy._2)
}

但是,以下编译失败,并显示Cannot resolve reference map with such signature

def scalarProduct(l1 : List[Int], l2 : List[Int]):List[Int] = {
  val l3 = l1 zip(l2); l3 map((x:Int,y:Int) => x*y)
}

这个 zip() 将返回一个 Int 对的列表,上面的地图也采用了一个采用 Int 对的函数。 有人能指出为什么第二种变体在这种情况下会失败吗?

【问题讨论】:

    标签: scala scala-collections


    【解决方案1】:

    您的第二个示例失败,因为您向 map 提供了一个带有 2 个参数的函数,而 map 采用了一个带有 1 个参数的函数。

    看一下,这是map 函数的(简化)签名:

    def map[B, That](f: A => B): That
    

    函数f 是您必须传递的函数才能进行转换。如您所见,它的类型为A => B,即接受单个参数。

    现在看一下(简化的)zip 函数签名:

    def zip [B](that : List[B]) : List[(A, B)]
    

    它实际上产生了一个列表,其成员是元组。 2 个元素的元组如下所示:(A, B)。当您在元组列表上调用 map 时,您必须提供函数 f,该函数将 2 个元素的元组作为参数,就像您在第一个示例中所做的那样。

    由于直接使用元组不方便,您可以使用模式匹配将元组成员的值提取到单独的变量中。

    这里有一个 REPL 会话来说明这一点。

    scala> List(1, 2, 3)
    res0: List[Int] = List(1, 2, 3)
    
    scala> List(2, 3, 4)
    res1: List[Int] = List(2, 3, 4)
    
    scala> res0 zip res1
    res2: List[(Int, Int)] = List((1,2), (2,3), (3,4))
    

    以下是使用模式匹配进行标准元组值提取的方法:

    scala> res2.map(t => t match {
         |   case (x, y) => x * y
         | })
    res3: List[Int] = List(2, 6, 12)
    

    需要注意的是,模式匹配需要一个偏函数作为参数。 IE。下面的表达式实际上是一个偏函数:

    {
        case (x, y) => x * y
    }
    

    部分函数在 Scala 中有自己的类型:trait PartialFunction[-A, +B] extends (A) => B,您可以阅读更多关于它的信息,例如,here

    偏函数是一个普通函数,因为它扩展了(A) => B,这就是为什么您可以将偏函数传递给map调用:

    scala> res2.map { case (x, y) => x * y }
    res4: List[Int] = List(2, 6, 12)
    

    您实际上在这里使用了特殊的 Scala 语法,它允许函数调用(在我们的例子中为map),而无需在其参数周围加上括号。您也可以用括号将其写成如下:

    scala> res2.map ({ case (x, y) => x * y })
    res5: List[Int] = List(2, 6, 12)
    

    最后两次调用完全没有区别。

    在对它进行模式匹配之前,您不必声明传递给map 的匿名函数的参数,这实际上是Scala 的语法糖。当您调用res2.map { case (x, y) => x * y } 时,真正发生的是使用偏函数进行模式匹配。

    希望这会有所帮助。

    【讨论】:

    • 这太棒了!仅这个答案就帮助我收紧了很多松散的结。谢谢..!
    • 如果您在地图上使用花括号,则不需要 t => t.match { 部分。就做res2.map { case (x, y) => x*y }
    【解决方案2】:

    你需要这样的东西:

    def scalarProduct(l1 : List[Int], l2 : List[Int]):List[Int] = {
      val l3 = l1 zip(l2); l3 map{ case (x:Int,y:Int) => x*y}
    }
    

    您可以查看this link 来帮助您解决此类问题。

    【讨论】:

    • 当您将map 与列表一起使用时,您将获得对列表中每个对象的引用。因此,您需要模式匹配来将该引用转换为您可能使用的对象。对于这些情况,不要使用map(x => x._1 * x._2),而应该使用map{x => x match { case (x:Int,y:Int) => x*y},因为这很难看,我们可以使用一些语法糖:map{ case (x:Int,y:Int) => x*y}
    • 这很奇怪,因为其中一个定义有map(f),另一个有map{f}。我不想承认,但我现在完全糊涂了
    • map() 是一个接受函数的方法。在答案中,我无法理解我们实际上是在将函数作为参数传递给 map(),还是我们重新定义了 map() 方法本身
    • 我们使用括号是因为我们在里面定义了一个表达式。当我们使用花括号时,我们定义了一个代码块。
    • 关于您的最后一条评论,我们正在将一个函数传递给 map。事实上,我们正在定义该函数。
    猜你喜欢
    • 2017-04-16
    • 2010-10-10
    • 2011-05-05
    • 2012-09-04
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2011-02-28
    • 1970-01-01
    相关资源
    最近更新 更多