Search code examples
listhaskellalgebraic-data-typescustom-function

How can you create a list comprising of calculations on each item of another list in Haskell?


I'm trying to make a function working out (and then outputting as a String) the difference between two elements in a list - 1st and 2nd, then 2nd and 3rd, and so on - I think I'm giving it a good go, but I currently keep running into error whack-a-mole, I've put the current error below, but first, obligatory code dump:

type Name = String
type Coordinates = (Int, Int)
type Pop = Int
type TotalPop = [Pop]
type City = (Name, (Coordinates, TotalPop))

testData :: [City]
testData = [("New York City", ((1,1), [5, 4, 3, 2])),
           ("Washingotn DC", ((3,3), [3, 2, 1, 1])),
           ("Los Angeles", ((2,2), [7, 7, 7, 5]))]

getPopGrowth :: [City] -> Name -> String
getPopGrowth cs name = concat
    [getPercentages z ++ "\n" | (x,z) <- maybeToList (lookup name cs)] where
    getPercentages z = unwords (map show z1) ++ "% " where
        z1 = percentageIncrease z

percentageIncrease :: [Int] -> [Float]
percentageIncrease (x:xs)
    | length (x:xs) > 2 = percentageIncrease (tail xs)
    | otherwise = (a / b - 1) * 100.0 where
        a = fromIntegral x :: Float
        b = fromIntegral (head xs) :: Float

And the error I'm getting at the moment is:

error:
    • Couldn't match expected type ‘[Float]’ with actual type ‘Float’
    • In the expression: (a / b - 1) * 100.0
      In an equation for ‘percentageIncrease’:
          percentageIncrease (x : xs)
            | length (x : xs) > 2 = percentageIncrease (tail xs)
            | otherwise = (a / b - 1) * 100.0
            where
                a = fromIntegral x :: Float
                b = fromIntegral (head xs) :: Float
   |
92 |     | otherwise = (a / b - 1) * 100.0 where
   |                   ^^^^^^^^^^^^^^^^^^^

I would like to emphasise, I understand the error, but I do not know how to resolve it in such a way that I get the desired outcome of the function. Just for some clarity around what I'm trying to do. Input: getPopGrowth testData "New York City" should Output: 25% 33.333% 50%


Solution

  • So far, you only calculate the percentage when the list has exactly two elements left. Less elements are not covered, and for longer lists, in all the steps before, the elements get dropped without further action. In the last step, however, you return a single Float instead of a list.

    The following example creates an increase percentage in every step, concatenating it with the list resulting from applying the function to the tail of the list. Also, the base cases are all covered:

    percentageIncrease :: [Int] -> [Float]
    percentageIncrease [] = []
    percentageIncrease (x:[]) = []
    percentageIncrease (x:y:xs) = ((a / b - 1) * 100.0) : percentageIncrease (y:xs) where
                                    a = fromIntegral x :: Float
                                    b = fromIntegral y :: Float
    

    Console output:

    *Main> getPopGrowth testData "New York City"
    "25.0 33.333336 50.0% \n"