替换列表中的元素



我需要实现一个替换列表中元素的函数 要替换的索引是元组中的fst和元组中的snd是用什么代替它。并且要求我使用foldrmap功能。

例如:

setElements   [(1, 'a'), (-4, 't'), (3, 'b')] "#####" = "#a#b#" 

setElements函数无法编译:

我的代码:

setElement :: Int -> a -> [a] -> [a]
setElement n x xs = if ((n < length xs) && n >= 0)
then (take n xs) ++ [x] ++ (drop (n + 1) xs)
else xs
setElements :: [(Int, a)] -> [a] -> [a]
setElements = foldr (t l-> setElement (fst t) (snd t) l) [] 

我得到:

• Couldn't match type ‘[a]’ with ‘[a] -> [a]’ 
Expected type: [(Int, a)] -> [a] -> [a] 
Actual type: [(Int, a)] -> [a] 
• Possible cause: ‘foldr’ is applied to too many arguments 
In the expression: foldr ( t l -> setElement (fst t) (snd t) l) [] 
In an equation for ‘setElements’: 
setElements = foldr ( t l -> setElement (fst t) (snd t) l) [] 
• Relevant bindings include 
setElements :: [(Int, a)] -> [a] -> [a] 
(bound at hw3.hs:79:1) 
| 
79 | setElements = foldr (t l-> setElement (fst t) (snd t) l) [] 
|

如何修复该错误?

让我们看看你的函数:

setElements :: [(Int, a)] -> [a] -> [a]
setElements = foldr (t l-> setElement (fst t) (snd t) l) []

并回忆一下foldr的类型:

foldr :: (a -> b -> b) -> b -> [a] -> b

在使用foldr时,您a(Int, a)b[a]。而且你只给它前 2 个参数。所以foldr (t l-> setElement (fst t) (snd t) l) []有类型[(Int, a)] -> [a]- 而setElements应该有类型[(Int, a)] -> [a] -> [a].请注意这些如何与 GHC 在错误消息中报告的"实际类型"和"预期类型"完全匹配。

为了解决这个问题,我实际上会后退一步。折叠是正确的想法 - 您的setElement函数已经根据索引和新值修改了原始列表(其第三个参数),您想要的是获取编码此数据的对列表,并继续应用此函数重复更新原始列表。(当然,这是Haskell,所以数据是不可变的 - 你不是真的就地更新它,而只是每次都返回一个新列表。但有时像这样松散地说话更容易。

这正是折叠。让我们试着把它写出来,不要试图用"无点"的方法太花哨,而是完全应用它:

setElements :: [(Int, a)] -> [a] -> [a]
setElements ps as = foldr myFunction as ps
where myFunction = undefined

这里的undefined只是一个占位符 - 如果您尝试使用该函数,它会导致运行时错误(但不会导致编译错误),我把它放在那里是因为我们需要考虑这一点,fold 函数通常是实现 fold 中最棘手的部分。但是,让我们检查一下我们是否了解其他术语在做什么:我们实际上"走在一起"的列表是告诉我们插入什么以及插入位置的(Int, a)术语列表 - 这就是我所说的ps(p是"对")。因为我们是从a列表开始的——我在逻辑上称之为as——那么这应该是起始累加器值,这是要foldr的第二个参数。

因此,剩下的就是折叠函数 - 它接受一对和一个列表,并根据对中的值更新列表。好吧,这是您已经在使用的功能:

t l-> setElement (fst t) (snd t) l

或者,用模式匹配重写(我发现它更具可读性,因此我认为大多数Haskell开发人员更喜欢):

(i, a) as -> setElement i a as

因此,将其代入,我们得出以下定义:

setElements :: [(Int, a)] -> [a] -> [a]
setElements ps as = foldr myFunction as ps
where myFunction = (i, a) as -> setElement i a as

现在这将编译并正常工作。但是,当您有一个工作函数时,总是值得退后一步,看看是否可以简化其定义。事实上,myFunction可以简化很多:

(i, a) as -> setElement i a as

可以先"减少"η 至

(i, a) -> setElement i a

使用标准库函数,简直是uncurry setElement.

在这个阶段,我们显然不再需要where子句(事实上我们以前从未这样做过,但 imo 它有助于任何不太重要的 lambda 的可读性),并且可以只写:

setElements :: [(Int, a)] -> [a] -> [a]
setElements ps as = foldr (uncurry setElement) as ps

事实上,虽然我不一定推荐它,但如果我们正在玩代码高尔夫,你甚至可以更进一步,只写:

setElements = flip . foldr . uncurry $ setElement

我个人认为,能够像上面一样简洁地表达相对复杂的函数,这绝对是 Haskell 魅力的一部分。但是,在我看来,与其试图立即编写这样的东西,不如从一些非常具体的东西开始,展示你想要如何转换你的数据 - 并且,只有在开始工作之后,如果你愿意,寻找一个更简洁的表示。

相关内容

  • 没有找到相关文章

最新更新