scala 偏函数与 map/collect

0. collect 与 map 的区别

  • 由于collect方法接收的是一个偏函数类型,所以它并不能接收一个lambda表达式:

    scala> List(1, 3, 5, "seven").collect(i => i + 1)
    error: type mismatch;
    
    scala> List(1, 3, 5, "seven").collect{case i => i + 1}
    error: type mismatch;
    
    scala> List(1, 3, 5, "seven").collect{case i:Int => i + 1}
    List[Int] = List(2, 4, 6)
    

    我们在使用collect时,可以利用偏函数的原理,同时实现filter与map的特性。例如:

    List(1, 2, 3, 4, 5, 6) collect { case i if i % 2 == 0 => i * i }
    // 等价于
    List(1, 2, 3, 4, 5, 6).filter(i => i % 2 == 0).map(i => i * i)
    

1. 普通函数与偏函数

  • 普通函数的定义中,有指定的输入类型,所以可以接受任意该类型下的值。比如一个(Int) => String的函数可以接收任意Int值,并返回一个字符串。

    偏函数的定义中,也有指定的输入类型,但是偏函数不接受所有该类型下的值。比诶如(Int) => String的偏函数不能接收所有Int值为输入(通过 case 进行指定)。

    def one:PartialFunction[Int, String] = {
        case 1 => "one"
    }
    
    one(1) // "one"
    one(2) // 报错
    
    def two: PartialFunction[Int, String] = {
        case 2 => "two"
    }
    
    two(1) // 报错
    two(2) // "two"
    
    def wildcard: PartialFunction[Int, String] = {
        case _ => "something else"
    }
    
    wildcard(1) // "something else"
    wildcard(2) // "something else"
    
  • 由于偏函数只会接收部分参数,所以可以使用 “orElse” 方法进行组合:

    val partial = one orElse two orElse wildcard
    
    partial(1) // "one"
    partial(2) // "two"
    partial(3) // "something else"
    

2. 偏函数常见成员

  • orElse

  • isDefinedAt:判断偏函数是否对参数中的参数有效

    one.isDefinedAt(1) // true
    one.isDefinedAt(2) // false
    
  • case语句其实是偏函数定义的语法糖,当我们编写一个case语句时,其实等同于创建了一个具有apply与isDefineAt方法的偏函数对象。

    当我们使用PartialFunction类型定义一个偏函数的时候,scala会被自动转换:

    def int2Char: PartialFunction[Int, Char] = {
        case 1 => 'a'
        case 3 => 'c'
    }
    

    会被转化为:

    val int2Char = new PartialFunction[Int, Char] {
        def apply(i: Int) = {
            case 1 => 'a'
            case 3 => 'c'
        }
        def isDefinedAt(i: Int): Boolean = i match {
            case 1 => true
            case 3 => true
            case _ => false
        }
    }
    

3. map/collect 的区别

  • map 与 collect 等价的情况:

    val languageToCount = Map("Scala" -> 10, "Java" -> 20, "Ruby" -> 5)
    languageToCount map { case (_, count) => count + 1 }
    
    languageToCount collect { case (_, count) => count + 1 }
    
  • collect 运行正常,map 运行失败:

    List(1, 3, 5, "seven") map { case i: Int => i + 1 } //won't work
    //scala.MatchError: seven (of class java.lang.String)
    List(1, 3, 5, "seven") collect { case i: Int => i + 1 } //it works
    
  • map 与 collect 的 api:

    def map[B](f: (A) ⇒ B): List[B]
    
    def collect[B](pf: PartialFunction[A, B]): List[B]
    

    两个方法的定义如出一辙,区别就在于前者接收的是一个函数类型的参数,而后者接收的是一个偏函数(partial function)类型的参数:

    map: Builds a new collection by applying a function to all elements of this list.

    colect: Builds a new collection by applying a partial function to all elements of this list on which the function is defined.

  • 我们可以对比map方法和collect方法的实现:

    def map[B, That](f: A => B)(implicit bf: CanBuildFrom[Repr, B, That]): That = {
      def builder = {
          val b = bf(repr)
          b.sizeHint(this)
          b
      }
      val b = builder  
      for (x <- this) b += f(x)  
      b.result
    }
    
    def collect[B, That](pf: PartialFunction[A, B])(implicit bf: CanBuildFrom[Repr, B, That]): That = {
       val b = bf(repr)
       for (x <- this) if (pf.isDefinedAt(x)) b += pf(x)  
       b.result
    }
    
    

    在调用map方法时,一旦遍历到值"seven",并调用f(x),因为类型不符合模式匹配中的Int类型,导致抛出MatchError错误。而collect方法在调用pf(x)之前,调用了pf的isDefinedAt(x)作了一次过滤。

©️2020 CSDN 皮肤主题: 大白 设计师: CSDN官方博客 返回首页
实付0元
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、C币套餐、付费专栏及课程。

余额充值