Scala教程–地图,集,groupBy,选项,展平,flatMap

前言

这是面向初学者的Scala教程的第7部分。 该博客上还有其他文章,您可以在我正在为其创建的计算语言学课程的链接页面上找到这些链接和其他资源。 此外,您可以在“ JCG Java教程”页面上找到本教程和其他教程系列。

列表(以及其他序列数据结构,例如Ranges和Arrays)使您可以按有序的方式对对象集合进行分组:您可以通过索引列表中的元素位置来访问列表中的元素,或者一遍又一遍地遍历列表元素, 用于表达式和序列函数,如mapfilterreducefold 。 数据结构的另一种重要类型是关联数组,您将在Scala中将其称为Map 。 (是的,这与map 函数有一个不幸的歧义,但是从上下文中可以很清楚地看到它们的用法。)Maps允许您存储键值对的集合并通过与它们关联的键来访问值,而不是通过索引(与列表一样)。

您可以使用地图的示例案例:

  • 将英语单词与其德语翻译相关联
  • 在给定文本中将每个单词与其计数相关联
  • 将每个单词与其可能的词性相关联

您将在本文中看到其中每个的具体示例。

创建地图并访问其元素

地图非常直观。 这是一个包含一些英语单词及其德语翻译的示例。 创建映射的一种简单方法是传入对列表,其中每对的第一个元素定义一个键,第二个元素定义一个对应的值。

scala> val engToDeu = Map(("dog","Hund"), ("cat","Katze"), ("rhinoceros","Nashorn"))
engToDeu: scala.collection.immutable.Map[java.lang.String,java.lang.String] = Map(dog -> Hund, cat -> Katze, rhinoceros -> Nashorn)

注意,Map条目的格式为key-> value 。 然后,我们可以通过在创建的地图中提供关键字“ dog ”来检索dog的德语翻译。

scala> engToDeu("dog")
res0: java.lang.String = Hund

想一想用List要做些什么。 您需要两个列表,每种语言一个,并且它们需要对齐,以便一个列表中的每个元素对应于另一列表中的翻译。

scala> val engWords = List("dog","cat","rhinoceros")
engWords: List[java.lang.String] = List(dog, cat, rhinoceros)

scala> val deuWords = List("Hund","Katze","Nashorn")
deuWords: List[java.lang.String] = List(Hund, Katze, Nashorn)

然后,找的翻译,你就必须找到猫engWords的索引,然后查找该指数deuWords。

scala> engWords.indexOf("cat")
res2: Int = 1

scala> deuWords(engWords.indexOf("cat"))
res3: java.lang.String = Katze

这实际上效率很低,并且还有其他问题。 映射是我们这里想要的正确的事情,它们确实可以非常有效地检索键的值。

事实证明,我们可以采用以这种方式对齐的两个列表,并非常容易地构建Map。 回想一下,将两个列表压缩在一起会创建一个成对的列表,其中每个对给出共享相同索引的元素。

scala> engWords.zip(deuWords)
res4: List[(java.lang.String, java.lang.String)] = List((dog,Hund), (cat,Katze), (rhinoceros,Nashorn))

通过在这样的对列表上调用toMap方法,我们得到了一个Map。

scala> engWords.zip(deuWords).toMap
res5: scala.collection.immutable.Map[java.lang.String,java.lang.String] = Map(dog -> Hund, cat -> Katze, rhinoceros -> Nashorn)

请注意,即使REPL显示的键/值对的顺序与我们从中构建映射的原始列表相同,也没有映射元素的固有顺序。

您可以使用+运算符以及每个键和值对之间的箭头->将元素添加到Map中以创建新Map。

scala> engToDeu + "owl" -> "Eule"
res6: (java.lang.String, java.lang.String) = (Map(dog -> Hund, cat -> Katze, rhinoceros -> Nashorn)owl,Eule)

scala> engToDeu + ("owl" -> "Eule", "hippopotamus" -> "Nilpferd")
res7: scala.collection.immutable.Map[java.lang.String,java.lang.String] = Map(rhinoceros -> Nashorn, dog -> Hund, owl -> Eule, hippopotamus -> Nilpferd, cat -> Katze)

您可以使用++运算符将一个Map添加到另一个Map。

scala> val newEntries = Map(("hippopotamus", "Nilpferd"),("owl","Eule"))
newEntries: scala.collection.immutable.Map[java.lang.String,java.lang.String] = Map(hippopotamus -> Nilpferd, owl -> Eule)

scala> val expandedEngToDeu = engToDeu ++ newEntries
expandedEngToDeu: scala.collection.immutable.Map[java.lang.String,java.lang.String] = Map(rhinoceros -> Nashorn, dog -> Hund, owl -> Eule, hippopotamus -> Nilpferd, cat -> Katze)

您可以通过将元组列表传递给++运算符来执行相同的操作。

scala> engToDeu ++ List(("hippopotamus", "Nilpferd"),("owl","Eule"))
res8: scala.collection.immutable.Map[java.lang.String,java.lang.String] = Map(rhinoceros -> Nashorn, dog -> Hund, owl -> Eule, hippopotamus -> Nilpferd, cat -> Katze)

您可以使用–运算符从地图中删除键。

scala> engToDeu - "dog"
res9: scala.collection.immutable.Map[java.lang.String,java.lang.String] = Map(cat -> Katze, rhinoceros -> Nashorn)

有关此类功能的更多示例,请参见Map API 。 注意:在这篇文章中,我始终坚持使用不可变的Maps-如果您正在看其他教程,并且想知道为什么其中的某些方法在这里不起作用,它们可能一直在使用可变Maps,我们将在后面讨论。

如果我们请求与Map中不存在的键相关联的值,则会收到错误消息。

scala> engToDeu("bird")
java.util.NoSuchElementException: key not found: bird
at scala.collection.MapLike$class.default(MapLike.scala:224)
(etc.)

您可以使用contains方法检查键是否在Map

scala> engToDeu.contains("bird")
res10: Boolean = false

scala> engToDeu.contains("dog")
res11: Boolean = true

假设您有一个英语单词列表,想要查找其对应的德语单词,并且想要保护自己免受NoSuchElementException的侵害。 一种方法是使用contains过滤单词,然后通过engToDeu映射其余单词

scala> val wordsToTranslate = List("dog","bird","cat","armadillo")
wordsToTranslate: List[java.lang.String] = List(dog, bird, cat, armadillo)

scala> wordsToTranslate.filter(x=>engToDeu.contains(x)).map(x=>engToDeu(x))
res12: List[java.lang.String] = List(Hund, Katze)

这是一种将地图安全地应用于项目列表的有用方法。 但是,稍后我们将看到使用“选项”处理缺失值的更好方法。

如果您可以尝试使用地图的任何键都有一个合理的默认值,则可以使用getOrElse方法。 您将键作为第一个参数,然后将默认值作为第二个参数。

scala> engToDeu.getOrElse("dog","???")
res1: java.lang.String = Hund

scala> engToDeu.getOrElse("armadillo","???")
res2: java.lang.String = ???

对于包含统计信息(例如字数统计)的地图,使用getOrElse (默认值为0)是很常见的,其中缺少键自然会表明它具有例如零计数。

如果地图中没有的任何键的默认值都一致,则可以使用withDefault方法进行设置。

scala> val engToDeu = Map(("dog","Hund"), ("cat","Katze"), ("rhinoceros","Nashorn")).withDefault(x => "???")
engToDeu: scala.collection.immutable.Map[java.lang.String,java.lang.String] = Map(dog -> Hund, cat -> Katze, rhinoceros -> Nashorn)

scala> engToDeu("armadillo")
res3: java.lang.String = ???

现在,您可以以通常的方式询问值,而无需使用getOrElse并每次都提供默认值。

地图中的键和值

您可能已经观察到,Scala可以告诉您的信息比您刚刚创建的Map还多。 与List一样,Map是一个参数化类型,这意味着它是一种将特定类型的对象收集在一起的通用方法。 在上方,我们看到了Map [String,String]的实例(省略了java.lang部分,使其更加清晰)。 第一个字符串表示是字符串,第二个表示是字符串。 基本上,任何类型都可以在任一位置使用( 警告 :除非您知道自己在做什么,否则应避免将可变数据结构用作键)。 下面是一些示例(尝试忽略scala.collection.immutablejava.lang部分,而只关注我们获得的Map [X,Y]签名)。

scala> Map((10,"ten"), (100,"one hundred"))
res0: scala.collection.immutable.Map[Int,java.lang.String] = Map(10 -> ten, 100 -> one hundred)

scala> Map(("a",1),("b",2))
res1: scala.collection.immutable.Map[java.lang.String,Int] = Map(a -> 1, b -> 2)

scala> Map((1,3.14), (2,6.28))
res2: scala.collection.immutable.Map[Int,Double] = Map(1 -> 3.14, 2 -> 6.28)

scala> Map((("pi",1),3.14), (("tau",2),6.28))
res3: scala.collection.immutable.Map[(java.lang.String, Int),Double] = Map((pi,1) -> 3.14, (tau,2) -> 6.28)

scala> Map(("the", List("Determiner")), ("book", List("Verb", "Noun")), ("off", List("Preposition", "Verb")))
res4: scala.collection.immutable.Map[java.lang.String, List[java.lang.String]] = Map(the -> List(Determiner), book -> List(Verb, Noun), off -> List(Preposition, Verb))

最后两个示例显示了键和值类型的一些非常有用的方面,使您可以使用更复杂的键和值。 前者使用(String,Int)对作为键,签名为Map [(String,Int),Double] ,后者使用List [String]作为值,签名为Map [String,List [String] ] 。 因此,您可以使用元组将几种类型捆绑在一起,并且可以使用参数化的数据结构来参数化另一个数据结构。

一个简单的翻译任务

这是一部小型德语/英语词典,作为地图。

scala> val miniDictionary = Map(("befreit", "liberated"), ("baeche", "brooks"), ("eise", "ice"), ("sind", "are"), ("strom", "river"), ("und", "and"), ("vom", "from"))
miniDictionary: scala.collection.immutable.Map[java.lang.String,java.lang.String] = Map(und -> and, eise -> ice, sind -> are, befreit -> liberated, strom -> river, vom -> from, baeche -> brooks)

我们可以使用该词典提供德语句子“ vom eise befreit sind strom und baeche ”的翻译(非常糟糕):我们只需拆分德语句子,然后映射其元素,在词典中查找每个单词。

scala> val example = "vom eise befreit sind strom und baeche"
example: java.lang.String = vom eise befreit sind strom und baeche

scala> example.split(" ").map(deuWord => miniDictionary(deuWord)).mkString(" ")
res0: String = from ice liberated are river and brooks

好的,不是完全“从冰,溪流和溪水中解脱出来”,但是再一次,这几乎是最愚蠢的机器翻译方法……

当然,这样做的危险是我们将拥有词典中未包含的单词,从而导致异常。

scala> val example2 = "vom eise befreit sind strom und schiffe"
example2: java.lang.String = vom eise befreit sind strom und schiffe

scala> example2.split(" ").map(deuWord => miniDictionary(deuWord)).mkString(" ")
java.util.NoSuchElementException: key not found: schiffe

我们将在下面返回到此。

使用groupBy从列表创建地图

我们经常将数据存储在特定的数据结构中,并希望使用另一种以其他方式组织数据点的数据结构来使用它。 在这里,我们将研究如何使用groupBy方法将List转换为Map,以便进行一些有用的处理来处理词性。 我们还将看到Set数据结构。

我们将从groupBy所做的非常基本的示例开始。 给定一个数字标记列表,我们可以获得从数字类型到每个数字的所有标记的映射。

scala> val numbers = List(1,4,5,1,6,5,2,8,1,9,2,1)
numbers: List[Int] = List(1, 4, 5, 1, 6, 5, 2, 8, 1, 9, 2, 1)

scala> numbers.groupBy(x=>x)
res19: scala.collection.immutable.Map[Int,List[Int]] = Map(5 -> List(5, 5), 1 -> List(1, 1, 1, 1), 6 -> List(6), 9 -> List(9), 2 -> List(2, 2), 8 -> List(8), 4 -> List(4))

从结果中可以看到, groupBy使用了匿名函数x => x ,将List中所有具有相同x值的元素分组,然后从每个x到包含其标记的组中创建了Map。 因此,我们得到2个映射到包含2?s的列表的映射,依此类推。 这似乎有点怪异,但是当我们考虑其中包含更多有趣元素的列表时,它非常有用。 为此,让我们回到这些教程的第4部分中的词性标记示例。 假设我们有一个用词性标记的句子,例如下面的(组成)示例,可确保某些标记的歧义。

在黑暗中,一个高个子的人看见了他需要砍伐黑暗树的锯子。

词性可以用以下方式注释(有很多简化,并对对任何人的语言敏感性造成的任何冒犯表示歉意)。

in / Prep the / Det dark / Noun,/ Punc a / Det high /形容词人/名词锯/动词/ Det锯/名词/代词他/代词需要/动词到/ Prep人/动词到/ Prep剪切/动词the / Det dark /形容词树/名词./Punc

有关以下表达式如何将这样的字符串转换为元组列表的详细说明, 请参见第4部分

scala> val tagged = "in/Prep the/Det dark/Noun ,/Punc a/Det tall/Adjective man/Noun saw/Verb the/Det saw/Noun that/Pronoun he/Pronoun needed/Verb to/Prep man/Verb to/Prep cut/Verb the/Det dark/Adjective tree/Noun ./Punc".split(" ").toList.map(x => x.split("/")).map(x => (x(0), x(1)))
tagged: List[(java.lang.String, java.lang.String)] = List((in,Prep), (the,Det), (dark,Noun), (,,Punc), (a,Det), (tall,Adjective), (man,Noun), (saw,Verb), (the,Det), (saw,Noun), (that,Pronoun), (he,Pronoun), (needed,Verb), (to,Prep), (man,Verb), (to,Prep), (cut,Verb), (the,Det), (dark,Adjective), (tree,Noun), (.,Punc))

现在,让我们以各种方式使用groupBy 。 我们可能感兴趣的第一件事是查看每个单词与哪个词性相关联。

scala> val groupedTagged = tagged.groupBy(x => x._1)
groupedTagged: scala.collection.immutable.Map[java.lang.String,List[(java.lang.String, java.lang.String)]] = Map(in -> List((in,Prep)), needed -> List((needed,Verb)), . -> List((.,Punc)), cut -> List((cut,Verb)), saw -> List((saw,Verb), (saw,Noun)), a -> List((a,Det)), man -> List((man,Noun), (man,Verb)), that -> List((that,Pronoun)), dark -> List((dark,Noun), (dark,Adjective)), to -> List((to,Prep), (to,Prep)), , -> List((,,Punc)), tall -> List((tall,Adjective)), he -> List((he,Pronoun)), tree -> List((tree,Noun)), the -> List((the,Det), (the,Det), (the,Det)))

因此,现在您看到由groupBy构造的Map中的键是单词,值是原始元素的组。 然后,可以看到,匿名函数x => x._1提供给GROUPBY做两两件事:它指定输入元素意愿组不同的项目的部分一起,则表示该输入的那部分限定的密钥空间。

但是,我们并没有想要的东西,即与每个单词相关的词性集合。 相反,我们有一个元组列表,例如:

scala> groupedTagged("saw")
res21: List[(java.lang.String, java.lang.String)] = List((saw,Verb), (saw,Noun))

暂时仅关注这一点,我们可以将其映射并生成仅包含词性的List,然后使用toSet方法将该List转换为Set以获得唯一的词性。

scala> groupedTagged("saw").map(x=>x._2)
res24: List[java.lang.String] = List(Verb, Noun)

scala> groupedTagged("saw").map(x=>x._2).toSet
res25: scala.collection.immutable.Set[java.lang.String] = Set(Verb, Noun)

将List转换为Set并没有多大作用,但请考虑使用 ,它具有多个具有相同词性的标记。

scala> groupedTagged("the")
res26: List[(java.lang.String, java.lang.String)] = List((the,Det), (the,Det), (the,Det))

scala> groupedTagged("the").map(x=>x._2)
res27: List[java.lang.String] = List(Det, Det, Det)

scala> groupedTagged("the").map(x=>x._2).toSet
res28: scala.collection.immutable.Set[java.lang.String] = Set(Det)

集是与地图和列表一起使用的另一个有用的数据结构。 它们的工作方式就像您希望Sets那样:它们包含一组唯一的无序元素,并且它们使您可以查看元素是否在集合中,一个集合是否是另一个集合的子集,对它们的元素进行迭代等。

现在,回到从单词/标签对到从单词到每个单词可能的标签的映射的过程。 我们想要的是从tags.groupBy(x => x._1)获得的键,但是我们想将单词/标签标记列表中的值转换为标签集,我们可以使用Maps上的mapValues方法进行操作。

scala> val wordsToTags = tagged.groupBy(x => x._1).mapValues(listOfWordTagPairs => listOfWordTagPairs.map(wordTagPair => wordTagPair._2).toSet)
wordsToTags: scala.collection.immutable.Map[java.lang.String,scala.collection.immutable.Set[java.lang.String]] = Map(in -> Set(Prep), needed -> Set(Verb), . -> Set(Punc), cut -> Set(Verb), saw -> Set(Verb, Noun), a -> Set(Det), man -> Set(Noun, Verb), that -> Set(Pronoun), dark -> Set(Noun, Adjective), to -> Set(Prep), , -> Set(Punc), tall -> Set(Adjective), he -> Set(Pronoun), tree -> Set(Noun), the -> Set(Det))

mapValues(...)部分中的内容将使一些读者sc之 ,但是您只需要看一下上面我们获得res28的那一行即可 :如果您了解这一点,那么您只需要意识到我们在做完全一样的事情即可。事情,但是现在是在值上映射而不是处理单个值。 现在,您知道如何映射要映射的值。

现在已经可以使用了,我们可以轻松地查询wordsToTags Map来查看各种单词是否具有各种标签。

scala> wordsToTags("man")("Noun")
res8: Boolean = true

scala> wordsToTags("man")("Det")
res9: Boolean = false

scala> wordsToTags("man")("Verb")
res10: Boolean = true

scala> wordsToTags("saw")("Verb")
res11: Boolean = true

这是数据结构内的数据结构(此处为Map中的Set)如何非常有用的示例。 ( 练习 :考虑一下一棵树是什么,以及如何使用列表实现它。)

利用Maps,您可以在计算语言学中做很多事情,从单词到词性。 一个简单的示例是计算每种单词类型的标签平均数量。

scala> val avgTagsPerType = wordsToTags.values.map(x=>x.size).sum/wordsToTags.size.toDouble
avgTagsPerType: Double = 1.2

如果您不清楚此处发生的情况,请在您自己的REPL中将其拆开!

我们可以用另一种方式来转换单词/标记对,以找出每个词性中包含哪些单词。 我们唯一需要做的是在每个对的第二个元素上使用groupBy ,然后将List值映射到它们的第一个元素并从中获取Set。

scala> val tagsToWords = tagged.groupBy(x => x._2).mapValues(listOfWordTagPairs => listOfWordTagPairs.map(wordTagPair => wordTagPair._1).toSet)
tagsToWords: scala.collection.immutable.Map[java.lang.String,scala.collection.immutable.Set[java.lang.String]] = Map(Prep -> Set(in, to), Det -> Set(the, a), Noun -> Set(dark, man, saw, tree), Pronoun -> Set(that, he), Verb -> Set(saw, needed, man, cut), Punc -> Set(,, .), Adjective -> Set(tall, dark))

这种基本范例是一种强大的范例,可以根据我们的需求在不同的数据结构之间进行切换。 它还展示了使用列表,地图和集合的几个重要概念。 下一部分显示了此想法的简单应用,用于计算文本中的单词。

数词

计算语言学的一项常见任务是计算单词统计信息,其中最基本的是计算特定文本中每种单词类型的记号的数量。 存储和访问这些计数的最常见方法是在地图中,但是如何从给定的文本创建这样的地图? 如果我们将文本视为字符串列表,则上面我们执行的groupBy范式将为我们提供所需的确切信息-实际上,它甚至比上面的单词/标签操作更简单。

我们将使用的示例文本是关于土拨鼠的绕口令。

scala> val woodchuck = "how much wood could a woodchuck chuck if a woodchuck could chuck wood ? as much wood as a woodchuck would , if a woodchuck could chuck wood ."
woodchuck: java.lang.String = how much wood could a woodchuck chuck if a woodchuck could chuck wood ? as much wood as a woodchuck would , if a woodchuck could chuck wood .

鉴于此,这是我们如何计算每种单词类型的出现次数。 首先,我们对元素进行分组 。 尽管字符串列表不像使用单词和标签那样具有包含元组的列表那么有趣,但是它仍然会产生有用的结果:我们现在有了一组唯一的键,它们对应于Array中元素的类型,每个值都有一个对应的值,即该类型的令牌数组。

scala> woodchuck.split(" ").groupBy(x=>x)
res29: scala.collection.immutable.Map[java.lang.String,Array[java.lang.String]] = Map(woodchuck -> Array(woodchuck, woodchuck, woodchuck, woodchuck), chuck -> Array(chuck, chuck, chuck), . -> Array(.), would -> Array(would), if -> Array(if, if), a -> Array(a, a, a, a), as -> Array(as, as), , -> Array(,), how -> Array(how), much -> Array(much, much), wood -> Array(wood, wood, wood, wood), ? -> Array(?), could -> Array(could, could, could))

而且,我们想要做的事情比使用词性示例简单得多:我们只需要计算每个列表的长度,因为它们每个包含对应单词类型的每个标记。 因此,传递给mapValues的函数比上一节中给出的函数要简单得多。

scala> val counts = woodchuck.split(" ").groupBy(x=>x).mapValues(x=>x.length)
counts: scala.collection.immutable.Map[java.lang.String,Int] = Map(woodchuck -> 4, chuck -> 3, . -> 1, would -> 1, if -> 2, a -> 4, as -> 2, , -> 1, how -> 1, much -> 2, wood -> 4, ? -> 1, could -> 3)

使用counts ,我们现在可以访问文本中任何单词的频率。

scala> counts("woodchuck")
res5: Int = 4

scala> counts("could")
res6: Int = 3

简单! 当然,我们通常希望为更长的文本建立单词计数,并将其存储在文件中,而不是显式添加到Scala代码中。 下一个教程将演示如何做到这一点。

遍历Map中的键和值

上面的材料展示了Maps的一些有用方面,但是您当然可以使用它们做更多的事情,通常需要遍历Map中的键值对。 我们将使用上面创建的计数 Map进行演示。

您只能访问键或值。

scala> counts.keys
res0: Iterable[java.lang.String] = Set(woodchuck, chuck, ., would, if, a, as, ,, how, much, wood, ?, could)

scala> counts.values
res1: Iterable[Int] = MapLike(4, 3, 1, 1, 2, 4, 2, 1, 1, 2, 4, 1, 3)

请注意,这些都是Iterable数据结构,因此我们可以使用列表完成所有常用的映射,过滤等操作。 (当然,如果您喜欢使用toList ,则可以将它们转换为Lists。)

您可以通过多种方式在Map中打印所有键->值对。 一种是使用for进行表达。

scala> for ((k,v) <- counts) println(k + " -> " + v)
woodchuck -> 4
chuck -> 3
. -> 1
would -> 1
if -> 2
a -> 4
as -> 2
, -> 1
how -> 1
much -> 2
wood -> 4
? -> 1
could -> 3

这是获得相同结果的其他方法(输出相同,因此省略输出)。

for (k <- counts.keys) println(k + " -> " + counts(k))
counts.map(kvPair => kvPair._1 + " -> " + kvPair._2).foreach(println)
counts.keys.map(k => k + " -> " + counts(k)).foreach(println)
counts.foreach { case(k,v) => println(k + " -> " + v) }
counts.foreach(kvPair => println(kvPair._1 + " -> " + kvPair._2))

等等。 基本上,您可以一次浏览“映射一个键-值”对,或者可以获取一组键,然后逐步浏览并从映射中访问值。 使用哪种形式取决于您的需要-例如, foreach构造不返回值,但是for表达式和map表达式确实返回值。 为什么要这么做? 好吧,作为一个例子,考虑对所有出现相同次数的单词进行分组。

scala> val countsToWords = counts.keys.toList.map(k => (counts(k),k)).groupBy(x=>x._1).mapValues(x=>x.map(y=>y._2))
countsToWords: scala.collection.immutable.Map[Int,List[java.lang.String]] = Map(3 -> List(chuck, could), 4 -> List(woodchuck, a, wood), 1 -> List(., would, ,, how, ?), 2 -> List(if, as, much))

我们从Map到其键的集合,再到那些键的列表,再到值的元组列表,再到从原始Map的值到此类Tuple的Map的键,然后我们将Map的值映射新地图只包含单词(原始键)。 (这是一个大问题,因此请尝试REPL中的每个步骤,以了解发生的详细情况。)

现在,我们可以输出countsToWords,该countsToWords按计数值降序排列,然后在每个计数中按单词按字母顺序排列。

scala> countsToWords.keys.toList.sorted.reverse.foreach(x => println(x + ": " + countsToWords(x).sorted.mkString(",")))
4: a,wood,woodchuck
3: chuck,could
2: as,if,much
1: ,,.,?,how,would

选项和flatMapping用于处理丢失的密钥

我指出,在本教程开始时,如果我们要求地图中不存在的密钥,就会遇到麻烦。 让我们回到开始的engToDeu Map。

scala> val engToDeu = Map(("dog","Hund"), ("cat","Katze"), ("rhinoceros","Nashorn"))
engToDeu: scala.collection.immutable.Map[java.lang.String,java.lang.String] = Map(dog -> Hund, cat -> Katze, rhinoceros -> Nashorn)

scala> engToDeu("dog")
res0: java.lang.String = Hund

scala> engToDeu("bird")
java.util.NoSuchElementException: key not found: bird

还有一种使用get方法访问Map元素的方法。

scala> engToDeu.get("dog")
res2: Option[java.lang.String] = Some(Hund)

scala> engToDeu.get("bird")
res3: Option[java.lang.String] = None

现在,返回值是Option [String]Option是包含值的SomeNone ,表示没有值。 如果要从Some中获取值,请在Options上使用get方法。

scala> val dogTrans = engToDeu.get("dog")
dogTrans: Option[java.lang.String] = Some(Hund)

scala> dogTrans.get
res4: java.lang.String = Hund

如果您仅使用Map上的get获取选项,然后立即在Option上调用get ,我们将获得与以前相同的行为。

scala> engToDeu.get("dog").get
res6: java.lang.String = Hund

scala> engToDeu.get("bird").get
java.util.NoSuchElementException: None.get

因此,在这一点上,您可能会认为这听起来像是在浪费时间,只会使事情变得更复杂。 等待! 实际上,由于模式匹配以及序列中许多方法的工作方式,它非常有用。

首先,这里是如何编写一种保护形式的列表中单词的翻译而不会出现异常的情况。

scala> wordsToTranslate.foreach { x => engToDeu.get(x) match {
|   case Some(y) => println(x + " -> " + y)
|   case None =>
| }}
dog -> Hund
cat -> Katze

我知道……这可能仍然无法令人信服-它看起来比我们上面(远)用来检查engToDeu是否包含给定键的条件(至少在此特定示例中)更复杂 。 坚持……因为现在我们已经准备好使事情变得更简单,并在此过程中学习有关List的一些有用的知识。

首先,您应该了解List上一个很棒的方法,称为flatten 。 如果您具有字符串列表列表,则可以使用flatten获取单个字符串列表。 考虑下面的示例,在该示例中,我们将字符串列表的列表展平,并使用mkString从结果中生成一个字符串。 请注意,当我们将其展平时,主列表第三点的空列表会消失。

scala> val sentences = List(List("Here","is","sentence","one","."), List("The","third","sentence","is","empty","!"), List(),List("Lastly",",","we","have","a","final","sentence","."))
sentences: List[List[java.lang.String]] = List(List(Here, is, sentence, one, .), List(The, third, sentence, is, empty, !), List(), List(Lastly, ,, we, have, a, final, sentence, .))

scala> sentences.flatten
res0: List[java.lang.String] = List(Here, is, sentence, one, ., The, third, sentence, is, empty, !, Lastly, ,, we, have, a, final, sentence, .)

scala> sentences.flatten.mkString(" ")
res1: String = Here is sentence one . The third sentence is empty ! Lastly , we have a final sentence .

通常,展平本身就非常有用。 使用Option值时,可以将Option视为一个List:有些像一个元素的List,而None像空的List。 因此,当您有一个“选项列表”时,flatten方法将为您提供“只值不包括”的值。

scala> wordsToTranslate.map(x => engToDeu.get(x))
res12: List[Option[java.lang.String]] = List(Some(Hund), None, Some(Katze), None)

scala> wordsToTranslate.map(x => engToDeu.get(x)).flatten
res13: List[java.lang.String] = List(Hund, Katze)

这是一个非常有用的范例,因此有一个函数flatMap可以做到这一点。

scala> wordsToTranslate.flatMap(x => engToDeu.get(x))
res14: List[java.lang.String] = List(Hund, Katze)

因此,回到上面的翻译示例,我们现在可以安全地跳过“ schiffe ”,而不必大惊小怪。

scala> example2.split(" ").flatMap(deuWord => miniDictionary.get(deuWord)).mkString(" ")
res15: String = from ice liberated are river and

在特定情况下这是否是所需的行为是另一个问题(例如,您确实应该进行一些特殊的未知字处理)。 尽管如此,您会发现flatMap通常对于这种模式非常方便,在这种模式中,使用元素列表从Map中检索会丢失某些值的值。

Options和flatMap进一步使用的一个示例是,您还可以创建返回Option的函数,从而可以使用flatMapping。 考虑一个仅将奇数平方并扔掉偶数的函数( 请注意 :%运算符是求模运算符,可求出一个数除以另一数的余数-在REPL中尝试)。

scala> def squareOddNumber (x: Int) = if (x % 2 != 0) Some(x*x) else None
squareOddNumber: (x: Int)Option[Int]

如果将数字映射到1到10,则将看到“和”和“无”,如果对它进行flatMap ,则可以得到所有奇数平方的期望结果,而偶数没有任何污染。

scala> (1 to 10).toList.map(x=>squareOddNumber(x))
res16: List[Option[Int]] = List(Some(1), None, Some(9), None, Some(25), None, Some(49), None, Some(81), None)

scala> (1 to 10).toList.flatMap(x=>squareOddNumber(x))
res17: List[Int] = List(1, 9, 25, 49, 81)

事实证明,这是非常有用且通用的,以至于“ 只不过是flatMap而已 ”这样的表达已成为Scala程序员中的普遍习惯。 Scala程序员甚至编写脚本来提醒他们这样做。 :)

参考: 对于初学者来说,Scala的第一步,来自Bcomposes博客的JCG合作伙伴 Jason Baldridge的 第7部分

相关文章 :


翻译自: https://www.javacodegeeks.com/2011/10/scala-tutorial-maps-sets-groupby.html

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值