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
oʻ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 oʻ 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'nissoz[1]— 1-manzildagi belgi, ya'niasoz[4]— 4-manzildagi belgi, ya'nim(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 range— izohi: "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
oharfi - 2-manzilda —
ʻbelgisi, butunlay alohida
Ya'ni oʻ — bu bitta harf emas, bu o va undan keyin turgan boshqa belgi.
Nega bu muhim?
koʻramansizning ko'zingizda 7 harf, kompyuter uchun 8 belgi.- Tokenizator
oʻni tasodifan ikkiga bo'lib yuborishi mumkin — vao+ʻdegan ma'nosiz bo'laklar hosil bo'ladi. - Va eng yomoni:
ʻni odamlar to'rt xil yozadi —oʻ,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]vasoz[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-gachasoz[3:]— 3-dan oxirigachasoz[:]— 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
TypeError— tur bilan bog'liq xato.- Izohi: "satrga faqat satrni ulash mumkin,
intni 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 men — ikki 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
fharfi 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 vaTrueyokiFalseqaytar.
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ʻqishni — o, ʻ, q, i, s, h, n, i = 8 ta. (oʻ 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ʻqish — o, ʻ, 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 gʻ — ikkita alohida belgi, xuddi oʻ 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: oʻ va gʻ. 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
- Matn — belgilar zanjiri. Har bir belgining manzili (indeks) bor, va sanash noldan boshlanadi.
matn[i]— bitta belgi.matn[-1]— oxirgisi.matn[a:b]— bo'lak.bkirmaydi. Indekslarni belgilar orasidagi kesish joylari deb tasavvur qiling — shunda hammasi joyiga tushadi.oʻvagʻ— ikkitadan belgi. Ko'z ko'rgan narsa kompyuter ko'rgan narsa emas..lower(),.replace()— yangi matn qaytaradi, eskisini o'zgartirmaydi. Natijani saqlab oling.=joylaydi,==tekshiradi.- 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.