Aho-Korasik算法
本示例考察了Julia编程语言中Aho-Korasik算法的实现。 该算法旨在在同一时间搜索文本中的多个样本。
理论部分
[算法Ахо-Корасик](https://ru.wikipedia.org/wiki/Алгоритм_Ахо_—_Корасик )是一种高效的子字符串搜索算法,旨在查找输入文本中有限模板字符串集(字典)的所有出现。 由Alfred V.Aho和Margaret J.设计,成立于1975年,广泛用于需要同时搜索多个模式的任务,如文本处理,生物信息学和网络安全。
任务说明
给出一个文本字符串 长度 还有很多 模板 ,其总长度为 (所有图案的长度之和)。 Aho-Korasik算法查找任何模式的所有出现 在 . 具体来说,它返回所有对。 ,使得模板 匹配文本的子字符串 从位置开始 (即 该算法在同一时间搜索多个模板时特别有效,因为它避免了每个模板单独的多个文本扫描。
问题的正式陈述:
输入:文本 还有很多模板 .
输出:所有对 ,使得模板 它发生在 从位置开始 .
申请表格
Aho-Korasik算法用于以下领域:
*全文搜索系统-用于搜索文档中的多个关键字;
*入侵检测系统-用于匹配网络流量中恶意模式的签名;
*生物信息学-在DNA序列中搜索多个基序;
*数据压缩和自然语言处理-用于基于模板的分析。
主要部分
数据字段
# Все допустимые символы английского алфавита от 'a' до 'z'
const VALID_CHARS = collect('a':'z')
有限状态机节点的结构
Bohr节点/Aho-Corasick有限状态机结构的确定
mutable struct Node
son::Vector{Int} # номера дочерних вершин по каждому символу ('a'-'z')
ans::Int # количество совпадений, оканчивающихся здесь
fail::Int # ссылка на «предыдущий» узел с таким же суффиксом
du::Int # число зависимых узлов, которые должны быть обработаны после текущего
idx::Int # уникальный индекс для обнаруженного слова
end
默认构造函数:创建一个没有子节点且具有初始值的新节点
Node() = Node(zeros(Int, length(VALID_CHARS)), 0, 1, 0, 0)
Aho-Korasik自动机的结构
Aho-Korasik自动机本身的定义
mutable struct ACAutomaton
tr::Vector{Node} # массив всех узлов дерева префиксного бора / автомата
tot::Int # общее количество использованных/существующих узлов
final_ans::Vector{Int} # результат подсчета количества вхождений каждого шаблона
pidx::Int # счетчик уникальных индексов слов
end
具有最大_nodes节点和根的自动机的初始化
ACAutomaton(max_nodes) = ACAutomaton([Node() for _ in 1:max_nodes], 1, Int[], 0)
机器上的操作
将图案插入老虎机
向机器添加新模板并为其分配唯一ID的功能
function insert(ac::ACAutomaton, pattern)
u = 1 # начальный узел -- корень
for ch in pattern # проход по каждому символу шаблона
char_code = findfirst(==(ch), VALID_CHARS)
isnothing(char_code) && continue
if ac.tr[u].son[char_code] == 0 # если такого дочернего узла нет
ac.tot += 1 # выделяем новый узел
ac.tr[u].son[char_code] = ac.tot
end
u = ac.tr[u].son[char_code] # переходим к следующему узлу
end
# Присваиваем паттерну уникальный идентификатор, если ещё не назначен
if ac.tr[u].idx == 0
ac.pidx += 1
ac.tr[u].idx = ac.pidx
end
# возвращаем уникальный индекс
return ac.tr[u].idx
end
构建"后缀"链接
构造后缀转换(失败链接)的功能对于AC算法的正确操作是必要的。
function build(ac::ACAutomaton)
q = [ac.tr[begin].son[i] for i in eachindex(VALID_CHARS) if ac.tr[begin].son[i] != 0]
while !isempty(q)
u = q[begin]
q = q[begin+1:end]
for i in eachindex(VALID_CHARS)
son_node_idx = ac.tr[u].son[i]
fail_node_idx = ac.tr[u].fail
if son_node_idx != 0
ac.tr[son_node_idx].fail = ac.tr[fail_node_idx].son[i]
ac.tr[ac.tr[son_node_idx].fail].du += 1
push!(q, son_node_idx)
else
ac.tr[u].son[i] = ac.tr[fail_node_idx].son[i] # делаем жадное продолжение
end
end
end
end
搜索输入文本中的单词
使用内置自动机搜索文本中所有单词的功能
function query(ac::ACAutomaton, text)
u = 1
for ch in text # идём по символам текста
char_code = findfirst(==(ch), VALID_CHARS)
isnothing(char_code) && continue
u = ac.tr[u].son[char_code]
ac.tr[u].ans += 1 # увеличиваем счётчик совпадений, заканчивающихся в этой вершине
end
end
最终结果的计算
完成主传递后,我们计算每个模板的精确匹配值,同时考虑到节点之间可能通过失败链接的依赖性。
function calculate_final_answers(ac::ACAutomaton)
ac.final_ans = zeros(Int, ac.pidx + 1)
# Начинаем с узлов, не имеющих зависимостей
q = filter(i -> ac.tr[i].du == 0, 1:ac.tot)
while !isempty(q)
u = pop!(q)
idx = ac.tr[u].idx
if idx != 0
ac.final_ans[idx] = ac.tr[u].ans
end
v = ac.tr[u].fail
if v > 1
ac.tr[v].ans += ac.tr[u].ans
ac.tr[v].du -= 1
ac.tr[v].du == 0 && push!(q, v) # если теперь этот узел готов к обработке, добавляем в очередь
end
end
end
实施测试
自动测试整个系统的功能
function test_aho_corasick(input, text)
max_nodes, n = 200000 + 6, 5 # ограничение на максимальное количество узлов
ac = ACAutomaton(max_nodes) # создаем новый автомат
ids = zeros(Int, n + 1) # массив хранения идентификаторов каждого шаблона
# Добавляем все шаблоны в автомат
for i in 1:n
pattern = input[i]
ids[i+1] = insert(ac, pattern)
end
build(ac) # построили суффиксные ссылки
query(ac, text) # выполнили поиск
calculate_final_answers(ac) # собрали ответы
# Выводим результаты
for i in 1:n
uniqueid = ids[i]
println("Number of instances of '$(input[i])': " => ac.final_ans[uniqueid+1])
end
end
让我们开始测试:
# Несколько тестовых шаблонов
input = ["a", "bb", "aa", "abaa", "abaaa"]
text = "abaaabaa" # Текст для анализа
test_aho_corasick(input, text)
结论
我们已经回顾了Aho-Korasik算法在Julia中的实现,这使得在一次传递中有效地查找文本中多个子字符串的所有出现成为可能。 此实现演示了图形类型数据结构(前缀树)以及用于快速状态转换的特殊指针(失败链接)的使用。
该算法对于文本过滤、字典编译、网络协议或任何其他需要并行分析多个样本的任务特别有用。 实现的代码可以扩展到更广泛的字符范围,并在大型NLP项目中使用。
该示例是使用[罗塞塔代码]的材料开发的(https://rosettacode.org/wiki/Aho–Corasick_algorithm )