Sono disponibili le soluzioni per alcuni degli esercizi proposti.
Scrivere un metodo Scala applicaDueVolte che, dati come parametri una funzione f:Int=>Int e un intero x, calcola f(f(x)). Deve essere possibile eseguire questo programma:
val y = E1.applicaDueVolte(x=>x+1, 10)
println(y) // stampa 12
Modificare il metodo applicaDueVolte dell’esercizio 1 usando la tecnica della currificazione in modo che sia parzialmente applicabile. Deve essere possibile eseguire questo programma:
val incrementaDueVolte = E1.applicaDueVolte(x=>x+1) _
val y = incrementaDueVolte(10)
println(y) // stampa 12
Scrivere un metodo Scala che verifica se un numero è primo.
Scrivere un metodo Scala che calcola l’n-esimo numero di Fibonacci F(n), dove F(1)=1, F(2)=1, F(3)=2, F(4)=3, F(5)=5, ecc.
Scrivere un metodo Scala concatena che, date tre funzioni f1:Double=>Double, f2:Double=>Double e f3:Double=>Double e due valori Double a e b con a<=b, restituisce una funzione che coincide con f1 per tutti gli argomenti prima di a, con f2 nell’intervallo [a,b], e con f3 dopo b.
Scrivere un metodo Scala equalInRange che, date due funzioni Int=>Int e due interi a e b, verifica se le due funzioni sono uguali nell’intervallo [a,b].
Scrivere un metodo Scala findIndices che, data una lista generica l e un elemento x, restituisce una lista che contiene gli indici i tali che l(i)==x. Se la lista non contiene x, il metodo restituisce una lista vuota.
Scrivere un metodo Scala isSorted che, data una lista di interi, verifica se è ordinata in modo non decrescente. Suggerimento: usare il metodo sliding. Elaborare sia una versione ricorsiva che non ricorsiva basata sui metodi funzionali sulle liste.
Scrivere un metodo Scala consecutivi che, data una lista generica di n elementi, restituisce la lista di n-1 coppie che contiene tutti gli elementi consecutivi della lista di ingresso. Ad esempio, se in ingresso viene dato List(3,6,5,7), il metodo deve produrre List((3,6),(6,5),(5,7)). Se la lista di ingresso contiene meno di due elementi, la funzione deve produrre la lista vuota.
Scrivere un metodo Scala generico removeDuplicates[T](l:List[T]):List[T] che crea una nuova lista ottenuta da l rimuovendo gli elementi duplicati.
Scrivere un metodo Scala generico union[T](a:List[T], b:List[T]):List[T] che genera la lista ottenuta come unione delle liste a e b. La lista prodotta non deve contenere duplicati.
Scrivere un metodo Scala max che, data una lista di stringhe, restituisce la lunghezza della stringa più lunga.
Suggerimento: usare map e reduce
Scrivere un metodo Scala ricorsivo equal che verifica se due liste di interi sono uguali (uguaglianza profonda).
Suggerimento: usare head, tail e isEmpty
Scrivere un metodo Scala ricorsivo mymap che, data una lista e una funzione, si comporta come la map predefinita delle liste. Esempio di uso:
val l = List(3,1,2,4,5)
println(mymap(l, _+1)) // stampa 4,2,3,5,6
Scrivere un metodo Scala ricorsivo minMax che, data una lista non vuota di interi, restituisce una coppia (min,max), dove min e max sono il minimo e il massimo elemento della lista, rispettivamente. Nota: data una coppia t=(a,b), t._1 è il primo elemento, t._2 è il secondo elemento.
Scrivere inoltre una variante non ricorsiva basata sui metodi standard della classe List visti a lezione.
Un sensore misura il numero di veicoli che passano in un tratto stradale in ogni minuto, producendo una lista di interi. Occasionalmente, il sensore smette di funzionare e richiede manutenzione. Quando il sensore smette di funzionare, produce un intero negativo e da quel momento in poi i numeri prodotti sono inattendibili.
Scrivere un metodo Scala mediaVeicoli(l:List[Int]):Double che calcola il numero medio di veicoli al minuto, ignorando tutti i valori a partire dal primo negativo. Il metodo deve essere realizzato in uno stile funzionale utilizzando i metodi di ordine superiore delle liste (e non iterazione o ricorsione).
Suggerimento: usare il metodo span delle liste come documentato in http://www.scala-lang.org/api/current/#scala.collection.immutable.List. Per il cast a double, usare il metodo toDouble.
Un semplice algoritmo iterativo basato sul metodo di Newton per calcolare la radice quadrata di un numero x consiste nel partire da una stima iniziale y (es. y=1) e raffinarla successivamente calcolando la media tra y e x/y. Il procedimento termina quando il quadrato della stima è sufficientemente vicino a x(es. inferiore a 0.01). Completare la seguente definizione Scala usando un approccio ricorsivo:
def sqrt(x:Double) = {
// completare...
}
println("Radice quadrata di 2 ~ "+sqrt(2))
Suggerimento: scomporre sempre un problema in funzioni più semplici. Questo non peggiora in pratica le prestazioni poiché il compilatore effettua inlining automaticamente, ma migliora molto la leggibilità, l’analisi di correttezza e la manutenibilità del codice. Definire funzioni annidate (es. per il calcolo del valore assoluto, ecc.).
Scrivere un metodo Scala filterByIndex (ricorsivo o meno) che, data una lista generica e un predicato su indici, restituisce la sottolista ottenuta prendendo solo gli elementi il cui indice soddisfa il predicato dato.
Esempio di uso:
filterByIndex(List("zero", "uno", "due", "tre"), i=>i%2==0) // prende solo gli elementi di indice pari
Scrivere un metodo Scala query(studenti:List[(String,Int)], esami:List[(String,String)]):Int che, data una lista studenti di coppie (studente, età) e una lista esami di coppie (studente, esame), restituisce l’età degli studenti che hanno svolto il maggior numero di esami.
Ad esempio, se:
studenti = List(("Paola", 21), ("Luca", 22), ("Lucia", 21), ("Matteo",22))
esami = List(("Paola","PFP"), ("Luca","SC"), ("Paola","DB"), ("Lucia","LTW"), ("Matteo","SO"), ("Lucia","PFP"))
il risultato deve essere 21, infatti Paola e Lucia, che hanno 21 anni, hanno svolto complessivamente 4 esami, mentre Luca e Matteo, che hanno 22 anni, ne hanno svolti 2.
Suggerimento: usare metodi sulle liste (map, distinct, etc.)
Scrivere un metodo Scala longestSublist[T](p:T=>Boolean)(l:List[T]) che, dato un predicato p e una lista l, restituisce la più lunga sottolista di elementi consecutivi di l che soddisfano il predicato p. Se la soluzione non è unica, restituirne una qualsiasi. Ad esempio, longestSublist((_:Int)>0)(List(-4,5,3,6,0,3,4,-1)) deve restituire List(5,3,6), che è la sottolista di elementi consecutivi positivi più lunga.
Suggerimento: usare il metodo foldLeft, consultando la documentazione della classe List.
Scrivere un metodo Scala generateSeq che, dato un intero non-negativo n, un seme iniziale intero x e una funzione f:Int=>Int, restituisce la lista l di n interi tale che l(0)=x e l(i+1)=f(l(i)). Ad esempio, generateSeq(5, 1, _*2) restituisce List(1,2,4,8,16).
Scrivere un metodo Scala query(studenti:List[(String,Int)], esami:List[(String,String)], e:Int):String che, data una lista studenti di coppie (studente, età), una lista esami di coppie (studente, esame) e un’età e, restituisce il nome dell’esame svolto dal maggior numero di studenti di età non superiore a e.
Ad esempio, se:
studenti = List(("Paola", 19), ("Luca", 23), ("Lucia", 21), ("Matteo",22), ("Francesca",23))
esami = List(("Paola","PFP"), ("Luca","SC"), ("Paola","DB"), ("Lucia","SC"), ("Matteo","LTW"), ("Lucia","PFP"), ("Francesca","SC"))
e = 22
il risultato deve essere "PFP". Infatti Paola, Lucia e Matteo, che hanno non più di 22 anni, hanno svolto complessivamente: PFP 2 volte, DB 1 volta, LTW 1 volta, e SC 1 volta.
Il nome Scala è un acronimo di SCAlable LAnguage, che riflette il design del linguaggio orientato all’estensibilità. In Scala è possibile introdurre nuovi costrutti sfruttando il meccanismo del passaggio dei parametri per nome e la flessibilità nell’invocazione dei metodi senza dover mettere argomenti tra parentesi.
Questo esercizio richiede di definire un costrutto funzionale Scala profila { E } che valuta l’espressione E e restituisce una coppia (v,t) dove v è il valore ottenuto dalla valutazione di E e t è il tempo in nanosecondi richiesto dalla valutazione di E. Usare il seguente programma di prova:
import E23._
object E23Main extends App {
val (v,t) = profila {
println("Valutazione espressione...")
(1 to 1000000).map(_.toLong).sum
}
println("Valore prodotto: "+v+" (corretto: 500000500000)")
println("Tempo richiesto: "+t*1E-9+" secondi")
}
Suggerimento: per misurare il tempo, usare System.nanoTime.
Completare il seguente frammento di programma Scala:
case class Studente(nome:String, età:Int, esami:List[String])
val q = List(
Studente("Marco", 23, List("PFP","SC")),
Studente("Laura", 19, List("SC", "FI1", "PFP")),
Studente("Stefano", 23, List("SC", "FI1")),
Studente("Marco", 25, List("SC", "FI1", "FI2")),
Studente("Paola", 22, List("SC", "PFP"))
)
val query1 = // estrarre tutti gli studenti con età compresa tra 20 e 24 anni (inclusi) che hanno sostenuto l'esame di "PFP"
// stampare i dati degli studenti in query1
val query2 = // estrarre tutti gli studenti con età strettamente inferiore a 24 anni che hanno sostenuto almeno due esami
// stampare i dati degli studenti in query2
Provare diverse soluzioni usando:
for ... yield)Scrivere una classe Scala Rational che rappresenta numeri razionali della forma num/den, dove num e den sono interi. Scrivere la classe in modo che sia possibile eseguire il seguente codice Scala:
object E25Main extends App {
val r1 = Rational(2, 7)
val r2 = Rational(8, 6)
val r3 = Rational(4, 14)
println(r1+r2) // stampa 34/21
println(r1-r2) // stampa -22/21
println(r1*r2) // stampa 8/21
println(r1/r2) // stampa 3/14
println(r1==r3) // stampa true
println(r1!=r3) // stampa false
println(r1!=r2) // stampa true
println(r1<r2) // stampa true
println(r2<r1) // stampa false
}
Suggerimento: calcolare il massimo comun divisore di numeratore e denominatore per riportare la frazione ai minimi termini (o irriducibile). Ad esempio, 4/14 dovrebbe essere memorizzato in un oggetto Rational come 2/7.
Completare il metodo Scala getGrid dell’object Model2D seguente in modo che costruisca un modello 2D che raffigura una griglia n x n. La figura è creata utilizzando esclusivamente segmenti di linea. La figura deve essere confinata nello spazio quadrato bidimensionale di lato unitario delimitato dall’origine degli assi fino al punto di coordinate (1,1). La figura viene poi scalata automaticamente dal modulo di visualizzazione per occupare la dimensione della finestra. Si ricordi che da Scala è possibile accedere a tutte le funzioni delle librerie Java (es. quelle della classe Math, se dovessero servire).
Per compilare da riga di comando usare: scalac Frame2D.scala Shape.scala Model2D.scala.
Model2D.scala:
import java.awt.Color
object Model2D {
def getGrid(n:Int) = {
val RED = new Color(0xFF0000) // in formato RGB (Red-Blue-Green) o anche Color.RED
// completare costruzione di un modello 2D di una griglia con n linee verticali ed n linee orizzontali
List(
Line(0.0,0.5,1.0,0.5,RED), // linea rossa
Line(0.5,0,0.5,1.0) // linea colore default (nero)
)
}
def main(args:Array[String]) {
println("Displaying 20x20 grid...")
Frame2D.display(Model2D.getGrid(20), 500, 500)
}
}
Shape.scala:
import java.awt.Color
// classe astratta che rappresenta elementi grafici 2D
sealed abstract class Shape
// cerchio di raggio r centrato in (x,y) di colore c
case class Circle(x:Double, y:Double, r:Double, c:Color = Color.RED) extends Shape
// segmento di linea da (x1,y1) a (x2,y2) di colore c
case class Line(x1:Double, y1:Double, x2:Double, y2:Double, c:Color = Color.RED) extends Shape
// si veda https://docs.oracle.com/javase/7/docs/api/java/awt/Color.html
// per la documentazione sui colori
Frame2D.scala:
import java.awt.{EventQueue,Graphics,Graphics2D,Dimension}
import javax.swing.{WindowConstants,JFrame,JPanel}
object Frame2D {
def display(l:List[Shape], width:Int, height:Int) {
EventQueue.invokeLater(new Runnable() {
override def run() {
val ex = new Frame2D(l,width,height)
ex.setVisible(true)
}
})
}
}
class Frame2D(l:List[Shape], width:Int, height:Int) extends JFrame {
class Surface2D extends JPanel {
def doDrawCircle(c:Circle, g2d:Graphics2D) {
val wc = (2*width*c.r).toInt
val hc = (2*height*c.r).toInt
val xc = (width*(c.x-c.r)).toInt
val yc = (height-height*(c.y-c.r)).toInt - hc
g2d.setColor(c.c)
g2d.drawOval(xc, yc, wc, hc)
}
def doDrawLine(l:Line, g2d:Graphics2D) {
val x1 = (width*l.x1).toInt
val y1 = height-(height*l.y1).toInt
val x2 = (width*l.x2).toInt
val y2 = height-(height*l.y2).toInt
g2d.setColor(l.c)
g2d.drawLine(x1, y1, x2, y2)
}
def doDrawing(g:Graphics) {
g match {
case g2d:Graphics2D =>
l foreach (_ match {
case c:Circle => doDrawCircle(c, g2d)
case l:Line => doDrawLine(l, g2d)
})
}
}
override def paintComponent(g:Graphics) {
super.paintComponent(g);
doDrawing(g);
}
}
val panel = new Surface2D()
panel.setPreferredSize(new Dimension(width, height))
add(panel);
setTitle("Shapes");
setResizable(false)
pack();
setLocationRelativeTo(null);
setDefaultCloseOperation(WindowConstants.EXIT_ON_CLOSE);
}
Completare il metodo getToroidalMandala dell’object ToroidalMandala seguente in modo che costruisca un modello 2D del Mandala toroidale a 24 “petali” mostrato in figura.
La figura è creata utilizzando esclusivamente cerchi. La figura deve essere confinata nello spazio quadrato bidimensionale di lato unitario delimitato dall’origine degli assi fino al punto di coordinate (1,1). La figura viene poi scalata automaticamente dal modulo di visualizzazione per occupare la dimensione della finestra. Si ricordi che da Scala è possibile accedere a tutte le funzioni delle librerie Java (es. Math.sin).
Approfondimento. I Mandala sono dei disegni di grande suggestione che rivestono un significato spirituale e rituale sia nel Buddhismo che nell’Hinduismo. Il Mandala (dal sanscrito maṇḍala (मण्डल), tradotto come “cerchio”) rappresenta, secondo i buddhisti, il processo mediante il quale il cosmo si è formato dal suo centro. Attraverso un articolato simbolismo, il Mandala consente una sorta di viaggio iniziatico che permette di crescere interiormente.
Usare il seguente programma di prova e le classi Shape e Frame2D dell’esercizio 26.
Model2D.scala:
object Model2D {
def getToroidalMandala(numPetals:Int) = {
// completare costruzione di un modello 2D di un Mandala toroidale
List(Circle(0.5, 0.5, 0.5))
}
def main(args:Array[String]) {
println("Displaying Toroidal Mandala...")
Frame2D.display(Model2D.getToroidalMandala(24), 500, 500)
}
}
Il cifrario di Cesare è un antico algoritmo di cifratura che consente nel sostituire ciascuna lettera con la corrispondente lettera a una certa distanza prefissata k>=0 nell’alfabeto. Ad esempio, la cifratura a distanza k = 1 di "zorro" è "apssp". Più precisamente, la funzione di codifica a distanza k del codice ASCII c di un carattere minuscolo dell’alfabeto inglese (26 caratteri) è f(c, k) = 'a' + (c - 'a' + k) MOD 26.
L’idea viene riportata già da Svetonio in Vite dei Cesari (56, I): “Extant et ad Ciceronem, item ad familiares, id est sic structo litterarum ordine, ut nullum verbum effici posset: quae si qui investigare et persequi velit, quartam elementorum, id est D pro A et perinde reliquas commutet”.
Definire un metodo Scala def rot(k:Int):String applicabile su stringhe che applica il cifrario di Cesare a distanza k, lasciando invariati i caratteri non alfabetici. Ad esempio, "zero uno due...".rot(1) deve restituire "afsp vop evf...". Si consideri il seguente programma di prova:
import E28._
object E28Main extends App {
def p(s:String, r:String, k:Int) =
println("\"" + s + "\" -> \"" + s.rot(k) + "\" (corretto: \""+ r + "\")")
val (s1, r1, k1) = ("Zorro", "Apssp", 1)
val (s2, r2, k2) = ("Apssp", "Zorro", 26-1)
val (s3, r3, k3) = ("Hello World!", "Uryyb Jbeyq!", 13)
val (s4, r4, k4) = ("Uryyb Jbeyq!", "Hello World!", 26-13)
p(s1, r1, k1)
p(s2, r2, k2)
p(s3, r3, k3)
p(s4, r4, k4)
}
Suggerimento: usare il metodo map applicato alle stringhe, in cui è possibile “sostituire” un carattere con un altro. Le operazioni aritmetiche su caratteri lavorano sui loro codici ASCII convertiti a Int. Per riportare i codici ASCII così manipolati di nuovo a carattere si usi toChar.
Scrivere un metodo Scala sum che, dato un albero binario con elementi interi, restituisca la somma degli elementi dell’albero. Completare il seguente frammento di codice:
sealed abstract class BinTree {
def sum = 0 // completare...
}
// albero non vuoto
case class T(l:BinTree, e:Int, r:BinTree) extends BinTree
// albero vuoto
case class E() extends BinTree
Deve essere possibile eseguire il seguente programma di prova:
object E29Main extends App {
val t1 = T(E(), 10, E())
val t2 = T(E(), 5, E())
val t3 = T(t1, 7, t2)
val t4 = T(t3, 2, E())
println(t1.sum + " (corretto: 10)")
println(t2.sum + " (corretto: 5)")
println(t3.sum + " (corretto: 22)")
println(t4.sum + " (corretto: 24)")
}
Scrivere un metodo Scala map su alberi binari con elementi interi che, data una funzione f:Int=>Int, restituisca un nuovo albero ottenuto da quello di partenza applicando la funzione f a ogni elemento presente nei nodi dell’albero. Completare il seguente frammento di codice:
sealed abstract class BinTree {
def map(f:Int=>Int):BinTree = E() // completare...
}
// albero non vuoto
case class T(l:BinTree, e:Int, r:BinTree) extends BinTree
// albero vuoto
case class E() extends BinTree
Deve essere possibile eseguire il seguente programma di prova:
object E30Main extends App {
val t1 = T(E(), 10, E())
val t2 = T(E(), 5, E())
val t3 = T(t1, 7, t2)
val t4 = T(t3, 2, E())
val t = t4.map(_*2)
println(t + " (corretto: T(T(T(E(),20,E()),14,T(E(),10,E())),4,E()))")
}
Option)Si scriva un metodo Scala def piùFrequente[T](l:Seq[T]):Option[(T,Int)] che, data una sequenza di elementi generici, restituisce un Option che contiene una coppia (x,n), dove x è l’elemento più frequente ed n il suo numero di occorrenze nella sequenza, e None se la sequenza di input è vuota.
Deve essere possibile eseguire il seguente programma di prova:
object E31Main extends App {
val s1 = Seq(1,2,4,2,1,1,5,2,4,2,5)
val t1 = E31.piùFrequente(s1) getOrElse "errore"
println(t1 + " (corretto: (2,4))")
val s2 = Seq()
val t2 = E31.piùFrequente(s2) getOrElse "errore"
println(t2 + " (corretto: errore)")
val s3 = Vector("uno", "due", "uno")
val t3 = E31.piùFrequente(s3) getOrElse "errore"
println(t3 + " (corretto: (uno,2))")
}
Suggerimento: usare i metodi groupBy e maxBy.
Set)Scrivere un metodo Scala def inter[T](a:List[T], b:List[T]):List[T] che calcola l’intersezione
delle liste a e b in tempo O(na+nb), dove na e nb sono le lunghezze delle rispettive liste.
Suggerimento: usare il metodo toSet per convertire liste in Set. I set forniscono il test di
appartenenza alla collezione (contains) in tempo O(1) (in media) usando di default un HashSet.
Si riporta un main di prova e una versione inefficiente che usa tempo O(na*nb). Compilare il codice
insieme al modulo Prof.scala che fornisce il costrutto profila per la misurazione dei tempi.
E32Main.scala:
import Prof.profila
object E32Main extends App {
val n = 100000
val a = List.range(1,n)
val b = List.range(-n,5)
println("Intersezione lenta...")
val (v1,t1) = profila {
E32Slow.inter(a,b)
}
println("Intersezione veloce...")
val (v2,t2) = profila {
E32.inter(a,b)
}
println("Tempo slow: "+t1*1E-9+" sec - Risultato: "+v1)
println("Tempo fast: "+t2*1E-9+" sec - Risultato: "+v2)
}
object E32Slow {
def inter[T](a:List[T], b:List[T]) = {
val (min, max) = if (a.size < b.size) (a,b) else (b,a)
def aux(l:List[T], res:List[T]):List[T] =
l match {
case Nil => res
case h::t => if (min contains h) aux(t,h::res) else aux(t,res)
}
aux(max, Nil).reverse
}
}
Prof.scala:
object Prof {
def profila[T](body: =>T):(T,Long) = {
val start = System.nanoTime
val v = body
val t = System.nanoTime - start
(v,t)
}
}
Stream)Scrivere un metodo Scala primes che genera uno stream infinito con la successione dei numeri primi: 2, 3, 5, 7, 11, ecc..
Suggerimento: scomporre il problema in sottoproblemi usando metodi ausiliari.
Usare il seguente main di prova:
E33Main.scala:
object E33Main extends App {
val s:Stream[Int] = E33.primes
println("Primi 5 numeri primi: " + s.take(5) .toList)
println("Primi 10 numeri primi: " + s.take(10).toList)
println("Primi 20 numeri primi: " + s.take(20).toList)
}
Scrivere un metodo Scala search che, dato un Vector[T] ordinato, dove sul dominio T è definito un ordinamento totale mediante la relazione <=, e un elemento e di tipo T, restituisce true se e appartiene al vettore, e false altrimenti. Il tempo di esecuzione deve essere O(log n), dove n è la lunghezza del vettore.
Suggerimento: usare l’algoritmo di ricerca binaria.
Usare il seguente main di prova:
object E34Main extends App {
val v1 = Vector(1,3,5,6,8,9)
val v2 = Vector()
val r1:Boolean = E34.search(v1,8)
val c1 = true
println(r1 + " (corretto: " + c1 + ")")
val r2 = E34.search(v1,0)
val c2 = false
println(r2 + " (corretto: " + c2 + ")")
val r3 = E34.search(v1,100)
val c3 = false
println(r3 + " (corretto: " + c3 + ")")
val r4 = E34.search(v1,1)
val c4 = true
println(r4 + " (corretto: " + c4 + ")")
val r5 = E34.search(v2,1)
val c5 = false
println(r5 + " (corretto: " + c5 + ")")
}
MyRed (replica metodo reduce - ricorsione su liste)Scrivere un metodo Scala myReduce applicabile a liste generiche che
replica il funzionamento del metodo reduce, restituendo però
un Option che vale None se la lista e vuota, e Some(x) altrimenti,
dove x è il risultato della riduzione. L’implementazione deve essere
ricorsiva e non deve usare il metodo predefinito reduce o
similari (fold ecc.), né costrutti della programmazione imperativa (var, while, ecc.).
Usare il seguente main di prova:
MyRedMain.scala:
import MyRed._
object MyRedMain extends App {
val l = List.range(1,1000000)
val v1 = l.myReduce(_+_)
println("Test 1: "+v1+" (corretto: " + Some(l.reduce(_+_)) + ")")
val v2 = List(1).myReduce(_+_)
println("Test 2: "+v2+" (corretto: " + Some(List(1).reduce(_+_)) + ")")
val v3 = List[Int]().myReduce(_+_)
println("Test 3 "+v3+" (corretto: " + None + ")")
}
PCoin (numero di resti possibili - parallelismo su liste)Il metodo fornito def countChange(amount:Int, coins:List[Int]):Int calcola ricorsivamente il numero di modi in cui è possibile dare il
resto per un ammontare amount usando il portafogli di monete coins. Si richiede di scrivere una versione parallela def countChangePar(amount:Int, coins:List[Int], maxThreads:Int):Int che prende come parametro aggiuntivo il numero massimo di thread che possono essere utilizzati per risolvere il problema.
PCoin.scala:
object PCoin {
// versione sequenziale (inefficiente, nel caso peggiore richiede 2^n chiamate ricorsive, con n=coins.size)
def countChange(amount:Int, coins:List[Int]):Int =
if (amount == 0) 1
else if (coins.isEmpty || amount < 0) 0
else countChange(amount - coins.head, coins.tail) + countChange(amount, coins.tail)
// versione parallela, da completare
def countChangePar(amount:Int, coins:List[Int], maxThreads:Int = Runtime.getRuntime().availableProcessors()):Int = ???
}
Si noti che Runtime.getRuntime().availableProcessors() fornisce il numero di core (logici) disponibili ed è usato come valore di
default per il numero massimo di thread creati per risolvere il problema.
Suggerimento: usare il costrutto fornito Par.par per valutare in parallelo due espressioni.
Usare il seguente programma di prova e i relativi moduli richiesti:
PCoinMain.scala:
object PCoinMain extends App {
val coins = List(10,100,1,2,5,20,10,50,1,200,100,5,20,10,10,5,1,20,50,1,5,200,2,50,20,1,1,2,5,200,100)
val a1 = 899
println("Versione sequenziale...")
val (v1, t1) = Prof.profila { PCoin.countChange (a1, coins) }
println("Versione parallela...")
val (vp1, tp1) = Prof.profila { PCoin.countChangePar (a1, coins) }
println("Test: " + vp1 + " (corretto: " + v1 + " - tempo seq: "+t1*1E-9+" sec - tempo par: "+tp1*1E-9+" sec)")
}
Par.scala
object Par {
def par[A,B](a: =>A)(b: =>B):(A,B) = {
var resA:Option[A] = None
val r = new Runnable {
def run() = resA = Some(a)
}
val t = new Thread(r)
t.start()
val resB = b
t.join()
(resA.get, resB)
}
}
Prof.scala
object Prof {
def profila[T](body: =>T):(T,Long) = {
val start = System.nanoTime
val v = body // il body passato come parametro per nome viene valutato qui
val t = System.nanoTime - start
(v,t)
}
}
IsRot (uguaglianza a meno di rotazioni - operazioni su sequenze)Scrivere un metodo Scala def isRot(m:Seq[T]):Option[Int] applicabile su un oggetto l di tipo Seq
che restituisce Some(n), se m è ottenibile ruotando l di n posizioni in avanti, e None
altrimenti. Se vi sono più valori possibili di n, restituire il più piccolo.
Usare il seguente main di prova:
IsRotMain.scala:
import IsRot._
object IsRotMain extends App {
val l1:Seq[Char] = "Hello world"
val m1:Seq[Char] = "orldHello w"
val r1:Option[Int] = l1.isRot(m1)
val c1 = Some(4)
println("Test 1: " + r1 + " (corretto: " + c1 + ")")
val l2:Seq[Int] = List(0,1,2,3,4,5,6,7,8,9)
val m2:Seq[Int] = List(3,4,5,6,7,8,9,0,1,2)
val r2:Option[Int] = l2.isRot(m2)
val c2 = Some(7)
println("Test 2: " + r2 + " (corretto: " + c2 + ")")
val l3:Seq[String] = List("one", "two")
val m3:Seq[String] = List("Two", "one")
val r3:Option[Int] = l3.isRot(m3)
val c3 = None
println("Test 3: " + r3 + " (corretto: " + c3 + ")")
val l4:Seq[Boolean] = List(true, false, false)
val m4:Seq[Boolean] = List(true, false, false)
val r4:Option[Int] = l4.isRot(m4)
val c4 = Some(0)
println("Test 4: " + r4 + " (corretto: " + c4 + ")")
val l5:Seq[Boolean] = List(true, false, false)
val m5:Seq[Boolean] = List(true)
val r5:Option[Int] = l5.isRot(m5)
val c5 = None
println("Test 5: " + r5 + " (corretto: " + c5 + ")")
val l6:Seq[Short] = List(1,2,1,2)
val m6:Seq[Short] = List(2,1,2,1)
val r6:Option[Int] = l6.isRot(m6)
val c6 = Some(1)
println("Test 6: " + r6 + " (corretto: " + c6 + ")")
}
PIsRot (uguaglianza a meno di rotazioni - parallelismo su sequenze)Scrivere una versione parallela isRotPar del metodo isRot dell’esercizio IsRot usando il metodo par dell’esercizio PCoin. Si parta dalla soluzione proposta per l’esercizio IsRot.
Usare il seguente programma di prova che richiede il modulo Prof.scala dell’esercizio PCoin:
import IsRot._
import PIsRot._
object PIsRotMain extends App {
val l1:Seq[Char] = "Hello world"
val m1:Seq[Char] = "orldHello w"
val r1:Option[Int] = l1.isRotPar(m1)
val c1 = Some(4)
println("Test 1: " + r1 + " (corretto: " + c1 + ")")
val l2:Seq[Int] = List(0,1,2,3,4,5,6,7,8,9)
val m2:Seq[Int] = List(3,4,5,6,7,8,9,0,1,2)
val r2:Option[Int] = l2.isRotPar(m2)
val c2 = Some(7)
println("Test 2: " + r2 + " (corretto: " + c2 + ")")
val l3:Seq[String] = List("one", "two")
val m3:Seq[String] = List("Two", "one")
val r3:Option[Int] = l3.isRotPar(m3)
val c3 = None
println("Test 3: " + r3 + " (corretto: " + c3 + ")")
val l4:Seq[Boolean] = List(true, false, false)
val m4:Seq[Boolean] = List(true, false, false)
val r4:Option[Int] = l4.isRotPar(m4)
val c4 = Some(0)
println("Test 4: " + r4 + " (corretto: " + c4 + ")")
val l5:Seq[Boolean] = List(true, false, false)
val m5:Seq[Boolean] = List(true)
val r5:Option[Int] = l5.isRotPar(m5)
val c5 = None
println("Test 5: " + r5 + " (corretto: " + c5 + ")")
val l6:Seq[Short] = List(1,2,1,2)
val m6:Seq[Short] = List(2,1,2,1)
val r6:Option[Int] = l6.isRotPar(m6)
val c6 = Some(1)
println("Test 6: " + r6 + " (corretto: " + c6 + ")")
val n = 20000
val l = (0 to n).toVector
val (a,b) = l splitAt 2*l.size/3
val m = b ++ a
println("Versione sequenziale...")
val (v, t) = Prof.profila { l.isRot(m) }
println("Versione parallela...")
val (vp, tp) = Prof.profila { l.isRotPar(m) }
println("Test 7: " + vp + " (corretto: " + v + " - tempo seq: "+t*1E-9+" sec - tempo par: "+tp*1E-9+" sec)")
}
veq32 (uguaglianza di array di int - vettorizzazione)Scrivere una funzione C int veq32(const int* a, int na, const int* b, int nb) che, dati due array di interi a e b di lunghezza rispettivamente na e nb, restituisce 1 se gli array sono uguali e 0 altrimenti. La funzione deve usare vettorizzazione SSE o AVX.
Suggerimento: si usino gli intrinsic _mm_cmpeq_epi32 e _mm_test_all_ones descritti nella guida Intel.
Usare il seguente pacchetto per realizzare e testare la soluzione:
veq32.c
#include "veq32.h"
// ---------------------------------------------------------------------
// veq32
// ---------------------------------------------------------------------
// versione vettorizzata
int veq32(const int* a, int na, const int* b, int nb) {
// da completare...
return 1;
}
// ---------------------------------------------------------------------
// veq32_seq
// ---------------------------------------------------------------------
// versione sequenziale
int veq32_seq(const int* a, int na, const int* b, int nb) {
int i;
if (na != nb) return 0;
for (i=0; i<na; ++i)
if (a[i] != b[i]) return 0;
return 1;
}
veq32.h
#ifndef __VEQ32__
#define __VEQ32__
int veq32_seq(const int* a, int na, const int* b, int nb);
int veq32(const int* a, int na, const int* b, int nb);
#endif
veq32-main.c
#include <sys/time.h>
#include <stdio.h>
#include <stdlib.h>
#include <assert.h>
#include "veq32.h"
#define N 200003
#define M 1000
// ---------------------------------------------------------------------
// get_real_time
// ---------------------------------------------------------------------
double get_real_time() {
struct timeval tv;
gettimeofday(&tv, NULL);
return tv.tv_sec+tv.tv_usec*1E-06;
}
// ---------------------------------------------------------------------
// do_test
// ---------------------------------------------------------------------
static void do_test(const int* a, int na, const int* b, int nb, int test_no, int print_time) {
double start, tseq, tsse;
int i, rseq = 0, rsse = 0;
printf("\nTest #%d\n", test_no);
// sequential
start = get_real_time();
for (i=0; i<M; ++i) rseq += veq32_seq(a, na, b, nb);
tseq = get_real_time()-start;
// SSE
start = get_real_time();
for (i=0; i<M; ++i) rsse += veq32(a, na, b, nb);
tsse = get_real_time()-start;
printf("- result: %d [expected: %d]\n", rsse, rseq);
if (print_time) {
printf("- sequential version: %.2f msec\n", tseq*1000);
printf("- SSE version: %.2f msec\n", tsse*1000);
printf("- speedup: %.2fx\n", tseq/(tsse==0.0 ? 1E-9 : tsse));
}
}
// ---------------------------------------------------------------------
// main
// ---------------------------------------------------------------------
int main(int argc, char** argv) {
int i;
int* a = malloc(N*sizeof(int));
int* b = malloc(N*sizeof(int));
int* c = malloc(N*sizeof(int));
assert(a != NULL);
assert(b != NULL);
assert(c != NULL);
for (i=0; i<N; ++i)
a[i] = b[i] = c[i] = 33 + i % 94;
b[N-1] = 32;
c[N-20] = 32;
do_test(a, N-1, b, N-1, 1, 1); // 1
do_test(a, N, c, N, 2, 1); // 0
do_test(a, N/2, b, N, 3, 0); // 0
do_test(a, N, b, N, 4, 1); // 0
do_test(a, 10, b, 10, 5, 0); // 1
do_test(a+N-10, 10, c+N-10, 10, 6, 0); // 1
do_test(a+N-10, 10, b+N-10, 10, 6, 0); // 0
free(a);
free(b);
free(c);
return 0;
}
Makefile
CC = gcc
CFLAGS = -O1 -Wall -g
LFLAGS = -msse4.2 # modificare se serve AVX
veq32: veq32-main.c veq32.c veq32.h
$(CC) $(CFLAGS) veq32-main.c veq32.c -o veq32 $(LFLAGS)
.phony: clean
clean:
rm -rf veq32 *.dSYM
vpal32 (verifica array palindromo - vettorizzazione)Scrivere una funzione C int vpal32(const int* v, int n) che, dato un array di interi v di lunghezza n, restituisce 1 se è palindromo (cioè se è uguale a se stesso rovesciato) e 0 altrimenti. Ad esempio, l’array {1,2,3,2,1} è palindromo. La funzione deve usare vettorizzazione SSE o AVX.
Suggerimento: si usino gli intrinsic _mm_shuffle_epi32, _mm_cmpeq_epi32 e _mm_test_all_ones descritti nella guida Intel.
Usare il seguente pacchetto per realizzare e testare la soluzione:
vpal32.c
#include "vpal32.h"
// ---------------------------------------------------------------------
// vpal32
// ---------------------------------------------------------------------
// SSE version
int vpal32(const int* v, int n) {
// da completare...
return 1;
}
// ---------------------------------------------------------------------
// vpal32_seq
// ---------------------------------------------------------------------
// sequential version
int vpal32_seq(const int* v, int n) {
int i;
for (i=0; i<n/2; ++i)
if (v[i] != v[n-1-i]) return 0;
return 1;
}
vpal32.h
#ifndef __VPAL32__
#define __VPAL32__
int vpal32_seq(const int* v, int n);
int vpal32(const int* a, int v);
#endif
vpal32-main.c
#include <sys/time.h>
#include <stdio.h>
#include <stdlib.h>
#include <assert.h>
#include "vpal32.h"
#define N 200003
#define M 1000
// ---------------------------------------------------------------------
// get_real_time
// ---------------------------------------------------------------------
double get_real_time() {
struct timeval tv;
gettimeofday(&tv, NULL);
return tv.tv_sec+tv.tv_usec*1E-06;
}
// ---------------------------------------------------------------------
// do_test
// ---------------------------------------------------------------------
static void do_test(const int* v, int n, int test_no, int print_time) {
double start, tseq, tsse;
int i, rseq = 0, rsse = 0;
printf("\nTest #%d\n", test_no);
// sequential
start = get_real_time();
for (i=0; i<M; ++i) rseq += vpal32_seq(v, n);
tseq = get_real_time()-start;
// SSE
start = get_real_time();
for (i=0; i<M; ++i) rsse += vpal32(v, n);
tsse = get_real_time()-start;
printf("- result: %d [expected: %d]\n", rsse, rseq);
if (print_time) {
printf("- sequential version: %.2f msec\n", tseq*1000);
printf("- SSE version: %.2f msec\n", tsse*1000);
printf("- speedup: %.2fx\n", tseq/(tsse==0.0 ? 1E-9 : tsse));
}
}
// ---------------------------------------------------------------------
// main
// ---------------------------------------------------------------------
int main(int argc, char** argv) {
int i;
int* a = calloc(N, sizeof(int));
int* b = calloc(N, sizeof(int));
int* c = calloc(N, sizeof(int));
assert(a != NULL);
assert(b != NULL);
assert(c != NULL);
for (i=0; i<N/2; ++i) {
a[i] = b[i] = c[i] = 33 + i % 94;
a[N-1-i] = b[N-1-i] = c[N-1-i] = a[i];
}
b[10] = 32;
c[N/2] = c[N/2+1] = 32;
do_test(a, N, 1, 1); // 1
do_test(b, N, 2, 0); // 0
do_test(c, N, 3, 1); // 0
do_test(a+10, N-20, 4, 1); // 1
free(a);
free(b);
free(c);
return 0;
}
Makefile
CC = gcc
CFLAGS = -O1 -Wall -g
LFLAGS = -msse4.2
vpal32: vpal32-main.c vpal32.c vpal32.h
$(CC) $(CFLAGS) vpal32-main.c vpal32.c -o vpal32 $(LFLAGS)
.phony: clean
clean:
rm -rf vpal32 *.dSYM