# HG changeset patch # User Christian Urban # Date 1665185451 -3600 # Node ID 6e93040e33787a85a1f47c0a3d37ca2d5ff61d6c # Parent b51467741af2aceda00ae00dc6791e8af7e52c3f updated diff -r b51467741af2 -r 6e93040e3378 cws/build --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/cws/build Sat Oct 08 00:30:51 2022 +0100 @@ -0,0 +1,15 @@ +#!/bin/bash +set -euo pipefail + +fls=${1:-"core_cw01.tex core_cw02.tex core_cw03.tex main_cw01.tex main_cw02.tex main_cw03.tex main_cw04.tex main_cw05.tex"} + +for f in $fls; do + echo -e "making $f" + xelatex $f + mv "${f%.tex}.pdf" tmp.pdf + gs -o "${f%.tex}.pdf" -dNoOutputFonts -sDEVICE=pdfwrite tmp.pdf +done + + +# prevent PDF from being copied +### gs -o "${f%.tex}.pdf" -dNoOutputFonts -sDEVICE=pdfwrite tmp.pdf diff -r b51467741af2 -r 6e93040e3378 cws/core_cw01.pdf Binary file cws/core_cw01.pdf has changed diff -r b51467741af2 -r 6e93040e3378 cws/core_cw02.pdf Binary file cws/core_cw02.pdf has changed diff -r b51467741af2 -r 6e93040e3378 cws/core_cw03.pdf Binary file cws/core_cw03.pdf has changed diff -r b51467741af2 -r 6e93040e3378 cws/disclaimer.sty --- a/cws/disclaimer.sty Thu Aug 04 16:53:38 2022 +0200 +++ b/cws/disclaimer.sty Sat Oct 08 00:30:51 2022 +0100 @@ -96,7 +96,7 @@ It should be understood that the work you submit represents your \textbf{own} effort! You have implemented the code entirely -on your own. You have not copied from anyone else. An +on your own. You have not used any unauthorised aid (e.g.~Google). You have not copied from anyone else. An exception is the Scala code I showed during the lectures or uploaded to KEATS, which you can freely use.\bigskip } diff -r b51467741af2 -r 6e93040e3378 cws/main_cw01.pdf Binary file cws/main_cw01.pdf has changed diff -r b51467741af2 -r 6e93040e3378 cws/main_cw02.pdf Binary file cws/main_cw02.pdf has changed diff -r b51467741af2 -r 6e93040e3378 cws/main_cw03.pdf Binary file cws/main_cw03.pdf has changed diff -r b51467741af2 -r 6e93040e3378 cws/main_cw04.pdf Binary file cws/main_cw04.pdf has changed diff -r b51467741af2 -r 6e93040e3378 cws/main_cw05.pdf Binary file cws/main_cw05.pdf has changed diff -r b51467741af2 -r 6e93040e3378 cws/resit.tex --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/cws/resit.tex Sat Oct 08 00:30:51 2022 +0100 @@ -0,0 +1,273 @@ + +% !TEX program = xelatex +\documentclass{article} +\usepackage{../styles/style} +\usepackage{disclaimer} +\usepackage{../styles/langs} +\usepackage{graphicx} + +\begin{document} + + +%% should ask to lower case the words. + +\section*{Resit: Evil Wordle Game (Scala, 6 Marks)} + + +\noindent +You are asked to implement a Scala program for making the popular Wordle game as difficult +as possible. The deadline for your submission is on 4th August at 16:00. There will be no +automated tests for the resit, but there are plenty of testcases in the template and the +task description. \bigskip + +\IMPORTANTNONE{} + +\noindent +Also note that the running time of each part will be restricted to a +maximum of 30 seconds on my laptop. + +\DISCLAIMER{} + +\subsection*{Hints} + +\noindent +Useful data functions: \texttt{Source.fromURL}, +\texttt{Source.fromFile} for obtaining a webpage and reading a file, +\texttt{.getOrElse(..,..)} allows to query a Map, but also gives a +default value if the Map is not defined, a Map can be `updated' by +using \texttt{+}, \texttt{.contains} and \texttt{.filter} can test whether +an element is included in a list, and respectively filter out elements in a list, +\texttt{.sortBy(\_.\_2)} sorts a list of pairs according to the second +elements in the pairs---the sorting is done from smallest to highest, +\texttt{.groupBy} orders lists according to same elements +. + + +\newpage + + +\subsection*{Resit (6 Marks, file wordle.scala)} + +You probably know the game of Wordle\footnote{\url{https://en.wikipedia.org/wiki/Wordle}} +where you are supposed to guess a five-letter word. The feedback for guesses can help +with the next guess (green letters are correct, orange letters are present, but in the +wrong place). For example: + +\begin{center} +\includegraphics[scale=0.2]{../pics/w.jpeg} +\end{center} + +\noindent +The idea of the program to be implemented here is to make the Wordle game as evil as possible +by finding words that are the most difficult to guess. A word list of five-letter words is available +from + +\begin{center} +\begin{tabular}{ll} + \url{https://nms.kcl.ac.uk/christian.urban/wordle.txt} & (78 KByte)\\ +\end{tabular} +\end{center} + +\noindent +In your program you need to download this list and implement some +functions that in the end select the most difficult words (given an +input from the user). If bandwidth is an issue for you, download the +file locally, but in the submitted version use \texttt{Source.fromURL} +instead of \texttt{Source.fromFile}. + +\subsection*{Tasks} + +\begin{itemize} +\item[(1)] Implement the function \pcode{get_wordle_list} which takes an + URL-string as argument and requests the corresponding file. The function should + return the word list appropriately broken up into lines. + The result should be a list of strings (the lines in the file). In case + the url does not produce a file, return the empty list.\\ + \mbox{}\hfill [0.25 Marks] + +\item[(2)] Implement a polymorphic function \pcode{removeN}, which removes $n$ occurences of an + element from a list (if this element is less than $n$ times pressent, then remove all occurences). + For example + +\begin{lstlisting}[numbers=none] +removeN(List(1,2,3,2,1), 3, 2) => List(1, 2, 2, 1) +removeN(List(1,2,3,2,1), 2, 1) => List(1, 3, 2, 1) +removeN(List(1,2,3,2,1), 2, 2) => List(1, 3, 1) +removeN(List(1,2,3,2,1), 1, 1) => List(2, 3, 2, 1) +removeN(List(1,2,3,2,1), 1, 3) => List(2, 3, 2) +removeN(List(1,2,3,2,1), 0, 2) => List(1, 2, 3, 2, 1) +\end{lstlisting} + +Make sure you only remove at most $n$ occurrences of the element from the list. +This function should work for lists of intergers but also lists of chars, strings etc.\\ + \mbox{}\hfill [0.25 Marks] + +\item[(3)] Implement a function \pcode{score} that calculates the + feedback for a word against a secret word using the rules of the + Wordle game. The output of \pcode{score} should be a list of 5 + elements of type \pcode{Tip} representing three outcomes: a letter + in the correct position, a letter that is present, but not in the + correct position and a letter that is absent. For example given the + secret word "chess" the score for the word "caves" is + +\begin{lstlisting}[numbers=none] +List(Correct, Absent, Absent, Present, Correct) +\end{lstlisting} + + You have to be careful with multiple occurrences of letters. For example + the secret "chess" with the guess "swiss" should produce + +\begin{lstlisting}[numbers=none] +List(Absent, Absent, Absent, Correct, Correct) +\end{lstlisting} + +even though the first 's' in "swiss" is present in the secret word, the 's' are already +`used up' by the two letters that are correct. To implement this you need to +implement first a function \pcode{pool} which calculates all the letters in +a secret that are not correct in a word. For example + +\begin{lstlisting}[numbers=none] + pool("chess", "caves") => List(h, e, s) + pool("chess", "swiss") => List(c, h, e) +\end{lstlisting} + + Now the helper function \pcode{aux} can analyse the arguments secret and word recursively letter-by-letter and + decide: if the letters are the same, then return \pcode{Correct} for the corresponding position. + If they are not the same, but the letter is in the pool, then return \pcode{Present} and also remove + this letter from the pool in the next recursive call of \pcode{aux}. Otherwise return \pcode{Absent} for the + corresponding position. The function \pcode{score} is a wrapper for the function \pcode{aux} + calling \pcode{aux} with the appropriate arguments (recall what is calculated with \pcode{pool}).\mbox{}\hfill [1.5 Marks] + +\item[(4)] Implement a function \pcode{eval} that gives an integer value to each of the + \pcode{Tip}s such that + + \begin{center} + \begin{tabular}{lcl} + \textit{eval (Correct)} & $\dn$ & $10$\\ + \textit{eval (Present)} & $\dn$ & $1$\\ + \textit{eval (Absent)} & $\dn$ & $0$ + \end{tabular} + \end{center} + + The function \pcode{iscore} then takes an output of \pcode{score} and sums + up all corresponding values. For example for + +\begin{lstlisting}[numbers=none] + iscore("chess", "caves") => 21 + iscore("chess", "swiss") => 20 +\end{lstlisting} + \mbox{}\hfill [0.5 Marks] + +\item[(5)] The function \pcode{evil} takes a list of secrets (the list from Task 1) + and a word as arguments, and calculates the list of words with the lowest + score (remember we want to make the Wordle game as difficult as possible---therefore + when the user gives us a word, we want to find the secrets that produce the lowest + score). For this implement a helper function \pcode{lowest} that goes through + the secrets one-by-one and calculates the score. The argument \pcode{current} is + the score of the ``currently'' found secrets. When the function \pcode{lowest} + is called for the first time then this will be set to the maximum integer value + \pcode{Int.MaxValue}. The accumulator will be first empty. If a secret is found + with the same score as \pcode{current} then this word is added to the accumulator. + If the secret has a lower score, then the accumulator will be discarded and this + secret will be the new accumulator. If the secret has a higher score, then it can be + ignored. For example \pcode{evil} (the wrapper for \pcode{lowest}) generates + +\begin{lstlisting}[numbers=none] +evil(secrets, "stent").length => 1907 +evil(secrets, "hexes").length => 2966 +evil(secrets, "horse").length => 1203 +evil(secrets, "hoise").length => 971 +evil(secrets, "house").length => 1228 +\end{lstlisting} + +where \pcode{secrets} is the list generated under Task 1. +In all cases above the iscore of the resulting secrets is 0, but this does not need to be the case +in general.\\ + \mbox{}\hfill [1.5 Marks] + +\item[(6)] The secrets generated in Task 5 are the ones with the lowest score + with respect to the word, or the secrets that are furthest ``away'' from the + given word. This is already quite evil for a secret word---remember we can choose + a secret \emph{after} a user has given a first word. Now we want to make it + even more evil by choosing words that have the most obscure letters. For this we + calculate the frequency of how many times certain letters occur in our secrets + list (see Task 1). The \emph{frequency} of the letter $c$, say, is given by the formula + + \begin{center} + $\textit{freq(c)} \dn 1 - \frac{\textit{number of occurrences of c}}{\textit{number of all letters}}$ + \end{center} + + That means that letters that occur fewer times in our secrets have a higher frequency. + For example the letter 'y' has the frequency 0.9680234350909651 while the much more + often occurring letter 'e' has only 0.897286463151403 (all calculations should be done + with Doubles). + + The function \pcode{frequencies} should calculate the frequencies for all lower-case letters + by generating a Map from letters (\pcode{Char}) to Doubles (frequencies).\\ + \mbox{}\hfill [1 Mark] + +\item[(7)] In this task we want to use the output of \pcode{evil}, rank each string in the + generated set and then filter out the strings that are ranked highest (the ones with the most obscure letters). + This list of strings often contains only a single word, but in general there might be more (see below). + First implement a function \pcode{rank} that takes a frequency map (from 6) and a string as arguments and + generates a rank by summing up all frequencies of the letters in the string. For example + +\begin{lstlisting}[numbers=none] +rank(frequencies(secrets), "adobe") => 4.673604687018193 +rank(frequencies(secrets), "gaffe") => 4.745205057045945 +rank(frequencies(secrets), "fuzzy") => 4.898735738513722 +\end{lstlisting} + + Finally, implement a function \pcode{ranked_evil} that selects from the output of \pcode{evil} + the string(s) which are highest ranked in evilness. + + +\begin{lstlisting}[numbers=none] +ranked_evil(secrets, "abbey") => List(whizz) +ranked_evil(secrets, "afear") => List(buzzy) +ranked_evil(secrets, "zincy") => List(jugum) +ranked_evil(secrets, "zippy") => List(chuff) +\end{lstlisting} + +This means if the user types in "abbey" then the most evil word to choose as secret is "whizzy" (according to +our calculations). This word has a zero \pcode{iscore} and the most obscure letters. + +% +%\color{red} +%\section*{Correction with \texttt{ranked\_evil}} +% +%The testcases above are actually not the maximum, but the minimum! I will make sure +%that the task will count as solved when either the minimum (as above) or the maximum (as intended) +%is used. The correct solutions for the above testcases using the maximum are: +%\color{black} +% +%\begin{lstlisting}[numbers=none] +%ranked_evil(secrets, "beats") => List(fuzzy) +%ranked_evil(secrets, "vitae") => List(fuzzy) +%ranked_evil(secrets, "bento") => List(fuzzy) +%ranked_evil(secrets, "belts") => List(fuzzy) +%\end{lstlisting} +% +%\noindent \textcolor{red}{Some further testcases for the maximum are:} +% +%\begin{lstlisting}[numbers=none] +%ranked_evil(secrets, "abbey") => List(whizz) +%ranked_evil(secrets, "afear") => List(buzzy) +%ranked_evil(secrets, "zincy") => List(jugum) +%ranked_evil(secrets, "zippy") => List(chuff) +%\end{lstlisting} +% +% + +\mbox{}\hfill [1 Mark] +\end{itemize} + +\end{document} + +%%% Local Variables: +%%% mode: latex +%%% TeX-master: t +%%% End: + + + diff -r b51467741af2 -r 6e93040e3378 cws/upload --- a/cws/upload Thu Aug 04 16:53:38 2022 +0200 +++ b/cws/upload Sat Oct 08 00:30:51 2022 +0100 @@ -13,3 +13,5 @@ #hg commit -m "updated jars" +# prevent PDF from being copied +###gs -o output.pdf -dNoOutputFonts -sDEVICE=pdfwrite main_cw01.pdf diff -r b51467741af2 -r 6e93040e3378 main_solution2-old/danube.jar Binary file main_solution2-old/danube.jar has changed diff -r b51467741af2 -r 6e93040e3378 main_solution2-old/danube.scala --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/main_solution2-old/danube.scala Sat Oct 08 00:30:51 2022 +0100 @@ -0,0 +1,161 @@ +// Core Part about Movie Recommendations +// at Danube.co.uk +//======================================== + + +object M2 { // for purposes of generating a jar + +import io.Source +import scala.util._ + + +// (1) Implement the function get_csv_url which takes an url-string +// as argument and requests the corresponding file. The two urls +// of interest are ratings_url and movies_url, which correspond +// to CSV-files. +// The function should return the CSV file appropriately broken +// up into lines, and the first line should be dropped (that is without +// the header of the CSV file). The result is a list of strings (lines +// in the file). + +def get_csv_url(url: String) : List[String] = { + val csv = Source.fromURL(url)("ISO-8859-1") + csv.mkString.split("\n").toList.drop(1) +} + +val ratings_url = """https://nms.kcl.ac.uk/christian.urban/ratings.csv""" +val movies_url = """https://nms.kcl.ac.uk/christian.urban/movies.csv""" + +// test cases + +//val ratings = get_csv_url(ratings_url) +//val movies = get_csv_url(movies_url) + +//ratings.length // 87313 +//movies.length // 9742 + +// (2) Implement two functions that process the CSV files. The ratings +// function filters out all ratings below 4 and returns a list of +// (userID, movieID) pairs. The movies function just returns a list +// of (movieId, title) pairs. + + +def process_ratings(lines: List[String]) : List[(String, String)] = { + for (cols <- lines.map(_.split(",").toList); + if (cols(2).toInt >= 4)) yield (cols(0), cols(1)) +} + +def process_movies(lines: List[String]) : List[(String, String)] = { + for (cols <- lines.map(_.split(",").toList)) yield (cols(0), cols(1)) +} + +// test cases + +//val good_ratings = process_ratings(ratings) +//val movie_names = process_movies(movies) + +//good_ratings.length //48580 +//movie_names.length // 9742 + + +// (3) Implement a grouping function that calulates a map +// containing the userIds and all the corresponding recommendations +// (list of movieIds). This should be implemented in a tail +// recursive fashion, using a map m as accumulator. This map +// is set to Map() at the beginning of the claculation. + +def groupById(ratings: List[(String, String)], + m: Map[String, List[String]]) : Map[String, List[String]] = ratings match { + case Nil => m + case (id, mov) :: rest => { + val old_ratings = m.getOrElse (id, Nil) + val new_ratings = m + (id -> (mov :: old_ratings)) + groupById(rest, new_ratings) + } +} + +// test cases +//val ratings_map = groupById(good_ratings, Map()) +//val movies_map = movie_names.toMap + +//ratings_map.get("414").get.map(movies_map.get(_)) // most prolific recommender with 1227 positive ratings +//ratings_map.get("474").get.map(movies_map.get(_)) // second-most prolific recommender with 787 positive ratings +//ratings_map.get("214").get.map(movies_map.get(_)) // least prolific recommender with only 1 positive rating + + + +//(4) Implement a function that takes a ratings map and a movie_name as argument. +// The function calculates all suggestions containing +// the movie mov in its recommendations. It returns a list of all these +// recommendations (each of them is a list and needs to have mov deleted, +// otherwise it might happen we recommend the same movie). + +def favourites(m: Map[String, List[String]], mov: String) : List[List[String]] = + (for (id <- m.keys.toList; + if m(id).contains(mov)) yield m(id).filter(_ != mov)) + + + +// test cases +// movie ID "912" -> Casablanca (1942) +// "858" -> Godfather +// "260" -> Star Wars: Episode IV - A New Hope (1977) + +//favourites(ratings_map, "912").length // => 80 + +// That means there are 80 users that recommend the movie with ID 912. +// Of these 80 users, 55 gave a good rating to movie 858 and +// 52 a good rating to movies 260, 318, 593. + + +// (5) Implement a suggestions function which takes a rating +// map and a movie_name as arguments. It calculates all the recommended +// movies sorted according to the most frequently suggested movie(s) first. +def suggestions(recs: Map[String, List[String]], + mov_name: String) : List[String] = { + val favs = favourites(recs, mov_name).flatten + val favs_counted = favs.groupBy(identity).view.mapValues(_.size).toList + val favs_sorted = favs_counted.sortBy(_._2).reverse + favs_sorted.map(_._1) +} + +// test cases + +//suggestions(ratings_map, "912") +//suggestions(ratings_map, "912").length +// => 4110 suggestions with List(858, 260, 318, 593, ...) +// being the most frequently suggested movies + +// (6) Implement recommendations functions which generates at most +// *two* of the most frequently suggested movies. It Returns the +// actual movie names, not the movieIDs. + +def recommendations(recs: Map[String, List[String]], + movs: Map[String, String], + mov_name: String) : List[String] = + suggestions(recs, mov_name).take(2).map(movs.get(_).get) + + +// testcases + +// recommendations(ratings_map, movies_map, "912") +// => List(Godfather, Star Wars: Episode IV - A NewHope (1977)) + +//recommendations(ratings_map, movies_map, "260") +// => List(Star Wars: Episode V - The Empire Strikes Back (1980), +// Star Wars: Episode VI - Return of the Jedi (1983)) + +// recommendations(ratings_map, movies_map, "2") +// => List(Lion King, Jurassic Park (1993)) + +// recommendations(ratings_map, movies_map, "0") +// => Nil + +// recommendations(ratings_map, movies_map, "1") +// => List(Shawshank Redemption, Forrest Gump (1994)) + +// recommendations(ratings_map, movies_map, "4") +// => Nil (there are three ratings for this movie in ratings.csv but they are not positive) + + +} diff -r b51467741af2 -r 6e93040e3378 main_solution2/danube.jar Binary file main_solution2/danube.jar has changed diff -r b51467741af2 -r 6e93040e3378 main_solution2/danube.scala --- a/main_solution2/danube.scala Thu Aug 04 16:53:38 2022 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,161 +0,0 @@ -// Core Part about Movie Recommendations -// at Danube.co.uk -//======================================== - - -object M2 { // for purposes of generating a jar - -import io.Source -import scala.util._ - - -// (1) Implement the function get_csv_url which takes an url-string -// as argument and requests the corresponding file. The two urls -// of interest are ratings_url and movies_url, which correspond -// to CSV-files. -// The function should return the CSV file appropriately broken -// up into lines, and the first line should be dropped (that is without -// the header of the CSV file). The result is a list of strings (lines -// in the file). - -def get_csv_url(url: String) : List[String] = { - val csv = Source.fromURL(url)("ISO-8859-1") - csv.mkString.split("\n").toList.drop(1) -} - -val ratings_url = """https://nms.kcl.ac.uk/christian.urban/ratings.csv""" -val movies_url = """https://nms.kcl.ac.uk/christian.urban/movies.csv""" - -// test cases - -//val ratings = get_csv_url(ratings_url) -//val movies = get_csv_url(movies_url) - -//ratings.length // 87313 -//movies.length // 9742 - -// (2) Implement two functions that process the CSV files. The ratings -// function filters out all ratings below 4 and returns a list of -// (userID, movieID) pairs. The movies function just returns a list -// of (movieId, title) pairs. - - -def process_ratings(lines: List[String]) : List[(String, String)] = { - for (cols <- lines.map(_.split(",").toList); - if (cols(2).toInt >= 4)) yield (cols(0), cols(1)) -} - -def process_movies(lines: List[String]) : List[(String, String)] = { - for (cols <- lines.map(_.split(",").toList)) yield (cols(0), cols(1)) -} - -// test cases - -//val good_ratings = process_ratings(ratings) -//val movie_names = process_movies(movies) - -//good_ratings.length //48580 -//movie_names.length // 9742 - - -// (3) Implement a grouping function that calulates a map -// containing the userIds and all the corresponding recommendations -// (list of movieIds). This should be implemented in a tail -// recursive fashion, using a map m as accumulator. This map -// is set to Map() at the beginning of the claculation. - -def groupById(ratings: List[(String, String)], - m: Map[String, List[String]]) : Map[String, List[String]] = ratings match { - case Nil => m - case (id, mov) :: rest => { - val old_ratings = m.getOrElse (id, Nil) - val new_ratings = m + (id -> (mov :: old_ratings)) - groupById(rest, new_ratings) - } -} - -// test cases -//val ratings_map = groupById(good_ratings, Map()) -//val movies_map = movie_names.toMap - -//ratings_map.get("414").get.map(movies_map.get(_)) // most prolific recommender with 1227 positive ratings -//ratings_map.get("474").get.map(movies_map.get(_)) // second-most prolific recommender with 787 positive ratings -//ratings_map.get("214").get.map(movies_map.get(_)) // least prolific recommender with only 1 positive rating - - - -//(4) Implement a function that takes a ratings map and a movie_name as argument. -// The function calculates all suggestions containing -// the movie mov in its recommendations. It returns a list of all these -// recommendations (each of them is a list and needs to have mov deleted, -// otherwise it might happen we recommend the same movie). - -def favourites(m: Map[String, List[String]], mov: String) : List[List[String]] = - (for (id <- m.keys.toList; - if m(id).contains(mov)) yield m(id).filter(_ != mov)) - - - -// test cases -// movie ID "912" -> Casablanca (1942) -// "858" -> Godfather -// "260" -> Star Wars: Episode IV - A New Hope (1977) - -//favourites(ratings_map, "912").length // => 80 - -// That means there are 80 users that recommend the movie with ID 912. -// Of these 80 users, 55 gave a good rating to movie 858 and -// 52 a good rating to movies 260, 318, 593. - - -// (5) Implement a suggestions function which takes a rating -// map and a movie_name as arguments. It calculates all the recommended -// movies sorted according to the most frequently suggested movie(s) first. -def suggestions(recs: Map[String, List[String]], - mov_name: String) : List[String] = { - val favs = favourites(recs, mov_name).flatten - val favs_counted = favs.groupBy(identity).view.mapValues(_.size).toList - val favs_sorted = favs_counted.sortBy(_._2).reverse - favs_sorted.map(_._1) -} - -// test cases - -//suggestions(ratings_map, "912") -//suggestions(ratings_map, "912").length -// => 4110 suggestions with List(858, 260, 318, 593, ...) -// being the most frequently suggested movies - -// (6) Implement recommendations functions which generates at most -// *two* of the most frequently suggested movies. It Returns the -// actual movie names, not the movieIDs. - -def recommendations(recs: Map[String, List[String]], - movs: Map[String, String], - mov_name: String) : List[String] = - suggestions(recs, mov_name).take(2).map(movs.get(_).get) - - -// testcases - -// recommendations(ratings_map, movies_map, "912") -// => List(Godfather, Star Wars: Episode IV - A NewHope (1977)) - -//recommendations(ratings_map, movies_map, "260") -// => List(Star Wars: Episode V - The Empire Strikes Back (1980), -// Star Wars: Episode VI - Return of the Jedi (1983)) - -// recommendations(ratings_map, movies_map, "2") -// => List(Lion King, Jurassic Park (1993)) - -// recommendations(ratings_map, movies_map, "0") -// => Nil - -// recommendations(ratings_map, movies_map, "1") -// => List(Shawshank Redemption, Forrest Gump (1994)) - -// recommendations(ratings_map, movies_map, "4") -// => Nil (there are three ratings for this movie in ratings.csv but they are not positive) - - -} diff -r b51467741af2 -r 6e93040e3378 pics/w.jpeg Binary file pics/w.jpeg has changed diff -r b51467741af2 -r 6e93040e3378 slides/slides01.tex --- a/slides/slides01.tex Thu Aug 04 16:53:38 2022 +0200 +++ b/slides/slides01.tex Sat Oct 08 00:30:51 2022 +0100 @@ -861,6 +861,14 @@ \end{frame} %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +\begin{frame}[t,fragile] + +\begin{bubble}[10.5cm] + "PEP was my favourite module so far during these 2 years. It motivated me to apply and get a summer internship offer at S\&P Global as a Scala developer. The module content was more than enough for me to start working on the projects here at the company." -- Szabolcs Daniel Nagi (PEP 2021) +\end{bubble} +\end{frame} +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% \begin{frame}[c]