You are on page 1of 21

Section 1: Getting Started

01. Introduction, and Getting Set Up


putanja do instalacije Java-e (JDK Java Development Kit + JRE Java RunTime Environment)
o C:\Program Files\Java\ jdk1.8.0_112\
o C:\Program Files\Java\jdk1.8.0_111\
putanja do Spark-a
o C:\spark
nakon toga se promeni log level Spark-a (u fajlu log4j.properties)
pod Windows-om je potrebno da instaliramo winutils.exe da bi smo mogli da radimo sa Hadoop-om
u folderu C:\winutils\bin je potrebno iskopirati winutils.exe

Podeavanje sistemskih varijabli


New User Variable >
o Variable name: SPARK_HOME
o Variable value: C:\spark
New User Variable >
o Variable name: JAVA_HOME
o Variable value: C:\Program Files\Java\jdk1.8.0_112
New User Variable >
o Variable name: HADOOP_HOME
o Variable value: C:\winutils

potrebno je podesiti PATH direktorijum -> Edit environment variable


o dodati %SPARK_HOME%\bin
o dodati %JAVA_HOME\bin

Podeavanje razvojnog okruenja scala-ide

link: http://scala-ide.org/

C:\Program Files\Java\jdk1.8.0_111\

val rdd = sc.textFile(README.md)


rdd.count()

02. Create a Histogram of Real Movie Ratings with Spark!


prvo emo preuzeti podatke
sauvaemo ih u folder-u C:\SparkScala
to je folder u kojem emo drati sve resurse tokom ovog kursa: kodove, projekte, podatke
sa sajta http://grouplens.org/ emo skinuti dataset ml-100k.zip
folder ml-100k.zip kopirati u C:\SparkScala
uz lekciju skinuti i materijal SparkScala.zip u kojem se nalazi kod
kodove iz foldera SparkScala.zip emo sauvati negde na hdd-u (u folderu C:\Source) da bi smo
mogli kasnije da ga koristimo kao izvor
sada emo otvoriti Eclipse i kao Workspace odabraemo folder C:\SparkScala
kreiraemo novi Scala projekat pod nazivom SparkScalaCourse
kreiraemo sada novi package u projektu i nazvaemo ga com.sundogsoftware.spark
kliknuemo sada na package i odabrati opciju Import
1
odabrati sada General, a nakon toga File System
za lokaciju odakle se vuku fajlovi odabrati C:\Source\SparkScala
odabrati RatingsCounter.scala
2 puta kliknimo na taj fajl
ukoliko pogledamo source kod vidimo da nam okruenje prijavljuje greku
ono to je u ovom trenutku problem ovde jeste to nismo import-ovali Spark biblioteke, tako da ovaj kod
nema sa im da radi
kliknuemo na SparkScalaCourse projekat i odabrati properties
nakon toga karticu Libraries i pritisnuti dugme Add External JARs...
i sa lokacije c:\spark\jars , selektovati sve fajlove i ubaciti ih
sada je potrebno kompajlirati projekat
odabrati opciju Run Configurations . . .
napraviti novi Scala Application, za ime staviti RatingsCounter, za ime projekta staviti
SparkScalaCourse, za Main Class staviti: com.sundogsoftware.spark.RatingsCounter
kada pokrenemo dobijemo sledee podatke:

filmovi sa 1 zvezdicom imaju rejting 6110, sa 2 zvezdice 11370


dakle, najpoluarniji su filmovi sa 4
dakle, ovo su podaci iz 1995 godine
ono to bi bilo interesantno jeste da se skine poslednji rejting filmova i da se vidi da li se
distribucija promenila !
ovo je va prvi SparkScala program

package com.sundogsoftware.spark

import org.apache.spark._
import org.apache.spark.SparkContext._
import org.apache.log4j._

/** Count up how many of each star rating exists in the MovieLens 100K data set. */
object RatingsCounter {

/** Our main function where the action happens */


def main(args: Array[String]) {

// Set the log level to only print errors


Logger.getLogger("org").setLevel(Level.ERROR)

// Create a SparkContext using every core of the local machine, named RatingsCounter
val sc = new SparkContext("local[*]", "RatingsCounter")

// Load up each line of the ratings data into an RDD


val lines = sc.textFile("../ml-100k/u.data")

// Convert each line to a string, split it out by tabs, and extract the third field.
// (The file format is userID, movieID, rating, timestamp)
val ratings = lines.map(x => x.toString().split("\t")(2))
2
// Count up how many times each value (rating) occurs
val results = ratings.countByValue()

// Sort the resulting map of (rating, count) tuples


val sortedResults = results.toSeq.sortBy(_._1)

// Print each result on its own line.


sortedResults.foreach(println)
}
}

vidimo da u kodu imamo objekat u kome se nalazi cela naa aplikacija i ije je ime RatingsCounter
vidimo da imamo main funkciju unutar koje se sve nalazi
zatim imamo instrukciju u kojoj kaemo da samo pamtimo logove koji su ERROR
nakon toga postavljamo SparkContext, koji pokreemo na lokalnoj maini i koristimo svaki CPU i ime
tog JOB-a je RatingsCounter
u sledeoj liniji koda uitavamo podatke o rejtinzima filmova u neto to zovemo RDD
u sledeoj liniji koda parsiramo te podatke, tako to ih podelimo na osnovu tab karaktera i upamo
same rejtinge filmova
nakon toga koristimo f-ju CountByValue, koja nam automatski prebrojava za svaki rejting rezultat
nakon toga je sortiramo i tampamo rezultate

Section 2: Scala Crash Course

03. Scala Basics, Part 1


Scala je veoma prilagoena za Spark jer radi paralelno procesiranje podataka na klasteru
sam Spark je izgraen u Scala-i, tako da imate pristup poslednjim feature-ima koje Spark ima pre
Python-a ili Java-e
Scala je ujedno i najefikasniji nain za pokretanje samog koda
upotrebom Scala-e imaete najbe JOB-ove koje Spark moe da izvrava
Scala se izvrava nad JVM-om (Java Virtual Machine), to znai da se kompajlira u byte-code i
izvrava je JVM
ono to je dobro u tome jeste to imate pristup Java-i i ukoliko imate Java biblioteku koju elite da
koristite u vaem kodu, to moete da uradite
Scala je fokusirana na funkcionalno programiranje

iz menija u Eclipse-u idemo na meni i odaberimo File -> New -> Scala project
nazovimo ovaj projekat LearningScala
u ovaj projekat Import-ujmo fajl LearningScala1.sc
ovo je Scala worksheet, kao notebook u Python-u
to je interaktivno, jer moete odmah videti rezultat nakon odreene linije koda
svaki put kada dodate neki kod i snimite, taj kod je evaluiran i interpretiran i to je prikazano

3
kada otvorimo fajl LearningScala1.sc, vidimo sledei kod

object LearningScala1 {
// VALUES are immutable constants. You can't change them once defined.
val hello: String = "Hola!" //> hello : String = Hola!
println(hello) //> Hola!

// Notice how Scala defines things backwards from other languages - you declare the
// name, then the type.

// VARIABLES are mutable


var helloThere: String = hello //> helloThere : String = Hola!
helloThere = hello + " There!"
println(helloThere) //> Hola! There!

// One key objective of functional programming is to use immutable objects as often as possible.
// Try to use operations that transform immutable objects into a new immutable object.
// For example, we could have done the same thing like this:
val immutableHelloThere = hello + "There!" //> immutableHelloThere : String = Hola!There!
println(immutableHelloThere) //> Hola!There!

// Some other types


val numberOne : Int = 1 //> numberOne : Int = 1
val truth : Boolean = true //> truth : Boolean = true
val letterA : Char = 'a' //> letterA : Char = a
val pi : Double = 3.14159265 //> pi : Double = 3.14159265
val piSinglePrecision : Float = 3.14159265f //> piSinglePrecision : Float = 3.1415927
val bigNumber : Long = 1234567890l //> bigNumber : Long = 1234567890
val smallNumber : Byte = 127 //> smallNumber : Byte = 127

4
// String printing tricks
// Concatenating stuff with +:
println("Here is a mess: " + numberOne + truth + letterA + pi + bigNumber)
//> Here is a mess: 1truea3.141592651234567890

// printf style:
println(f"Pi is about $piSinglePrecision%.3f") //> Pi is about 3.142
println(f"Zero padding on the left: $numberOne%05d")
//> Zero padding on the left: 00001

// Substituting in variables:
println(s"I can use the s prefix to use variables like $numberOne $truth $letterA")
//> I can use the s prefix to use variables like 1 true a
// Substituting expressions (with curly brackets):
println(s"The s prefix isn't limited to variables; I can include any expression. Like ${1+2}")
//> The s prefix isn't limited to variables; I can include any expression.
Like
//| 3

// Using regular expressions:


val theUltimateAnswer: String = "To life, the universe, and everything is 42."
//> theUltimateAnswer : String = To life, the universe, and everything is
42.
//|
val pattern = """.* ([\d]+).*""".r //> pattern : scala.util.matching.Regex = .* ([\d]+).*
val pattern(answerString) = theUltimateAnswer //> answerString : String = 42
val answer = answerString.toInt //> answer : Int = 42
println(answer) //> 42

// Dealing with booleans


val isGreater = 1 > 2 //> isGreater : Boolean = false
val isLesser = 1 < 2 //> isLesser : Boolean = true
val impossible = isGreater & isLesser //> impossible : Boolean = false
val anotherWay = isGreater && isLesser //> anotherWay : Boolean = false

val picard: String = "Picard" //> picard : String = Picard


val bestCaptain: String = "Picard" //> bestCaptain : String = Picard
val isBest: Boolean = picard == bestCaptain //> isBest : Boolean = true

// EXERCISE
// Write some code that takes the value of pi, doubles it, and then prints it within a string with
// three decimal places of precision to the right.
// Just write your code below here; any time you save the file it will automatically display the results!

svaki programski jezik ima promenljive (eng. variables)


promenljive mogu da budu:
o promenljive (eng. mutable)
o nepromenljive (eng. immutable)
mutable znai da mogu da se menjaju

5
immutable znai da ne mogu da se menjaju
u drugim jezicima se to zove konstantna promenljiva ili finalna promenljiva u zavisnosti od jezika o
kojem priamo
Scala ohrabruje upotrebu immutable promenljivih koliko god je to mogue
i one se u Scala oznaavaju sa val od value
sintaksa za deklarisanje promenljive je malo neobiajena, pogledajmo sada ovde:
o val hello: String = "Hola!"
val je dakle immutable konstanta, ta konstanta e biti tipa String i postaviemo je na vrednost Hola!
iako Scala vas ohrabruje da koristitite Immutable vrednosti, takoe moete koristiti Mutable vrednosti,
tada koristite var kljunu re umesto val
o var helloThere: String = hello
o helloThere = hello + " There!"
o println(helloThere)
var mogu da promenim
Scala moe implicitno da odredi tip
o val immutableHelloThere = hello + "There!"

04. Scala Basics, Part 2


Ponekad je u potrebno da imate formatiran numeriki izlaz.
Ukoliko to elite da uradite morate ispred stringa da dodate prefix f.
o println(f"Pi is about $piSinglePrecision%.3f")
o println(f"Zero padding on the left: $numberOne%05d")
na poetku imamo znak f za float promenljivu, znak dolara $, koji prati ime promenljive piSinglePrecision,
oznaava gde emo ispisati vrednost te promenljive, a znak % i iza njega oznaavaju sa koliko decimala
e te to ispisati (u ovom sluaju 3 cifre preciznosti za float-point)
kada imamo slovo d u nastavku to oznaava sa koliko nula koje prethode emo prikazati broj
pored obinog referenciranja varijabli upotrebom znaka dolar, moemo raditi i jo neke druge operacije
prilikom ispisa string-a, npr. moemo evaluirati izraz (eng. expression)
o println(s"The s prefix isn't limited to variables; I can include any expression. Like ${1+2}")
ukoliko ste upoznati sa regularnim izrazima (eng. regular expression), poznato vam je da oni mogu da
budu veoma korisni u prepoznavanju paterna i izvlaenju podataka
zapoinimo sada sa sledeim string-om:
o val theUltimateAnswer: String = "To life, the universe, and everything is 42."
sada imamo regularni izraz koji ide i prepoznaje prvi broj u string-u i u Scala-i izgleda ovako
o val pattern = """.* ([\d]+).*""".r
sada elimo iz stringa theUltimateAnswer da izvuemo string upotrebom regularnog izraza
pogledajmo sada ovaj kod koji je poprilino nelogian
o val pattern(answerString) = theUltimateAnswer
dakle upotrebom regularnog izrata broj 42 se izvlai iz string-a i smeta se u promenljivu answerString
sada ukoliko elimo da taj string tretiramo kao broj moemo pozvati f-ju .toInt
o val answer = answerString.toInt
na kraju kada odtampamo promenljivu answer dobijemo 42
o println(answer)
... boolean operatori ...

05. Flow Control in Scala


if else
match
for to + range operator
while

6
...
izrazi (eng. expressions) u Scala-i funkcioniu malo drugaije ukoliko imamo blok koda, koji izgleda
ovako:
{val x = 10; x + 20}
zapoinjemo sa izrazom x = 10 i kasnije dodajemo 20 na to
u drugim jezicima to ne bi uradilo nita, zato to nita ne vraamo od tog izraza
ali u Scala-i to ima implicitnu vrednost, a to je da kompletan izraz ima vrednost poslednjeg izraza
dakle, vi moete da uzmete ceo ovaj izraz i da koristite i on ima vrednost 30
println({val x = 10; x + 20})
daje vrednost 30,
dakle u funkcionalnom programiranju izrazi (eng. expressions) mogu biti korieni kao same vrednosti
(eng. values)

06. Functions in Scala


. . . osnovna sintaksa funkcija . . .
funkcije mogu da prihvataju druge funkcije kao parametre

object LearningScala3 {
// Functions

// Format is def <function name>(parameter name: type...) : return type = { expression }


// Don't forget the = before the expression!
def squareIt(x: Int) : Int = {
x*x
} //> squareIt: (x: Int)Int

def cubeIt(x: Int): Int = {x * x * x} //> cubeIt: (x: Int)Int

println(squareIt(2)) //> 4

println(cubeIt(2)) //> 8

// Functions can take other functions as parameters

def transformInt(x: Int, f: Int => Int) : Int = {


f(x)
} //> transformInt: (x: Int, f: Int => Int)Int

val result = transformInt(2, cubeIt) //> result : Int = 8


println (result) //> 8

// "Lambda functions", "anonymous functions", "function literals"


// You can declare functions inline without even giving them a name
// This happens a lot in Spark.
transformInt(3, x => x * x * x) //> res0: Int = 27

transformInt(10, x => x / 2) //> res1: Int = 5

transformInt(2, x => {val y = x * 2; y * y}) //> res2: Int = 16

// This is really important!

}
vidimo f-ju transformInt, koja prihvata 2 parametra, gde kao prvi parametar prihvata Int, a kao drugi f-ju
koja transformie Int u drugi Int, kao rezultat vraa f-ju f kojoj je prosleen parametar x
7
07. Data Structures in Scala
torke (eng. tuples)
specijalni sluaj jeste kada torka (eng. tuple) moe da sadri 2 stvari, primer picardShip

8
object LearningScala4 {
// Data structures

// Tuples (Also really common with Spark!!)


// Immutable lists
// Often thought of as database fields, or columns.
// Useful for passing around entire rows of data.

val captainStuff = ("Picard", "Enterprise-D", "NCC-1701-D")


//> captainStuff : (String, String, String) = (Picard,Enterprise-D,NCC-
1701-D)
//|
println(captainStuff) //> (Picard,Enterprise-D,NCC-1701-D)

// You refer to individual fields with their ONE-BASED index:


println(captainStuff._1) //> Picard
println(captainStuff._2) //> Enterprise-D
println(captainStuff._3) //> NCC-1701-D

// You can create a key/value pair with ->


val picardsShip = "Picard" -> "Enterprise-D" //> picardsShip : (String, String) = (Picard,Enterprise-
D)
println(picardsShip._2) //> Enterprise-D

// You can mix different types in a tuple


val aBunchOfStuff = ("Kirk", 1964, true) //> aBunchOfStuff : (String, Int, Boolean) =
(Kirk,1964,true)

// Lists
// Like a tuple, but it's an actual Collection object that has more functionality.
// It's a singly-linked list under the hood.

val shipList = List("Enterprise", "Defiant", "Voyager", "Deep Space Nine")


//> shipList : List[String] = List(Enterprise, Defiant, Voyager, Deep
Space Nin
//| e)

// Access individual members using () with ZERO-BASED index (confused yet?)


println(shipList(1)) //> Defiant

// head and tail give you the first item, and the remaining ones.
println(shipList.head) //> Enterprise
println(shipList.tail) //> List(Defiant, Voyager, Deep Space Nine)

// Iterating though a list


for (ship <- shipList) {println(ship)} //> Enterprise
//| Defiant
//| Voyager
//| Deep Space Nine

// Let's apply a function literal to a list! map() can be used to apply any function to every item in a
collection.
val backwardShips = shipList.map( (ship: String) => {ship.reverse})
//> backwardShips : List[String] = List(esirpretnE, tnaifeD, regayoV,
eniN eca
//| pS peeD)

9
for (ship <- backwardShips) {println(ship)} //> esirpretnE
//| tnaifeD
//| regayoV
//| eniN ecapS peeD

// reduce() can be used to combine together all the items in a collection using some function.
val numberList = List(1, 2, 3, 4, 5) //> numberList : List[Int] = List(1, 2, 3, 4, 5)
val sum = numberList.reduce( (x: Int, y: Int) => x + y)
//> sum : Int = 15
println(sum) //> 15

// filter() can remove stuff you don't want. Here we'll introduce wildcard syntax while we're at it.
val iHateFives = numberList.filter( (x: Int) => x != 5)
//> iHateFives : List[Int] = List(1, 2, 3, 4)
val iHateThrees = numberList.filter(_ != 3) //> iHateThrees : List[Int] = List(1, 2, 4, 5)

// Note that Spark has its own map, reduce, and filter functions that can distribute these operations. But
they work the same way!
// Also, you understand MapReduce now :)

// Concatenating lists
val moreNumbers = List(6, 7, 8) //> moreNumbers : List[Int] = List(6, 7, 8)
val lotsOfNumbers = numberList ++ moreNumbers //> lotsOfNumbers : List[Int] = List(1, 2, 3, 4, 5,
6, 7, 8)
// More list fun
val reversed = numberList.reverse //> reversed : List[Int] = List(5, 4, 3, 2, 1)
val sorted = reversed.sorted //> sorted : List[Int] = List(1, 2, 3, 4, 5)
val lotsOfDuplicates = numberList ++ numberList //> lotsOfDuplicates : List[Int] = List(1, 2, 3, 4, 5, 1,
2, 3, 4, 5)
val distinctValues = lotsOfDuplicates.distinct //> distinctValues : List[Int] = List(1, 2, 3, 4, 5)
val maxValue = numberList.max //> maxValue : Int = 5
val total = numberList.sum //> total : Int = 15
val hasThree = iHateThrees.contains(3) //> hasThree : Boolean = false

// Maps
// Useful for key/value lookups on distinct keys
// Like dictionaries in other languages

val shipMap = Map("Kirk" -> "Enterprise", "Picard" -> "Enterprise-D", "Sisko" -> "Deep Space Nine",
"Janeway" -> "Voyager")
//> shipMap : scala.collection.immutable.Map[String,String] = Map(Kirk
-> Ente
//| rprise, Picard -> Enterprise-D, Sisko -> Deep Space Nine, Janeway ->
Voyage //| r)
println(shipMap("Janeway")) //> Voyager

// Dealing with missing keys


println(shipMap.contains("Archer")) //> false

val archersShip = util.Try(shipMap("Archer")) getOrElse "Unknown"


//> archersShip : String = Unknown
println(archersShip) //> Unknown

liste imaju vie mogunosti od torki (eng. tuples)


tuple su FIRST-BASED
liste su ZERO-BASED
map
reduce
10
filter
Map su kao renici (eng. dictionary) u drugim jezicma

Section 3: Spark Basics and Simple Examples

08. Introduction to Spark


Spark je brz generalni endin za procesiranje velike koliine podataka.
u sutini Spark je framework za programiranje i distribuirano procesiranje velikih skupova podtaka (eng.
data set)
on sadri funkcije koje nam omoguavaju da uvuemo (eng. import) podatke iz distribuiranog skladita
podataka (eng. data store), kao to su HDFS fajl sistem ili S3 ili neto slino i obezbeuje mehanizam
za veoma prosto i veoma efikasno procesiranje podataka
dakle, vi dodeljujete funkcije koje transformiu te podatke ili izvode razliite akcije nad njima i to moete
uraditi na klasteru upotrebom istog koda koji koristite na vaem desktop-u
isti kod moete pokrenuti na vaem desktop-u u svrhu razvoja kao i na pravom klasteru, koji moe da
skalira koliko god vi elite
kompletna snaga horizontalnog skaliranja je dostupna ukoliko imate zadatke masivnog procesiranja
podataka, sve to treba da uradite jeste da dodate jo malo hardware-a i to moe da dri celu stvar
to je kompletna ideja koja stoji iza Spark-a, jer vam omoguava da radite stvari koje ne biste mogli da
radite na samostalnom raunaru
Spark je veoma skalabilan i njegova arhitektura izgleda ovako:

ono to vi razvijete su Driver Program-i


sve ono to budemo pisali na ovom kursu su Driver Program-i
oni su izgraeni oko jednog objekta koji se zove SparkContext, koji zapravo enkapsulira kompletan
hardware na kojem pokreete programe
na vrhu toga vi pokreete neki Cluster Manager, jedan je obzebeen samim Spark-om, dok na vrhu
Hadoop klastera moete koristiti YARN
takoe Apache Mesos je opcija

11
Cluster Manager je odgovoran za distribuiranje posla koji je definisan driver script-om izmeu
viestrukih vorova
svaki vor koji je pokrenut na svakoj maini ima Executor proces, koji ima svoj ke i njegovu listu
zadataka (eng. task) i moe da podeli te podatke na vitestruke izvrioce (eng. executors), tako da
ukoliko imate veliki skup podatka (eng. data set), moe da uzme jedan mali deo toga i postavi svaki od
ovih delova na jedan od vorova na vaem klasteru za paralelno procesiranje i kada klaster menader
(eng. cluster manager) shvati kako da rekombinuje sve te podatke i prosledi ih sledeem koraku ukoliko
je potrebno
lepota toga je to se sve sa desne strane ove arhitekture deava automatski vei deo toga
sve to treba da brinete kao Spark programer je logika kako ete da procesirate ove podatke, Spark
Cluster Manager je odgovoran tada za kako da distribuirate na efikasan nain

Spark je 100 puta bri od MapReduce ili 10 puta na disku


Spark ima DAG direktni aciklini graf (eng. directed acyclic graph) za optimizovanje njegovog
workflow-a
u Sparku se nita ne deava dok ne odarite komandu Ja elim da vidim rezultate i da neto uradim sa
njima.
jednom kada Spark vidi akciju kao tu, on e se vratiti i skapirati optimalni nain za kombinovanje svog
prethodnog koda zajedno i doi e do plana koji je optimalan za iznalaenje rezultata koji elite
veoma je razliita paradigma od MapReduce-a
dosta ljudi koristi Spark danas
Spark moete pisati u Python-u, Java-i ili Scala-i (i R je dodat)
Spark ima jedan glavni koncept RDD Resilient Distributed Dataset
RDD je apstrakcija nad ogromnim skupom podatka i vi samo uzmete ove RDD-eve transformiite ih i
primenite akcije nad njima i to je sve to ima u programiranju Spark-a da pokuate da shvatite
odgovarajuu strategiju kako da doete iz take A u taku B, gde imate skup ulaznih podataka i eljeni
skup rezultata
spark ima sledee komponenete:

Spark core radi sa osnova RDD-eva, transformie ih, skuplja njihove rezultate, zatim redukuje ih
(eng. reduce)
postoje neke biblioteke na vrhu Spark-a koje ine sloene operacije prostim
jedna od tih stvari je Spark Streaming to je tehnologija izgraena na vrhu koja moe da upravlja
malim delovima podataka kako oni dolaze u realnom vremenu, dakle vi moete da procesirate stream
podataka od mnogo Web servera ili npr. moda podatke od tone senzora od Internet of Things
aplikacija kako oni dolaze jednu sekundu u vremenu i koji nastavljaju da auriraju vae rezultate kako
idete u realnom vremenu i to je pokrenuto zauvek
Spark SQL omoguava jednostavan interfejs ka Sparku, moete otvoriti neto kao malu konekciju i
pokrenuti SQL upite nad masivnom koliinom podataka, to je veoma mona stvar ponekad
12
MLLib omoguava operacije mainskog uenja na masivnom skupu podatka
ukoliko je potrebno da uradite kao linearnu regresiju ili preporuivanje artikala prema ponaanju
korisnika, MLLib ima rutine koje to rade automatski i distribuiraju ih preko klastera
na kraju GraphX, ovde se radi o teoriji grafova, gde moete razmiljati o socijalnoj mrei gde imate vei
broj povezanih individua, koji su povezani meusobno na razliite naine, GraphX obezbeuje
framework za dobijanje informacija o atributima i svojstvima tog grafa, kao i generalni mehanizam
Pregel koji emo pogledati kasnije u kursu

Zato Scala ?
sam Spark je napisan u Scala-i
poslednji i najbolji Spark-ovi feature-i uvek prvo izlaze u Scala-i
dakle, Scala je najbolji izbor
daje najbolje performanse i pouzdanost i to je najjednostavniji kod u koji moete da gledate

09. The Resilient Distributed Dataset


RDD Resilient Distributed Dataset, apstrahuje sloenost i pokuava da upravlja otpornou na otkaze
(eng. fault tolerance)
RDD moete posmatrati kao enkapsulaciju velikog skupa podataka, nad kojim moete primenjivati
transformacije i akcije
sve o emu kao programer je potrebno da vodite rauna jeste o funkcijama koje izvode transformacije i
koje specifine akcije elite da primenite nad podacima
RDD uklanja kompletnu sloenost koju Spark radi za vas, pri emu se osigurava da je fault tolerant
otporan na otkaze, resiliant elastian, ukoliko jedan vor (eng. node) ode dole u vaem klasteru,
moe da se oporavi od toga i da nastavi od onog dela gde je stao
takoe obezbeuje da je distribuiran, tako da vodi rauna o svim detaljima kako zapravo deli podatke, i
pothranjuje vorove u klasteru
u osnovi on je Data Set skup podataka
RDD je ukratko, veliki skup podataka, red za redom informacija, i to mogu da budu linije sirovog teksta
ili mogu da budu klju-vrednost (eng. key-value) informacije
Odakle RDD-evi dolaze ?
kada kreirate Spark driver ili script, moete da kreirate neto to se zove Spark Context objekat
ukoliko pokreete spark shell, dobijate jedan automatski koji se zove sc
kada piete kod vi ete eksplicitno napisati spark context i vi ete rei spark context-u, OK ovo je
klaster koji ja pokreem, a ovo je ime mog posla (eng. job) koji pokreem
jednom kada imate Spark Context on kreira RDD-eve za vas
pogledajmo sada neke primere (sa slajda):
ovaj prvi primer ovde jeste hard-coder-irana lista integer-a
upotrebom parallize metode da kreira RDD iz toga
val nums = parallelize(List(1, 2, 3, 4))
sc.textFile(file:///c:/users/frank/gobs-o-text.txt)
ili s3n:// , hdfs://

hiveCtx = HiveContext(sc)
rows = hiveCtx.sql(SELECT name, age FROM
users)
dakle, to bi zapravo bilo seed parallelize
ovo je korisno u testne svrhe, ali nije namenjeno ozbiljnom radu
pored toga, moete uitati podatke iz nekog fajl sistema (eng. file system) i to moe da bude sa vaeg
lokalnog fajl sistema ili moe da bude iz distribuiranog fajl sistema, to je verovatnije u realnom
scenariju

13
u ovom primeru preuzimamo veliki tekstualni fajl sa hard diska, ali vi moete koristiti s3n ukoliko ste
skladitili na Amazon-u ili hdfs ukoliko ste koristili distribuiran hdfs sistem na Hadoop-u klasteru umesto
fajla
dakle, ukoliko imate veliki komad podataka koji je na nekom distribuiranom fajl sistemu, textFile e vas
pustititi da to uradite
takoe moete da integriete sa Hive-om, koji je drugi deo Hadoop ekosistema koji vas puta da
skladitite Big Data na nain koji je strukturisaniji
ukoliko imate Hive bazu podataka, vi moete da kreirate HiveContext sa datim SparkContext-om i
nakon toga koristite HiveContext SQL upite nad Hive podacima i to e vratiti RDD nad kojim moete da
sprovodite Spark operacije
postoji jo drugih izvora podataka (eng. data source) koji moete da integriete sa Spark-om
JDBC
Cassandra
HBase
Elasticsearch
JSON, CSV, sequence files, object files, razliiti kompresovani formati

jednom kada imate RDD, ta ete raditi sa istim ?


postoje 2 osnovne stvari koje moete raditi sa RDD-em:
o moete transformisati RDD - primeniti neke funkcije na podatke u RDD-u i time kreirati novi RDD
o primeniti akcije gde kaete ok elim da nekako uradim neto sa ovim, koje se redukuje na
krajnje rezultate koje ja elim da vratim na kraju
prvo emo priati o transformacijama (eng. transformation)
najea operacija koju moete da radite sa RDD-evima je funkcija map
o ono to map radi jeste da primenjuje funkciju na ceo RDD, ukoliko imate RDD pun podataka i
recimo da elite da izvuete jedno polje iz njega ili da primenite neko parsiranje ili nekako
multiplicirate sve, vi samo prosleujete funkcju map f-ji koja kae kako e se transformisati svaki
red u mojem RDD-u
o dakle, prolazite kroz RDD, jednu po jednu liniju i primenjujete vau funkciju na toj liniji i pravite
novi RDD koji je transformisan, ispod haube on e se distribuirati ukoliko je potrebno i nosie se
sa fault tolerance i td...
flatmap je slian koncept, stim da kod flatmap-e nije potrebno da imate jedan na jedan vezu izmeu
redova vaeg RDD-a sa kojim ste zapoeli i transformisanog RDD-a
o kod flatmap moete pogledati pojedinanu liniju u rei OK neu nita of toga u mom novom
RDD-u ili moete da imate vie njih u novom RDD-u
o dakle ukoliko nemate 1 na 1 vezu izmeu RDD-a sa kojim zapoinjete i onog koji je
transformisan, koristite flatmap
filter uzima boolean funkciju i govori elim da neto izbacim iz ovog RDD-a to ne pripada nekom
kriterijumu
o to je dobar nai da trimujete RDD i da ih uparite sa podacima koji vam trebaju
distinct samo vraa distinct redove, tako da ukoliko imate duplikate moete ih se reiti
sample kreira primerak (eng. sample) RDD-a i koristan je za testiranje
i na kraju moete primeniti operacije skupova na vaem RDD-u, kao to su:
o union
o intersection
o substract
o cartesian

Pogledajmo sada primer upotrebe map funkcije

14
o neka imamo hard-coder-iranu listu (1, 2, 3, 4)

val rdd = sc.parallelize(List(1, 2, 3, 4))


val squares = rdd.map(x => x * x)

o ono to ovde radimo, jeste da uzimamo Spark Context objekat sc i pozivamo parallelize nad
istim
o i ono ta ovaj kod radi jeste da proizvodi novi RDD i taj RDD sadri 4 reda 1, 2, 3, 4
o pogledajmo sada kako map radi: uzimamo rdd objekat i pozivamo map funkciju nad njim i kao
parametar funkcije map prosleujemo funkciju
o dakle, ova funkcija u zagradi map funkcije kae zapravo sledee prihvati kao ulaz vrednost x i
vrati x puta x
o dakle, prosleivanjem ove f-je koja kvadrira svaki red u RDD-u, squares RDD je transformisan i
sadri redove 1, 4, 9 i 16
o dakle, prosleujemo funkciju koja se primenjuje na svaki individualni red u vaem RDD-u i kreira
novi RDD kao rezultat
o dakle RDD se ne menja, ve se pravi novi RDD, dakle RDD je immutable

popriajmo sada o akcijama (eng. actions) koje se mogu primeniti na RDD-evima


o zapamtite da se u Spark-u nita ne deava dok ne pozovete akciju (eng. action) na RDD-u, kada
pozovete collect ili neku drugu akciju, Spark e pokuati da utvrdi koji je optimalni nain
izvravanja za kreiranje rezultata koje elite i u tom trenutku e kreirati direktni aciklini graf
DAG, izvriti ga na najoptimalniji nain i tada e vratiti rezultate. Sve akcije pokreu neto da se
zaista uradi u Sparku.
o ovo su neke od popolarnijih akcija koje moete da izvedete:
o collect skuplja sve podatke iz RDD-a i prosleuje ih nazad do vae driver script-e, gde moete
sa njima da radite
o count prebrojava koliko je redova u RDD-u
o countByValue pretrauje jedinstvene vrednosti u vaem RDD-u i vea vam broj koliko puta se
svaka vrednost pojavljuje, zapamtite da RDD moe da duplicira redove, to je sasvim u redu i vi
moete da primenite countByValue da prebrojite koliko ima jedinstvenih vrednosti
o take samo uzima nekoliko prvih redova iz RDD-a u svrhu da vidite kao izgleda RDD
o top ima takoe slinu ideju kao i take
o reduce omoguava vam da kombinujete sve razliite vrednosti koje su povezane sa datim
kljuem. Zamislite da imate parove klju-vrednost i elite da redukujte (eng. reduce) sve
vrednosti za dati klju, moda elite da ih saberete ili pomnoite ili neto tree i to sve reduce
omoguava
o lazy evaluation nite se zapravo ne deava u vaem driver program-u dok se ne pozove
akcija !

10. Ratings Histogram Walkthrough


vratimo se sada na RatingsCounter.scala program koji smo pisali na poetku kursa
sada emo malo dublje objasniti ta se deava u ovom programu
ova skripta prolazi kroz 100000 rejtinga filmova i prebrojava distribuciju razliitih rejtinga (ocena)
filmova
dakle imamo 6110 rejtinga sa 1 zvezdicom, 11370 sa 2 zvezdice, i td...

15
u prvoj liniji koda definiemo u kojem paketu ovaj kod ivi
o package com.sundogsoftware.spark
ovo je standardan nain imenovanja paketa tako da na kod ne bude u konfliktu sa bibliotekama drugih
ljudi
sledee emo import-ovati specifine biblioteka koje su nam potrebne za ove skripte
import-ovaemo sve to nam je potrebno iz spark biblioteke, specifino: SparkContext objekat, takoe
log4j paket koji radi sa log-ovima
o import org.apache.spark._
o import org.apache.spark.SparkContext._
o import org.apache.log4j._
kao to vidimo Logger koristimo samo da oznaimo Spark-u da loguje ukoliko doe do greaka
o Logger.getLogger("org").setLevel(Level.ERROR)
prva linija samog Spark koda je sledea
o val sc = new SparkContext("local[*]", "RatingsCounter")
SparkContext obejkat se koristi za kreiranje RDD-eva, tako da je to prva stvar koju je potrebno da
uradimo
dakle, kreiramo novi SparkContex objekat koji smo nazvali sc i to je immutable Spark Context objekat
ova zvezda u pravougaonim zagradama oznaava da emo kod pokreuti na lokalnoj maini, a ne u
klasteru, a sama zvezda oznaava da ,pemo da koristimo sva jezgra na naem CPU na maini, da
distribuiramo to lokalno ukoliko je to potrebno
ukoliko imate viejezgarni CPU ovaj kod moe da se distribuira izmeu jezgara
drugi parametar SparkContext objekat, je nita drugo do ime Spark Context-a
dalje, prvo to radimo jeste da uzimamo taj Spark Contex objekat i pozivamo tekstualni fajl nad njim sa
putanjom gde se nalazi taj tekstualni fajl sa podacima
u.data je fajl koji sadri aktuelne rejtinge podataka i importujemo jednu liniju po jednu u novi RDD koji
se zove lines
o val lines = sc.textFile("../ml-100k/u.data")
dakle, lines je dataset gde svaki red sadri jedan red ulaznih podataka
oni izgledaju na sledei nain:
196 242 3
881250949
186 302 3
891717742
22 377 1
878887116

vidimo da je format sledei USER ID, MOVIE ID, RATING i TIMESTAMP


interpretacija neke linije bi bila recimo: USER ID 196, gledao je film 242, ocenio ga je sa 3 zvezdice u
odreeno vreme (poslednja kolona)
dakle, importovali smo sirove podatke i dobili smo RDD koji se zove lines u ovom trenutku
sledee ono to elimo da uradimo jeste dobra praksa, a to je da odbacimo sve informacije koje nam
nisu potrebne
dosta vremena i resursa Spark job zahteva, to je naradno povezano sa koliko podataka radi, tako da
to pre se reite podataka koji vam nisu potrebni, tim je bolje
i to je upravo ono to radimo ovde

16
o val ratings = lines.map(x => x.toString().split("\t")(2))
pozivamo funkciju map nad lines RDD-em, da bi smo ga transformisali u novi RDD koji se zove ratings,
koji parsira svaku liniju od ovih linija i samo izvlai polja koje su nam potrebne, a to je tree polje,
zapravo vrednost samog rejtinga
zapamtite da na krajnji rezultat je - koliko puta se svaki rejting pojavio, tako da ne moramo da brinemo
o korisnicima filova ili time stamp-ovima, sve o emu brinemo jesu ove vrednosti rejting-a
vidimo da pozivamo map funkciju da transformie taj ulazni RDD lines i prosleujemo ga ovoj funkciji u
zagradi da ga transformie, dakle ta linija koda znai sledee uzmi svaku pojedinanu liniju x i
konvertuj je u string, zatim je podeli u listu zasnovano na karakteru TAB (zato to je ovaj fajl TAB
delimited) i izvuci polje broj 2 iz te liste
dakle, zapoeli smo brojanje od nule tako da je polje 0 USER ID, polje 1 je MOVIE ID, a polje 2 je
RATING i to je ono o emu brinemo
dakle, ova funkcija se primenjuje na svaku liniju ulaznog fajla i mi vraamo na RDD koji sadri ocene
(rejtinge) i koji izgleda ovako
3
3
1
2
1

dakle, sada imamo ratings RDD gde svaki red je zapravo ocena (vrednost rejtinga) za svaki individualni
rejting
ok, ta dalje ?
krajnji rezultat koji elimo jeste da broj svake ocene (rejtinga) i koliko puta se dogodilo i Spark ima
korisnu funkciju koja se zove countByValue, koja e to odraditi za nas
zapamtite da je countByValue akcija i da kada je pozovete, to e uzrokovati Spark da ode i da srauna
direktni aciklini graf i da zapravo zapone da distribuira podatke ukoliko moe i ba kraju emo dobiti
ovo
3
3 (3, 2)
1 (1, 2)
2 (2, 1)
1

dakle, countByValue vraa, prihvata RDD, ali vraa Scala map objekat, gde zapravo mapa prikazuje
koliko puta su se odreene ocene desile
u ovom primeru zapoinjemo sa RDD-em koji zapoinje sa samim rejtinzima (ocenama), countByValue
nam vraa Scala map, sa kojo moemo da radimo ta poelimo i koja samo mapira vrednosti rejtinga
koliko puta su se dogodili (npr. ocena, tj. rejting 3 se pojavio 2 puta, rejting 1 se pojavio 2 puta, ...)
sada ono to je potrebno jeste da prikaemo rezultate na odgovarajui nain
prvo emo konvertovati nau mapu u sekvencu, jer je to neto to moemo da sortiramo
o val sortedResults = results.toSeq.sortBy(_._1)
i to emo sortirati po prvom polju
nakon toga moemo da uzmemo tu sortiranu sekvencu i pozovemo for each nad njom i prikaemo
svaki red i to je ono to tampa svaki pojedinani red rezultata u sortiranom redosledu i na finalni
rezultat su: ocena (rejting) 1 se pojavio 2 puta, rejting 2 se pojavio 1 put a rejting 3 se pojavio 2 puta

o sortedResults.foreach(println)

12
21
32

17
11. Spark internals
pogledajmo sada ta se deava ispod haube sa ovim primerom histograma ocena iz skup podataka o
ocenama filmova
dobra stvar da razumete onoga to se deava ispod haube ne samo iz akademskih razloga, ve da
kreirate i struktuirate Spark skripte koje mogu da se izvravaju najoptimalnije
pogledajmo sada to na primeru RatingsCounter.scala iz prethodne lekcije

ono to se zapravo dogaa kada pozovemo countByValue nad RDD-em, to je poslednji korak u naoj
skripti, to je zapravo akcija nad RDD-em, to uslovljava Spark da se vrati i zapravo skapira plan
izvrenja kako da doe do rezultata koji smo traili
dakle ono to se deava jeste da prati sve stvari koje smo ulanali zajedno iz razliitih RDD-eva i kako
su oni povezani izmeu sebe i na osnovu te informacije on kreira DAG direktni aciklini graf, u ovom
sluaju moe biti jedan veoma jednostavan, koji izgleda kao na slici
zapoinjemo sa textFile komandom putem koje importujemo gomilu sirovih podataka u RDD, nakon
toga map parsira informacije koje su nam vane to su zapravo same ocene filmova i na kraju
zovemo akciju countByValue da sumira sve razliite brojeve za svaki tip rejtinga (ocene), dakle to je
plan izvrenja
ono to je potrebno da shvatite jeste da sam map imate 1 na 1 vezu izmeu svakog ulaznog i izlaznog
reda RDD-eva, tako da sve moemo da drimo particionisano na isti nain u tom koraku, jer se to
veoma lako izvodi na distribuiran nain
ta se deava kada pozovemo countByValue ? moda je potrebno da promeamo i pomeamo malo
stvari ovde dakle ovde stvari postaju malo komplikovanije i ova operacije se zove shuffle (promeati)
operacija u Sparku i to moe izazvati u Sparku da mora da pogura gomilu podtaka na vaem klasteru i
to moe da bude veoma skupa operacija
u mnogo situacija kada optimizujete Spark job-ove, potrebno je da razmiljate kako da umanjite shuffle
operacije i kako da stvari ostanu paralelizovane kao kada mapiramo stvari
Dakle, jednom kada Spark ima plan izvrenja, i kada je potrebno da promea (shuffle) podatke, tada je
potrebno da razdvoji korake (stage-ove)

18
u tom trenutku Spark e rastaviti ovaj job na 2 stage-a: gde e se u prvom tekstualni fajl uitati i
mapiraemo da izvuemo podatke koji su potrebni i to sve moe biti pokrenuto paralelno
kada doemo do countByValue stvari su potrebne da se promeaju, i to je potreno da bude izvreno
kao korak 2
koraci (stage) se kreiraju zasnovano na delovima procesiranja koji mogu da budu uraeni paralelno bez
ponovnog premetanja (shuffle) podatka
kada pokrenete Spark job on e vam dati informaciju ukoliko je distribuiran, zatim prikazae koji korak
(eng. stage) je pokrenut i o emu priamo
jednom kada imamo korake (eng. stages) Spark e podeliti te korake u zadatke (eng. task)
to je zapravo gde stvari zapravo postaju distribuirane
zadatak (eng. task) zapravo razbija paralelizovane zadatke u diskretne delove koji mogu da budu
procesirani individualno i paralelno
dakle, zapoinjemo sa planom izvravanja, plan izvravanja se razbija na stage-ove (zasnovano na
osnovu stvari koje se mogu procesirati paralelno), i koraci (eng. stages) se razbijaju na zadatke koji su
distribuirani na individualnim vorovima na vaem klasteru
nakon toga Spark samo ide i radi
tada je red na Cluster Manager-a da preuzme podatke i sakupi ih tamo gde je potrebno da budu na
kraju i vaa driver script-a i maina se pokree i vi dobijate rezultate dakle, to je na visokom nivou
kako Spark radi interno

12. Key / Value RDDs, and Average Friends by Age example


svaku put kada imate potrebu da agregirate informacije u neki klju vrednost (eng. key value), tada
ete koristiti key-value RDD
pretpostavimo sada da imamo sluaj u kojem je potrebno da pronaemo prosean broj prijatelja po
godinama za neku lanu socijalnu mrenu koju imamo
pretpostavimo da imamo skup podataka (eng. dataset) u kojem svaka osoba ima godine i koliko
prijatelja ima sa jo nekim informacijama koje moemo da imamo
sada ukoliko elim da utvrdim prosean broj prijetalja po godinama, potrebno je nekako da agregiram
sav broj prijatelja za svaku zadatu godinu
u ovom sluaju klju je stvar koju elimo da agregiramo po godinama i vrednost e biti stvari koje
agregiramo, broj prijatelja
dakle, elimo da agregiramo zajedno ove brojeve prijatelja po godinama i da zavrimo koji je prosean
broj prijatelja za dvadesetogodinake, tj. ima klju 20, koji je prosean broj prijatelja koji imaju 33
godine, tj. za klju 33 i td..
kako to radi
sintaksno ono to je potrebno jeste da predstavimo vrednosti, i to ne samo kao jednu vrednost ve kao
torku (eng. tuple) od 2 vrednosti
dakle, u prethodnom primeru smo imali RDD gde je svaki red predstavljao liniju input text-a, svaki red je
predstavljao integer
takoe moete predstaviti torku (eng. tuple) i ukoliko torka sadri 2 objekta tada Spark moe to
automatski tretirati kao klju vrednost (eng. key-value) par
postoje neke specijalne stvari koje moete uraditi sa tom informacijom
pogledajmo sada sledei kod:
o totalsByAge = rdd.map(x => (x, 1))
u ovom sluaju uzimamo RDD koji sadri jednu informaciju po redu i transformiemo ga u klju
vrednost (eng. key value) RDD
sve ove transformacije funkcija radi mapiranjem RDD-a gde je svaka linija x, transformisna u torku
(eng. tuple) (x, 1)
dakle, to je kreiraje para klju vrednost od orginalne vrednosti x-a i vrednosti koja je 1 za svaki red
sada imate key-value RDD tako to skladitite torke sa 2 stvari u njima i zapravo vi moete da
skladitite ak i komplikovanije strukture podataka
19
ukoliko imate torku (eng. tuple), gde imate neke vrednosti kao klju u nekoj kompleksnijoj torki (eng.
tuple) kao vrednost, dakle vi moete da skladitite informacije na struktuiran i komplikovan nain kako
elite, ali na kraju dokle god imate 2 stvari na najviem nivou (klju, vrednost), to se rauna kao klju
vrednost RDD
sada kada imate key value RDD, moete uraditi gomilu stvari
jedna esta operacije je reduceByKey
o rdd.reduceByKey((x, y) => x + y)
ona omoguava da obezbedite bilo koju funkciju koju elite da iskombinujete vrednosti zajedno i da ih
grupiete po kljuu
u ovom primeru ovde, uzimamo torku (x, y) i transformiemo je u x + y
x, y ovde ne predstavljaju par klju vrednost, ve predstavlja 2 vrednosti koje je potrebno da budu
kombinovane zajedno
reduceByKey elim funkciju koja e prihvatiti 2 vrednosti za isti klju i definisati kako e se ove stvari
kombinovati zajedno u novi RDD
dakle, recimo da elim da dodam broj prijatelja za svakog ko je star 20 godina, to mogu da dobijem za
klju 20, klju 20 se nikad ne menja i bie automatski prebaen u moj novi RDD, ali ono ta se menja je
vrednost, gde u agregirati zajedno sve vrednosti za klju 20
dakle ova f-ja kae za 2 vrednosti za isti klju kako u ih iskombinovati zajedno, u ovom sluaju elim
da ih saberem zajedno i uzeemo svaku vrednost za zadati klju i sabraemo ih, dve u trenutku
zato dve u trenutku ? setite se, ovo moe da bude distribuirano, tako da ne moemo sve da uzmemo
odjednom i da ih saberemo u jednoj velikoj funkciji, ve moramo po delovima, jedan po jedan
potrebno je da kaemo Spark-u dati ukupan zbir i neka nova vrednost, kako da ih kombinujem zajedno,
i to je ono to ova funkcija radi
takoe moemo da urdimo groupByKey() ukoliko ne elite da agregirate sve ove vrednosti zajedno za
dati klju, moete ih grupisati u jednu veliku O listu
dakle ukoliko elim samo da sakupim zajedno sve vrednosti za dati klju, groupByKey() e vratiti RDD
koji je upravo to to e vratiti klju, vrednost gde ete koristiti svaki jedinstveni klju i vrednosti e biti
liste svih vrednosti povezane sa tim kljuem
moemo da koristimo sortByKey(), koji e sortirati RDD prema vrednostima kljueva
takoe moete vratiti pojedinaan RDD, samo kljueve ili samo vrednosti i funkcije keys() i values() e
to uraditi za vas

neke od drugih stvari koje moete uraditi su: SQL stil JOIN-ove ukoliko imate key-value parove
(razmislite o tome (key, value) RDD je poprilino kao NoSQL baza podatka, zar ne i moemo da
uradimo neke stvari kao sa bazom, obzirom da sada imamo malo strukturisanije podatke, moete da
uradite razliite JOIN-ove, razliite flavours,
ukoliko ete samo mapirati samo vrednosti u vaem key-value RDD-u i elite da kljueve (eng, keys)
ostavite samostalne, bie mnogo jednostavnje da koristite funkcje mapValues() i flatMapValues(),
umesto map() i flatMap()

Preimo sada na primer:


pretpostavimo sada da imamo neke podatke socijalne mree i svaki red sirovih informacija izgleda
ovako:
0,Will,33,385
1,Jean-Luc,26,2
2,Hugh,55,221
3,Deanna,40,46
5
4,Quark,68,21

dakle imamo redove informacija, koje su podeljenje zarezom (eng. comma) i sastoje se od: USER ID,
USER NAME, USER AGE, USER NUMBER OF FRIENDS

20
dakle ono to mi pokuavamo da uradimo jeste da pokuamo da naemo prosean broj prijatelja za
date godine
u ovom sluaju imamo Will a koji ima 33 godine i td...., takoe Jean-Luc ima 33 godine, tako da
nekako elimo da iskombinujemo ova 2 user-a i da vidimo koliki je njih prosean broj prijatelja

21

You might also like