Tokenizator qurish · Dars 02 · ~45 daqiqa

Matn bilan ishlash

Bu darsdan keyin siz...

  • olasiz matndan istalgan bitta belgini — nomeri bo'yicha;
  • kesasiz matnning istalgan bo'lagini — boshidan, o'rtasidan, oxiridan;
  • tushunasiz nima uchun Python noldan sanashini, va bu nima uchun aslida qulay;
  • almashtirasiz matn ichidagi belgilarni — bu tokenizatoringizning haqiqiy qismi bo'ladi;
  • ko'rasiz ning ichida yashiringan sirni, va u butun kursga ta'sir qiladi.

1. Bitta savol

O'tgan darsda savol qoldirgan edim:

"salom" matnining 0-belgisi qaysi harf?

Ko'pchilik a deb javob beradi. Mantiq shunday: birinchi harf s, demak 0-chisi undan oldin, yoki keyingisi...

Tekshiramiz:

soz = "salom"
print(soz[0])
s

0-belgi — bu birinchi harf.

Python (va deyarli barcha dasturlash tillari) noldan sanaydi. Birinchi — 0, ikkinchi — 1, uchinchi — 2.

Bu g'alati tuyuladi, lekin sababi bor va u darsning o'rtasida ochiladi.

Hozir esa ikkinchi savol, va bu ancha muhimroq:

"koʻraman" matnining 1-belgisi nima?

Siz deb o'ylayapsiz. Javob boshqacha. Va bu farq butun kursning eng muhim texnik muammolaridan birini ochadi.

2. Matn — bu belgilar zanjiri

Kompyuter uchun matn — bu bir-biriga ulangan belgilar zanjiri. Har bir belgining o'z o'rni bor, va o'rinlar raqamlangan:

 s   a   l   o   m
 0   1   2   3   4

Bu raqam indeks deb ataladi. Indeks — bu belgining manzili.

Matndan bitta belgini olish uchun kvadrat qavs ichida uning indeksini yozasiz:

soz = "salom"
print(soz[0])
print(soz[1])
print(soz[4])
s
a
m
  • soz[0] — 0-manzildagi belgi, ya'ni s
  • soz[1] — 1-manzildagi belgi, ya'ni a
  • soz[4] — 4-manzildagi belgi, ya'ni m (oxirgisi)
Diqqat: qavs turi muhim. soz(0) ❌ — dumaloq qavs buyruq chaqiradi. soz[0] ✓ — kvadrat qavs indeks oladi.

3. Ataylab xato: chegaradan chiqish

Nima bo'ladi, agar mavjud bo'lmagan manzilni so'rasak? "salom" da 5 ta belgi bor, manzillar 0 dan 4 gacha. soz[5] ni so'raymiz:

soz = "salom"
print(soz[5])
IndexError: string index out of range

Xatoni o'qiymiz (Dars 01 dagidek — ikki bo'lakka ajratamiz):

  • IndexError — xato turi. Indeks bilan bog'liq muammo.
  • string index out of rangeizohi: "satr indeksi chegaradan tashqarida".

Kompyuter 5-manzilga bordi, u yerda hech narsa yo'q edi, va shuni aytdi.

Qoida: n ta belgili matnda manzillar 0 dan n−1 gacha. 5 ta belgi → 0, 1, 2, 3, 4. Oxirgi manzil har doim len(matn) - 1.

Bu -1 ni Dars 01 dagi savolga bog'lang: sanash noldan boshlangani uchun, oxirgi raqam har doim umumiy sondan bitta kam.

4. Orqadan sanash

Oxirgi belgini olish uchun har safar len(matn) - 1 hisoblash noqulay. Python osonroq yo'l beradi — manfiy indeks:

soz = "salom"
print(soz[-1])
print(soz[-2])
m
o
 s   a   l   o   m
 0   1   2   3   4      ← oldidan
-5  -4  -3  -2  -1      ← orqadan
  • soz[-1] — oxirgi belgi. Har doim. Matn uzunligini bilish shart emas.
  • soz[-2] — oxiridan ikkinchisi.

E'tibor bering: orqadan sanash birdan boshlanadi, noldan emas. Chunki -0 degan narsa yo'q — -0 bu 0, ya'ni birinchi belgi.

soz[-1] ni yodlab qo'ying. Bu kursda juda ko'p ishlatiladi.

5. Yashirin belgi

Endi 1-bo'limdagi ikkinchi savolga qaytamiz.

soz = "koʻraman"
print("belgilar soni:", len(soz))
print("0:", soz[0])
print("1:", soz[1])
print("2:", soz[2])
print("3:", soz[3])
belgilar soni: 8
0: k
1: o
2: ʻ
3: r

To'xtang va buni diqqat bilan o'qing.

Siz koʻraman ni ko'rganingizda koʻ ni bitta harf deb o'ylaysiz. O'zbek tilida bu bitta tovush, bitta harf.

Kompyuter uchun esa bu ikkita alohida belgi:

 k   o   ʻ   r   a   m   a   n
 0   1   2   3   4   5   6   7
  • 1-manzilda — oddiy o harfi
  • 2-manzilda — ʻ belgisi, butunlay alohida

Ya'ni — bu bitta harf emas, bu o va undan keyin turgan boshqa belgi.

Nega bu muhim?

  1. koʻraman sizning ko'zingizda 7 harf, kompyuter uchun 8 belgi.
  2. Tokenizator ni tasodifan ikkiga bo'lib yuborishi mumkin — va o + ʻ degan ma'nosiz bo'laklar hosil bo'ladi.
  3. Va eng yomoni: ʻ ni odamlar to'rt xil yozadi — , o', o‘, o. Kompyuter uchun bular to'rt xil so'z.

Bu — o'zbek tili uchun tokenizator qurishdagi asosiy texnik muammolardan biri. Uni Dars 14 da to'liq hal qilamiz. Hozircha shuni biling: ko'z ko'rgan narsa va kompyuter ko'rgan narsa — har doim bir xil emas.

6. Kesish — bo'lak olish

Bitta belgi emas, bo'lak olish kerak bo'lsa, ikki nuqta ishlatiladi:

soz = "koʻraman"
print(soz[0:3])
print(soz[3:8])
koʻ
raman

Yozilishi: matn[boshi:oxiri]

  • boshi — qayerdan boshlab olish (shu manzil kiradi)
  • oxiri — qayerda to'xtash (shu manzil kirmaydi)

soz[0:3] = 0, 1, 2-manzillar. 3-manzil kirmaydi.

Nima uchun oxirgisi kirmaydi?

Bu boshida g'alati tuyuladi, lekin sababi juda amaliy.

Indekslarni belgilarda emas, belgilar orasidagi kesish joylarida tasavvur qiling:

 |  k  |  o  |  ʻ  |  r  |  a  |  m  |  a  |  n  |
 0     1     2     3     4     5     6     7     8

Endi soz[0:3] — bu "0-kesikdan 3-kesikgacha bo'lgan bo'lak". Va hammasi joyiga tushadi:

  • bo'lak uzunligi har doim oxiri - boshi = 3 − 0 = 3 ✓
  • soz[0:3] va soz[3:8] — hech narsa yo'qolmaydi, hech narsa takrorlanmaydi
  • ikkisini qo'shsangiz, butun so'z chiqadi

Bu tokenizator uchun hayotiy muhim: so'zni bo'laklarga bo'lganda hamma belgi aynan bir marta ishlatilishi kerak.

Qisqartmalar

Agar boshi 0 bo'lsa yoki oxiri matn oxirigacha bo'lsa, ularni yozmasa ham bo'ladi:

soz = "koʻraman"
print(soz[:3])
print(soz[3:])
print(soz[:])
koʻ
raman
koʻraman
  • soz[:3] — boshidan 3-gacha
  • soz[3:] — 3-dan oxirigacha
  • soz[:] — butunicha (nusxa)

Bitta mashq, o'zingiz taxmin qiling: "salom"[1:4] nima beradi?

print("salom"[1:4])
alo

1, 2, 3-manzillar: a, l, o. 4-manzil (m) kirmadi.

7. Matnlarni qo'shish

a = "salom"
b = "dunyo"
print(a + b)
print(a + " " + b)
print(a * 3)
salomdunyo
salom dunyo
salomsalomsalom
  • + — matnlarni ulaydi. Bo'shliq o'zi qo'shilmaydi, kerak bo'lsa qo'lda qo'shasiz: a + " " + b.
  • * — matnni takrorlaydi.

Diqqat: + sonlar bilan qo'shadi, matnlar bilan ulaydi. 2 + 3 = 5, lekin "2" + "3" = "23".

Ataylab xato: turlarni aralashtirish

print("salom" + 5)
TypeError: can only concatenate str (not "int") to str
  • TypeErrortur bilan bog'liq xato.
  • Izohi: "satrga faqat satrni ulash mumkin, int ni emas".
  • str = string (satr, matn). int = integer (butun son).

Kompyuter matn bilan sonni qo'sha olmaydi — ular boshqa turdagi narsalar.

Tuzatish: sonni matnga aylantiramiz:

print("salom" + str(5))
salom5

str(...) — istalgan narsani matnga aylantiradi.

8. Matnni o'zgartirish

.lower() va .upper()

katta = "Men OʻQISHNI Yaxshi Koʻraman"
print(katta.lower())
print(katta.upper())
men oʻqishni yaxshi koʻraman
MEN OʻQISHNI YAXSHI KOʻRAMAN

Nima uchun bu kerak? Chunki kompyuter uchun Men va menikki xil matn. Agar tokenizator ularni alohida o'rgansa, bilim ikkiga bo'linadi va ikkalasi ham yomon o'rganiladi. Shuning uchun ko'pincha matn avval kichiklashtiriladi.

E'tibor bering: .upper() da ʻ o'zgarmadi. Uning katta harfi yo'q — u harf emas, yordamchi belgi. Yana bir dalil: ʻ alohida yashaydi.

.replace() — almashtirish

xato = "o'zbek tili o'zbekcha"
togri = xato.replace("'", "ʻ")
print("oldin :", xato)
print("keyin :", togri)
oldin : o'zbek tili o'zbekcha
keyin : oʻzbek tili oʻzbekcha

matn.replace(nimani, nimaga) — ikkita parametr oladi:

  • 1-parametr — nimani qidirish kerak
  • 2-parametr — o'rniga nimani qo'yish kerak

Va u hamma uchragan joyni almashtiradi, faqat birinchisini emas:

print("aaa".replace("a", "b"))
bbb

Shu ikki qator kod — sizning tokenizatoringizning haqiqiy qismi. O'zbek matnida odamlar ʻ o'rniga oddiy apostrof ' yozadi. Tokenizator ishlashidan oldin ularni bir xil ko'rinishga keltirish kerak. Bu normalizatsiya deb ataladi va Dars 14 da to'liq yozamiz.

Muhim tuzoq: matn o'zgarmaydi

asl = "salom"
asl.replace("s", "S")
print(asl)
salom

Hech narsa o'zgarmadi! Nega?

Chunki .replace() asl matnni o'zgartirmaydi — u yangi matn qaytaradi. Agar uni saqlab olmasangiz, natija yo'qoladi.

To'g'ri yo'l:

asl = "salom"
yangi = asl.replace("s", "S")
print(yangi)
Salom
Python'da matn o'zgarmas (immutable). Uni tahrirlab bo'lmaydi, faqat yangisini yasash mumkin. .lower(), .upper(), .replace() — hammasi shunday ishlaydi: natijani o'zgaruvchiga yozib olish kerak. Bu boshlovchilar eng ko'p qoqiladigan tuzoq. "Kodim ishlamayapti" deganlarning yarmi aynan shu.

9. f-satr — matn ichiga qiymat qo'yish

Dars 01 da print ichida vergul ishlatgan edik. Chiroyliroq yo'l bor:

soz = "koʻraman"
print(f"{soz} soʻzida {len(soz)} ta belgi bor")
print(f"birinchi belgi: {soz[0]}, oxirgi belgi: {soz[-1]}")
koʻraman soʻzida 8 ta belgi bor
birinchi belgi: k, oxirgi belgi: n
  • Qo'shtirnoqdan oldin f harfi turadi — bu "formatlangan satr" degani.
  • {...} ichidagi narsa hisoblanadi va natijasi matnga qo'yiladi.
  • {} ichiga o'zgaruvchi ham, buyruq ham (len(soz)), indeks ham (soz[0]) yozish mumkin.

f ni unutsangiz, xato chiqmaydi — shunchaki qavslar matn bo'lib chiqadi:

print("{soz} ta belgi")
{soz} ta belgi

Xato yo'q, lekin natija noto'g'ri. Bunday xatolarni topish qiyinroq, chunki kompyuter shikoyat qilmaydi.

10. Buni tokenizatorga bog'laymiz

Endi biz so'zni bo'laklarga bo'la olamiz. Tokenizatorning butun ishi shu:

soz = "koʻraman"

ozak = soz[:3]
qoshimcha = soz[3:]

print("oʻzak     :", ozak)
print("qoʻshimcha:", qoshimcha)
print("qayta yigʻish:", ozak + qoshimcha)
print("teng keldimi:", ozak + qoshimcha == soz)
oʻzak     : koʻ
qoʻshimcha: raman
qayta yigʻish: koʻraman
teng keldimi: True

Oxirgi qatorda yangi belgi bor: ==.

  • = (bitta) — joyla: o'ngdagini chapdagi qutiga sol.
  • == (ikkita) — tengmi?: ikki tomonni solishtir va True yoki False qaytar.

Bu ikkisini aralashtirmang. == hech narsani o'zgartirmaydi, faqat tekshiradi.

True — bu "rost". Ya'ni bo'laklarni qayta yig'ganimizda aynan asl so'z chiqdi. Hech narsa yo'qolmadi.

Bu tekshiruvning nomi bor: round-trip (borib-kelish). Tokenizator uchun bu eng muhim sinov:

matn → bo'laklar → raqamlar → bo'laklar → matn

Agar oxirida asl matn chiqmasa, tokenizator buzuq. Kurs davomida bu tekshiruvni ko'p marta yozasiz.

11. Muammo

Bo'laklarga bo'lishni o'rgandik. Lekin bitta savol javobsiz qoldi:

Qayerdan bo'lish kerakligini kim aytadi?

Men soz[:3] deb yozdim — ya'ni bo'lish joyini o'zim tanladim. koʻ + raman. Nega 3? Chunki men o'zbek tilini bilaman.

Lekin tokenizator million so'zni bo'lishi kerak. Ularning har biri uchun qo'lda raqam yozib chiqib bo'lmaydi.

Va ikkinchi muammo: hozircha men faqat bitta so'z bilan ishladim. Million so'zni qanday ishlayman? Har biri uchun alohida print yozamanmi?

Ikkala muammoning ham javobi bitta narsada: ko'p narsani bir joyda saqlash va ular bo'ylab avtomatik yurish. Bu keyingi darsning mavzusi.

12. To'liq kod

# ---- 1. Indeks ----
soz = "salom"
print(soz[0])       # s
print(soz[4])       # m
print(soz[-1])      # m  (oxirgisi)

# ---- 2. Yashirin belgi ----
soz2 = "koʻraman"
print("belgilar soni:", len(soz2))
print("0:", soz2[0], "| 1:", soz2[1], "| 2:", soz2[2], "| 3:", soz2[3])

# ---- 3. Kesish ----
print(soz2[0:3])    # koʻ
print(soz2[3:])     # raman
print(soz2[:])      # koʻraman

# ---- 4. Qoʻshish ----
a = "salom"
b = "dunyo"
print(a + " " + b)
print(a * 3)

# ---- 5. Oʻzgartirish ----
katta = "Men Koʻraman"
print(katta.lower())

xato = "o'zbek tili"
togri = xato.replace("'", "ʻ")
print(togri)

# ---- 6. f-satr ----
print(f"{soz2} soʻzida {len(soz2)} ta belgi bor")

# ---- 7. Round-trip tekshiruvi ----
ozak = soz2[:3]
qoshimcha = soz2[3:]
print("teng keldimi:", ozak + qoshimcha == soz2)
s
m
m
belgilar soni: 8
0: k | 1: o | 2: ʻ | 3: r
koʻ
raman
koʻraman
salom dunyo
salomsalomsalom
men koʻraman
oʻzbek tili
koʻraman soʻzida 8 ta belgi bor
teng keldimi: True

13. O'zingiz yozing

soz = "oʻqishni"

# 1. Nechta belgi bor?
print("belgilar:", ___)

# 2. Birinchi va oxirgi belgini chiqaring
print("birinchi:", soz[___])
print("oxirgi  :", soz[___])

# 3. Soʻzni ikkiga boʻling: "oʻqish" va "ni"
birinchi_bolak = soz[___:___]
ikkinchi_bolak = soz[___:]
print(birinchi_bolak, "+", ikkinchi_bolak)

# 4. Round-trip tekshiruvi
print("teng keldimi:", birinchi_bolak + ikkinchi_bolak == ___)
Yechimni ko'rsatish

Avval belgilarni sanash kerak. oʻqishnio, ʻ, q, i, s, h, n, i = 8 ta. ( ikkita belgi ekanini unutmang!)

soz = "oʻqishni"

print("belgilar:", len(soz))            # 8

print("birinchi:", soz[0])              # o
print("oxirgi  :", soz[-1])             # i

birinchi_bolak = soz[0:6]               # oʻqish
ikkinchi_bolak = soz[6:]                # ni
print(birinchi_bolak, "+", ikkinchi_bolak)

print("teng keldimi:", birinchi_bolak + ikkinchi_bolak == soz)   # True
belgilar: 8
birinchi: o
oxirgi  : i
oʻqish + ni
teng keldimi: True

Nega 6? Chunki oʻqisho, ʻ, q, i, s, h = 6 ta belgi, ya'ni 0 dan 5 gacha. Kesish 6-da to'xtaydi va 6 kirmaydi.

14. Mashqlar

Mashq 1

"Toshkent" matnida:

  • 0-belgi nima?
  • 3-belgi nima?
  • [-1] nima?
  • [0:4] nima beradi?

Avval qo'lda yozing, keyin tekshiring.

Javobni ko'rsatish
 T   o   s   h   k   e   n   t
 0   1   2   3   4   5   6   7
shahar = "Toshkent"
print(shahar[0])     # T
print(shahar[3])     # h
print(shahar[-1])    # t
print(shahar[0:4])   # Tosh

[0:4] = 0, 1, 2, 3-manzillar = Tosh. 4-manzil (k) kirmadi.

Mashq 2

Bu kod nima uchun xato beradi va uni qanday tuzatasiz?

yosh = 17
print("Mening yoshim: " + yosh)
Javobni ko'rsatish
TypeError: can only concatenate str (not "int") to str

yosh — bu son (int), "Mening yoshim: " — bu matn (str). + bilan ularni ulab bo'lmaydi.

Uch xil tuzatish, uchalasi ham to'g'ri:

print("Mening yoshim: " + str(yosh))     # sonni matnga aylantirish
print("Mening yoshim:", yosh)            # vergul (Dars 01)
print(f"Mening yoshim: {yosh}")          # f-satr — eng qulayi

Uchalasi ham Mening yoshim: 17 beradi.

Mashq 3

Bu kod nima chiqaradi? Ishga tushirmasdan ayting.

matn = "salom"
matn.upper()
print(matn)
Javobni ko'rsatish
salom

Katta harf bilan emas!

matn.upper() yangi matn qaytardi, lekin uni hech kim saqlamadi. Natija paydo bo'ldi va darhol yo'qoldi. matn esa o'zgarmadi — chunki Python'da matn o'zgarmas.

To'g'ri yo'l:

matn = matn.upper()
print(matn)      # SALOM

Bu tuzoqni yaxshi eslab qoling. Kursda .replace() ni ko'p ishlatasiz va bu xatoni albatta bir marta qilasiz.

Mashq 4

"gʻalaba" so'zida nechta belgi bor? Avval qo'lda sanang, keyin tekshiring. Va [1] qaysi belgi?

Javobni ko'rsatish
soz = "gʻalaba"
print(len(soz))     # 7
print(soz[0])       # g
print(soz[1])       # ʻ
print(soz[2])       # a

7 ta belgi, chunki — ikkita alohida belgi, xuddi kabi:

 g   ʻ   a   l   a   b   a
 0   1   2   3   4   5   6

Ko'zingiz gʻalaba da 6 harf ko'radi. Kompyuter 7 ta belgi ko'radi.

O'zbek lotin yozuvida shunday ikki juftlik bor: va . Ikkalasi ham tokenizator uchun alohida e'tibor talab qiladi.

Mashq 5 (eng muhimi)

Quyidagi uchta matn ko'zga bir xil ko'rinadi. Lekin Python uchun ular bir xilmi?

a = "oʻzbek"      # toʻgʻri belgi (U+02BB)
b = "o'zbek"      # oddiy apostrof
c = "o‘zbek"      # burchakli qoʻshtirnoq

print(len(a), len(b), len(c))
print(a == b)
print(a == c)
Javobni ko'rsatish
6 6 6
False
False

Uchalasining ham uzunligi bir xil — 6 belgi. Lekin ular teng emas.

Har uchtasi ham o + biror belgi, lekin o'sha "biror belgi" har safar boshqa: ʻ (U+02BB), ' (oddiy apostrof), (burchakli qo'shtirnoq).

Nima uchun bu falokat: internetdagi o'zbek matnida uchchalasi ham uchraydi, chunki odamlar turli klaviaturada yozadi. Agar tokenizator ularni tuzatmasa, u oʻzbek so'zini uch xil so'z deb o'rganadi. Lug'atda uch marta joy egallaydi, va har biri uchun bilim uch marta kam bo'ladi.

Yechim — hammasini bittasiga keltirish:

b = b.replace("'", "ʻ")
c = c.replace("‘", "ʻ")
print(a == b, a == c)    # True True

Mana shu — Dars 14 ning butun mazmuni. Va bu haqiqiy tokenizatorda haqiqatan bor: uzbek-bpe-16k ishlashidan oldin aynan shunday normalizatsiya qiladi. Siz hozir uni allaqachon yoza olasiz.

15. Xulosa

  1. Matn — belgilar zanjiri. Har bir belgining manzili (indeks) bor, va sanash noldan boshlanadi.
  2. matn[i] — bitta belgi. matn[-1] — oxirgisi.
  3. matn[a:b] — bo'lak. b kirmaydi. Indekslarni belgilar orasidagi kesish joylari deb tasavvur qiling — shunda hammasi joyiga tushadi.
  4. va ikkitadan belgi. Ko'z ko'rgan narsa kompyuter ko'rgan narsa emas.
  5. .lower(), .replace() — yangi matn qaytaradi, eskisini o'zgartirmaydi. Natijani saqlab oling.
  6. = joylaydi, == tekshiradi.
  7. Round-trip: bo'laklarni qayta yig'ganda asl matn chiqishi shart.

Keyingi dars

Bugun bitta so'z bilan ishladik. Tokenizator esa millionlab so'z bilan ishlaydi.

Dars 03 — Ro'yxat va tsikl. Ko'p narsani bitta joyda saqlash va ular bo'ylab avtomatik yurish. Shundan keyin "har bir belgini ko'rib chiq" degan buyruqni bitta qatorda yoza olasiz.

Keyingi darsning savoli: "salom" so'zidagi hamma qo'shni juftliklarni (sa, al, lo, om) qanday chiqarasiz — qo'lda yozmasdan? Bu juftliklar BPE algoritmining birinchi qadami bo'ladi.