commit 03319aef3dc0e1d10ebc07d91c960186a5180eb7 Author: Timothée Floure Date: Wed Oct 30 15:34:26 2019 +0100 Import quickcheck handout diff --git a/.vscode/settings.json b/.vscode/settings.json new file mode 100644 index 0000000..a35362b --- /dev/null +++ b/.vscode/settings.json @@ -0,0 +1,8 @@ +{ + "dotty": { + "trace": { + "remoteTracingUrl": "wss://lamppc36.epfl.ch/dotty-remote-tracer/upload/lsp.log", + "server": { "format": "JSON", "verbosity": "verbose" } + } + } +} diff --git a/build.sbt b/build.sbt new file mode 100644 index 0000000..beabef7 --- /dev/null +++ b/build.sbt @@ -0,0 +1,11 @@ +course := "progfun2" +assignment := "quickcheck" +name := course.value + "-" + assignment.value +testSuite := "quickcheck.QuickCheckSuite" + +scalaVersion := "0.19.0-RC1" +scalacOptions ++= Seq("-language:implicitConversions", "-deprecation") +libraryDependencies += "com.novocode" % "junit-interface" % "0.11" % Test +libraryDependencies += ("org.scalacheck" %% "scalacheck" % "1.14.2").withDottyCompat(scalaVersion.value) + +testOptions in Test += Tests.Argument(TestFrameworks.JUnit, "-a", "-v", "-s") diff --git a/grading-tests.jar b/grading-tests.jar new file mode 100644 index 0000000..9820c2a Binary files /dev/null and b/grading-tests.jar differ diff --git a/project/MOOCSettings.scala b/project/MOOCSettings.scala new file mode 100644 index 0000000..ea30b36 --- /dev/null +++ b/project/MOOCSettings.scala @@ -0,0 +1,25 @@ +package ch.epfl.lamp + +import sbt._ +import sbt.Keys._ + +/** + * Settings shared by all assignments, reused in various tasks. + */ +object MOOCSettings extends AutoPlugin { + + object autoImport { + val course = SettingKey[String]("course") + val assignment = SettingKey[String]("assignment") + val testSuite = SettingKey[String]("testSuite") + val options = SettingKey[Map[String, Map[String, String]]]("options") + } + + override def trigger = allRequirements + + 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 + ) +} diff --git a/project/StudentTasks.scala b/project/StudentTasks.scala new file mode 100644 index 0000000..587ba85 --- /dev/null +++ b/project/StudentTasks.scala @@ -0,0 +1,323 @@ +package ch.epfl.lamp + +import sbt._ +import Keys._ + +// import scalaj.http._ +import java.io.{File, FileInputStream, IOException} +import java.nio.file.FileSystems +import org.apache.commons.codec.binary.Base64 +// import play.api.libs.json.{Json, JsObject, JsPath} +import scala.util.{Failure, Success, Try} + +import MOOCSettings.autoImport._ + +case class AssignmentInfo( + key: String, + itemId: String, + premiumItemId: Option[String], + partId: String +) + +/** + * Provides tasks for submitting the assignment + */ +object StudentTasks extends AutoPlugin { + + object autoImport { + val assignmentInfo = SettingKey[AssignmentInfo]("assignmentInfo") + + 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") + val runGradingTests = taskKey[Unit]("run black-box tests used for final grading") + } + + + import autoImport._ + + override lazy val projectSettings = Seq( + packageSubmissionSetting, + // submitSetting, // FIXME: restore assignmentInfo setting on assignments + runGradingTestsSettings, + + fork := true, + connectInput in run := true, + outputStrategy := Some(StdoutOutput) + ) ++ packageSubmissionZipSettings + + lazy val runGradingTestsSettings = runGradingTests := { + val testSuiteJar = "grading-tests.jar" + if (!new File(testSuiteJar).exists) { + throw new MessageOnlyException(s"Could not find tests JarFile: $testSuiteJar") + } + + val classPath = s"${(Test / dependencyClasspath).value.map(_.data).mkString(File.pathSeparator)}${File.pathSeparator}$testSuiteJar" + val junitProcess = + Fork.java.fork( + ForkOptions(), + "-cp" :: classPath :: + "org.junit.runner.JUnitCore" :: + (Test / testSuite).value :: + Nil + ) + + // Wait for tests to complete. + junitProcess.exitValue() + } + + + /** ********************************************************** + * 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) + 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 := { + val args: Seq[String] = Def.spaceDelimited("").parsed + val s: TaskStreams = streams.value // for logging + val jar = (packageSubmissionZip in Compile).value + + val assignmentDetails = assignmentInfo.value + 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 + | + |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)) +} diff --git a/project/build.properties b/project/build.properties new file mode 100644 index 0000000..c0bab04 --- /dev/null +++ b/project/build.properties @@ -0,0 +1 @@ +sbt.version=1.2.8 diff --git a/project/buildSettings.sbt b/project/buildSettings.sbt new file mode 100644 index 0000000..f7847f8 --- /dev/null +++ b/project/buildSettings.sbt @@ -0,0 +1,7 @@ +// Used for base64 encoding +libraryDependencies += "commons-codec" % "commons-codec" % "1.10" + +// Used for Coursera submussion +// libraryDependencies += "org.scalaj" %% "scalaj-http" % "2.3.0" +// libraryDependencies += "com.typesafe.play" %% "play-json" % "2.6.9" + diff --git a/project/plugins.sbt b/project/plugins.sbt new file mode 100644 index 0000000..64a2492 --- /dev/null +++ b/project/plugins.sbt @@ -0,0 +1,2 @@ +addSbtPlugin("io.get-coursier" % "sbt-coursier" % "2.0.0-RC3-5") +addSbtPlugin("ch.epfl.lamp" % "sbt-dotty" % "0.3.4") diff --git a/src/main/scala/quickcheck/Heap.scala b/src/main/scala/quickcheck/Heap.scala new file mode 100644 index 0000000..4475a8c --- /dev/null +++ b/src/main/scala/quickcheck/Heap.scala @@ -0,0 +1,119 @@ +package quickcheck + +trait IntHeap extends Heap { + override type A = Int + override def ord = scala.math.Ordering.Int +} + +// http://www.brics.dk/RS/96/37/BRICS-RS-96-37.pdf + +// Figure 1, page 3 +trait Heap { + type H // type of a heap + type A // type of an element + def ord: Ordering[A] // ordering on elements + + def empty: H // the empty heap + def isEmpty(h: H): Boolean // whether the given heap h is empty + + def insert(x: A, h: H): H // the heap resulting from inserting x into h + def meld(h1: H, h2: H): H // the heap resulting from merging h1 and h2 + + def findMin(h: H): A // a minimum of the heap h + def deleteMin(h: H): H // a heap resulting from deleting a minimum of h +} + +// Figure 3, page 7 +trait BinomialHeap extends Heap { + + type Rank = Int + case class Node(x: A, r: Rank, c: List[Node]) + override type H = List[Node] + + protected def root(t: Node) = t.x + protected def rank(t: Node) = t.r + protected def link(t1: Node, t2: Node): Node = // t1.r == t2.r + if ord.lteq(t1.x, t2.x) then + Node(t1.x, t1.r + 1, t2 :: t1.c) + else + Node(t2.x, t2.r + 1, t1 :: t2.c) + protected def ins(t: Node, ts: H): H = ts match + case Nil => List(t) + case tp :: ts => // t.r <= tp.r + if t.r < tp.r then + t :: tp :: ts + else + ins(link(t, tp), ts) + + override def empty = Nil + override def isEmpty(ts: H) = ts.isEmpty + + override def insert(x: A, ts: H) = ins(Node(x, 0, Nil), ts) + override def meld(ts1: H, ts2: H) = (ts1, ts2) match + case (Nil, ts) => ts + case (ts, Nil) => ts + case (t1 :: ts1, t2 :: ts2) => + if t1.r < t2.r then + t1 :: meld(ts1, t2 :: ts2) + else if t2.r < t1.r then + t2 :: meld(t1 :: ts1, ts2) + else + ins(link(t1, t2), meld(ts1, ts2)) + + override def findMin(ts: H) = ts match + case Nil => throw new NoSuchElementException("min of empty heap") + case t :: Nil => root(t) + case t :: ts => + val x = findMin(ts) + if ord.lteq(root(t), x) then + root(t) + else + x + override def deleteMin(ts: H) = ts match + case Nil => throw new NoSuchElementException("delete min of empty heap") + case t :: ts => + def getMin(t: Node, ts: H): (Node, H) = ts match + case Nil => (t, Nil) + case tp :: tsp => + val (tq, tsq) = getMin(tp, tsp) + if ord.lteq(root(t), root(tq)) then + (t, ts) + else + (tq, t :: tsq) + val (Node(_, _, c), tsq) = getMin(t, ts) + meld(c.reverse, tsq) +} + +trait Bogus1BinomialHeap extends BinomialHeap { + override def findMin(ts: H) = ts match + case Nil => throw new NoSuchElementException("min of empty heap") + case t :: ts => root(t) +} + +trait Bogus2BinomialHeap extends BinomialHeap { + override protected def link(t1: Node, t2: Node): Node = // t1.r == t2.r + if !ord.lteq(t1.x, t2.x) then + Node(t1.x, t1.r + 1, t2 :: t1.c) + else + Node(t2.x, t2.r + 1, t1 :: t2.c) +} + +trait Bogus3BinomialHeap extends BinomialHeap { + override protected def link(t1: Node, t2: Node): Node = // t1.r == t2.r + if ord.lteq(t1.x, t2.x) then + Node(t1.x, t1.r + 1, t1 :: t1.c) + else + Node(t2.x, t2.r + 1, t2 :: t2.c) +} + +trait Bogus4BinomialHeap extends BinomialHeap { + override def deleteMin(ts: H) = ts match + case Nil => throw new NoSuchElementException("delete min of empty heap") + case t :: ts => meld(t.c.reverse, ts) +} + +trait Bogus5BinomialHeap extends BinomialHeap { + override def meld(ts1: H, ts2: H) = ts1 match + case Nil => ts2 + case t1 :: ts1 => List(Node(t1.x, t1.r, ts1 ++ ts2)) +} diff --git a/src/main/scala/quickcheck/QuickCheck.scala b/src/main/scala/quickcheck/QuickCheck.scala new file mode 100644 index 0000000..59ca28b --- /dev/null +++ b/src/main/scala/quickcheck/QuickCheck.scala @@ -0,0 +1,18 @@ +package quickcheck + +import org.scalacheck._ +import Arbitrary._ +import Gen._ +import Prop.{BooleanOperators => _, _} + +abstract class QuickCheckHeap extends Properties("Heap") with IntHeap { + + lazy val genHeap: Gen[H] = ??? + implicit lazy val arbHeap: Arbitrary[H] = Arbitrary(genHeap) + + property("gen1") = forAll { (h: H) => + val m = if isEmpty(h) then 0 else findMin(h) + findMin(insert(m, h)) == m + } + +} diff --git a/src/test/scala/quickcheck/QuickCheckSuite.scala b/src/test/scala/quickcheck/QuickCheckSuite.scala new file mode 100644 index 0000000..de89e84 --- /dev/null +++ b/src/test/scala/quickcheck/QuickCheckSuite.scala @@ -0,0 +1,55 @@ +package quickcheck + +import org.scalacheck.Properties +import org.junit._ + +import org.scalacheck.Arbitrary._ +import org.scalacheck.Prop +import org.scalacheck.Prop.{BooleanOperators => _, _} +import org.scalacheck.Test.{check, Result, Failed, PropException} + +object QuickCheckBinomialHeap extends QuickCheckHeap with BinomialHeap + +class QuickCheckSuite { + def checkBogus(p: Properties): Unit = + def fail = throw new AssertionError( + s"A bogus heap should NOT satisfy all properties. Try to find the bug!") + + check(asProp(p))(identity) match + case r: Result => r.status match + case _: Failed => + () // OK: scalacheck found a counter example! + case p: PropException => + p.e match + case e: NoSuchElementException => + () // OK: the implementation throws NSEE + case _ => + fail + case _ => + fail + + /** Turns a `Properties` instance into a single `Prop` by combining all the properties */ + def asProp(properties: Properties): Prop = Prop.all(properties.properties.map(_._2).toSeq:_*) + + @Test def `Binomial heap satisfies properties. (5pts)`: Unit = + Assert.assertTrue( + check(asProp(new QuickCheckHeap with quickcheck.test.BinomialHeap))(identity).passed + ) + + @Test def `Bogus (1) binomial heap does not satisfy properties. (10pts)`: Unit = + checkBogus(new QuickCheckHeap with quickcheck.test.Bogus1BinomialHeap) + + @Test def `Bogus (2) binomial heap does not satisfy properties. (10pts)`: Unit = + checkBogus(new QuickCheckHeap with quickcheck.test.Bogus2BinomialHeap) + + @Test def `Bogus (3) binomial heap does not satisfy properties. (10pts)`: Unit = + checkBogus(new QuickCheckHeap with quickcheck.test.Bogus3BinomialHeap) + + @Test def `Bogus (4) binomial heap does not satisfy properties. (10pts)`: Unit = + checkBogus(new QuickCheckHeap with quickcheck.test.Bogus4BinomialHeap) + + @Test def `Bogus (5) binomial heap does not satisfy properties. (10pts)`: Unit = + checkBogus(new QuickCheckHeap with quickcheck.test.Bogus5BinomialHeap) + + @Rule def individualTestTimeout = new org.junit.rules.Timeout(10 * 1000) +} diff --git a/src/test/scala/quickcheck/test/Heap.scala b/src/test/scala/quickcheck/test/Heap.scala new file mode 100644 index 0000000..2f82fb3 --- /dev/null +++ b/src/test/scala/quickcheck/test/Heap.scala @@ -0,0 +1,96 @@ +package quickcheck.test + +// Figure 3, page 7 +trait BinomialHeap extends quickcheck.Heap { + + type Rank = Int + case class Node(x: A, r: Rank, c: List[Node]) + override type H = List[Node] + + protected def root(t: Node) = t.x + protected def rank(t: Node) = t.r + protected def link(t1: Node, t2: Node): Node = // t1.r == t2.r + if ord.lteq(t1.x, t2.x) then + Node(t1.x, t1.r + 1, t2 :: t1.c) + else + Node(t2.x, t2.r + 1, t1 :: t2.c) + protected def ins(t: Node, ts: H): H = ts match + case Nil => List(t) + case tp :: ts => // t.r<=tp.r + if t.r < tp.r then + t :: tp :: ts + else + ins(link(t, tp), ts) + + override def empty = Nil + override def isEmpty(ts: H) = ts.isEmpty + + override def insert(x: A, ts: H) = ins(Node(x, 0, Nil), ts) + override def meld(ts1: H, ts2: H) = (ts1, ts2) match + case (Nil, ts) => ts + case (ts, Nil) => ts + case (t1 :: ts1, t2 :: ts2) => + if t1.r < t2.r then + t1 :: meld(ts1, t2 :: ts2) + else if t2.r < t1.r then + t2 :: meld(t1 :: ts1, ts2) + else + ins(link(t1, t2), meld(ts1, ts2)) + + override def findMin(ts: H) = ts match + case Nil => throw new NoSuchElementException("min of empty heap") + case t :: Nil => root(t) + case t :: ts => + val x = findMin(ts) + if ord.lteq(root(t), x) then + root(t) + else + x + override def deleteMin(ts: H) = ts match + case Nil => throw new NoSuchElementException("delete min of empty heap") + case t :: ts => + def getMin(t: Node, ts: H): (Node, H) = ts match + case Nil => (t, Nil) + case tp :: tsp => + val (tq, tsq) = getMin(tp, tsp) + if ord.lteq(root(t), root(tq)) then + (t, ts) + else + (tq, t :: tsq) + val (Node(_, _, c), tsq) = getMin(t, ts) + meld(c.reverse, tsq) +} + +trait Bogus1BinomialHeap extends BinomialHeap { + override def findMin(ts: H) = ts match + case Nil => throw new NoSuchElementException("min of empty heap") + case t :: ts => root(t) +} + +trait Bogus2BinomialHeap extends BinomialHeap { + override protected def link(t1: Node, t2: Node): Node = // t1.r == t2.r + if !ord.lteq(t1.x, t2.x) then + Node(t1.x, t1.r + 1, t2 :: t1.c) + else + Node(t2.x, t2.r + 1, t1 :: t2.c) +} + +trait Bogus3BinomialHeap extends BinomialHeap { + override protected def link(t1: Node, t2: Node): Node = // t1.r == t2.r + if ord.lteq(t1.x, t2.x) then + Node(t1.x, t1.r + 1, t1 :: t1.c) + else + Node(t2.x, t2.r + 1, t2 :: t2.c) +} + +trait Bogus4BinomialHeap extends BinomialHeap { + override def deleteMin(ts: H) = ts match + case Nil => throw new NoSuchElementException("delete min of empty heap") + case t :: ts => meld(t.c.reverse, ts) +} + +trait Bogus5BinomialHeap extends BinomialHeap { + override def meld(ts1: H, ts2: H) = ts1 match + case Nil => ts2 + case t1 :: ts1 => List(Node(t1.x, t1.r, ts1++ts2)) +} diff --git a/student.sbt b/student.sbt new file mode 100644 index 0000000..855fa0c --- /dev/null +++ b/student.sbt @@ -0,0 +1,9 @@ +// Used for base64 encoding +libraryDependencies += "commons-codec" % "commons-codec" % "1.10" + +// Used for Coursera submussion +// libraryDependencies += "org.scalaj" %% "scalaj-http" % "2.4.2" +// libraryDependencies += "com.typesafe.play" %% "play-json" % "2.7.4" + +// Student tasks (i.e. packageSubmission) +enablePlugins(StudentTasks)