【问题标题】:parsing words in a continuous string解析连续字符串中的单词
【发布时间】:2011-09-18 11:48:28
【问题描述】:

如果 a 有一个包含单词且没有空格的字符串,鉴于我有一个包含这些单词的字典/列表,我应该如何解析这些单词?

例如,如果我的字符串是“thisisastringwithwords”,我如何使用字典来创建输出“this is a string with words”?

我听说使用数据结构Tries 可能会有所帮助,但也许有人可以帮助处理伪代码?例如,我在想也许你可以将字典索引到一个 trie 结构中,然后沿着 trie 中的每个字符;问题是,我不熟悉如何在(伪)代码中执行此操作。

【问题讨论】:

  • 不可能的任务。如果原始字符串的公共部分中存在两个不同的单词会怎样? “inthemississippi”有“miss”、“is”和“pi”这三个词,但应该真正找到“mississippi”。
  • 寻找最长的单词或为此提供某种规则。无论如何,这不是一件容易的事..
  • 我同意这是不可能的。你在哪里需要它???
  • 最佳猜测的解决方案怎么样?我知道这不会是完美的。
  • @Jason Jong,cfarm54's ("thisisastringwithwords") 帖子中的字符串给我的不是你的意思,而是这个帖子。

标签: c# algorithm data-structures text-segmentation


【解决方案1】:

如果您有单词词典并且需要快速实现,这可以通过 O(n^2) 时间内的动态编程有效地解决,假设词典查找为 O(1)。下面是一些 C# 代码,可以改进子字符串提取和字典查找。

public static String[] StringToWords(String str, HashSet<string> words)
{      
  //Index of char - length of last valid word
  int[] bps = new int[str.Length + 1];

  for (int i = 0; i < bps.Length; i++)      
    bps[i] = -1;

  for (int i = 0; i < str.Length; i++)
  {
    for (int j = i + 1; j <= str.Length ; j++)
    {
      if (bps[j] == -1)
      {
        //Destination cell doesn't have valid backpointer yet
        //Try with the current substring
        String s = str.Substring(i, j - i);
        if (words.Contains(s))
          bps[j] = i;
      }
    }        
  }      

  //Backtrack to recovery sequence and then reverse 
  List<String> seg = new List<string>();
  for (int bp = str.Length; bps[bp] != -1 ;bp = bps[bp])      
    seg.Add(str.Substring(bps[bp], bp - bps[bp]));      
  seg.Reverse();
  return seg.ToArray();
}

使用 /usr/share/dict/words 中的单词列表构建一个 hastset 并使用

进行测试
foreach (var s in StringSplitter.StringToWords("thisisastringwithwords", dict))
    Console.WriteLine(s);

我得到输出“t hi sis a string with words”。因为正如其他人指出的那样,该算法将返回有效的分段(如果存在),但这可能不是您期望的分段。短词的存在降低了分割质量,如果两个有效的子分割进入一个元素,您也许可以添加启发式以偏爱较长的词。

有更复杂的方法,例如有限状态机和语言模型,可以生成多个分段并应用概率排名。

【讨论】:

    【解决方案2】:

    好的,我会尝试一下。您的问题的完美(ish)数据结构(正如您所说的那样)由字典中的单词组成。最好将 trie 可视化为 DFA,这是一个很好的状态机,您可以在其中从一个状态转到每个新角色的下一个状态。这在代码中很容易做到,Java(ish) 风格的类是:

    Class State 
    {
       String matchedWord;
       Map<char,State> mapChildren;
    }
    

    从这里开始,构建 trie 就很容易了。它就像一个有根的树结构,每个节点都有多个子节点。每个孩子都在一个字符转换上被访问。使用HashMap 类型的结构可以减少查找字符到下一个State 映射的时间。或者,如果您只有 26 个字母表字符,fixed size array of 26 也可以解决问题。

    现在,假设所有这些都说得通,您试了一下,您的问题仍未完全解决。这是您开始做正则表达式引擎之类的事情的地方,沿着特里树走,跟踪与字典中整个单词匹配的状态(这就是我在State 结构中的matchedWord),使用如果当前路径遇到死胡同,一些回溯逻辑可以跳转到先前的匹配状态。我知道它的一般性,但考虑到 trie 结构,其余部分相当简单。

    【讨论】:

      【解决方案3】:

      这正是人们在尝试以编程方式解析诸如中文之类的语言时遇到的问题,其中单词之间没有空格。一种适用于这些语言的方法是首先在标点符号上拆分文本。这给了你短语。接下来,您遍历短语并尝试将它们分解为从字典中最长单词的长度开始的单词。假设长度为 13 个字符。从短语中取出前 13 个字符,看看它是否在您的字典中。如果是这样,现在把它当作一个正确的词,在短语中前进并重复。否则,将子字符串缩短为 12 个字符,然后是 11 个字符,等等。

      这非常有效,但并不完美,因为我们不小心对先出现的单词产生了偏见。消除这种偏见并仔细检查结果的一种方法是从短语末尾开始重复该过程。如果你得到相同的单词中断,你可能会称之为好。如果没有,你有一个重叠的词段。例如,当您从末尾开始解析示例短语时,您可能会得到(向后强调)

      words with string a Isis th
      

      起初,Isis(埃及女神)这个词似乎是正确的词。但是,当您发现“th”不在您的字典中时,您就知道附近存在分词问题。通过对未对齐序列“thisis”使用正向分割结果“this is”来解决此问题,因为这两个词都在字典中。

      这个问题的一个不太常见的变体是当相邻的单词共享一个可以去任何方向的序列时。如果你有一个像“archand”(编造一些东西)这样的序列,它应该是“arc hand”还是“arch and”?确定的方法是对结果应用语法检查器。无论如何,这应该对整个文本进行。

      【讨论】:

        【解决方案4】:

        如果您确定字典中有该短语的所有单词,则可以使用该算法:

        String phrase = "thisisastringwithwords";
        String fullPhrase = "";
        Set<String> myDictionary;
        do {
            foreach(item in myDictionary){
                if(phrase.startsWith(item){
                    fullPhrase += item + " ";
                    phrase.remove(item);
                    break;
                }
            }
        } while(phrase.length != 0);
        

        有这么多的复杂性,比如,一些项目开始相同,所以代码将被更改为使用一些树搜索,BST 左右。

        【讨论】:

        • 嗯。如果您这样做,则每次通过循环时都需要删除短语的第一部分。因为它是你有一个无限循环 - 这将产生“这个这个这个.....”
        • 搞定了,我只是打了一个代码的schema,不会打满,要控制的异常太多了。
        【解决方案5】:

        我已经做过类似的事情了。您不能使用简单的字典。结果会很乱。这取决于您是否只需执行一次或作为整个程序执行此操作。

        我的解决方案是:

        1. 连接到工作正常的数据库 字典列表中的单词(对于 在线词典示例)
        2. 过滤字典中的长词和短词并检查是否要修剪内容(例如,不要使用只有一个字符的词,如 'I')
        3. 从简短的单词开始,将您的 bigString 与数据库字典进行比较。

        现在您需要创建一个“可能性表”。因为很多词可以100%适应但都是错误的。你越确定这个词越长,这个词就是正确的。

        它是 cpu 密集型的,但它可以在结果中精确地工作。 因此,假设您使用的是 10,000 个单词的小字典,其中 3,000 个单词的长度为 8 个字符,您需要在开始时将 bigString 与所有 3,000 个单词进行比较,并且只有找到结果后,才允许继续下一个词。如果您的 bigString 中有 200 个字符,您需要大约 (2000chars / 8 average chars) = 250 个完整循环,并进行比较。

        对我来说,我还对拼写错误的单词进行了小验证,以进行比较。

        过程示例(请勿复制粘贴)

            Dim bigString As String = "helloworld.thisisastackoverflowtest!"
        
            Dim dictionary As New List(Of String) 'contains the original words. lets make it case insentitive
            dictionary.Add("Hello")
            dictionary.Add("World")
            dictionary.Add("this")
            dictionary.Add("is")
            dictionary.Add("a")
            dictionary.Add("stack")
            dictionary.Add("over")
            dictionary.Add("flow")
            dictionary.Add("stackoverflow")
            dictionary.Add("test")
            dictionary.Add("!")
        
        
            For Each word As String In dictionary
                If word.Length < 1 Then dictionary.Remove(word) 'remove short words (will not work with for each in real)
                word = word.ToLower 'make it case insentitive
            Next
        
            Dim ResultComparer As New Dictionary(Of String, Double) 'String is the dictionary word. Double is a value as percent for a own function to weight result
        
            Dim i As Integer = 0 'start at the beginning
            Dim Found As Boolean = False
            Do
                For Each word In dictionary
                    If bigString.IndexOf(word, i) > 0 Then
                        ResultComparer.Add(word, MyWeightOfWord) 'add the word if found, long words are better and will increase the weight value 
                        Found = True
                    End If
                Next
                If Found = True Then
                    i += ResultComparer(BestWordWithBestWeight).Length
                Else
                    i += 1
                End If
            Loop
        

        【讨论】:

          【解决方案6】:

          我假设您想要一个有效的解决方案,而不是您反复检查文本是否以字典单词开头的显而易见的解决方案。

          如果字典足够小,我想你可以尝试修改标准的KMP 算法。基本上,在您的字典上构建一个有限状态机,它会逐个字符地使用文本并生成构建的单词。

          编辑:看来我是在重塑 tries。

          【讨论】:

          • 如果您使用的是某个版本的尝试,您能否提供某种示例代码(伪代码或 c#)来帮助得出解决方案?
          • 不,抱歉。任何比 Wiki 文章更详细的内容都会花费我很多时间。
          【解决方案7】:

          我告诉过你,这似乎是一项不可能完成的任务。但是你可以看看this related SO question——它可能会对你有所帮助。

          【讨论】:

            猜你喜欢
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 2013-06-17
            • 2013-06-10
            • 1970-01-01
            • 2019-11-29
            • 1970-01-01
            • 2011-01-19
            相关资源
            最近更新 更多