如果您将整个文本文件作为单个 string 传递,您的第一个循环很容易遇到内存不足的异常!
// imagine if s.Length was 100k or so
for (int i = 0; i < s.Length; i++)
{
AddString(s.Substring(i, s.Length-i));
}
在读取文件以构建 trie 时,您需要拆分每一行并可能对字符进行规范化:
string line;
while (null != (line = reader.ReadLine()))
{
string[] parts = line.Split(' ', ',', '.', '!', '\t', '?'); // naive
foreach (string part in parts)
{
if (part.Length > 0)
{
// make each string uppercase so as to avoid Hello and hello being
// two trie entries
trie.AddSuffix(part.ToUpperInvariant());
}
}
}
例如(dir /b c:\windows 的输出):
A
D
D
I
N
S
E
D
P
P
C
O
M
P
A
T
P
A
T
C
H
...
为了适当地处理较大的文件,需要更紧凑的 trie 结构。我只会将未共享的后缀存储在单独的字典中:
// If you add a character, but there is no entry in m_children
// just park the tail end of it here
Dictionary<char, string> m_tails;
然后,您可以将每个字符的逻辑移动到您的 AddString 或 SuffixNode:
public void AddString(string s)
{
if (s.Length == 0) return;
char c = s[0];
if (m_children.ContainsKey(c))
{
if (s.Length > 1) m_children[c].AddString(s.Substring(1));
}
else if (m_tails.ContainsKey(c))
{
SuffixNode node = new SuffixNode();
node.AddString(m_tails[c]);
if (s.Length > 1) node.AddString(s.Substring(1));
m_children.Add(c, node);
m_tails.Remove(c);
}
else
{
m_tails.Add(c, s.Length > 1 ? s.Substring(1) : "");
}
}
现在您有了一个更紧凑的 trie 版本,这将大大减少为任何给定语料库创建的子 SuffixNodes 的数量。回到dir /b c:\windows 的例子,我们可以看到节点的实际减少:
A
P
P
COMPAT
PATCH
I
T
I
O
N
S
...
此时,您的 trie 具有更有效的表示形式。您需要确定如何处理终端节点表示,以确保查找准确。