如果您只对前 N 个最常用的词感兴趣,并且不需要精确,那么您可以使用一个非常聪明的结构。我是通过 Udi Manber 听说的,它的工作原理如下:
您创建了一个包含 N 个元素的数组,每个元素跟踪一个值和一个计数,您还保留了一个索引该数组的计数器。此外,您有一个从值到索引到该数组的映射。
每次用一个值(如文本流中的一个词)更新结构时,您首先检查您的地图以查看该值是否已经在您的数组中,如果是,则增加该值的计数。如果不是,则减少计数器指向的任何元素的计数,然后增加计数器。
这听起来很简单,算法的任何内容都使它看起来会产生任何有用的东西,但对于典型的真实数据,它往往做得很好。通常,如果您希望跟踪前 N 个事物,您可能希望使这个结构的容量为 10*N,因为其中会有很多空值。使用 King James Bible 作为输入,以下是该结构列出的最常用词(无特定顺序):
0 : in
1 : And
2 : shall
3 : of
4 : that
5 : to
6 : he
7 : and
8 : the
9 : I
这里是前十个最常用的词(按顺序):
0 : the , 62600
1 : and , 37820
2 : of , 34513
3 : to , 13497
4 : And , 12703
5 : in , 12216
6 : that , 11699
7 : he , 9447
8 : shall , 9335
9 : unto , 8912
您会看到,前 10 个单词中有 9 个是正确的,而且它只使用了 50 个元素的空间。根据您的用例,此处节省的空间可能非常有用。它也非常快。
这是我用 Go 编写的 topN 的实现:
type Event string
type TopN struct {
events []Event
counts []int
current int
mapped map[Event]int
}
func makeTopN(N int) *TopN {
return &TopN{
counts: make([]int, N),
events: make([]Event, N),
current: 0,
mapped: make(map[Event]int, N),
}
}
func (t *TopN) RegisterEvent(e Event) {
if index, ok := t.mapped[e]; ok {
t.counts[index]++
} else {
if t.counts[t.current] == 0 {
t.counts[t.current] = 1
t.events[t.current] = e
t.mapped[e] = t.current
} else {
t.counts[t.current]--
if t.counts[t.current] == 0 {
delete(t.mapped, t.events[t.current])
}
}
}
t.current = (t.current + 1) % len(t.counts)
}