《Scala实用指南》读书笔记三:集合

#scala

1. 集合

Scala标准库包含了一组丰富的集合类,以及用于组合、遍历和提取元素的强大操作。在创建Scala应用程序时,会经常用到这些集合。如果想要在使用Scala时更加具有生产力,彻底地学习这些集合是很有必要的。

scala 30分钟极简入门 也简单介绍过集合。

例如:

  val colors = Set("Bule", "Green", "Red")        //> colors  : scala.collection.immutable.Set[String] = Set(Bule, Green, Red)
  colors.getClass                                 //> res0: Class[?0] = class scala.collection.immutable.Set$Set3
  colors getClass                                 //> res1: Class[?0] = class scala.collection.immutable.Set$Set3

根据所提供的参数,Scala发现我们需要的是一个Set[String]。同样地,如果是Set(1,2,3),那么我们将会得到一个Set[Int]。因为特殊的apply()方法(也被称为工厂方法),所以才得以创建一个对象而又不用使用new关键字。类似于X(…)这样的语句,其中X是一个类的名称或者一个实例的引用,将会被看作是X.apply(…)。

提到 scala,不会不介绍集合,而且方法大都见名知意,因此为了简洁,这里直接贴一下代码:

  val feeds1 = Set("blog.toolshed.com", "pragdave.me", "blog.agiledeveloper.com")
  //> feeds1  : scala.collection.immutable.Set[String] = Set(blog.toolshed.com, pragdave.me, blog.agiledeveloper.com)
  val feeds2 = Set("blog.toolshed.com", "martinfowler.com/bliki")
  //> feeds2  : scala.collection.immutable.Set[String] = Set(blog.toolshed.com, martinfowler.com/bliki)

  feeds1 filter(_ contains "blog")  //> res2: scala.collection.immutable.Set[String] = Set(blog.toolshed.com, blog.agiledeveloper.com)
  feeds1 ++ feeds2                  //> res3: scala.collection.immutable.Set[String] = Set(blog.toolshed.com, pragdave.me, blog.agiledeveloper.com, martinfowler.com/bliki)
  feeds1 & feeds2                   //> res4: scala.collection.immutable.Set[String] = Set(blog.toolshed.com)

  feeds1 map ("http://" + _)                //> res5: scala.collection.immutable.Set[String] = Set(http://blog.toolshed.com,  http://pragdave.me, http://blog.agiledeveloper.com)

  //关联映射
  val feeds = Map(
    "Andy Hunt" -> "blog.toolshed.com",
    "Dave Thomas" -> "pragdave.me",
    "NFJS" -> "nofluffjuststuff.com/blog")
  //> feeds  : scala.collection.immutable.Map[String,String] = Map(Andy Hunt -> blog.toolshed.com, Dave Thomas -> pragdave.me, NFJS -> nofluffjuststuff.com/blog)

  feeds filterKeys (_ startsWith "D")       //> res6: scala.collection.immutable.Map[String,String] = Map(Dave Thomas -> pragdave.me)

  val filterNameStartWithDAndPraprogInFeed = feeds filter { element =>
    val (key, value) = element
    (key startsWith "D") && (value contains "pragdave")
    //> filterNameStartWithDAndPraprogInFeed  : scala.collection.immutable.Map[String,String] = Map(Dave Thomas -> pragdave.me)
  }

  //Some[T]
  feeds.get("Andy Hunt")                    //> res7: Option[String] = Some(blog.toolshed.com)
  //None
  feeds.get("?")                            //> res8: Option[String] = None
  
  feeds("Andy Hunt")                        //> res9: String = blog.toolshed.com
  // feeds("?")
  val newFeeds1 = feeds.updated("Venkat Subramaniam", "blog.agiledeveloper.com")
  //> newFeeds1  : scala.collection.immutable.Map[String,String] = Map(Andy Hunt -> blog.toolshed.com, Dave Thomas -> pragdave.me, NFJS -> nofluffjuststuff.com/blog, Venkat Subramaniam -> blog.agiledeveloper.com)

  // 不可变列表
  val lists = List("blog.toolshed.com", "pragdave.me", "blog.agiledeveloper.com")
  //> lists  : List[String] = List(blog.toolshed.com, pragdave.me, blog.agiledeveloper.com)
  // 如果我们想要前插一个元素,即将一个元素放在当前List的前面,我们可以使用特殊的::()方法。
  // a :: list读作“将a前插到list”。虽然list跟在这个操作符之后,但它是list上的一个方法。
  "prepend" :: lists                        //> res10: List[String] = List(prepend, blog.toolshed.com, pragdave.me, blog.agiledeveloper.com)
  // 假设我们想要追加一个列表到另外一个列表,例如,将listA追加到另外一个列表list。那么我们可以使用:::()方法将list实际上前插到listA。
  // 因此,代码应该是list ::: listA,并读作“将list前插到listA”。
  // 因为List是不可变的,所以我们不会影响前面的任何一个列表。我们只是使用这两个列表中的元素创建了一个新列表。
  lists ::: List("another", "list")         //> res11: List[String] = List(blog.toolshed.com, pragdave.me, blog.agiledeveloper.com, another, list)
  lists.filter(_ contains "blog").mkString(" ,")
  //> res12: String = blog.toolshed.com ,blog.agiledeveloper.com
  lists.forall(_ contains "com")            //> res13: Boolean = false
  lists.exists(_ contains "dave")           //> res14: Boolean = true
  lists.exists(_ contains "?")              //> res15: Boolean = false
  
  lists.map(_.length)                       //> res16: List[Int] = List(17, 11, 23)
  
  // foldLeft 越来越简洁,/:()方法等价于foldLeft()方法,而\:()方法等价于foldRight()方法。
  lists.foldLeft(0){ (total, feed) => total + feed.length }
                                                  //> res17: Int = 51
  (0 /: lists){ (total, feed) => total + feed.length }
                                                  //> res18: Int = 51
  (0 /: lists){ _ + _.length }              //> res19: Int = 51

1.1. 方法名约定

如果要前插一个值到列表中,可以编写value :: list。即使它读起来好像是“将value前插到list中”,但是,该方法的目标实际上是list,而value作为参数,即list.::(value)。

如果方法名以冒号(:)结尾,那么调用的目标是该操作符后面的实例。Scala不允许使用字母作为操作符的名称,除非使用下划线对该操作符增加前缀。因此,一个名为jumpOver:()的方法是被拒绝的,但是jumpOver_:()则会被接受。

例如:

  class Cow {
    def ^(moon: Moon): Unit = println("Cow jumped over the moon")
  }
  
  class Moon {
    def ^:(cow: Cow): Unit = println("This cow jumped over the moon too")
  }
  
  val c = new Cow                           //> c  : Collections.Cow = Collections$Cow$1@5702b3b1
  val m = new Moon                          //> m  : Collections.Moon = Collections$Moon$1@69ea3742
  c ^ m //调用 c                              //> Cow jumped over the moon
  c ^: m //调用 m                             //> This cow jumped over the moon too
  m.^:(c)                                   //> This cow jumped over the moon too

^()方法是一个定义在Cow类上的方法,而^:()方法是独立定义在Moon类上的一个方法。对这两个方法的调用看起来几乎是完全一样的,cow都在操作符的左边,而moon都在操作符的右边。但是,第一个调用发生在cow上,而第二个调用发生在moon上,这一区别相当微妙。

+-!~这些操作符也是跟随着他们后面的实例:

  class Sample {
    def unary_+(): Unit = println("Called unary +")
    def unary_-(): Unit = println("Called unary -")
    def unary_!(): Unit = println("Called unary !")
    def unary_~(): Unit = println("Called unary ~")
  }
  
  val sample = new Sample                   //> sample  : Collections.Sample = Collections$Sample$1@4b952a2d
  +sample                                   //> Called unary +
  -sample                                   //> Called unary -
  !sample                                   //> Called unary !
  ~sample                                   //> Called unary ~

1.2. for 表达式

  for (i <- 1 to 3) {println(s"ho ${i}")}   //> ho 1
                                            //| ho 2
                                            //| ho 3
  val results1 = for (i <- 1 to 10)
    yield i * 2                       //> results1  : scala.collection.immutable.IndexedSeq[Int] = Vector(2, 4, 6, 8, 10, 12, 14, 16, 18, 20)
  val results2 = (1 to 10).map(_ * 2)       //> results2  : scala.collection.immutable.IndexedSeq[Int] = Vector(2, 4, 6, 8, 10, 12, 14, 16, 18, 20)
  // 列表推导: list comprehension
  val doubleEven1 = for (i <- 1 to 10; if i % 2 == 0)
    yield i * 2                       //> doubleEven1  : scala.collection.immutable.IndexedSeq[Int] = Vector(4, 8, 12, 16, 20)
  for {
    i <- 1 to 10
    if i % 2 == 0
  } yield i * 2                             //> res20: scala.collection.immutable.IndexedSeq[Int] = Vector(4, 8, 12, 16, 20)

  // 多个生成器的话,每个生成器都将形成一个内部循环
  // 最右边的生成器控制最里面的循环
  for (i <- 1 to 3; j <- 4 to 6) {
    println(s"($i, $j) ")             //> (1, 4)
                                      //| (1, 5)
                                      //| (1, 6)
                                      //| (2, 4)
                                      //| (2, 5)
                                      //| (2, 6)
                                      //| (3, 4)
                                      //| (3, 5)
                                      //| (3, 6)