仅替换列表中的元素一次 - Haskell

时间:2013-01-03 10:38:24

标签: list haskell

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

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

如何修改代码以便仅在第一个匹配的项目中进行更改?

感谢您的帮助!

8 个答案:

答案 0 :(得分:11)

关键是,mapf(示例中为check)仅就如何转换单个元素进行沟通。他们不会就列表在多大程度上改变元素进行沟通: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将意为“更改此元素,并保留其余元素不变”
  3. 所以:

    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
    

答案 1 :(得分: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' [] = []

答案 2 :(得分: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

我喜欢列表作为最后一个参数来执行类似

的操作
myRep = rep 3 5 . rep 7 8 . rep 9 1

答案 3 :(得分:3)

说实话,我不喜欢到目前为止的大部分答案。 dave4420提供了我对map的一些很好的见解,但我也不喜欢他的解决方案。

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

  应用于谓词break和列表p

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

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

  1. 将列表拆分为两部分:old第一次出现之前的所有元素,其余部分。
  2. “rest”列表将为空,或者其第一个元素将是old的第一个元素。这两种情况都很容易处理。
  3. 所以我们有这个解决方案:

    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. 当遇到无法通过库函数组合解决的问题时,请尝试创建自己的有用的泛型函数。
    6. 编辑:我刚刚意识到break实际上是Prelude函数,不需要导入。尽管如此,Data.List仍然是最好的学习图书馆之一。

答案 4 :(得分:3)

使用Lens library的替代方案。

>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 - &gt; 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]]

答案 5 :(得分:2)

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

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

<强> [编辑]

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

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

<强> [编辑]

Arrgh !!!

rep xs x y = left ++ r right where
  (left, right) = break (== x) xs 
  r (_:rs) = y:rs
  r [] = []

答案 6 :(得分:1)

replaceValue :: Int -> Int -> [Int] -> [Int]
replaceValue a b (x:xs) 
        |(a == x) = [b] ++ xs
        |otherwise = [x] ++ replaceValue a b xs

答案 7 :(得分:0)

使用State Monad:

,这是一种必要的方法
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