Useful Custom Functions/ Iterators and Operators

Table of Contents

  • Index
  • Repository
  • 1 Useful Custom Functions/ Iterators and Operators

    This is a collection of useful short code snippets.

    1.1 Pipelining Operators

    Haskell doesn't have a native Pipe operator like F# (F-Sharp) does, however it can be defined by the user.

    > let (|>) x f = f x
    > 
    > let (|>>) x f = map f x
    
    > let (?>>) x f = filter f x
    
    
    > take 3 (reverse (filter even [1..10]))
    [10,8,6]
    
    > [1..10] |> filter even |> reverse |> take 3
    [10,8,6]
    > 
    
    
    > [1..10] |>> (^2) |>> (/10) |>> (+100)
    [100.1,100.4,100.9,101.6,102.5,103.6,104.9,106.4,108.1,110.0]
    
    > 
    > [1..10] ?>> even
    [2,4,6,8,10]
    > 
    > [1..10] ?>> even |>> (+1)
    [3,5,7,9,11]
    > 
    >
    

    1.1.1 Iterators

    1.1.2 Pair Iterator

    Definition:

    pairs alist = zip alist (tail alist)
    

    The pairs iterator converts a list of elements to a new list of consecutive elements tuple.

    Pseudo code:

    pairs [e0, e1, e2, e3, e4 ...] ==> [(e0, e1), (e1, e2), (e3, e4) ...]
    

    Let f be a function of two arguments:

    f :: a -> a -> b
    

    The function f can be applied to to the pairs sequence using the higher order function uncurry.

    Pseudo code:

    > g = uncurry(f) :: (a, a) -> b
    > g (x, y) = f x y
    
    > map uncurry(f) $ pairs [e0, e1, e2, e3, e4 ...]
    >   [g (e0, e1), g (e1, e2), g (e2, e3), g (e3, e4) ...]
    

    It can be useful to calculate the distance between two points, lagged difference, growth of a time series, draw a line between each two consecutive points or apply any function to two consecutive elements.

    Example: Grouping Consecutive numbers

    > let pairs alist = zip alist (tail alist)
    
    > pairs [1..5]
    [(1,2),(2,3),(3,4),(4,5)]
    

    Example: Lagged Difference

    Pseudo code:

    lagdiff [e0, e1, e2, e3, e4 ...] ==> [(e1 - e0), (e2 - e1), (e3 - e2) ... ]
    

    Development:

    > let pairs alist = zip alist (tail alist)
    
    > :t pairs
    pairs :: [b] -> [(b, b)]
    
    > :t (-)
    (-) :: Num a => a -> a -> a
    
    > :t uncurry(-)
    uncurry(-) :: Num c => (c, c) -> c
    > 
    
    > (-) 20 10
    10
    > 
    
    > uncurry(-) (20, 10)
    10
    > 
    
    > uncurry(-) (10, 20)
    -10
    > 
    
    > uncurry(flip (-)) (10, 20)
    10
    > 
    
    > pairs [10.3, 20.5, 5.6, 8.23, 40.3]
    [(10.3,20.5),(20.5,5.6),(5.6,8.23),(8.23,40.3)]
    > 
    
    > map (uncurry ( flip (-))) $ pairs [10.3, 20.5, 5.6, 8.23, 40.3]
    [10.2,-14.9,2.630000000000001,32.06999999999999]
    > 
    
    > let lagdiff series = map (uncurry ( flip (-))) $ pairs series
    > :t lagdiff 
    lagdiff :: Num b => [b] -> [b]
    > 
    
    > lagdiff [10.3, 20.5, 5.6, 8.23, 40.3]
    [10.2,-14.9,2.630000000000001,32.06999999999999]
    > 
    
    > lagdiff [10, 30, 5, 8, 100]
    [20,-25,3,92]
    >
    

    Example: Distance between points on the plane.

    > let pairs alist = zip alist (tail alist)
    
    {- [(X, Y)]  coordinates of points in a plane -}
    > let points = [(1.0, 2.0), (3.0, 4.0), (-1.0, 5.0), (6.0, 6.0)]
    > let distance (x1, y1) (x2, y2) = sqrt( (x2-x1)^2 + (y2-y1)^2 )
    
    > let lines = pairs points 
    > lines
    [((1.0,2.0),(3.0,4.0)),((3.0,4.0),(-1.0,5.0)),((-1.0,5.0),(6.0,6.0))]
    > 
    
    > distance (1.0, 2.0) (3.0, 4.0)
    2.8284271247461903
    > 
    
    {- Calculate the length of each line segment -}
    
    > map (uncurry(distance)) lines
    [2.8284271247461903,4.123105625617661,7.0710678118654755]
    > 
    
    > sum $ map (uncurry(distance)) lines
    14.022600562229327
    > 
    
    > let totalLength points  =  sum $  map (uncurry(distance)) $ pairs (points)
    > 
    > totalLength points 
    14.022600562229327
    >
    

    1.1.3 Triples Iterator

    Definition:

    triples alist = zip3 alist (tail alist) (tail $ tail alist)
    

    Example:

    > triples [1..10]
    [(1,2,3),(2,3,4),(3,4,5),(4,5,6),(5,6,7),(6,7,8),(7,8,9),(8,9,10)]
    > 
    
    > :t triples 
    triples :: [c] -> [(c, c, c)]
    > 
    >
    

    1.1.4 Sliding Window Iterator

    This iterator is used in Scala and it is a generalized pairs iterator.

    Definition:

    sliding n alist = map (take n) (take (length(alist) -n + 1 ) $ iterate tail alist)
    

    Example:

    >  sliding 3 [1..10]
    [[1,2,3],[2,3,4],[3,4,5],[4,5,6],[5,6,7],[6,7,8],[7,8,9],[8,9,10]]
    
    >  sliding 4 [1..10]
    [[1,2,3,4],[2,3,4,5],[3,4,5,6],[4,5,6,7],[5,6,7,8],[6,7,8,9],[7,8,9,10]]
    
    > sliding 5 [1..10]
    [[1,2,3,4,5],[2,3,4,5,6],[3,4,5,6,7],[4,5,6,7,8],[5,6,7,8,9],[6,7,8,9,10]]
    
    > sliding 6 [1..10]
    [[1,2,3,4,5,6],[2,3,4,5,6,7],[3,4,5,6,7,8],[4,5,6,7,8,9],[5,6,7,8,9,10]]
    
    > sliding 9 [1..10]
    [[1,2,3,4,5,6,7,8,9],[2,3,4,5,6,7,8,9,10]]
    >
    

    Scala Equivalent

    scala> (1 to 5).iterator.sliding(3).toList
    res2: List[Seq[Int]] = List(List(1, 2, 3), List(2, 3, 4), List(3, 4, 5))
    

    1.1.5 Enumerate Iterator

    Equivalent to python enumerate.

    Definition:

    enumerate :: [b] -> [(Int, b)]
    enumerate alist = zip [0..(length(alist)-1)] alist
    

    Example:

    > enumerate ['a', 'b', 'c', 'd', 'e', 'f']
    [(0,'a'),(1,'b'),(2,'c'),(3,'d'),(4,'e'),(5,'f')]
    
    > take 8  (enumerate ['a'..'z'])
    [(0,'a'),(1,'b'),(2,'c'),(3,'d'),(4,'e'),(5,'f'),(6,'g'),(7,'h')]
    >
    

    Group by length

    Definition:

    groupByLen n alist = filter (\a -> length(a) == n  ) ( map f indexes )
        where
        len = length(alist)
        indexes = map (\i -> n*i) [0..(div len n)]
        f idx = take n (drop idx alist)
    

    Example:

    > groupByLen 3 [1..15]
    [[1,2,3],[4,5,6],[7,8,9],[10,11,12],[13,14,15]]
    > 
    > groupByLen 5 [1..15]
    [[1,2,3,4,5],[6,7,8,9,10],[11,12,13,14,15]]
    > 
    > groupByLen 6 [0..20]
    [[0,1,2,3,4,5],[6,7,8,9,10,11],[12,13,14,15,16,17]]
    > 
    > groupByLen 3 ['a'..'z']
    ["abc","def","ghi","jkl","mno","pqr","stu","vwx"]
    >
    

    1.2 Applying Multiples Functions

    1.2.1 Applying a list of functions to the same argument.

    juxt is a function that allows apply a list of functions of same type signature to a single argument. This is useful for numerical analysis, statistics and engineering. This function was taken from the Clojure library.

    juxt fs x = map ($ x) fs
    

    Example:

    > let juxt fs x = map ($ x) fs
    
    > juxt [(*3), (+4), (/10)] 30
    [90.0,34.0,3.0]
    > 
    > let fs = juxt [(*3), (+4), (/10)]
    > 
    > :t fs
    fs :: Double -> [Double]
    >
    > fs 30
    [90.0,34.0,3.0]
    > fs 40
    [120.0,44.0,4.0]
    > 
    > map fs [10, 20, 30]
    [[30.0,14.0,1.0],[60.0,24.0,2.0],[90.0,34.0,3.0]]
    >
    

    1.2.2 Applying a tuple of functions to a same argument.

    The family of functions juxt2, juxt3, juxt4 allow apply tuples of functions to a single argument. This is necessary when the functions don't have the same type signature.

    juxt2 (f1, f2) x = (f1 x, f2 x)
    juxt3 (f1, f2, f3) x = (f1 x, f2 x, f3 x)
    juxt4 (f1, f2, f3, f4) x = (f1 x, f2 x, f3 x, f4 x)
    juxt5 (f1, f2, f3, f4, f5) x = (f1 x, f2 x, f3 x, f4 x, f5 x)
    

    Example:

    {- 
    
    This function fails in static typed language when 
    the functions don't have the same type signature 
    
    -}
    > juxt [(>100), (+100)]  30
    
    <interactive>:36:9:
        No instance for (Num Bool) arising from the literal `100'
        Possible fix: add an instance declaration for (Num Bool)
        In the second argument of `(>)', namely `100'
        In the expression: (> 100)
        In the first argument of `juxt', namely `[(> 100), (+ 100)]'
    
    
    > juxt2 ((>100), (+100))  30
    (False,130)
    > 
    > 
    > let f = juxt2 ((>100), (+100))
    > :t f
    f :: Integer -> (Bool, Integer)
    > 
    > f 30
    (False,130)
    >
    > map f [10, 20, 25, 30, 100, 150]
    [(False,110),(False,120),(False,125),(False,130),(False,200),(True,250)]
    > 
    
    
    > 
    > juxt3 (length, maximum, minimum)  [100.23, 23.23, 12.33, -123.23, -1000.23, 4000.5]
    (6,4000.5,-1000.23)
    > 
    
    
    {- 
    
        The type system fails to resolve the types in someone
        cases. So when it happens the developer must make the
        function types explicit.
    
    -}
    > let analytics = juxt3 (length, maximum, minimum)
    > 
    > :t analytics 
    analytics :: [()] -> (Int, (), ())
    > 
    > analytics [100.23, 23.23, 12.33, -123.23, -1000.23, 4000.5]
    
    <interactive>:79:12:
        No instance for (Fractional ()) arising from the literal `100.23'
        Possible fix: add an instance declaration for (Fractional ())
        In the expression: 100.23
        In the first argument of `analytics', namely
          `[100.23, 23.23, 12.33, - 123.23, ....]'
        In the expression: analytics [100.23, 23.23, 12.33, - 123.23, ....]
    
    > let analytics :: [Double] -> (Int, Double, Double)  ; analytics = juxt3 (length, maximum, minimum)
    > 
    > :t analytics 
    analytics :: [Double] -> (Int, Double, Double)
    > 
    
    > analytics [100.23, 23.23, 12.33, -123.23, -1000.23, 4000.5]
    (6,4000.5,-1000.23)
    > 
    
    > import Data.Char
    > 
    > let f = juxt3 (succ, pred, ord)
    > :t f
    f :: Char -> (Char, Char, Int)
    > 
    > let a = map f "haskell is fun"
    > a
    [('i','g',104),('b','`',97),('t','r',115),('l','j',107),('f','d',101),('m','k',108),('m','k',108),('!','\US',32),('j','h',105),('t','r',115),('!','\US',32),('g','e',102),('v','t',117),('o','m',110)]
    > 
    > unzip3 a
    ("ibtlfmm!jt!gvo","g`rjdkk\UShr\USetm",[104,97,115,107,101,108,108,32,105,115,32,102,117,110])
    > 
    >
    

    1.2.3 Control Flow Functions

    between

    between a b x = a <= x && x <= b
    
    > filter (between 10 20) [23, 5, 8, 17, 24, 13, 12]
    [17,13,12]
    > 
    
    > filter (not . between 10 20) [23, 5, 8, 17, 24, 13, 12]
    [23,5,8,24]
    

    ifelseDo

    Pseudo Code:

    f(x) = if pred(x) == True then: fa(x) else fb(x)
    
    ifelseDo pred fa fb  x  =  if pred x then fa x else fb x
    

    Example: Apply the list the function (if x >10 then 10*x else x+5)

    > let  ifelseDo pred fa fb  x  =  if pred x then fa x else fb x
    
    > map (ifelseDo (>10) (*4) (+5)) [-1, 2, 7, 8, 9, 10, 20, 30, 50]
    [4,7,12,13,14,15,80,120,200]
    
    > let f = ifelseDo (>10) (*4) (+5)
    
    > f 1
    6
    > f 3
    8
    > f 5
    10
    > 
    
    > f 20
    80
    > f 30
    120
    
    λ> map f [-1, 2, 7, 8, 9, 10, 20, 30, 50]
    [4,7,12,13,14,15,80,120,200]
    

    ifelse*

    Pseudo Code:

    f(x) = if pred(x) == True then: a else b
    
    ifelse   pred a  b   x  =  if pred x then a   else b
    

    Example:

    If x<0 set x to -1 else set to 1

    > let f  = ifelse (<0) (-1) 1
    > 
    > f 2
    1
    > f 40
    1
    > f (-1)
    -1
    > f (-10)
    -1
    
    > map f [-1, -2, 3, 4, -5, -6, 0, 2]
    [-1,-1,1,1,-1,-1,1,1]
    >
    

    ifelseEq

    Pseudo Code:

    f(x) = if pred(x) == True then: a else x
    
    ifelseEq pred a      x  =  if pred x then a   else x
    

    Example: if(x) > 10 then 30 else x

    > let ifelseEq pred a x  =  if pred x then a   else x
    
    > let f = ifelseEq (>10) 30
    
    > map f [1, 2, 20, 10, 9, 8, 100]
    [1,2,30,10,9,8,30]
    >
    

    Author: nobody

    Created: 2018-05-07 Mon 10:12

    Emacs 25.3.1 (Org mode 8.2.10)

    Validate