Skip to content
Snippets Groups Projects
Commit 1384f1fb authored by Matt Bovel's avatar Matt Bovel
Browse files

Add midterm solutions

parent 3438db3e
No related branches found
No related tags found
No related merge requests found
Showing
with 25101 additions and 0 deletions
.vscode
.metals
.bloop
.bsp
target
metals.sbt
build.properties
version = "3.4.0"
runner.dialect = scala3
rewrite.scala3.convertToNewSyntax = true
rewrite.scala3.removeOptionalBraces = true
# How to run
This folder contains the code of most of the exercises of the midterm, along with tests and comments.
- Questions 1-3: [Part1.scala](src/main/scala/midterm/Part1.scala)
- Questions 4-7: [Part2.scala](src/main/scala/midterm/Part2.scala)
- Question 8: [Part3.scala](src/main/scala/midterm/Part3.scala)
- Questions 9-15: [Part4.scala](src/main/scala/midterm/Part4.scala)
- Question 21: [Part6.scala](src/main/scala/midterm/Part6.scala)
- Questions 22-24: [Part7.scala](src/main/scala/midterm/Part7.scala)
- Question 25: [Part8.scala](src/main/scala/midterm/Part8.scala)
## Test
```
sbt test
```
or to run a specific suite:
```
sbt testOnly midterm.Part1Test
```
## Run a main function
```
sbt runMain midterm.part1
```
## Format
```
sbt scalafmt
```
## Benchmark
```
sbt jmh:run -bm ss -i 40 -wi 5 -rf JSON -rff CollectionsBenchmarkResults.json bench.CollectionBenchmark
```
[info] Benchmark (collection) (size) Mode Cnt Score Error Units
[info] CollectionBenchmark.drop Vector 10000 ss 200 ≈ 10⁻⁵ s/op
[info] CollectionBenchmark.drop Vector 100000 ss 200 ≈ 10⁻⁵ s/op
[info] CollectionBenchmark.drop Vector 1000000 ss 200 ≈ 10⁻⁵ s/op
[info] CollectionBenchmark.drop Vector 10000000 ss 200 ≈ 10⁻⁵ s/op
[info] CollectionBenchmark.drop Array 10000 ss 200 ≈ 10⁻⁴ s/op
[info] CollectionBenchmark.drop Array 100000 ss 200 ≈ 10⁻⁴ s/op
[info] CollectionBenchmark.drop Array 1000000 ss 200 0.003 ± 0.001 s/op
[info] CollectionBenchmark.drop Array 10000000 ss 200 0.019 ± 0.002 s/op
[info] CollectionBenchmark.drop ArrayBuffer 10000 ss 200 ≈ 10⁻⁴ s/op
[info] CollectionBenchmark.drop ArrayBuffer 100000 ss 200 ≈ 10⁻⁴ s/op
[info] CollectionBenchmark.drop ArrayBuffer 1000000 ss 200 0.002 ± 0.001 s/op
[info] CollectionBenchmark.drop ArrayBuffer 10000000 ss 200 0.017 ± 0.007 s/op
[info] CollectionBenchmark.drop List 10000 ss 200 ≈ 10⁻⁴ s/op
[info] CollectionBenchmark.drop List 100000 ss 200 ≈ 10⁻⁴ s/op
[info] CollectionBenchmark.drop List 1000000 ss 200 0.002 ± 0.001 s/op
[info] CollectionBenchmark.drop List 10000000 ss 200 0.024 ± 0.001 s/op
[info] CollectionBenchmark.take Vector 10000 ss 200 ≈ 10⁻⁵ s/op
[info] CollectionBenchmark.take Vector 100000 ss 200 ≈ 10⁻⁵ s/op
[info] CollectionBenchmark.take Vector 1000000 ss 200 ≈ 10⁻⁵ s/op
[info] CollectionBenchmark.take Vector 10000000 ss 200 ≈ 10⁻⁵ s/op
[info] CollectionBenchmark.take Array 10000 ss 200 ≈ 10⁻⁴ s/op
[info] CollectionBenchmark.take Array 100000 ss 200 ≈ 10⁻⁴ s/op
[info] CollectionBenchmark.take Array 1000000 ss 200 0.003 ± 0.001 s/op
[info] CollectionBenchmark.take Array 10000000 ss 200 0.018 ± 0.002 s/op
[info] CollectionBenchmark.take ArrayBuffer 10000 ss 200 ≈ 10⁻⁴ s/op
[info] CollectionBenchmark.take ArrayBuffer 100000 ss 200 ≈ 10⁻⁴ s/op
[info] CollectionBenchmark.take ArrayBuffer 1000000 ss 200 0.003 ± 0.001 s/op
[info] CollectionBenchmark.take ArrayBuffer 10000000 ss 200 0.019 ± 0.007 s/op
[info] CollectionBenchmark.take List 10000 ss 200 ≈ 10⁻⁴ s/op
[info] CollectionBenchmark.take List 100000 ss 200 0.001 ± 0.001 s/op
[info] CollectionBenchmark.take List 1000000 ss 200 0.004 ± 0.003 s/op
[info] CollectionBenchmark.take List 10000000 ss 200 0.460 ± 0.237 s/op
previous-exams/2022-midterm-code/bench-results/CollectionBenchmarkResults_drop.png

204 KiB

previous-exams/2022-midterm-code/bench-results/CollectionBenchmarkResults_take.png

235 KiB

This diff is collapsed.
previous-exams/2022-midterm-code/bench-results/Part2BenchmarkResults.png

546 KiB

val scala3Version = "3.1.2"
enablePlugins(JmhPlugin)
lazy val root = project
.in(file("."))
.settings(
name := "code",
version := "0.1.0-SNAPSHOT",
scalaVersion := scala3Version,
libraryDependencies += "org.scala-lang.modules" %% "scala-parallel-collections" % "1.0.3",
libraryDependencies += "junit" % "junit" % "4.13" % Test,
libraryDependencies += "com.github.sbt" % "junit-interface" % "0.13.3" % Test,
Test / parallelExecution := false,
Test / testOptions += Tests.Argument(TestFrameworks.JUnit)
)
addSbtPlugin("pl.project13.scala" % "sbt-jmh" % "0.4.3")
addSbtPlugin("org.scalameta" % "sbt-scalafmt" % "2.4.6")
package bench
import org.openjdk.jmh.annotations.*
@State(Scope.Benchmark)
//@Fork(jvmArgsAppend = Array("-Djava.util.concurrent.ForkJoinPool.common.parallelism=4"))
abstract class AbstractCollectionBenchmark:
@Param(Array("10000", "100000", "1000000", "10000000"))
var size: Int = _
@Param(Array("Vector", "Array", "ArrayBuffer", "List"))
var collection: String = _
var haystack: Iterable[Int] = _
@Setup(Level.Invocation)
def setup() =
val gen = (1 to (size * 2) by 2)
haystack = collection match
case "Vector" => gen.toVector
case "Array" => gen.toArray
case "ArrayBuffer" => gen.toBuffer
case "List" => gen.toList
package bench
import org.openjdk.jmh.annotations.*
class CollectionBenchmark extends AbstractCollectionBenchmark:
@Benchmark
def take() =
haystack.take(size / 2)
@Benchmark
def drop() =
haystack.drop(size / 2)
package bench
import midterm.contains
import org.openjdk.jmh.annotations.*
class MidtermPart2Benchmark extends AbstractCollectionBenchmark:
val needle = 10
@Param(Array("true", "false"))
var parallel: Boolean = _
@Setup(Level.Invocation)
override def setup() =
super.setup()
midterm.parallelismEnabled = parallel
@Benchmark
def containsBenchmark() =
contains(haystack, needle)
package midterm
import scala.collection.mutable.Set
@main def mock1() =
val values = Set[Int]()
for _ <- 1 to 100000 do
var sum = 0
val t1 = task { sum += 1 }
val t2 = task { sum += 1 }
t1.join()
t2.join()
values += sum
println(values)
package midterm
import midterm.instrumentation.Monitor
class Account(private var amount: Int = 0) extends Monitor:
def transfer(target: Account, n: Int) =
this.synchronized {
target.synchronized {
this.amount -= n
target.amount += n
}
}
@main def mock2() =
val a = new Account(50)
val b = new Account(70)
val t1 = task { a.transfer(b, 10) }
val t2 = task { b.transfer(a, 10) }
t1.join()
t2.join()
package midterm
import scala.collection.parallel.Task
import scala.collection.parallel.CollectionConverters.*
// Questions 1-3
// See tests in midterm.Part1Test.
// Run with `sbt testOnly midterm.Part1Test`.
def parallel3[A, B, C](op1: => A, op2: => B, op3: => C): (A, B, C) =
val res1 = task { op1 }
val res2 = task { op2 }
val res3 = op3
(res1.join(), res2.join(), res3)
def find(arr: Array[Int], value: Int, threshold: Int): Option[Int] =
def findHelper(start: Int, end: Int): Option[Int] =
if end - start <= threshold then
var i = start
while i < end do
if arr(i) == value then return Some(value)
i += 1
None
else
val inc = (end - start) / 3
val (res1, res2, res3) = parallel3(
findHelper(start, start + inc),
findHelper(start + inc, start + 2 * inc),
findHelper(start + 2 * inc, end)
)
res1.orElse(res2).orElse(res3)
findHelper(0, arr.size)
def findAggregated(arr: Array[Int], value: Int): Option[Int] =
val no: Option[Int] = None
val yes: Option[Int] = Some(value)
def f = (x1: Option[Int], x2: Int) => if x2 == value then Some(x2) else x1
def g = (x1: Option[Int], x2: Option[Int]) => if x1 != None then x1 else x2
arr.par.aggregate(no)(f, g)
@main def part1() =
println(find(Array(1, 2, 3), 2, 1))
// See tests in Part1Test
package midterm
// Questions 4-7
// See tests in midterm.Part2Test.
// Run with `sbt testOnly midterm.Part2Test`.
/*
Answers to the exam questions:
When called with a Vector:
The total amount of work is O(n), as it is dominated by the time needed to
read the array. More precisely W(n) = c + 2*W(n/2) = O(n).
The depth is O(log(n)), because every recursion takes constant time
and we divide the size of the input by 2 every time, i.e. D(n) = c + D(n/2) = O(log(n)).
Note however that in practice it is often still faster to manipulate
start and end indices rather than using take and drop.
When called with a List:
Every recursion takes up to time O(n) rather than constant time.
The total amount of work is O(n) times the number of recursion, because
take and drop takes time O(n) on lists. Precisely, W(n) = n + 2*W(n/2) = O(log(n)*n)
The depth is computed similarly: D(n) = n + D(n/2) = O(n), i.e.
Note: these are theoretical results. In practice, you should always double-check
that kind of conclusions with benchmarks. We did so in
`midterm-code/src/main/scala/bench`. Results are available in `bench-results`.
From these results, we can conclude that
1. Vectors are indeed faster in this case, and
2. parallelization of `contains` yields a 2x speedup.
*/
def contains[A](l: Iterable[A], elem: A): Boolean =
val n = l.size
if n <= 5 then
for i <- l do if i == elem then return true
false
else
val (p0, p1) = parallel(
contains(l.take(n / 2), elem),
contains(l.drop(n / 2), elem)
)
p0 || p1
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment