Что такое Map / Reduce?
Я много слышу о map / reduce, особенно в контексте массово параллельной вычислительной системы Google. Что именно это такое?
6 ответов:
из аннотации Google MapReduce научные публикации, страницы:
MapReduce-это модель программирования и связанная реализация для обработка и генерация больших данных наборы. Пользователи задают функцию карты это обрабатывает пару ключ / значение создать набор промежуточных пары ключ / значение и функция уменьшения что объединяет все промежуточные значения связанный с тем же промежуточным звеном ключ.
В преимущество MapReduce заключается в том, что обработка может выполняться параллельно на нескольких узлах обработки (нескольких серверах), поэтому это система, которая может очень хорошо масштабироваться.
Так как он основан на функциональное программирование модель
map
иreduce
шаги каждый не имеют никаких побочных эффектов (состояние и результаты из каждого подраздела amap
процесс не зависит от другого), поэтому сопоставляемый и сокращаемый набор данных может быть разделен на несколько узлы обработки.Иоиля Может Ли Ваш Язык Программирования Сделать Это? часть обсуждает, как понимание функционального программирования было важно в Google, чтобы придумать MapReduce, который приводит в действие свою поисковую систему. Это очень хорошо читать, если вы не знакомы с функциональным программированием и, насколько это позволяет масштабируемый код.
Читайте также: Википедия: MapReduce
Map-это функция, которая применяет другую функцию ко всем элементам списка, чтобы создать другой список со всеми возвращаемыми значениями на нем. (Другой способ сказать "применить f К x "- это"вызвать f, передав его x". Поэтому иногда лучше сказать " применить "вместо"позвонить".)
вот как карта, вероятно, написана на 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 чувак, и Джоэл Сполски любит говорить грубую несправедливую ложь о как дерьмовая 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
что превращает два элемента в a одно значение. Это будет работать, давая первые два пунктаfunc
. Тогда результат этого вместе с третьим пунктом. Затем результат этого с четвертым элементом, и так далее, пока все элементы не ушли, и мы остались с одним значением.в C# сокращение называется
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
функции, которые вы передаете в map и reduce, не должны использовать или обновлять какое-либо состояние. Они должны возвращать значение, зависящее только от переданных им аргументов. В противном случае результаты будут полностью испорчены, когда вы попытаетесь запустить все это параллельно.
после получения наиболее разочарованы либо очень долго waffley или очень короткие расплывчатые сообщения в блоге я в конечном итоге обнаружил это очень хороший строгий лаконичный документ.
затем я пошел вперед и сделал его более кратким, переведя на Scala, где я предоставил самый простой случай, когда пользователь просто указывает
map
иreduce
части приложения. В Hadoop/Spark, строго говоря, используется более сложная модель программирования, требующая пользователя чтобы явно указать еще 4 функции, описанные здесь:http://en.wikipedia.org/wiki/MapReduce#Dataflowimport 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, который может быть применен к массиву. Он создает новый массив в результате некоторой функции, сопоставленной каждому элементу исходного массива. Поэтому, если вы сопоставили функцию (элемент) { return element * 2;}, он вернет новый массив с каждым удвоенным элементом. Исходный массив останется неизменным.
https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/map
Reduce-это собственный метод JS это также может быть применено к массиву. Она применяет функцию к массиву и имеет начальное значение на выходе аккумулятора. Он проходит через каждый элемент в массиве, применяет функцию и уменьшает их до одного значения (которое начинается как аккумулятор). Это полезно, потому что вы можете иметь любой выход, который вы хотите, вы просто должны начать с этого типа аккумулятора. Поэтому, если бы я хотел уменьшить что-то в объект, я бы начал с аккумулятора {}.
https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/Reduce?v=a