{-
File: basics.hs
Author: Max Levatich
-}

re :: [Char] -> [Char]
re s = 'r' : 'e' : s

-- factorial 5 = 5 * 4 * 3 * 2 * 1 = 120
factorial :: Int -> Int
factorial x
    | x == 1    = 1
    | x < 1     = 0
    | otherwise = x * factorial (pred x)

-- doubleList [1,2,3] = [2,4,6]
doubleList :: [Int] -> [Int]
doubleList []     = []
doubleList (x:xs) = double x : (doubleList xs)
    where
        double n = n * 2

-- length' [1,2,2] = 3
length' :: [a] -> Int
length' [] = 0
length' (_:xs) = 1 + length' xs

-- sum' [5, 6, 7] = 18
sum' :: [Int] -> Int
sum' []     = 0
sum' (x:xs) = x + sum' xs

-- doubleList = map ((*) 2)
map' :: (a -> b) -> [a] -> [b]
map' _ []     = []
map' f (x:xs) = f x : map' f xs

filter' :: (a -> Bool) -> [a] -> [a]
filter' _ [] = []
filter' f (x:xs) =
    case f x of
        True  -> x : filter' f xs
        False ->     filter' f xs

-- sum = fold' (+) 0
fold' :: (a -> b -> b) -> b -> [a] -> b
fold' _ start []     = start
fold' f start (x:xs) = f x (fold' f start xs)

-- negate' [1,2,3] = [-1, -2, -3]
negate' :: [Int] -> [Int]
negate' = map ((*) (-1))

-- mkSentence ["hello", "hi", "I'm", "Max"] = "hello hi I'm Max"
mkSentence :: [[Char]] -> [Char]
mkSentence = fold' smushFunc ""
    where
        smushFunc :: [Char] -> [Char] -> [Char]
        smushFunc ls1 []  = ls1
        smushFunc ls1 ls2 = ls1 ++ ' ' : ls2

-- firstLetterOfEach ["hello", "hi", "I'm", "Max"] = ['h', 'h', 'I', 'M'] = "hhIM"
-- firstLetterOfEach ["hello", "", "hi"] = "hh"
firstLetterOfEach :: [[Char]] -> [Char]
firstLetterOfEach wrds =
    map getFirstLetter $ filterNotEmpty wrds
    -- ALTERNATIVE: fold (\x y -> getFirstLetter x : y) "" $ filterNotEmpty words
    where
        filterNotEmpty ls = filter ((/=) "") ls
        getFirstLetter ls = head ls 

-- More concise version...
firstLetterOfEach' :: [[Char]] -> [Char]
firstLetterOfEach' = map head . filter ((/=) "")

-- Tuples
myTuple :: (Int, Char, Bool)
myTuple = (5, 'a', True)

-- getThird myTuple = True
getThird :: (a, b, c) -> c
getThird (_, _, x) = x

-- Zip
-- fullName ["max", "stephen"] ["levatich", "edwards"] = [("max", "levatich"), ("stephen", "edwards")]
fullName :: [[Char]] -> [[Char]] -> [([Char], [Char])]
fullName firstnames lastnames = zip firstnames lastnames
-- concise: fullName = zip

-- ...Or combine the strings using zipWith
-- fullName' ["max", "stephen"] ["levatich", "edwards"] = ["max levatich","stephen edwards"]
fullName' :: [[Char]] -> [[Char]] -> [[Char]]
fullName' firstnames lastnames = zipWith (\s1 s2 -> s1 ++ ' ' : s2) firstnames lastnames
-- concise: fullName' = zipWith (\s1 s2 -> s1 ++ ' ' : s2)

-- List comprehensions: compute pythagorian triples
-- pythagorean 20 = [(3,4,5),(5,12,13),(6,8,10),(8,15,17),(9,12,15),(12,16,20)]
pythagorean :: Int -> [(Int, Int, Int)]
pythagorean n = [ (a, b, c) | a <- [1..n], b <- [a..n], c <- [b..n], a^(2 :: Int) + b^(2 :: Int) == c^(2 :: Int) ]

-- Cartesian product of CS research jargon
jargon :: [String]
jargon = [ adj ++ " " ++ noun | adj <- ["Deep","Practical","Large Language","Generative"],
                                noun <- ["Learning","AI"] ]

-- The sieve of Erastothenes: enumerate all prime numbers!
-- Read about algorithm here: https://en.wikipedia.org/wiki/Sieve_of_Eratosthenes
-- take 50 primes = [2,3,5,7,11,13,17,19,23,29,31,37,41,43,47,53,59,61,67,71,73,79,83,89,97,101,103,107,109,113,127,131,137,139,149,151,157,163,167,173,179,181,191,193,197,199,211,223,227,229]

-- Work-in-progress version
sieve :: [Int]
sieve = 
    let
        filterOutDivisibleBy x nums = filter (\n -> n `mod` x /= 0) nums
        -- step1 = 2 : (filterOutDivisibleBy 2 [3..150])
        -- step2 = 2 : 3 : (filterOutDivisibleBy 3 (filterOutDivisibleBy 2 [3..150]))
        recursiveFilter [] = []
        recursiveFilter (x:xs) = x : (recursiveFilter $ filterOutDivisibleBy x xs)
    in
    recursiveFilter [2..]

-- Concise
sieve' :: [Int]
sieve' = filterPrimes [2..]
    where
        -- Alternate: A list comprehension: filterPrimes (p:xs) = p : [ x | x <- xs, x `mod` p /= 0 ]
        filterPrimes (x:xs) = x : (filterPrimes $ filter (\n -> n `mod` x /= 0) xs)
        filterPrimes _ = []
        