什么是Map / Reduce?

我听到很多关于map / reduce的信息,特别是在Google大规模并行计算系统的背景下。 究竟是什么?

摘自Google的MapReduce研究出版物页面:

MapReduce是用于处理和生成大型数据集的编程模型和相关实现。 用户指定一个处理键/值对的映射函数来生成一组中间键/值对,以及一个reduce函数,用于合并与相同中间键相关联的所有中间值。

MapReduce的优点是可以在多个处理节点(多个服务器)上并行执行处理,所以它是一个可以很好地扩展的系统。

由于它是基于函数式编程模型的,所以mapreduce步骤都没有任何副作用( map过程的每个子段的状态和结果都不依赖于另一个),所以映射和减less的数据集可以每个在多个处理节点上分开。

乔尔的可以你的编程语言做到这一点? 这篇文章讨论了如何理解函数式编程对Google来说至关重要的一点,那就是为其search引擎提供动力的MapReduce。 如果您不熟悉函数式编程以及它如何允许可伸缩的代码,那么这是一个很好的解读。

另见: 维基百科:MapReduce

相关问题: 请简单地解释mapreduce

Map是一个函数,它将另一个函数应用于列表上的所有项目,以产生具有所有返回值的列表。 (另一种说法是“将f应用于x”是“call f,把它传递给x”,所以有时用“apply”而不是“call”来说,听起来更好听)

这是如何地图可能用C#(它被称为Select ,并在标准库中):

 public static IEnumerable<R> Select<T, R>(this IEnumerable<T> list, Func<T, R> func) { foreach (T item in list) yield return func(item); } 

因为你是一个Java的家伙,而且Joel Spolsky喜欢把粗糙的Java告诉GROSSLY UNIFIR LIES(实际上,他不是在说谎,它很糟糕,但是我试图赢得你),这是我非常粗暴的尝试一个Java版本(我没有Java编译器,我隐约记得Java版本1.1!):

 // represents a function that takes one arg and returns a result public interface IFunctor { object invoke(object arg); } public static object[] map(object[] list, IFunctor func) { object[] returnValues = new object[list.length]; for (int n = 0; n < list.length; n++) returnValues[n] = func.invoke(list[n]); return returnValues; } 

我相信这可以通过百万种方法得到改善。 但这是基本的想法。

Reduce是一个将列表中的所有项目转换为单个值的函数。 要做到这一点,它需要被赋予另一个函数func ,将两个项目变成一个单一的值。 这将通过给func的前两个项目工作。 然后,结果与第三项一起。 然后是第四项的结果,直到所有的项目都消失了,剩下一个值。

在C#中,reduce称为Aggregate ,并且再次位于标准库中。 我将直接跳到Java版本:

 // represents a function that takes two args and returns a result public interface IBinaryFunctor { object invoke(object arg1, object arg2); } public static object reduce(object[] list, IBinaryFunctor func) { if (list.length == 0) return null; // or throw something? if (list.length == 1) return list[0]; // just return the only item object returnValue = func.invoke(list[0], list[1]); for (int n = 1; n < list.length; n++) returnValue = func.invoke(returnValue, list[n]); return returnValue; } 

这些Java版本需要generics添加到他们,但我不知道如何在Java中做到这一点。 但是你应该能够通过匿名内部类来提供函子:

 string[] names = getLotsOfNames(); string commaSeparatedNames = (string)reduce(names, new IBinaryFunctor { public object invoke(object arg1, object arg2) { return ((string)arg1) + ", " + ((string)arg2); } } 

希望generics将摆脱演员。 C#中的types安全等价物是:

 string commaSeparatedNames = names.Aggregate((a, b) => a + ", " + b); 

为什么这是“酷”? 简单的方法将较大的计算分解成较小的部分,这样他们可以以不同的方式放在一起,总是很酷。 Google应用这个想法的方式是并行化,因为map和reduce都可以在多台计算机上共享。

但关键的要求不是你的语言可以把function当作价值来对待。 任何OO语言都可以做到这一点。 并行化的实际要求是,你传递给映射和减less的小func函数不能使用或更新任何状态。 他们必须返回一个仅依赖于传递给它们的参数的值。 否则,当您尝试并行运行整个事件时,结果将被彻底搞乱。

MapReduce解释 。

这比我所能解释的更好。 它有帮助吗?

最让我感到沮丧的是无论是很长的waffley还是非常短暂的博客文章,我最终都发现了这个非常严谨的简洁文章 。

然后,我继续前进,通过翻译成Scala来简化它,在那里我提供了一个最简单的情况,用户只需指定mapreduce应用程序的部分。 在Hadoop / Spark中,严格来说,使用了一个更复杂的编程模型,要求用户明确指定4个更多的函数: http : //en.wikipedia.org/wiki/MapReduce#Dataflow

 import scalaz.syntax.id._ trait MapReduceModel { type MultiSet[T] = Iterable[T] // `map` must be a pure function def mapPhase[K1, K2, V1, V2](map: ((K1, V1)) => MultiSet[(K2, V2)]) (data: MultiSet[(K1, V1)]): MultiSet[(K2, V2)] = data.flatMap(map) def shufflePhase[K2, V2](mappedData: MultiSet[(K2, V2)]): Map[K2, MultiSet[V2]] = mappedData.groupBy(_._1).mapValues(_.map(_._2)) // `reduce` must be a monoid def reducePhase[K2, V2, V3](reduce: ((K2, MultiSet[V2])) => MultiSet[(K2, V3)]) (shuffledData: Map[K2, MultiSet[V2]]): MultiSet[V3] = shuffledData.flatMap(reduce).map(_._2) def mapReduce[K1, K2, V1, V2, V3](data: MultiSet[(K1, V1)]) (map: ((K1, V1)) => MultiSet[(K2, V2)]) (reduce: ((K2, MultiSet[V2])) => MultiSet[(K2, V3)]): MultiSet[V3] = mapPhase(map)(data) |> shufflePhase |> reducePhase(reduce) } // Kinda how MapReduce works in Hadoop and Spark except `.par` would ensure 1 element gets a process/thread on a cluster // Furthermore, the splitting here won't enforce any kind of balance and is quite unnecessary anyway as one would expect // it to already be splitted on HDFS - ie the filename would constitute K1 // The shuffle phase will also be parallelized, and use the same partition as the map phase. abstract class ParMapReduce(mapParNum: Int, reduceParNum: Int) extends MapReduceModel { def split[T](splitNum: Int)(data: MultiSet[T]): Set[MultiSet[T]] override def mapPhase[K1, K2, V1, V2](map: ((K1, V1)) => MultiSet[(K2, V2)]) (data: MultiSet[(K1, V1)]): MultiSet[(K2, V2)] = { val groupedByKey = data.groupBy(_._1).map(_._2) groupedByKey.flatMap(split(mapParNum / groupedByKey.size + 1)) .par.flatMap(_.map(map)).flatten.toList } override def reducePhase[K2, V2, V3](reduce: ((K2, MultiSet[V2])) => MultiSet[(K2, V3)]) (shuffledData: Map[K2, MultiSet[V2]]): MultiSet[V3] = shuffledData.map(g => split(reduceParNum / shuffledData.size + 1)(g._2).map((g._1, _))) .par.flatMap(_.map(reduce)) .flatten.map(_._2).toList } 

Map是一个本地JS方法,可以应用于一个数组。 它将映射到原始数组中的每个元素的某个函数的结果创build一个新的数组。 所以如果你映射一个函数(元素){返回元素* 2;},它会返回一个新的数组,每个元素加倍。 原始数组将不加修改。

https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/map

Reduce是一种本地JS方法,也可以应用于数组。 它将一个函数应用于一个数组,并具有一个称为累加器的初始输出值。 它遍历数组中的每个元素,应用一个函数,并将它们减less为一个单独的值(以累加器开始)。 这是有用的,因为你可以有任何你想要的输出,你只需要从那种types的累加器开始。 所以如果我想把东西变成一个对象,我会从一个累加器开始。

https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/Reduce?v=a