2

我正在编写一个程序,它将根据某些特定规则对输入文本进行标记。我为此使用 C++。

规则

Letter 'a' should be converted to token 'V-A'
Letter 'p' should be converted to token 'C-PA'
Letter 'pp' should be converted to token 'C-PPA'
Letter 'u' should be converted to token 'V-U'

这只是一个示例,实时我有大约 500 多个这样的规则。如果我将输入作为“ appu ”提供,它应该像“ VA + C-PPA + VU ”一样标记化。我已经实现了一个算法来做到这一点,并想确保我做的是正确的事情。

算法

所有规则都将保存在一个 XML 文件中,并具有到令牌的相应映射。就像是

<rules>
  <rule pattern="a" token="V-A" />
  <rule pattern="p" token="C-PA" />
  <rule pattern="pp" token="C-PPA" />
  <rule pattern="u" token="V-U" />
</rules>

1 - 应用程序启动时,读取此 xml 文件并将值保存在“ std::map ”中。这将在应用程序结束之前可用(单例模式实现)。

2 - 迭代输入的文本字符。对于每个字符,查找匹配项。如果找到,变得更加贪婪并通过从输入文本中获取下一个字符来寻找更多匹配项。这样做,直到我们得到一个不匹配。因此,对于输入文本“ appu ”,首先查找“ a ”的匹配项。如果找到,请尝试通过从输入文本中获取下一个字符来获得更多匹配。所以它会尝试匹配 ' ap ' 并没有找到匹配项。所以它只是返回。

3 - 替换输入文本中的字母“a”,因为我们得到了它的标记。

4 - 对输入文本中的剩余字符重复步骤 2 和 3。

下面是更简单的步骤说明

input-text = 'appu'
tokens-generated=''

// First iteration
character-to-match = 'a'
pattern-found = true

// since pattern found, going recursive and check for more matches
character-to-match = 'ap'
pattern-found = false

tokens-generated = 'V-A'

// since no match found for 'ap', taking the first success and replacing it from input text
input-text = 'ppu'

// second iteration
character-to-match = 'p'
pattern-found = true

// since pattern found, going recursive and check for more matches
character-to-match = 'pp'
pattern-found = true

// since pattern found, going recursive and check for more matches
character-to-match = 'ppu'
pattern-found = false

tokens-generated = 'V-A + C-PPA'

// since no match found for 'ppu', taking the first success and replacing it from input text
input-text = 'u'

// third iteration
character-to-match = 'u'
pattern-found = true

tokens-generated = 'V-A + C-PPA + V-U'  // we'r done!

问题

1 - 这个算法看起来很好解决这个问题还是有更好的方法来解决这个问题?

2 - 如果这是正确的方法,std::map 是一个不错的选择吗?还是我需要创建自己的键/值容器?

3 - 是否有可以像上面一样标记字符串的库?

任何帮助,将不胜感激

:)

4

5 回答 5

3

所以你要遍历地图中的所有标记来寻找匹配项?您不妨在那里使用列表或数组;无论如何,这将是一个低效的搜索。

找到适合开始或继续比赛的标记的一种更有效的方法是将它们存储为trie。在那里查找一个字母会给你一个子树,它只包含以那个字母作为第一个字母的标记,然后你就可以继续向下搜索。


编辑:让我进一步解释一下。

首先,我应该解释一下,除了名称之外,我对这些 C++ 并不熟悉std::map,这使得这是一个完美的例子,说明为什么人们要学习这些东西的理论以及特定编程语言中特定库的细节:除非库严重滥用了“map”这个名字(这不太可能),这个名字本身告诉了我很多关于数据结构特征的信息。例如,我知道会有一个函数,给定一个键和映射,将非常有效地搜索并返回与该键关联的值,并且可能还有一个函数会给你一个列表/array/whatever 所有键,您可以使用自己的代码自行搜索。

我对您的数据结构的解释是您有一个映射,其中键是您所谓的模式,那些是字符列表(或数组,或类似性质的东西),而值是标记。因此,给定一个完整的模式,您可以快速找到与其关联的令牌。

不幸的是,虽然这样的映射非常适合将您的 XML 输入格式转换为内部数据结构,但它并不适合您需要执行的搜索。请注意,您不是查找整个模式,而是查找模式的第一个字符,生成一组可能的标记,然后从第一次查找生成的模式集中查找模式的第二个字符,并且很快。

所以你真正需要的不是一个单一的地图,而是地图的地图,每个地图都由一个字符键控。在顶层查找“p”应该会为您提供一个新地图,其中包含两个键:p生成C-PPA令牌和“其他任何东西”,生成C-PA令牌。这实际上是一个 trie 数据结构。

这有意义吗?

如果您首先以这种方式编写解析代码,这可能会有所帮助:想象其他人将编写函数来执行您需要的查找,他是一个非常优秀的程序员,几乎可以做任何您想要的魔术。编写解析代码,专注于使其尽可能简单和干净,使用您需要的这些任意函数创建任何接口(同时不要变得微不足道,并用一个函数替换整个事情!)。现在您可以查看最终使用的查找函数,它会告诉您需要如何访问数据结构,这将引导您找到所需的数据结构类型。一旦你弄清楚了,你就可以弄清楚如何加载它。

于 2009-05-24T05:40:37.683 回答
1
  1. 这种方法会奏效 - 我不确定它是否有效,但它应该有效。

  2. 我会使用标准的 std::map 而不是你自己的系统。

  3. 有像lex(或flex)这样的工具可以用于此。问题在于您是否可以重新生成当 XML 规范更改时它将构建的词法分析器。如果 XML 规范不经常更改,您可以使用诸如扫描和映射之类的工具lex来更轻松地进行扫描和映射。如果 XML 规范可以随使用程序的人的心血来潮而改变,那么lex可能不太合适。

有一些警告——特别是两者都lex生成flexC 代码,而不是 C++。

我还会考虑研究模式匹配技术——特别使用的那种东西egrep。这具有可以在运行时处理的优点(因为egrep它一直在处理)。或者你可以选择一种脚本语言——Perl、Python……或者你可以考虑像 PCRE(Perl 兼容正则表达式)库之类的东西。

于 2009-05-24T05:43:58.693 回答
1

您可以使用正则表达式(也许是 boost::regex 库)。如果所有模式都只是字母串,那么像“(a|p|pp|u)”这样的正则表达式会找到一个贪婪匹配。所以:

  1. 使用上述模式运行 regex_search 以找到下一个匹配项
  2. 将匹配文本插入您的 std::map 以获取替换文本。
  3. 将不匹配的消耗输入和替换文本打印到输出,然后在剩余输入上重复 1。

并做了。

于 2009-05-24T05:46:59.687 回答
1

更好的是,如果你要使用 boost 库,总是有 Boost 标记器库 -> http://www.boost.org/doc/libs/1_39_0/libs/tokenizer/index.html

于 2009-05-24T05:55:45.410 回答
1

它可能看起来有点复杂,但最有效的方法是使用图形来表示状态图。起初,我认为boost.statechart会有所帮助,但我认为这并不合适。这种方法比使用简单的 std::map 更有效,如果有很多规则,可能的字符数是有限的,并且要读取的文本长度相当长。

所以无论如何,使用一个简单的图表:

0)创建带有“开始”顶点的图形

1)读取xml配置文件并在需要时创建顶点(从一个“字符集”(例如“pp”)转换为另外一个(例如“ppa”))。在每个顶点内,存储到下一个顶点的转换表。如果“关键文本”完整,则将顶点标记为最终文本并存储结果文本

2) 现在阅读文本并使用图表进行解释。从“开始”顶点开始。( * ) 使用表格来解释一个字符并跳转到新的顶点。如果没有选择新的顶点,则会发出错误。否则,如果新顶点是最终的,则打印结果文本并跳回开始顶点。返回 (*) 直到没有更多文本需要解释。

您可以使用boost.graph来表示图形,但我认为它对于您的需要来说过于复杂。制作您自己的自定义表示。

于 2009-05-24T21:09:13.417 回答