使用 Map 而不是 List 理解 foldLeft

Understanding foldLeft with Map instead of List

我很想了解 foldLeft 地图的工作原理。如果我有一个 List 并使用零元素和函数对其调用 foldLeft,我确实理解它是如何工作的:

val list1 = List(1,2,3)
list1.foldLeft(0)((a,b) => a + b)

我将零元素 0 添加到 list1 的第一个元素,然后添加 list1 的第二个元素,依此类推。所以输出成为新的输入,第一个输入是零元素。

现在我得到了代码

val map1 = Map(1 -> 2.0, 3 -> 4.0, 5 -> 6.2) withDefaultValue 0.0
val map2 = Map(0 -> 3.0, 3 -> 7.0) withDefaultValue 0.0
def myfct(terms: Map[Int, Double], term: (Int, Double)): Map[Int, Double] = ???

map1.foldLeft(map2)(myfct)
  1. 所以我这里的第一个元素是 Tuple2,但是由于 map2Map 而不是 Tuple2,零元素是什么?
  2. 当我们有一个List,即list1,我们总是"took the next element in list1"。 map1中的“下一个元素是什么?是另一对map1吗?

在这种情况下,您可以将 Map 视为元组列表。您可以像这样创建一个列表:List(1 -> 2.0, 3 -> 4.0, 5 -> 6.2),然后在其上调用 foldLeft(这或多或少正是 Map.foldLeft 所做的)。如果您了解 foldLeft 如何处理列表,那么现在您也知道它如何处理地图 :) 回答您的具体问题:

  1. foldLeft的第一个参数可以是任意类型。您也可以在第一个示例中传入 Map 而不是 Int 。它不必与您正在处理的集合的元素具有相同的类型(尽管可能是),就像您在第一个示例中那样,也不需要与集合本身具有相同的类型,就像您在最后一个例子中有它。 举个例子:

     List(1,2,3,4,5,6).foldLeft(Map.empty[String,Int]) { case(map,elem) => 
       map + (elem.toString -> elem) 
     }
    

这会产生与 list.map { x => x.toString -> x }.toMap 相同的结果。如您所见,这里的第一个参数是 Map,既不是 List 也不是 Int.

你传给foldLeft的类型也是它returns的类型,你传入的函数也是returns的类型。它不是“元素零”。 foldLeft 将该参数连同列表的第一个元素传递给您的 reducer 函数。您的函数将组合这两个元素,并生成与第一个参数类型相同的新值。该值再次传入,再次使用第二个元素......等等。 也许,检查 foldLeft 的签名会有所帮助:

   foldLeft[B](z: B)(op: (B, A) ⇒ B): B

这里A是你的集合元素的类型,B可以是任何东西,唯一的要求是它出现的四个地方是相同的类型。

这是另一个例子,(几乎)等同于 list.mkString(","):

   List(1,2,3,4,5,6).foldLeft("") { 
     case("", i) => i.toString
     case(s,i) => s + "," + i
   }
  1. 正如我在开头所解释的那样,此上下文中的映射是一种列表(而不是序列)。就像处理列表时“我们总是取列表的下一个元素”一样,在这种情况下我们将取“地图的下一个元素”。你自己说了,map的元素是元组,所以下一个元素的类型就是:

     Map("one" -> 1, "two" -> 2, "three" -> 3)
      .foldLeft("") { 
        case("", (key,value)) => key + "->" + value
        case(s, (key,value)) => s + ", " + key + "->" + value
      }
    

如上所述,foldLeft操作的签名如图:

foldLeft[B](z: B)(op: (B, A) ⇒ B): B

操作的结果类型是B。所以,这回答了你的第一个问题:

So my first element here is a Tuple2, but since map2 is a Map and not a Tuple2, what is the zero-element?

零元素是你想从foldLeft输出的任何东西。它可以是 IntMap 或任何其他与此相关的内容。

函数签名(即第二个参数)中的

op 是您希望对 map1 的每个元素进行操作的方式,它是一个 (key,value) 对,或者另一种方式写成 key -> value.

让我们尝试通过更简单的操作构建它来理解它。

val map1 = Map(1 -> 1.0, 4 -> 4.0, 5 -> 5.0) withDefaultValue 0.0
val map2 = Map(0 -> 0.0, 3 -> 3.0) withDefaultValue 0.0

// Here we're just making the output an Int
// So we just add the first element of the key-value pair.
def myOpInt(z: Int, term: (Int, Double)): Int = {
  z + term._1
}

// adds all the first elements of the key-value pairs of map1 together
map1.foldLeft(0)(myOpInt)  // ... output is 10
// same as above, but for map2 ...
map2.foldLeft(0)(myOpInt) // ... output is 3

现在,通过使用零元素 (z) 作为现有映射将其带入下一步...我们基本上将元素添加到我们用作 z 的映射中。

val map1 = Map(1 -> 1.0, 4 -> 4.0, 5 -> 5.0) withDefaultValue 0.0
val map2 = Map(0 -> 0.0, 3 -> 3.0) withDefaultValue 0.0

// Here z is a Map of Int -> Double
// We expect a pair of (Int, Double) as the terms to fold left with z
def myfct(z: Map[Int, Double], term: (Int, Double)): Map[Int, Double] = {
  z + term
}

map1.foldLeft(map2)(myfct) 
// output is Map(0 -> 0.0, 5 -> 5.0, 1 -> 1.0, 3 -> 3.0, 4 -> 4.0)
// i.e. same elements of both maps concatenated together, albeit out of order, since Maps don't guarantee order ...

When we had a List, namely list1, we always "took the next element in list1". What is the "next element in map1? Is it another pair of map1?

是的,这是 map1 中的另一个键值对 (k,v)