Scala集合,
Scala集合,
这个章节的内容包含
- 基本数据结构
- List
- Set
- Tuple
- Maps
- 函数组合器
- map
- foreach
- filter
- zip
- partition
- find
- drop and dropWhile
- foldRight and foldLeft
- flatten
- flatMap
- 广义的函数组合器
- 如何处理好Map?
基本数据结构
Scala提供了一些很方便的集合类。
参考 《Effective Scala》中关于怎么使用集合类的内容。
List
scala> val numbers = List(1, 2, 3, 4) numbers: List[Int] = List(1, 2, 3, 4)
Set
集合中没有重复元素
scala> Set(1, 1, 2) res0: scala.collection.immutable.Set[Int] = Set(1, 2)
元组(Tuple)
元组可以直接把一些具有简单逻辑关系的一组数据组合在一起,并且不需要额外的类。
scala> val hostPort = ("localhost", 80) hostPort: (String, Int) = (localhost, 80)
和case class不同,元组的元素不能通过名称进行访问,不过它们可以通过基于它们位置的名称进行访问,这个位置是从1开始而非从0开始。
scala> hostPort._1 res0: String = localhost scala> hostPort._2 res1: Int = 80
元组可以很好地和模式匹配配合使用。
hostPort match { case ("localhost", port) => ... case (host, port) => ... }
创建一个包含2个值的元组有一个很简单的方式:->
scala> 1 -> 2 res0: (Int, Int) = (1,2)
参考 《Effective Scala》中关于解除绑定(拆封一个元组)的观点。
Map
Map里可以存放基本的数据类型。
Map(1 -> 2) Map("foo" -> "bar")
这个看起来是一个特殊的语法,不过回想一下前面我们讨论元组的时候,->
符号是可以用来创建元组的。
Map()可以使用我们在第一节里讲到的可变参数的语法:Map( 1 -> "one", 2 -> "two")
,它会被扩展为Map((1,"one"),(2,"two"))
,其中第一个元素参数是key,第二个元素是value。
Map里也可以包含Map,甚至也可以把函数当作值存在Map里。
Map(1 -> Map("foo" -> "bar"))
Map("timesTwo" -> { timesTwo(_) })
Option
Option
是一个包含或者不包含某些事物的容器。
Option的基本接口类似于:
trait Option[T] { def isDefined: Boolean def get: T def getOrElse(t: T): T }
Option本身是泛型的,它有两个子类:Some[T]
和None
我们来看一个Option的示例: Map.get
使用Option
来作为它的返回类型。Option的作用是告诉你这个方法可能不会返回你请求的值。
scala> val numbers = Map(1 -> "one", 2 -> "two") numbers: scala.collection.immutable.Map[Int,String] = Map((1,one), (2,two)) scala> numbers.get(2) res0: Option[java.lang.String] = Some(two) scala> numbers.get(3) res1: Option[java.lang.String] = None
现在,我们要的数据存在于这个Option
里。那么我们该怎么处理它呢?
一个比较直观的方法就是根据isDefined
方法的返回结果作出不同的处理。
//如果这个值存在的话,那么我们把它乘以2,否则返回0。 val result = if (res1.isDefined) { res1.get * 2 } else { 0 }
不过,我们更加建议你使用getOrElse
或者模式匹配来处理这个结构。
getOrElse让你可以很方便地定义一个默认值。
val result = res1.getOrElse(0) * 2
模式匹配可以很好地和Option
进行配合使用。
val result = res1 match { case Some(n) => n * 2 case None => 0 }
参考 《Effective Scala》中关于 Options的内容。
函数组合器
List(1,2,3) map squared
会在列表的每个元素上分别应用squared
函数,并且返回一个新的列表,可能是List(1,4,9)
。我们把类似于map
这样的操作称为组合器。(如果你需要一个更好的定义,你或许会喜欢Stackoverflow上的关于组合器的解释。
map
在列表中的每个元素上计算一个函数,并且返回一个包含相同数目元素的列表。
scala> numbers.map((i: Int) => i * 2) res0: List[Int] = List(2, 4, 6, 8)
或者传入一个部分计算的函数
scala> def timesTwo(i: Int): Int = i * 2 timesTwo: (i: Int)Int scala> numbers.map(timesTwo _) res0: List[Int] = List(2, 4, 6, 8)
foreach
foreach和map相似,只不过它没有返回值,foreach只要是为了对参数进行作用。
scala> numbers.foreach((i: Int) => i * 2)
没有返回值。
你可以尝试把返回值放在一个变量里,不过它的类型应该是Unit(或者是void)
scala> val doubled = numbers.foreach((i: Int) => i * 2) doubled: Unit = ()
filter
移除任何使得传入的函数返回false的元素。返回Boolean类型的函数一般都称为断言函数。
scala> numbers.filter((i: Int) => i % 2 == 0) res0: List[Int] = List(2, 4)
scala> def isEven(i: Int): Boolean = i % 2 == 0 isEven: (i: Int)Boolean scala> numbers.filter(isEven _) res2: List[Int] = List(2, 4)
zip
zip把两个列表的元素合成一个由元素对组成的列表里。
scala> List(1, 2, 3).zip(List("a", "b", "c")) res0: List[(Int, String)] = List((1,a), (2,b), (3,c))
partition
partition
根据断言函数的返回值对列表进行拆分。
scala> val numbers = List(1, 2, 3, 4, 5, 6, 7, 8, 9, 10) scala> numbers.partition(_ %2 == 0) res0: (List[Int], List[Int]) = (List(2, 4, 6, 8, 10),List(1, 3, 5, 7, 9))
find
find返回集合里第一个匹配断言函数的元素
scala> numbers.find((i: Int) => i > 5) res0: Option[Int] = Some(6)
drop & dropWhile
drop
丢弃前i个元素
scala> numbers.drop(5) res0: List[Int] = List(6, 7, 8, 9, 10)
dropWhile
移除前几个匹配断言函数的元素。例如,如果我们从numbers列表里dropWhile
奇数的话,1
会被移除(3
则不会,因为它被2
所“保护”)。
scala> numbers.dropWhile(_ % 2 != 0) res0: List[Int] = List(2, 3, 4, 5, 6, 7, 8, 9, 10)
foldLeft
scala> numbers.foldLeft(0)((m: Int, n: Int) => m + n) res0: Int = 55
0是起始值(注意numbers是一个List[Int]),m是累加值。
更加直观的来看:
scala> numbers.foldLeft(0) { (m: Int, n: Int) => println("m: " + m + " n: " + n); m + n } m: 0 n: 1 m: 1 n: 2 m: 3 n: 3 m: 6 n: 4 m: 10 n: 5 m: 15 n: 6 m: 21 n: 7 m: 28 n: 8 m: 36 n: 9 m: 45 n: 10 res0: Int = 55
foldRight
这个和foldLeft相似,只不过是方向相反。
scala> numbers.foldRight(0) { (m: Int, n: Int) => println("m: " + m + " n: " + n); m + n } m: 10 n: 0 m: 9 n: 10 m: 8 n: 19 m: 7 n: 27 m: 6 n: 34 m: 5 n: 40 m: 4 n: 45 m: 3 n: 49 m: 2 n: 52 m: 1 n: 54 res0: Int = 55
flatten
flatten可以把嵌套的结构展开。
scala> List(List(1, 2), List(3, 4)).flatten res0: List[Int] = List(1, 2, 3, 4)
flaoMap
flatMap是一个常用的combinator,它结合了map和flatten的功能。flatMap接收一个可以处理嵌套列表的函数,然后把返回结果连接起来。
scala> val nestedNumbers = List(List(1, 2), List(3, 4)) nestedNumbers: List[List[Int]] = List(List(1, 2), List(3, 4)) scala> nestedNumbers.flatMap(x => x.map(_ * 2)) res0: List[Int] = List(2, 4, 6, 8)
可以把它当作map和flatten两者的缩写:
scala> nestedNumbers.map((x: List[Int]) => x.map(_ * 2)).flatten res1: List[Int] = List(2, 4, 6, 8)
这个调用map和flatten的示例是这些函数的类“组合器”特点的展示。
See Also Effective Scala has opinions about flatMap.
参考 《Effective Scala》中关于flatMap的内容.
广义的函数组合器
现在,我们学习了一大堆处理集合的函数。
不过,我们更加感兴趣的是怎么写我们自己的函数组合器。
有趣的是,上面展示的每个函数组合器都是可以通过fold来实现的。我们来看一些示例。
def ourMap(numbers: List[Int], fn: Int => Int): List[Int] = { numbers.foldRight(List[Int]()) { (x: Int, xs: List[Int]) => fn(x) :: xs } } scala> ourMap(numbers, timesTwo(_)) res0: List[Int] = List(2, 4, 6, 8, 10, 12, 14, 16, 18, 20)
为什么要List[Int]?因为Scala还不能聪明到知道你需要在一个空的Int列表上来进行累加。
如何处理好Map?
我们上面所展示的所有函数组合器都能都Map进行处理。Map可以当作是由键值对组成的列表,这样你写的函数就可以对Map里的key和value进行处理。
scala> val extensions = Map("steve" -> 100, "bob" -> 101, "joe" -> 201) extensions: scala.collection.immutable.Map[String,Int] = Map((steve,100), (bob,101), (joe,201))
现在过滤出所有分机号码小于200的元素。
scala> extensions.filter((namePhone: (String, Int)) => namePhone._2 < 200) res0: scala.collection.immutable.Map[String,Int] = Map((steve,100), (bob,101))
因为你拿到的是一个元组,所以你不得不通过它们的位置来取得对应的key和value,太恶心了!
幸运的是,我们实际上可以用一个模式匹配来优雅地获取key和value。
scala> extensions.filter({case (name, extension) => extension < 200}) res0: scala.collection.immutable.Map[String,Int] = Map((steve,100), (bob,101))
为什么这样也可以呢?为什么可以直接传入一个局部的模式匹配呢?
这个内容就留在下周吧!
英文原文: Scala School,翻译:Wld5 – 朱伟杰
本文链接:http://www.wld5.com/3673.html
【如需转载,请在正文中标注并保留原文链接、译文链接和译者等信息,谢谢合作!】
用户点评