在Haskell中入门字符串

3

我需要编写一个函数,它可以接收一个由 gggggggeeeeetttttt 组成的字符串,并能够计算每个字母重复出现的次数,并将结果输出为 g7e5t6。

我刚刚开始学习 Haskell,对如何开始完全没有头绪。


@FUZxxl:非常苛刻。在获得15个声望之前,您实际上无法点赞。欢迎来到本网站。 - Alex M
@amccausl:抱歉,我忘了那件事。 - fuz
4个回答

13
函数group可以将列表中相同的元素分组在一起。由于字符串是字符列表,因此我们有以下代码:
group "ggggeeetttt" = ["gggg","eee","tttt"]

为了获得这个字母,我们可以使用map head,因为head会获取字符串(或任何列表)的第一个元素:
map head ["gggg","eee","tttt"] = ['g','e','t']

现在,我们想知道每个子字符串中有多少个元素。可以使用 map length 来完成:
map length ["gggg","eee","tttt"] = [4,3,4]

让我们把这些数字转换回字符串:

map show [4,3,4] = ["4","3","4"]

现在我们需要以某种方式将原始列表和长度列表组合起来。我们可以按照以下步骤进行:

zipWith (:) ['g','e','t'] ["4","3","4"]

zipWith 函数将对两个列表中的成对元素应用指定的函数。这里我使用了 (:),它会在列表开头添加一个元素。

这为您提供了实现所需功能的所有构建块。例如,尽管我没有测试过,但以下代码应该可以工作:

f s = concat $ zipWith (:) letters lengths
    where
        groups  = group s
        letters = map head groups
        lengths = map (show . length) groups

我还应该说一下,你需要导入Data.List才能使用其中的一些列表操作函数。 - chrisdb
不错的回答。如果您省略最后的“总结”,让OP自己整理会更好。 - luqui
是的,我曾经在辩论是否将它放在那里。也许没有放那里会更好。 - chrisdb
在稍微不同的话题上,Eric,你理解使用(.),($)和concat的含义吗?我意识到在发布函数之前没有定义它们。 - chrisdb

7

使用推导式的变体,我认为稍微更加美观(考虑之前的解释只是代码):

ghci> let s = "gggggggeeeeetttttt"
ghci> putStrLn $ concat [head g: show (length g) | g <- group s]
g7e5t6

他问如何入门,而不是如何从网站上阅读完整的解决方案。这很可能是作业,并且你的答案很可能是作业问题的有效答案。这并没有帮助。 - luqui
@luqui:除了解析器之外,这里的所有内容都已经在之前解释过了。我认为在ghci中阅读和操作代码非常容易理解,即使它实际上是一份作业。因此,作为辅助答案,它可能很有用。 - gorlum0

3
开始的最佳方式是找出你的问题。我建议使用以下方法:
1.将字符串分割为相等字符的子字符串。因此,您需要一个接受字符串并返回字符串列表([String])的函数。 例如:splitStr "gggggggeeeeetttttt" 应返回 ["ggggggg","eeeee","tttttt"] 幸运的是,模块Data.List已经提供了这样的函数,它被命名为group。
2.计算重复次数和重复的字母。重复次数就是子串的长度,可以使用length函数进行计算。
3.获取重复的字符并将其放在重复次数前面。对于重复的字符,我们可以使用列表中的任何一个,我们取第一个。要计算列表(也称为字符串)的第一个元素,可以使用head函数。例如,head "abc" 产生 'a'。 要将其置于长度前面,可以使用一个小助手函数和show函数,将大多数内容转换为字符串。我们的助手看起来像这样:makeRepetitions string = head string : show (length string)。:(也称为cons)将元素添加到列表的前面。
4.将步骤1中字符串列表的所有元素应用步骤3的助手。我们使用map来实现。map将函数应用于值列表,并返回结果列表。
5.将子列表连接到一起以获得结果。我们可以使用concat进行此操作。实际上,我们可以使用concatMap结合步骤4和5。此函数将函数映射到值列表并连接结果,正如我们想要的那样。
现在,您的代码看起来像这样:
import Data.List

runlength :: String -> String
runlength string = concatMap makeRepetitions (group string)) where
  makeRepetitions string = head string : show (length string)

由于太多的括号很让人烦恼,Haskeller们经常使用.。这个点号将两个函数组合起来创建一个新的函数。f = functionA . functionB可以理解为f x = functionA (functionB x)。使用这个点号,我们可以稍微调整程序格式:

import Data.List

runlength :: String -> String
runlength = concatMap makeRepetitions . group where
  makeRepetitions string = head string : show (length string)

在我看来,这种表达方式更易读。您可以将runlength视为管道。首先应用group,然后我们使用concatMapmakeRepetitions映射到输入上,并连接结果。


再次感谢您的回答,如果您没有在最后为他总结一切,那就更好了。我会在第5点后结束它。 - luqui

1

箭头滥用和点无关 :)

f = group >>> concatMap (head &&& (show . length) >>> uncurry (:))

这里是应用程序:

f = concatMap ((:) <$> head <*> (show . length)) . group

我知道你只是在玩,但编写一个以密集不透明的方式完成任务的函数对于初学者来说并没有帮助,特别是如果这可能是作业。 - luqui
2
我确实说过这是一种滥用行为。为什么要踩我?展示不良实践并明确将其定义为不良是很好的。 - Vagif Verdi

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接