第一章 基础
1.在Scala REPL中键入3.,然后按Tab键。有哪些方法可以被应用?
1 | != < >>> doubleValue isNaN |
2.在Scala REPL中,计算3的平方根然后再对该值求平方,结果相差多少?
1 | scala> math.sqrt(3) |
3.res变量是var还是val?
1 | 是val。只需要对res赋值看能否成功即可。 |
4.数字乘字符串,这个操作做什么?在Scaladoc中如何找到这个操作?
1 | scala> "crazy" * 3 |
a. 从结果可见字符串的 运算相当于复制运算。 3 = 复制三次
b. 从代码可以推断, “*“是 ”crazy”这个字符串所具有的方法,但是Java中的String没这个方法。因此,该方法在StringOps中,在Scaladoc中搜索”StringOps“,然后搜索”*”即可找到
5.10 max 2 的意义是什么? max 方法定义在哪个类中?
a. 根据scala中一切事物皆对象的原则,10 max 2 相当于 (10).max(2)
b.由于10是Int类型,所以max方法定义在Scala.Int类中。(如果Int类中没有,则可能是在RichInt类中)
6.用BigInt计算2的1024次方
1 | object exercise6{ |
7. 为了在使用probablePrime(100,Random)获取随机数时不在probablePrime和Random之前使用任何限定符,你需要引入什么?
Random在scala.util中,而probablePrime是BigInt中的方法。所以,
import scala.math.BigInt.probablePrime
import scala.util.Random
8. 创建随机文件的方式之一是生成一个随机的BigInt,然后将它转换成三十六进制,输出类似”qsnvbevtomcj38o06kul”这样字样的字符串。查阅Scaladoc,找到在Scala中实现该逻辑的方法。
1 | scala> scala.math.BigInt(scala.util.Random.nextInt).toString(36) |
BigInt中toString方法定义:
1 | def toString(radix: Int): String |
9. 在Scala中如何获取字符串的首字符和尾字符?
1 | // 首字符 |
10. take, drop, takeRight, dropRight 这些字符串函数是做什么用的?和substring相比,他们的优缺点有哪些?
1 | def take(n: Int): String // Selects first n elements. |
优点:简洁
缺点:单方向。如果需要取中间的字串,则需要同时调用多个函数;或者用substring
1 | scala> s |
第二章 控制结构和函数
1.signum函数:正数返回1,负数返回-1,0返回0。
1 | object exercise1{ |
2. 一个空的表达式{}的值是什么?类型是什么?
1 | scala> val t = {} |
a.空表达式的值是 no value
b.对应的类型是 Unit (或者说Nothing)
3. 指出在Scala中何种情况下赋值语句 x = y = 1是合法的。
由于y=1,所以y可以定义为var类型。(y=1)本身是Unit,所以可以定义x为var Unit类型
1 | scala> var x:Unit = {} //()和{}都是Unit类型 |
4. 针对下列Java循环编写一个Scala版本:for(int i = 10; i >= 0; i–) System.out.println(i);
1 | for(i <- 0 to 10 reverse) println(i) |
5. 编写一个过程countdown(n : Int), 打印从n到0的数字
过程 - 没有返回值的函数,或者返回值为Unit的函数(没有等于号)
函数 - (有等于号)
方法 - 对象的行为
1 | def countdown(n : Int){ |
更scala的写法
1 | (0 to n).reverse foreach println |
6. 编写一个for循环,计算字符串中所有字母的Unicode代码的乘积。举例来说,“Hello”中所有字符的乘积为9415087488L
1 | def unicodeMuti(str : String) = { |
7. 同样是解决前一个练习的问题,但这次不使用循环。(提示:在Scaladoc中查看StringOps)
1 | def unicodeMuti2(str : String) = { |
9. 把前一个练习中的函数改成递归函数。
1 | def product(s:String):Long = { |
10.编写函数计算xn,其中n是整数,使用如下递归定义:
xn=y2,如果n是正偶数的话,这里的y=xn/2。
xn=x*xn-1,如果n是正奇数的话。
- x0=1
- xn=1/x-n,如果n是负数的话。
1 | //模式匹配 |
1 | def mypow(x:Int,n:Int):Double={ |
第三章 数组相关操作
1. 编写一段代码,将a设置为一个n个随机整数的数组,要求随机数结余0(包含)和n(不包含)之间。
1 | def makeArr(n:Int) = { |
2. 编写一个循环,将整数数组中相邻的元素互换。例如,Array(1,2,3,4,5)经过互换后得到Array(2,1,4,3,5)
1 | def main(args:Array[String]){ |
3. 重复前一个练习,不过这一次生成一个新的值交换过的数组。用for/yield。(note: 习题3是返回一个新数组;习题2是直接修改原数组)
1 | def main(args:Array[String]){ |
4. 给定一个整数数组,产出一个新的数组,包含原数组中的所有正值,以原有顺序排列;之后的元素是所有零或负值,按原有顺序排列
1 | import scala.collection.mutable.ArrayBuffer |
5. 如何计算Array[Double]的平均值
1 | def avg(arr:Array[Double]) = { |
6. 如何重新组织Array[Int]的元素将它们以反序排列?对于ArrayBuffer[Int]你又会怎么做呢?
1 | // Array的反转 |
1 | // ArrayBuffer的反转,直接调用reverse函数 |
7. 编写一段代码,产出数组中的所有值,去掉重复项。
1 | def main(args:Array[String]){ |
8. 重新编写3.4节结尾的示例:移除整数数组缓存中除第一个负数外的所有负数。思路:收集负值元素的下标,反序,去掉最后一个下标,然后对每一个下标调用a.remove(i)。比较这样的效率和3.4节另外两种方法的效率。
1 | def main(args:Array[String]){ |
10. 创建一个由java.util.TimeZone.getAvailableIDs返回的时区集合,判断条件是它们在美洲。去掉”America/“前缀并排序
1 | def main(args:Array[String]){ |
更多方法查看API文档:Array ArrayBuffer
第四章 映射和元组
1. 设置一个映射,其中包含你想要的一些装备,以及它们的价格。然后构建另外一个映射,采用同一组键,但在价格上打九折。
1 | def main(args:Array[String]){ |
2.编写一段程序,从文件中读取单词。用一个可变映射来清点每个单词出现的频率。读取这些单词的操作可以使用java.util.Scanner:
1 | val in = new java.util.Scanner(new java.io.File("myfile.txt")) |
最后,打印出所有单词和它们出现的次数。
1 | def readByJava(){ |
1 | //更scala的写法 |
3. 重复前一个练习,这次用不可变的映射
1 | def main(args:Array[String]) { |
4. 重复前一个练习,这次使用已排序的映射,以便单词可以按顺序打印出来
1 | def exercise4(){ |
5. 重复前一个练习,这次使用java.util.TreeMap并使之适用于Scala API
1 | import scala.collection.JavaConversions.mapAsScalaMap //必须引入 |
6. 定义一个链式哈希映射,将”Monday”映射到 java.util.Calendar.MONDAY,依次类推加入其他日期。展示元素是以插入的顺序被访问的
1 | import scala.collection.mutable.LinkedHashMap // 链式哈希映射 |
7. 打印出所有Java系统属性的表格
1 | import scala.collection.JavaConversions.propertiesAsScalaMap//必须引入 |
1 | env.emacs | |
8. 编写一个函数minmax(values:Array[Int]),返回数组中最小值和最大值的对偶
1 | def main(args:Array[String]) { |
9. 编写一个函数Iteqgt(values:Array[Int],v:Int),返回数组中小于v,等于v和大于v的数量,要求三个值一起返回
1 | def main(args:Array[String]) { |
10. 当你将两个字符串拉链在一起,比如”Hello”.zip(“World”),会是什么结果?想出一个讲得通的用例
1 | def exercise10() { |
结果:
1 | (H,w) |
或者:
1 | "hello".zip("world") |
更多方法查看API文档:Map
第五章 类
1. 改进5.1节的Counter类,让它不要在Int.MaxValue时变成负数
1 | class counter{ |
2. 编写一个BankAccount类,加入deposit和withdraw方法,和一个只读的balance属性
1 | class BankAccount{ |
3. 编写一个Time类,加入只读属性hours和minutes,和一个检查某一时刻是否早于另一时刻的方法 before(other:Time):Boolean。Time对象应该以new Time(hrs,min)方式构建。其中hrs以军用时间格式呈现(介于0和23之间)
1 | class Time(val hours:Int,val minutes:Int) { |
4. 重新实现前一个类中的Time类,将内部呈现改成午夜起的分钟数(介于0到24*60-1之间)。不要改变公有接口。也就是说,客户端代码不应因你的修改而受影响
1 | class Time(val hours:Int,val minutes:Int) { |
5. 创建一个Student类,加入可读写的JavaBeans属性name(类型为String)和id(类型为Long)。有哪些方法被生产?(用javap查看。)你可以在Scala中调用JavaBeans的getter和setter方法吗?应该这样做吗?
1 | import scala.beans.BeanProperty |
1 | Compiled from "Student.scala" |
6. 在5.2节的Person类中提供一个主构造器,将负年龄转换为0
1 | class Person(var age:Int) { |
7. 编写一个Person类,其主构造器接受一个字符串,该字符串包含名字,空格和姓,如new Person(“Fred Smith”)。提供只读属性firstName和lastName。主构造器参数应该是var,val还是普通参数?为什么?
1 | class Person2(val name:String) { |
应该设为val。如果为var,则对应的此字符串有get和set方法,而Person中的firstName和lastName为只读的,
所以不能重复赋值。如果为var则会重复赋值而报错。
8. 创建一个Car类,以只读属性对应制造商,型号名称,型号年份以及一个可读写的属性用于车牌。提供四组构造器。每个构造器fc都要求制造商和型号为必填。型号年份和车牌可选,如果未填,则型号年份为-1,车牌为空串。你会选择哪一个作为你的主构造器?为什么?
1 | class Car(val maker:String, val typeName:String, val year:Int = -1, var licencePlate:String = "") { |
直接用四个参数的主构造器会非常省代码!!!其他都需要多些几行~
10. 考虑如下的类
1 | class Employ(val name:String, var salary:Double){ |
重写该类,使用显示的字段定义,和一个缺省主构造器。你更倾向于使用哪种形式?为什么?
1 | class Employee(val name:String,var salary:Double) { |
第二种更Java/C++风格,第一种更Scala风格。从Scalable的角度来说的话,第一种更容易拓展,代码量更少。所以,值得转换思路。
第六章 对象(面向对象编程)
1. 编写一个Conversion对象,加入inchesToCentimeters、gallonsToLitters和milesToKilometers方法
1 | object Conversions{ |
2. 前一个练习不是很面向对象。提供一个通用的超类UnitConversion并定义扩展该超类的InchesToCentimeters、GallonsToLitters和MilesToKilometers对象
1 | abstract class UnitConversion{ |
3. 定义一个扩展自java.awt.Point的Origin对象。为什么说这实际上不是个好主意?(仔细看Point类的方法)
1 | import java.awt.Point |
4. 定义一个Point类和伴生对象,使得我们可以不用new而直接用Point(3,4)来构造Point实例
1 | class Pointer(x:Int,y:Int) { |
5. 编写一个scala应用程序,使用App特质,以反序打印命令行参数,用空格隔开。举例来说,
scala Reverse Hello World应该打印出World Hello
1 | object Reverse extends App { |
6. 编写一个扑克牌四种花色的枚举,让其toString方法分别返回梅花、方块、红桃和黑桃。
1 | object PokerFace extends Enumeration with App { |
7. 实现一个函数,检查某张牌的花色是否为红色。
1 | object Card extends Enumeration with App { |
8. 编写一个枚举,描述RGB立方体的8个角。ID使用颜色值(例如,红色是0xff0000)
1 | object RGB extends Enumeration with App { |
第八章 继承
1.扩展如下的BankAccount类,新类CheckingAccount对每次存款和取款都收取1美元的手续费。
1 | class BankAccount(initialBalance:Double) { |
1 | class CheckingAccount(initialBalance:Double) extends BankAccount(initialBalance){ |
2.扩展前一个练习的BankAccount类,新类SavingAccount每个月都有利息产生(earnMonthlyInterest方法被调用),并且有每月三次免手续费的存款或取款。在earnMonthlyInterest方法中重置交易计数
1 | class SavingAccount(initialBalance:Double) extends BankAccount(initialBalance){ |
4.定义一个抽象类Item,加入方法price和description。SimpleItem是一个在构造器中给出价格和描述的物件。利用val可以重写def这个事实。Bundle是一个可以包含其他物件的物件。其价格是打包中所有物件的价格之和。同时提供一个将物件添加到打包中的机制,以及一个合适的description方法。
1 | import scala.collection.mutable.ArrayBuffer |
7.提供一个Square类,其扩展自java.awt.Rectangle并且有三个构造器:一个以给定的端点和宽度构造的正方形,一个以(0,0)为端点和给定的宽度构造正方形,还有一个以(0,0)为端点、0为宽度构造正方形。
1 | import java.awt.{Point, Rectangle} |
第十章 特质
1.java.awt.Rectangle类有两个很有用的方法translate和grow,但可惜的是像java.awt.geom.Ellipse2D这样的类中没有。在Scala中,你可以解决掉这个问题。定义一个RectangleLike特质,加入具体的translate和grow方法。提供任何你需要用来实现的抽象方法,以便你可以像如下代码这样混入该特质:
1 | val egg = new Ellipse2D.Double(5, 10, 15, 20) with RectangleLike |
1 | import java.awt.geom.{Ellipse2D} |
2.通过把scala.math.Ordered[Point]混入java.awt.Point的方式,定义OrderedPoint类。按照词典顺序排序,也就是说,如果x<x‘或者x=x‘且y<y‘则(x,y)<(x‘,y‘)。
1 | import java.awt.Point |
4.提供一个CryptoLogger类,将日志消息以凯撒密码加密。缺省情况下密钥为3,不过使用者可以重写它。提供缺省密钥和-3作为密钥时的使用示例。
1 | class CryptoLogger { |
1 | 明文为:abcdefg |
5.JavaBeans规范里有一种提法叫做“属性变更监听器(property change listener)”,这是bean用来通知其属性变更的标准方式。PropertyChangeSupport类对于任何想要支持属性变更监听器的bean而言是个便捷的超类。但可惜已有其他超类的类(比如JComponent)必须重新实现相应的方法。将PropertyChangeSupport重新实现为一个特质,然后将它混入到java.awt.Point类。
1 | import java.awt.Point |
8.做一个你自己的关于特质的继承层级,要求体现出叠加在一起的特质、具体的和抽象的方法,以及具体的和抽象的字段。
1 | trait Fly { |
9.在java.io类库中,你可以通过BufferedInputStream修饰器来给输入流增加缓冲机制。用特质来重新实现缓冲。简单起见,重写read方法。
1 | import java.io.{FileInputStream, InputStream} |
10.使用本章的日志生成器特质,给前一个练习中的方案增加日志功能,要求体现出缓冲的效果。
1 | import java.io.{FileInputStream, InputStream} |
11.实现一个IterableInputStream类,扩展java.io.InputStream并混入Iterable[Byte]特质。
1 | import java.io.{FileInputStream, InputStream} |
第十三章 集合
1.编写一个函数,给定字符串,产生一个包含所有字符的下标的映射
1 | def exercise1(){ |
1 | def indexes2(s:String):collection.mutable.Map[Char,List[Int]]={ |
2.重复前一个练习,这次用字符到列表的不可变映射
1 | def exercise2(){ |
3.从整形链表中去除所有的0
1 | def exercise3(){ |
4.编写一个函数,接受一个字符串的集合,以及一个从字符串到整数值的映射。返回整型的集合,其值为能和集合中某个字符串相对应的映射的值。举例来说,给定Array(“Tom”,”Fred”,”Harry”)和Map(“Tom” -> 3,”Dick” -> 4,”Harry” -> 5),返回Array(3,5)。提示:用flatMap将get返回的Option值组合在一起。(从一个字符串的数组中去匹配一个[String,Int]的Map,将值也就是Int再生成一个集返回)
1 | def exercise4(): Unit ={ |
5.实现一个函数,作用域mkString相同,使用reduceLeft。
1 | def exercise5(): Unit ={ |
6.给定整型列表lst,(lst :\ List[Int]())(_ :: _)得到什么?(List[Int]() /: lst)(_ :+ _)又得到什么?如何修改它们中的一个,以对原列表进行反向排列?
结果是一样的顺序序列,不一样的表达
1 | def exercise6(): Unit ={ |
7.在13.10节中,表达式(prices zip quantities) map { p => p._1 p._2}有些不够优雅。我们不能用(prices zip quantities) map { _ _},因为_ * _是一个带两个参数的函数,而我们需要的是一个带单个类型为元组的参数的函数。Function对象的tupled方法可以将带两个参数的函数改为以元组为参数的函数。将tupled应用于乘法函数,以便我们可以用它来映射由对偶组成的列表。(利用Function.tuple实现map接受一个元组)
1 | def exercise7(): Unit ={ |
8.编写一个函数,将Double数组转换成二维数组。传入列数作为参数。举例来说,Array(1,2,3,4,5,6)和三列,返回Array(Array(1,2,3),Array(4,5,6))。用grouped方法。
1 | def exercise8(): Unit ={ |
附:林子雨《Spark编程基础》Scala容器相关操作
1.遍历操作(foreach for)
List
1 | scala> val list = List(1,2,3) |
Map
1 | map foreach{kv => println(kv._1+":"+kv._2)} |
2.映射操作(map flatMap)
map:将某个函数应用到集合中的每个元素,映射得到一个新元素
1 | scala> val books = List("Hadoop","Hive","HDFS") |
flatMap:将某个函数应用到容器中的元素时,对每个元素都会返回一个容器(而不是一个元素),然后flatMap把生成的多个容器“拍扁”成为一个容器并返回。
1 | scala> books.flatMap(s => s.toList) //toList,toSet,toMap |
3.过滤操作(filter filterNot exists find)
filter:遍历一个容器,从中获取满足指定条件的元素,返回一个新的容器。
1 | scala> val map = Map(1 -> 1,2 -> 2) |
exists
1 | scala> books exists {_ startsWith "H"} |
find
1 | scala> books find {_ startsWith "H"} |
4.规约操作(reduce reduceLeft reduceRight fold foldLeft foldRight)
reduce:规约操作是对容器的元素进行两两运算,将其“规约”为一个值。
1 | scala> list |
reduceLeft:保证遍历顺序,第一个参数表示累计值
1 | scala> val list = List(1,2,3,4,5) |
reduceRight:保证遍历顺序,第二个参数表示累计值
1 | scala> list reduceRight (_ - _) |
fold:与reduce方法非常类似,是一个双参数列表的函数,第一个参数列表接受一个规约的初始值,第二个参数列表接受与reduce中一样的的二元函数参数。reduce是从容器的两个元素开始规约,而fold则是从提供的初始值开始规约。
1 | scala> list |
对空容器fold的结果为初始值,对空容器调用reduce会报错。
reduce操作总是返回于容器元素相同类型的结果,但是fold操作可以输出与容器元素类型完全不同类型的值,甚至是一个新容器。
1 | //fold实现类似map的功能 |
5.拆分操作(partition groupBy grouped sliding)
拆分操作是把一个容器里的元素按一定规则分割成多个子容器。
partition:接受一个布尔函数,用该函数对容器元素进行遍历,以二元组的形式返回满足条件和不满足条件的两个C[T]类型的集合。
1 | scala> val list = List(1,2,3,4,5) |
groupBy:接受一个返回U类型的函数,用该函数对容器元素进行遍历,将返回值相同的元素作为一个子容器,并与该相同的值构成一个键值对,最后返回的是一个类型为Map[U,C[T]]的映射。
1 | scala> val gby = list.groupBy(x=>x%3) //按被3整除的余数进行划分 |
grouped和sliding方法都接受一个整型参数n,两个方法都将容器拆分为多个与原容器类型相同的子容器,并返回由这些子容器构成的迭代器,即Iterator[C[T]]。
grouped按从左到右的方式将容器划分为多个大小为n的子容器(最后一个的大小可能小于n)。
sliding使用一个长度为n的滑动窗口,从左到右将容器截取为多个大小为n的子容器。
1 | scala> list.grouped(3) //拆分大小为3的子容器 |
1 | scala> list.sliding(3) //滑动拆分大小为3的子容器 |
6.函数式编程实例(单词统计)
1 | import java.io.{File, PrintWriter} |