Scala-HashMap**非foldLeft上的折叠操作示例

Scala-HashMap**非foldLeft上的折叠操作示例,scala,hashmap,reduce,fold,Scala,Hashmap,Reduce,Fold,我对使用Scala感兴趣,因为它似乎是并行化操作的好方法。我需要设计一个利用向量乘法的机器学习算法(和许多算法一样)。我知道如何实现该算法,但我想从HashMaps中实现稀疏向量。几乎所有向量都存储为HashMaps[Int,Double],其中向量中给定Double的索引是作为键的整数 使用Pythonish伪代码, =>{1:7,2:6,3:5,4:4} 我想定义一个点积函数使用折叠,减少,映射。。。等等,但我不想用折叠式,还原式。。。因为我希望这是潜在的平行化,因为我的向量可以达到6000

我对使用Scala感兴趣,因为它似乎是并行化操作的好方法。我需要设计一个利用向量乘法的机器学习算法(和许多算法一样)。我知道如何实现该算法,但我想从HashMaps中实现稀疏向量。几乎所有向量都存储为HashMaps[Int,Double],其中向量中给定Double的索引是作为键的整数

使用Pythonish伪代码, =>{1:7,2:6,3:5,4:4}

我想定义一个点积函数使用折叠,减少,映射。。。等等,但我不想用折叠式,还原式。。。因为我希望这是潜在的平行化,因为我的向量可以达到6000+维,对于点积,顺序并不重要

我已经阅读了许多foldLeft和reduceLeft的示例,但是我还没有找到如何使用HashMap.fold或HashMap.reduce

我相当了解函数式编程,但不理解Scala中的错误消息。这是我或多或少想要的模板

object NGramAnalysis {
  def main(args: Array[String]) {
    val mapped = HashMap(1->1.2, 5->2.4)
    println(mapped.fold( .... What goes here ... )
  }
}
结论 我想要一个使用HashMap.fold而不是foldLeft和HashMap.reduce的实例


先谢谢你。我已经挣扎了一段时间。

首先,
fold
reduce
之间的区别在于
fold
采用了一个附加参数作为初始值,而
reduce
采用集合中的第一个元素作为初始值,如果集合为空,则抛出异常。因此,
fold
reduce
更一般,因此从现在起,我将这两个函数都称为
fold

要使
fold
正常工作,集合中的元素必须形成半群,也就是说,应该有一个二进制操作,它也必须是关联的,也就是说,以下标识应该保持:
(a`op`b)`op`c==a`op`(b`op`c)
。需要关联性,因为
fold
没有指定操作应用程序顺序,这在并行上下文中尤其重要。此操作用于执行折叠:

a1 `op` a2 `op` a3 `op` ... `op` an
如果并行运行
reduce
,它可以拆分集合,并在一个线程中减少前一半,在另一个线程中减少后一半;然后使用相同的操作将它们的结果连接起来。仅当操作是关联的时,此操作才能正常工作

正如我已经说过的,
fold
方法接受两个参数:初始值和[关联]二进制运算符。例如,要并行连接字符串列表,可以执行以下操作:

val strings = Seq("a", "b", "c", "d", ...)
strings.par.fold("")(_ ++ _)  // or strings.par.reduce(_ ++ _) if you know that strings is non-empty
所以,要实现点积,您需要考虑要折叠/缩减的集合和执行此缩减的二进制运算符

这是两个集合的点积的简单实现:

(c1 zip c2).par.map {
  case (e1, e2) => e1 * e2
}.reduce(_ + _)
也就是说,我们使用
*
操作符将这些集合压缩在一起,成对地乘以它们的元素,然后使用
+
操作符减少结果。当然,
*
+
必须在
c1
c2
的元素上定义

但是,
HashMap
没有顺序,因此其迭代顺序未定义。无法保证
zip
将使用相同的键连接元素,这使得上述dot-product概念不正确。您需要这样做:

c1.par.map {
  case (k, v) => v * c2(k)
}.reduce(_ + _)

这里我们不是压缩集合,而是使用第一个映射中的所有键在第二个映射中执行查找。

我只想添加一个简单的示例实现,因为@Vladimir Matveev覆盖了背景

在这里,向量在一个
HashMap
上。应用工厂方法确保所有未指定的索引都有一个默认值
0

这个想法很简单。我们合并键集,以便在任一映射中指定所有键。然后我们乘以相应的值并求和。因为我们对不存在的键有一个默认值,所以它可以正常工作

class SparseVector private (val entries: Map[Int, Double]) {

  def **(vec: SparseVector) = 
    (entries.keySet ++ vec.entries.keySet).par
      .map(index => entries(index) * vec.entries(index)).sum

  //alternative suggested by @wingedsubmariner
  def **(vec: SparseVector) = 
    (entries.keySet ++ vec.entries.keySet).par
     .aggregate(0.0)((sum, index) => sum + entries(index) * vec.entries(index), (_ + _))
}

object SparseVector {
  def apply(entries: HashMap[Int, Double]) =
    new SparseVector(entries.withDefaultValue(0.0))
}

方法
map
sum
aggregate
都有一个并行实现。

.aggregate(0)((sum,index)=>sum+entries(index)*vec.entries(index))(+)
可能更有效一些,因为它可以组合
map
sum
操作。有趣的是。我从未使用过这个函数。它也让我感到困惑,因为它是在
TraverseableOnce
中实现的,就像这样:
def aggregate[B](z:=>B)(seqop:(B,A)=>B,combop:(B,B)=>B:B=foldLeft(z)(seqop)
。甚至没有使用参数
combop
,它只是调用
foldLeft
。我想它是设计用来并行的。是的,
aggregate()
是正确的。在我写答案的时候,我想到了最近的Java 8可变缩减,我记得Scala中有Java
Stream.reduce()
方法的精确模拟,但我忘记了它的确切名称<代码>聚合()实际上是;如果需要并行化,它真的应该用于此任务。感谢您这么快的响应。这里还有一些代码,以防有人也试图弄明白这些问题。val mm=HashMap(1->1.2,2->3.3,5->4.0);默认值为(0.0)的毫米;val v=mm.map(i=>(i._1,mm(i._1)*5));val vv=v.fold((10,10.0))((a,b)=>(a._1+b._1,a._2+b._2));println(v);println(vv);