替换列表中的元素
Replace elements in the list
我需要实现一个函数来替换列表中的元素 --
要替换的索引是元组中的 fst
和元组中的 snd
是用什么来代替它。我被要求使用 foldr
或 map
函数。
例如:
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]
。你只给它前两个参数。所以 foldr (\t l-> setElement (fst t) (snd t) l) []
有类型 [(Int, a)] -> [a]
- 而 setElements
应该有类型 [(Int, a)] -> [a] -> [a]
。请注意它们如何与 GHC 在错误消息中报告的 "actual type" 和 "expected type" 完全匹配。
要解决这个问题,我实际上会倒退一步。折叠是正确的想法 - 你的 setElement
函数已经根据索引和新值修改了原始列表(它的第三个参数),你想要的是获取一个编码该数据的对列表,并继续应用此函数重复更新原始列表。 (当然这是 Haskell 所以数据是不可变的——你不是字面意义上的就地更新它,而是每次都简单地返回一个新列表。但有时像这样松散地谈论更容易。)
这正是弃牌的含义。让我们试着把它写出来,不要太花哨地使用 "point-free" 方法,而是完全应用它:
setElements :: [(Int, a)] -> [a] -> [a]
setElements ps as = foldr myFunction as ps
where myFunction = undefined
这里的 undefined
只是一个占位符 - 如果您尝试使用该函数,它会导致运行时错误(但不会导致编译错误),我把它放在那里是因为我们需要考虑一下,折叠功能通常是实现折叠最棘手的部分。但是让我们检查一下我们是否理解其他术语在做什么:我们实际上是 "walking along" 的列表是 (Int, a)
术语的列表,它告诉我们要插入什么以及在哪里插入 - 这就是我所说的 ps
(p
对应 "pair")。因为我们从 a
s 的列表开始——我在这里逻辑上称之为 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
可以先"eta-reduced"到
\(i, a) -> setElement i a
使用标准库函数,就是 uncurry setElement
.
在这个阶段,我们显然不再需要 where
子句(事实上我们以前从未这样做过,但我认为它有助于任何 lambda 的可读性,这不是很简单),并且可以写:
setElements :: [(Int, a)] -> [a] -> [a]
setElements ps as = foldr (uncurry setElement) as ps
事实上,虽然我不一定会推荐它,但如果我们正在玩代码高尔夫,您甚至可以更进一步,只需写下:
setElements = flip . foldr . uncurry $ setElement
我个人认为能够像上面这样简洁的表达相对复杂的功能,绝对是Haskell魅力的一部分。但是,与其尝试直接写这样的东西,在我看来,最好从一些非常具体的东西开始,展示你想如何转换你的数据——而且,只有在它工作之后,寻找一个更简洁的表示,如果你想要。
我需要实现一个函数来替换列表中的元素 --
要替换的索引是元组中的 fst
和元组中的 snd
是用什么来代替它。我被要求使用 foldr
或 map
函数。
例如:
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]
。你只给它前两个参数。所以 foldr (\t l-> setElement (fst t) (snd t) l) []
有类型 [(Int, a)] -> [a]
- 而 setElements
应该有类型 [(Int, a)] -> [a] -> [a]
。请注意它们如何与 GHC 在错误消息中报告的 "actual type" 和 "expected type" 完全匹配。
要解决这个问题,我实际上会倒退一步。折叠是正确的想法 - 你的 setElement
函数已经根据索引和新值修改了原始列表(它的第三个参数),你想要的是获取一个编码该数据的对列表,并继续应用此函数重复更新原始列表。 (当然这是 Haskell 所以数据是不可变的——你不是字面意义上的就地更新它,而是每次都简单地返回一个新列表。但有时像这样松散地谈论更容易。)
这正是弃牌的含义。让我们试着把它写出来,不要太花哨地使用 "point-free" 方法,而是完全应用它:
setElements :: [(Int, a)] -> [a] -> [a]
setElements ps as = foldr myFunction as ps
where myFunction = undefined
这里的 undefined
只是一个占位符 - 如果您尝试使用该函数,它会导致运行时错误(但不会导致编译错误),我把它放在那里是因为我们需要考虑一下,折叠功能通常是实现折叠最棘手的部分。但是让我们检查一下我们是否理解其他术语在做什么:我们实际上是 "walking along" 的列表是 (Int, a)
术语的列表,它告诉我们要插入什么以及在哪里插入 - 这就是我所说的 ps
(p
对应 "pair")。因为我们从 a
s 的列表开始——我在这里逻辑上称之为 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
可以先"eta-reduced"到
\(i, a) -> setElement i a
使用标准库函数,就是 uncurry setElement
.
在这个阶段,我们显然不再需要 where
子句(事实上我们以前从未这样做过,但我认为它有助于任何 lambda 的可读性,这不是很简单),并且可以写:
setElements :: [(Int, a)] -> [a] -> [a]
setElements ps as = foldr (uncurry setElement) as ps
事实上,虽然我不一定会推荐它,但如果我们正在玩代码高尔夫,您甚至可以更进一步,只需写下:
setElements = flip . foldr . uncurry $ setElement
我个人认为能够像上面这样简洁的表达相对复杂的功能,绝对是Haskell魅力的一部分。但是,与其尝试直接写这样的东西,在我看来,最好从一些非常具体的东西开始,展示你想如何转换你的数据——而且,只有在它工作之后,寻找一个更简洁的表示,如果你想要。