scala写算法-List、Stream、以及剑指Offer里部分题目基于scala解法
Stream(immutable)
Stream是惰性列表。实现细节涉及到lazy懒惰求值、传名参数等等技术(具体细节详见维基百科-求值策略)。
Stream和List是scala中严格求值和非严格求值两个代表性不可变函数式数据结构。
考虑字符串拼接的表达式"foo"+"bar"的到"foobar",空串""就是这个操作的单位元(identity,数学中又称幺元),也就是说s+""或者""+s的值就是s。如果将三个字符串相加s+t+r,由于操作是可结合的(associative),所以说((s+t)+r)和(s+(t+r))的结果一样。
在函数式编程里,把这类代数称为monoid。结合律(associativity)和同一律(identity)法则被称为monoid法则。
可折叠数据结构
比如List、Stream、Tree、Vector等等这类都是可折叠数据结构。monoid与折叠操作有着紧密联系。
比如words=List("aa","bb","cc"),运用折叠方法如下:
words.foldRight("")((a,b)=>a+b) == ((""+"aa")+"bb")+"cc"
words.foldLeft("")((a,b)=>a+b) == "aa"+("bb"+("cc"+""))
首先实现Stream.fold方法,然后用fold去实现map filter flatMap等等高阶函数(大可不必仅使用fold去编写其它函数,就像尺规作图没有刻度一样。这样写仅仅为了好玩,没有银弹No Silver Bullet)。至于map、flatMap是什么?函子(Functor)是对map的泛化,Monad是对flatMap的泛化(相关概念参见Fp in Scala)。
fold源码如下(采用尾递归):
def fold[B](z: =>B)(f:(A,B)=>B):B={
@tailrec
def loop(stream: Stream[A])(result: =>B)(f:(A,B)=>B):B=stream match {
case Empty =>result
case Cons(h,t) =>loop(t())(f(h(),result))(f)
}
loop(self)(z)(f)
}
Stream实现如下:
trait Stream[+A] {self=>
import Stream.{cons,empty}
def fold[B](z: =>B)(f:(A,B)=>B):B={
@tailrec
def loop(stream: Stream[A])(result: =>B)(f:(A,B)=>B):B=stream match {
case Empty =>result
case Cons(h,t) =>loop(t())(f(h(),result))(f)
}
loop(self)(z)(f)
}
def toList=fold(Nil:List[A])((a,b)=> ::(a,b)).invert
def exits(p:A=>Boolean)=fold(false)((a,b)=>p(a)||b)
def forall(p:A=>Boolean)=fold(true)((a,b)=>p(a)&&b)
def invert=fold(Empty:Stream[A])((a,b)=>cons(a,b))
def map[B](f:A=>B)=fold(Empty:Stream[B])((a,b)=> cons(f(a),b)).invert
def ++[B>:A](stream:Stream[B])=invert.fold(stream)((a,b)=>{cons(a,b)})
def flatMap[B>:A](f:A=>Stream[B])=fold(Empty:Stream[B])((a,b)=>{f(a)++b}).invert
def filter(p:A=>Boolean)=fold(Empty:Stream[A])((a,b)=>p(a) match {
case true =>cons(a,b)
case false =>b
}).invert
def take(n:Int)=fold((n,empty[A]))((a,b)=> b._1 match {
case r if r >0=>(r-1,cons(a,b._2))
case 0=>(0,b._2)
})._2.invert
def drop(n:Int)=fold((n,empty[A]))((a,b)=>b._1 match {
case r if r>0 =>(r-1,b._2)
case 0 => (0,cons(a,b._2))
})._2
}
case class Cons[+A](h:()=>A,t:()=>Stream[A]) extends Stream[A]
case object Empty extends Stream[Nothing]
object Stream{
def cons[A](hd: =>A,tl: =>Stream[A]) :Stream[A]={
lazy val head=hd
lazy val tail=tl
Cons(()=>head,()=>tail)
}
def empty[A]:Stream[A]=Empty
def apply[A](xs:A*):Stream[A]=
if(xs.isEmpty)
empty
else
cons(xs.head,apply(xs.tail:_*))
}
List(immutable)
scala中的List实现方式类似于c语言中的链表数据结构,但是不同于链表,List是不可变的。就像字符串操作a=b+c,字符串a加上字符串b的到新的字符串c,同理,不可变的List,al=bl++cl,listbl与listcl相连接的到新的listal,而bl与cl本身不变。相比抽象数据结构ADT,我更愿意把List称作代数数据结构。其不变性与函数式数据结构中的数据共享有关(通过复制与共享实现了不变性)。
至于exists forall takeWhile take filter map foldRight等函数均采用尾递归方式,无需考虑爆栈问题。与官方库基于CanBuildFrom这类的虚类型实现方式不同,本程序完全采用递归实现。
trait List[+A]{self=>
def tail=self match {
case Nil => Nil
case _::t=>t
}
def init:List[A]=self match {
case Nil=> Nil
case h::Nil => Nil
case h::t=> ::(h,t.init)
}
def print={
def loop(list: List[A]):String=list match {
case Nil=>"Nil"
case h::t=>s"$h :: ${loop(t)}"
}
loop(self)
}
override def toString= print
def setHead[B>:A](newHead:B)= ::(newHead,self.tail)
def drop(n:Int):List[A]=n match {
case 0=>self
case _ if n>0 => self match {
case _::t=> t.drop(n-1)
}
case _ if n<0 =>Nil
}
def dropWhile(p:A=>Boolean):List[A]=self match {
case Nil=>Nil
case h::t=>p(h) match {
case true => t.dropWhile(p)
case false=> self
}
}
def ++[B>:A](list: List[B]):List[B]=self match {
case Nil=>list
case h::t=> ::(h,t++list)
}
def foldRight[B](z:B)(f:(A,B)=>B)={
@tailrec
def loop(list: List[A])(z:B)(f:(A,B)=>B):B=list match {
case Nil => z
case h::t => loop(t)(f(h,z))(f)
}
loop(self)(z)(f)
}
def invert=foldRight(Nil:List[A])(::(_,_))
def length=foldRight(0)((_,b)=>b+1)
def flatMap[B>:A](f:B=>List[B]):List[B]=self match {
case Nil=> Nil
case h::t=> f(h)++t.flatMap(f)
}
def map[B](f:A=>B)={
@tailrec
def loop(list: List[A])(result:List[B])(f:A=>B):List[B]=list match {
case Nil=>result
case h::t => loop(t)(::(f(h),result))(f)
}
loop(self)(Nil:List[B])(f).invert
}
def filter(p:A=>Boolean):List[A]={
@tailrec
def loop(list: List[A])(result:List[A])(p:A=>Boolean):List[A]=list match {
case Nil => result
case h::t => p(h) match {
case true =>loop(t)(::(h,result))(p)
case false=>loop(t)(result)(p)
}
}
loop(self)(Nil)(p).invert
}
def take(n:Int):List[A]={
@tailrec
def loop(list: List[A])(result:List[A])(n:Int):List[A]=list match {
case Nil=> result
case h::t => n match {
case 0 =>result
case _ if n>0 =>loop(t)(::(h,result))(n-1)
}
}
loop(self)(Nil)(n).invert
}
def takeWhile(p:A=>Boolean):List[A]={
@tailrec
def loop(list: List[A])(result:List[A])(p:A=>Boolean):List[A]=list match {
case Nil=>result
case h::t=>p(h) match {
case true => loop(t)(::(h,result))(p)
case false => result
}
}
loop(self)(Nil)(p).invert
}
def forall(p:A=>Boolean):Boolean={
@tailrec
def loop(list: List[A])(result:Boolean)(p:A=>Boolean):Boolean=list match {
case Nil => result
case h::t => p(h) match {
case true => loop(t)(result)(p)
case false=>false
}
}
loop(self)(true)(p)
}
def exists(p:A=>Boolean):Boolean={
@tailrec
def loop(list: List[A])(result:Boolean)(p:A=>Boolean):Boolean=list match {
case Nil => result
case h::t => p(h) match {
case true=>true
case false=>loop(t)(result)(p)
}
}
loop(self)(false)(p)
}
}
case class ::[+A](h:A,t:List[A]) extends List[A]
case object Nil extends List[Nothing]
object List{
def empty[A]:List[A]=Nil
def apply[A](xs:A*):List[A]={
if(xs.isEmpty) empty[A]
else ::(xs.head,apply(xs.tail:_*))
}
}
Tree
trait Tree[+A] {
}
case object EmptyNode extends Tree[Nothing]{
override def toString: String = "Nil"
}
case class Node[+A](value:A,left:Tree[A],right:Tree[A]) extends Tree[A]
object Tree{
def node[A](value:A,left:Tree[A]=EmptyNode,right:Tree[A]=EmptyNode)=Node(value,left,right)
def empty[A]:Tree[A]=EmptyNode
}
剑指Offers上的有关题目(基于scala解法)
数组中只出现一次的数字
| 0 | 0 | 0 ^ 0 = 0 |
|---|---|---|
| 0 | 1 | 0 ^ 1 = 1 |
| 1 | 1 | 1 ^ 1 = 0 |
一个整型数组里除了一个数字外,其它数字均出现两次,如数组Array(2, 3, 6, 3, 2, 5, 5),返回结果应该为6,程序一句话就ok
scala写算法-List、Stream、以及剑指Offer里部分题目基于scala解法的更多相关文章
- 面试经典算法题集锦——《剑指 offer》小结
从今年 3 月份开始准备找实习,到现在校招结束,申请的工作均为机器学习/数据挖掘算法相关职位,也拿到了几个 sp offer.经历这半年的洗礼,自己的综合能力和素质都得到了一个质的提升. 实话说对于未 ...
- 《剑指offer》全部题目-含Java实现
1.二维数组中的查找 在一个二维数组中,每一行都按照从左到右递增的顺序排序,每一列都按照从上到下递增的顺序排序.请完成一个函数,输入这样的一个二维数组和一个整数,判断数组中是否含有该整数. publi ...
- 剑指offer算法总结
剑指offer算法学习总结 节选剑指offer比较经典和巧妙的一些题目,以便复习使用.一部分题目给出了完整代码,一部分题目比较简单直接给出思路.但是不保证我说的思路都是正确的,个人对算法也不是特别在行 ...
- JS数据结构与算法 - 剑指offer二叉树算法题汇总
❗❗ 必看经验 在博主刷题期间,基本上是碰到一道二叉树就不会碰到一道就不会,有时候一个下午都在搞一道题,看别人解题思路就算能看懂,自己写就呵呵了.一气之下不刷了,改而先去把二叉树的基础算法给搞搞懂,然 ...
- 《剑指offer》面试题11: 数值的整数次方
面试题11: 数值的整数次方 剑指offer面试题11,题目如下 实现函数double power(double base,int exponent),求base的exponent次方, 不得使用库 ...
- 【剑指offer】旋转数组的最小值
採用二分查找的策略,重点要考虑一些边界情况:旋转了0元素.即输入的是一个升序排列的数组.仅仅包括一个数字的数组.有非常多反复数字的数组等. AC代码: #include<stdio.h> ...
- LeetCode—剑指 Offer学习计划
第 1 天 栈与队列(简单) 剑指 Offer 09. 用两个栈实现队列 class CQueue { public: CQueue() { } stack<int>s1,s2; void ...
- 【剑指offer】丑数
把只包含因子2.3和5的数称作丑数(Ugly Number).例如6.8都是丑数,但14不是,因为它包含因子7. 习惯上我们把1当做是第一个丑数.求按从小到大的顺序的第N个丑数. leetcode上也 ...
- 剑指offer之字符串是否为数值
1. 题目 这是<剑指offer>上的一道题,刚开始觉得这是一道挺简单的题目,后来发现自己太年轻了,考虑的因素太少了,思考了而是分钟还是无从下手,看了作者的思路深深被他折服了,题目如下: ...
随机推荐
- redis配置文件之复制
主从复制使用slaveof将Redis实例作为另一个Redis服务器的副本. 1) Redis复制是异步的,master可以配置成如果它连接的slave没有达到给定的数量,就停止接受写入.2) 如果断 ...
- margin、padding单位百分比
年前做了一个测试题 https://www.wenjuan.com/s/VjaEva/,里面有一道题目涉及到了margin和padding单位为百分比的情况.写出来记录一下以防止自己忘记. <! ...
- 爬虫day 04_01(爬百度页面)
import urllib.request import http.cookiejar from lxml import etree head = { 'Connection': 'Keep-Aliv ...
- 2017最新PHP经典面试题目汇总(上篇)
1.双引号和单引号的区别 双引号解释变量,单引号不解释变量 双引号里插入单引号,其中单引号里如果有变量的话,变量解释 双引号的变量名后面必须要有一个非数字.字母.下划线的特殊字符,或者用{}讲变量括起 ...
- macbookPro 搭建maven环境下载jar包
今天要用itext的jar包,去官网看发现好像只能用maven下载,而我之前又换了电话,没办法咯,重新搭一次maven环境吧,在此记录,已便分享或自己将来查找 首选确定自己环境上jdk装好了,如果没有 ...
- python中csv文件的读取问题
在python读取csv格式的文件时,使用csv.reader读取文件对象,出现了line contains NULL byte的错误,如下: reader = csv.reader(open(fil ...
- react-native从开始趟的坑
好多天没更了..... 之前用的华为手机老人机真机调试的,最近几天换了小米,又遇上了坑... 跟之前所有手机一样打开开发者模式,开发者模式是(关于手机--版本号---一直点啊点--退出---辅助功能里 ...
- Python 开发与接口测试学习笔记
这是我跟着虫师学习中积累下来的学习笔记,写得比较简单,适合想学习Python开发与接口测试的初学者学习. 一.开发投票系统 1.参考官网文档,创建投票系统. https://docs.djangopr ...
- 简陋的斗地主,js实现
最近闲了两天没事做,用js写了个斗地主,练习练习.代码和功能都很简陋,还有bug,咋只是聊聊自己的思路. 这里说说斗地主主要包含的功能:洗牌,发牌,玩家出牌.电脑出牌,出牌规则的验证,输赢啥的没有判断 ...
- Java SE 8 流库(二)
1.3. filter,map,flatMAP方法 流的转换会产生一个新流,它的元素派生出自另一个流中的元素: Stream<T> filter(Predicate<? super ...