Adding a predicate to a map function

Brand new to Haskell and learning through Learn Haskell is a great benefit.

I am looking at the map function

map :: (a -> b) -> [a] -> [b]  
map _ [] = []  
map f (x:xs) = f x : map f xs  

      

Can a predicate be added to this? for example to only display each item in the list?

+3


source to share


8 answers


You can create your own version map

to apply f

only to even (or odd) positions as follows. (The indices below start with 0

)

mapEven :: (a->a) -> [a] -> [a]
mapEven f []     = []
mapEven f (x:xs) = f x : mapOdd f xs

mapOdd :: (a->a) -> [a] -> [a]
mapOdd f []     = []
mapOdd f (x:xs) = x : mapEven f xs

      

If you want to use library functions instead, you can do something like

mapEven :: (a->a) -> [a] -> [a]
mapEven f = map (\(flag,x) -> if flag then f x else x) . zip (cycle [True,False])

      

or even

mapEven :: (a->a) -> [a] -> [a]
mapEven f = map (uncurry (\flag -> if flag then f else id)) . zip (cycle [True,False])

      

If you want to filter using an arbitrary predicate in the index then:



mapPred :: (Int -> Bool) -> (a->a) -> [a] -> [a]
mapPred p f = map (\(i,x) -> if p i then f x else x) . zip [0..]

      


A more direct solution can be achieved using zipWith

(as @amalloy suggests).

mapEven :: (a->a) -> [a] -> [a]
mapEven f = zipWith (\flag x -> if flag then f x else x) (cycle [True,False])

      

This can be further refined as follows

mapEven :: (a->a) -> [a] -> [a]
mapEven f = zipWith ($) (cycle [f,id])

      

+7


source


The "canonical" way to filter based on positions is to zip

sequence with naturals to add an index to each element:

> zip [1, 1, 2, 3, 5, 8, 13] [0..]
[(1,0),(1,1),(2,2),(3,3),(5,4),(8,5),(13,6)]

      

So you can filter the whole thing using the second part of the tuples and then display a function that discards the indices:



indexedFilterMap p f xs = (map (\(x,_) -> f x)) . (filter (\(_,y) -> p y)) $ (zip xs [0..])
oddFibsPlusOne = indexedFilterMap odd (+1) [1, 1, 2, 3, 5, 8, 13]

      

To be specific to you, you can simply put

mapEveryOther f = indexedFilterMap odd f

      

+3


source


You can display with a function (possibly a lambda as well):

plusIfOdd :: Int -> Int
plusIfOdd a 
  | odd a = a
  | otherwise = a + 100

map plusIfOdd [1..5]

      

+1


source


As a first step, write a function for what you want to do with a separate list item:

applytoOdd :: Integral a => (a -> a) -> a -> a
applytoOdd f x = if odd x
                 then (f x)
                 else x

      

Thus, the function applytoOdd

will apply the function f

to the item if the item is odd, or it will return the same item if it is even. Now you can apply map

to the following:

λ> let a = [1,2,3,4,5]
λ> map (applytoOdd (+ 100)) a
[101,2,103,4,105]

      

Or if you want to add to it 200

then:

λ> map (applytoOdd (+ 200)) a
[201,2,203,4,205]

      


Looking at the comments, it seems like what you want map

based on the index position. You can change your method accordingly applytoOdd

for this:

applytoOdd :: Integral a => (b -> b) -> (a, b) -> b
applytoOdd f (x,y) = if odd x
                     then (f y)
                     else y

      

Here the type variable a

corresponds to the index element. If it's odd, you are applying the function to the actual list item. And then in ghci:

λ> map (applytoOdd (+ 100)) (zip [1..5] [1..])
[101,2,103,4,105]
λ> map (applytoOdd (+ 200)) (zip [1..5] [1..])
[201,2,203,4,205]

      

+1


source


Or use a list comprehension:

mapOdd f x = if odd x then f x else x
[ mapOdd (+100) x | x <- [1,2,3,4,5]]

      

+1


source


I'm glad you took the time to learn about Haskell. This is an amazing language. However, this requires you to develop a certain mindset. So this is what I do when I run into a problem in Haskell. Start with a problem statement:

Can a predicate be added to a function map

? For example, just for map

every other item in the list?

You have two questions:

  • Can a predicate be added to a function map

    ?
  • How map

    for every other item in the list?

Thus, people think in Haskell through type signatures. For example, when an engineer designs a building, she visualizes how the building should look from above (top view), front (front view), and side (side view). Likewise, when functional programmers write code, they render their code in terms of type signatures.

Let's start with what we know (i.e. function type signatures map

):

map :: (a -> b) -> [a] -> [b]

      

Now you want to add a predicate to the function map

. The predicate is a type function a -> Bool

. Therefore, the function map

with the predicate will be of the type:

mapP :: (a -> Bool) -> (a -> b) -> [a] -> [b]

      

However, in your case, you also want to keep the values ​​unchanged. For example, mapP odd (+100) [1,2,3,4,5]

should contain [101,2,103,4,105]

, not [101,103,105]

. It follows that the type of the input list must correspond to the type of the output list (that is, a

and b

must be of the same type). Therefore, it mapP

must be of type:

mapP :: (a -> Bool) -> (a -> a) -> [a] -> [a]

      

It's easy to implement a function like this:

map :: (a -> Bool) -> (a -> a) -> [a] -> [a]
mapP p f = map (\x -> if p x then f x else x)

      


Now to answer your second question (i.e. how map

for every other item in the list). You can use zip

and unzip

like this:

snd . unzip . mapP (odd . fst) (fmap (+100)) $ zip [1..] [1,2,3,4,5]

      

Here's what's going on:

  • First the zip

    index of each element with the element itself. Hence, zip [1..] [1,2,3,4,5]

    results in [(1,1),(2,2),(3,3),(4,4),(5,5)]

    , where the value of fst

    each pair is the index.
  • For each item in the index, odd

    we apply a function (+100)

    to the item. Hence the resulting list [(1,101),(2,2),(3,103),(4,4),(5,105)]

    .
  • We are a unzip

    list, and the result is two separate lists ([1,2,3,4,5],[101,2,103,4,105])

    .
  • Dropping the list of indices and saving the list of displayed results with snd

    .

We can make this function more general. The type signature of the resulting function will be:

mapI :: ((Int, a) -> Bool) -> (a -> a) -> [a] -> [a]

      

The function definition mapI

is simple enough:

mapI :: ((Int, a) -> Bool) -> (a -> a) -> [a] -> [a]
mapI p f = snd . unzip . mapP p (fmap f) . zip [1..]

      

You can use it like this:

mapI (odd . fst) (+100) [1,2,3,4,5]

      

Hope it helps.

0


source


Can a predicate be added to this? for example to only display each item in the list?

Yes, but functions should ideally only do one relatively simple thing. If you need to do something more complex, you should ideally try to do it by composing two or more functions.

I'm not 100% sure, I understand your question, so I'll show some examples. First: if you mean you only want to display the map when the bundled predicate returns true for the input element, but otherwise just leave it alone, you can do so by reusing the function map

:

mapIfTrue :: (a -> Bool) -> (a -> a) -> [a] -> [a]
mapIfTrue pred f xs = map step xs
   where step x | pred x    = f x
                | otherwise = x

      

If you mean you want to discard the list items that do not satisfy the predicate and apply this function to the rest, then you can do this by concatenating map

and filter

:

filterMap :: (a -> Bool) -> (a -> b) -> [a] -> [b]
filterMap pred f xs = map f (filter pred xs)

      

The display of the function on all other list items is different from these two because it is not a predicate over the list items; it is either a structural transformation of the list of its state traversal.

Also, I don't understand if you want to discard or keep items that you don't apply the function to, which would mean different answers. If you are discarding them, you can do it by simply discarding the alternate list items, and then matching the function over the rest:

keepEven :: [a] -> [a]
keepEven xs = step True xs
    where step _ [] = []
          step True (x:xs) = x : step False xs
          step False (_:xs) = step True xs

mapEven :: (a -> b) -> [a] -> [b]
mapEven f xs = map f (keepEven xs)

      

If you keep them, one way to do it is to mark each list item with its position, filter the list to keep only those in even positions, discard the tags, and then display the function:

-- Note: I'm calling the first element of a list index 0, and thus even.
mapEven :: (a -> a) -> [a] -> [a]
mapEven f xs = map aux (filter evenIndex (zip [0..] xs))
    where evenIndex (i, _) = even i
          aux (_, x) = f x

      

As already mentioned, it zip :: [a] -> [b] -> [(a, b)]

combines two lists by position.

But this is a common philosophy: to do a complex thing, to use a combination of common universal functions. If you're familiar with Unix, it looks like this.


Another easy way to write the latter. This is longer, but keep in mind that evens

, odds

and interleave

all are generic and reusable:

evens, odds :: [a] -> [a]
evens = alternate True
odds = alternate False

alternate :: Bool -> [a] -> [a]
alternate _ [] = []
alternate True (x:xs) = x : alternate False xs
alternate False (_:xs) = alternate True xs

interleave :: [a] -> [a] -> [a]
interleave [] ys = ys
interleave (x:xs) ys = x : interleave ys xs

mapEven :: (a -> a) -> [a] -> [a]
mapEven f xs = interleave (map f (evens xs)) (odds xs)

      

0


source


You cannot use a predicate because predicates operate on the values ​​of the list, not their indices.

I really like this format for what you are trying to do, as it makes message handling clear to the function:

newMap :: (t -> t) -> [t] -> [t]
newMap f [] = [] -- no items in list
newMap f [x] = [f x] -- one item in list
newMap f (x:y:xs) = (f x) : y : newMap f xs -- 2 or more items in list

      

For example, running:

newMap (\x -> x + 1) [1,2,3,4]

      

Productivity:

[2,2,4,4]

      

0


source







All Articles