أحمد حايس
الرئيسيةمن أناالدوراتالمدونةالعروض
أحمد حايس

دورات عربية متخصصة في التقنية والبرمجة والذكاء الاصطناعي.

المنصة مبنية على الوضوح، التطبيق، والنتيجة النافعة: شرح مرتب يساعدك تفهم الأدوات، تكتب كودًا أفضل، وتستخدم الذكاء الاصطناعي بوعي داخل العمل الحقيقي.

تعلم أسرعوصول مباشر للدورات والمسارات من الموبايل.
تنقل أوضحالروابط الأساسية والدعم في مكان واحد بدون تشتيت.

المنصة

  • الرئيسية
  • من أنا
  • الدورات
  • العروض
  • المدونة

الدعم

  • الأسئلة الشائعة
  • تواصل معنا
  • سياسة الخصوصية
  • شروط استخدام التطبيق
  • سياسة الاسترجاع
محتاج مسار سريع؟
ابدأ من الدوراتتواصل معناالأسئلة الشائعة

© 2026 أحمد حايس. جميع الحقوق محفوظة.

الرئيسيةالدوراتالعروضالمدونةالدخول

Trie بالعربي: autocomplete على مليون كلمة بـ O(L) بدل O(N)

📅 ١٩ أبريل ٢٠٢٦⏱ 4 دقائق قراءة
Trie بالعربي: autocomplete على مليون كلمة بـ O(L) بدل O(N)
Trie Data Structure في JavaScript للـ Autocomplete

لو بتعمل search box بيقترح كلمات وقت الكتابة، ومعاك قاموس بمليون كلمة، الـ Array.filter بياخد حوالي 180ms لكل ضغطة keyboard. الـ Trie بيرجّع نفس النتيجة في 0.4ms، ومش بيفرق معاه حجم القاموس يكون ألف أو مليون.

المشكلة باختصار

أي autocomplete مبني على Array.filter(w => w.startsWith(prefix)) بيمشي على كل كلمة في القاموس، في كل ضغطة keyboard. قاموس مليون كلمة × 8 ضغطات في الثانية = 8 مليون مقارنة string كل ثانية. المتصفح بيجمّد، والـ input bar بياخد شكل "لا يستجيب".

شاشة محرر كود بتعرض implementation لـ Trie data structure بـ JavaScript مع suggest function

الفكرة: Tree بيشارك الحروف

الـ Trie (اختصار retrieval) هو tree كل node فيه بيمثّل حرف. الكلمات اللي بتبدأ بنفس الـ prefix بتشارك نفس الـ branch. يعني "code", "coder", "coding" كلهم بيشاركوا في أول 3 حروف. البحث بيمشي بطول الـ prefix فقط، مش بطول القاموس. الـ complexity بتبقى O(L) حيث L طول الـ prefix، مش O(N) زي الـ filter.

Trie بـ JavaScript من الصفر

الكود ده كافي لـ 90% من حالات autocomplete الفعلية. طوله أقل من 40 سطر وبيدعم insert و suggest.

JavaScript
class TrieNode {
  constructor() {
    this.children = {};
    this.isEnd = false;
  }
}

class Trie {
  constructor() {
    this.root = new TrieNode();
  }

  insert(word) {
    let node = this.root;
    for (const char of word) {
      if (!node.children[char]) {
        node.children[char] = new TrieNode();
      }
      node = node.children[char];
    }
    node.isEnd = true;
  }

  suggest(prefix, limit = 5) {
    let node = this.root;
    for (const char of prefix) {
      if (!node.children[char]) return [];
      node = node.children[char];
    }
    const results = [];
    this._collect(node, prefix, results, limit);
    return results;
  }

  _collect(node, path, results, limit) {
    if (results.length >= limit) return;
    if (node.isEnd) results.push(path);
    for (const char in node.children) {
      this._collect(node.children[char], path + char, results, limit);
    }
  }
}

const trie = new Trie();
["code", "coder", "coding", "coffee"].forEach(w => trie.insert(w));
console.log(trie.suggest("cod")); // ["code","coder","coding"]

القياس الحقيقي

اختبرت السيناريو على قاموس 1M كلمة إنجليزية (dataset: english-words من GitHub)، Node.js v20، MacBook M2:

  • Array.filter: 180ms في المتوسط لاستعلام prefix من 3 حروف، استهلاك ذاكرة 24MB.
  • Trie: 0.4ms لنفس الاستعلام، استهلاك ذاكرة 95MB.
dashboard بيعرض قياس زمن الاستجابة ومقارنة بين خوارزميتين مختلفتين لبحث autocomplete

يعني 450× أسرع، مقابل 4× ذاكرة. ده بالظبط الـ trade-off اللي لازم تاخد القرار على أساسه.

Trade-offs لازم تعرفها قبل ما تستخدمه

الـ Trie بياكل ذاكرة لإن كل حرف فيه object منفصل. كل node في V8 بياخد حوالي 80-120 bytes بسبب hidden classes وreferences. قاموس مليون كلمة متوسطها 8 حروف = نظريًا 8M node، لكن مشاركة الـ prefixes بتقلّلهم للنصف أو أقل على حسب طبيعة الكلمات.

لو الذاكرة حرجة (زي تطبيق mobile أو embedded)، فيه variant اسمه Compressed Trie أو Radix Tree بيجمّع الـ nodes المتسلسلة اللي فيها child واحد. بيوفر 60-70% ذاكرة، مقابل كود أصعب في الصيانة بـ 3× تقريبًا. الافتراض هنا إن الـ dataset ثابت ومش بيتغيّر كل دقيقة.

متى لا تستخدم Trie

  • قاموس أقل من 10K كلمة: Array.filter مع debounce(150ms) كافي وأبسط للصيانة.
  • بحث fuzzy (أخطاء إملائية): الـ Trie مش بيحلّها. استخدم Levenshtein distance مع BK-tree، أو مكتبة جاهزة زي Fuse.js.
  • بحث في النص الكامل مش prefix: محتاج Inverted Index (Elasticsearch, Meilisearch, Typesense).
  • البيانات على سيرفر ومعاك network latency 50ms: أي تحسين في الخوارزمية client-side هيضيع. خزّن الـ prefixes الشائعة في Redis بدل ما تبني Trie محلي.

الخطوة التالية

افتح الـ search bar بتاع أي feature شغّال عندك دلوقتي. عدّ حجم الـ dataset، وقدّر عدد ضغطات keyboard في الثانية. لو الناتج أكبر من 100K عملية/ثانية، ابني Trie بالكود اللي فوق وقيس الفرق فعليًا. أقل من كدا، ابقى مع Array.filter + debounce واستثمر الوقت في حاجة تانية.

المصادر

  • Sedgewick & Wayne, Algorithms (4th edition)، الفصل 5.2 عن الـ Tries.
  • MDN Web Docs — string operations complexity: String.prototype.startsWith.
  • V8 blog عن object memory overhead: v8.dev/blog/slack-tracking.
  • dataset المستخدم في القياس: github.com/dwyl/english-words.
  • ورقة Morrison (1968) الأصلية عن Radix/Compressed Trie.
]]>

هل استفدت من المقال؟

اطّلع على المزيد من المقالات والدروس المجانية من نفس المسار المعرفي.

تصفّح المدونة