Skip to content
Snippets Groups Projects

Compare revisions

Changes are shown as if the source revision was being merged into the target revision. Learn more about comparing revisions.

Source

Select target project
No results found

Target

Select target project
  • lamp/cs206
  • bwermeil/cs206-2020
  • zabifade/cs206-2020
  • cauderan/cs206-2020
  • malonga/cs206-2020
  • dumoncel/cs206
  • bounekhe/cs206
  • bergerault/cs206
  • flealsan/cs206
  • hsu/cs206
  • mouchel/cs206
  • vebraun/cs206
  • vcanard/cs206
  • ybelghmi/cs206
  • belghmi/cs206
  • bousbina/cs206
  • waked/cs206
  • gtagemou/cs206
  • arahmoun/cs206
  • elhachem/cs206
  • benrahha/cs206
  • benslima/cs206
22 results
Show changes
Showing
with 0 additions and 1443 deletions
package ch.epfl.lamp
import sbt._
import Keys._
// import scalaj.http._
import java.io.{File, FileInputStream, IOException}
import org.apache.commons.codec.binary.Base64
// import play.api.libs.json.{Json, JsObject, JsPath}
import scala.util.{Failure, Success, Try}
/**
* Provides tasks for submitting the assignment
*/
object StudentTasks extends AutoPlugin {
override def requires = super.requires && MOOCSettings
object autoImport {
val packageSourcesOnly = TaskKey[File]("packageSourcesOnly", "Package the sources of the project")
val packageBinWithoutResources = TaskKey[File]("packageBinWithoutResources", "Like packageBin, but without the resources")
val packageSubmissionZip = TaskKey[File]("packageSubmissionZip")
val packageSubmission = inputKey[Unit]("package solution as an archive file")
lazy val Grading = config("grading") extend(Runtime)
}
import autoImport._
import MOOCSettings.autoImport._
override lazy val projectSettings = Seq(
packageSubmissionSetting,
fork := true,
connectInput in run := true,
outputStrategy := Some(StdoutOutput),
) ++
packageSubmissionZipSettings ++
inConfig(Grading)(Defaults.testSettings ++ Seq(
unmanagedJars += file("grading-tests.jar"),
definedTests := (definedTests in Test).value,
internalDependencyClasspath := (internalDependencyClasspath in Test).value
))
/** **********************************************************
* SUBMITTING A SOLUTION TO COURSERA
*/
val packageSubmissionZipSettings = Seq(
packageSubmissionZip := {
val submission = crossTarget.value / "submission.zip"
val sources = (packageSourcesOnly in Compile).value
val binaries = (packageBinWithoutResources in Compile).value
IO.zip(Seq(sources -> "sources.zip", binaries -> "binaries.jar"), submission, None)
submission
},
artifactClassifier in packageSourcesOnly := Some("sources"),
artifact in (Compile, packageBinWithoutResources) ~= (art => art.withName(art.name + "-without-resources"))
) ++
inConfig(Compile)(
Defaults.packageTaskSettings(packageSourcesOnly, Defaults.sourceMappings) ++
Defaults.packageTaskSettings(packageBinWithoutResources, Def.task {
val relativePaths =
(unmanagedResources in Compile).value.flatMap(Path.relativeTo((unmanagedResourceDirectories in Compile).value)(_))
(mappings in (Compile, packageBin)).value.filterNot { case (_, path) => relativePaths.contains(path) }
})
)
val maxSubmitFileSize = {
val mb = 1024 * 1024
10 * mb
}
/** Check that the jar exists, isn't empty, isn't crazy big, and can be read
* If so, encode jar as base64 so we can send it to Coursera
*/
def prepareJar(jar: File, s: TaskStreams): String = {
val errPrefix = "Error submitting assignment jar: "
val fileLength = jar.length()
if (!jar.exists()) {
s.log.error(errPrefix + "jar archive does not exist\n" + jar.getAbsolutePath)
failSubmit()
} else if (fileLength == 0L) {
s.log.error(errPrefix + "jar archive is empty\n" + jar.getAbsolutePath)
failSubmit()
} else if (fileLength > maxSubmitFileSize) {
s.log.error(errPrefix + "jar archive is too big. Allowed size: " +
maxSubmitFileSize + " bytes, found " + fileLength + " bytes.\n" +
jar.getAbsolutePath)
failSubmit()
} else {
val bytes = new Array[Byte](fileLength.toInt)
val sizeRead = try {
val is = new FileInputStream(jar)
val read = is.read(bytes)
is.close()
read
} catch {
case ex: IOException =>
s.log.error(errPrefix + "failed to read sources jar archive\n" + ex.toString)
failSubmit()
}
if (sizeRead != bytes.length) {
s.log.error(errPrefix + "failed to read the sources jar archive, size read: " + sizeRead)
failSubmit()
} else encodeBase64(bytes)
}
}
/** Task to package solution to a given file path */
lazy val packageSubmissionSetting = packageSubmission := {
val args: Seq[String] = Def.spaceDelimited("[path]").parsed
val s: TaskStreams = streams.value // for logging
val jar = (packageSubmissionZip in Compile).value
val base64Jar = prepareJar(jar, s)
val path = args.headOption.getOrElse((baseDirectory.value / "submission.jar").absolutePath)
scala.tools.nsc.io.File(path).writeAll(base64Jar)
}
/*
/** Task to submit a solution to coursera */
val submit = inputKey[Unit]("submit solution to Coursera")
lazy val submitSetting = submit := {
// Fail if scalafix linting does not pass.
scalafixLinting.value
val args: Seq[String] = Def.spaceDelimited("<arg>").parsed
val s: TaskStreams = streams.value // for logging
val jar = (packageSubmissionZip in Compile).value
val assignmentDetails =
courseraId.?.value.getOrElse(throw new MessageOnlyException("This assignment can not be submitted to Coursera because the `courseraId` setting is undefined"))
val assignmentKey = assignmentDetails.key
val courseName =
course.value match {
case "capstone" => "scala-capstone"
case "bigdata" => "scala-spark-big-data"
case other => other
}
val partId = assignmentDetails.partId
val itemId = assignmentDetails.itemId
val premiumItemId = assignmentDetails.premiumItemId
val (email, secret) = args match {
case email :: secret :: Nil =>
(email, secret)
case _ =>
val inputErr =
s"""|Invalid input to `submit`. The required syntax for `submit` is:
|submit <email-address> <submit-token>
|
|The submit token is NOT YOUR LOGIN PASSWORD.
|It can be obtained from the assignment page:
|https://www.coursera.org/learn/$courseName/programming/$itemId
|${
premiumItemId.fold("") { id =>
s"""or (for premium learners):
|https://www.coursera.org/learn/$courseName/programming/$id
""".stripMargin
}
}
""".stripMargin
s.log.error(inputErr)
failSubmit()
}
val base64Jar = prepareJar(jar, s)
val json =
s"""|{
| "assignmentKey":"$assignmentKey",
| "submitterEmail":"$email",
| "secret":"$secret",
| "parts":{
| "$partId":{
| "output":"$base64Jar"
| }
| }
|}""".stripMargin
def postSubmission[T](data: String): Try[HttpResponse[String]] = {
val http = Http("https://www.coursera.org/api/onDemandProgrammingScriptSubmissions.v1")
val hs = List(
("Cache-Control", "no-cache"),
("Content-Type", "application/json")
)
s.log.info("Connecting to Coursera...")
val response = Try(http.postData(data)
.headers(hs)
.option(HttpOptions.connTimeout(10000)) // scalaj default timeout is only 100ms, changing that to 10s
.asString) // kick off HTTP POST
response
}
val connectMsg =
s"""|Attempting to submit "${assignment.value}" assignment in "$courseName" course
|Using:
|- email: $email
|- submit token: $secret""".stripMargin
s.log.info(connectMsg)
def reportCourseraResponse(response: HttpResponse[String]): Unit = {
val code = response.code
val respBody = response.body
/* Sample JSON response from Coursera
{
"message": "Invalid email or token.",
"details": {
"learnerMessage": "Invalid email or token."
}
}
*/
// Success, Coursera responds with 2xx HTTP status code
if (response.is2xx) {
val successfulSubmitMsg =
s"""|Successfully connected to Coursera. (Status $code)
|
|Assignment submitted successfully!
|
|You can see how you scored by going to:
|https://www.coursera.org/learn/$courseName/programming/$itemId/
|${
premiumItemId.fold("") { id =>
s"""or (for premium learners):
|https://www.coursera.org/learn/$courseName/programming/$id
""".stripMargin
}
}
|and clicking on "My Submission".""".stripMargin
s.log.info(successfulSubmitMsg)
}
// Failure, Coursera responds with 4xx HTTP status code (client-side failure)
else if (response.is4xx) {
val result = Try(Json.parse(respBody)).toOption
val learnerMsg = result match {
case Some(resp: JsObject) =>
(JsPath \ "details" \ "learnerMessage").read[String].reads(resp).get
case Some(x) => // shouldn't happen
"Could not parse Coursera's response:\n" + x
case None =>
"Could not parse Coursera's response:\n" + respBody
}
val failedSubmitMsg =
s"""|Submission failed.
|There was something wrong while attempting to submit.
|Coursera says:
|$learnerMsg (Status $code)""".stripMargin
s.log.error(failedSubmitMsg)
}
// Failure, Coursera responds with 5xx HTTP status code (server-side failure)
else if (response.is5xx) {
val failedSubmitMsg =
s"""|Submission failed.
|Coursera seems to be unavailable at the moment (Status $code)
|Check https://status.coursera.org/ and try again in a few minutes.
""".stripMargin
s.log.error(failedSubmitMsg)
}
// Failure, Coursera repsonds with an unexpected status code
else {
val failedSubmitMsg =
s"""|Submission failed.
|Coursera replied with an unexpected code (Status $code)
""".stripMargin
s.log.error(failedSubmitMsg)
}
}
// kick it all off, actually make request
postSubmission(json) match {
case Success(resp) => reportCourseraResponse(resp)
case Failure(e) =>
val failedConnectMsg =
s"""|Connection to Coursera failed.
|There was something wrong while attempting to connect to Coursera.
|Check your internet connection.
|${e.toString}""".stripMargin
s.log.error(failedConnectMsg)
}
}
*/
def failSubmit(): Nothing = {
sys.error("Submission failed")
}
/**
* *****************
* DEALING WITH JARS
*/
def encodeBase64(bytes: Array[Byte]): String =
new String(Base64.encodeBase64(bytes))
}
sbt.version=1.4.7
// Used for Coursera submission (StudentPlugin)
// libraryDependencies += "org.scalaj" %% "scalaj-http" % "2.4.2"
// libraryDependencies += "com.typesafe.play" %% "play-json" % "2.7.4"
// Used for Base64 (StudentPlugin)
libraryDependencies += "commons-codec" % "commons-codec" % "1.10"
// addSbtPlugin("org.scala-js" % "sbt-scalajs" % "0.6.28")
addSbtPlugin("ch.epfl.lamp" % "sbt-dotty" % "0.5.3")
package m3
////////////////////////////////////////
// NO NEED TO MODIFY THIS SOURCE FILE //
////////////////////////////////////////
trait Lib {
/** If an array has `n` elements and `n < THRESHOLD`, then it should be processed sequentially */
final val THRESHOLD: Int = 33
/** Compute the two values in parallel
*
* Note: Most tests just compute those two sequentially to make any bug simpler to debug
*/
def parallel[T1, T2](op1: => T1, op2: => T2): (T1, T2)
/** A limited array. It only contains the required operations for this exercise. */
trait Arr[T] {
/** Get the i-th element of the array (0-based) */
def apply(i: Int): T
/** Update the i-th element of the array with the given value (0-based) */
def update(i: Int, x: T): Unit
/** Number of elements in this array */
def length: Int
/** Create a copy of this array without the first element */
def tail: Arr[T]
/** Create a copy of this array by mapping all the elements with the given function */
def map[U](f: T => U): Arr[U]
}
object Arr {
/** Create an array with the given elements */
def apply[T](xs: T*): Arr[T] = {
val arr: Arr[T] = Arr.ofLength(xs.length)
for i <- 0 until xs.length do arr(i) = xs(i)
arr
}
/** Create an array with the given length. All elements are initialized to `null`. */
def ofLength[T](n: Int): Arr[T] =
newArrOfLength(n)
}
/** Create an array with the given length. All elements are initialized to `null`. */
def newArrOfLength[T](n: Int): Arr[T]
/** A number representing the average of a list of integers (the "window") */
case class AvgWin(list: List[Int]) {
def push(i: Int) = list match {
case i3 :: i2 :: i1 :: Nil => AvgWin(i :: i3 :: i2 :: Nil)
case list => AvgWin(i :: list)
}
def pushAll(other: AvgWin) =
other.list.foldRight(this)((el, self) => self.push(el))
def toDouble: Double = if list.isEmpty then 0 else list.sum / list.length
}
/** Tree result of an upsweep operation. Specialized for `AvgWin` results. */
trait TreeRes { val res: AvgWin }
/** Leaf result of an upsweep operation. Specialized for `AvgWin` results. */
case class Leaf(from: Int, to: Int, res: AvgWin) extends TreeRes
/** Tree node result of an upsweep operation. Specialized for `AvgWin` results. */
case class Node(left: TreeRes, res: AvgWin, right: TreeRes) extends TreeRes
}
package m3
trait M3 extends Lib {
// Functions and classes of Lib can be used in here
/** Compute the rolling windowed mean of an array.
*
* For an array `arr = Arr(x1, x2, x3, ..., x_n)` the result is
* `Arr(x1, (x1+x2)/2, (x1+x2+x3)/3, (x2+x3+x4)/3, ..., (x_{n-2}, x_{n-1}, x_n)/n)`
*/
def rollingWinMeanParallel(arr: Arr[Int]): Arr[Double] = {
if (arr.length == 0) return Arr.ofLength(0)
val out: Arr[Double] = Arr.ofLength(arr.length)
val tree = upsweep(arr, 0, arr.length)
downsweep(arr, AvgWin(Nil), tree, out)
out
}
// No need to modify this
def scanOp(acc: AvgWin, x: AvgWin) =
acc.pushAll(x)
def upsweep(input: Arr[Int], from: Int, to: Int): TreeRes = {
if (to - from < THRESHOLD)
Leaf(from, to, reduceSequential(input, from + 1, to, AvgWin(input(from) :: Nil)))
else {
val mid = from + (to - from)/2
val (tL, tR) = parallel(
upsweep(input, from, mid),
upsweep(input, mid, to)
)
Node(tL, scanOp(tL.res, tR.res), tR)
}
}
def downsweep(input: Arr[Int], a0: AvgWin, tree: TreeRes, output: Arr[Double]): Unit = {
tree match {
case Node(left, _, right) =>
parallel(
downsweep(input, a0, left, output),
downsweep(input, scanOp(a0, left.res), right, output)
)
case Leaf(from, to, _) =>
downsweepSequential(input, from, to, a0, output)
}
}
def downsweepSequential(input: Arr[Int], from: Int, to: Int, a0: AvgWin, output: Arr[Double]): Unit = {
if (from < to) {
var i = from
var a = a0
while (i < to) {
a = scanOp(a, AvgWin(input(i) :: Nil))
output(i) = a.toDouble
i = i + 1
}
}
}
def reduceSequential(input: Arr[Int], from: Int, to: Int, a0: AvgWin): AvgWin = {
var a = a0
var i = from
while (i < to) {
a = scanOp(a, AvgWin(input(i) :: Nil))
i = i + 1
}
a
}
}
package m3
class M3Suite extends munit.FunSuite {
test("Rolling windowed average result test (5pts)") {
RollingWinMeanBasicLogicTest.basicTests()
RollingWinMeanBasicLogicTest.normalTests()
RollingWinMeanBasicLogicTest.largeTests()
}
test("[TASK 1] Rolling windowed average parallelism test (30pts)") {
RollingWinMeanCallsToParallel.parallelismTest()
RollingWinMeanParallel.basicTests()
RollingWinMeanParallel.normalTests()
RollingWinMeanParallel.largeTests()
}
test("[TASK 2] Rolling windowed average no `map` test (35pts)") {
RollingWinMeanNoMap.basicTests()
RollingWinMeanNoMap.normalTests()
RollingWinMeanNoMap.largeTests()
}
test("[TASK 3] Rolling windowed average no `tail` test (30pts)") {
RollingWinMeanNoTail.basicTests()
RollingWinMeanNoTail.normalTests()
RollingWinMeanNoTail.largeTests()
}
object RollingWinMeanBasicLogicTest extends M3 with LibImpl with RollingWinMeanTest {
def parallel[T1, T2](op1: => T1, op2: => T2): (T1, T2) = (op1, op2)
def newArrFrom[T](arr: Array[AnyRef]): Arr[T] = new ArrImpl(arr)
}
object RollingWinMeanCallsToParallel extends M3 with LibImpl with RollingWinMeanTest {
private var count = 0
def parallel[T1, T2](op1: => T1, op2: => T2): (T1, T2) =
count += 1
(op1, op2)
def newArrFrom[T](arr: Array[AnyRef]): Arr[T] = new ArrImpl(arr)
def parallelismTest() = {
assertParallelCount(Arr(), 0)
assertParallelCount(Arr(1), 0)
assertParallelCount(Arr(1, 2, 3, 4), 0)
assertParallelCount(Arr(Array.tabulate(16)(identity): _*), 0)
assertParallelCount(Arr(Array.tabulate(32)(identity): _*), 0)
assertParallelCount(Arr(Array.tabulate(33)(identity): _*), 2)
assertParallelCount(Arr(Array.tabulate(64)(identity): _*), 2)
assertParallelCount(Arr(Array.tabulate(128)(identity): _*), 6)
assertParallelCount(Arr(Array.tabulate(256)(identity): _*), 14)
assertParallelCount(Arr(Array.tabulate(1000)(identity): _*), 62)
assertParallelCount(Arr(Array.tabulate(1024)(identity): _*), 62)
}
def assertParallelCount(arr: Arr[Int], expected: Int): Unit = {
try {
count = 0
rollingWinMeanParallel(arr)
assert(count == expected, {
val extra = if (expected == 0) "" else s" ${expected/2} for the `upsweep` and ${expected/2} for the `downsweep`"
s"\n$arr\n\nERROR: Expected $expected instead of $count calls to `parallel(...)` for an array of ${arr.length} elements. Current parallel threshold is $THRESHOLD.$extra"
})
} finally {
count = 0
}
}
}
object RollingWinMeanNoMap extends M3 with LibImpl with RollingWinMeanTest {
def parallel[T1, T2](op1: => T1, op2: => T2): (T1, T2) = (op1, op2)
def newArrFrom[T](arr: Array[AnyRef]): Arr[T] = new ArrImpl[T](arr) {
override def map[U](f: T => U): Arr[U] = throw Exception("Should not call Arr.map")
}
}
object RollingWinMeanNoTail extends M3 with LibImpl with RollingWinMeanTest {
def parallel[T1, T2](op1: => T1, op2: => T2): (T1, T2) = (op1, op2)
def newArrFrom[T](arr: Array[AnyRef]): Arr[T] = new ArrImpl[T](arr) {
override def tail: Arr[T] = throw Exception("Should not call Arr.tail")
}
}
object RollingWinMeanParallel extends M3 with LibImpl with RollingWinMeanTest {
import scala.concurrent.duration._
val TIMEOUT = Duration(10, SECONDS)
def parallel[T1, T2](op1: => T1, op2: => T2): (T1, T2) = {
import concurrent.ExecutionContext.Implicits.global
import scala.concurrent._
Await.result(Future(op1).zip(Future(op2)), TIMEOUT) // FIXME not timing-out
}
def newArrFrom[T](arr: Array[AnyRef]): Arr[T] = new ArrImpl(arr)
}
trait LibImpl extends Lib {
def newArrFrom[T](arr: Array[AnyRef]): Arr[T]
def newArrOfLength[T](n: Int): Arr[T] =
newArrFrom(new Array(n))
class ArrImpl[T](val arr: Array[AnyRef]) extends Arr[T]:
def apply(i: Int): T =
arr(i).asInstanceOf[T]
def update(i: Int, x: T): Unit =
arr(i) = x.asInstanceOf[AnyRef]
def length: Int =
arr.length
def map[U](f: T => U): Arr[U] =
newArrFrom(arr.map(f.asInstanceOf[AnyRef => AnyRef]))
def tail: Arr[T] =
newArrFrom(arr.tail)
override def toString: String =
arr.mkString("Arr(", ", ", ")")
override def equals(that: Any): Boolean =
that match
case that: ArrImpl[_] => Array.equals(arr, that.arr)
case _ => false
}
trait RollingWinMeanTest extends M3 {
def tabulate[T](n: Int)(f: Int => T): Arr[T] =
val arr = Arr.ofLength[T](n)
for i <- 0 until n do
arr(i) = f(i)
arr
def asSeq(arr: Arr[Double]) =
val array = new Array[Double](arr.length)
for i <- 0 to (arr.length - 1) do
array(i) = arr(i)
array.toSeq
def scanOp_(acc: AvgWin, x: AvgWin) =
acc.pushAll(x)
def result(ds: Seq[Int]): Arr[Double] =
Arr(ds.map(x => AvgWin(x :: Nil)).scan(AvgWin(Nil))(scanOp_).tail.map(_.toDouble): _*)
def check(input: Seq[Int]) =
assertEquals(
asSeq(rollingWinMeanParallel(Arr(input: _*))),
asSeq(result(input))
)
def basicTests() = {
check(Seq())
check(Seq(1))
check(Seq(1, 2, 3, 4))
check(Seq(4, 4, 4, 4))
}
def normalTests() = {
check(Seq.tabulate(64)(identity))
check(Seq(4, 4, 4, 4))
check(Seq(4, 8, 6, 4))
check(Seq(4, 3, 2, 1))
check(Seq.tabulate(64)(identity).reverse)
check(Seq.tabulate(128)(i => 128 - 2*i).reverse)
}
def largeTests() = {
check(Seq.tabulate(500)(identity))
check(Seq.tabulate(512)(identity))
check(Seq.tabulate(1_000)(identity))
check(Seq.tabulate(10_000)(identity))
}
}
}
Use the following commands to make a fresh clone of your repository:
```
git clone -b m6 git@gitlab.epfl.ch:lamp/student-repositories-s21/cs206-GASPAR.git m6
```
## Useful links
* [A guide to the Scala parallel collections](https://docs.scala-lang.org/overviews/parallel-collections/overview.html)
* [The API documentation of the Scala parallel collections](https://www.javadoc.io/doc/org.scala-lang.modules/scala-parallel-collections_2.13/latest/scala/collection/index.html)
* [The API documentation of the Scala standard library](https://www.scala-lang.org/files/archive/api/2.13.4)
* [The API documentation of the Java standard library](https://docs.oracle.com/en/java/javase/15/docs/api/index.html)
**If you have issues with the IDE, try [reimporting the
build](https://gitlab.epfl.ch/lamp/cs206/-/blob/master/labs/example-lab.md#ide-features-like-type-on-hover-or-go-to-definition-do-not-work),
if you still have problems, use `compile` in sbt instead.**
## Exercise
In this exercise, you will implement an array Combiner using internally a doubly linked list of arrays. Your goal is to complete the implementation of the (simplified) Combiner interface, by implementing the `result` method to compute the result array from this array combiner.
Here you can see the declaration of the `DLLCombiner` class and the related `Node` class definition. Look at the `Lib` trait in the `lib.scala` file to find all definitions of relevant functions and classes.
```scala
class Node(val size: Int) {
var value: Array[Int] = new Array(size)
var next: Node = null
var previous: Node = null
var cnt = 0
def add(v: Int) = {
value(cnt) = v
cnt += 1
}
}
// Simplified Combiner interface
// Implements methods += and combine
// Abstract methods should be implemented in subclasses
abstract class DLLCombiner(val chunk_size: Int)
```
`DLLCombiner` class contains the implementation of methods `+=` and `combine`. You should look at them to better understand the structure of this array Combiner, before moving on to solving this exercise.
Your task in the exercise will be to implement the `result` method of the `DLLCombinerImplementation` class. This method should compute the result array from this array combiner. This method should work in parallel according to the Combiner contract. Implement this method efficiently using 4 parallel tasks, by copying the doubly linked list to the array from both ends at the same time. Two threads should start from the start of the list and two from the end. In each case, one thread would be responsible for odd list indexes and the other for even ones.
```scala
class DLLCombinerImplementation(chunk_size: Int = 3) extends DLLCombiner(chunk_size) {
// Computes every other Integer element of data array, starting from the first (index 0), up to the middle
def task1(data: Array[Int]) = task {
???
}
// Computes every other Integer element of data array, starting from the second, up to the middle
def task2(data: Array[Int]) = task {
???
}
// Computes every other Integer element of data array, starting from the second to last, up to the middle
def task3(data: Array[Int]) = task {
???
}
// Computes every other Integer element of data array, starting from the last, up to the middle
// This is executed on the current thread.
def task4(data: Array[Int]) = {
???
}
def result(): Array[Int] = {
val data = new Array[Int](cnt)
???
data
}
}
```
Following the description above, your task in the exercise is to:
- Implement the four tasks to copy parts of the array. Each task is responsible for computing one quarter of the array, as indicated in comments:
+ Task 1: Computes every other Integer element of data array, starting from the first (index 0), up to the middle
+ Task 2: Computes every other Integer element of data array, starting from the second, up to the middle
+ Task 3: Computes every other Integer element of data array, starting from the second to last, up to the middle
+ Task 4: Computes every other Integer element of data array, starting from the last, up to the middle
- Implement the method `result` to compute the result array in parallel using those four tasks.
Hints:
- Note that this doubly linked list implementation uses `null` pointers to represent the absence of nodes. Be careful when working with `null` pointers in your solution, to avoid causing a `NullPointerException`.
- `DLLCombinerImplementation` comes with a private `copyForward` method that you can use in your solution:
```scala
private def copyForward(data: Array[Int], curr: Node, from: Int, to: Int, limit: Int)
```
This method copies certain elements from a doubly linked list to an array, in a way that might be useful for this exercise.
## Examples
Here is one example of the `result` method with intermediate results:
```scala
val combiner1 = DLLCombinerImplementation(4)
combiner1 += 7 // (7)
combiner1 += 2 // (7, 2)
combiner1 += 4 // (7, 2, 4)
combiner1 += 3 // (7, 2, 4, 3)
combiner1 += 9 // (7, 2, 4, 3) <-> (9)
combiner1 += 5 // (7, 2, 4, 3) <-> (9, 5)
combiner1 += 1 // (7, 2, 4, 3) <-> (9, 5, 1)
val res1 = combiner1.result() // (7, 2, 4, 3, 9, 5, 1)
```
In this example, `task1` was responsible for computing elements at indexes 0 and 2, `task2` for computing the element at index 1, `task3` for computing elements at indexes 5 and 3, and `task4` for computing elements at indexes 6 and 4.
Here is another example with combining:
```scala
val c1 = DLLCombinerImplementation(4)
c1 += 7 // (7)
c1 += 2 // (7, 2)
c1 += 4 // (7, 2, 4)
c1 += 3 // (7, 2, 4, 3)
c1 += 9 // (7, 2, 4, 3) <-> (9)
c1 += 5 // (7, 2, 4, 3) <-> (9, 5)
c1 += 1 // (7, 2, 4, 3) <-> (9, 5, 1)
val c2 = DLLCombinerImplementation(4)
c2 += 6 // (6)
c2 += 8 // (6, 8)
c2 += 5 // (6, 8, 5)
c2 += 1 // (6, 8, 5, 1)
val c3 = DLLCombinerImplementation(4)
c3 += 1 // (1)
c1.combine(c2).combine(c3) // (7, 2, 4, 3) <-> (9, 5, 1) <-> (6, 8, 5, 1) <-> (1)
val res = c1.result() // (7, 2, 4, 3, 9, 5, 1, 6, 8, 5, 1, 1)
```
You can look at the public tests to find more examples.
In your solution you should only make changes to the `DLLCombinerImplementation` class. You are not allowed to change the file `lib.scala`. You can get partial points for solving parts of this exercise.
# General
*.DS_Store
*.swp
*~
# Dotty
*.class
*.tasty
*.hasTasty
# sbt
target/
# IDE
.bsp
.bloop
.metals
.vscode
# datasets
stackoverflow-grading.csv
wikipedia-grading.dat
// Student tasks (i.e. submit, packageSubmission)
enablePlugins(StudentTasks)
course := "midterm"
assignment := "m6"
scalaVersion := "3.0.0-RC1"
scalacOptions ++= Seq("-language:implicitConversions", "-deprecation")
libraryDependencies += "org.scalameta" %% "munit" % "0.7.22"
val MUnitFramework = new TestFramework("munit.Framework")
testFrameworks += MUnitFramework
// Decode Scala names
testOptions += Tests.Argument(MUnitFramework, "-s")
testSuite := "m6.M6Suite"
File deleted
package sbt // To access the private[sbt] compilerReporter key
package filteringReporterPlugin
import Keys._
import ch.epfl.lamp._
object FilteringReporterPlugin extends AutoPlugin {
override lazy val projectSettings = Seq(
// Turn off warning coming from scalameter that we cannot fix without changing scalameter
compilerReporter in (Compile, compile) ~= { reporter => new FilteringReporter(reporter) }
)
}
class FilteringReporter(reporter: xsbti.Reporter) extends xsbti.Reporter {
def reset(): Unit = reporter.reset()
def hasErrors: Boolean = reporter.hasErrors
def hasWarnings: Boolean = reporter.hasWarnings
def printSummary(): Unit = reporter.printSummary()
def problems: Array[xsbti.Problem] = reporter.problems
def log(problem: xsbti.Problem): Unit = {
if (!problem.message.contains("An existential type that came from a Scala-2 classfile cannot be"))
reporter.log(problem)
}
def comment(pos: xsbti.Position, msg: String): Unit =
reporter.comment(pos, msg)
override def toString = s"CollectingReporter($reporter)"
}
package ch.epfl.lamp
import sbt._
import sbt.Keys._
/**
* Coursera uses two versions of each assignment. They both have the same assignment key and part id but have
* different item ids.
*
* @param key Assignment key
* @param partId Assignment partId
* @param itemId Item id of the non premium version
* @param premiumItemId Item id of the premium version (`None` if the assignment is optional)
*/
case class CourseraId(key: String, partId: String, itemId: String, premiumItemId: Option[String])
/**
* Settings shared by all assignments, reused in various tasks.
*/
object MOOCSettings extends AutoPlugin {
override def requires = super.requires && filteringReporterPlugin.FilteringReporterPlugin
object autoImport {
val course = SettingKey[String]("course")
val assignment = SettingKey[String]("assignment")
val options = SettingKey[Map[String, Map[String, String]]]("options")
val courseraId = settingKey[CourseraId]("Coursera-specific information identifying the assignment")
val testSuite = settingKey[String]("Fully qualified name of the test suite of this assignment")
.withRank(KeyRanks.Invisible)
// Convenient alias
type CourseraId = ch.epfl.lamp.CourseraId
val CourseraId = ch.epfl.lamp.CourseraId
}
import autoImport._
override val globalSettings: Seq[Def.Setting[_]] = Seq(
// supershell is verbose, buggy and useless.
useSuperShell := false
)
override val projectSettings: Seq[Def.Setting[_]] = Seq(
parallelExecution in Test := false,
// Report test result after each test instead of waiting for every test to finish
logBuffered in Test := false,
name := s"${course.value}-${assignment.value}"
)
}
package ch.epfl.lamp
import sbt._
import Keys._
// import scalaj.http._
import java.io.{File, FileInputStream, IOException}
import org.apache.commons.codec.binary.Base64
// import play.api.libs.json.{Json, JsObject, JsPath}
import scala.util.{Failure, Success, Try}
/**
* Provides tasks for submitting the assignment
*/
object StudentTasks extends AutoPlugin {
override def requires = super.requires && MOOCSettings
object autoImport {
val packageSourcesOnly = TaskKey[File]("packageSourcesOnly", "Package the sources of the project")
val packageBinWithoutResources = TaskKey[File]("packageBinWithoutResources", "Like packageBin, but without the resources")
val packageSubmissionZip = TaskKey[File]("packageSubmissionZip")
val packageSubmission = inputKey[Unit]("package solution as an archive file")
lazy val Grading = config("grading") extend(Runtime)
}
import autoImport._
import MOOCSettings.autoImport._
override lazy val projectSettings = Seq(
packageSubmissionSetting,
fork := true,
connectInput in run := true,
outputStrategy := Some(StdoutOutput),
) ++
packageSubmissionZipSettings ++
inConfig(Grading)(Defaults.testSettings ++ Seq(
unmanagedJars += file("grading-tests.jar"),
definedTests := (definedTests in Test).value,
internalDependencyClasspath := (internalDependencyClasspath in Test).value
))
/** **********************************************************
* SUBMITTING A SOLUTION TO COURSERA
*/
val packageSubmissionZipSettings = Seq(
packageSubmissionZip := {
val submission = crossTarget.value / "submission.zip"
val sources = (packageSourcesOnly in Compile).value
val binaries = (packageBinWithoutResources in Compile).value
IO.zip(Seq(sources -> "sources.zip", binaries -> "binaries.jar"), submission, None)
submission
},
artifactClassifier in packageSourcesOnly := Some("sources"),
artifact in (Compile, packageBinWithoutResources) ~= (art => art.withName(art.name + "-without-resources"))
) ++
inConfig(Compile)(
Defaults.packageTaskSettings(packageSourcesOnly, Defaults.sourceMappings) ++
Defaults.packageTaskSettings(packageBinWithoutResources, Def.task {
val relativePaths =
(unmanagedResources in Compile).value.flatMap(Path.relativeTo((unmanagedResourceDirectories in Compile).value)(_))
(mappings in (Compile, packageBin)).value.filterNot { case (_, path) => relativePaths.contains(path) }
})
)
val maxSubmitFileSize = {
val mb = 1024 * 1024
10 * mb
}
/** Check that the jar exists, isn't empty, isn't crazy big, and can be read
* If so, encode jar as base64 so we can send it to Coursera
*/
def prepareJar(jar: File, s: TaskStreams): String = {
val errPrefix = "Error submitting assignment jar: "
val fileLength = jar.length()
if (!jar.exists()) {
s.log.error(errPrefix + "jar archive does not exist\n" + jar.getAbsolutePath)
failSubmit()
} else if (fileLength == 0L) {
s.log.error(errPrefix + "jar archive is empty\n" + jar.getAbsolutePath)
failSubmit()
} else if (fileLength > maxSubmitFileSize) {
s.log.error(errPrefix + "jar archive is too big. Allowed size: " +
maxSubmitFileSize + " bytes, found " + fileLength + " bytes.\n" +
jar.getAbsolutePath)
failSubmit()
} else {
val bytes = new Array[Byte](fileLength.toInt)
val sizeRead = try {
val is = new FileInputStream(jar)
val read = is.read(bytes)
is.close()
read
} catch {
case ex: IOException =>
s.log.error(errPrefix + "failed to read sources jar archive\n" + ex.toString)
failSubmit()
}
if (sizeRead != bytes.length) {
s.log.error(errPrefix + "failed to read the sources jar archive, size read: " + sizeRead)
failSubmit()
} else encodeBase64(bytes)
}
}
/** Task to package solution to a given file path */
lazy val packageSubmissionSetting = packageSubmission := {
val args: Seq[String] = Def.spaceDelimited("[path]").parsed
val s: TaskStreams = streams.value // for logging
val jar = (packageSubmissionZip in Compile).value
val base64Jar = prepareJar(jar, s)
val path = args.headOption.getOrElse((baseDirectory.value / "submission.jar").absolutePath)
scala.tools.nsc.io.File(path).writeAll(base64Jar)
}
/*
/** Task to submit a solution to coursera */
val submit = inputKey[Unit]("submit solution to Coursera")
lazy val submitSetting = submit := {
// Fail if scalafix linting does not pass.
scalafixLinting.value
val args: Seq[String] = Def.spaceDelimited("<arg>").parsed
val s: TaskStreams = streams.value // for logging
val jar = (packageSubmissionZip in Compile).value
val assignmentDetails =
courseraId.?.value.getOrElse(throw new MessageOnlyException("This assignment can not be submitted to Coursera because the `courseraId` setting is undefined"))
val assignmentKey = assignmentDetails.key
val courseName =
course.value match {
case "capstone" => "scala-capstone"
case "bigdata" => "scala-spark-big-data"
case other => other
}
val partId = assignmentDetails.partId
val itemId = assignmentDetails.itemId
val premiumItemId = assignmentDetails.premiumItemId
val (email, secret) = args match {
case email :: secret :: Nil =>
(email, secret)
case _ =>
val inputErr =
s"""|Invalid input to `submit`. The required syntax for `submit` is:
|submit <email-address> <submit-token>
|
|The submit token is NOT YOUR LOGIN PASSWORD.
|It can be obtained from the assignment page:
|https://www.coursera.org/learn/$courseName/programming/$itemId
|${
premiumItemId.fold("") { id =>
s"""or (for premium learners):
|https://www.coursera.org/learn/$courseName/programming/$id
""".stripMargin
}
}
""".stripMargin
s.log.error(inputErr)
failSubmit()
}
val base64Jar = prepareJar(jar, s)
val json =
s"""|{
| "assignmentKey":"$assignmentKey",
| "submitterEmail":"$email",
| "secret":"$secret",
| "parts":{
| "$partId":{
| "output":"$base64Jar"
| }
| }
|}""".stripMargin
def postSubmission[T](data: String): Try[HttpResponse[String]] = {
val http = Http("https://www.coursera.org/api/onDemandProgrammingScriptSubmissions.v1")
val hs = List(
("Cache-Control", "no-cache"),
("Content-Type", "application/json")
)
s.log.info("Connecting to Coursera...")
val response = Try(http.postData(data)
.headers(hs)
.option(HttpOptions.connTimeout(10000)) // scalaj default timeout is only 100ms, changing that to 10s
.asString) // kick off HTTP POST
response
}
val connectMsg =
s"""|Attempting to submit "${assignment.value}" assignment in "$courseName" course
|Using:
|- email: $email
|- submit token: $secret""".stripMargin
s.log.info(connectMsg)
def reportCourseraResponse(response: HttpResponse[String]): Unit = {
val code = response.code
val respBody = response.body
/* Sample JSON response from Coursera
{
"message": "Invalid email or token.",
"details": {
"learnerMessage": "Invalid email or token."
}
}
*/
// Success, Coursera responds with 2xx HTTP status code
if (response.is2xx) {
val successfulSubmitMsg =
s"""|Successfully connected to Coursera. (Status $code)
|
|Assignment submitted successfully!
|
|You can see how you scored by going to:
|https://www.coursera.org/learn/$courseName/programming/$itemId/
|${
premiumItemId.fold("") { id =>
s"""or (for premium learners):
|https://www.coursera.org/learn/$courseName/programming/$id
""".stripMargin
}
}
|and clicking on "My Submission".""".stripMargin
s.log.info(successfulSubmitMsg)
}
// Failure, Coursera responds with 4xx HTTP status code (client-side failure)
else if (response.is4xx) {
val result = Try(Json.parse(respBody)).toOption
val learnerMsg = result match {
case Some(resp: JsObject) =>
(JsPath \ "details" \ "learnerMessage").read[String].reads(resp).get
case Some(x) => // shouldn't happen
"Could not parse Coursera's response:\n" + x
case None =>
"Could not parse Coursera's response:\n" + respBody
}
val failedSubmitMsg =
s"""|Submission failed.
|There was something wrong while attempting to submit.
|Coursera says:
|$learnerMsg (Status $code)""".stripMargin
s.log.error(failedSubmitMsg)
}
// Failure, Coursera responds with 5xx HTTP status code (server-side failure)
else if (response.is5xx) {
val failedSubmitMsg =
s"""|Submission failed.
|Coursera seems to be unavailable at the moment (Status $code)
|Check https://status.coursera.org/ and try again in a few minutes.
""".stripMargin
s.log.error(failedSubmitMsg)
}
// Failure, Coursera repsonds with an unexpected status code
else {
val failedSubmitMsg =
s"""|Submission failed.
|Coursera replied with an unexpected code (Status $code)
""".stripMargin
s.log.error(failedSubmitMsg)
}
}
// kick it all off, actually make request
postSubmission(json) match {
case Success(resp) => reportCourseraResponse(resp)
case Failure(e) =>
val failedConnectMsg =
s"""|Connection to Coursera failed.
|There was something wrong while attempting to connect to Coursera.
|Check your internet connection.
|${e.toString}""".stripMargin
s.log.error(failedConnectMsg)
}
}
*/
def failSubmit(): Nothing = {
sys.error("Submission failed")
}
/**
* *****************
* DEALING WITH JARS
*/
def encodeBase64(bytes: Array[Byte]): String =
new String(Base64.encodeBase64(bytes))
}
sbt.version=1.4.7
// Used for Coursera submission (StudentPlugin)
// libraryDependencies += "org.scalaj" %% "scalaj-http" % "2.4.2"
// libraryDependencies += "com.typesafe.play" %% "play-json" % "2.7.4"
// Used for Base64 (StudentPlugin)
libraryDependencies += "commons-codec" % "commons-codec" % "1.10"
// addSbtPlugin("org.scala-js" % "sbt-scalajs" % "0.6.28")
addSbtPlugin("ch.epfl.lamp" % "sbt-dotty" % "0.5.3")
package m6
import java.util.concurrent._
import scala.util.DynamicVariable
trait M6 extends Lib {
class DLLCombinerImplementation(chunk_size: Int = 3) extends DLLCombiner(chunk_size){
// Computes every other Integer element of data array, starting from the first (index 0), up to the middle
def task1(data: Array[Int]): ForkJoinTask[Unit] = task {
var current = head
if(current != null) {
var i_from = 0
var i_to = 0
copyForward(data, current, i_from, i_to, cnt/2)
}
}
// Computes every other Integer element of data array, starting from the second, up to the middle
def task2(data: Array[Int]): ForkJoinTask[Unit] = task {
var current = head
if(current != null) {
var i_from = 1
var i_to = 1
if(i_from >= current.cnt) {
current = current.next
if(current != null) {
i_from = 0
}
else i_to = cnt/2 // to stop the loop
}
copyForward(data, current, i_from, i_to, cnt/2)
}
}
// Computes every other Integer element of data array, starting from the second to last, up to the middle
def task3(data: Array[Int]): ForkJoinTask[Unit] = task {
var current = last
if( current != null) {
var i_to = cnt - 2
var i_from = current.cnt - 2
if(i_from < 0) {
current = current.previous
if(current != null) {
i_from = current.cnt - 1
}
else i_to = cnt/2 - 1 // to stop the loop
}
copyBackward(data, current, i_from, i_to, cnt/2)
}
}
// Computes every other Integer element of data array, starting from the last, up to the middle
// This is executed on the current thread.
def task4(data: Array[Int]): Unit = {
var current = last
if( current != null) {
var i_from = current.cnt - 1
var i_to = cnt - 1
copyBackward(data, current, i_from, i_to, cnt/2)
}
}
def result(): Array[Int] = {
val data = new Array[Int](cnt)
val t1 = task1(data)
val t2 = task2(data)
val t3 = task3(data)
task4(data)
t1.join()
t2.join()
t3.join()
data
}
private def copyBackward(data: Array[Int], curr: Node, from: Int, to: Int, limit: Int) = {
var current = curr
var i_from = from
var i_to = to
while (i_to >= limit) {
try{
data(i_to) = current.value(i_from)
i_to -= 2
i_from -= 2
if(i_from == -1) {
current = current.previous
i_from = current.cnt - 1
}
else if(i_from == -2) {
current = current.previous
i_from = current.cnt - 2
if(current.cnt == 1) {
current = current.previous
i_from = current.cnt - 1
}
}
}
catch{
case e: Exception =>
}
}
}
private def copyForward(data: Array[Int], curr: Node, from: Int, to: Int, limit: Int) = {
var current = curr
var i_from = from
var i_to = to
while (i_to < limit) {
try {
data(i_to) = current.value(i_from)
i_to += 2
i_from += 2
if(i_from == current.cnt){
current = current.next
i_from = 0
}
else if(i_from > current.cnt) {
current = current.next
i_from = 1
if(current.cnt == 1) {
current = current.next
i_from = 0
}
}
}
catch{
case e: Exception =>
}
}
}
}
}
package m6
import java.util.concurrent._
import scala.util.DynamicVariable
trait Lib {
class Node(val size: Int) {
var value: Array[Int] = new Array(size)
var next: Node = null
var previous: Node = null
var cnt = 0
def add(v: Int) = {
value(cnt) = v
cnt += 1
}
}
// Simplified Combiner interface
// Implements methods += and combine
// Abstract methods should be implemented in subclasses
abstract class DLLCombiner(val chunk_size: Int) {
var head: Node = null
var last: Node = null
var cnt: Int = 0
var chunks: Int = 0
// Adds an Integer to the last node of this array combiner. If the last node is full, allocates a new node.
def +=(elem: Int): Unit = {
if(cnt % chunk_size == 0) {
chunks = chunks + 1
val node = new Node(chunk_size)
if (cnt == 0) {
head = node
last = node
}
else {
last.next = node
node.previous = last
last = node
}
}
last.add(elem)
cnt += 1
}
// Combines this array combiner and another given combiner in constant O(1) complexity.
def combine(that: DLLCombiner): DLLCombiner = {
assert(this.chunk_size == that.chunk_size)
if (this.cnt == 0) {
this.head = that.head
this.last = that.last
this.cnt = that.cnt
this.chunks = that.chunks
this
}
else if (that.cnt == 0)
this
else {
this.last.next = that.head
that.head.previous = this.last
this.cnt = this.cnt + that.cnt
this.chunks = this.chunks + that.chunks
this.last = that.last
this
}
}
def task1(data: Array[Int]): ForkJoinTask[Unit]
def task2(data: Array[Int]): ForkJoinTask[Unit]
def task3(data: Array[Int]): ForkJoinTask[Unit]
def task4(data: Array[Int]): Unit
def result(): Array[Int]
}
def task[T](body: => T): ForkJoinTask[T]
}
\ No newline at end of file