This was literally on the exam. You must know all three approaches cold. Each one teaches a different fundamental Haskell concept.
The most intuitive version. Peel off the head, reverse the tail, then stick the head at the end.
myReverse :: [a] -> [a] myReverse [] = [] myReverse (x:xs) = myReverse xs ++ [x]
++ traverses the entire left list each time it is called. Fine for small lists and exams, but know the better way.Use a helper function go that carries an accumulator. Each element is consed onto the front of the accumulator, naturally reversing the order.
myReverse :: [a] -> [a]
myReverse xs = go xs []
where
go [] acc = acc
go (x:xs) acc = go xs (x:acc)
foldl processes the list left-to-right. At each step, we cons the current element onto the front of the accumulator. Since the last element processed ends up at the front, the list gets reversed.
myReverse :: [a] -> [a] myReverse = foldl (flip (:)) [] -- or equivalently: -- myReverse = foldl (\acc x -> x:acc) []
flip (:) takes acc and x, and returns x:acc. This is exactly what the accumulator version does, just written point-free.
myReverse xs ++ [x] -- easy to write, O(n^2)go + where -- O(n), tail-recursivefoldl (flip (:)) [] -- one-liner, same O(n) behavior-- === Save as reverse.hs === -- Approach A: Naive Recursive rev1 :: [a] -> [a] rev1 [] = [] rev1 (x:xs) = rev1 xs ++ [x] -- Approach B: Accumulator rev2 :: [a] -> [a] rev2 xs = go xs [] where go [] acc = acc go (x:xs) acc = go xs (x:acc) -- Approach C: foldl rev3 :: [a] -> [a] rev3 = foldl (flip (:)) []
-- === Test in GHCi === -- $ ghci reverse.hs ghci> rev1 [1,2,3,4,5] [5,4,3,2,1] ghci> rev2 "hello" "olleh" ghci> rev3 [] []
Session 2 asked: “Given a list and a number, find the first occurrence of that number.” The main challenge was I/O. Here are three ways to implement it:
findIndex :: Eq a => a -> [a] -> Int
findIndex _ [] = -1
findIndex x (y:ys)
| x == y = 0
| otherwise = let idx = findIndex x ys
in if idx == -1 then -1 else 1 + idx
if idx == -1 check is error-prone. The accumulator version below is cleaner.findIndex :: Eq a => a -> [a] -> Int findIndex x xs = go xs 0 where go [] _ = -1 go (y:ys) i | x == y = i | otherwise = go ys (i + 1)
go [3,5,7,9] 0 → 3 /= 7, so go [5,7,9] 1go [5,7,9] 1 → 5 /= 7, so go [7,9] 2go [7,9] 2 → 7 == 7, return 2
import Data.List (elemIndex) import Data.Maybe (fromMaybe) -- elemIndex returns Maybe Int -- elemIndex 7 [3,5,7,9] = Just 2 -- elemIndex 1 [3,5,7,9] = Nothing findIndex :: Eq a => a -> [a] -> Int findIndex x xs = fromMaybe (-1) (elemIndex x xs)
The exam gives input like: a list on line 1, target on line 2. You need to read both and print the index.
import System.IO main :: IO () main = do line1 <- getLine -- "3 5 7 9" line2 <- getLine -- "7" let xs = map read (words line1) :: [Int] -- [3, 5, 7, 9] target = read line2 :: Int -- 7 print (findIndex target xs) -- 2
getLine returns a String — you must read to convert to Intwords "3 5 7" gives ["3","5","7"] — splits on spacesmap read (words line) :: [Int] — the type annotation is REQUIRED or Haskell won’t know what to parseprint x = putStrLn (show x) — use for numbersputStrLn for strings, print for numbers<- for I/O actions, let for pure computations inside dogetLine to read raw stringslet ... = map read (words line) :: [Int] to parseprint or putStrLn the result-- === Save as find_index.hs === findIndex :: Eq a => a -> [a] -> Int findIndex x xs = go xs 0 where go [] _ = -1 go (y:ys) i | x == y = i | otherwise = go ys (i + 1)
-- === Test in GHCi === -- $ ghci find_index.hs ghci> findIndex 7 [3, 5, 7, 9] 2 ghci> findIndex 1 [3, 5, 7, 9] -1 ghci> findIndex 3 [3, 5, 7, 9] 0
The accumulator is THE most important Haskell pattern for exam problems. Almost every list-processing function can be written this way. Learn the template and you can derive any function on the spot.
myFunction xs = go xs initialValue where go [] acc = acc -- or some transformation of acc go (x:xs) acc = go xs (combine x acc)
The idea: carry a running result (acc) through the recursion. At each step, combine the current element with the accumulator. When the list is empty, the accumulator holds the final answer.
myReverse xs = go xs []
where
go [] acc = acc
go (x:xs) acc = go xs (x:acc)
myLength :: [a] -> Int
myLength xs = go xs 0
where
go [] n = n
go (_:xs) n = go xs (n + 1)
mySum :: Num a => [a] -> a
mySum xs = go xs 0
where
go [] s = s
go (x:xs) s = go xs (s + x)
myProduct :: Num a => [a] -> a
myProduct xs = go xs 1
where
go [] p = p
go (x:xs) p = go xs (p * x)
go on the tail with updated accumulator-- === Save as accum.hs === myLength :: [a] -> Int myLength xs = go xs 0 where go [] n = n go (_:xs) n = go xs (n + 1) mySum :: Num a => [a] -> a mySum xs = go xs 0 where go [] s = s go (x:xs) s = go xs (s + x) myProduct :: Num a => [a] -> a myProduct xs = go xs 1 where go [] p = p go (x:xs) p = go xs (p * x)
-- === Test in GHCi === -- $ ghci accum.hs ghci> myLength [1,2,3,4] 4 ghci> mySum [1,2,3,4] 10 ghci> myProduct [1,2,3,4] 24
zip pairs up corresponding elements from two lists. It stops at the shorter list. unzip is the inverse: it splits a list of pairs into two separate lists.
myZip :: [a] -> [b] -> [(a,b)] myZip [] _ = [] myZip _ [] = [] myZip (x:xs) (y:ys) = (x,y) : myZip xs ys
Note the two base cases: if either list is empty, we stop. This means zip always produces a list whose length equals the shorter input.
myUnzip :: [(a,b)] -> ([a],[b])
myUnzip [] = ([], [])
myUnzip ((a,b):ps) = (a:as, b:bs)
where (as, bs) = myUnzip ps
The where clause pattern-matches on the recursive result. We extract both halves and cons the current pair's components onto each.
zip stops at the shorter list -- no index-out-of-bounds errorsunzip uses pattern matching in where to split the recursive resultunzip . zip does NOT always give you back the originals (if lengths differ)-- === Save as zip_unzip.hs === myZip :: [a] -> [b] -> [(a,b)] myZip [] _ = [] myZip _ [] = [] myZip (x:xs) (y:ys) = (x,y) : myZip xs ys myUnzip :: [(a,b)] -> ([a],[b]) myUnzip [] = ([], []) myUnzip ((a,b):ps) = (a:as, b:bs) where (as, bs) = myUnzip ps
-- === Test in GHCi === -- $ ghci zip_unzip.hs ghci> myZip [1,2,3] ["a","b","c"] [(1,"a"),(2,"b"),(3,"c")] ghci> myZip [1,2] [10,20,30] [(1,10),(2,20)] ghci> myUnzip [(1,'a'),(2,'b')] ([1,2],"ab")
These three functions are building blocks for many list operations. They split a list at a given index.
myTake :: Int -> [a] -> [a] myTake _ [] = [] myTake 0 _ = [] myTake n (x:xs) = x : myTake (n-1) xs
myDrop :: Int -> [a] -> [a] myDrop _ [] = [] myDrop 0 xs = xs myDrop n (_:xs) = myDrop (n-1) xs
mySplitAt :: Int -> [a] -> ([a],[a]) mySplitAt n xs = (myTake n xs, myDrop n xs)
Note the defensive base cases: both myTake and myDrop handle the empty list first, so you never crash on short inputs. If n exceeds the list length, take returns the whole list and drop returns [].
take -- keep the first n, discard the restdrop -- discard the first n, keep the restsplitAt n xs == (take n xs, drop n xs)-- === Save as take_drop.hs ===
myTake :: Int -> [a] -> [a]
myTake _ [] = []
myTake 0 _ = []
myTake n (x:xs) = x : myTake (n-1) xs
myDrop :: Int -> [a] -> [a]
myDrop _ [] = []
myDrop 0 xs = xs
myDrop n (_:xs) = myDrop (n-1) xs
mySplitAt :: Int -> [a] -> ([a],[a])
mySplitAt n xs = (myTake n xs, myDrop n xs)
-- === Test in GHCi === -- $ ghci take_drop.hs ghci> myTake 3 [1,2,3,4,5] [1,2,3] ghci> myDrop 2 [1,2,3,4,5] [3,4,5] ghci> mySplitAt 3 [1,2,3,4,5] ([1,2,3],[4,5])
Rotation moves the first n elements to the end. It combines take and drop in a clever way.
rotate :: Int -> [a] -> [a]
rotate _ [] = []
rotate n xs = drop m xs ++ take m xs
where m = n `mod` length xs
The mod ensures we handle rotations larger than the list length. Rotating by length xs gives back the original list.
-- === Save as rotate.hs === myTake :: Int -> [a] -> [a] myTake _ [] = [] myTake 0 _ = [] myTake n (x:xs) = x : myTake (n-1) xs myDrop :: Int -> [a] -> [a] myDrop _ [] = [] myDrop 0 xs = xs myDrop n (_:xs) = myDrop (n-1) xs rotate :: Int -> [a] -> [a] rotate _ [] = [] rotate n xs = myDrop m xs ++ myTake m xs where m = n `mod` length xs
-- === Test in GHCi === -- $ ghci rotate.hs ghci> rotate 2 [1,2,3,4,5] [3,4,5,1,2] ghci> rotate 0 [1,2,3] [1,2,3] ghci> rotate 7 [1,2,3,4,5] [3,4,5,1,2]
A palindrome reads the same forwards and backwards. The Haskell solution is beautifully simple.
isPalindrome :: Eq a => [a] -> Bool isPalindrome xs = xs == reverse xs
Or, if you want to use your own reverse:
isPalindrome :: Eq a => [a] -> Bool
isPalindrome xs = xs == myReverse xs
where
myReverse = foldl (flip (:)) []
Eq a => type class constraint -- we need equality comparison on elementsEq, there is no way to compare list elements, so == would not compileisPalindrome "racecar" returns True-- === Save as palindrome.hs ===
isPalindrome :: Eq a => [a] -> Bool
isPalindrome xs = xs == reverse xs
-- === Test in GHCi === -- $ ghci palindrome.hs ghci> isPalindrome [1,2,3,2,1] True ghci> isPalindrome "racecar" True ghci> isPalindrome [1,2,3] False
Interleaving alternates elements from two lists. When one list runs out, the remainder of the other list is appended.
interleave :: [a] -> [a] -> [a] interleave [] ys = ys interleave xs [] = xs interleave (x:xs) (y:ys) = x : y : interleave xs ys
Notice how the third base case (interleave xs []) handles different-length lists gracefully by returning the remaining elements of the non-empty list.
zip, interleave preserves all elements from both lists-- === Save as interleave.hs ===
interleave :: [a] -> [a] -> [a]
interleave [] ys = ys
interleave xs [] = xs
interleave (x:xs) (y:ys) = x : y : interleave xs ys
-- === Test in GHCi === -- $ ghci interleave.hs ghci> interleave [1,2,3] [10,20,30] [1,10,2,20,3,30] ghci> interleave [1,2] [10,20,30,40] [1,10,2,20,30,40] ghci> interleave [] [1,2,3] [1,2,3]
Run-length encoding compresses consecutive duplicate elements into (count, element) pairs. This is a classic exam problem.
encode :: Eq a => [a] -> [(Int, a)]
encode [] = []
encode (x:xs) = (1 + length prefix, x) : encode rest
where (prefix, rest) = span (== x) xs
span is the key function here. span (== x) xs splits xs into two parts: a prefix of elements equal to x, and the rest. For example, span (== 'a') "aab" gives ("aa", "b"). We add 1 to the prefix length because x itself is the first occurrence.
decode :: [(Int, a)] -> [a] decode [] = [] decode ((n,x):rest) = replicate n x ++ decode rest
replicate n x creates a list of n copies of x. For example, replicate 3 'a' gives "aaa". We concatenate each expanded run and recurse on the rest.
span p xs -- splits list into (longest prefix satisfying p, the rest)replicate n x -- creates a list of n copies of xdecode . encode == id for any list (round-trip property)-- === Save as rle.hs === encode :: Eq a => [a] -> [(Int, a)] encode [] = [] encode (x:xs) = (1 + length prefix, x) : encode rest where (prefix, rest) = span (== x) xs decode :: [(Int, a)] -> [a] decode [] = [] decode ((n,x):rest) = replicate n x ++ decode rest
-- === Test in GHCi === -- $ ghci rle.hs ghci> encode "aaabbbcc" [(3,'a'),(3,'b'),(2,'c')] ghci> encode [1,1,2,3,3,3] [(2,1),(1,2),(3,3)] ghci> decode [(3,'a'),(2,'b'),(1,'c')] "aaabbc"
Insertion sort in Haskell is elegant: recursively sort the tail, then insert the head into the correct position.
insertionSort :: Ord a => [a] -> [a]
insertionSort [] = []
insertionSort (x:xs) = insert x (insertionSort xs)
insert :: Ord a => a -> [a] -> [a]
insert x [] = [x]
insert x (y:ys)
| x <= y = x : y : ys
| otherwise = y : insert x ys
insert walks along a sorted list until it finds the right spot, then places the element there. insertionSort builds up the sorted list one element at a time from right to left.
-- === Save as sort.hs === insertionSort :: Ord a => [a] -> [a] insertionSort [] = [] insertionSort (x:xs) = insert x (insertionSort xs) insert :: Ord a => a -> [a] -> [a] insert x [] = [x] insert x (y:ys) | x <= y = x : y : ys | otherwise = y : insert x ys
-- === Test in GHCi === -- $ ghci sort.hs ghci> insertionSort [3,1,4,1,5,9,2,6] [1,1,2,3,4,5,6,9] ghci> insertionSort "hello" "ehllo" ghci> insert 4 [1,3,5,7] [1,3,4,5,7]
Merging two already-sorted lists into one sorted list. Compare the heads, take the smaller one, and recurse.
merge :: Ord a => [a] -> [a] -> [a]
merge [] ys = ys
merge xs [] = xs
merge (x:xs) (y:ys)
| x <= y = x : merge xs (y:ys)
| otherwise = y : merge (x:xs) ys
-- === Save as merge.hs === merge :: Ord a => [a] -> [a] -> [a] merge [] ys = ys merge xs [] = xs merge (x:xs) (y:ys) | x <= y = x : merge xs (y:ys) | otherwise = y : merge (x:xs) ys
-- === Test in GHCi === -- $ ghci merge.hs ghci> merge [1,3,5] [2,4,6] [1,2,3,4,5,6] ghci> merge [1,2,3] [] [1,2,3] ghci> merge [1,1] [1,2] [1,1,1,2]
DOMjudge problems require reading from stdin and writing to stdout. Here are the patterns you need.
import System.IO main :: IO () main = do line <- getLine let nums = map read (words line) :: [Int] -- process nums putStrLn (unwords (map show result))
import System.IO main :: IO () main = do contents <- getContents let allLines = lines contents let results = map processLine allLines mapM_ putStrLn results processLine :: String -> String processLine line = let nums = map read (words line) :: [Int] in show (sum nums) -- example: sum each line
main :: IO () main = do n <- readLn :: IO Int inputLines <- sequence (replicate n getLine) let nums = map (\l -> map read (words l) :: [Int]) inputLines -- nums is [[Int]], one list per line mapM_ print (map processLine nums)
getLine -- read one line as StringgetContents -- read all of stdin as one String (lazy)words -- split string on whitespace into [String]lines -- split string on newlines into [String]read -- parse a String into a number (needs type annotation)show -- convert a value to Stringunwords / unlines -- join with spaces / newlinesmapM_ -- apply an IO action to each element in a listThis was the Session 2 exam pattern. Read a list and a single value separately.
main :: IO () main = do line1 <- getLine line2 <- getLine let xs = map read (words line1) :: [Int] target = read line2 :: Int -- now use xs and target print (someFunction target xs)
Some problems give N on line 1, then N numbers on line 2.
main :: IO () main = do nStr <- getLine let n = read nStr :: Int line <- getLine let xs = take n (map read (words line) :: [Int]) -- process xs print (someFunction xs)
main :: IO () main = do contents <- getContents -- read ALL stdin at once let allLines = lines contents -- split into lines nums = map read allLines :: [Int] -- parse each line as Int -- process nums mapM_ print (someFunction nums)
-- === Save as solution.hs, compile & run === -- $ ghc -o solution solution.hs -- $ echo "1 2 3 4 5" | ./solution -- Or run interactively: -- $ runghc solution.hs -- (then type input and press Enter)
Try each of these without looking at the solution. Write the code on paper if you can -- that is what the exam demands.
Implement myReverse :: [a] -> [a] using an accumulator. Do NOT use ++.
myReverse :: [a] -> [a]
myReverse xs = go xs []
where
go [] acc = acc
go (x:xs) acc = go xs (x:acc)
The accumulator starts empty. Each element is consed onto the front, naturally reversing the order. This runs in O(n) because (:) is O(1).
Write myZip :: [a] -> [b] -> [(a,b)] that pairs up corresponding elements from two lists. Stop at the shorter list.
myZip :: [a] -> [b] -> [(a,b)] myZip [] _ = [] myZip _ [] = [] myZip (x:xs) (y:ys) = (x,y) : myZip xs ys
Two base cases handle either list being empty. The recursive case pairs the heads and recurses on the tails.
Write rotate :: Int -> [a] -> [a] that rotates a list left by n positions.
rotate :: Int -> [a] -> [a]
rotate _ [] = []
rotate n xs = drop m xs ++ take m xs
where m = n `mod` length xs
drop m gives everything after the first m elements, and take m gives the first m elements. Concatenating them achieves the rotation. The mod handles cases where n is larger than the list length.
Write encode :: Eq a => [a] -> [(Int, a)] that compresses consecutive duplicates into (count, element) pairs.
encode :: Eq a => [a] -> [(Int, a)]
encode [] = []
encode (x:xs) = (1 + length prefix, x) : encode rest
where (prefix, rest) = span (== x) xs
span (== x) xs splits xs into a prefix of elements equal to x and the rest. We add 1 to the prefix length to count x itself (the head we already removed).
Write insertionSort :: Ord a => [a] -> [a] and the helper insert :: Ord a => a -> [a] -> [a].
insertionSort :: Ord a => [a] -> [a]
insertionSort [] = []
insertionSort (x:xs) = insert x (insertionSort xs)
insert :: Ord a => a -> [a] -> [a]
insert x [] = [x]
insert x (y:ys)
| x <= y = x : y : ys
| otherwise = y : insert x ys
insertionSort recursively sorts the tail, then inserts the head into its correct position. insert walks along the sorted list, comparing until it finds the right spot.
Write isSorted :: Ord a => [a] -> Bool that checks whether a list is in non-decreasing order. Do NOT use sort.
Hint: Compare adjacent pairs. A list with 0 or 1 elements is always sorted.
isSorted :: Ord a => [a] -> Bool isSorted [] = True isSorted [_] = True isSorted (x:y:xs) = x <= y && isSorted (y:xs)
Base cases: empty list and single-element list are trivially sorted. Recursive case: check that the first two elements are in order, then recurse with the second element still at the head (so we compare every adjacent pair).
Alternative using zip:
isSorted :: Ord a => [a] -> Bool isSorted xs = and (zipWith (<=) xs (tail xs))
zipWith (<=) xs (tail xs) produces a list of Bools, one for each adjacent pair. and returns True only if all of them are True.
Write findIndex :: Eq a => a -> [a] -> Int that returns the 0-based index of the first occurrence, or -1 if not found. Then write a main that reads a list of integers (line 1) and a target (line 2), and prints the result.
-- Input: -- Output: -- 10 20 30 40 50 -- 2 -- 30
findIndex :: Eq a => a -> [a] -> Int findIndex x xs = go xs 0 where go [] _ = -1 go (y:ys) i | x == y = i | otherwise = go ys (i + 1) main :: IO () main = do line1 <- getLine line2 <- getLine let xs = map read (words line1) :: [Int] target = read line2 :: Int print (findIndex target xs)
The accumulator version is clean and efficient. The I/O is the standard recipe: getLine, words, map read, type annotate.