Adding a predicate to a map function
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])
source to share
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
source to share
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]
source to share
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 formap
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 offst
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.
source to share
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)
source to share
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]
source to share