【问题标题】:Replacing Nth character in String array in Haskell在Haskell中替换字符串数组中的第N个字符
【发布时间】:2014-08-05 14:05:36
【问题描述】:

我是 Haskell 和函数式编程的新手。我正在尝试实现一个函数来获取这样的列表

["abc", "def", "ghi"]

并希望能够替换第 y 个元素中的第 x 个字符,例如

replaceChar 1 2 'd' arr

会产生

["abc", "ded", "ghi"]

所以本质上第一个参数是元素,第二个是字符串的位置,第三个是字符,最后一个是[字符串]。

这个函数的签名是这样的:

replaceChar :: Int -> Int -> Char -> [String] -> [String]

任何帮助将不胜感激。谢谢!

【问题讨论】:

  • 您是否尝试过自己实现此功能?向我们展示你到目前为止所拥有的。

标签: list haskell functional-programming


【解决方案1】:

首先要注意:虽然您的签名非常好,但您实际上并没有使用您正在处理字符串的事实,它也可以是任何其他类型的列表。通过使用完全通用的类型变量(小写字母)而不是Char,在您的签名中表明这一点通常是一个好主意1

replaceAtXY :: Int -> Int -> a -> [[a]] -> [[a]]

接下来,请注意,基本上问题可以归结为修改普通(非嵌套)列表的第 n 个元素。在外部列表中,您修改第 y 个子列表,即在该子列表中您修改第 x 个元素。

那么“修改”在 Haskell 中是什么意思?我们当然不能变异元素2。我们需要一个函数,它接受一个列表并返回另一个列表,并基于一个对列表的单个元素进行操作的函数来执行此操作。

modifyNth :: Int -> (a->a) -> [a]->[a]

注意这有点类似于标准函数map :: (a->b) -> [a]->[b]

一旦你有了这个功能,你就可以轻松实现

modifyXY :: Int -> Int -> (a->a) -> [[a]]->[[a]]
modifyXY x y f nList = modifyNth y (modifyNth x f) nList

(顺便说一下nList参数不用写,η-reduce就可以了)。


1至于为什么这是个好主意:显然,它允许您在更一般的设置中使用该功能.但更重要的是,它为类型检查器提供了额外的信息,即您不会对包含的元素本身做任何事情。这实际上有助于在更复杂的应用程序中捕获大量错误!

2实际上你可以在 ST monad 中,即使语义相当好。

【讨论】:

  • 感谢详细的解释!我真的很感激!
【解决方案2】:

让我们把这个问题分解成两个函数,一个用新的字符替换字符串中的元素,另一个用于字符串列表。

我会推荐类似的东西:

replaceCharInStr :: Int -> Char -> String -> String
replaceCharInStr 0 c (s:ss) = c:ss
replaceCharInStr n c (s:ss) = s : ???
replaceCharInStr n c [] = error "replaceCharInStr: Empty string"

这里我们说如果n 为0,则忽略c 字符串的第一个元素,然后如果n 不为0 并且列表至少有一个元素,则将该元素放在前面(练习留给读者。提示:递归),然后如果我们的字符串为空,则引发错误。我会说我不是特别喜欢这里使用error,最好返回Maybe String,或者我们可以说replaceCharInStr n c [] = [c]。我们还可以将类型签名更改为replaceCharInStr :: Int -> a -> [a] -> [a],因为这不是特定于字符串的。

对于下一个函数,我们要做的是获取一个索引,并在该索引处应用一个函数。一般来说,这个函数会有类型

applyAt :: Int -> (a -> a) -> [a] -> [a]

并且可以与replaceCharInStr 类似地实现

applyAt :: Int -> (a -> a) -> [a] -> [a]
applyAt 0 f (x:xs) = f x : xs
applyAt n c (x:xs) = x : ???
applyAt n c [] = error "applyAt: Empty list"

实际上,这与replaceCharInStr 的形状完全相同,所以如果你实现了这个,那么你应该能够按照applyAt 来实现replaceCharInStr

replaceCharInStr n c xs = applyAt n (\x -> c) xs
-- Or                   = applyAt n (const c) xs

那么你的replaceChar函数可以实现为

replaceChar :: Int -> Int -> Char -> [String] -> [String]
replaceChar n m c strings = applyAt n (replaceCharInStr m c) strings
-- Or                     = applyAt n (applyAt m (const c)) strings

剩下的就是实现applyAt

【讨论】:

  • StackOverflow 需要一些关于其他回答者正在进行的工作的指标......同时几乎相同的答案令人沮丧。 (当然你的更完整。)
  • @leftaroundabout 我同意,但至少这样 OP 会得到两个措辞不同的相似答案,这通常有助于更好地理解问题。即使解释是相同的,有时我发现两个不同的人解释它更好,因为使用的精确措辞可以帮助使事情“点击”。你也有一个很好的答案,而且你提到了一些我没有提到的东西,比如不可变变量和 eta-reduction,这很值得了解。
【解决方案3】:

如果您有 Edward Kmett 的 Lens 包,那么您的示例就是单线:

import Control.Lens
["abc", "def", "ghi"] & ix 1 . ix 2 .~ 'd'

返回

["abc","ded","ghi"]

Lens 可以模拟您对命令式语言所期望的索引和属性访问,但在 Haskell 中。如果您刚刚开始学习 Haskell,您可能应该在使用 Lens 之前稍等片刻。它既聪明又强大,但也庞大而复杂。

【讨论】:

    【解决方案4】:

    试试这个:

    replace n 0 c (x:xs) = (replace' n c x) : xs
    replace n m c (x:xs) = x : (replace n (m-1) c xs)
      where
        replace' 0 c (x:xs) = c : xs
        replace' n c (x:xs) = x : (replace' (n-1) c xs)
    

    在这里,您只需遍历列表,直到对应的索引为0,然后我们替换匹配列表中的字符。我们使用相同的原则来替换列表中的字符。我们遍历它,当我们到达指定的索引时,我们用我们的新索引替换那个索引处的字符。

    最后,所有东西都被相互替换以替换旧结构,这次是替换了字符。

    【讨论】:

    • 这给了我一个错误 Not in scope: replace' 在第二行
    • @BobTheBuilder 已更正
    • 感谢您阐明该功能的作用,但由于某种原因,replace' 不在范围内。我确实有正确的缩进,不知道为什么它会给出错误。因此,我尝试将函数拆分为两个不同的函数,但出现文本不在范围内的错误。
    • 简化并删除了超出范围的错误(希望如此)(移动回答,抱歉:D)
    • 别担心!但由于某种原因,它仍然超出范围,如果我将它重写为两个单独的函数 replace 和 replace' 它似乎不起作用。它运行但数组没有替换值。
    猜你喜欢
    • 1970-01-01
    • 2017-12-01
    • 1970-01-01
    • 2015-10-24
    • 2021-04-27
    • 1970-01-01
    • 1970-01-01
    • 2016-05-07
    • 2021-09-26
    相关资源
    最近更新 更多