我想仅在第一次出现时用新值替换列表中的元素。 我编写了下面的代码,但使用它,所有匹配的元素都会改变。
replaceX :: [Int] -> Int -> Int -> [Int]
replaceX items old new = map check items where
check item | item == old = new
| otherwise = item
如何修改代码以便仅在第一个匹配的项目中进行更改?
感谢您的帮助!
答案 0 :(得分:11)
关键是,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
答案 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
,第二个元素是列表的其余部分。
有了这个,我们可以像这样解决问题:
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]
所以我给你的建议是:
答案 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