最近在一个项目中需要针对上百万条(大约在800W条)的字符串进行相关的处理。该字符串是以文本的形式存放在本地硬盘,并且更新频率为20分钟一次。
具体需求:
1、判断某一个字符串是否存在这800W条字符串中,时间要求在5ms以内
2、根据字符串前缀,返回包含此前缀的10-100条字符串。时间要求在5ms以内
3、占用内存要小
本人接到此需求第一反应用List,或者二叉树啥的。结果效率慢得死,
后经过大量资料查阅,发现采用DAWG(Directed Acyclic Word Graph)和Trie可以很快速的查找到相应的字符串。
主要思路:将每个字符串的字符进行创建相应的Node,如果有相同的字符则放置在同一个节点中。
例如有6个字符串:Top, Tops, Tap, Tapc, Topa and Taps。根据DAWG和Trie的原理将创建成如下一个Tree
在添加字符串的时候,根据字符串的字符创建树形结构,每个节点代表一个字符。如果是相同字符时,则存储在同一个节点中。这样可少占用内存。
在进行判断字符串是否存在时,只需要依次判断字符串的节点是否存在。
首先创建一个字符节点类LetterNode. 该类主要用于存储相关字符节点的信息:子节点,父节点等等。
View Code
///
<summary>
///
The Dawg's letter node
///
</summary>
internal
sealed
class
LetterNode
{
private
const
int
InitialSpreadCapacity
=
4
;
private
Dictionary
<
char
, LetterNode
>
_childNodes;
///
<summary>
///
Initializes a new instance of the
<see cref="LetterNode"/>
class.
///
</summary>
public
LetterNode()
{
}
///
<summary>
///
Initializes a new instance of the
<see cref="LetterNode"/>
class.
///
</summary>
///
<param name="letter">
The letter.
</param>
public
LetterNode(
char
letter)
:
this
()
{
Letter
=
letter;
}
///
<summary>
///
The node's child nodes
///
</summary>
public
Dictionary
<
char
, LetterNode
>
ChildNodes
{
get
{
if
(
this
._childNodes
==
null
)
{
this
._childNodes
=
new
Dictionary
<
char
, LetterNode
>
(InitialSpreadCapacity);
}
return
this
._childNodes;
}
}
///
<summary>
///
Describe the node is end of the word.
///
</summary>
public
bool
IsEndOfWord
{
get
;
set
;
}
///
<summary>
///
Gets or sets the letter.
///
</summary>
///
<value>
The letter.
</value>
public
char
Letter
{
get
;
set
;
}
///
<summary>
///
Gets or sets the parent of this node.
///
</summary>
///
<value>
The parent.
</value>
public
LetterNode Parent
{
get
;
set
;
}
///
<summary>
///
Gets the word defined at this element.
///
</summary>
///
<value>
The word ending here (if this is a word, or the empty string.
</value>
public
string
Word
{
get
{
if
(IsEndOfWord)
{
StringBuilder sb
=
new
StringBuilder(
20
);
sb.Append(Letter);
var node
=
Parent;
while
(node
!=
null
)
{
sb.Insert(
0
, node.Letter);
node
=
node.Parent;
}
return
sb.ToString();
}
else
{
return
string
.Empty;
}
}
}
}
接下来实现Dawg针对字符串创建树形节点。思路是参照上图。
///
<summary>
///
Adds the specified item.
///
</summary>
///
<param name="item">
The item.
</param>
public
void
Add(
string
item)
{
if
(
string
.IsNullOrEmpty(item))
{
return
;
}
item
=
item.ToLowerInvariant();
LetterNode node
=
null
, parentNode
=
null
;
if
(
!
this
._rootNodes.TryGetValue(item[
0
],
out
node))
{
node
=
new
LetterNode(item[
0
]);
this
._rootNodes[item[
0
]]
=
node;
}
for
(
int
i
=
1
; i
<
item.Length;
++
i)
{
parentNode
=
node;
if
(
!
node.ChildNodes.TryGetValue(item[i],
out
node))
{
node
=
new
LetterNode(item[i]);
node.Parent
=
parentNode;
parentNode.ChildNodes[item[i]]
=
node;
}
}
if
(
!
node.IsEndOfWord)
{
node.IsEndOfWord
=
true
;
this
._count
++
;
}
}
判断字符串是否存在:
private
LetterNode SearchPrefixLetterNode(
string
prefix)
{
prefix
=
prefix.ToLowerInvariant();
Dictionary
<
char
, LetterNode
>
nodes
=
this
._rootNodes;
LetterNode node
=
null
;
for
(
int
i
=
0
; i
<
prefix.Length;
++
i)
{
if
(nodes.TryGetValue(prefix[i],
out
node))
{
nodes
=
node.ChildNodes;
}
else
{
return
null
;
}
}
return
node;
}
public
bool
Contains(
string
item)
{
var node
=
this
.SearchPrefixLetterNode(item);
return
node
!=
null
&&
node.IsEndOfWord;
}
代码可从此处下载:http://files.cnblogs.com/foolishfox/Dawg.zip