我正在编写一个小型练习应用,用于计算seq
strings
中唯一字母(包括Unicode)的数量,我正在使用aggregate
作为我尝试并行运行
这是我的代码:
class Frequency(seq: Seq[String]) {
type FreqMap = Map[Char, Int]
def calculate() = {
val freqMap: FreqMap = Map[Char, Int]()
val pattern = "(\\p{L}+)".r
val seqop: (FreqMap, String) => FreqMap = (fm, s) => {
s.toLowerCase().foldLeft(freqMap){(fm, c) =>
c match {
case pattern(char) => fm.get(char) match {
case None => fm+((char, 1))
case Some(i) => fm.updated(char, i+1)
}
case _ => fm
}
}
}
val reduce: (FreqMap, FreqMap) => FreqMap =
(m1, m2) => {
m1 ++ m2.map { case (k, v) => k -> (v + m1.getOrElse(k, 0)) }
}
seq.par.aggregate(freqMap)(seqop, reduce)
}
}
然后是使用该
的代码object Frequency extends App {
val text = List("abc", "abc", "abc", "abc", "abc", "abc", "abc", "abc", "abc");
def frequency(seq: Seq[String]):Map[Char, Int] = {
new Frequency(seq).calculate()
}
Console println frequency(seq=text)
}
虽然我提供了9次“abc”,结果是Map(a -> 8, b -> 8, c -> 8)
,因为它适用于任意数量的"abc"
> 8
我看过this,好像我正在使用聚合
有什么建议让它发挥作用吗?
答案 0 :(得分:2)
您正在丢弃已经收集的结果(第一个fm
)在您的seqop中。您需要将这些添加到您正在计算的新结果中,例如像这样:
def calculate() = {
val freqMap: FreqMap = Map[Char, Int]()
val pattern = "(\\p{L}+)".r
val reduce: (FreqMap, FreqMap) => FreqMap =
(m1, m2) => {
m1 ++ m2.map { case (k, v) => k -> (v + m1.getOrElse(k, 0)) }
}
val seqop: (FreqMap, String) => FreqMap = (fm, s) => {
val res = s.toLowerCase().foldLeft(freqMap){(fm, c) =>
c match {
case pattern(char) => fm.get(char) match {
case None => fm+((char, 1))
case Some(i) => fm.updated(char, i+1)
}
case _ => fm
}
}
// I'm reusing your existing combinator function here:
reduce(res,fm)
}
seq.par.aggregate(freqMap)(seqop, reduce)
}
根据并行集合如何划分工作,您可以丢弃其中的一部分。在你的情况下(9x“abc”),它将事物划分为8个并行的seqop操作,这意味着你只丢弃一个结果集。这取决于数字,如果你运行说17x“abc”它运行在13个并行操作中,丢弃4个结果集(在我的机器上无论如何 - 我不熟悉底层代码以及它如何划分工作,这可能取决于使用的ExecutionContext / Threadpool以及随后的CPU /核心数等等)。
通常并行集合是顺序集合的替代品,这意味着如果删除.par
,您仍应获得相同的结果,尽管通常较慢。如果使用原始代码执行此操作,则会得到1的结果,这会告诉您它不是并行化问题。这是一种很好的方法,可以测试你在使用它们时是否正确。
最后但并非最不重要:因为你使用相同的变量名两次,然后是影子fm
,这对我来说比平常更难发现。不这样做会使代码更具可读性,并且更容易发现错误。