Play scala 4 List的实现

函数式的集合不允许 update value in place, 只能用函数操作集合,每个操作都产生新的集合。这样会造成大量copy吗? 令人惊异的是,No !

  1. 自己实现List
package ch3.dslist

sealed trait List[+A]  //sealed的作用: 所有继承List的类型都必须定义在本文件

case object Nil extends List[Nothing]

case class Cons[+A](head: A, tail: List[A]) extends List[A]

object List {
  // 求和函数
  def sum(ints: List[Int]): Int =  ints match {
    case Nil => 0
    case Cons(x, xs) => x + sum(xs)   // 用构造器作模式匹配
  }
  
  // 求积函数,要求只要其中一个为0就立即返回0
  def product(nums: List[Double]) : Double = nums match {
    case Nil => 1.0
    case Cons(0.0, xs) => 0
    case Cons(x, xs) => x * product(xs)
  }
  
  // 创建List的方法
  def apply[A](as: A*):List[A] = {
    if (as.isEmpty) Nil
    else Cons(as.head, apply(as.tail: _*))
  }
}

测试

package ch3.dslist

object testList {
  println("Welcome to the Scala worksheet")       //> Welcome to the Scala worksheet
  val example = Cons(1, Cons(2, Cons(3, Nil)))    //> example  : ch3.dslist.Cons[Int] = Cons(1,Cons(2,Cons(3,Nil)))
  val example2 = List(1.0, 2.0, 3.0)              //> example2  : ch3.dslist.List[Double] = Cons(1.0,Cons(2.0,Cons(3.0,Nil)))
  List.sum(example)                               //> res0: Int = 6
  List.product(example2)                          //> res1: Double = 6.0
}

2. 添加一下常用操作

  
  def tail[A](l: List[A]): List[A] = l match {
    case Nil => throw new Error("Nil.tail")
    case Cons(x, xs) => xs
  }
  
  def drop[A] (l: List[A], n: Int): List[A] = {
    if (n == 1) tail(l)
    else drop(tail(l), n-1)
  }
  
  def dropWhile[A](l: List[A])(f: A => Boolean): List[A] = l match {
    case Nil => Nil
    case Cons(x, xs) => 
      if (f(x)) dropWhile(xs)(f)
      else Cons(x, dropWhile(xs)(f))
  }
  
  def setHead[A](l: List[A], h: A): List[A] = Cons(h, l)
  
  // O(N)
  def append[A](a1: List[A], a2: List[A]): List[A] = a1 match {
    case Nil => a2
    case Cons(x, xs) => Cons(x, append(xs, a2))
  }

这些操作并没有大量copy,而是复用了内存。因为整体是immutable的,所有部分也是immutable的。当call tail时,只需将tail的引用返回就好了。不用担心以后因原来的list有变化,而影响作为返回值的list。

这个世界本来就应该immutable和muttable共存。因为有些操作中immutable的集合上操作更高效,比如tail,append。有些操作中muttable的集合上操作更高效,比如update一个元素

3.实现init函数,返回除最后一个元素之外,其它元素按顺序组成的List

  def init[A](l: List[A]): List[A] = l match {
    case Nil => Nil
    case Cons(x, xs) => 
      xs match {
        case Nil => List()
        case Cons(y, ys) => Cons(x, init(xs))
      }
  }

4. 实现foldRight函数。并用之改进sum和product

  def foldRight[A, B](l: List[A], z: B)(f: (A, B) => B): B = l match {
    case Nil => z
    case Cons(x, xs) => f(x, foldRight(xs, z)(f))
  }
  
  def sum2(l: List[Int]) = foldRight(l, 0)(_ + _)
  
  def product2(l: List[Double]) = foldRight(l, 1.0)(_ * _)

5. 用foldRight实现length函数

def length[A](l: List[A]): Int = foldRight(l, 0)((a ,b) => b +1)

6. 我们的foldRight函数的实现不是尾递归的,当List很大时容易造成stackOverflow。现在用尾递归实现一个foldLeft函数,并重新实现sum 和 product。

  def foldLeft[A, B](l: List[A], z: B)(f: (A, B) => B): B = {
     def ff = 0
     def loop(a : B, m:List[A]): B = m match {
       case Nil => a
       case Cons(x, xs) => loop(f(x, a), xs)
     }
    loop(z, l)
  }
  def sum3(l: List[Int]) = foldLeft(l, 0)(_ + _)
  def product3(l: List[Double]) = foldLeft(l, 1.0) (_ * _)

将非尾递归改为尾递归的一般方法: 用循环结构重新实现算法,把这个循环结构写成新的递归

7.Write a function that transforms a list of integers by adding  1 to  each  element.  (Reminder:  this  should  be  a  pure  function  that  returns  a  new  !) List

给每个元素都加1

  def addOne(l: List[Int]): List[Int] = l match {
    case Nil => Nil
    case Cons(x, xs) => Cons(x +1, addOne(xs))
  }

8.  Write a function that turns each value in a  List[Double] into a  . String

将Double转化为String

  def all2String(l:List[Double]): List[String] = l match {
    case Nil => Nil
    case Cons(x, xs) => Cons(x.toString, all2String(xs))
  }

9. 实现map函数

  def map[A, B](l: List[A])(f: A => B): List[B] = l match {
    case Nil => Nil
    case Cons(x, xs) => Cons(f(x), map(xs)(f))
  }

10. Write a function  that removes elements from a list filter unless they satisfy a given predicate. Use it to remote all odd numbers from a . List[Int]

实现filter函数,并用它筛去List[Int]中的所有奇数

  def filter[A](l: List[A])(f: A => Boolean): List[A] = l match {
    case Nil => Nil
    case Cons(x, xs) =>
      if (f(x))  Cons(x, filter(xs)(f))
      else filter(xs)(f)
  }

11. 实现flatMap

  def flatMap[A, B](l: List[A])(f: A => List[B]): List[B] = l match {
    case Nil => Nil
    case Cons(x, xs) => 
      f(x) match {
        case Nil => flatMap(xs)(f)
        case Cons(y, ys) => append(f(x), flatMap(xs)(f))
      }
  }

12. 用flatMap实现filter

 def filter2[A](l: List[A])(f: A => Boolean): List[A] = flatMap(l)(  x => if (f(x)) List(x)  else Nil)

13. 实现向量加法

比如:add(List(1,2,3), List(2, 3, 4)) = List(3, 5, 7)

  def head[A](l: List[A]): A = l match {
    case Nil => throw new Error("Nil.head")
    case Cons(x, xs) => x
  }
  
  def add(a1: List[Int], a2: List[Int]): List[Int] = a1 match {
    case Nil => Nil
    case Cons(x, xs) => Cons(x + head(a2), add(xs, tail(a2)))
  }

14. 判断一个序列是不是另一序列的子序列:def hasSubsequence[A](l: List[A], sub: List[A]): Boolean

我们用最自然的算法判断:拿第二个list与第一个list从头开始比较,如果不相等,就从第二个元素开始比较。

这个算法显然要依赖另外两个函数:1. equals函数,用于比较两个list  2. take函数,用于获取前n个元素

 def take[A](l: List[A], n: Int): List[A] =  l match {
    case Nil => 
      if (n == 0) Nil
      else throw new Exception("No element")
    case Cons(x, xs) =>
      if (n >= 1) Cons(x, take(xs, n-1))
      else Nil
  }
  
  def equals[A](a: List[A], b: List[A]): Boolean = a match {
    case Nil => 
      b match {
        case Nil => true
        case _ => false
      }
    case Cons(x, xs) => 
      b match {
        case Nil => false
        case Cons(y, ys) => 
          if (x == y) equals(xs, ys)
          else false
      }
  }
  
  def hasSubsequence[A](a: List[A], b: List[A]): Boolean = {
    if (length(a) < length(b)) false
    else {
      val c = take(a, length(b))
      if (equals(c, b)) true
      else hasSubsequence(tail(a), b)
    }
  }

显然,上述代码有很多地方可以优化,比如length函数和take函数调了很多次,会很浪费时间。

时间: 2024-10-27 07:17:43

Play scala 4 List的实现的相关文章

Scala Study --- override

以前没使用过Scala, 其实我Java也是半截水平\无奈, 学Java的时候刚从C++中挣脱出来,发现Java无比优雅,但很快又对Java种种不信任程序员的设计感到受限. 直到, , 今天遇到了Scala\撒花 Scala的collection设计不能更赞!一段时间后打算专门写篇文章总结Scala,名字就叫"我为什么喜欢Scala!". 废话就不多说了,今天研究了一下Scala的override用法与特点. override --- one of the key words of S

Scala 中apply方法的用法

Scala 是构建在 JVM 上的静态类型的脚本语言,而脚本语言总是会有些约定来增强灵活性.关于协议在Python中是挺多的,看看Python的对象协议,有很多很多,如果对Python的对象协议了解(不了解的可以点击此处)的比较深刻的话,其实scala的apply方法也是很好理解的,比如说 Scala 为配合 DSL 在方法调用时有这么一条约定: 在明确了方法调用的接收者的情况下,若方法只有一个参数时,调用的时候就可以省略点及括号.如 "0 to 2",实际完整调用是 "0.

【Scala】Scala之Numbers

一.前言 前面已经学习了Scala中的String,接着学习Scala的Numbers. 二.Numbers 在Scala中,所有的数字类型,如Byte,Char,Double,Float,Int,Long,Short都是对象,这七种数字类型继承AnyVal特质,这七种数字类型与其在Java中有相同的范围,而Unit和Boolean则被认为是非数字值类型,Boolean有false和true两个值,你可以获取到各个数字类型的最值. 复杂的数字和日期 如果需要更强大的数类,可以使用spire,sc

scala控制结构

#判断 scala> def min(x:Int,y:Int):Int={ var a=x if(x>y) a=y return a } scala> min(1,2)res1: Int = 1 #循环    ##引申:函数式编程里面尽量使用常量,所以尽量避免 while do? 变量? while (A) B do B while A scala> var m=3scala> while (m!=0){ println(m) m-=1 } 321 #枚举 for (i<

scala学习手记19 - Option类型

看到Option类型就知道这本教材应该要说那个了. 使用过guava后,应该知道guava中的Optional类的作用是什么.算了找下原始文档好了: Optional<T> is a way of replacing a nullable T reference with a non-null value. An Optional may either contain a non-null T reference (in which case we say the reference is &

scala学习手记13 - 类继承

在scala里,类继承有两点限制: 重写方法需要使用override关键字: 只有主构造函数才能往父类构造函数中传参数. 在java1.5中引入了override注解,但不强制使用.不过在scala中要想重写方法必须使用override关键字.如果确实重写了父类的方法又不使用override关键字的话,则会在编译时报错,提示没有使用override修饰符. scala的副构造函数必须调用主构造函数或是另一个副构造函数.只有在主构造函数中才能向父类的构造函数中传递数据.可以看出来主构造函数如同父类

scala学习手记10 - 访问修饰符

scala的访问修饰符有如下几个特性: 如果不指定访问修饰符,scala默认为public: 较之Java,scala对protected的定义更加严格: scala可以对可见性进行细粒度的控制. scala的默认访问修饰符 如果没有修饰符,scala会默认把类.字段.方法的访问修饰符当做public.如果要将之调整为private或protected,只需在前面添加对应的修饰符关键字即可.就如下面的程序: class Microwave{ def start() = println("star

Scala应用函数

我们使用“_” 来代替单个的参数,实际上你也可以使用“_”来代替整个参数列表,比如说,你可以使用 print _ 来代替 println (_). someNumbers.foreach(println _) Scala编译器自动将上面代码解释成: someNumbers.foreach( x => println (x)) 因此这里的“_” 代表了Println的整个参数列表,而不仅仅替代单个参数. 当你采用这种方法使用“_”,你就创建了一个部分应用的函数(partially applied

Scala函数字面量

Scala中函数为头等公民,你不仅可以定义一个函数然后调用它,而且你可以写一个未命名的函数字面量,然后可以把它当成一个值传递到其它函数或是赋值给其它变量.下面的例子为一个简单的函数字面量(参考整数字面量,3 为一整数字面量). (x :Int ) => x +1 这是个函数字面量,它的功能为+1. 符好 => 表示这个函数将符号左边的东西(本例为一个整数),转换成符号右边的东西(加1). 函数字面量为一个对象(就像3是个对象),因此如果你愿意的话,可以把这个函数字面量保持在一个变量中.这个变量

Scala函数字面量简化写法

Scala提供了多种方法来简化函数字面量中多余的部分,比如前面例子中filter方法中使用的函数字面量,完整的写法如下: (x :Int ) => x +1 首先可以省略到参数的类型,Scala可以根据上下文推算出参数的类型,函数定义可以简化为: (x) => x +1 这个函数可以进一步去掉参数的括号,这里的括号不起什么作用: x => x +1 Scala 还可以进一步简化,Scala允许使用”占位符”下划线”_”来替代一个或多个参数,只要这个参数值函数定义中只出现一次,Scala编