Matn ichida qidiruv. Rabin-Karp algoritmi

Matn ichida qidiruv uchun yana bir diqqatga molik algoritmlardan biri – Rabin-Karp algoritmi hashing usuliga asoslanadi. Uning ishlash tartibini batafsil tushuntirishga harakat qilaman.

Bizda quyidagi matn bor bo’lsin: FINDINAHAYSTACKNEEDLEINA
Pattern: NEEDLE

  1. patterndagi 0 dan M-1 gacha belgilar bo’yicha hash soni generatsiya qilinadi. Bunda M – patterndagi belgilar soni.
  2. har bir [i..N-1] sikl ichida matndagi i dan M + i – 1 gacha belgilar bo’yicha hash soni generatsiya qilinadi. Bunda N – matndagi belgilar soni
  3. Agar pattern hash va matn substring hash bir-biriga teng bo’lsa, pattern matn ichidan topilgan hisoblanadi.

Hash sonini generatsiya qilishning eng tezkor, oson usullardan biri – pattern’ning har bir belgisi kodini olib, ularni birlashtirish va uni biror katta tub songa bo’lib, qoldig’ini qoldirish kerak. Javascriptda bu ish quyidagicha bajariladi:

const primeNumber = 997 // tub son
const pattern = "NEEDLE" // qidiriladigan so'z
const hash = pattern.split('').map(char => char.charCodeAt()).join('') % primeNumber 
// pattern uchun hash soni 769

Demak bizda patternning hash soni generatsiya qilindi. Endi uni matndagi har bir i dan M + i – 1 gacha substring’lardan hash son generatsiya qilib uni pattern’ning hash soniga solishtirib chiqiladi. Substring uchun ham, pattern uchun ham bir xil usulda hash generatsiya qilinadi.

FINDINAHAYSTACKNEEDLEINA
FINDIN  // 5 != 769
 INDINA  // 877 != 769
  NDINAH  // 105 != 769
   DINAHA  // 259 != 769
    INAHAY  // 541 != 769
     NAHAYS  // 414 != 769
      AHAYST  // 271 != 769
       HAYSTA  // 963 != 769
        AYSTAC  // 805 != 769
         YSTACK  // 535 != 769
          STACKN  // 507 != 769
           TACKNE  // 178 != 769
            ACKNEE  // 98 != 769
             CKNEED  // 615 != 769
              KNEEDL  // 317 != 769
               NEEDLE  // 769 == 769

Juda oddiy va mohirona usul!

Yuqoridagi hash generatsiya qilish usuli split() va join() metodlari ishlatilgani sababli sekinroq ishlashi mumkin. Shuning uchun biz hash generatsiya qilishni sinalgan usul – Horner metodi yordamida amalga oshiramiz. Umumiy formula quyidagicha:

xi = ( ti * RM-1 + ti+1 * RM-2 + … + ti+M-1 * R0 ) % Q

Bu yerda:
xi – matndagi [i..M + i – 1] substring uchun hash soni.
ti – str.charCodeAt(i) – matn/pattern belgisining ASCII jadvalidagi raqami.
R – radix soni. ASCII uchun 128 yoki 256.
M – patterndagi belgilar soni.
Q – tub son.

Hash funksiya kodi:

function hornerHash(str, M) {
  const R = 128
  const Q = 997
  let hash = 0

  for (let j = 0; j < M; j++) {
    hash = (R * hash + str.charCodeAt(j)) % Q
  }

  return hash
}

Hash funksiyada kolliziya ehtimolligi Q tub sonning qanchalik katta son ekanligiga bog’liq. Masalan agar Q son M * N2 dan katta bo’lsa, kolliziya ehtimoli 1/N ga teng bo’ladi.

Kod