- vừa được xem lúc

(C#) Tối ưu tìm kiếm nhiều từ khóa trong văn bản với Aho-Corasick

0 0 7

Người đăng: Đặng Việt Anh

Theo Viblo Asia

👋 Hi anh em, hôm nay mình muốn chia sẻ một case study nho nhỏ trong dự án mà mình đang làm. Cụ thể là bài toán lọc các từ khóa nhạy cảm. Đây là một bài toán nghe đơn giản nhưng khi đụng đến data thật với hàng trăm, hàng nghìn từ khóa thì lại phát sinh nhiều vấn đề thú vị về hiệu năng. Và thế là mình có dịp “đào bới” lại một giải thuật kinh điển: Aho-Corasick. 🚀

image.png

Một bộ ba dựa trên các từ "Java", "Rad", "Rand", "Rau", "Raum" và "Rose".
Hình ảnh của [nd](https://de.wikipedia.org/wiki/Benutzer:Nd) được phân phối theo giấy phép CC BY-SA 3.0.

1. Vấn đề

Trong dự án hiện tại, nhóm mình đang phát triển một service – đóng vai trò trung gian để điều hướng và forward các request từ nhiều chatbot nội bộ sang các mô hình ngôn ngữ khác nhau (như OpenAI, Gemini, v.v.).

Trước khi chuyển request ra bên thứ ba, service này cần thực hiện tiền xử lý dữ liệu nhằm đảm bảo tính an toàn và tuân thủ quy tắc nội bộ. Một trong những bước quan trọng chính là lọc từ khóa nhạy cảm.

Việc lọc này giúp:

  • Loại bỏ hoặc cảnh báo khi người dùng nhập những từ ngữ vi phạm chính sách (chửi tục, nhạy cảm chính trị, thông tin cá nhân…).
  • Đảm bảo trải nghiệm an toàn và phù hợp với người dùng cuối. Tuy nhiên, với số lượng từ khóa nhạy cảm lên đến hàng trăm hay hàng nghìn, việc tìm kiếm theo cách thông thường (so sánh chuỗi từng từ với input) vừa tốn thời gian, vừa kém hiệu quả trên các đoạn văn bản dài.

2. Giải pháp

Khi bắt đầu xử lý bài toán, cách đầu tiên mình nghĩ tới là duyệt từng từ khóa rồi dùng IndexOf hoặc regex để kiểm tra trong đoạn text. Cách này thì dễ hiểu, dễ làm, nhưng khi số lượng từ khóa lên đến hàng trăm hay hàng nghìn thì tốc độ giảm rõ rệt, đặc biệt với những input rất dài.

Trong quá trình tìm hiểu, mình phát hiện ra một giải thuật kinh điển cho việc tìm kiếm nhiều mẫu cùng lúc, đó là Aho-Corasick. Ưu điểm của nó là có thể duyệt chuỗi đầu vào chỉ một lần mà vẫn phát hiện được tất cả các từ khóa, với độ phức tạp O(N + M + Z) (N là độ dài chuỗi, M là tổng độ dài của tất cả từ khóa, Z là số lần khớp).

May mắn là trong .NET đã có sẵn thư viện Ganss.Text.AhoCorasick cài đặt thuật toán này, nên mình có thể áp dụng ngay mà không cần tự viết lại từ đầu.

3. Triển khai

Cài đặt NuGet

dotnet add package AhoCorasick --version 2.0.279

Ví dụ code

using SharpToken;
using System;
using System.Diagnostics;
using System.Text;
public class ToxicWord
{ // array of toxic words public static readonly string[] _toxicWords = ["chó", "chó điên", "dm", "tổ sư"]; // get toxic words public static List<string> GetToxicsWords(int repeat = 1) { //return _toxicWords; return Enumerable.Range(0, repeat) .SelectMany(_ => _toxicWords) .ToList(); }
} using Ganss.Text;
using System.Diagnostics;
using System.Text;
using ToxicDetection; class PerfTest
{ static void Main() { Console.OutputEncoding = Encoding.UTF8; // Tạo danh sách từ nhạy cảm giả lập var keywords = ToxicWord.GetToxicsWords(1000); // Build input 128k * " abc", rải keyword var sb = new StringBuilder(); for (int i = 0; i < 100 * 1024; i++) { sb.Append(" abc"); if (i % 777 == 0) sb.Append(" chó điên "); // rải keyword thật } string input = sb.ToString(); Console.WriteLine("============== Thông tin Input =============="); Console.WriteLine($"Chuỗi ký tự dài : {input.Length:N0} ký tự"); //Console.WriteLine($"Tổng số token : {tokens.Count:N0}"); Console.WriteLine($"Số keyword : {keywords.Count}"); Console.WriteLine(); // ==================== // Naive Matching // ==================== var sw = Stopwatch.StartNew(); var naiveMatches = new List<string>(); foreach (var kw in keywords) { if (input.IndexOf(kw, StringComparison.OrdinalIgnoreCase) >= 0) naiveMatches.Add(kw); } sw.Stop(); Console.WriteLine("============== Naive Matching =============="); Console.WriteLine($"Thời gian : {sw.ElapsedMilliseconds} ms"); Console.WriteLine($"Số từ tìm thấy : {naiveMatches.Count}"); if (naiveMatches.Count > 0) Console.WriteLine($"Danh sách : {string.Join(", ", naiveMatches)}"); Console.WriteLine(); // ==================== // Aho-Corasick Matching // ==================== var ac = new AhoCorasick(keywords); sw.Restart(); var ahoMatches = new List<string>(); foreach (var m in ac.Search(input)) { ahoMatches.Add(m.Word); } sw.Stop(); Console.WriteLine("============== Aho-Corasick =============="); Console.WriteLine($"Thời gian : {sw.ElapsedMilliseconds} ms"); Console.WriteLine($"Số từ tìm thấy : {ahoMatches.Count}"); if (ahoMatches.Count > 0) Console.WriteLine($"Danh sách : {string.Join(", ", ahoMatches)}"); }
}

4. Kết quả

image.png

  • Với chuỗi dài 410,920 ký tự, danh sách 43,800 từ khóa:
    • Naive search mất 7,434s
    • Aho-Corasick chỉ mất 5 ms.
  • Kết quả cho thấy Aho-Corasick tối ưu vượt trội khi cần kiểm tra nhiều từ khóa trong văn bản lớn.

5. Nguồn tham khảo

Bình luận

Bài viết tương tự

- vừa được xem lúc

Thuật toán quay lui (Backtracking)

Quay lui là một kĩ thuật thiết kế giải thuật dựa trên đệ quy. Ý tưởng của quay lui là tìm lời giải từng bước, mỗi bước chọn một trong số các lựa chọn khả dĩ và đệ quy.

0 0 67

- vừa được xem lúc

Các thuật toán cơ bản trong AI - Phân biệt Best First Search và Uniform Cost Search (UCS)

Nếu bạn từng đọc các thuật toán trong AI (Artificial Intelligence - Trí tuệ nhân tạo), rất có thể bạn từng nghe qua về các thuật toán tìm kiếm cơ bản: UCS (thuộc chiến lược tìm kiếm mù) và Best First Search (thuộc chiến lược tìm kiếm kinh nghiệm). Khác nhau rõ từ khâu phân loại rồi, thế nhưng hai th

0 0 194

- vừa được xem lúc

Sử dụng vector trong lập trình C++ - giải bài toán lập trình muôn thủa

Chào buổi tối mọi người, hôm nay lang thang trên mạng bắt gặp bài toán quen thuộc một thời của quãng đường sinh viên IT. Đấy chính là câu số 1 trong đề thi dưới đây:.

0 0 76

- vừa được xem lúc

MÔ PHỎNG THUẬT TOÁN VƯƠNG HẠO TRONG PROLOG

. 1. Các luật suy diễn trong thuật toán Vương Hạo. Luật 1: Chuyển vế các giả thuyết và kết luận ở dạng phủ định. Ví dụ: p v q, !(r ^ s), !q, p v r -> s, !p <=> p v q, p v r, p -> s, r ^ s, q.

0 0 109

- vừa được xem lúc

A* Search Algorithm

What is A* Search Algorithm. How it works. . Explanation.

0 0 72

- vừa được xem lúc

Python: Jump Search

Search là một từ khóa khá là quen thuộc đối với chúng ta. Hiểu theo đúng nghĩa đen của nó chính là "Tìm kiếm".

0 0 74