<jupyter_start><jupyter_text>Tuple, List, Type, Pattern Matching *Some examples in this notebook are taken from BLG458E Functional Programming course slides which can be accessed through the following link: https://www.slideshare.net/uyar/tag/blg458e* TupleTuples can contain different types of data, however size of the tuple is fixed.<jupyter_code>dummy_tuple :: (Integer, Float, Integer, Bool)
dummy_tuple = (3, 56.2, 8, True)<jupyter_output><empty_output><jupyter_text>If the tuple is a pair (meaning that it has exactly 2 elements), we can use *fst* and *snd* functions.  * **fst** returns the first element of the tuple* **snd** returns the second element<jupyter_code>hometown :: (String, Integer)
hometown = ("Sivas", 58)
fst hometown
snd hometown<jupyter_output><empty_output><jupyter_text>Passing tuples to functionsTuples can be sent as parameters to functions and also be returned from functions.  *Note: passing a tuple as parameter is NOT equal to passing multiple elements.*<jupyter_code>gcd :: (Integer, Integer) -> Integer
gcd x = if snd x == 0 then fst x else gcd (snd x, fst x `mod` snd x)  
gcd (5,10)
gcd (3, 7)<jupyter_output><empty_output><jupyter_text>Returning tuples from functions**Simplification function**:  Given a tuple (a,b), we simplify it using the following equation:$$ (a,b) = \left(\frac{a}{gcd(a,b)}, \frac{b}{gcd(a,b)}\right)$$<jupyter_code>simplify :: (Integer, Integer) -> (Integer, Integer)
simplify (a, b) = (a `div` g, b `div` g)
  where
    g = gcd (a,b)
simplify (9, 15)<jupyter_output><empty_output><jupyter_text>Type SynonymWe can give a new name to an existing type (like typedef in C)  *Format*: `type newName = existingType`<jupyter_code>type Fraction = (Integer, Integer)
simplify2 :: Fraction -> Fraction
simplify2 (x,y) = (x `div` g, y `div` g)
  where
    g = gcd (x,y)
simplify2 (2,18)<jupyter_output><empty_output><jupyter_text>ListLists can contain an arbitrary number of values, however all elements must be of the same type.<jupyter_code>li :: [Int]
li = [34, 58, 23, 87, 356, 41]
li<jupyter_output><empty_output><jupyter_text>RangesYou can use ranges to define lists.  [a .. b] = [a, a+1, a+2, ... , b]  [a, p .. n] = [a, p, 2p-a, 3p-a, ... , n]<jupyter_code>[5 .. 15]
[1, 3 .. 19]
['m' .. 'z']
type Term = (Float, Int)
poly :: [Term]
poly = [(34.7, 3), (743.8, 2)]
poly<jupyter_output><empty_output><jupyter_text>Useful list functions**head**: returns the first element of a list  **tail**: returns all the elements except the first one  **last**: returns the last element of a list  **init**: returns all the elements except the last one  **null**: checks if the list is empty  **elem**: checks if an element is a member of a list  (usage: elem 'a' "Mustafa")  **reverse**: returns the list in reverse order  **replicate**: creates a list containing an item repeated x times (usage: replicate x 'c')  **take**: return the first n elements of the list (usage: take x li)  **drop**: drop n elements from the front of the list (usage: drop x li)    **splitAt**: split the elements at a given index (usage: splitAt x li)    **concat**: flattens a list  **zip**: zip [1, 2] "ab" ~> [(1, ’a’), (2, ’b’)]<jupyter_code>li
head li
tail li
last li
init li
null li
let li2 = []
null li2
elem 'a' "Mustafa"
elem 'b' "Mustafa"
elem 'A' "Mustafa"
replicate 15 'a'
concat [[1,2], [], [4]]
zip [1,2,3,4,5] [9,8,7,6,5]<jupyter_output><empty_output><jupyter_text>Index Function (!!)* returns the element in the given index<jupyter_code>li3 :: [Float]
li3 = [3.5, 6.32, 9.83]

li3 !! 2  -- returns the element in index 2<jupyter_output><empty_output><jupyter_text>Append Function (:)* takes an item and a list* adds the item to the list.* a right-associative function* equivalent to append() function in Python<jupyter_code>my_list :: [Int]
my_list = [1,9,4]
my_list
5 : my_list  -- not the same as my_list : 5 which would give an error<jupyter_output><empty_output><jupyter_text>You can also append to an empty list<jupyter_code>1453 : 1773 : []<jupyter_output><empty_output><jupyter_text>List Concatenation (++)* takes two lists* returns a list which is a concatenation of the given lists<jupyter_code>li_x :: [Int]
li_x = [1,2,3]

li_y :: [Int]
li_y = [9,8,7]

li_x ++ li_y<jupyter_output><empty_output><jupyter_text>Length Function* returns the length of a list* equivalent to **len()** in Python<jupyter_code>length li_x<jupyter_output><empty_output><jupyter_text>Let's write a function that returns the sum of the first and second elements in a list.<jupyter_code>firstAndSecond :: [Int] -> Int
firstAndSecond xs 
  | length xs < 2  = error "list size is less than 2"
  | otherwise      = xs !! 0 + xs !! 1<jupyter_output><empty_output><jupyter_text>StringA string is nothing but a list of characters, therefore you can use any list function on strings.<jupyter_code>name :: String
name = "Mustafa"
head name
tail name
null name
length name
name !! 3
name ++ "Coban"
'?' : name<jupyter_output><empty_output><jupyter_text>Algebraic Data TypesInstead of using types like Int, Float, etc. we can introduce our own data types.   For example, Bool type can be defined as `data Bool = False | True`.  Here the **type** is Bool and the **value constructors** are True and False. These two are the values that a Bool can have.   1-) Enumerated TypesThis is just like **enum** in programming languages such as C or Java. For example, we can introduce a new type for seasons that can only have 4 different values: Winter, Spring, Summer, and Fall as below.  ```haskell data Season = Winter | Spring | Summer | Fall```<jupyter_code>data Season = Winter | Spring | Summer | Fall
              deriving Show  
              
-- we put this "deriving Show" thing in order to be able to print these values
 
-- now we have a type called Season
-- let's create a variable of type Season

currentSeason :: Season
currentSeason = Winter
currentSeason  

-- if you don't include "deriving Show" in the type definition,
-- this statement raises an error<jupyter_output><empty_output><jupyter_text>2-) Product TypesInstead of using tuples to represent entities, we can use product types.  At the beginning of this notebook, in order to represent my hometown, I used the following notation:  ```haskellhometown :: (String, Integer)hometown = ("Sivas", 58)```Instead of using a tuple, I can represent this using a product type as below.```haskelldata City = City String Integer```So as to construct an element of type Hometown, a string and an Integer must be supplied.  As you may guess, this is a *Constructor* just like what we have in OOP languages. There can be more than one constructor as well.*Note: Constructor name can be the same as or different from the type name*<jupyter_code>data City = City String Integer
                deriving Show

hometown :: City
hometown = City "Sivas" 58

hometown
best_city :: City
best_city = City "Istanbul" 34

best_city<jupyter_output><empty_output><jupyter_text>Let's also see an example where we have more than one constructor.<jupyter_code>data Shape = Point (Float, Float)         -- float1: x coordinate, float2: y coordinate 
           | Circle (Float, Float) Float  -- float3: radius
           | Rectangle (Float, Float) Float Float -- float3: height, float4: width
             deriving Show

point_1 :: Shape
point_1 = Point (3.2, 5.4)

point_1
circle_1 :: Shape
circle_1 = Circle (4.3,5.6) 34.3<jupyter_output><empty_output><jupyter_text>3-) Record TypesIn product types, we can write names of the fields in the constructors, which makes it more readable.  Another advantage is that all so-called *getter* functions are automatically created for the attributes.<jupyter_code>data Person = Student {name :: String, 
                       school :: String,
                       gpa :: Float}
              deriving Show
              
me :: Person
me = Student {name="Mustafa", school="ITU", gpa=3.99}
me
gpa me
school me<jupyter_output><empty_output><jupyter_text>Pattern Matching* We can check expressions against patterns* The first matched pattern is used.Format:  **case** *exp* **of**  &nbsp;&nbsp;&nbsp;&nbsp;pattern1 -> expression1  &nbsp;&nbsp;&nbsp;&nbsp;pattern2 -> expression2  &nbsp;&nbsp;&nbsp;&nbsp;...  &nbsp;&nbsp;&nbsp;&nbsp;_ -> eHere we compare exp and patterns, as soon as we one of the patterns matches, we evaluate the expression of the matched pattern.When patterns are matched, name bindings are created.*Note*: _ pattern (aka wildcard) at the end matches any expression (just like *otherwise* guards), so if no pattern is matched, this last expression is evaluated.<jupyter_code>-- Literal value pattern
my_gcd :: Int -> Int -> Int
my_gcd x y = case y of
  0 -> x                    -- if y == 0, then return x
  _ -> my_gcd y (x `mod` y)    -- in any other case, evaluate this line
  
my_gcd 10 4
-- Tuple pattern
gcd' :: (Int, Int) -> Int
gcd' a = case a of 
  (x, 0) -> x
  (x, y) -> gcd'(y, x `mod` y)

gcd' (10, 4)
-- nested patterns

shift :: ((Int, Int), Int) -> (Int, (Int, Int))
shift a = case a of
  ((x,y), z) -> (x, (y,z))

shift ((3,5),2)
-- when you don't need a binding, use wildcard notation "_"

third :: (Int, Int, Int) -> Int
third a = case a of
  (_, _, t) -> t
  
third (3,5,6)<jupyter_output><empty_output><jupyter_text>List Patterns* empty list = [ ]* nonempty list = x:xs* list with exactly one element = [x]* list with exactly two elements = [x1, x2]* list with at least two elements = x1:x2:xs<jupyter_code>len :: [a] -> Int
len x = case x of
  []   -> 0
  x':xs -> 1 + len xs

len [2,4,7]
firstPlusThird :: [Int] -> Int
firstPlusThird a = case a of
  []        -> 0
  [x1]      -> x1
  [x1, _]   -> x1
  x1:_:x3:_ -> x1 + x3

firstPlusThird [7,3,9,1,5,6]
checkNondecreasing :: [Int] -> Bool
checkNondecreasing a = case a of 
  []       -> error "empty list"
  [_]      -> True
  x1:x2:xs -> x2 >= x1 && checkNondecreasing (x2:xs)  -- this creates a new list
checkNondecreasing [2,5,5,7]
checkNondecreasing [2,5,5,3]<jupyter_output><empty_output><jupyter_text>In the checkNondecreasing function, (x2:xs) creates a new list and then passes it to the next function.  If you don't need to create a new list, you can use '@' as below.<jupyter_code>checkNondecreasing2 :: [Int] -> Bool
checkNondecreasing2 a = case a of
  []       -> error "empty list"
  [_]      -> True
  x1:xs@(x2:_)-> x2 >= x1 && checkNondecreasing xs<jupyter_output><empty_output><jupyter_text>Pattern matching can also be used to get the values in the algebraic types.<jupyter_code>data Person = Person String Integer
              deriving Show

getName :: Person -> String
getName p = case p of
  Person name _ -> name
  
him :: Person
him = Person "Bob" 1958

getName him<jupyter_output><empty_output><jupyter_text>Parameter pattern matching can also be used as follows.<jupyter_code>-- gcd with 2 integer parameters

gcd2 :: Integer -> Integer -> Integer
gcd2 x 0 = x
gcd2 x y = gcd2 y (x `mod` y)

-- gcd with a tuple parameter
gcdT :: (Integer, Integer) -> Integer
gcdT (x, 0) = x
gcdT (x, y) = gcdT (y, (x `mod` y))


gcd2 10 4
gcdT (10,4)<jupyter_output><empty_output><jupyter_text>**Exercise: write your own zip3 function**<jupyter_code>zip33 :: [a] -> [b] -> [c] -> [(a,b,c)]
zip33 [] [] [] = []
zip33 (x:xs) (y:ys) (z:zs) = (x,y,z) : zip33 xs ys zs
zip33 [1,2,3] [10,20,30] [100,200,300]<jupyter_output><empty_output><jupyter_text>**Exercise: write your own unzip function**<jupyter_code>my_unzip :: [(a,b)] -> ([a], [b])
my_unzip []     = ([], [])
my_unzip ((x,y):xys) = (x : xs, y : ys)
  where
    (xs, ys) = my_unzip xys 
merg :: [Int] -> [Int] -> [Int]
merg [] [] = []
merg (x:xs) [] = (x:xs)
merg [] (y:ys) = (y:ys)
merg (x:xs) (y:ys) = if x <= y then x : merg xs (y:ys) else y : merg (x:xs) ys
merg [1,3,4,5] [2,6,8,9]<jupyter_output><empty_output>
{'fraction_non_alphanumeric': 0.12919391828760723, 'fraction_numerical': 0.023103711883122397, 'mean_word_length': 4.145979020979021, 'pattern_counts': {'":': 0, '<': 107, '<?xml version=': 0, '>': 153, 'https://': 1, 'lorem ipsum': 0, 'www.': 1, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'chain_length': '26', 'dir': 'jupyter-structured-clean-dedup', 'id': '537645', 'license': 'MIT', 'n_tokens_mistral': 4204, 'n_tokens_neox': 3978, 'n_words': 1481, 'path': '3pattern_matching.ipynb', 'repo_name': 'mustafacobann/Functional-Programming', 'size': '11773'}