Problem
像上一节一样,你想把一个集合的每个元素通过某种算法变换后生成一个新的集合
Solution
我们要调用集合的map方法,然后传给它一个函数、匿名函数或者方法来对每一个集合元素进行变换,而不是for/yield。下面这个例子中我们可以看到,我们把一组字符串的首字母变为大写:
scala> val helpers = Vector("adam", "kim", "melissa")helpers: scala.collection.immutable.Vector[String] = Vector(adam, kim, melissa)scala> helpers.map(e => e.capitalize)res26: scala.collection.immutable.Vector[String] = Vector(Adam, Kim, Melissa)scala> helpers.map(_.capitalize)res1: scala.collection.immutable.Vector[String] = Vector(Adam, Kim, Melissa)
在下面这个例子中,我们把一个字符串集合转化成了一个整形集合:
scala> val names = Array("Fred", "Joe", "Jonathan")names: Array[String] = Array(Fred, Joe, Jonathan)scala> val lengths = names.map(_.length)lengths: Array[Int] = Array(4, 3, 8)
同样地,map方法可以把一个集合转化为一个xml元素:
scala> val nieces = List("Aleka", "Christina", "Molly")nieces: List[String] = List(Aleka, Christina, Molly)scala> val elems = nieces.map(niece =>
我们可以使用类似的方法,把一个集合转化为ul:
scala> val ul =
- {nieces.map(niece =>
- {niece} )}
- Aleka
- Christina
- Molly
被传入map方法的函数可以是任意复杂度的,当然函数的负责度这是根据你的需求来制定的。在Discussion中你会看到如何在map中使用一个多行匿名函数。当你的算法足够复杂,直到匿名函数无法满足你的需求的时候,你可以先定义一个函数,然后把这个函数传给map方法。
scala> def plusOne(c: Char): Char = (c.toByte+1).toCharplusOne: (c: Char)Charscala> "HAL".map(plusOne)res2: String = IBM
当你想定义一个能够传入map方法的函数的时候,这个函数必须只有一个和集合元素同类型的参数。在上面这个例子中,plusOne被定义为接受一个char,因为String世界上就是一个char的集合元素。函数的返回值可以是任何你想要的。实际上names.map(_.length)就是这么一个输入String,输出Int的函数。
和for/yield结构不相同的地方是map方法可以形成一个方法调用链。也就是你可以在map方法后直接对map返回的集合进行其它操作活着把map加在一个返回集合的方法的后面。比如,你可以把一个字符串根据某中规则切分成一个字符串数组,然后再去掉字符串两边的空格。
scala> val s = " eggs, milk, butter, Coco Puffs "s: String = " eggs, milk, butter, Coco Puffs "scala> val items = s.split(",").map(_.trim)items: Array[String] = Array(eggs, milk, butter, Coco Puffs)
Discussion
对于简单一点的情况来说,使用map和使用for/yield是一样:
scala> val people = List("adam", "kim", "melissa")people: List[String] = List(adam, kim, melissa)scala> val caps1 = people.map(_.capitalize)caps1: List[String] = List(Adam, Kim, Melissa)scala> val caps2 = for (f <- people) yield f.capitalizecaps2: List[String] = List(Adam, Kim, Melissa)
但是一旦当你添加guard(警卫)的时候,for/yield即可购就不再直接等于map方法调用了。拂过你尝试使用if字句在你的算法中,然后把这个函数传递给map方法,你会发现你得到了与你想象中不同的结果:
scala> val fruits = List("apple", "banana", "lime", "orange", "raspberry")fruits: List[String] = List(apple, banana, lime, orange, raspberry)scala> fruits.map{ fruit => { | if(fruit.length < 6) fruit.toUpperCase | }}res5: List[Any] = List(APPLE, (), LIME, (), ())
这样是不行的,但是你可以使用filter方法来对集合元素进行过滤,然后再执行map方法调用:
scala> fruits.filter(_.length < 6).map(_.toUpperCase)res6: List[String] = List(APPLE, LIME)