Scala快速入门-常用集合操作

By timebusker on January 10, 2018

知识点

  • 所有的集合都扩展自Iterable特质
  • 集合有三大类,分别为序列、集和映射
  • 几乎所有集合类,Scala都同时提供了可变和不可变的版本
  • Scala列表要么是空的,要么拥有一头一尾,其中尾部本身又是一个表列
  • 集是无先后次序的集合
  • 用LinkedHashSet来保留插入顺序,或用SortedSet来按顺序进行迭代
  • +将元素添加到无先后次序的集合中;+:和:+向前或向后追加到序列;++将两个集合串接在一起;-和–移除元素
  • 映射、折叠和拉链操作是很有用的技巧,用来将函数和操作应用到集合中的元素

主要的集合特质

  • Iterable指的是那些能生成用来访问集合中所有元素的Iterator的集合
  • Seq是一个有先后次序的值的序列,如数组或列表。IndexedSeq允许我们通过整型的下标快速地访问任意元素
  • Set是一组没有先后次序的值。SortedSet中,元素以某种排过序的顺序被访问
  • Map是一组键值对,SortedMap按照键的排序访问其中的实体
  • 每个Scala集合特质或类都有一个带有apply方法的伴生对象,这个apply方法可以用来构建该集合中的实例

可变和不可变集合

  • Scala同时支持可变的和不可变的集合,不可变集合可以安全地共享其引用,甚至在一个多线程的应用程序当中也没有问题
  • Scala优先采用不可变集合
  • 可以基于不可变的老集合创建新集合,老集合保持不变

列表

  • 在Scala中,列表要么是Nil,要么是一个head元素加上一个tail,而tail又是一个列表
  • ::操作符从给定的头或尾创建一个新的列表,9 :: List(4, 2),::是右结合的,通过此操作符,列表将从末端开始构建
/**
  * @author Yezhiwei
  * @date 18/1/10
  */
object IterableLearn extends App{

  val digits = List(4, 2)

  println("head " + digits.head)
  println("tail " + digits.tail)

  // 右结合性
  println(9 :: digits)
  println(List(1, 3) :: 9 :: Nil)

  // 求列表的和
  println(digits.sum)
}

// 输出
head 4
tail List(2)
List(9, 4, 2)
List(List(1, 3), 9)
6

Set

  • Set是不重复元素的集合,已经存在的元素无法再次加入
  • Set并不保留元素插入的顺序,缺省情况下以哈希集实现的,其元素根据hashCode方法的值进行组织
  • scala.collection.mutable.LinkedHashSet 链式哈希集可以记住元素被插入的顺序
  • scala.collection.mutable.SortedSet 按照已排顺序来访问集中的元素(用红黑树实现的)
object IterableLearn extends App{

  // 1 已经存在,不会被重复添加
  println(Set(1, 2, 3) + 1)

  // 链式哈希集可以记住元素被插入的顺序
  val weekdays = scala.collection.mutable.LinkedHashSet("Mo", "Tu", "We", "Th", "Fr")
  println("weekdays " + weekdays)

  // 按照已排顺序来访问集中的元素
  println(scala.collection.mutable.SortedSet(2, 4, 1, 5))

  val digitsSet = Set(2, 5, 9)
  // 检查某个值是否在集合中
  println(digitsSet.contains(3))
  println(digitsSet.contains(5))

  // 某个集合中的所有元素是否包含在另一个集合中
  println(Set(2, 4).subsetOf(digitsSet) )
  println(Set(2, 5).subsetOf(digitsSet) )

  val set1 = Set(1, 2)
  val set2 = Set(2, 4)

  // 并 | 或 ++ 或 union
  println("====并 | 或 ++ 或 union====")
  println(set1 | set2)
  println(set1 ++ set2)
  println(set1 union set2)
  // 差 &~ 或 -- 或 diff
  println("====差 &~ 或 -- 或 diff====")
  println(set1 &~ set2)
  println(set1 -- set2)
  println(set1 diff set2)
  // 交 & 或 intersect
  println("====交 & 或 intersect====")
  println(set1 & set2)
  println(set1 intersect set2)
}

// 输出结果
Set(1, 2, 3)
weekdays Set(Mo, Tu, We, Th, Fr)
TreeSet(1, 2, 4, 5)
false
true
false
true
====并 | 或 ++ 或 union====
Set(1, 2, 4)
Set(1, 2, 4)
Set(1, 2, 4)
====差 &~ 或 -- 或 diff====
Set(1)
Set(1)
Set(1)
====交 & 或 intersect====
Set(2)
Set(2)

将函数映射到集合

  • map方法可以将某个函数应用到集合中的每个元素并产出其结果的集合
val names = List("Perter", "Paul", "Mary")
println("names to upperCase" + names.map(_.toUpperCase))

// 输出
names to upperCaseList(PERTER, PAUL, MARY)  
  • 如果函数产出一个集合而不是单个值,将所有的值串接在一起,则用flatMap
def ulcase(s: String) = Vector(s.toUpperCase, s.toLowerCase)
// 函数返回的是一个值
println(names.map(ulcase(_)))

// 函数返回的是一个集合
println(names.flatMap(ulcase(_)))

// 输出
List(Vector(PERTER, perter), Vector(PAUL, paul), Vector(MARY, mary))
List(PERTER, perter, PAUL, paul, MARY, mary)

化简、折叠和扫描

  • s.reduceLeft(op)op相继应用到所有元素;s.reduceRight(op) 方法同样,只不过它是从集合的尾部开始
  • coll.foldLeft(init)(op) init 初始值, 将op应用到初始值及所有的元素
  • 折叠有时可以作为循环的替代,如下面计算字母出现的次数
  • scanLeftscanRight方法将产出所有中间结果和最后的结果
object IterableLearn extends App{
  println("--" * 10)

  // 相当于(((1 + 2) + 3) + 4) + 5
  println("从1加到5的和")
  println(List(1, 2, 3, 4, 5).reduceLeft(_ + _))


  println("初始值为6,从左到右将1加到5的和")
  println(List(1, 2, 3, 4, 5).foldLeft(6)(_ + _))
  println((6 /: List(1, 2, 3, 4, 5))(_ + _))

  println("统计字符串中每个字符出现的次数")
  val m = (Map[Char, Int]() /: "Mississipyyz") (
    (m, c) => m + (c -> (m.getOrElse(c, 0) + 1))
  )
  for((k, v) <- m) {
    println(k, v)
  }

  println("产出所有中间结果和最后的和")
  println((1 to 10).scanLeft(0)(_ + _))

}

// 输出
--------------------
从1加到5的和
15
初始值为6,从左到右将1加到5的和
21
21
统计字符串中每个字符出现的次数
(s,4)
(y,2)
(M,1)
(i,3)
(p,1)
(z,1)
产出所有中间结果和最后的和
Vector(0, 1, 3, 6, 10, 15, 21, 28, 36, 45, 55)

拉链操作

  • zip这个方法之所以叫”拉链操作“,是因为它像拉链的齿状结构一样将两个集合结合在一起
  • 如将两个集合,把相互对应的元素结合在一起,如产品价格列表及相应的数量,zip方法将它们组成一个个对偶的列表
val prices = List(50.0, 20.0, 9.8)
val quantities = List(10, 20, 50)

println(prices zip quantities)
  
// 输出
List((50.0,10), (20.0,20), (9.8,50))
  • 如果一个集合比另一个短,结果中的对偶数量和较短的那个集合的元素数量相同
  • zipAll方法可指定短列表的缺省值,自身列表比较短的话使用thisElem进行填充,对方列表较短的话使用thatElem进行填充
// 将对应位置的元素组成一个pair,若列表长度不一致,自身列表比较短的话使用thisElem进行填充,对方列表较短的话使用thatElem进行填充
println(prices.zipAll(List(2, 3), 0, 3))
println(prices.zipAll(List(2, 3, 4, 6), 0, 3))

// 输出
List((50.0,2), (20.0,3), (9.8,3))
List((50.0,2), (20.0,3), (9.8,4), (0,6))