【问题标题】:Replace an element in a list only once - Haskell仅替换列表中的元素一次 - Haskell
【发布时间】:2013-01-03 10:38:23
【问题描述】:

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

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

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

感谢您的帮助!

【问题讨论】:

    标签: list haskell


    【解决方案1】:
    replaceValue :: Int -> Int -> [Int] -> [Int]
    replaceValue a b (x:xs) 
            |(a == x) = [b] ++ xs
            |otherwise = [x] ++ replaceValue a b xs
    

    【讨论】:

      【解决方案2】:

      使用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 -> Bool),该函数应在您想要修改的类型“a”上返回 True。

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

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

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

      >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]]
      

      【讨论】:

        【解决方案3】:

        重点是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 -&gt; a,而是更改为a -&gt; 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
        

        【讨论】:

        • 非常感谢您的准确解释!我学到了很多。
        【解决方案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' [] = []
        

        【讨论】:

        • 我对 Haskell 很陌生。请问“Eq a=>”是什么意思?
        • 这是一个类型约束;这意味着a 必须是 Eq 类型类的实例。需要它才能使用==
        【解决方案5】:

        直接实现是

        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
        

        【讨论】:

          【解决方案6】:

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

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

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

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

          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 仍然是最好的学习库之一。

          【讨论】:

            【解决方案7】:

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

            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 
            

            [编辑]

            啊!!!

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

            【讨论】:

            • 虽然如果x 不在xs 中出现,模式匹配将失败。
            • 重新编辑:如果x 不在xs 中出现,您的安全版本将返回xs ++ [y]。这不是其他解决方案所做的。我并不是说这是错误的(尽管我认为这不是 OP 想要的,但毫无疑问在某些情况下这种行为是正确的),但我认为这是值得注意的。
            • 这是一个 hack:rep xs x y = let (left, right) = break (== x) xs; (yes, no) = splitAt 1 right in left ++ [y | _ &lt;- yes] ++ no.
            【解决方案8】:

            这是一个命令式的方法,使用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
            

            【讨论】:

              猜你喜欢
              • 1970-01-01
              • 1970-01-01
              • 1970-01-01
              • 2021-05-19
              • 2011-08-16
              • 2016-07-20
              • 1970-01-01
              • 1970-01-01
              • 1970-01-01
              相关资源
              最近更新 更多