CS 463 - Homework 4, Comparison Code (Haskell)
deadline:
Thursday February 22nd 2024, 5pm
Table of Contents
1 Notes
1.1 Installation
1.1.1 Getting GHC (the compiler+interpreter)
You need to install Haskell on your machine in order to
run your code. You'll have the ghci
(and ghc
) executables, plenty
of libraries available, and more. On Windows, they may suggest
installing the Chocolatey
package manager to streamline the
process, but all platforms are now recommended to use ghcup
as the
install approach.
There is a version 8.2.2 of ghci
on zeus; it should be fine on this
assignment but will be problematic once we get to concurrent code, so
ultimately I recommend getting it installed locally.
Installing GHC
- The GHC installation page has been updated and now suggests that
all platforms use GHCUP as the installation path:
- https://www.haskell.org/ghcup/
- the version offered is current enough for our class; last I checked that is 9.2, but anything 8.x or greater should be fine.
1.1.2 Getting libraries
Then, you'll want a couple of libraries to be available for our
testing harness. It may be worth it to just install a package manager
called cabal
, with these commands:
Installing packages:
- install the
cabal-install
package, and use its commands.- should be available as part of the Haskell Platform and the GHCup
installation paths
- you can also download the package here (Downloads section), and follow the instructions on the same page under the Readme section:
- each time you use it, run commands like this; for example, to
install
HUnit
, a unit tester framework:cabal update cabal list hunit
- note the name you want, e.g.
HUnit
(and not the many other variants/additions for now)cabal install HUnit --lib
- it should automatically download and install dependencies.
- be sure to include the
--lib
tag to avoid a warning about seeing no executables in the package (lets cabal know this is fine as you'll only use it as library code)
- should be available as part of the Haskell Platform and the GHCup
installation paths
Alternative: rather than make you go all the way through
installing another package manager (e.g. cabal-install
or stack
),
you can just put the source of HUnit
and CallStack
adjacent to
your code and not mess with any of that.
- Some Extra Support Files - download and unzip this from our class
directory, and put the two folders (Data/, Test/) adjacent to
your source code and our tester. You only need this if you don't
have
cabal-install
installed. - Hopefully this includes all the library code you'd need/want through our semester, but that's also a bit of a guess of what modules you'll want to try out on the last Haskell homework.
1.2 Tester
- Get the tester here: Tester4H.hs
- You can also start with this template file to ensure all
definitions are present (though effectively empty). Just remember
to change the name to
Homework4.hs
. Homework4_TEMPLATE.hs
Run it this way all at once from the command line:
ghci -e "main" Tester4H.hs
Or, load it up, and run (a) all tests on all functions, (b) all tests on one function, or (c) one test on one function (by its index in the tester file)
demo$ ghci Tester4H.hs Loaded package environment from /Users/mudd/.ghc/aarch64-darwin-9.2.5/environments/default GHCi, version 9.2.5: https://www.haskell.org/ghc/ :? for help [1 of 6] Compiling Homework4 ( Homework4.hs, interpreted ) [2 of 6] Compiling Test.HUnit.Lang ( Test/HUnit/Lang.hs, interpreted ) [3 of 6] Compiling Test.HUnit.Base ( Test/HUnit/Base.hs, interpreted ) [4 of 6] Compiling Test.HUnit.Text ( Test/HUnit/Text.hs, interpreted ) [5 of 6] Compiling Test.HUnit ( Test/HUnit.hs, interpreted ) [6 of 6] Compiling Main ( Tester4H.hs, interpreted ) Ok, six modules loaded. -- (a) ghci> main Cases: 105 Tried: 105 Errors: 0 Failures: 0 Counts {cases = 105, tried = 105, errors = 0, failures = 0} -- (b) ghci> testFunc test_coprime Cases: 11 Tried: 11 Errors: 0 Failures: 0 Counts {cases = 11, tried = 11, errors = 0, failures = 0} -- (c) ghci> testOne test_coprime 0 Cases: 1 Tried: 1 Errors: 0 Failures: 0 Counts {cases = 1, tried = 1, errors = 0, failures = 0} ghci> :quit Leaving GHCi. demo$
1.3 Implementation Notes
- Haskell is a whitespace language, so be sure to use a text editor
that knows what Haskell code is. You might use VSCode, or emacs and
add the Haskell Mode, but you can choose the working environment
you want. Keep in mind that we'll be doing at least a couple
homeworks/projects in Haskell, so it's worth your time getting it
all set up, more so than other languages this semester.
- note - you should not need a lot of excessive indentations! Ther are many ways you can indent your code, as long as the compiler still knows what you mean. Especially early on, if you have questions about how to avoid lots of indentations, we're happy to help on either private piazza posts or "vanilla" code (things that don't solve the assignment but have the general shape you're working on) in a public post.
- You'll be implementing the same functions as before, in a file
named
Homework4.hs
. It must have this specific name for testing purposes, much like a Java class name. Put your own name and netID in a comment at the top. Also, you must include these lines at the top, to make it a proper Haskell module, and to hide the Prelude-defined version ofzipWith
and ofany
. Of course use your own name/netID.-- Name: Georgie Mason -- NetID: gmason76 module Homework4 where import Prelude hiding (zipWith,any)
- Again, a major theme should be writing helper functions that solve parts of the task. We can also think of the required function as being the driver function that just calls another function that needs more arguments. Turning a loop-style algorithm into recursion can often be thought of as making all variables that are used in the loop become arguments for the recursive function. If you took my advice and wrote a direct and simple algorithm, you already have somthing that can almost entirely port over to Haskell with no extra mental overhead, and you'll only need to deal with recursion vs iteration, and then syntax and typing issues.
- Be sure to comment your code sufficiently. That includes both a top-comment per function as well as describing the algorithm throughout.
- To turn in your work, submit the single file under the appropriate submission. Since everyone's file is named the same, keep the grader happy by actually putting your name in the file as directed.
- remember, if your code clearly does not cover all the expected input range, even if you pass all test cases we might deduct points. Directly hardcoding the test cases is the extreme case of this, but as an example, not handling any inputs larger than the tests I've chosen would be another example of a problem that would lose points.
1.4 What Can I Use?
The point of these assignments is to experience implementing an algorithm in various languages.
- you must not import anything unless expressly allowed in the homework specs
- if you find that there's a built in function that does exactly the
same thing (perhaps by a different name), you can't use it. For
instance, Haskell has a
reverse
function that you can't use to implementreversed
. If it feels like you're dodging the assignment's task, that's where you won't get points. When in doubt you can ask in a private piazza post, but of course leave enough time for us to respond. More to the point, use these assignments as chances to explore the languages themselves, don't view them as roadblocks to reaching the end of the semester. These homeworks will be good practice for the quizzes and tests, too, so get the understanding in while it's a relaxed untimed environment.- reminder: if your code doesn't implement the function requested, but happens to pass test cases, we may deduct points accordingly. This tends to happen when a function expects a boolean answer and the function just always returns either True or False, or when it's a numeric output but your function does not consider all the expected inputs, e.g. stopping after the inputs used in test cases and giving a hardcoded answer from some value upwards.
2 Functions
2.1 primeFactors
primeFactors :: Int -> [Int]
given a positive integer, return a list of its prime factors, in
increasing order. Include the correct number of occurrences,
e.g. primeFactors 24
is [2,2,2,3]
.
*Homework4> primeFactors 5 [5] *Homework4> primeFactors 50 [2,5,5] *Homework4> primeFactors 66 [2,3,11] *Homework4> primeFactors 463 [463] *Homework4> primeFactors 512 [2,2,2,2,2,2,2,2,2]
2.2 coprime
coprime :: Int -> Int -> Bool
Given two positive integers, return whether they are co-prime or not. Coprime numbers don't share any divisors other than 1.
*Homework4> coprime 7 11 True *Homework4> coprime 10 21 True *Homework4> coprime 50 100 False *Homework4> coprime 367 463 True *Homework4> coprime 330 463 True *Homework4> coprime 222 262 False
2.3 trib
trib :: Int -> Int
Given a non-negative integer n, calculate the nth tribonnaci number
(where values are the sums of the previous three items in the
sequence, or 1 when there aren't enough values ahead of it). For our
purposes, the sequence begins as 1,1,1,...
(with indexes 0, 1, 2,
…).
- You need to implement a fast version (O(n)); if your code takes some exponential amount of time to complete, you will not get credit for this function.
*Homework4> trib 0 1 *Homework4> trib 1 1 *Homework4> trib 2 1 *Homework4> trib 3 3 *Homework4> trib 4 5 *Homework4> trib 5 9 *Homework4> trib 6 17 *Homework4> map trib [0..20] [1,1,1,3,5,9,17,31,57,105,193,355,653,1201,2209,4063,7473,13745,25281,46499,85525]
2.4 maxTwo
maxTwo :: [Int] -> [Int]
Given a list of integers xs
, find the two largest values and return
them in a list (largest first). Largest duplicates should be
preserved (included) in the output. Do not modify the original list.
- Hint: doing one traversal and no modification is the suggested approach.
*Homework4> maxTwo [1,2,3,4,5,1,2] [5,4] *Homework4> maxTwo [3,6,1,2,6,4] [6,6] *Homework4> maxTwo [-3,-4,-5,-2,-10] [-2,-3]
2.5 reversed
reversed :: [a] -> [a]
Given a list of ay type of values, create the list whose values are in the opposite order. Do not modify the original list.
*Homework4> reversed [1,2,3,4,5] [5,4,3,2,1] *Homework4> reversed [4] [4] *Homework4> reversed [True, False, False] [False,False,True]
2.6 clockwise
clockwise :: [[a]] -> [[a]]
Given a list of lists, assume it is rectangular (all rows have same length), create and return the list of lists that contains the same values, but rotated clockwise.
*Homework4> clockwise [[1,2,3],[4,5,6]] [[4,1],[5,2],[6,3]] *Homework4> clockwise [[1,2,3,4,5]] [[1],[2],[3],[4],[5]] *Homework4> clockwise [[1],[2],[3],[4],[5]] [[5,4,3,2,1]]
2.7 any
any :: [Bool] -> Bool
Given a list of Bool
values, return True
if any of them is True
,
and return False
if every single one of them is False
. Do not
modify the original list.
*Homework4> any [False, False, False] False *Homework4> any [True, False, True, True] True *Homework4> any [] False
2.8 select
select :: (a->Bool) -> [a] -> [a]
Given a predicate function (::a->Bool
) as well as a list of values (::[a]
),
create a list of all items from the argument list that pass the
predicate function. Preserve ordering in your output.
- note: there are built in functions
even
,odd
(defined in thePrelude
) that you might want to use for some manual testing (but they of course won't be part of your solution).
*Homework4> select even [1,2,3,4,5] [2,4] *Homework4> select (\x -> mod x 2 == 1) [1,2,3,4,5] [1,3,5] *Homework4> let window x = 5<x && x<10 *Homework4> select window [1..10] [6,7,8,9]
2.9 zipWith
zipWith :: (a->b->c) -> [a] -> [b] -> [c]
Given a two-argument function and two lists of arguments to supply, create a list of the results of applying the function to each same-indexed pair of arguments from the two lists. (Zip up the two lists with the function). The answer is as long as the shortest of the two input lists. Do not modify the argument lists.
Note: (+)
is an example of making an infix operator behave like a regular two-argument function. (+)
means the same thing as (\x -> \y -> x+y
).
*Homework4> zipWith (+) [1,2,3] [10,20,30] [11,22,33] *Homework4> zipWith (+) [1,2,3] [10,20,30,40,50,60] [11,22,33] *Homework4> zipWith (*) [1,2,3,4] [2,4,6,8] [2,8,18,32] *Homework4> zipWith (*) [1,2,3,4] [] []
2.10 augdentity
augdentity :: Int -> Int -> [[Int]]
Given positive ints r
and c
indicating number of rows and columns,
create a 2D list that represents the "augmented identity matrix" with
that dimension: It's the k x k
identity matrix (where k =
min(r,c)
), and augmented rightwards or downwards as needed with
zeroes in order to be of size r x c
. Stated another way, it's an r
x c
matrix filled with zeroes that has ones along its main diagonal.
*Homework4> augdentity 3 3 [[1,0,0],[0,1,0],[0,0,1]] *Homework4> augdentity 3 5 [[1,0,0,0,0],[0,1,0,0,0],[0,0,1,0,0]] *Homework4> augdentity 5 3 [[1,0,0],[0,1,0],[0,0,1],[0,0,0],[0,0,0]] *Homework4> augdentity 2 2 [[1,0],[0,1]]