Skip to content

2. English ‐ CoxyHasList

Bernard Sibanda edited this page Sep 13, 2025 · 2 revisions

📘 CoxyHasList — Beginner Tutorial on Lists Processing in Haskell

Company : Coxygen Global Date : 10-09-2025 Licence : MIT

Table of Contents

  1. 🔧 Full Module (copy-paste ready)
  2. ✅ Mini Test Helper: assert
  3. 📏 List Size: sizeOfList
  4. 🔎 Find/Check Items: isItemInTheList, findPosItemList, findElemList
  5. 🪪 Safe Access: firstElemList
  6. 🧰 Keep/Remove with Conditions: filterItemsList, removeItemList
  7. ➕ Combining Lists: joinList
  8. ✂️ Drop Ends: notHeadList, notLastList, bodyElemList
  9. 🧱 Add to Front: addItemList
  10. 🔁 Reverse: reverseList
  11. 🎯 Last Element (safe): lastElemList
  12. 🗂️ Sort Asc/Des: orderList
  13. 🔠 Case Change (no imports): strToUpper, strToLower
  14. 🧼 String Cleanups: removeSpaces, removeVowels, removeNoAlphabetLetters, removeSymbols, lettersOnly
  15. ✍️ Find & Replace (substring): findAndReplaceStrings, startsWith
  16. 🏆 Extremes: biggestItemList, smallestItemList
  17. 📚 Glossary

1) 🔧

module CoxyHasList where

-- 2) ✅ Mini Test Helper
assert :: (Eq a, Show a) => String -> a -> a -> String
assert itemNameTested expected actual =
  if expected == actual
    then "[Pass] : " ++ itemNameTested ++ " Answer : " ++ show actual
    else "[Fail] : " ++ itemNameTested ++
         " Expected : " ++ show expected ++
         " But got : " ++ show actual

-- 3) 📏 List Size
sizeOfList :: [a] -> Int
sizeOfList []     = 0
sizeOfList [_]    = 1
sizeOfList (_:xs) = 1 + sizeOfList xs

-- 4) 🔎 Find/Check Items
isItemInTheList :: (Eq a) => a -> [a] -> Bool
isItemInTheList _ [] = False
isItemInTheList item (x:xs)
  | item == x  = True
  | otherwise  = isItemInTheList item xs

-- Alias used by main in your text
findElemList :: Eq a => a -> [a] -> Bool
findElemList = isItemInTheList

findPosItemList :: Eq a => [a] -> a -> Maybe Int
findPosItemList xs y = go 0 xs
  where
    go :: Eq a => Int -> [a] -> Maybe Int
    go _ []     = Nothing
    go i (x:xs)
      | x == y    = Just i
      | otherwise = go (i + 1) xs

-- 5) 🪪 Safe Access
firstElemList :: [a] -> Maybe a
firstElemList []     = Nothing
firstElemList (x :_) = Just x

-- 6) 🧰 Keep/Remove with Conditions
filterItemsList :: (a -> Bool) -> [a] -> [a]
filterItemsList _ [] = []
filterItemsList p (x:xs)
  | p x       = x : filterItemsList p xs
  | otherwise =     filterItemsList p xs

removeItemList :: Eq a => a -> [a] -> [a]
removeItemList x = filter (\y -> y /= x)

-- 7) ➕ Combining Lists
joinList :: [a] -> [a] -> [a]
joinList xs ys = xs ++ ys

-- 8) ✂️ Drop Ends
notHeadList :: [a] -> [a]
notHeadList []     = []
notHeadList (_:xs) = xs

notLastList :: [a] -> [a]
notLastList []     = []
notLastList [_]    = []
notLastList (x:xs) = x : notLastList xs

-- Two equivalent versions were present; keep the simpler one:
bodyElemList :: [a] -> [a]
bodyElemList xs = notLastList (notHeadList xs)

-- 9) 🧱 Add to Front
addItemList :: a -> [a] -> [a]
addItemList a as = a : as

-- 10) 🔁 Reverse
reverseList :: [a] -> [a]
reverseList []       = []
reverseList (a:as)   = reverseList as ++ [a]

-- 11) 🎯 Last Element (safe)
lastElemList :: [a] -> Maybe a
lastElemList []       = Nothing
lastElemList [a]      = Just a
lastElemList (_:as)   = lastElemList as

-- 12) 🗂️ Sort Asc/Des (quicksort style)
orderList :: (Ord a) => [a] -> String -> [a]
orderList [] _ = []
orderList (x:xs) dir
  | dir == "asc" =
      let smaller = [y | y <- xs, y <= x]
          larger  = [y | y <- xs, y >  x]
      in orderList smaller dir ++ [x] ++ orderList larger dir
  | dir == "des" =
      let smaller = [y | y <- xs, y >  x]
          larger  = [y | y <- xs, y <= x]
      in orderList smaller dir ++ [x] ++ orderList larger dir
  | otherwise = []

-- 13) 🔠 Case Change (ASCII only, no imports)
strToUpper :: String -> String
strToUpper str =
  [ if 'a' <= c && c <= 'z'
      then toEnum (fromEnum c - 32)
      else c
  | c <- str ]

strToLower :: String -> String
strToLower str =
  [ if 'A' <= c && c <= 'Z'
      then toEnum (fromEnum c + 32)
      else c
  | c <- str ]

-- 14) 🧼 String Cleanups
removeSpaces :: String -> String
removeSpaces str = [ y | y <- str
                       , y /= '\n' && y /= '\r' && y /= ' ' && y /= '\t' ]

removeVowels :: String -> String
removeVowels str = [ y | y <- str, y `notElem` "aeiou" ]

-- fixed: keep only alphabet letters (ASCII)
removeNoAlphabetLetters :: String -> String
removeNoAlphabetLetters str = [ y | y <- str
                                  , y `elem` (['a'..'z'] ++ ['A'..'Z']) ]

-- this actually "keeps only letters" (name suggests removal)
removeSymbols :: String -> String
removeSymbols str = [ y | y <- str
                        , y `elem` (['a'..'z'] ++ ['A'..'Z']) ]

-- keep only consonant letters (ASCII)
lettersOnly :: String -> String
lettersOnly str =
  [ y | y <- str
      , y `elem` (['a'..'z'] ++ ['A'..'Z'])
      , y `notElem` "aeiou" ]

-- 15) ✍️ Find & Replace (substring, non-overlapping)
findAndReplaceStrings :: String -> String -> String -> String
findAndReplaceStrings old new s
  | null old  = s
  | otherwise = go s
  where
    n = length old
    go [] = []
    go str
      | startsWith old str = new ++ go (drop n str)
      | otherwise          = head str : go (tail str)

startsWith :: String -> String -> Bool
startsWith []     _      = True
startsWith _      []     = False
startsWith (x:xs) (y:ys) = x == y && startsWith xs ys

-- 16) 🏆 Extremes
biggestItemList :: Ord a => [a] -> a
biggestItemList []     = error "biggestItemList: empty list"
biggestItemList [x]    = x
biggestItemList (x:xs) =
  let m = biggestItemList xs
  in if x >= m then x else m

smallestItemList :: Ord a => [a] -> a
smallestItemList []     = error "smallestItemList: empty list"
smallestItemList [x]    = x
smallestItemList (x:xs) =
  let m = smallestItemList xs
  in if x <= m then x else m

2) ✅ assert

  • 🧠 What: Tiny helper to check expected vs actual.

  • 🧪 Use:

    assert "reverse" [3,2,1] (reverseList [1,2,3])
main ::IO()
main = do
  print $ findElemList 3 [2,5,6]
  let l1 = [2,3,4]
  let l2 = [4,6,7,8,9,3,5,1,56,1]
  print $ joinList l1 l2 
  print $ assert "Test 3 functions head, last and body using [4,6,7,8,9]" [6,7,8] $ bodyElemList l2
  print $ addItemList 78 l1
  print $ reverseList l2
  print $ assert "List reversed " [9,8,7,6,4] $ reverseList [4,6,7,8,9]
  print $ lastElemList l2 
  print $ orderList l2 "des"
  print $ strToUpper "bernarD"
  print $ strToLower "THIS IS BIGG"
  print $ removeSpaces "this is nothing \r \n "
  print $ removeVowels "hellow world"
  print $ removeSymbols "Why? is th94375478484 .<>"
  let string = "the quick brown fox jumps over the lazy dog"
  print $ lettersOnly string
  -- print $ findAndReplace "the" "256" string
  print $ findAndReplaceStrings "the" "145" string 
  print $ findAndReplaceStrings "a" "@" string 
  

3) 📏 sizeOfList

  • Counts items recursively.

Why patterns?

  • [] has size 0.
  • [_] (one element) has size 1.
  • Otherwise, strip head and add 1.

4) 🔎 isItemInTheList, findPosItemList, findElemList

  • isItemInTheList → boolean membership.
  • findPosItemList → first index (Maybe Int).
  • findElemList → alias used by your original main.

5) 🪪 firstElemList

  • Safe head (returns Nothing on empty).

6) 🧰 filterItemsList, removeItemList

  • filterItemsList p xs keeps items where p x is True.
  • removeItemList v xs removes all v.

7) ➕ joinList

  • Concatenates two lists.

8) ✂️ notHeadList, notLastList, bodyElemList

  • Remove first, last, or both ends.

9) 🧱 addItemList

  • Prepend an element: O(1).

10) 🔁 reverseList

  • Classic recursive reverse.

11) 🎯 lastElemList

  • Safe last using Maybe.

12) 🗂️ orderList

  • Quicksort style; "asc" or "des".
  • Any other flag → [] (by your design).

13) 🔠 strToUpper, strToLower

  • ASCII-only transformations using fromEnum/toEnum.

14) 🧼 removeSpaces, removeVowels, removeNoAlphabetLetters, removeSymbols, lettersOnly

  • Fixes made:

    • removeNoAlphabetLetters: was using "a..Z" (not a range). Now uses ranges properly.
    • removeSymbols: name suggests “remove symbols”, and the implementation keeps only letters (so symbols are removed). Behavior preserved, just calling it out.

15) ✍️ findAndReplaceStrings, startsWith

  • Non-overlapping replacement (standard behavior).
  • Works for chars, words, digit groups—anything that’s a substring.

16) 🏆 biggestItemList, smallestItemList

  • Recursive extrema (throw error on empty list—same behavior you had).

17) 📚 Glossary

  • Eq / Ord: Typeclasses for equality / ordering.
  • Maybe: Optional type: Just x or Nothing.
  • Pattern Matching: Matching shapes like x:xs, [].
  • Guards: | condition = ... choosing branches by boolean tests.
  • List Comprehension: [y | y <- xs, predicate y].
  • Recursion: Function calling itself on smaller input.
  • Concatenation (++): Join lists end-to-end.

Awesome! Here’s a Practice Pack for CoxyHasList—with numbered exercises, tiny hints, and a solutions section you can reveal when ready. I kept everything compatible with your module (no imports). 🧩🚀


🧪 Practice Exercises for CoxyHasList

Table of Contents

  1. Warm-ups
  2. List Core
  3. Safe Access & Search
  4. List Transformations
  5. Sorting
  6. String Utilities
  7. Find & Replace
  8. Extremes
  9. Bonus Challenges
  10. Solutions

1) 🔥 Warm-ups

1.1 — Count elements Write countElems :: [a] -> Int using only sizeOfList.

Hint: It’s basically a wrapper.


1.2 — Is empty? Write isEmpty :: [a] -> Bool using pattern matching (no library calls).

Hint: [] vs (_:_).


2) 🧱 List Core

2.1 — Keep evens keepEvens :: [Int] -> [Int] using filterItemsList.

Hint: Use \n -> n mod 2 == 0.


2.2 — Remove zeros dropZeros :: [Int] -> [Int] using removeItemList.


2.3 — Concat three join3 :: [a] -> [a] -> [a] -> [a] using joinList only.


3) 🪪 Safe Access & Search

3.1 — First or default firstOr :: a -> [a] -> a If list is empty, return the default; else the first. Use firstElemList.


3.2 — Find index or -1 findIndexOrNeg1 :: Eq a => a -> [a] -> Int using findPosItemList.


3.3 — Contains all? containsAll :: Eq a => [a] -> [a] -> Bool Return True if every element of list b is found in list a. Use isItemInTheList.


4) ✂️ List Transformations

4.1 — Drop both ends safely middleOrEmpty :: [a] -> [a] using bodyElemList.


4.2 — Push front pushFrontMany :: [a] -> [a] -> [a] that prepends all items of the first list to the second using addItemList (call it repeatedly).


4.3 — Reverse twice rev2 :: [a] -> [a] using reverseList only (twice).


5) 🗂️ Sorting

5.1 — Sort asc sortAsc :: Ord a => [a] -> [a] using orderList.


5.2 — Sort desc sortDesc :: Ord a => [a] -> [a] using orderList.


5.3 — Top-3 largest top3 :: Ord a => [a] -> [a] Sort descending with orderList and keep first 3 (if available).


6) 🔠 String Utilities

6.1 — Title the first letter capitalizeFirst :: String -> String Uppercase the first character only using strToUpper + pattern matching.


6.2 — All caps, no spaces shout :: String -> String using strToUpper then removeSpaces.


6.3 — Only letters, lowercase lettersLower :: String -> String using removeSymbols then strToLower.


7) ✍️ Find & Replace

7.1 — Censor a word censor :: String -> String -> String Replace all occurrences of a word with "***" using findAndReplaceStrings.


7.2 — Replace digits with X maskDigits :: String -> String Replace "0".."9" by "X" by repeatedly calling findAndReplaceStrings.


8) 🏆 Extremes

8.1 — Safe max safeMax :: Ord a => [a] -> Maybe a Use biggestItemList for non-empty; otherwise Nothing.


8.2 — Safe min safeMin :: Ord a => [a] -> Maybe a Use smallestItemList for non-empty; otherwise Nothing.


9) 🧠 Bonus Challenges

9.1 — Unique (preserve firsts) unique :: Eq a => [a] -> [a] Keep first occurrences only. Use isItemInTheList + recursion.


9.2 — Anagram key anagramKey :: String -> String Sort letters ascending using orderList (works on Char).


9.3 — Word count (simple) wordCount :: String -> Int Assume words are separated by single spaces (no trim). Use recursion or split by scanning.



✅ Solutions

Tip: paste these below your module. They only use functions you already have.

-- 1.1
countElems :: [a] -> Int
countElems = sizeOfList

-- 1.2
isEmpty :: [a] -> Bool
isEmpty [] = True
isEmpty _  = False

-- 2.1
keepEvens :: [Int] -> [Int]
keepEvens xs = filterItemsList (\n -> n `mod` 2 == 0) xs

-- 2.2
dropZeros :: [Int] -> [Int]
dropZeros = removeItemList 0

-- 2.3
join3 :: [a] -> [a] -> [a] -> [a]
join3 a b c = joinList (joinList a b) c

-- 3.1
firstOr :: a -> [a] -> a
firstOr def xs =
  case firstElemList xs of
    Nothing -> def
    Just v  -> v

-- 3.2
findIndexOrNeg1 :: Eq a => a -> [a] -> Int
findIndexOrNeg1 x xs =
  case findPosItemList xs x of
    Nothing -> (-1)
    Just i  -> i

-- 3.3
containsAll :: Eq a => [a] -> [a] -> Bool
containsAll as []     = True
containsAll as (b:bs) = isItemInTheList b as && containsAll as bs

-- 4.1
middleOrEmpty :: [a] -> [a]
middleOrEmpty = bodyElemList

-- 4.2
pushFrontMany :: [a] -> [a] -> [a]
pushFrontMany [] acc     = acc
pushFrontMany (x:xs) acc = pushFrontMany xs (addItemList x acc)

-- 4.3
rev2 :: [a] -> [a]
rev2 = reverseList . reverseList

-- 5.1
sortAsc :: Ord a => [a] -> [a]
sortAsc xs = orderList xs "asc"

-- 5.2
sortDesc :: Ord a => [a] -> [a]
sortDesc xs = orderList xs "des"

-- 5.3
top3 :: Ord a => [a] -> [a]
top3 xs =
  let sorted = sortDesc xs
  in case sorted of
       (a:b:c:_) -> [a,b,c]
       _         -> sorted  -- if fewer than 3, return what we have

-- 6.1
capitalizeFirst :: String -> String
capitalizeFirst []     = []
capitalizeFirst (c:cs) = head (strToUpper [c]) : cs
-- explanation: strToUpper works on whole strings, so call it on [c]

-- 6.2
shout :: String -> String
shout = removeSpaces . strToUpper

-- 6.3
lettersLower :: String -> String
lettersLower = strToLower . removeSymbols

-- 7.1
censor :: String -> String -> String
censor word s = findAndReplaceStrings word "***" s

-- 7.2
maskDigits :: String -> String
maskDigits s =
  let step d acc = findAndReplaceStrings [d] "X" acc
  in  step '0' . step '1' . step '2' . step '3' . step '4'
    . step '5' . step '6' . step '7' . step '8' . step '9' $ s

-- 8.1
safeMax :: Ord a => [a] -> Maybe a
safeMax [] = Nothing
safeMax xs = Just (biggestItemList xs)

-- 8.2
safeMin :: Ord a => [a] -> Maybe a
safeMin [] = Nothing
safeMin xs = Just (smallestItemList xs)

-- 9.1
unique :: Eq a => [a] -> [a]
unique []     = []
unique (x:xs) =
  let rest = unique xs
  in if isItemInTheList x rest
        then rest
        else addItemList x rest

-- 9.2
anagramKey :: String -> String
anagramKey s = orderList s "asc"

-- 9.3 (simple; expects single-space-separated words)
wordCount :: String -> Int
wordCount []       = 0
wordCount (' ':xs) = 1 + wordCount (dropWhileSpace xs)
wordCount (_:xs)   = wordCount xs
  where
    dropWhileSpace []         = []
    dropWhileSpace (' ':rest) = dropWhileSpace rest
    dropWhileSpace r          = r

🧪 Quick Self-Checks (optional)

You can use your assert to sanity-check:

-- countElems / isEmpty
assert "countElems" 3 (countElems [1,2,3])
assert "isEmpty True" True (isEmpty [])
assert "isEmpty False" False (isEmpty [1])

-- keepEvens / dropZeros
assert "keepEvens" [2,4,6] (keepEvens [1,2,3,4,5,6])
assert "dropZeros" [1,2,3] (dropZeros [0,1,0,2,0,3])

-- join3
assert "join3" [1,2,3,4,5] (join3 [1] [2,3] [4,5])

-- firstOr / findIndexOrNeg1
assert "firstOr" 10 (firstOr 10 [])
assert "findIndexOrNeg1" 2 (findIndexOrNeg1 'c' "abcde")

-- pushFrontMany / rev2
assert "pushFrontMany" [3,2,1,4,5] (pushFrontMany [1,2,3] [4,5])
assert "rev2" [1,2,3] (rev2 [1,2,3])

-- sortAsc / sortDesc / top3
assert "sortAsc" [1,2,3] (sortAsc [3,1,2])
assert "sortDesc" [3,2,1] (sortDesc [3,1,2])
assert "top3" [9,8,7] (top3 [1,9,3,7,8,2])

-- strings
assert "capitalizeFirst" "Hello" (capitalizeFirst "hello")
assert "shout" "HELLOWORLD!" (shout "Hello World!")
assert "lettersLower" "abcxyz" (lettersLower "Abc-XYZ!")

-- replace / mask
assert "censor" "the *** and ***" (censor "cat" "the cat and cat")
assert "maskDigits" "abcXXdef" (maskDigits "abc12def")

-- extremes
assert "safeMax" (Just 9) (safeMax [1,9,2])
assert "safeMin" (Just 1) (safeMin [1,9,2])

-- unique / anagramKey / wordCount
assert "unique" [3,2,1] (unique [1,2,3,2,1] |> sortDesc)
  where (|>) = flip ($)
assert "anagramKey" "aelpp" (anagramKey "apple")
assert "wordCount" 3 (wordCount "one two three")
-- simple crud demo with Cart
module Main where

type Id   = String
type Name = String
type Mark = Int

data Item = Item (Id, Name, Mark) deriving (Show)

data Cart = Cart
  { unCart :: [Item]
  } deriving (Show)

createItem :: Id -> Name -> Mark -> Maybe Item
createItem a b c
  | a == "" || b == "" || c < 0 || c > 100 = Nothing
  | otherwise                              = Just (Item (a, b, c))

emptyCart :: Cart
emptyCart = Cart []

createCart :: Maybe Item -> Cart -> Cart
createCart Nothing  c        = c
createCart (Just a) (Cart c) = Cart (a : c)

updateCart :: Id -> Maybe Item -> Cart -> Cart
updateCart _      Nothing                 cart         = cart
updateCart _ _ (Cart [])                               = Cart []
updateCart cartId (Just newItemToReplace) (Cart items) =
  Cart [if i == cartId then newItemToReplace else y | y@(Item (i,_,_)) <- items]

filterCart :: Id -> Cart -> Cart
filterCart _ (Cart [])           = Cart []
filterCart targetId (Cart items) =
  Cart([y|y@(Item(i,_,_)) <- items, i /= targetId])

displayItem :: Id -> Cart -> String
displayItem _ (Cart []) = "Cart is empty"
displayItem i_ (Cart (Item(i,n,m):xs)) = 
  if i_ == i 
  then 
   "Item id : "++ i ++", name : "++n++", mark : "++ show m
  else
   displayItem i_ (Cart xs)

main :: IO ()
main = do
  let s1 = createItem "001" "james"   25
  let s2 = createItem "002" "mary"    50
  let s3 = createItem "003" "ruth"    22
  let s4 = createItem "004" "john"    29
  let s5 = createItem "003" "gilbert" 55

  let c1 = createCart s1 emptyCart
  let c2 = createCart s2 c1
  let c3 = createCart s3 c2
  let c4 = createCart s4 c3
  print c4

  let c4Update = updateCart "003" s5 c4
  print c4Update

  -- Example: filter out all with Id "002"
  let c4Filtered = filterCart "002" c4Update
  print c3
  print $ displayItem "002" c3

Clone this wiki locally