泛函编程(6)-数据结构-List基础

List是一种最普通的泛函数据结构,比较直观,有良好的示范基础。List就像一个管子,里面可以装载一长条任何类型的东西。如需要对管子里的东西进行处理,则必须在管子内按直线顺序一个一个的来,这符合泛函编程的风格。与其它的泛函数据结构设计思路一样,设计List时先考虑List的两种状态:空或不为空两种类型。这两种类型可以用case class 来表现:

1     trait List[+A] {}
2     case class Cons[+A](head: A, tail: List[A]) extends List[A]
3     case object Nil extends List[Nothing]

以上是一个可以装载A类型元素的List,是一个多态的类型(Polymorphic Type)。+A表示List是协变(Covariant)的,意思是如果apple是fruit的子类(subtype)那么List[apple]就是List[fruit]的子类。Nil继承了List[Nothing],Nothing是所有类型的子类。结合协变性质,Nil可以被视为List[Int],List[String]...

List的另一种实现方式:

1     trait List[+A] {
2         def node: Option[(A, List[A])]
3         def isEmpty = node.isEmpty
4     }
5     object List {
6         def empty[A] = new List[A] { def node = None}
7         def cons[A](head: A, tail: List[A]) = new List[A] { def node = Some((head, tail))}
8     }

以上代码中empty,cons两个方法可以实现List的两个状态。

我们还是采用第一种实现方式来进行下面有关List数据运算的示范。第二种方式留待Stream的具体实现示范说明。

先来个List自由构建器:可以用List(1,2,3)这种形式构建List:

1     object List {
2         def apply[A](as: A*): List[A] = {
3             if (as.isEmpty) Nil
4             else Cons(as.head,apply(as.tail:_*))
5         }
6     }

说明:使用了递归算法来处理可变数量的输入参数。apply的传入参数as是个数组Array[A],我们使用了Scala标准集合库Array的方法:as.head, as.tail。示范如下:

1 scala> Array(1,2,3).head
2 res11: Int = 1
3
4 scala> Array(1,2,3).tail
5 res12: Array[Int] = Array(2, 3)

增加了apply方法后示范一下List的构成:

1 val li = List(1,2,3)                              //> li  : ch3.list.List[Int] = Cons(1,Cons(2,Cons(3,Nil)))
2 val ls = List("one","two","three")                //> ls  : ch3.list.List[String] = Cons(one,Cons(two,Cons(three,Nil)))

与以下方式对比,写法简洁多了:

1 val lInt = Cons(1,Cons(2,Cons(3,Nil)))            //> lInt  : ch3.list.Cons[Int] = Cons(1,Cons(2,Cons(3,Nil)))

再来试一个运算:计算List[Int]里所有元素的和,还是用模式匹配和递归方式来写:

1     trait List[+A] {
2       def sum: Int = this match {
3           case Nil => 0
4           case Cons(h: Int,t: List[Int]) => h + t.sum
5       }
6     }

我们把sum的实现放到特质申明里就可以用以下简洁的表达方式了:

1 List(1,2,3) sum                                   //> res0: Int = 6

再试着玩多态函数sum:

1       def sum[B >: A](z: B)(f: (B,B) => B): B = this match {
2           case Nil => z
3           case Cons(h,t) => f(h, t.sum(z)(f))
4       }

现在可以分别试试List[Int]和List[String]:

1 List(1,2,3).sum(0){_ + _}                         //> res0: Int = 6
2 List("hello",",","World","!").sum(""){_ + _}      //> res1: String = hello,World!

以下是一些List常用的函数:

 1     trait List[+A] {
 2
 3       def head: A = this match {
 4           case Nil => sys.error("Empty List!")
 5           case Cons(h,t) => h
 6       }
 7       def tail: List[A] = this match {
 8           case Nil => sys.error("Empty List!")
 9           case Cons(h,t) => t
10       }
11       def take(n: Int): List[A] = n match {
12         case k if(k<0) => sys.error("index < 0 !")
13         case 0 => Nil
14         case _ => this match {
15               case Nil => Nil
16               case Cons(h,t) => Cons(h,t.take(n-1))
17           }
18       }
19       def takeWhile(f: A => Boolean): List[A] = this match {
20           case Nil => Nil
21           case Cons(h,t) => if(f(h)) Cons(h,t.takeWhile(f)) else Nil
22       }
23       def drop(n: Int): List[A] = n match {
24         case k if(k<0) => sys.error("index < 0 !")
25         case 0 => this
26         case _ => this match {
27               case Nil => Nil
28               case Cons(h,t) => t.drop(n-1)
29           }
30       }
31       def dropWhile(f: A => Boolean): List[A] = this match {
32           case Nil => Nil
33           case Cons(h,t) => if (f(h)) t.dropWhile(f) else this
34       }
35     }

看看以上的这些函数;是不是都比较相似?那是因为都是泛函编程风格的原因。主要以模式匹配和递归算法来实现。以下是使用示范:

1 List(1,2,3).head                                  //> res0: Int = 1
2 List(1,2,3).tail                                  //> res1: ch3.list.List[Int] = Cons(2,Cons(3,Nil))
3 List(1,2,3).take(2)                               //> res2: ch3.list.List[Int] = Cons(1,Cons(2,Nil))
4 List(1,2,3).takeWhile(x => x < 3)                 //> res3: ch3.list.List[Int] = Cons(1,Cons(2,Nil))
5 List(1,2,3) takeWhile {_ < 3}                     //> res4: ch3.list.List[Int] = Cons(1,Cons(2,Nil))
6 List(1,2,3).drop(2)                               //> res5: ch3.list.List[Int] = Cons(3,Nil)
7 List(1,2,3).dropWhile(x => x < 3)                 //> res6: ch3.list.List[Int] = Cons(3,Nil)
8 List(1,2,3) dropWhile {_ < 3}                     //> res7: ch3.list.List[Int] = Cons(3,Nil)

试试把一个List拼在另一个List后面:

1         def ++[B >: A](a: List[B]): List[B] = this match {
2             case Nil => a
3             case Cons(h,t) => Cons(h,t.++(a))
4         }
1 ist(1,2) ++ List(3,4)                            //> res8: ch3.list.List[Int] = Cons(1,Cons(2,Cons(3,Cons(4,Nil))))

只是想试试Scala的简洁表达方式。

噢,漏了两个:

1       def init: List[A] = this match {
2           case Cons(_,Nil) => Nil
3           case Cons(h,t) => Cons(h,t.init)
4       }
5       def length: Int = this match {
6         case Nil => 0
7         case Cons(h,t) => 1 + t.length
8       }
1 List(1,2,3).init                                  //> res9: ch3.list.List[Int] = Cons(1,Cons(2,Nil))
2 List(1,2,3).length                                //> res10: Int = 3

下面把几个泛函数据结构通用的函数实现一下:

 1       def map[B](f: A => B): List[B] = this match {
 2           case Nil => Nil
 3           case Cons(h,t) => Cons(f(h),( t map f))
 4       }
 5       def flatMap[B]( f: A => List[B]): List[B] = this match {
 6           case Nil => Nil
 7           case Cons(h,t) => f(h) ++ ( t flatMap f )
 8       }
 9       def filter(f: A => Boolean): List[A] = this match {
10            case Nil => Nil
11            case Cons(h,t) => if (f(h)) Cons(h,t.filter(f)) else t.filter(f)
12       }
1 List(1,2,3) map {_ + 10}                          //> res13: ch3.list.List[Int] = Cons(11,Cons(12,Cons(13,Nil)))
2 List(1,2,3) flatMap {x => List(x+10)}             //> res14: ch3.list.List[Int] = Cons(11,Cons(12,Cons(13,Nil)))
3 List(1,2,3) filter {_ != 2}                       //> res15: ch3.list.List[Int] = Cons(1,Cons(3,Nil))

这几个函数有多种实现方法,使Scala for-comprehension对支持的数据结构得以实现。有关这几个函数在泛函编程里的原理和意义在后面的有关Functor,Applicative,Monad课题里细说。

时间: 2024-11-02 16:13:09

泛函编程(6)-数据结构-List基础的相关文章

泛函编程(5)-数据结构(Functional Data Structures)

编程即是编制对数据进行运算的过程.特殊的运算必须用特定的数据结构来支持有效运算.如果没有数据结构的支持,我们就只能为每条数据申明一个内存地址了,然后使用这些地址来操作这些数据,也就是我们熟悉的申明变量再对变量进行读写这个过程了.试想想如果没有数据结构,那我们要申明多少个变量呢.所以说,数据结构是任何编程不可缺少的元素. 泛函编程使用泛函数据结构(Functional Data Structure)来支持泛函程序.泛函数据结构的特点是”不可变特性“(Immutability), 是泛函编程中函数组

泛函编程(8)-数据结构-Tree

上节介绍了泛函数据结构List及相关的泛函编程函数设计使用,还附带了少许多态类型(Polymorphic Type)及变形(Type Variance)的介绍.有关Polymorphism的详细介绍会放在typeclass讨论中.为了更多了解泛函数据结构(Functional Data Structure),想在这个章节把另一个我们熟悉的数据结构-Tree做些简单介绍. Tree的状态不是枝(Branch)就是叶(Leaf),这个很容易理解.那么就按照上节设计List那样设计Tree类型: 1

泛函编程(7)-数据结构-List-折叠算法

折叠算法是List的典型算法.通过折叠算法可以实现众多函数组合(function composition).所以折叠算法也是泛函编程里的基本组件(function combinator).了解折叠算法的原理对了解泛函组合有着至关紧要的帮助.折叠算法又可分右折叠和左折叠.我们先从右折叠(foldRight)开始: 从以上两图示可以得出对List(a,b,c)的右折叠算法:op(a,op(b,op(c,z))) 可以看出括号是从右开始的.计算方式如图二:op(a,sub), sub是重复子树,可以肯

泛函编程(14)-try to map them all

虽然明白泛函编程风格中最重要的就是对一个管子里的元素进行操作.这个管子就是这么一个东西:F[A],我们说F是一个针对元素A的高阶类型,其实F就是一个装载A类型元素的管子,A类型是相对低阶,或者说是基础的类型.泛函编程风格就是在F内部用对付A类的函数对里面的元素进行操作.但在之前现实编程中确总是没能真正体会这种编程模式畅顺的用法:到底应该在哪里用?怎么用?可能内心里还是没能摆脱OOP的思维方式吧.在前面Stream设计章节里,我们采用了封装形式的数据结构设计,把数据结构uncons放进了特质申明里

泛函编程(29)-泛函实用结构:Trampoline-不再怕StackOverflow

泛函编程方式其中一个特点就是普遍地使用递归算法,而且有些地方还无法避免使用递归算法.比如说flatMap就是一种推进式的递归算法,没了它就无法使用for-comprehension,那么泛函编程也就无法被称为Monadic Programming了.虽然递归算法能使代码更简洁易明,但同时又以占用堆栈(stack)方式运作.堆栈是软件程序有限资源,所以在使用递归算法对大型数据源进行运算时系统往往会出现StackOverflow错误.如果不想办法解决递归算法带来的StackOverflow问题,泛函

泛函编程(21)-泛函数据类型-Monoid

Monoid是数学范畴理论(category theory)中的一个特殊范畴(category).不过我并没有打算花时间从范畴理论的角度去介绍Monoid,而是希望从一个程序员的角度去分析Monoid以及它在泛函编程里的作用.从这个思路出发我们很自然得出Monoid就是一种数据类型,或者是一种在泛函编程过程中经常会遇到的数据类型:当我们针对List或者loop进行一个数值的积累操作时我们就会使用到Monoid.实际上Monoid就是List[A] => A的抽象模型.好了,我们就不要越描越黑了吧

泛函编程(9)-异常处理-Option

Option是一种新的数据类型.形象的来描述:Option就是一种特殊的List,都是把数据放在一个管子里:然后在管子内部对数据进行各种操作.所以Option的数据操作与List很相似.不同的是Option的管子内最多只能存放一个元素,在这个方面Option的数据操作就比List简单的多,因为使用者不必理会数据元素的位置.顺序.Option只有两种状态:包含一个任何类型的元素或者为空.或者这样讲:一个Option实例包含 0 或 1 个元素:None代表为空,Some(x)代表包含一个任意类型的

泛函编程(27)-泛函编程模式-Monad Transformer

经过了一段时间的学习,我们了解了一系列泛函数据类型.我们知道,在所有编程语言中,数据类型是支持软件编程的基础.同样,泛函数据类型Foldable,Monoid,Functor,Applicative,Traversable,Monad也是我们将来进入实际泛函编程的必需.在前面对这些数据类型的探讨中我们发现: 1.Monoid的主要用途是在进行折叠(Foldable)算法时对可折叠结构内元素进行函数施用(function application). 2.Functor可以对任何高阶数据类型F[_]

泛函编程(23)-泛函数据类型-Monad

简单来说:Monad就是泛函编程中最概括通用的数据模型(高阶数据类型).它不但涵盖了所有基础类型(primitive types)的泛函行为及操作,而且任何高阶类或者自定义类一旦具备Monad特性就可以与任何类型的Monad实例一样在泛函编程中共同提供一套通用的泛函编程方式.所以有人把泛函编程视作Monadic Programming也不为过之.那么,具体什么是Monad呢? 在前面我们讨论过Monoid,我们说过它是一个特殊的范畴(Category),所有数据类型的Monoid实例都共同拥有一