上位N個の最も頻繁に使用される単語に関心があり、正確である必要がない場合は、非常に巧妙な構造を使用できます。ウディ・マンバー経由でこれを聞いた、それは次のように機能します:
N個の要素の配列を作成し、各要素が値とカウントを追跡し、この配列にインデックスを付けるカウンターも保持します。さらに、値からその配列へのインデックスへのマップがあります。構造を値(テキストストリームからの単語など)で更新するたびに、最初にマップをチェックして、その値のカウントをインクリメントする場合は、その値がすでに配列にあるかどうかを確認します。そうでない場合は、カウンターが指している要素のカウントをデクリメントしてから、カウンターをインクリメントします。
これは単純に聞こえます。アルゴリズムについては何も有用ではないように見えますが、一般的な実際のデータの場合は非常にうまくいく傾向があります。通常、上位N個を追跡する場合は、空の値が多数含まれるため、10*Nの容量でこの構造を作成することをお勧めします。欽定訳聖書を入力として使用すると、この構造が最も頻繁に使用される単語として(順不同で)リストされます。
0 : in
1 : And
2 : shall
3 : of
4 : that
5 : to
6 : he
7 : and
8 : the
9 : I
そして、ここに(順番に)トップ10の最も頻繁な単語があります:
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要素のみのスペースを使用して正解したことがわかります。ユースケースによっては、ここでのスペースの節約が非常に役立つ場合があります。また、非常に高速です。
これが私が使用したtopNの実装で、Goで書かれています。
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)
}