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
  • lara/cs206-demos
  • gcharles/cs206-demos
  • gambhir/cs206-demos
3 results
Show changes
Commits on Source (7)
...@@ -21,4 +21,4 @@ test: ...@@ -21,4 +21,4 @@ test:
tags: tags:
- cs320 - cs320
script: script:
- sbt "runMain lecture1.javaThreads; runMain lecture1.scalaThreadWrapper; runMain lecture1.ExampleThread; runMain lecture3.intersectionWrong; runMain lecture3.intersectionCorrect; runMain lecture3.intersectionNoSideEffect; runMain lecture3.parallelGraphContraction; runMain lecture3.parallelGraphContractionCorrect; runMain midterm22.mock1; runMain midterm22.part3; test; scalafmtCheck; Test / scalafmtCheck" - sbt "runMain lecture1.javaThreads; runMain lecture1.scalaThreadWrapper; runMain lecture1.ExampleThread; runMain lecture3.intersectionWrong; runMain lecture3.intersectionCorrect; runMain lecture3.intersectionNoSideEffect; runMain lecture3.parallelGraphContraction; runMain lecture3.parallelGraphContractionCorrect; runMain midterm22.mock1; runMain midterm22.part3; runMain ed.patternMatching; runMain lecture13.askPatternDemo; test; runMain lecture13.becomeDemo; runMain ed.futuresForTranslation; runMain concpar20final03.concpar20final03; scalafmtCheck; Test / scalafmtCheck"
package concpar20final03
import scala.concurrent.{Await, Future}
import scala.concurrent.duration.Duration
import scala.concurrent.ExecutionContext.Implicits.global
import scala.util.{Failure, Success}
def sequence1[A](fs: List[Future[A]]): Future[List[A]] =
fs match
case f :: fs =>
for
x <- f
xs <- sequence1(fs)
yield x :: xs
case Nil =>
Future.successful(Nil)
def sequence2[A](fs: List[Future[A]]): Future[List[A]] =
fs match
case f :: fs =>
f.flatMap(x => sequence2(fs).map(xs => x :: xs))
case Nil =>
Future.successful(Nil)
def sequence3[A](fs: List[Future[A]]): Future[List[A]] =
fs match
case f :: fs =>
f.transformWith {
case Failure(e) => Future.failed(e)
case Success(value) =>
sequence3(fs).transform {
case res: Failure[List[A]] => res
case Success(values) => Success(value :: values)
}
}
case Nil =>
Future.successful(Nil)
def sequence4[A](fs: List[Future[A]]): Future[List[A]] =
fs match
case f :: fs =>
val fsFuture: Future[List[A]] = sequence4(fs)
f.zip(fsFuture).map((fValue, fsValue) => fValue :: fsValue)
case Nil =>
Future.successful(Nil)
@main def concpar20final03 =
val xs = List(Future { 1 }, Future { 2 }, Future { 3 })
val ys = List(Future { 1 }, Future.failed(Error("BOOM")), Future { 3 })
println(Await.result(sequence1(xs), Duration.Inf))
// println(Await.result(sequence1(xs), Duration.Inf)) // Throws exception
println(Await.result(sequence2(xs), Duration.Inf))
// println(Await.result(sequence2(ys), Duration.Inf)) // Throws exception
println(Await.result(sequence3(xs), Duration.Inf))
// println(Await.result(sequence3(ys), Duration.Inf)) // Throws exception
println(Await.result(sequence4(xs), Duration.Inf))
// println(Await.result(sequence4(ys), Duration.Inf)) // Throws exception
package ed
// To demonstrate different ways of pattern matching, let's consider the // To demonstrate different ways of pattern matching, let's consider the
// following example case class and instance: // following example case class and instance:
case class Person(name: String, age: Int) case class Person(name: String, age: Int)
...@@ -5,20 +7,20 @@ val ada = Person("Ada", 36) ...@@ -5,20 +7,20 @@ val ada = Person("Ada", 36)
// Run using `sbt "runMain ed.patternMatching"`. // Run using `sbt "runMain ed.patternMatching"`.
@main def patternMatching = @main def patternMatching =
// There are several ways to pattern match on the `ada` instance: // There are several ways to pattern match on the `ada` instance:
// 1. Pattern matching on the case class constructor using `Person(name, age)`. // 1. Pattern matching on the case class constructor using `Person(name, age)`.
// If the pattern matches, the value of `n` is bound `ada.name` field, and // If the pattern matches, the value of `n` is bound `ada.name` field, and
// `a` is bound to the `ada.age` field. // `a` is bound to the `ada.age` field.
ada match ada match
case Person(n, a) => println(f"$n is $a years old") case Person(n, a) => println(f"$n is $a years old")
// 2. We can also check only that `ada` is of type `Person` without binding its // 2. We can also check only that `ada` is of type `Person` without binding its
// fields by using a `:` pattern. The `:` pattern is used to check if a value is // fields by using a `:` pattern. The `:` pattern is used to check if a value is
// of a certain type. // of a certain type.
ada match ada match
case p: Person => println(f"${p.name} is ${p.age} years old") case p: Person => println(f"${p.name} is ${p.age} years old")
// 3. If we want to both bind the fields and bind a value to the whole instance, // 3. If we want to both bind the fields and bind a value to the whole instance,
// we can use an `@` pattern. // we can use an `@` pattern.
ada match ada match
case p @ Person(n, a) => println(f"${p.name} is ${a} years old") case p @ Person(n, a) => println(f"${p.name} is ${a} years old")
package ed
import concurrent.{Await, Future}
import concurrent.duration.Duration
import concurrent.ExecutionContext.Implicits.global
// Pro Tip: you can print the code generated by the Scala compiler after the
// translation of for-comprehensions by running:
//
// scala -Xprint:firstTransform src/main/scala/ed/40056.scala
@main def futuresForTranslation =
val f1 = Future { 451 }
val f2 = Future { 1984 }
val f3 = for v1 <- f1; v2 <- f2 yield v1 + v2
println(Await.result(f3, Duration.Inf))
val f4 = Future { 451 }
val f5 = Future { 1984 }
val f6 = f4.flatMap(v4 => f5.map(v5 => v4 + v5))
println(Await.result(f6, Duration.Inf))
...@@ -9,10 +9,10 @@ import akka.pattern.pipe ...@@ -9,10 +9,10 @@ import akka.pattern.pipe
import akka.testkit.{ImplicitSender, TestKit} import akka.testkit.{ImplicitSender, TestKit}
import akka.util.Timeout import akka.util.Timeout
/** /** This demonstrates a simplified implementation of the ask pattern.
* This demonstrates a simplified implementation of the ask pattern.
* *
* @param promise the promise to be completed when a message is received * @param promise
* the promise to be completed when a message is received
*/ */
class AskMiniActor(promise: Promise[Any]) extends Actor: class AskMiniActor(promise: Promise[Any]) extends Actor:
def receive = LoggingReceive { def receive = LoggingReceive {
...@@ -21,16 +21,18 @@ class AskMiniActor(promise: Promise[Any]) extends Actor: ...@@ -21,16 +21,18 @@ class AskMiniActor(promise: Promise[Any]) extends Actor:
extension (receiver: ActorRef) extension (receiver: ActorRef)
def ?(msg: Any)(using def ?(msg: Any)(using
// In this simplified implementation, we don't use the timeout. // In this simplified implementation, we don't use the timeout.
timeout: Timeout, timeout: Timeout,
// This only used for logging purposes (and to get the actor system in the // This only used for logging purposes (and to get the actor system in the
// real implementation), but is not used otherwise in the implementation. // real implementation), but is not used otherwise in the implementation.
sender: ActorRef, sender: ActorRef,
// In the real implementation, the actor system is retrieved differently, // In the real implementation, the actor system is retrieved differently,
// but here we pass it as an additional implicit parameter for simplicity. // but here we pass it as an additional implicit parameter for simplicity.
context: ActorContext context: ActorContext
): Future[Any] = ): Future[Any] =
context.system.log.debug(s"Create mini actor to ask $msg from $sender to $receiver") context.system.log.debug(
s"Create mini actor to ask $msg from $sender to $receiver"
)
// Create a `Promise` that will be completed when a message is received. // Create a `Promise` that will be completed when a message is received.
val promise = Promise[Any]() val promise = Promise[Any]()
...@@ -49,91 +51,93 @@ extension (receiver: ActorRef) ...@@ -49,91 +51,93 @@ extension (receiver: ActorRef)
promise.future promise.future
object Person: object Person:
enum Protocol: enum Protocol:
case GetName case GetName
case GetAge case GetAge
enum Response: enum Response:
case Name(name: String) case Name(name: String)
case Age(age: Int) case Age(age: Int)
case UnknownMessage case UnknownMessage
class Person(name: String, age: Int) extends Actor: class Person(name: String, age: Int) extends Actor:
import Person.Protocol._ import Person.Protocol.*
import Person.Response._ import Person.Response.*
def receive = LoggingReceive { def receive = LoggingReceive {
case GetName => sender() ! Name(name) case GetName => sender() ! Name(name)
case GetAge => sender() ! Age(age) case GetAge => sender() ! Age(age)
case _ => sender() ! UnknownMessage case _ => sender() ! UnknownMessage
} }
object PersonsDatabase: object PersonsDatabase:
enum Protocol: enum Protocol:
case CreatePerson(name: String, age: Int) case CreatePerson(name: String, age: Int)
case GetPersonNames(ids: List[Int]) case GetPersonNames(ids: List[Int])
enum Response: enum Response:
case PersonCreated(id: Int) case PersonCreated(id: Int)
case PersonNames(names: List[String]) case PersonNames(names: List[String])
class PersonsDatabase extends Actor: class PersonsDatabase extends Actor:
import Person.Protocol._ import Person.Protocol.*
import Person.Response._ import Person.Response.*
import PersonsDatabase.Protocol._ import PersonsDatabase.Protocol.*
import PersonsDatabase.Response._ import PersonsDatabase.Response.*
var persons: Map[Int, ActorRef] = Map.empty var persons: Map[Int, ActorRef] = Map.empty
var maxId = 0 var maxId = 0
given ExecutionContext = context.system.dispatcher given ExecutionContext = context.system.dispatcher
given Timeout = Timeout(200.millis) given Timeout = Timeout(200.millis)
def receive = LoggingReceive { def receive = LoggingReceive {
case CreatePerson(name, age) => case CreatePerson(name, age) =>
val personRef = context.actorOf(Props(Person(name, age))) val personRef = context.actorOf(Props(Person(name, age)))
persons = persons + (maxId -> personRef) persons = persons + (maxId -> personRef)
sender() ! PersonCreated(maxId) sender() ! PersonCreated(maxId)
maxId += 1 maxId += 1
case GetPersonNames(ids) => case GetPersonNames(ids) =>
// We ask every person for their name using the ask pattern. This // We ask every person for their name using the ask pattern. This
// gives us a list of `Future`s that will each be completed with a // gives us a list of `Future`s that will each be completed with a
// `Name` message. // `Name` message.
val rawResponsesFutures: List[Future[Any]] = ids.map(id => (persons(id) ? GetName)) val rawResponsesFutures: List[Future[Any]] =
ids.map(id => (persons(id) ? GetName))
// We first map each `Future[Any]` to a `Future[Name]` using the
// `mapTo` method. Then, we map each `Future[Name]` to a // We first map each `Future[Any]` to a `Future[Name]` using the
// `Future[String]` using the `map` method. // `mapTo` method. Then, we map each `Future[Name]` to a
val namesFutures: List[Future[String]] = rawResponsesFutures.map(_.mapTo[Name].map(_.name)) // `Future[String]` using the `map` method.
val namesFutures: List[Future[String]] =
// We use the `Future.sequence` method to convert a rawResponsesFutures.map(_.mapTo[Name].map(_.name))
// `List[Future[String]]` to a `Future[List[String]]`. The resulting
// future will be completed once all the futures in the input list // We use the `Future.sequence` method to convert a
// are completed. // `List[Future[String]]` to a `Future[List[String]]`. The resulting
val futureOfNames: Future[List[String]] = Future.sequence(namesFutures) // future will be completed once all the futures in the input list
// are completed.
// Finally, map the `Future[List[String]]` to a val futureOfNames: Future[List[String]] = Future.sequence(namesFutures)
// `Future[PersonNames]` and pipe it to the sender of the
// `GetPersonNames` message. // Finally, map the `Future[List[String]]` to a `Future[PersonNames]` and
futureOfNames.map(PersonNames.apply).pipeTo(sender()) // pipe it to the sender of the `GetPersonNames` message.
} futureOfNames.map(PersonNames.apply).pipeTo(sender())
}
@main def askPatternDemo() = new TestKit(ActorSystem("DebugSystem")) with ImplicitSender:
import Person.Protocol._ @main def askPatternDemo() =
import Person.Response._ new TestKit(ActorSystem("DebugSystem")) with ImplicitSender:
import PersonsDatabase.Protocol._ import Person.Protocol.*
import PersonsDatabase.Response._ import Person.Response.*
import PersonsDatabase.Protocol.*
try import PersonsDatabase.Response.*
val personsDb = system.actorOf(Props(PersonsDatabase()))
try
personsDb ! CreatePerson("Anita", 20) val personsDb = system.actorOf(Props(PersonsDatabase()))
expectMsg(PersonCreated(0))
personsDb ! CreatePerson("Anita", 20)
personsDb ! CreatePerson("Berta", 30) expectMsg(PersonCreated(0))
expectMsg(PersonCreated(1))
personsDb ! CreatePerson("Berta", 30)
personsDb ! CreatePerson("Cecilia", 40) expectMsg(PersonCreated(1))
expectMsg(PersonCreated(2))
personsDb ! CreatePerson("Cecilia", 40)
personsDb ! GetPersonNames(List(0, 1, 2)) expectMsg(PersonCreated(2))
expectMsg(PersonNames(List("Anita", "Berta", "Cecilia")))
finally shutdown(system) personsDb ! GetPersonNames(List(0, 1, 2))
expectMsg(PersonNames(List("Anita", "Berta", "Cecilia")))
finally shutdown(system)
package lecture13
import akka.actor.{Actor, ActorContext, ActorRef, ActorSystem, Props}
import akka.event.LoggingReceive
import akka.testkit.{ImplicitSender, TestKit}
class CounterRight extends Actor:
def counter(n: Int): Receive = {
case "increment" => context.become(counter(n + 1), discardOld = false)
// ^^^^^^^^^^^^^^^^^^
// This is needed.
case "get" => sender() ! n
case "decrement" => if n != 0 then context.unbecome()
}
def receive = counter(0)
class CounterWrong extends Actor:
def counter(n: Int): Receive = {
case "increment" => context.become(counter(n + 1))
case "get" => sender() ! n
case "decrement" => if n != 0 then context.unbecome()
}
def receive = counter(0)
@main def becomeDemo() =
new TestKit(ActorSystem("DebugSystem")) with ImplicitSender:
try
val counter = system.actorOf(Props(CounterRight()), "counter")
counter ! "increment"
counter ! "get"
expectMsg(1)
counter ! "increment"
counter ! "get"
expectMsg(2)
counter ! "decrement"
counter ! "get"
expectMsg(1)
// This is wrong if we use CounterWrong because it doesn't set the
// discardOld parameter to false. Therefore, it will not remember the
// previous state and reset the behavior to the initial one, which is
// `counter(0)`.
counter ! "decrement"
counter ! "get"
expectMsg(0)
finally shutdown(system)
...@@ -16,13 +16,30 @@ def sequentialConvolve( ...@@ -16,13 +16,30 @@ def sequentialConvolve(
from: Int, from: Int,
until: Int until: Int
): Unit = { ): Unit = {
// Approach A, as described in the clarification announcement for this
// exercise, where we treat `from` and `until` as indices on `output`
// instead of `input` as in the given code.
var iOutput = from var iOutput = from
while iOutput < until do while iOutput < until do
var iKernel = math.max(0, iOutput - input.length + 1) var iKernel = math.max(0, iOutput - input.length + 1)
while iKernel < kernel.length && iKernel <= iOutput do while iKernel < kernel.length && iKernel <= iOutput do
// `output` is only ever written to between the indices `from` and
// `until`, the range of `iOutput`. The indices for `input` and
// `kernel` are computed accordingly.
output(iOutput) += input(iOutput - iKernel) * kernel(iKernel) output(iOutput) += input(iOutput - iKernel) * kernel(iKernel)
iKernel += 1 iKernel += 1
iOutput += 1 iOutput += 1
// ALTERNATE SOLUTION: Approach B, as described in the clarification
// announcement for this exercise, which is unchanged from the given
// code, i.e. we treat `from` and `until` as indices on `input`.
var iInput = from
while iInput < until do
var iKernel = 0
while iKernel < kernel.length do
output(iInput + iKernel) += input(iInput) * kernel(iKernel)
iKernel += 1
iInput += 1
} }
def parallelConvolve( def parallelConvolve(
...@@ -32,7 +49,12 @@ def parallelConvolve( ...@@ -32,7 +49,12 @@ def parallelConvolve(
from: Int, from: Int,
until: Int, until: Int,
threshold: Int threshold: Int
): Unit = ): Unit =
// Approach A, as described in the clarification announcement for this
// exercise, where we treat `from` and `until` as indices on `output`
// instead of `input` as in the given code. This does not require us to
// change anything in this function. Only receives full credit if used
// together with Approach A for `sequentialConvolve`.
if (until - from) <= threshold then if (until - from) <= threshold then
sequentialConvolve(input, kernel, output, from, until) sequentialConvolve(input, kernel, output, from, until)
else else
...@@ -42,6 +64,31 @@ def parallelConvolve( ...@@ -42,6 +64,31 @@ def parallelConvolve(
parallelConvolve(input, kernel, output, mid, until, threshold) parallelConvolve(input, kernel, output, mid, until, threshold)
) )
// ALTERNATE SOLUTION: Approach B, as described in the clarification
// announcement for this exercise, where we treat `from` and `until` as
// indices on `input` as in the given code. This requires up to leave a
// gap in the parallel calls to be filled in sequentially as described
// below. Only receives full credit if used together with Approach B for
// `sequentialConvolve`.
if (until - from) <= threshold then
sequentialConvolve(input, kernel, output, from, until)
else
val mid = from + (until - from) / 2
val gap = numContributeTo(input, kernel, output, mid)
// Leave a gap large enough that accesses to `output` from the first
// parallel call will not overlap with accesses from the second. This
// gap is of size equal to the number of elements of `input` that will
// contribute to the computation of the result at the lowest index of
// `output` in the second parallel call, namely, at index `mid`.
// However, we are careful not to access indices lower than `from`.
val gapBeforeMid = Math.max(from, mid - gap + 1)
parallel(
parallelConvolve(input, kernel, output, from, gapBeforeMid, threshold),
parallelConvolve(input, kernel, output, mid, until, threshold)
)
// Fill in the gap sequentially.
sequentialConvolve(input, kernel, output, gapBeforeMid, mid)
object Original_WithOutputRace: object Original_WithOutputRace:
def sequentialConvolve( def sequentialConvolve(
...@@ -50,15 +97,13 @@ object Original_WithOutputRace: ...@@ -50,15 +97,13 @@ object Original_WithOutputRace:
output: Array[Int], output: Array[Int],
from: Int, from: Int,
until: Int): Unit = until: Int): Unit =
var iOutput = from var iInput = from
while iOutput < until do while iInput < until do
var iKernel = 0 var iKernel = 0
while iKernel < kernel.length do while iKernel < kernel.length do
val iInput = iOutput - iKernel output(iInput + iKernel) += input(iInput) * kernel(iKernel)
if 0 <= iInput && iInput < input.length then
output(iOutput) += input(iInput) * kernel(iKernel)
iKernel += 1 iKernel += 1
iOutput += 1 iInput += 1
def parallelConvolve( def parallelConvolve(
input: Array[Int], input: Array[Int],
......