J'entends beaucoup parler de map/reduction, en particulier dans le contexte du système de calcul massivement parallèle de Google. C'est quoi exactement?
Extrait de la MapReduce page de publication de recherche de Google:
MapReduce est un modèle de programmation et Une implémentation associée pour le traitement de Et la génération de grands ensembles de données . Les utilisateurs spécifient une fonction de la carte Qui traite une paire clé/valeur pour Générer un ensemble de paires clé/valeur Intermédiaires et une fonction de réduction Qui fusionne tous les éléments. valeurs intermédiaires associées à la même touche intermédiaire .
L’avantage de MapReduce est que le traitement peut être effectué en parallèle sur plusieurs nœuds de traitement (plusieurs serveurs), ce qui en fait un système très évolutif.
Comme il est basé sur le modèle programmation fonctionnelle , les étapes map
et reduce
n'ont pas d'effets secondaires (l'état et les résultats de chaque sous-section d'un processus map
ne dépendent les ensembles en cours de mappage et de réduction peuvent être séparés sur plusieurs noeuds de traitement.
L'article de Joel intitulé { Votre langage de programmation peut-il faire cela? Explique-t-il à quel point la compréhension de la programmation fonctionnelle était essentielle dans Google pour proposer MapReduce, qui alimente son moteur de recherche. C'est une très bonne lecture si vous n'êtes pas familier avec la programmation fonctionnelle et comment elle permet un code évolutif.
Voir aussi: Wikipedia: MapReduce
Question connexe: _ { veuillez expliquer simplement mapreduce } _
Map est une fonction qui applique une autre fonction à tous les éléments d'une liste pour produire une autre liste avec toutes les valeurs de retour. (Une autre façon de dire "appliquez f à x" est "appel f, en lui passant x". Ainsi, il semble parfois plus agréable de dire "appliquer" au lieu de "appel".)
C'est ainsi que map est probablement écrit en C # (il s'appelle Select
et se trouve dans la bibliothèque standard):
public static IEnumerable<R> Select<T, R>(this IEnumerable<T> list, Func<T, R> func)
{
foreach (T item in list)
yield return func(item);
}
Comme vous êtes un mec de Java et que Joel Spolsky aime raconter des mensonges grossiers et injustes à propos de la mauvaise qualité de Java (en fait, il ne ment pas, il est vraiment nul, mais j'essaie de vous convaincre), voici ma très dure tentative de une version Java (je n'ai pas de compilateur Java, et je me souviens vaguement de Java version 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;
}
Je suis sûr que cela peut être amélioré de mille façons. Mais c'est l'idée de base.
Réduire est une fonction qui transforme tous les éléments d’une liste en une valeur unique. Pour ce faire, il faut lui attribuer une autre fonction func
transformant deux éléments en une seule valeur. Cela fonctionnerait en donnant les deux premiers éléments à func
. Ensuite, le résultat de cela avec le troisième élément. Ensuite, le résultat avec le quatrième élément, et ainsi de suite, jusqu'à ce que tous les éléments disparaissent et qu'il ne nous reste qu'une valeur.
En C #, réduire s'appelle Aggregate
et se trouve de nouveau dans la bibliothèque standard. Je vais passer directement à une version 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;
}
Ces versions Java nécessitent l'ajout de génériques, mais je ne sais pas comment le faire en Java. Mais vous devriez pouvoir leur transmettre des classes internes anonymes pour fournir aux foncteurs:
string[] names = getLotsOfNames();
string commaSeparatedNames = (string)reduce(names,
new IBinaryFunctor {
public object invoke(object arg1, object arg2)
{ return ((string)arg1) + ", " + ((string)arg2); }
}
Espérons que les génériques se débarrassent des moulages. L'équivalent typesafe en C # est:
string commaSeparatedNames = names.Aggregate((a, b) => a + ", " + b);
Pourquoi est-ce "cool"? Les méthodes simples permettant de diviser des calculs plus volumineux en éléments plus petits, afin de pouvoir les reconstituer de différentes manières, sont toujours cool. Google applique cette idée à la parallélisation, car la carte et la réduction peuvent être partagées sur plusieurs ordinateurs.
Mais l'exigence clé n'est PAS que votre langage puisse traiter les fonctions comme des valeurs. N'importe quelle langue OO peut le faire. En réalité, la parallélisation nécessite que les petites fonctions func
que vous transmettez pour mapper et réduire ne utilisent ni ne mettent à jour aucun état. Ils doivent renvoyer une valeur qui dépend uniquement du ou des arguments qui leur sont transmis. Sinon, les résultats seront complètement foirés lorsque vous essayez de tout faire en parallèle.
Cela explique mieux que ce que je peux. Aide-t-il?
Après avoir été très frustré par de très longs waffley ou de très courts messages de blogues vagues, j'ai finalement découvert ceci très bon document concis rigoureux .
Ensuite, je suis allé de l'avant et l'ai rendu plus concis en traduisant en Scala, où j'ai fourni le cas le plus simple, où un utilisateur spécifie simplement les parties map
et reduce
de l'application. À proprement parler, Hadoop/Spark utilise un modèle de programmation plus complexe qui oblige l'utilisateur à spécifier explicitement 4 fonctions supplémentaires décrites ici: 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
}
MapReduce:
Pour utiliser quelque chose de grand, nous pouvons utiliser la puissance de calcul de différents ordinateurs de notre bureau. La partie difficile consiste à diviser la tâche entre différents ordinateurs. Elle est effectuée par la bibliothèque MapReduce.
L'idée de base est que vous divisez le travail en deux parties: une carte et une réduction. Fondamentalement, Map prend en charge le problème, le divise en sous-parties et envoie les sous-parties à différentes machines - de sorte que toutes les parties fonctionnent en même temps. Réduire prend les résultats des sous-parties et les combine pour obtenir une réponse unique.
L'entrée est une liste d'enregistrements. Le résultat du calcul de la carte est une liste de paires clé/valeur. Réduire prend chaque ensemble de valeurs ayant la même clé et les combine en une valeur unique. Vous ne pouvez pas savoir si le travail a été divisé en 100 ou 2 morceaux; le résultat final ressemble à peu près au résultat d'une seule carte.
Veuillez regarder une carte simple et réduire le programme:
La fonction de carte est utilisée pour appliquer une fonction sur notre liste d'origine et une nouvelle liste est donc générée. La fonction map () en Python prend une fonction et une liste en argument. Une nouvelle liste est renvoyée en appliquant une fonction à chaque élément de la liste.
li = [5, 7, 4, 9]
final_list = list(map(lambda x: x*x , li))
print(final_list) #[25, 49, 16, 81]
La fonction reduction () en Python prend une fonction et une liste en argument. La fonction est appelée avec une fonction lambda et une liste et un nouveau résultat réduit est renvoyé. Cela effectue une opération répétitive sur les paires de la liste.
#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
Map est une méthode JS native pouvant être appliquée à un tableau. Il crée un nouveau tableau à la suite d'une fonction mappée sur chaque élément du tableau d'origine. Ainsi, si vous avez mappé une fonction (élément) {return element * 2;}, elle renverrait un nouveau tableau avec chaque élément doublé. Le tableau d'origine resterait non modifié.
https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/map
Réduire est une méthode JS native qui peut également être appliquée à un tableau. Il applique une fonction à un tableau et a une valeur de sortie initiale appelée un accumulateur. Il parcourt chaque élément du tableau, applique une fonction et les réduit à une valeur unique (qui commence par l'accumulateur). C'est utile parce que vous pouvez avoir n'importe quelle sortie que vous voulez, il vous suffit de commencer avec ce type d'accumulateur. Donc, si je voulais réduire quelque chose en objet, je commencerais par un accumulateur {}.
https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/Reduce?v=a