Home > Scala > Scala 集合框架:Tuple,Map[K,V],Option[T]

Scala 集合框架:Tuple,Map[K,V],Option[T]

Tuple

Tuple 能让一个方法返回两个或三个值,例如下面的代码输入一个 List[Double],返回元素个数,元素的和及其平方和:

def sumSq(in: List[Double]): (Int, Double, Double) =
in.foldLeft((0, 0d, 0d))((t, v) => (t._1 + 1, t._2 + v, t._3 + v * v))

这里,编译器会将在括号中的集合元素看作是一个Tuple,上面的代码会翻译成:Tuple3[Int,Double,Double],这个函数接收两个参数:t和 v,t是一个Tuple3,而v是一个Double。
使用模式匹配可以让上面的代码更易理解:

def sumSq(in: List[Double]) : (Int, Double, Double) =
in.foldLeft((0, 0d, 0d)){
case ((cnt, sum, sq), v) => (cnt + 1, sum + v, sq + v * v)}

当然,也可以用其它方式来创建一个Tuple:

scala> Tuple2(1,2) == Pair(1,2)
scala> Pair(1,2) == (1,2)
scala> (1,2) == 1 -> 2

其中最后一种写法 1 -> 2 对于传递pairs 是很有用的一种方式。Pairs经常出现在代码中,包括用来创建 Maps 的 name/value 对。

对于Tuple元素的读取,应该使用下标 _n,例如 t._1, t._2 ,t._3 ,注意这里起始为1,并且对元素的引用不能越界。

Map[K,V]

Map 是一个 key/value 对的集合, key 是唯一的,但value 可以不唯一,Scala中的默认Map实现是不可变的,可以用于多线程环境,而且性能与Java中的HashMap相当。
创建一个 Map:

scala> var p = Map(1 -> "David", 9 -> "Elwood")
p: scala.collection.immutable.Map[Int,java.lang.String] = Map((1,David), (9,Elwood))

添加一个元素:

scala> p += 8 -> "Archer"

scala>  p = p + (9 -> "Archer")
p: scala.collection.immutable.Map[Int,java.lang.String] = Map(1 -> David, 8 -> Archer, 9 -> Archer)

如果想取出一个不存在的元素:

scala> p(88)
java.util.NoSuchElementException: key not found: 88

这里会抛出异常,虽然看起来并不方便,因为Java的Map能够通过返回null来处理这个情况,但这样做有两个缺点:首先,必须对每个Map的访问进行 null-test,其次,这代表着不能在一个Map中存储null值。对此Scala有一个更好的机制,即Map的get()方法将返回一个包含了結果的Option(Some 或none):

scala> p.get(88)
res10: Option[java.lang.String] = None

如果key没有找到,可以返回一个默认值:

scala> p.getOrElse(99, "Nobody")
res55: java.lang.String = Nobody
scala> p.getOrElse(1, "Nobody")
res56: java.lang.String = David

也可以使用flatMap来返回key在1-5之间的值:

scala> 1 to 5 flatMap(p.get)
res13: scala.collection.immutable.IndexedSeq[java.lang.String] = Vector(David)

注意,表面上来看这里p.get 只是一个方法,但是Scala正期待着一个拥有特定类型的参数,而你传递了一个接收一个参数的方法,这时Scala会将这个方法和它未写的参数提升为一个函数。

删除一个元素:

scala> p -=9

scala> p
res24: scala.collection.immutable.Map[Int,java.lang.String] = Map((1,David), (8,Archer), (10,Archer))

同时,我们可以在keys 或values之上进行操作:

//查找最大的key
scala> p.keys.reduceLeft(_ max _)
res22: Int = 10
//查找最大的value
scala> p.values.reduceLeft((a, b) => if (a > b) a else b)
res23: java.lang.String = David
//是否有value包含 "z”
scala> p.values.exists(_.contains("z"))
res28: Boolean = false
//批量增加元素
scala> p ++= List(5 -> "Cat", 6 -> "Dog")
scala> p
res29: scala.collection.immutable.Map[Int,java.lang.String] = Map((5,Cat), (10,Archer), (1,David), (6,Dog), (8,Archer))
//批量减少元素
scala> p --= List(8, 6)
scala> p
res31: scala.collection.immutable.Map[Int,java.lang.String] = Map((5,Cat), (10,Archer), (1,David))

既然Map也是一个集合,这也意味着可以在Map对象上使用map,filter,foldLeft方法。

Option[T]

Option[T]是一个包含零个或一个给定元素类型的集合,它提供了对Java的null的替代,一个Option[T]可以是 Some[T]或者是None,其中None是一个单例对象,在整个Scala应用程序中只有一个实例,但是None也有自己的方法:可以在它之上调用map,flatMap,filter,foreach,所以不管是None或者是Some,都可以使用业务逻辑代码。

// 导入别名是为了不和Scala中的Boolean冲突
scala>  import java.lang.{Boolean => JBool}
import java.lang.{Boolean=>JBool}

scala> def tryo[T](f: => T): Option[T] = try {Some(f)} catch {case _ => None}
tryo: [T](f: => T)Option[T]

scala> def toInt(s: String): Option[Int] = tryo(s.toInt)
toInt: (s: String)Option[Int]

scala> def toBool(s: String) = tryo(JBool.parseBoolean(s))
toBool: (s: String)Option[Boolean]

有了上面的代码,可以不再使用 null-test:

def personFromParams(p: Map[String, String]): Option[Person] =
for {name <- p.get("name")
     ageStr <- p.get("age")
     age <- toInt(ageStr)
     validStr <- p.get("valid")
     valid <- toBool(validStr)}
     yield new Person(name, age, valid)

可以使用get方法来取得Option中的值:

scala> Some(3).get
res32: Int = 3

但是对None调用get方法会抛出一个异常:

scala> None.get
java.util.NoSuchElementException: None.get

像map一样,Option也有一个getOrElse方法,在内容未定义时返回一个默认值:

scala> Some(3).getOrElse(44)
res34: Int = 3

scala> None.getOrElse(44)
res35: Int = 44

Option 的出现,减少了很多复杂的代码,让业务逻辑看起来更加清晰自然。

Related posts:

  1. Scala 集合框架:List
  2. Scala : 作用域中的变量,容器
  3. Scala : Partial Functions 和 Type Parameters
  4. Scala : Option 和 foldLeft
  5. Scala : Functions and Parameters
Categories: Scala Tags:
  1. No comments yet.
  1. No trackbacks yet.