4
\$\begingroup\$

I am trying to solve following problem in Haskell using recursion:

Define a recursive function funkyMap :: (a -> b) -> (a -> b) -> [a] -> [b] that takes as arguments two functions f and g and a list xs, and applies f to all elements at even positions [0, 2..] in xs and g to all elements at odd positions [1, 3..] in xs.

Example: funkyMap (+10) (+100) [1, 2, 3, 4, 5] = [(+10) 1, (+100) 2, (+10) 3, (+100) 4, (+10) 5].

I came up with following solution:

h1 p q [] = []
h1 p q ((cnt, val) : xs) = (if odd cnt then (q val) else (p val) ) : h1 p q xs

funkyMap f g xs = h1 f g ( zip [0..] xs)

If I try funkyMap (+10) (+100) [1, 2, 3, 4, 5] I get [11,102,13,104,15] which is expected.

Also funkyMap (+100) (+2) [1] gives [101]

and funkyMap (+100) (+2) [] gives []

Please review this solution and let me know your feedback.

\$\endgroup\$
0

3 Answers 3

6
\$\begingroup\$

So, like @pigworker has said your code looks functionally good to me, but you don't have to break up your solution into two parts like this or create a data structure you don't need.

You might find a better solution using a different approach (pattern matching):

funkyMap :: (a -> b) -> (a -> b) -> [a] -> [b]
funkyMap p q [] = []
funkyMap p q [x] = [p x]
funkyMap p q (x : y : xs) = p x : q y : funkyMap p q xs

The real benefit of this is you don't have to create any intermediate data structures.

The idea behind this solution is to let pattern matching do the hard work for you. Really you have 3 senarios for your function

  • when there are no elements in the list (where you probably just want to return the empty list)
  • when you have just one element in the list (where you just want to apply p to the element)
  • when you have 2 or more elements in the list (where you apply p and q, then use recursion to evaluate the rest of the list)

Using pattern matching lets you wrap that logic up in a really obvious way

\$\endgroup\$
1
  • \$\begingroup\$ Very Clean, before I read the answer I tried this myself and forgot that I could pattern match two elements off the front. Ended up using an internal version with an index counter. This is much cleaner. \$\endgroup\$ Dec 15, 2015 at 20:32
4
\$\begingroup\$

Your solution appears to be functionally correct, but computationally expensive. It uses zip to generate an intermediate data structure which contains too much information (labelling each list element with its exact numerical position), and then h1 throws that information away (testing the oddness of the number, needing only its least significant bit).

There are much cleaner solutions, which make more direct use of the key property that even and odd positions alternate successively. To know the parity of a position, you don't need to know its number, just the parity of the previous position.

\$\endgroup\$
2
  • \$\begingroup\$ I am a newbie to Haskell and trying to complete a homework problem. I am very eager to learn more. Can you suggest an alternate please? \$\endgroup\$
    – John Smith
    Dec 7, 2015 at 1:27
  • 5
    \$\begingroup\$ @JohnSmith There are many spellings. A first pass would be to use cycle [False, True] in place of [0..], which reduces from numbers to parity information; then you might partially evaluate h1 and use mapFunky f g = zipWith ($) (cycle [f, g]). You might also consider a custom implementation using mapFunky f g (x:xs) = f x : mapFunky g f xs. \$\endgroup\$ Dec 7, 2015 at 2:09
1
\$\begingroup\$

I suggest using the higher order functions to express the calculation by using known "building blocks", this makes the solution really concise:

funkyMap f g = zipWith ($) (cycle [f, g])

Let us step through this:

  • cycle [f, g]

Prelude> :t cycle
cycle :: [a] -> [a]
Prelude> take 10 $ cycle [1, 2, 3]
[1,2,3,1,2,3,1,2,3,1]

cycle given a list repeats it infinitely, so this creates a non-ending list of [f, g, f, g, f ...]

  • $

Prelude> :t ($)
($) :: (a -> b) -> a -> b
Prelude> (\x -> x + 1) $ 3
4

$ given a function and an argument, applies the function to the argument.

  • zipWith

Prelude> :t zipWith
zipWith :: (a -> b -> c) -> [a] -> [b] -> [c]
Prelude> zipWith (+) [1, 4] [2, 10]
[3,14]
Prelude> let zip = zipWith (,)

zipWith, applies the function to pairs of the two given lists, it is the general version of zip (zip = zipWith (,))


So we create a list of infinite repetitions of [f, g] repeated and applie f to the first, g to the second.. etc, as per the problem specification.

\$\endgroup\$

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.