我只想在第一次出现时用新值替换列表中的元素。我写了下面的代码,但是使用它,所有匹配的元素都会改变。
replaceX :: [Int] -> Int -> Int -> [Int]
replaceX items old new = map check items where
check item | item == old = new
| otherwise = item
如何修改代码以使更改仅发生在第一个匹配项上?
感谢您的帮助!
关键是map
并且f
(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]
关于这种类型签名有两点需要注意:
因为我们可能会f
在列表的中途停止应用,所以输入列表和输出列表必须具有相同的类型。(使用map
,因为将始终映射整个列表,所以类型可以更改。)
的类型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
您可以轻松地将其编写为递归迭代,如下所示:
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' [] = []
直接实施将是
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
坦率地说,到目前为止,我不喜欢大多数答案。dave4420 对map
我第二次提出了一些很好的见解,但我也不喜欢他的解决方案。
为什么我不喜欢这些答案?因为你应该学习通过将它们分解成更小的问题来解决这些问题,这些问题可以通过更简单的函数来解决,最好是库函数。在这种情况下,库是Data.List
,函数是break
:
break
,应用于谓词p
和列表,返回一个元组,其中第一个元素是不满足的元素xs
的最长前缀(可能为空),第二个元素是列表的其余部分。xs
p
有了这个,我们可以这样解决问题:
old
,以及其余元素。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]
所以我给你的建议:
Data.List
是一个很好的起点。Data.List
并编写您自己的版本。编辑:我刚刚意识到这break
实际上是一个Prelude
函数,不需要导入。尽管如此,它仍然Data.List
是最好的学习图书馆之一。
使用镜头库的替代方法。
>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]]
也许不是最快的解决方案,但很容易理解:
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 [] = []
replaceValue :: Int -> Int -> [Int] -> [Int]
replaceValue a b (x:xs)
|(a == x) = [b] ++ xs
|otherwise = [x] ++ replaceValue a b xs
这是使用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