6

我只想在第一次出现时用新值替换列表中的元素。我写了下面的代码,但是使用它,所有匹配的元素都会改变。

replaceX :: [Int] -> Int -> Int -> [Int]
replaceX items old new = map check items where
check item  | item == old = new 
            | otherwise = item

如何修改代码以使更改仅发生在第一个匹配项上?

感谢您的帮助!

4

8 回答 8

11

关键是map并且fcheck在您的示例中)仅就如何转换单个元素进行交流。他们不会就转换元素的列表向下进行交流:map总是一直进行到最后。

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

让我们写一个新版本的map--- 我会调用它,mapOnce因为我想不出更好的名字。

mapOnce :: (a -> Maybe a) -> [a] -> [a]

关于这种类型签名有两点需要注意:

  1. 因为我们可能会f在列表的中途停止应用,所以输入列表和输出列表必须具有相同的类型。(使用map,因为将始终映射整个列表,所以类型可以更改。)

  2. 的类型f没有更改为a -> a,而是更改为a -> Maybe a

    • Nothing将意味着“保持此元素不变,继续向下列表”
    • Just y将意味着“更改此元素,并保持其余元素不变”

所以:

mapOnce _ []     = []
mapOnce f (x:xs) = case f x of
        Nothing -> x : mapOnce f xs
        Just y  -> y : xs

您的示例现在是:

replaceX :: [Int] -> Int -> Int -> [Int]
replaceX items old new = mapOnce check items where
    check item  | item == old = Just new 
                | otherwise   = Nothing
于 2013-01-03T11:14:57.477 回答
4

您可以轻松地将其编写为递归迭代,如下所示:

rep :: Eq a => [a] -> a -> a -> [a]
rep items old new = rep' items
    where rep' (x:xs) | x == old  = new : xs
                      | otherwise = x : rep' xs
          rep' [] = []
于 2013-01-03T10:53:12.457 回答
4

直接实施将是

rep :: Eq a => a -> a -> [a] -> [a]
rep _ _ [] = []
rep a b (x:xs) = if x == a then b:xs else x:rep a b xs

我喜欢 list 作为最后一个参数来做类似的事情

myRep = rep 3 5 . rep 7 8 . rep 9 1
于 2013-01-03T13:47:46.807 回答
3

坦率地说,到目前为止,我不喜欢大多数答案。dave4420 对map我第二次提出了一些很好的见解,但我也不喜欢他的解决方案。

为什么我不喜欢这些答案?因为你应该学习通过将它们分解成更小的问题来解决这些问题,这些问题可以通过更简单的函数来解决,最好是库函数。在这种情况下,库是Data.List,函数是break

break,应用于谓词p和列表,返回一个元组,其中第一个元素是不满足的元素xs的最长前缀(可能为空),第二个元素是列表的其余部分。xsp

有了这个,我们可以这样解决问题:

  1. 将列表分成两部分:第一次出现 之前的所有元素old,以及其余元素。
  2. “rest”列表要么是空的,要么它的第一个元素是old. 这两种情况都很容易处理。

所以我们有这个解决方案:

import Data.List (break)

replaceX :: Eq a => a -> a -> [a] -> [a] 
replaceX old new xs = beforeOld ++ replaceFirst oldAndRest 
    where (beforeOld, oldAndRest) = break (==old) xs
          replaceFirst [] = []
          replaceFirst (_:rest) = new:rest

例子:

*Main> replaceX 5 7 ([1..7] ++ [1..7])
[1,2,3,4,7,6,7,1,2,3,4,5,6,7]

所以我给你的建议:

  1. 了解如何导入库。
  2. 学习图书馆文档并学习标准功能。 Data.List是一个很好的起点。
  3. 尽可能多地使用这些库函数。
  4. 作为自学练习,您可以从中挑选一些标准函数Data.List并编写您自己的版本。
  5. 当您遇到无法通过组合库函数解决的问题时,请尝试发明自己有用的通用函数。

编辑:我刚刚意识到这break实际上是一个Prelude函数,不需要导入。尽管如此,它仍然Data.List是最好的学习图书馆之一。

于 2013-01-04T02:29:00.377 回答
3

使用镜头库的替代方法。

>import Control.Lens
>import Control.Applicative

>_find :: (a -> Bool) -> Simple Traversal [a] a                                   
>_find _ _ [] = pure []                                                           
>_find pred f (a:as) = if pred a                                                  
>                       then (: as) <$> f a                                       
>                       else (a:) <$> (_find pred f as)

这个函数需要一个 (a -> Bool),它是一个函数,它应该在你想要修改的类型 'a' 上返回 True。

如果第一个大于 5 的数字需要加倍,那么我们可以这样写:

>over (_find (>5)) (*2) [4, 5, 3, 2, 20, 0, 8]
[4,5,3,2,40,0,8]

镜头的伟大之处在于您可以通过组合它们(。)将它们组合在一起。因此,如果我们想将第二个子列表中的第一个数字 <100 归零,我们可以:

>over ((element 1).(_find (<100))) (const 0) [[1,2,99],[101,456,50,80,4],[1,2,3,4]]
[[1,2,99],[101,456,0,80,4],[1,2,3,4]]
于 2013-01-04T03:22:53.807 回答
2

也许不是最快的解决方案,但很容易理解:

rep xs x y = 
  let (left, (_ : right)) = break (== x) xs 
  in left ++ [y] ++ right 

[编辑]

正如戴夫评论的那样,如果 x 不在列表中,这将失败。一个安全的版本是:

rep xs x y = 
  let (left, right) = break (== x) xs 
  in left ++ [y] ++ drop 1 right 

[编辑]

啊!!!

rep xs x y = left ++ r right where
  (left, right) = break (== x) xs 
  r (_:rs) = y:rs
  r [] = []
于 2013-01-03T14:16:13.330 回答
1
replaceValue :: Int -> Int -> [Int] -> [Int]
replaceValue a b (x:xs) 
        |(a == x) = [b] ++ xs
        |otherwise = [x] ++ replaceValue a b xs
于 2013-11-07T10:35:50.470 回答
0

这是使用StateMonad 的必要方法:

import Control.Monad.State                                                  

replaceOnce :: Eq a => a -> a -> [a] -> [a]
replaceOnce old new items = flip evalState False $ do
  forM items $ \item -> do
    replacedBefore <- get
    if item == old && not replacedBefore
      then do
        put True
        return new
      else
        return old
于 2013-01-04T09:56:55.680 回答