Skip to content

Instantly share code, notes, and snippets.

@erickedji
Created October 21, 2009 21:33
Show Gist options
  • Save erickedji/215479 to your computer and use it in GitHub Desktop.
Save erickedji/215479 to your computer and use it in GitHub Desktop.
Ninety-nine Haskell problem (translated from Scheme)
------------------------------------------------------
------------------------------------------------------
-- Ninety-nine Haskell problems
-- KEDJI Komlan Akpédjé <eric.kedji@gmail.com>
-- http://erickedji.wordpress.com/
-- PHD student at IRIT (Toulouse, FRANCE)
--
-- Based on 'Ninety-nine Lisp Problems'
-- (www.ic.unicamp.br/~meidanis/courses/
-- mc336/2006s2/funcional/
-- L-99_Ninety-Nine_Lisp_Problems.html),
-- based itself on 'Ninety-nine Prolog
-- problems, by werner.hett@hti.bfh.ch
--
-- Stars after problem numbers represent the
-- difficulty level.
-- In the solution, I try to use the most
-- idiomatic Haskell I'm aware of, so free
-- feel to flame me if something looks wrong.
------------------------------------------------------
------------------------------------------------------
--
module Nnhp where
import Data.List
import Control.Arrow
--
------------------------------------------------------
-- Manipulation de listes
------------------------------------------------------
--
------------------------------------------------------
--
-- P01 (*) Trouver la dernière cellule d'une liste.
-- Exemple:
-- * (my-last '(a b c d))
-- (d)
------------------------------------------------------
--
-- La dernière cellule d'une liste à 1|0 élément est la liste elle même.
-- La dernière cellule d'une liste à plus d'un élément est identique à
-- la dernière cellule de son cdr
myLast :: [a] -> [a]
myLast [] = []
myLast [x] = [x]
myLast (x:xs) = myLast xs
--(define (my-last liste)
-- (if (or (null? liste)
-- (null? (cdr liste)))
-- liste
-- (my-last (cdr liste))))
--
--(my-last '(a b c d))
--
------------------------------------------------------
--
-- P02 (*) Trouver l'avant dernière cellule d'une liste.
-- Exemple:
-- * (my-but-last '(a b c d))
-- (c d)
------------------------------------------------------
--
myButLast :: [a] -> [a]
myButLast [] = []
myButLast [x] = [x]
myButLast [x, y] = [x, y]
myButLast (x:xs) = myButLast xs
--(define (my-but-last liste)
-- (if (or (null? liste)
-- (null? (cdr liste))
-- (null? (cddr liste)))
-- liste
-- (my-but-last (cdr liste))))
--
--(my-but-last '(a b c d))
--
------------------------------------------------------
--
-- P03 (*) Trouver le Kieme élément d'une liste.
-- Le premier élément de la liste porte le numéro 1
-- Exemple:
-- * (element-at '(a b c d e) 3)
-- c
------------------------------------------------------
--
-- L'élément à la position 1 dans une liste est le car
-- L'élément à la position k dans une liste est à la position k-1
-- dans son cdr
elementAt :: [a] -> Int -> a
elementAt (x:xs) 1 = x
elementAt (x:xs) n = elementAt xs (n-1)
-- Note: In Haskell, the indexing operator is `!!', so this is
-- better written: elementAt list n = list !! (n - 1)
--(define (element-at liste numero)
-- (if (= 1 numero)
-- (car liste)
-- (element-at (cdr liste) (- numero 1))))
--
--(element-at '(a b c d e) 3)
--
------------------------------------------------------
--
-- P04 (*) Trouver le nombre d'éléments d'une liste.
-- * (number-of-elements '(1 2 3 4))
-- 4
-- * (number-of-elements '(1 (2 3) 4))
-- 3
------------------------------------------------------
--
numberOfElements :: [a] -> Int
numberOfElements = foldr (\ x -> (+) 1) 0
-- Note:
-- * using the prelude: numberOfElements = length
-- * with a left fold, the accumulator should be
-- uncurried, that is:
-- numberOfElements = foldl' (\ n x -> x + 1) 0
-- or it must be flipped:
-- numberOfElements = foldl' (flip (\ x -> (+) 1)) 0
-- but the latter is less readable.
-- * what about: numberOfElements = sum . map (\ x -> 1)
--
--(define (number-of-elements liste)
-- (if (null? liste)
-- 0
-- (+ 1 (number-of-elements (cdr liste)))))
--
--(number-of-elements '(a b h j (k t) l l j))
--
-- Version iterative (c'est une technique générale pour
-- transformer la récursivité non-terminale en récusivité
-- terminale (tail-recursion))
-- Au lieu de laisser les appels récursifs construire le résultat,
-- on accumule explicitement le résultat dans un paramètre.
-- Tout comme les boucles classiques dans les langages impératifs.
--
-- In Haskell, foldl' sidesteps the languages laziness (by
-- eager evaluation of the args to the combinator function),
-- and compiles down to an imperative loop.
numberOfElementsIter :: [a] -> Int
numberOfElementsIter = foldl' (\ n x -> n + 1) 0
--(define (number-of-elements2 liste)
-- (define (noe-iter reste-liste count)
-- (if (null? reste-liste)
-- count
-- (noe-iter (cdr reste-liste) (+ 1 count))))
-- (noe-iter liste 0))
--
--(number-of-elements2 '(a b (c ge f) d e))
--
-- compter le nombre d'atomes (ie le nombre
-- d'éléments qui ne sont pas des paires)
-- * (number-of-atoms '(1 (2 3) 4)
-- 4
-- * (number-of-atoms '(1 (2 . 3) 4)
-- 4
-- * (number-of-atoms '(1 2 3 (4 5 6 . 7) 8))
-- 8
--
-- Haskell's static typing does not allow to mix
-- atoms and lists in the same list, so I guess
-- this exercice does not apply. I'll return to
-- it if there is future contrary evidence
--
--(define (number-of-atoms liste)
-- (define (atom? x) (not (pair? x)))
-- (define (notnull? x) (not (null? x)))
-- (if (null? liste)
-- 0
-- (let ((tete (car liste))
-- (queue (cdr liste)))
-- (+ (if (atom? tete)
-- 1
-- (number-of-atoms tete))
-- (if (and (atom? queue) (notnull? queue)) ; une paire: (a . b)
-- 1
-- (number-of-atoms (cdr liste)))))))
--
--(number-of-atoms '(a b h j (k t) l l (e (e) (a b) j))) ; => 13
--(number-of-atoms '(1 2 (3 4 . 5) 6)) ; => 6
--
------------------------------------------------------
--
-- P05 (*) Inverser l'ordre des éléments d'une liste.
-- * (my-reverse '(1 2 3))
-- (3 2 1)
-- * (my-reverse '(1 (2 3) 4))
-- (4 (2 3) 1)
------------------------------------------------------
--
myReverse :: [a] -> [a]
myReverse = foldl' (flip (:)) []
-- Note: using the prelude: myReverse = reverse
--(define (my-reverse liste)
-- (if (null? liste)
-- '()
-- (append (my-reverse (cdr liste))
-- (list (car liste)))))
--
-- par accumulation
-- - copier en tête de la liste source
-- - coller en tête de la liste cible
--
-- No difference, Haskell is a lazy beast.
--
--(define (my-reverse2 liste)
-- (define (mr-iter reste-liste result)
-- (if (null? reste-liste)
-- result
-- (mr-iter (cdr reste-liste)
-- (cons (car reste-liste) result))))
-- (mr-iter liste '()))
--
--(my-reverse '(s ff k h (k l) l t))
--(my-reverse2 '(s ff k h (k l) l t))
--
-- deep-reverse: inverser l'ordre dans les sous-listes
-- aussi (récursivement). Toutes les listes sont garanties
-- se terminer par nil (ie par de dotted pair du style (... a . b))
-- * (deep-reverse '(a (b c) d))
-- (d (c b) a)
--
-- Not interresting in the case of Haskell, as we know up front
-- the type of the elements in the list.
--
--(define (deep-reverse liste)
-- (cond ((null? liste) '())
-- ((null? (cdr liste)) (if (not (list? (car liste)))
-- liste
-- (list (deep-reverse (car liste)))))
-- (else (append (deep-reverse (cdr liste))
-- (deep-reverse (list (car liste)))))))
--
--(deep-reverse '(1 2 (3 4) 5 (6 (7 8 (9)) 10) 11))
------------------------------------------------------
--
-- P06 (*) Déterminer si une liste est palindrome.
-- Un palindrome peut être lu aussi bien d'avant en arrière que
-- d'arrière en avant, comme (x a m a x).
------------------------------------------------------
--
-- I start using type-inference. Also note how the definition is
-- succinct and uncluttered, compared to scheme. +1 for Haskell!
isPalindrome list = list == reverse list
--
--(define (is-palindrome liste)
-- (equal? liste (reverse liste)))
--
--(is-palindrome '(x (a b) a m a (a b) x))
--
------------------------------------------------------
--
-- P07 (**) Aplatir une structure de listes imbriquées.
-- Transformer une liste, contenant potentiellement des listes
-- comme éléments en une liste 'plate', en remplaçant chaque
-- liste par ses éléments (récursivement).
--
-- Exemple:
-- * (my-flatten '(a (b (c d) e)))
-- (a b c d e)
--
-- Indication: Utiliser les fonctions prédéfinies list et append.
------------------------------------------------------
-- Not really challenging, in Haskell, as the closer you can get
-- is to flatten a list of lists of <a>s which is trivial.
-- Note that whenever we repeat an argument verbatim in
-- the body of a function, we should instead use composition. In
-- academic parlance, this is called an "Eta reduce".
-- flattenIntListsList list = foldl (++) [] list
flattenList = foldl (++) []
-- Note: using the prelude: flattenList = concat
--
--(define (my-flatten liste)
-- (if (null? liste)
-- '()
-- (append (if (list? (car liste))
-- (my-flatten (car liste))
-- (list (car liste)))
-- (my-flatten (cdr liste)))))
--
--(my-flatten '(a (b (c d) e)))
--
------------------------------------------------------
--
-- P08 (**) Eliminer les répétitions consécutives dans une liste.
-- Si une liste contient des éléments répétés (consécutifs),
-- les remplacer par une seule copie. L'ordre des éléments
-- ne doit pas être modifié.
--
-- Exemple:
-- * (compress '(a a a a b c c a a d e e e e))
-- (a b c a d e)
------------------------------------------------------
--
-- Notice the "Eq a" used as the context for the type
-- signature for compress: equality must be defined
-- for the list elements.
compress :: Eq a => [a] -> [a]
compress [] = []
compress (x:xs) = x : compress (dropWhile (== x) xs)
-- Note: a more basic version, without `dropWhile'
-- compress :: Eq a => [a] -> [a]
-- compress [] = []
-- compress [x] = [x]
-- compress (y:x:xs)
-- | x == y = compress (x:xs)
-- | otherwise = y : compress (x:xs)
--
--(define (compress liste)
-- (if (or (null? liste) (null? (cdr liste)))
-- liste
-- (let ((compressed-cdr (compress (cdr liste))))
-- (if (equal? (car liste) (car compressed-cdr))
-- compressed-cdr
-- (cons (car liste) compressed-cdr)))))
--
--(compress '(a a a a b (c c) (c c) (a b) (a b) a a d e e e e))
--
-- Proper tail-recursive version
--
-- The Haskell version serves only to illustrate the use of
-- `where' (forward declaration)
compressAcc :: Eq a => [a] -> [a]
compressAcc list = reverse (compressAccHelper list [])
where
compressAccHelper [] compressedList = compressedList
compressAccHelper (x:xs) [] = compressAccHelper xs [x]
compressAccHelper (x:xs) (y:ys)
| x == y = compressAccHelper xs (y:ys)
| otherwise = compressAccHelper xs (x:y:ys)
--(define (iterative-compress liste)
-- (define (ic-helper reste-liste reversed-result)
-- (if (null? reste-liste)
-- reversed-result
-- (if (equal? (car reste-liste) (car reversed-result))
-- (ic-helper (cdr reste-liste) reversed-result)
-- (ic-helper (cdr reste-liste)
-- (cons (car reste-liste) reversed-result)))))
-- (reverse (ic-helper (cdr liste) (list (car liste)))))
--
--(iterative-compress '(a a a a b (c c) (a b) (a b) a a d e e e e))
--
------------------------------------------------------
--
-- P09 (**) Regrouper les répétitions consécutives d'éléments d'une
-- liste en des sous-listes
--
-- Exemple:
-- * (pack '(a a a a b c c a a d e e e e))
-- ((a a a a) (b) (c c) (a a) (d) (e e e e))
------------------------------------------------------
--
pack :: Eq a => [a] -> [[a]]
pack [] = []
pack list@(x:xs) = [takeWhile (== x) list] ++ pack (dropWhile (== x) xs)
-- (I love how the last line reads as english)
-- Note: more verbose version, illustrates the use of `case ... of'
-- pack :: Eq a => [a] -> [[a]]
-- pack [] = []
-- pack [x] = [[x]]
-- pack (x:xs) =
-- let packedTail = pack xs in
-- case packedTail of
-- (list@(y:ys):lists) ->
-- if y == x
-- then (x:y:ys):lists
-- else [x]:list:lists
--
--(define (pack liste)
-- (if (null? liste)
-- (list liste) ; cas trivial
-- (let ((packed-cdr (pack (cdr liste))))
-- (cond ((null? (car packed-cdr)) ; packed-cdr provient du cas trivial
-- (list (list (car liste))))
-- ((equal? (car liste) (caar packed-cdr)) ; même élément: ajout à la sous-liste
-- (cons (cons (car liste)
-- (car packed-cdr))
-- (cdr packed-cdr)))
-- (else (cons (list (car liste)) ; nouvel élément: nouvelle sous-liste
-- packed-cdr))))))
--
--(pack '(a a a a b c c a a d e (e) (e) e))
--
------------------------------------------------------
--
-- P10 (*) Compression d'une liste par `run-length encoding'
-- Utiliser le résultat du problème P09 to implémenter la
-- technique de compression de données connue sous le nom
-- de run-length encoding. Les répétitions consécutives
-- d'éléments sont codés en listes de la forme (N E), où
-- N est le nombre de répétitions de l'élément E.
--
-- Exemple:
-- * (encode '(a a a a b c c a a d e e e e))
-- ((4 a) (1 b) (2 c) (2 a) (1 d)(4 e))
------------------------------------------------------
--
-- Note the function composition. map is curried, to yield
-- a function expecting only the list (as the transformer
-- is already given). The resulting function is composed
-- with pack, so as to pack the list given to encode, prior
-- to the mapping.
-- Hard to read for the beginner, but a known pattern for
-- the seasoned haskeller.
-- (I love how encode in Haskell reads as english)
encode :: Eq a => [a] -> [(Int, a)]
encode = map (length &&& head) . pack
-- with parens for added clarity: (map (length &&& head)) . pack
-- same as: encode = map (\ lst -> (length lst, head lst)) . pack
--
--(define (encode liste)
-- (define (encode-helper packed-list)
-- (if (null? packed-list)
-- '()
-- (cons (list (length (car packed-list))
-- (caar packed-list))
-- (encode-helper (cdr packed-list)))))
-- (encode-helper (pack liste)))
--
--(encode '(a a a a b c c a a d e e e e))
--
------------------------------------------------------
--
-- P11 (*) Amélioration de run-length encoding.
-- Modifier le résultat du problème P10, de telle sorte que les
-- éléments non dupliqués soient tout simplement copiés. Seuls les
-- éléments dupliqués sont mis sous la forme (N E).
--
-- Exemple:
-- * (encode-modified '(a a a a b c c a a d e e e e))
-- ((4 a) b (2 c) (2 a) d (4 e))
------------------------------------------------------
--
-- Inapplicable à Haskell (fort typage)
--
--(define (encode-modified liste)
-- (define (simplify encoded-list)
-- (if (null? encoded-list)
-- '()
-- (cons (if (= 1 (caar encoded-list))
-- (cadar encoded-list)
-- (car encoded-list))
-- (simplify (cdr encoded-list)))))
-- (simplify (encode liste)))
--
--(encode-modified '(a a a a b c c a a d e e e e))
--
------------------------------------------------------
--
-- P12 (**) Decoder une liste compressée par run-length encoding.
-- Etant donné une liste compressée comme spécifié dans le
-- le problème P11, retrouver la liste originale.
------------------------------------------------------
--
-- For Haskell, we suppose the list is encoded with P10
--
decodeRl :: [(Int, a)] -> [a]
decodeRl list = concat (map expand list)
where
expand tuple = replicate (fst tuple) (snd tuple)
-- as readable as it gets; compare to this verbose version:
-- decodeRl :: [(Int, a)] -> [a]
-- decodeRl list = foldl (++) [] (map expand list)
-- where
-- expand :: (Int, a) -> [a]
-- expand (1, x) = [x]
-- expand (n, x) = x : expand (n-1, x)
--
--(define (decode-rl encoded-list)
-- (define (expand element count)
-- (if (= count 0)
-- '()
-- (cons element
-- (expand element (- count 1)))))
-- (cond ((null? encoded-list) '())
-- ((not (list? (car encoded-list)))
-- (append (list (car encoded-list))
-- (decode-rl (cdr encoded-list))))
-- (else (append (expand (cadar encoded-list)
-- (caar encoded-list))
-- (decode-rl (cdr encoded-list))))))
--
--(decode-rl (encode-modified '(a a a a b c c a a d e e e e)))
--
------------------------------------------------------
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment