我听说过很多有关地图/归约的知识,尤其是在Google的大规模并行计算系统中。到底是什么
Answers:
从Google MapReduce研究出版物页面的摘要中:
MapReduce是用于处理和生成大型数据集的编程模型以及相关的实现。用户指定一个处理键/值对以生成一组中间键/值对的映射函数,以及一个reduce函数,该函数合并与同一中间键关联的所有中间值。
MapReduce的优点是可以在多个处理节点(多个服务器)上并行执行处理,因此它是一个可以很好扩展的系统。
由于和都是基于功能编程模型的,因此每个map
和reduce
步骤都没有任何副作用(map
流程的每个子部分的状态和结果都不依赖于另一个),因此可以分别映射和简化数据集在多个处理节点上。
乔尔的编程语言可以做到这一点吗?文章讨论了如何理解函数式编程对于Google提出MapReduce(为搜索引擎提供动力)至关重要。如果您不熟悉函数式编程及其如何支持可扩展代码,那么这是一本很好的读物。
另请参阅:维基百科:MapReduce
相关问题:请简单解释一下mapreduce
它比我能解释的更好。有帮助吗?
Map是一个函数,它将另一个函数应用于列表中的所有项目,以生成具有所有返回值的另一个列表。(另一种表示“将f应用于x”的方式是“调用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喜欢向GROSSLY UNFAIR LIES讲Java到底有多烂(实际上,他没有撒谎,这很烂,但是我想让您胜过),这是我很努力的尝试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版本需要添加泛型,但是我不知道如何在Java中实现。但是您应该能够传递给它们匿名内部类以提供函子:
string[] names = getLotsOfNames();
string commaSeparatedNames = (string)reduce(names,
new IBinaryFunctor {
public object invoke(object arg1, object arg2)
{ return ((string)arg1) + ", " + ((string)arg2); }
}
希望泛型可以摆脱强制转换。C#中的类型安全等效项为:
string commaSeparatedNames = names.Aggregate((a, b) => a + ", " + b);
为什么这个“酷”?将较大的计算分解为较小的部分的简单方法,使它们可以以不同的方式组合在一起,总是很酷。Google应用此想法的方法是并行化,因为map和reduce可以在多台计算机上共享。
但是关键的要求不是您的语言可以将函数视为值。任何OO语言都可以做到这一点。并行化的实际要求是,func
传递给映射和reduce的小函数不得使用或更新任何状态。他们必须返回仅依赖于传递给他们的参数的值。否则,当您尝试并行运行整个过程时,结果将被完全弄乱。
在对冗长的华夫饼干或简短的模糊博客文章感到最沮丧之后,我最终发现了这篇非常严谨的论文。
然后,我着手翻译成Scala,使其更加简洁,其中提供了最简单的情况,即用户只需指定应用程序的map
和reduce
部分即可。严格来说,在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 - i.e. 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方法。由于某些函数映射到原始数组中的每个元素,因此会创建一个新数组。因此,如果您映射了一个function(element){return element * 2;},它将返回一个新数组,其中每个元素都加倍。原始数组将保持不变。
https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Global_Objects/Array/map
Reduce是一种本机JS方法,也可以应用于数组。它将函数应用于数组,并具有称为累加器的初始输出值。它循环遍历数组中的每个元素,应用一个函数,并将其减少为单个值(从累加器开始)。这很有用,因为您可以拥有所需的任何输出,而只需要从这种类型的累加器开始即可。因此,如果我想将某事物简化为一个对象,我将从累加器{}开始。
https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Global_Objects/Array/Reduce?v=a
MapReduce:
要运行大型计算机,我们可以使用办公室中不同计算机的计算能力。困难的部分是将任务拆分到不同的计算机之间,由MapReduce库完成。
基本思想是将作业分为两部分:映射和简化。Map基本上解决了该问题,将其拆分为多个子部分,然后将这些子部分发送到不同的计算机上-因此所有部分都在同一时间运行。Reduce将从子部分中获得结果,并将其组合回去以获得单个答案。
输入是记录列表,映射计算的结果是键/值对列表。Reduce会将具有相同键的每组值合并为一个值。您无法分辨该作业是分成100个还是2个。最终结果看起来很像单个地图的结果。
请看一下简单的地图并减少程序:
Map函数用于在我们的原始列表上应用某些功能,因此会生成一个新列表。Python中的map()函数接受一个函数和一个列表作为参数。通过将函数应用于列表的每个项目,将返回一个新列表。
li = [5, 7, 4, 9]
final_list = list(map(lambda x: x*x , li))
print(final_list) #[25, 49, 16, 81]
Python中的reduce()函数接受一个函数和一个列表作为参数。该函数使用lambda函数和一个列表进行调用,并返回一个新的简化结果。这会对列表对执行重复操作。
#reduce func to find product/sum of list
x=(1,2,3,4)
from functools import reduce
reduce(lambda a,b:a*b ,x) #24