Я багато чую про карту / зменшення, особливо в контексті масово паралельної обчислювальної системи Google. Що саме це?
Я багато чую про карту / зменшення, особливо в контексті масово паралельної обчислювальної системи Google. Що саме це?
Відповіді:
З реферату сторінки публікації дослідження MapReduce від Google :
MapReduce - це модель програмування та відповідна реалізація для обробки та генерації великих наборів даних. Користувачі вказують функцію карти, яка обробляє пару ключ / значення для генерації набору проміжних пар ключ / значення, і функцію зменшення, яка об'єднує всі проміжні значення, пов'язані з тим самим проміжним ключем.
Перевага MapReduce полягає в тому, що обробку можна виконувати паралельно на декількох вузлах обробки (декількох серверах), тому це система, яка може дуже добре масштабуватися.
Оскільки вона заснована з функціонального програмування моделі, map
і reduce
кроки кожного не мають яких - небудь побічних ефектів (стан і результати кожного підрозділу в map
процесі не залежить від іншого), тому набір даних прив'язується і зменшується кожен може бути відділений над кількома вузлами обробки.
Joel's Чи може це зробити ваша мова програмування? стаття обговорює, як розуміння функціонального програмування було надзвичайно важливим для 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;
}
Я впевнений, що це можна покращити мільйоном способів. Але це основна ідея.
Зменшити - це функція, яка перетворює всі елементи у списку в одне значення. Для цього йому потрібно надати ще одну функцію, func
яка перетворює два елементи в одне значення. Це спрацювало б, віддавши перші два пункти func
. Тоді результат цього разом із третім пунктом. Потім результат цього з четвертим елементом, і так далі, поки всі елементи не підуть, і у нас залишиться одне значення.
У C # викликається reduction 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 застосовує цю ідею до розпаралелювання, оскільки як карту, так і зменшення можна розподілити на кількох комп’ютерах.
Але ключовою вимогою НЕ є те, що ваша мова може розглядати функції як значення. Будь-яка мова OO може це зробити. Фактичною вимогою до розпаралелювання є те, що ті маленькі func
функції, які ви передаєте для зіставлення та зменшення, не повинні використовувати або оновлювати будь-який стан. Вони повинні повернути значення, яке залежить лише від аргументів, переданих їм. В іншому випадку результати будуть повністю зіпсовані при спробі запустити все це паралельно.
Отримавши найбільше розчарування або дуже довгими вафлями, або дуже короткими розпливчастими повідомленнями в блозі, я врешті виявив цю дуже гарну сувору стислу папір .
Потім я продовжив і зробив це більш стислим, переклавши на 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, який можна застосувати до масиву. Він створює новий масив в результаті деякої функції, що відображається на кожен елемент вихідного масиву. Отже, якщо ви зіставили функцію (елемент) {return element * 2;}, вона поверне новий масив із кожним подвоєним елементом. Оригінальний масив не змінювався.
https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/map
Зменшити - це власний метод JS, який також можна застосувати до масиву. Він застосовує функцію до масиву і має початкове вихідне значення, яке називається накопичувачем. Він циклічно перебирає кожен елемент масиву, застосовує функцію та зменшує їх до одного значення (яке починається як накопичувач). Це корисно, оскільки ви можете отримати будь-який вихід, який вам потрібно, вам просто потрібно почати з такого типу акумулятора. Тож, якщо я хотів би щось зменшити в об’єкт, я б почав з акумулятора {}.
https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/Reduce?v=a
MapReduce:
Щоб запустити щось велике, ми можемо використовувати обчислювальну потужність різних комп’ютерів у нашому офісі. Важкою частиною є розподіл завдання між різними комп'ютерами, що робиться за допомогою бібліотеки MapReduce.
Основна ідея полягає в тому, що ви ділите роботу на дві частини: карту та зменшення. Map в основному приймає проблему, розбиває її на підрозділи та надсилає підрозділи на різні машини - отже, всі частини працюють одночасно. Зменшення бере результати з підрозділів та комбінує їх назад, щоб отримати єдину відповідь.
Введення - це список записів, а результат обчислення карти - список пар ключ / значення. Зменшити приймає кожен набір значень, що має один і той же ключ, і об'єднує їх в одне значення. Ви не можете сказати, чи було завдання розбито на 100 частин або 2 частини; кінцевий результат майже схожий на результат однієї карти.
Будь ласка, подивіться на просту карту та зменшіть програму:
Функція Map використовується для застосування якоїсь функції до нашого початкового списку, і, отже, створюється новий список. Функція map () у Python бере як аргумент функцію та список. Новий список повертається, застосовуючи функцію до кожного елемента списку.
li = [5, 7, 4, 9]
final_list = list(map(lambda x: x*x , li))
print(final_list) #[25, 49, 16, 81]
Функція reduce () у Python приймає функцію та список як аргумент. Функція викликається за допомогою лямбда-функції та списку та повертається новий зменшений результат. Це виконує повторювану операцію над парами списку.
#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