将谓词添加到地图功能

时间:2014-08-20 11:25:47

标签: haskell map filtering

Haskell全新,通过学习Haskell学习更大的好处。

我在看地图功能

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

是否可以为此添加谓词?例如,仅映射到列表中的每个其他元素?

8 个答案:

答案 0 :(得分:7)

您可以将自己的map版本编码为仅将f应用于偶数(或奇数)位置,如下所示。 (索引低于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

如果您想要利用库函数,可以执行类似

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

甚至

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

如果要使用索引上的任意谓词进行过滤,则:

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

使用zipWith可以达到更直接的解决方案(正如@amalloy建议的那样)。

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

这可以进一步细化如下

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

答案 1 :(得分:3)

"规范"基于位置执行过滤的方法是zip具有自然的序列,以便为每个元素附加索引:

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

这样你可以使用元组的第二部分过滤整个事物,然后映射一个丢弃索引的函数:

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]

具体到你的问题,可以简单地提出

mapEveryOther f = indexedFilterMap odd f

答案 2 :(得分:1)

您可以使用函数进行映射(也可以使用lambda):

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

map plusIfOdd [1..5]

答案 3 :(得分:1)

作为第一步,将要执行的操作编写到列表的单个元素中:

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

因此,如果元素为奇数,applytoOdd函数将函数f应用于元素,否则返回相同的元素。现在您可以将map应用于此:

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

或者如果您想向其添加200,则:

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

查看评论,您似乎希望基于索引位置map。您可以适当修改applytoOdd方法:

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

这里,类型变量a对应于索引元素。如果它很奇怪你将该函数应用于列表的实际元素。然后在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]

答案 4 :(得分:1)

或使用列表理解:

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

答案 5 :(得分:0)

我很高兴您花时间了解Haskell。这是一种很棒的语言。但它确实需要你培养一定的心态。所以这就是我在Haskell遇到问题时所做的事情。让我们从您的问题陈述开始:

  

是否可以在map函数中添加谓词?例如,仅map到列表中的每个其他元素?

所以你有两个问题:

  1. 是否可以向map函数添加谓词?
  2. 如何map列表中的其他所有元素?
  3. 所以人们在Haskell中的思考方式是通过类型签名。例如,当工程师正在设计建筑物时,她可以看到建筑物应该如何寻找顶部(顶视图),前部(前视图)和侧视图(侧视图)。类似地,当功能程序员编写代码时,他们根据类型签名来可视化他们的代码。

    让我们从我们所知道的开始(即map函数的类型签名):

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

    现在您要为map函数添加谓词。谓词是a -> Bool类型的函数。因此,具有谓词的map函数将具有以下类型:

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

    但是,在您的情况下,您还希望保留未映射的值。例如,mapP odd (+100) [1,2,3,4,5]应该导致[101,2,103,4,105]而不是[101,103,105]。因此,输入列表的类型应该与输出列表的类型匹配(即ab必须是相同的类型)。因此mapP应为:

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

    实现这样的功能很容易:

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

    现在回答你的第二个问题(即如何map到列表中的每个其他元素。您可以使用zipunzip,如下所示:

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

    以下是发生的事情:

    1. 我们首先zip每个元素的索引与元素本身。因此,zip [1..] [1,2,3,4,5]会产生[(1,1),(2,2),(3,3),(4,4),(5,5)],其中每对的fst值都是索引。
    2. 对于每个odd索引元素,我们将(+100)函数应用于元素。因此,结果列表为[(1,101),(2,2),(3,103),(4,4),(5,105)]
    3. 我们unzip列表会生成两个单独的列表([1,2,3,4,5],[101,2,103,4,105])
    4. 我们会丢弃索引列表,并使用snd保留映射结果列表。
    5. 我们可以使这个功能更通用。结果函数的类型签名为:

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

      mapI函数的定义很简单:

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

      您可以按如下方式使用它:

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

      希望有所帮助。

答案 6 :(得分:0)

  

是否可以为此添加谓词?例如,仅映射到列表中的每个其他元素?

是的,但功能理想情况下应该只做一个相对简单的事情。如果你需要做一些更复杂的事情,理想情况下你应该尝试通过编写两个或更多函数来完成它。

我不是100%确定我理解你的问题,所以我会举几个例子。首先:如果您的意思是您只想在提供的谓词返回输入元素的true的情况下进行映射,否则只是不管它,那么您可以通过重用map函数来实现:

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

如果您想要丢弃不满足谓词的列表元素,并将该函数应用于其余元素,那么您可以通过组合map和{{1 }}:

filter

将函数映射到列表的每个其他元素与这两个元素不同,因为它不是对列表元素的谓词;它是有状态遍历列表的结构转换

另外,我不清楚你是否要丢弃或保留你不应用该功能的元素,这意味着不同的答案。如果您要丢弃它们,那么您可以通过丢弃备用列表元素然后将该函数映射到其余列表元素来实现:

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

如果您要保留它们,可以通过标记每个列表元素的位置,过滤列表以仅保留偶数位置的列表元素,丢弃标记然后映射函数来实现: / p>

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)

如上所述,-- 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 按位置成对组合了两个列表。

但这是一般的哲学:做一件复杂的事情,使用通用通用函数的组合。如果您熟悉Unix,那就类似于它。


写下最后一个的另一种简单方法。它的时间更长,但请注意zip :: [a] -> [b] -> [(a, b)]evensodds都是通用且可重复使用的:

interleave

答案 7 :(得分:0)

您不能使用谓词,因为谓词操作列表值,而不是其索引。

我非常喜欢这种格式,因为它使得该功能的案例处理非常清晰:

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

例如,运行:

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

收率:

[2,2,4,4]