Що таке Карта / Зменшити?


84

Я багато чую про карту / зменшення, особливо в контексті масово паралельної обчислювальної системи Google. Що саме це?


3
@Rinat: Тим не менше, це все ще гарне запитання.
Білл Карвін,

3
Звичайно, я міг і робив це в Google; але (а) SO покликаний зростати, щоб мати відповіді на всі важливі питання (ми заохочуємось навіть розміщувати запитання, на які ми вже маємо відповіді), і (b) я хотів, щоб ця спільнота взяла його на себе.
Лоуренс Дол

Відповіді:


69

З реферату сторінки публікації дослідження MapReduce від Google :

MapReduce - це модель програмування та відповідна реалізація для обробки та генерації великих наборів даних. Користувачі вказують функцію карти, яка обробляє пару ключ / значення для генерації набору проміжних пар ключ / значення, і функцію зменшення, яка об'єднує всі проміжні значення, пов'язані з тим самим проміжним ключем.

Перевага MapReduce полягає в тому, що обробку можна виконувати паралельно на декількох вузлах обробки (декількох серверах), тому це система, яка може дуже добре масштабуватися.

Оскільки вона заснована з функціонального програмування моделі, mapі reduceкроки кожного не мають яких - небудь побічних ефектів (стан і результати кожного підрозділу в mapпроцесі не залежить від іншого), тому набір даних прив'язується і зменшується кожен може бути відділений над кількома вузлами обробки.

Joel's Чи може це зробити ваша мова програмування? стаття обговорює, як розуміння функціонального програмування було надзвичайно важливим для Google, щоб запропонувати MapReduce, який забезпечує роботу його пошукової системи. Це дуже гарне читання, якщо ви не знайомі з функціональним програмуванням та тим, як воно дозволяє масштабований код.

Дивіться також: Вікіпедія: MapReduce

Пов'язане запитання: Будь ласка, поясніть просто карту


3
Чудово пояснив. А що стосується Software Monkey, M / R неймовірно просто впровадити практично в будь-що, як тільки ви це зрозумієте, і не обмежується наведеними тут прикладами. Існує кілька способів обійти голову, можна подумати, що це колектори та лійки.
Еско


16

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функції, які ви передаєте для зіставлення та зменшення, не повинні використовувати або оновлювати будь-який стан. Вони повинні повернути значення, яке залежить лише від аргументів, переданих їм. В іншому випадку результати будуть повністю зіпсовані при спробі запустити все це паралельно.


2
Загалом хороша відповідь, варта +1; Хоча мені не сподобався удар на Java - але я пропустив значення функції з тих пір, як перейшов на Java із C, і погоджуюсь, що їх наявність давно назріла в Java.
Лоуренс Дол

1
Ява не був серйозним ударом на Java - у нього є близько трьох недоліків, яких достатньо, щоб я віддав перевагу C # прямо зараз, але у C # теж є список недоліків, які, можливо, колись змусять мене вивчати іншу мову.
Деніел Ервікер

До речі, мені було б дуже приємно, якби хтось міг редагувати приклади, щоб вони використовували загальні засоби Java, якщо це насправді можливо. Або якщо ви не можете редагувати, то тут розміщуйте фрагменти, і я редагуватиму.
Даніель Ервікер

Я почав редагувати, але метод map () створює масив типу return; Java не дозволяє створювати масиви загальних типів. Я міг би змінити його, щоб використовувати список (і, можливо, перетворити його на масив), але у мене тоді майже не вистачило амбіцій.
Майкл Майєрс

1
Синтаксис закриття, схожий на (a, b) => a + "," + b, був тим, чого я справді з нетерпінням чекав у Java 7, особливо з деякими новими матеріалами API, які, схоже, будуть входити. Цей синтаксис зробили подібні речі набагато чистішими; Шкода, не схоже, що це станеться.
Адам Яскевич,

2

Отримавши найбільше розчарування або дуже довгими вафлями, або дуже короткими розпливчастими повідомленнями в блозі, я врешті виявив цю дуже гарну сувору стислу папір .

Потім я продовжив і зробив це більш стислим, переклавши на 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
}


0

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


0

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
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.