【问题标题】:Haskell: Traverse through a String/Text FileHaskell:遍历字符串/文本文件
【发布时间】:2013-02-16 18:02:41
【问题描述】:

我正在尝试读取脚本文件,然后将其处理并输出到 html 文件。在我的脚本文件中,只要有 @title(this is a title),我就会在我的 html 中添加标签 [header] this is a title [/header]输出。所以我的做法是先读取脚本文件,将内容写入字符串,处理字符串,再将字符串写入html文件。

为了识别@title,我需要逐个字符地读取字符串。当我阅读“@”时,我需要检测下一个字符以查看它们是否正确。

问题:如何在 Haskell 中遍历一个字符串(这是一个 char 列表)?

【问题讨论】:

  • 写一个解析器。你可以在短期内做其他更简单的技巧,但你以后会后悔的。
  • 关于解析器的话题,Parsec 拥有。
  • @CatPlusPlus 这值得商榷。在性能方面,Attoparsec 可能经常超过它。

标签: parsing haskell parsec attoparsec


【解决方案1】:

你可以使用一个简单的递归技巧,例如

findTag [] = -- end of list code.
findTag ('@':xs)
  | take 5 xs == "title" = -- your code for @title
  | otherwise            = findTag xs
findTag (_:xs) = findTag xs

所以基本上你只是模式匹配如果下一个字符(列表头)是'@',然后你检查接下来的 5 个字符是否形成“标题”。如果是这样,您可以继续解析代码。如果下一个字符不是'@',你只需继续递归。一旦列表为空,您将到达第一个模式匹配。

其他人可能有更好的解决方案。

我希望这能回答你的问题。

编辑:

为了更灵活一点,如果你想找到一个特定的标签,你可以这样做:

findTag [] _ = -- end of list code.
findTag ('@':xs) tagName
  | take (length tagName) xs == tagName = -- your code for @title
  | otherwise = findTag xs
findTag (_:xs) _ = findTag xs

如果你这样做,就这样

findTag text "title"

您将专门寻找标题,并且您可以随时将标记名更改为您想要的任何内容。

另一个编辑:

findTag [] _ = -- end of list code.
findTag ('@':xs) tagName
  | take tLength xs == tagName = getTagContents tLength xs
  | otherwise = findTag xs
  where tLength = length tagName
findTag (_:xs) _ = findTag xs

getTagContents :: Int -> String -> String
getTagContents len = takeWhile (/=')') . drop (len + 1) 

说实话,这有点混乱,但这是发生的事情:

您首先删除 tagName 的长度,然后再删除一个用于左括号的长度,然后使用 takeWhile 将字符提取到右括号。

【讨论】:

  • 感谢您的建议!
【解决方案2】:

显然您的问题属于解析类别。正如 Daniel Wagner 明智地指出的那样,出于可维护性的原因,您最好使用解析器来处理它。

另一件事是,如果您想有效地处理文本数据,最好使用Text 而不是String

以下是使用 Attoparsec 解析器库解决问题的方法:

-- For autocasting of hardcoded strings to `Text` type
{-# LANGUAGE OverloadedStrings #-}

-- Import a way more convenient prelude, excluding symbols conflicting 
-- with the parser library. See
-- http://hackage.haskell.org/package/classy-prelude
import ClassyPrelude hiding (takeWhile, try)
-- Exclude the standard Prelude
import Prelude ()
import Data.Attoparsec.Text

-- A parser and an inplace converter for title
title = do
  string "@title("
  r <- takeWhile $ notInClass ")"
  string ")"
  return $ "[header]" ++ r ++ "[/header]"

-- A parser which parses the whole document to parts which are either
-- single-character `Text`s or modified titles
parts = 
  (try endOfInput >> return []) ++
    ((:) <$> (try title ++ (singleton <$> anyChar)) <*> parts)

-- The topmost parser which concats all parts into a single text
top = concat <$> parts

-- A sample input
input = "aldsfj@title(this is a title)sdlfkj@title(this is a title2)"

-- Run the parser and output result
main = print $ parseOnly top input

这个输出

Right "aldsfj[header]this is a title[/header]sdlfkj[header]this is a title2[/header]"

附: ClassyPrelude 将++ 重新实现为Monoidmappend 的别名,因此您可以根据需要将其替换为mappend&lt;&gt;Alternative&lt;|&gt;

【讨论】:

    【解决方案3】:

    对于模式搜索和替换,您可以使用 streamEdit.

    import Replace.Megaparsec
    import Text.Megaparsec
    import Text.Megaparsec.Char
    
    title :: Parsec Void String String
    title = do
        void $ string "@title("
        someTill anySingle $ string ")"
    
    editor t = "[header]" ++ t ++ "[/header]"
    
    streamEdit title editor " @title(this is a title) "
    
    " [header]this is a title[/header] "
    

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2020-12-28
      • 2016-10-04
      • 1970-01-01
      • 2016-07-21
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2016-07-06
      相关资源
      最近更新 更多