Scala:如何在Map中使用fold*?

12

我有一个Map[String, String],想将值连接成一个字符串。

我可以看到如何使用List来实现这一点...

scala> val l = List("te", "st", "ing", "123")
l: List[java.lang.String] = List(te, st, ing, 123)

scala> l.reduceLeft[String](_+_)
res8: String = testing123

使用fold*或reduce*似乎是正确的方法,但我无法为Map获取正确的语法。

2个回答

35

在地图上进行折叠的方式与对一系列配对元素进行折叠的方式相同。您不能使用reduce,因为结果类型必须与元素类型相同(即一个对),但您需要一个字符串。因此,您可以使用foldLeft,其中空字符串作为中性元素。您也不能仅使用_+_,因为那样会尝试将一个对添加到字符串中。相反,您必须使用一个函数,该函数添加累积的字符串、对的第一个值和对的第二个值。因此您会得到如下代码:

scala> val m = Map("la" -> "la", "foo" -> "bar")                 
m: scala.collection.immutable.Map[java.lang.String,java.lang.String] = Map(la -> la, foo -> bar)

scala> m.foldLeft("")( (acc, kv) => acc + kv._1 + kv._2)
res14: java.lang.String = lalafoobar

对于fold的第一个参数的解释:

如你所知,函数(acc, kv) => acc + kv._1 + kv._2有两个参数:第二个参数是当前正在处理的键值对,第一个参数是迄今为止累计的结果。但是,在处理第一对(且尚未累积任何结果)时,acc的值是多少呢?当你使用reduce时,acc的第一个值将是列表中的第一个对(kv的第一个值将是列表中的第二个对)。然而,如果想要结果类型与元素类型不同,则此方法不适用。因此,我们使用fold而不是reduce,在foldLeft中传递acc的第一个值作为第一个参数。

简而言之:foldLeft的第一个参数指定了acc的起始值。

正如Tom指出的那样,需要记住Map不一定保持插入顺序(Map2等可以,但哈希表不行),因此该字符串可能以与插入顺序不同的顺序列出元素。


非常酷...它运行得很好。我错过了元组语法。你能帮我理解 foldLeft 后面的("")吗?对于这个新手问题感到抱歉。 - Vonn
("")是折叠的初始化值。因此,通过对空结构进行折叠,将返回此值。 - Tom Crockett
2
你需要注意的一件事是,映射并没有必然的定义顺序,因此将其折叠成字符串会强加一个可能是任意的顺序。这只是一个需要注意的警告。 - Tom Crockett
谢谢你提醒我关于顺序的问题。我之前不确定,本来想使用SortedMap。 - Vonn
如果您使用SortedMap,那就没有问题! - Tom Crockett

8
这个问题已经有了答案,但我想指出如果你只是想生成那些字符串,有更简单的方法。像这样:
scala> val l = List("te", "st", "ing", "123")
l: List[java.lang.String] = List(te, st, ing, 123)

scala> l.mkString
res0: String = testing123

scala> val m = Map(1 -> "abc", 2 -> "def", 3 -> "ghi")
m: scala.collection.immutable.Map[Int,java.lang.String] = Map((1,abc), (2,def), (3,ghi))

scala> m.values.mkString
res1: String = abcdefghi

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接