CS 360
Winter 2018
Programming Language Concepts
CS 360-001 Tuesday/Thursday 15:30-16:50 (Rush 209)
CS 360-002 Tuesday/Thursday 14:00-15:20 (Rush 209)
CS 360-003 Tuesday 18:30-21:20 (UCross 151)

Geoffrey Mainland
Office: University Crossings 106
Office hours: Mondays 4pm–7pm; Thursdays 5pm–6pm.
Teaching Assistant:
Xiao Han
CLC office hours: Tuesday 12pm–2pm; Thursday 6pm–8pm
Allen Yang
CLC office hours: Wednesday 6pm–8pm

Homework 4: Haskell

Due Monday, February 19, 11:59:59PM EST.

Accept this assignment on GitHub Classroom here. The standard homework instructions apply.

In this assignment, you will implement the Luhn algorithm for validating credit card numbers and solve some additional small problems in Haskell. 1

You must implement the functions as specified. You may write other helper functions and define test data in your file, but you may not change the functions’ names or the number or order of arguments.

You should complete the homework by modifying the file src/HW04.hs that we provide as part of your repository.

Make sure you are using the correct version of GHC, 8.0.2. If you are using the course VM, you are all set. If you are using tux, please read our instructions for getting the course software on tux to make sure you have the correct version.

Note: The first time you build your code, it will take some time, as all prerequisites will need to be automatically downloaded and installed. Subsequent builds will be much faster.

This assignment is worth 50 points. There are 51 possible points.

Problem 1: Implementing the Luhn Algorithm (30 points total)

The Luhn algorithm is used to check the validity of credit card numbers. You can read about it on Wikipedia here. For this problem, you will implement the Luhn algorithm in Haskell. The algorithm encompasses the following steps:

  1. Double the value of every second digit beginning from the right. That is, the last digit is unchanged; the second-to-last digit is doubled; the third-to-last digit is unchanged; and so on. For example, [1,3,8,6] becomes [2,3,16,6].
  2. Add the digits of the doubled values and the undoubled digits from the original number. For example, [2,3,16,6] becomes 2+3+1+6+6 = 18.
  3. Calculate the remainder when the sum is divided by 10. For the above example, the remainder would be 8. If the result equals 0, then the number is valid.

Problem 1.1 (5 points)

We first need to be able to break up a number into its last digit and the rest of the number. Write these functions:

lastDigit :: Integer -> Integer
dropLastDigit :: Integer -> Integer

If you’re stumped, look through some of the arithmetic operators mentioned in the lecture.

Example output:

GHCi, version 8.0.2: http://www.haskell.org/ghc/  :? for help
Prelude> :load HW04.hs 
[1 of 2] Compiling Set              ( Set.hs, interpreted )
[2 of 2] Compiling HW04             ( HW04.hs, interpreted )
Ok, modules loaded: HW04, Set.
*HW04> lastDigit 123
*HW04> lastDigit 0
*HW04> dropLastDigit 123
*HW04> dropLastDigit 5

Problem 1.2 (5 points)

Now, we can break apart a number into its digits. Define the function

toDigits :: Integer -> [Integer]

toDigits should convert positive Integers to a list of digits. For 0 or negative inputs, toDigits should return the empty list.


toDigits 1234 == [1,2,3,4]
toDigits 0 == []
toDigits (-17) == []

Problem 1.3 (5 points)

Once we have the digits in the proper order, we need to double every other one. Define a function

doubleEveryOther :: [Integer] -> [Integer]

Remember that doubleEveryOther should double every other number beginning from the right, that is, the second-to-last, fourth-to-last, … numbers are doubled. Note that it’s much easier to perform this operation on a list of digits that’s in reverse order. You will likely need helper functions to make this work.


doubleEveryOther [8,7,6,5] == [16,7,12,5]
doubleEveryOther [1,2,3] == [1,4,3]

Problem 1.4 (5 points)

The output of doubleEveryOther has a mix of one-digit and two-digit numbers. Define the function

sumDigits :: [Integer] -> Integer

to calculate the sum of all digits.


sumDigits [16,7,12,5] = 1 + 6 + 7 + 1 + 2 + 5 = 22

Problem 1.5 (10 points)

Define the function

validate :: Integer -> Bool

that indicates whether an Integer could be a valid credit card number. This will use all functions defined in the previous exercises.


validate 4012888888881881 = True
validate 4012888888881882 = False

Problem 2: Tower of Hanoi (10 points)

The Tower of Hanoi is a puzzle in which disks of different sizes are stacked on three pegs. The goal is to get from the initial state, in which all disks are stacked on the first peg, to a final state in which all disks are stacked on the second peg. The only rules are:

  1. Only one disk may be moved at a time.
  2. A larger disk can never be stacked on a smaller disk.

The Tower of Hanoi has an elegant recursive solution: to move $n$ disks from peg $A$ to peg $B$,

  1. Move $n-1$ disks from peg $A$ to peg $C$ using peg $B$ as intermediate storage.
  2. Move the $n$th disk from peg $A$ to peg $B$,
  3. Move $n-1$ disks from peg $C$ to peg $B$ using peg $A$ as intermediate storage.

You will define a function hanoi that when given an integer $n$ and the names of three pegs, outputs a list of moves necessary to transfer the stack of $n$ disks from the first peg to the second.

type Peg = String
type Move = (Peg, Peg)

hanoi :: Integer -> Peg -> Peg -> Peg -> [Move]

Example output in ghci:

> hanoi 2 "a" "b" "c"
[("a","c"), ("a","b"), ("c","b")]

See Wikipedia for a full explanation of the Tower of Hanoi game.

Hints: My solution is two lines: base case and inductive case. You will probably want to use list functions like ++. Read the LYAH Intro to Lists if you haven’t already.

Problem 3: Power set of a set (10 points)

Implement the power set function, $\mathcal{P}(\cdot)$, from lecture. Recall that the power set $\mathcal{P}(S)$ of a set $S$ is the set of all subsets of $S$, including the empty set and $S$ itself. Also recall the inductive proof that a set with $n$ elements has a power set with $2^n$ elements.

The type signature for powerSet should look like this:

powerSet :: ... => ... -> ...

Before you write the function, figure out what its type signature should be. If you cannot write the function, you will receive partial credit for a correct type signature. However, your code must compile for you to receive any credit, so if you only write a type signature for powerSet, don’t remove the dummy function definition.

One of the goals of this problem is to treat Set as an abstract data type; we should be able to change the representation of sets, keeping the external interface the same, and the code you wrote to compute the power set would run unchanged. This means that you cannot rely in any way on the fact that sets happen to be implemented as sorted lists with no duplicates. How then can you test for an empty set? Fortunately, there is an isEmpty predicate exported by the Set module. How can you split a set into an element plus the rest of the set? Again, we fortunately have a split function exported by the Set module.

If you are stuck trying to pattern match on the structure of a set, stop! You can’t do that, since you don’t get to see the representation! Instead, pattern match and use guards, or, if you prefer, just use Haskell’s if.

Please note the following constraints:

  1. You may not change the code in Set.hs.
  2. Solutions that themselves use fromList or toList will receive zero credit. You must use the Set abstraction without relying on the fact that “under the covers” it is implemented using lists.
  3. You may use any function that is exported by the Set module, even if it uses fromList or toList internally. Think of the Set module as a black box whose implementation you are not allowed to see.

Problem 4: Homework Statistics (1 point)

How long did spend on each problem? Please tell us in your README.md. You may enter time using any format described here.


  1. These problems are adapted from Brent Yorgey’s course, “Introduction to Haskell”.