前言
这是面向初学者的Scala教程的第4部分。 该博客上还有其他文章,您可以在我正在为其创建的计算语言学课程的链接页面上找到这些链接和其他资源。 此外,您可以在“ JCG Java教程”页面上找到本教程和其他教程系列。
本教程背离了前三个教程的初学者性质,因此对于已经具有另一种语言编程经验的读者来说,这可能会更加有趣。 (不过,另请参阅第3部分中有关在Scala中使用匹配的部分 。)
迭代,Scala方式
到目前为止,我们(大多数)已经使用它们的索引访问了列表中的各个项目。 但是,与列表相关的最自然的事情之一就是对列表中的每个项目重复执行某些操作,例如:“对于给定单词列表中的每个单词:打印它”。 这是怎么说在斯卡拉。
scala> val animals = List("newt", "armadillo", "cat", "guppy")
animals: List[java.lang.String] = List(newt, armadillo, cat, guppy)
scala> animals.foreach(println)
newt
armadillo
cat
guppy
这表示采用列表的每个元素(由foreach指示)并按顺序对其应用函数(在本例中为println )。 发生了一些规格不足,因为我们没有提供变量来命名元素。 在某些情况下(例如上述情况),此方法可行,但并非总是可能的。 这是完整的外观,带有一个变量命名元素。
scala> animals.foreach(animal => println(animal))
newt
armadillo
cat
guppy
当您需要做更多事情时,例如将一个String元素与另一个String串联时,这很有用。
scala> animals.foreach(animal => println("She turned me into a " + animal))
She turned me into a newt
She turned me into a armadillo
She turned me into a cat
She turned me into a guppy
或者,如果要使用它执行计算,例如输出字符串列表中每个元素的长度。
scala> animals.foreach(animal => println(animal.length))
4
9
3
5
我们可以使用for表达式获得与foreach相同的结果。
scala> for (animal <- animals) println(animal.length)
4
9
3
5
到目前为止,我们一直在做的事情,这两种表示对List元素进行迭代的方式是等效的。 但是,它们是不同的: for表达式返回一个值,而foreach只是对列表的每个元素执行一些功能。 后一种用法称为副作用 :通过打印每个元素,我们不会创建新的值,而只是在每个元素上执行操作。 使用for表达式 ,我们可以产生创建转换后的列表的值。 例如,使用println与以下内容进行对比。
scala> val lengths = for (animal <- animals) yield animal.length
lengths: List[Int] = List(4, 9, 3, 5)
结果是一个新列表,其中包含动物列表中每个元素的长度(字符数)。 (当然,您现在可以通过执行lengths.foreach(println)来打印其内容,但是通常我们希望使用这种值来执行其他通常更有趣的事情。)
我们刚才所做的是使用函数length一对一地将动物的值映射到一组新的值中。 列表还有另一个称为map的功能,可以直接执行此操作。
scala> val lengthsMapped = animals.map(animal => animal.length)
lengthsMapped: List[Int] = List(4, 9, 3, 5)
因此, 让步表达式和map方法获得相同的输出,并且在许多情况下它们几乎是等效的。 但是,使用map通常更方便,因为您可以轻松地将一系列操作链接在一起。 例如,假设您要在数字列表中加1,然后求平方,因此将List(1,2,3)变成List(2,3,4)变成List(4,9,16 )。 您可以使用地图轻松地做到这一点。
nums.map(x=>x+1).map(x=>x*x)
一些读者会对刚做的事情感到困惑。 此处更明确地使用中间变量nums2存储附加列表。
scala> val nums2 = nums.map(x=>x+1)
nums2: List[Int] = List(2, 3, 4)
scala> nums2.map(x=>x*x)
res9: List[Int] = List(4, 9, 16)
由于nums.map(x => x + 1)返回一个List,因此我们不必将其命名为变量即可使用它-我们可以立即使用它,包括对其执行另一个map函数。 (当然,可以一次执行该计算,例如map((x + 1)*(x + 1)),但通常一个人正在使用一系列内置函数,或者一个函数已经预定义了) 。
您可以继续映射到您的心脏内容,包括从Ints到Strings的映射。
scala> nums.map(x=>x+1).map(x=>x*x).map(x=>x-1).map(x=>x*(-1)).map(x=>"The answer is: " + x)
res12: List[java.lang.String] = List(The answer is: -3, The answer is: -8, The answer is: -15)
注意:在所有这些情况下使用x都不重要。 它们可以分别命名为x,y,z和turlingdromes42-任何有效的变量名。
遍历多个列表
有时您有两个配对的列表,并且您需要同时对每个列表中的元素进行操作。 例如,假设您有一个单词标记列表和另一个带有词性的列表。 (有关语音部分的讨论,请参见前面的教程 。)
scala> val tokens = List("the", "program", "halted")
tokens: List[java.lang.String] = List(the, program, halted)
scala> val tags = List("DT","NN","VB")
tags: List[java.lang.String] = List(DT, NN, VB)
现在,假设我们要将它们输出为以下字符串:
/ DT程序/ NN停止/ VB
最初,我们将一步一步地进行操作,然后说明如何在一行中完成所有操作。
首先,我们使用zip函数将两个列表放在一起,并从每个列表中获得一个新的成对元素列表。
scala> val tokenTagPairs = tokens.zip(tags)
tokenTagPairs: List[(java.lang.String, java.lang.String)] = List((the,DT), (program,NN), (halted,VB))
Zipping two lists together in this way is a common pattern used for iterating over two lists.
Now we have a list of token-tag pairs we can use a for expression to turn it into a List of strings.
1
scala> val tokenTagSlashStrings = for ((token, tag) <- tokenTagPairs) yield token + "/" + tag
tokenTagSlashStrings: List[java.lang.String] = List(the/DT, program/NN, halted/VB)
现在,我们只需要通过将所有字符串元素之间留有空格的方式将字符串列表转换为单个字符串即可。 函数mkString使此操作变得容易。
scala> tokenTagSlashStrings.mkString(" ")
res19: String = the/DT program/NN halted/VB
最后,这一切都一步一步完成。
scala> (for ((token, tag) <- tokens.zip(tags)) yield token + "/" + tag).mkString(" ")
res23: String = the/DT program/NN halted/VB
将字符串翻录成有用的数据结构
在计算语言学中,通常需要将字符串输入转换为有用的数据结构。 考虑上一教程中提到的词性标记的句子。 首先,将其分配给变量sendRaw。
val sentRaw = "The/DT index/NN of/IN the/DT 100/CD largest/JJS Nasdaq/NNP financial/JJ stocks/NNS rose/VBD modestly/RB as/IN well/RB ./."
现在,让我们将其转换为“元组列表”,其中每个元组以单词为第一个元素,以postag为第二个元素。 我们从执行此操作的一行开始,以便您可以看到所需的结果,然后我们将详细检查每个步骤。
scala> val tokenTagPairs = sentRaw.split(" ").toList.map(x => x.split("/")).map(x => Tuple2(x(0), x(1)))
tokenTagPairs: List[(java.lang.String, java.lang.String)] = List((The,DT), (index,NN), (of,IN), (the,DT), (100,CD), (largest,JJS), (Nasdaq,NNP), (financial,JJ), (stocks,NNS), (rose,VBD), (modestly,RB), (as,IN), (well,RB), (.,.))
让我们依次介绍一下这些内容。 第一个分割在每个空格字符处剪切sendRaw ,并返回一个字符串数组,其中每个元素都是空格之间的材料。
scala> sentRaw.split(" ")
res0: Array[java.lang.String] = Array(The/DT, index/NN, of/IN, the/DT, 100/CD, largest/JJS, Nasdaq/NNP, financial/JJ, stocks/NNS, rose/VBD, modestly/RB, as/IN, well/RB, ./.)
什么是数组? 这是一种序列,例如List,但是它具有一些不同的属性,我们将在后面讨论。 现在,让我们继续使用列表,这可以通过使用toList方法来完成。 另外,让我们将其分配给变量,以便其余操作更易于关注。
scala> val tokenTagSlashStrings = sentRaw.split(" ").toList
tokenTagSlashStrings: List[java.lang.String] = List(The/DT, index/NN, of/IN, the/DT, 100/CD, largest/JJS, Nasdaq/NNP, financial/JJ, stocks/NNS, rose/VBD, modestly/RB, as/IN, well/RB, ./.)
现在,我们需要将该列表中的每个元素转换为标记和标记对。 让我们首先考虑一个元素,将类似“ The / DT ”的东西变成一对( “ The”,“ DT”) 。 下一行显示了如何使用中间变量一次完成此步骤。
scala> val first = "The/DT"
first: java.lang.String = The/DT
scala> val firstSplit = first.split("/")
firstSplit: Array[java.lang.String] = Array(The, DT)
scala> val firstPair = Tuple2(firstSplit(0), firstSplit(1))
firstPair: (java.lang.String, java.lang.String) = (The,DT)
因此, firstPair是一个元组,表示首先在字符串中编码的信息。 这涉及到两个操作:拆分,然后从拆分产生的Array中创建一个元组。 我们可以使用map对tokenTagSlashStrings中的所有元素执行此操作。 首先让我们将字符串转换为数组。
scala> val tokenTagArrays = tokenTagSlashStrings.map(x => x.split("/"))
res0: List[Array[java.lang.String]] = List(Array(The, DT), Array(index, NN), Array(of, IN), Array(the, DT), Array(100, CD), Array(largest, JJS), Array(Nasdaq, NNP), Array(financial, JJ), Array(stocks, NNS), Array(rose, VBD), Array(modestly, RB), Array(as, IN), Array(well, RB), Array(., .))
最后,我们将Arrays转换为Tuple2s,并获得我们之前使用单线获得的结果。
scala> val tokenTagPairs = tokenTagArrays.map(x => Tuple2(x(0), x(1)))
tokenTagPairs: List[(java.lang.String, java.lang.String)] = List((The,DT), (index,NN), (of,IN), (the,DT), (100,CD), (largest,JJS), (Nasdaq,NNP), (financial,JJ), (stocks,NNS), (rose,VBD), (modestly,RB), (as,IN), (well,RB), (.,.))
注意 :如果您愿意使用将一系列操作链接在一起的单线,则一定要使用它们。 但是,如果使用几行涉及一堆中间变量的行,这可以帮助您分解任务并获得所需的结果,则不会感到羞耻。
拥有成对列表(Tuple2s)的非常有用的事情之一是, 解压缩功能为我们提供了两个列表,一个包含所有第一个元素,另一个包含所有第二个元素。
scala> val (tokens, tags) = tokenTagPairs.unzip
tokens: List[java.lang.String] = List(The, index, of, the, 100, largest, Nasdaq, financial, stocks, rose, modestly, as, well, .)
tags: List[java.lang.String] = List(DT, NN, IN, DT, CD, JJS, NNP, JJ, NNS, VBD, RB, IN, RB, .)
有了这个,我们就转了一圈。 从原始字符串开始(例如我们很可能从文本文件中读取),现在我们有了Lists,使我们能够进行有用的计算,例如将这些标签转换为另一种形式。
提供您已定义的映射功能
让我们回到上一教程中简化过的postag练习。 我们将对其进行一些修改:不是缩短Penn Treebank的词性,而是使用大多数人都熟悉的英语单词(例如名词和动词)将它们转换为课程的词性。 以下函数将Penn Treebank标记转换为这些课程标记,以提供比上一教程中介绍的标记更多的标记(请注意:这仍然是不完整的,但仅用于说明这一点)。
def coursePos (tag: String) = tag match {
case "NN" | "NNS" | "NNP" | "NNPS" => "Noun"
case "JJ" | "JJR" | "JJS" => "Adjective"
case "VB" | "VBD" | "VBG" | "VBN" | "VBP" | "VBZ" | "MD" => "Verb"
case "RB" | "RBR" | "RBS" | "WRB" | "EX" => "Adverb"
case "PRP" | "PRP$" | "WP" | "WP$" => "Pronoun"
case "DT" | "PDT" | "WDT" => "Article"
case "CC" => "Conjunction"
case "IN" | "TO" => "Preposition"
case _ => "Other"
}
现在,我们可以将此功能映射到先前获得的集合中的语音部分。
scala> tags.map(coursePos)
res1: List[java.lang.String] = List(Article, Noun, Preposition, Article, Other, Adjective, Noun, Adjective, Noun, Verb, Adverb, Preposition, Adverb, Other)
瞧! 如果我们要以这种方式转换标签,然后像开始时那样将它们输出为字符串,则只需几个步骤。 我们将从头开始并回顾一下。 尝试自己运行以下内容。
val sentRaw = "The/DT index/NN of/IN the/DT 100/CD largest/JJS Nasdaq/NNP financial/JJ stocks/NNS rose/VBD modestly/RB as/IN well/RB ./."
val (tokens, tags) = sentRaw.split(" ").toList.map(x => x.split("/")).map(x => Tuple2(x(0), x(1))).unzip
tokens.zip(tags.map(coursePos)).map(x => x._1+"/"+x._2).mkString(" ")
还有一点是,当您提供(x => x + 1)这样的表达式来映射时 ,实际上是在定义一个匿名函数! 这是具有不同规格级别的相同地图操作
scala> val numbers = (1 to 5).toList
numbers: List[Int] = List(1, 2, 3, 4, 5)
scala> numbers.map(1+)
res11: List[Int] = List(2, 3, 4, 5, 6)
scala> numbers.map(_+1)
res12: List[Int] = List(2, 3, 4, 5, 6)
scala> numbers.map(x=>x+1)
res13: List[Int] = List(2, 3, 4, 5, 6)
scala> numbers.map((x: Int) => x+1)
res14: List[Int] = List(2, 3, 4, 5, 6)
因此,这是完全一致的:无论您传入命名函数还是匿名函数, map都会将其应用于列表中的每个元素。
最后,请注意,您可以使用最终形式来定义函数。
scala> def addOne = (x: Int) => x + 1
addOne: (Int) => Int
scala> addOne(1)
res15: Int = 2
这类似于我们之前定义的函数(例如def addOne(x:Int)= x + 1 ),但是在某些情况下更方便,我们将在后面介绍。 现在,要实现的事情是,每当进行映射时,您要么使用已存在的功能,要么即时创建一个功能。
过滤和计数
映射方法是一种对List的每个元素执行计算的便捷方法,可以有效地将List从一组值转换为具有从每个对应元素计算出的一组值的新List。 还有更多的方法具有其他作用,例如从List( 过滤器 )中删除元素,计算满足给定谓词的元素数( count ),以及从List中的所有元素计算合计单个结果( reduce和fold) )。 让我们考虑一个简单的任务:计算一个标记句子中不是名词或形容词的记号。 首先,让我们从之前获取映射的postag列表。
scala> val courseTags = tags.map(coursePos)
courseTags: List[java.lang.String] = List(Article, Noun, Preposition, Article, Other, Adjective, Noun, Adjective, Noun, Verb, Adverb, Preposition, Adverb, Other)
一种方法是过滤掉所有名词和形容词,以获得不含它们的列表,然后获取其长度。
scala> val noNouns = courseTags.filter(x => x != "Noun")noNouns: List[java.lang.String] = List(Article, Preposition, Article, Other, Adjective, Adjective, Verb, Adverb, Preposition, Adverb, Other)
scala> val noNounsOrAdjectives = noNouns.filter(x => x != "Adjective")
noNounsOrAdjectives: List[java.lang.String] = List(Article, Preposition, Article, Other, Verb, Adverb, Preposition, Adverb, Other)
scala> noNounsOrAdjectives.length
res8: Int = 9
但是,由于filter仅采用布尔值,因此我们当然可以使用布尔合取和析取来简化事物。 并且,我们不需要保存中间变量。 这是一个班轮。
scala> courseTags.filter(x => x != "Noun" && x != "Adjective").length
res9: Int = 9
如果我们想要的只是元素的数量,我们可以只使用带有相同谓词的count 。
scala> courseTags.count(x => x != "Noun" && x != "Adjective")
res10: Int = 9
作为练习,请尝试执行以sendRaw开头并提供值“ resX:Int = 9 ”(其中X是您在Scala REPL中得到的值)的单线 。
在下一个教程中,我们将看到如何使用reduce和fold来计算List的聚合结果。
参考: 对于初学者来说,Scala的第一步,来自Bcompose博客的JCG合作伙伴 Jason Baldridge的 第4部分 。
相关文章 :
- Scala教程– Scala REPL,表达式,变量,基本类型,简单函数,保存和运行程序,注释
- Scala教程–元组,列表,列表和字符串上的方法
- Scala教程–使用if-else块和匹配条件执行
- Scala教程–正则表达式,匹配
- Scala教程–使用scala.util.matching API进行正则表达式,匹配和替换
- Scala教程–地图,集,groupBy,选项,展平,flatMap
- Scala教程– scala.io.Source,访问文件,flatMap,可变地图
- Scala教程–对象,类,继承,特征,具有多个相关类型的列表,适用
- Scala教程–脚本编写,编译,主要方法,函数的返回值
- Scala教程– SBT,scalabha,软件包,构建系统
- Scala教程–代码块,编码样式,闭包,scala文档项目
- Scala中功能组合的乐趣
- Scala如何改变了我对Java代码的思考方式
- Scala中删除了Java的哪些功能?
- 用Scala测试
- 每个程序员都应该知道的事情
翻译自: https://www.javacodegeeks.com/2011/10/scala-tutorial-iteration-for.html