From 61001512a6e7c89664d277703d5febf8aeb47862 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Timoth=C3=A9e=20Floure?= Date: Wed, 9 Oct 2019 17:10:27 +0200 Subject: [PATCH] Import patmat handout --- .vscode/settings.json | 8 + build.sbt | 12 + grading-tests.jar | Bin 0 -> 37784 bytes project/MOOCSettings.scala | 23 ++ project/StudentTasks.scala | 323 +++++++++++++++++++ project/build.properties | 1 + project/buildSettings.sbt | 8 + project/plugins.sbt | 2 + src/main/scala/patmat/Huffman.scala | 192 +++++++++++ src/main/scala/patmat/HuffmanInterface.scala | 23 ++ src/test/scala/patmat/HuffmanSuite.scala | 46 +++ student.sbt | 9 + 12 files changed, 647 insertions(+) create mode 100644 .vscode/settings.json create mode 100644 build.sbt create mode 100644 grading-tests.jar create mode 100644 project/MOOCSettings.scala create mode 100644 project/StudentTasks.scala create mode 100644 project/build.properties create mode 100644 project/buildSettings.sbt create mode 100644 project/plugins.sbt create mode 100644 src/main/scala/patmat/Huffman.scala create mode 100644 src/main/scala/patmat/HuffmanInterface.scala create mode 100644 src/test/scala/patmat/HuffmanSuite.scala create mode 100644 student.sbt 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..71c30f6 --- /dev/null +++ b/build.sbt @@ -0,0 +1,12 @@ +course := "progfun1" +assignment := "patmat" +name := course.value + "-" + assignment.value +testSuite := "patmat.HuffmanSuite" + +scalaVersion := "0.19.0-bin-20190918-dd68eb8-NIGHTLY" + +scalacOptions ++= Seq("-language:implicitConversions", "-deprecation") + +libraryDependencies += "com.novocode" % "junit-interface" % "0.11" % Test + +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 0000000000000000000000000000000000000000..d50aa197e2dc43f432d7462e1c7683ba1db499c7 GIT binary patch literal 37784 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 rX9 "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..a309025 --- /dev/null +++ b/project/buildSettings.sbt @@ -0,0 +1,8 @@ +libraryDependencies += "com.novocode" % "junit-interface" % "0.11" % Test +// 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/patmat/Huffman.scala b/src/main/scala/patmat/Huffman.scala new file mode 100644 index 0000000..056b43a --- /dev/null +++ b/src/main/scala/patmat/Huffman.scala @@ -0,0 +1,192 @@ +package patmat + +/** + * A huffman code is represented by a binary tree. + * + * Every `Leaf` node of the tree represents one character of the alphabet that the tree can encode. + * The weight of a `Leaf` is the frequency of appearance of the character. + * + * The branches of the huffman tree, the `Fork` nodes, represent a set containing all the characters + * present in the leaves below it. The weight of a `Fork` node is the sum of the weights of these + * leaves. + */ +abstract class CodeTree +case class Fork(left: CodeTree, right: CodeTree, chars: List[Char], weight: Int) extends CodeTree +case class Leaf(char: Char, weight: Int) extends CodeTree + +/** + * Assignment 4: Huffman coding + * + */ +trait Huffman extends HuffmanInterface { + + // Part 1: Basics + def weight(tree: CodeTree): Int = ??? // tree match ... + + def chars(tree: CodeTree): List[Char] = ??? // tree match ... + + def makeCodeTree(left: CodeTree, right: CodeTree) = + Fork(left, right, chars(left) ::: chars(right), weight(left) + weight(right)) + + // Part 2: Generating Huffman trees + + /** + * In this assignment, we are working with lists of characters. This function allows + * you to easily create a character list from a given string. + */ + def string2Chars(str: String): List[Char] = str.toList + + /** + * This function computes for each unique character in the list `chars` the number of + * times it occurs. For example, the invocation + * + * times(List('a', 'b', 'a')) + * + * should return the following (the order of the resulting list is not important): + * + * List(('a', 2), ('b', 1)) + * + * The type `List[(Char, Int)]` denotes a list of pairs, where each pair consists of a + * character and an integer. Pairs can be constructed easily using parentheses: + * + * val pair: (Char, Int) = ('c', 1) + * + * In order to access the two elements of a pair, you can use the accessors `_1` and `_2`: + * + * val theChar = pair._1 + * val theInt = pair._2 + * + * Another way to deconstruct a pair is using pattern matching: + * + * pair match { + * case (theChar, theInt) => + * println("character is: "+ theChar) + * println("integer is : "+ theInt) + * } + */ + def times(chars: List[Char]): List[(Char, Int)] = ??? + + /** + * Returns a list of `Leaf` nodes for a given frequency table `freqs`. + * + * The returned list should be ordered by ascending weights (i.e. the + * head of the list should have the smallest weight), where the weight + * of a leaf is the frequency of the character. + */ + def makeOrderedLeafList(freqs: List[(Char, Int)]): List[Leaf] = ??? + + /** + * Checks whether the list `trees` contains only one single code tree. + */ + def singleton(trees: List[CodeTree]): Boolean = ??? + + /** + * The parameter `trees` of this function is a list of code trees ordered + * by ascending weights. + * + * This function takes the first two elements of the list `trees` and combines + * them into a single `Fork` node. This node is then added back into the + * remaining elements of `trees` at a position such that the ordering by weights + * is preserved. + * + * If `trees` is a list of less than two elements, that list should be returned + * unchanged. + */ + def combine(trees: List[CodeTree]): List[CodeTree] = ??? + + /** + * This function will be called in the following way: + * + * until(singleton, combine)(trees) + * + * where `trees` is of type `List[CodeTree]`, `singleton` and `combine` refer to + * the two functions defined above. + * + * In such an invocation, `until` should call the two functions until the list of + * code trees contains only one single tree, and then return that singleton list. + */ + def until(done: List[CodeTree] => Boolean, merge: List[CodeTree] => List[CodeTree])(trees: List[CodeTree]): List[CodeTree] = ??? + + /** + * This function creates a code tree which is optimal to encode the text `chars`. + * + * The parameter `chars` is an arbitrary text. This function extracts the character + * frequencies from that text and creates a code tree based on them. + */ + def createCodeTree(chars: List[Char]): CodeTree = ??? + + + // Part 3: Decoding + + type Bit = Int + + /** + * This function decodes the bit sequence `bits` using the code tree `tree` and returns + * the resulting list of characters. + */ + def decode(tree: CodeTree, bits: List[Bit]): List[Char] = ??? + + /** + * A Huffman coding tree for the French language. + * Generated from the data given at + * http://fr.wikipedia.org/wiki/Fr%C3%A9quence_d%27apparition_des_lettres_en_fran%C3%A7ais + */ + val frenchCode: CodeTree = Fork(Fork(Fork(Leaf('s',121895),Fork(Leaf('d',56269),Fork(Fork(Fork(Leaf('x',5928),Leaf('j',8351),List('x','j'),14279),Leaf('f',16351),List('x','j','f'),30630),Fork(Fork(Fork(Fork(Leaf('z',2093),Fork(Leaf('k',745),Leaf('w',1747),List('k','w'),2492),List('z','k','w'),4585),Leaf('y',4725),List('z','k','w','y'),9310),Leaf('h',11298),List('z','k','w','y','h'),20608),Leaf('q',20889),List('z','k','w','y','h','q'),41497),List('x','j','f','z','k','w','y','h','q'),72127),List('d','x','j','f','z','k','w','y','h','q'),128396),List('s','d','x','j','f','z','k','w','y','h','q'),250291),Fork(Fork(Leaf('o',82762),Leaf('l',83668),List('o','l'),166430),Fork(Fork(Leaf('m',45521),Leaf('p',46335),List('m','p'),91856),Leaf('u',96785),List('m','p','u'),188641),List('o','l','m','p','u'),355071),List('s','d','x','j','f','z','k','w','y','h','q','o','l','m','p','u'),605362),Fork(Fork(Fork(Leaf('r',100500),Fork(Leaf('c',50003),Fork(Leaf('v',24975),Fork(Leaf('g',13288),Leaf('b',13822),List('g','b'),27110),List('v','g','b'),52085),List('c','v','g','b'),102088),List('r','c','v','g','b'),202588),Fork(Leaf('n',108812),Leaf('t',111103),List('n','t'),219915),List('r','c','v','g','b','n','t'),422503),Fork(Leaf('e',225947),Fork(Leaf('i',115465),Leaf('a',117110),List('i','a'),232575),List('e','i','a'),458522),List('r','c','v','g','b','n','t','e','i','a'),881025),List('s','d','x','j','f','z','k','w','y','h','q','o','l','m','p','u','r','c','v','g','b','n','t','e','i','a'),1486387) + + /** + * What does the secret message say? Can you decode it? + * For the decoding use the `frenchCode' Huffman tree defined above. + */ + val secret: List[Bit] = List(0,0,1,1,1,0,1,0,1,1,1,0,0,1,1,0,1,0,0,1,1,0,1,0,1,1,0,0,1,1,1,1,1,0,1,0,1,1,0,0,0,0,1,0,1,1,1,0,0,1,0,0,1,0,0,0,1,0,0,0,1,0,1) + + /** + * Write a function that returns the decoded secret + */ + def decodedSecret: List[Char] = ??? + + + // Part 4a: Encoding using Huffman tree + + /** + * This function encodes `text` using the code tree `tree` + * into a sequence of bits. + */ + def encode(tree: CodeTree)(text: List[Char]): List[Bit] = ??? + + // Part 4b: Encoding using code table + + type CodeTable = List[(Char, List[Bit])] + + /** + * This function returns the bit sequence that represents the character `char` in + * the code table `table`. + */ + def codeBits(table: CodeTable)(char: Char): List[Bit] = ??? + + /** + * Given a code tree, create a code table which contains, for every character in the + * code tree, the sequence of bits representing that character. + * + * Hint: think of a recursive solution: every sub-tree of the code tree `tree` is itself + * a valid code tree that can be represented as a code table. Using the code tables of the + * sub-trees, think of how to build the code table for the entire tree. + */ + def convert(tree: CodeTree): CodeTable = ??? + + /** + * This function takes two code tables and merges them into one. Depending on how you + * use it in the `convert` method above, this merge method might also do some transformations + * on the two parameter code tables. + */ + def mergeCodeTables(a: CodeTable, b: CodeTable): CodeTable = ??? + + /** + * This function encodes `text` according to the code tree `tree`. + * + * To speed up the encoding process, it first converts the code tree to a code table + * and then uses it to perform the actual encoding. + */ + def quickEncode(tree: CodeTree)(text: List[Char]): List[Bit] = ??? +} + +object Huffman extends Huffman diff --git a/src/main/scala/patmat/HuffmanInterface.scala b/src/main/scala/patmat/HuffmanInterface.scala new file mode 100644 index 0000000..7dd3a63 --- /dev/null +++ b/src/main/scala/patmat/HuffmanInterface.scala @@ -0,0 +1,23 @@ +package patmat + +/** + * The interface used by the grading infrastructure. Do not change signatures + * or your submission will fail with a NoSuchMethodError. + */ +trait HuffmanInterface { + def weight(tree: CodeTree): Int + def chars(tree: CodeTree): List[Char] + def times(chars: List[Char]): List[(Char, Int)] + def makeOrderedLeafList(freqs: List[(Char, Int)]): List[Leaf] + def singleton(trees: List[CodeTree]): Boolean + def combine(trees: List[CodeTree]): List[CodeTree] + def until(done: List[CodeTree] => Boolean, merge: List[CodeTree] => List[CodeTree])(trees: List[CodeTree]): List[CodeTree] + def createCodeTree(chars: List[Char]): CodeTree + def decode(tree: CodeTree, bits: List[Int]): List[Char] + def decodedSecret: List[Char] + def encode(tree: CodeTree)(text: List[Char]): List[Int] + def convert(tree: CodeTree): List[(Char, List[Int])] + def quickEncode(tree: CodeTree)(text: List[Char]): List[Int] + def frenchCode: CodeTree + def secret: List[Int] +} diff --git a/src/test/scala/patmat/HuffmanSuite.scala b/src/test/scala/patmat/HuffmanSuite.scala new file mode 100644 index 0000000..4fd7655 --- /dev/null +++ b/src/test/scala/patmat/HuffmanSuite.scala @@ -0,0 +1,46 @@ +package patmat + +import org.junit._ +import org.junit.Assert.assertEquals + +class HuffmanSuite { + import Huffman._ + + trait TestTrees { + val t1 = Fork(Leaf('a',2), Leaf('b',3), List('a','b'), 5) + val t2 = Fork(Fork(Leaf('a',2), Leaf('b',3), List('a','b'), 5), Leaf('d',4), List('a','b','d'), 9) + } + + + @Test def `weight of a larger tree (10pts)`: Unit = + new TestTrees { + assertEquals(5, weight(t1)) + } + + + @Test def `chars of a larger tree (10pts)`: Unit = + new TestTrees { + assertEquals(List('a','b','d'), chars(t2)) + } + + @Test def `string2chars hello world`: Unit = + assertEquals(List('h', 'e', 'l', 'l', 'o', ',', ' ', 'w', 'o', 'r', 'l', 'd'), string2Chars("hello, world")) + + + @Test def `make ordered leaf list for some frequency table (15pts)`: Unit = + assertEquals(List(Leaf('e',1), Leaf('t',2), Leaf('x',3)), makeOrderedLeafList(List(('t', 2), ('e', 1), ('x', 3)))) + + + @Test def `combine of some leaf list (15pts)`: Unit = + val leaflist = List(Leaf('e', 1), Leaf('t', 2), Leaf('x', 4)) + assertEquals(List(Fork(Leaf('e',1),Leaf('t',2),List('e', 't'),3), Leaf('x',4)), combine(leaflist)) + + + @Test def `decode and encode a very short text should be identity (10pts)`: Unit = + new TestTrees { + assertEquals("ab".toList, decode(t1, encode(t1)("ab".toList))) + } + + + @Rule def individualTestTimeout = new org.junit.rules.Timeout(10 * 1000) +} 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)