Remarque générale : Le cours ne peut être self-contained –> consulter la documentation en ligne de Spark.
Cet exercice illustre les différentes structures de contrôle de Scala présentées en cours. Il permet de comprendre le paradigme fonctionnel : seules les fonctions map, reduce, flatten, filter, flatMap sont autorisées.
Lancer le spark-shell en mode local (voir Doc). Répondre aux questions ci-dessous.
Tester au fur et à mesure ces fonctions sur listeEntiers construit comme suit :
val listeEntiers = List.range(1,11)
Réponse
def maxEntiers(in: List[Int])= in.reduce((a,b)=>(if(a>b)a else b)) def scEntiers(in: List[Int])= in.map(e=>e*e).reduce((a,b)=>(a+b)) def moyEntiers(in: List[Int])={val p = in.map(e=>(1,e)).reduce((a,b)=>(a._1+b._1, a._2+b._2)); p._2/p._1}
Soit une liste chaine de caractères construite à l'aide de l'instruction suivante
val listeTemp = List("7,2010,04,27,75", "12,2009,01,31,78", "41,2009,03,25,95", "2,2008,04,28,76", "7,2010,02,32,91")
Chaque élément représente un enregistrement fictif de températures avec le format (station, année, mois, température, code_département).
Bien entendu, il faudra faire les transformations et les conversions de type nécessaires!
Réponse
val temp2009 = listeTemp.map(x=>x.split(",")).filter(_(1).toInt==2009).map(x=>x(3).toInt) maxEntiers(temp2009) moyEntiers(temp2009)
Soit une liste chaine de caractères construite à l'aide de l'instruction suivante
val melange = List("1233,100,3,20171010", "1224,22,4,20171009", "100,lala,comedie", "22,loup,documentaire")
Deux types d'éléments existent : ceux de la forme (userID, movieID, rating, timestamp) et ceux de la forme (movieID, title, genre). Le domaine des userID est [1000, 2000] et celui des movieID est [0, 100].
Il est demandé de construire à partir de melange deux listes distinctes :
Réponse
val notes = melange.map(_.split(",")).filter(_(0).toInt>=1000).map(x=>(x(0).toInt,x(1), x(2).toInt, x(3).toInt)) val films = melange.map(_.split(",")).filter(_(0).toInt<=100).map(x=>(x(0).toInt,x(1), x(2)))
Soit une liste personnes contenant des tuples ayant trois attributs décrivant des personnes :
les enseignants.
val personnes = List(("Joe", "etu", 3), ("Lee", "etu", 4), ("Sara", "ens", 10), ("John", "ens", 5), ("Bill", "nan",20))
en entrée. Par exemple, le tuple (“Joe”, “etu”, 3) devra être transformé en un objet Etu(“Joe”, 3).
Attention Les personnes de type inconnu ne doivent être dans le résultat!
Astuce Utiliser le pattern matching
Réponse
val personnes = List(("Joe", "etu", 3), ("Lee", "etu", 4), ("Sara", "ens", 10), ("John", "ens", 5), ("Bill", "eng",20)) class Etu(nom:String, annee:Int){override def toString()="Etudiant en " +annee+" annee"} class Ens(nom:String, annee:Int){override def toString()="Enseignant avec " +annee+" annee d'experience"} val classes_personnes = personnes.map(x=> x match { case(a,"ens",b) =>new Ens(a,b); case(a, "etu", b) =>new Etu(a,b); case _=>None}).filter(_!=None)
Commencer par copier et décompresser dans votre espace de travail le fichier
/Infos/bd/spark/bdle/2015/data/wordcount.txt.bz2
Le fichier obtenu, wordcount.txt (45Mo), contient des statistiques d’accès aux pages Wikimdia en différentes langue. Une ligne de la forme En.d updates 3 24145 indique dans une page écrite en anglais (symbole ‘En’), intitulée ‘updates’ qui a été cliqué 3 fois et qui fait 24145 octets.
Lancer le spark-shell en mode local (voir Doc) en suivant les instructions fournies puis charger le fichier
wordcount.txt
au moyen de la méthode textFile() invoquée à partir de la variable context comme suit :
val data = sc.textFile("<le_chemin_dans_votre_espace_perso>/wordcount.txt")
Ici, la variable data est une RDD, et donc, de type Array tel que vous pouvez le constater sur le shell. L’invocation de la méthode take(n) sur cette variable affiche les n premiers éléments sur une seule ligne. Pour un affichage sur plusieurs lignes (tel un head -n sous linux), utiliser plutôt take(n).foreach(println) qui itère sur les éléments de la variable d’où elle est invoquée et affiche chaque élément sur une ligne séparément. Noter également que la méthode count retourne la cardinalité (nombre d’éléments) de la RDD d’où elle est invoquée. Par soucis de lisibilité de votre code, stocker le résultat de chaque question dans des variables nommées q1, q2, q3 etc. Penser à tester au fur et à mesure le résultat des instructions lorsque celles-ci ne contiennent que des transformations.
Réponse
//1. Extraire dans une variable appelée listEnt le 4e champ de chaque élément de data. val q1 = data.map(x=>x.split(" ")).map(x=>x(3).toDouble) //2. Construire à partir de q1 une liste contenant les nombres compris strictement entre 1000 et 1300 puis convertir en type entier. val q2 = q1.filter(x=> x>1000 && x<1300 )map(x=>x.toInt) //. Construire à partir de q2 une liste contenant les multiples de 3 et l’appeler q33. Faire val q33 = q2.filter(x=>x%3==0) val q39 = q2.filter(x=>x%9==0) //4. Construire une liste obtenue en divisant par 10 chaque élément de q33. val q4 = q33.map(x=>x/10) //5. Construire à partir de q4 un ensemble d’éléments (liste sans doublons). val q5 = q4.distinct //6. Construire à partir de q2 une liste contenant ses qui sont multiples de 3 et de 9 à la fois. Utiliser impérativement q33 et q39. val q6 = q33.intersection(q39) //7. Construire à partir de q2 une liste contenant ses éléments qui sont multiples de 3 mais pas de 9. Utiliser impérativement q33 et q39. val q7 = q33.subtract(q39) //8. Construire à partir de q2 une liste contenant ses éléments qui sont multiples de 3 ou de 10. val q8 = q33.union(q2.filter(x=>x%3==0)) //9. Calculer la somme, la moyenne, le minium ainsi que le maximum des éléments de q8. val q9sum = q8.map(x=>x.toDouble).reduce(_+_) val q9max = q8.map(x=>x.toDouble).reduce((x,y)=>if (x > y) x else y) val q9min = q8.map(x=>x.toDouble).reduce((x,y)=>if (x < y) x else y) val q9avg = q8.map(x=>(x,1)).reduce((x,y)=>(x._1+y._1,x._2+y._2)) q9avg._1/q9avg._2