Mundarija (29)
- Bu darsda
- 1. Nega bu kerak?
- 2. Massivning oxiri arzon, boshi qimmat
- 2.1 Indekslar qayta raqamlanadi
- 2.2 O'lchov: kutilmagan jarlik
- 2.3 Yechim: navbat boshini indeks bilan eslash
- 3. Qidirish: includes va Set.has
- 4. Nusxa oladigan amallar
- 4.1 Har nusxa — n ta element
- 4.2 reduce + spread — eng mashhur yashirin O(n²)
- 5. sort — O(n log n) va joyida
- 6. Satr birlashtirish: += yoki join?
- 7. Object.keys va do'stlari
- 8. Narxlar jadvali
- 9. Chegaraviy holatlar
- 10. Ko'p uchraydigan xatolar
- 10.1 "Bir qator — O(1)"
- 10.2 Navbatni shift bilan yozish
- 10.3 reduce ichida spread
- 10.4 Faqat eng kichigi kerak, lekin saralash
- 10.5 Eski maslahatlarga ko'r-ko'rona ishonish
- 11. Mashqlar
- 1-mashq (oson): Narxini ayting
- 2-mashq (o'rta): Yashirin sikllarni toping
- 3-mashq (qiyin): Xotirasi tozalanadigan navbat
- 4-mashq: Amaliy tajriba — narxlar jadvali
- 12. Real ishda
- Xulosa
- Manbalar
JS o'rnatilgan amallarining narxi: shift, includes, slice, spread va sort qancha turadi
Qisqacha: Bir qatorlik amal ham ichida sikl yashirishi mumkin. Massiv oxiri bilan ishlash (
push,pop,at,length) — O(1). Boshi bilan (shift,unshift) — O(n): qolgan elementlar suriladi. Qidirish (includes,indexOf,find) — O(n),Set.hasvaMap.getesa o'rtacha O(1). Nusxa oladiganlar (slice, spread,map,filter,Object.keys) — O(n) vaqt va O(n) xotira.sort— O(n log n). Bu amallar sikl ichiga tushsa, narxi n ga ko'payadi.
Bu darsda
- Massivning har asosiy metodi va
Map/Set/satr amallarining Big-O narxini jadvaldan ayta olasiz. shiftbilan navbat nega katta ma'lumotda qotib qolishini ko'rasiz va uni ko'rsatkich bilan tuzata olasiz.reduce+ spread,slicebilan rekursiya kabi yashirin O(n²) tuzoqlarni taniysiz.- Satr birlashtirish va
Object.keyshaqidagi eski maslahatlarni o'lchov bilan tekshirasiz.
Oldin bilishingiz kerak: Xotira murakkabligi va amortizatsiya, Element qo'shish va olib tashlash, Spread va rest operatori, reduce, Set.
1. Nega bu kerak?
Sardor «Bahor» uchun kichik dastur yozdi. U kunlik buyurtmalar faylini o'qiydi va har buyurtmani navbat bilan oshxona hisobotiga qo'shadi. Navbat uchun massiv metodlari darsidagi usulni tanladi: push bilan oxiriga qo'shish, shift bilan boshidan olish.
Bir kunlik fayl — 3 000 buyurtma. Dastur bir zumda ishladi. Keyin Jasur aka bir oylik hisobot so'radi: 60 000 buyurtma. Dastur 4 soniya "o'yladi". Kodda na ichma-ich sikl bor, na murakkab hisob — faqat bitta while va bitta shift.
Sababi — shift ning o'zi. U bir qatorlik, lekin ichida butun massivni aylanib chiqadi. Big-O'ni o'rganganimizda "yashirin sikl" degan gapni ko'p aytdik (Kodning murakkabligini hisoblash). Bugun JavaScript'ning eng ko'p ishlatiladigan amallarini birma-bir o'lchaymiz va narxlar jadvalini tuzamiz. Bu jadval keyingi barcha algoritm darslarida kerak bo'ladi.
Oshxonadan o'xshatish: menyuda narxi yozilmagan taom bor. Uni buyurtma qilsangiz, chek kelganda hayron qolasiz. O'rnatilgan amallar ham shunday — narxini bilmasangiz, hisob kelganda kech bo'ladi.
2. Massivning oxiri arzon, boshi qimmat
2.1 Indekslar qayta raqamlanadi
Massivda har element o'z indeksida turadi: 0, 1, 2... push oxiriga qo'shadi — boshqa elementlarga tegmaydi. pop oxiridan oladi — yana hech kim joyidan qo'zg'almaydi. Ikkalasi O(1) (push amortizatsiyalangan O(1)).
shift esa boshidan oladi. Shundan keyin 0-indeks bo'sh qolmasligi kerak: hamma qolgan elementlar bittadan chapga suriladi va yangi indeks oladi.
const queue = [101, 102, 103];
queue.push(104); // oxiriga — hech kim surilmaydi
console.log(queue.shift()); // 101
console.log(queue); // [ 102, 103, 104 ]Natija to'g'ri. Endi ichkarida nima bo'lganini qadamma-qadam ko'ramiz. Navbatda 5 ta buyurtma, oshxona ularni birma-bir oladi. moves — nechta element joyidan surilganini sanaydi. Kataklardagi belgilarga qarang: ● — olinayotgan buyurtma, ⇄ — joyidan surilayotganlar:
5 ta buyurtma — 10 ta surish. n ta buyurtma uchun: (n − 1) + (n − 2) + … + 1 = n · (n − 1) ÷ 2. Bu birinchi darsdagi "hamma juftlar" bilan bir xil yig'indi. Demak, bitta shift — O(n), n ta shift — O(n²).
unshift (boshiga qo'shish) — xuddi shunday, faqat teskari yo'nalishda: hamma element bittadan o'ngga suriladi. Bitta unshift — O(n).
2.2 O'lchov: kutilmagan jarlik
Navbatni shift bilan bo'shatishni o'lchadik. Taqqoslash uchun — massivga tegmasdan, faqat indeks bilan o'qish (buni hozir ko'ramiz). olcha usuli: har n alohida jarayonda, 5 o'lchov medianasi (Node 24.21, noutbuk protsessori):
| Buyurtmalar (n) | shift bilan |
Indeks bilan | unshift bilan qurish |
|---|---|---|---|
| 5 000 | ≈ 0,3 ms | ≈ 0,01 ms | ≈ 1,3 ms |
| 10 000 | ≈ 0,5 ms | ≈ 0,02 ms | ≈ 6,8 ms |
| 20 000 | ≈ 470 ms | ≈ 0,02 ms | ≈ 30 ms |
| 40 000 | ≈ 1 900 ms | ≈ 0,04 ms | ≈ 120 ms |
unshift ustuni darslikdagidek: n ikki baravar — vaqt to'rt baravar, O(n²). shift ustuni esa g'alati. 10 000 gacha u deyarli chiziqli va tez. 20 000 da birdan 900 baravar sekinlashdi. Keyin yana kvadratik: 40 000 da to'rt baravar.
Chegarani ikkiga bo'lib qidirdik (Node 24, V8 13.6): 16 383 ta elementdan boshlab shift haqiqatan O(n) bo'ladi. 16 382 ta elementli navbat — 0,9 ms, 16 383 tasi — 300 ms. Bu tasodif emas. V8 kichik massivlarda hiyla ishlatadi: elementlarni surish o'rniga massiv boshlanish manzilini bitta katakka siljitadi. Bu O(1).
Nega hiyla aynan shu yerda tugaydi? Bu — bizning taxminimiz (V8 manba kodiga tayanadi, to'g'ridan-to'g'ri tekshirmadik). Massivning ichki zaxirasi taxminan 128 KB dan oshsa (16 384 katak × 8 bayt ≈ 128 KB), V8 uni "katta obyektlar" uchun alohida xotira maydoniga qo'yadi. U yerda massiv boshini siljitib bo'lmaydi va shift hamma elementni suradi. Taxminni bitta kuzatish quvvatlaydi: biz navbatni Array.from bilan aniq o'lchamda yasadik; push bilan o'stirilgan massivning zaxirasi kattaroq (sig'im) va unda jarlik biroz oldinroq keldi. Boshqa Node versiyasida chegara boshqacha bo'lishi mumkin.
- shift
- indeks (head)
| Buyurtmalar | shift | indeks (head) |
|---|---|---|
| 5 | 0,3 | |
| 10 | 0,5 | |
| 20 | 473 | |
| 40 | 1 903 | |
| 5 | 0,01 | |
| 10 | 0,02 | |
| 20 | 0,02 | |
| 40 | 0,04 |
Manba: O'lchov: 12/32 dagi usul (har n alohida jarayonda, isitish, mediana), Node 24, i5-12500H; Node 24.21 (V8 13.6), Windows 11, 2026-10-06; 5 o'lchov
Grafikda jarlikka qarang: 10 mingdan 20 minggacha chiziq tik ko'tariladi. Sardorning kunlik fayli (3 000) jarlikdan oldin edi, oylik fayli (60 000) — keyin.
Diqqat: Dvigatel hiylalariga tayanmang. Ular versiyadan versiyaga o'zgaradi va boshqa brauzerda bo'lmasligi mumkin. Big-O — kafolat, hiyla — sovg'a.
shiftning kafolati — O(n).
2.3 Yechim: navbat boshini indeks bilan eslash
Elementni o'chirish shart emas. Navbat boshini bitta son bilan eslab qolamiz — head. Olish — head ni bittaga oshirish. Massiv joyidan qo'zg'almaydi:
class OrderQueue {
#items = [];
#head = 0; // navbat boshi — massivdagi indeks
enqueue(order) {
this.#items.push(order); // O(1) amortizatsiyalangan
}
dequeue() {
if (this.#head === this.#items.length) return undefined;
const order = this.#items[this.#head];
this.#head++; // hech kim surilmaydi — O(1)
return order;
}
get size() {
return this.#items.length - this.#head;
}
}
const kitchen = new OrderQueue();
kitchen.enqueue(101);
kitchen.enqueue(102);
console.log(kitchen.dequeue()); // 101
console.log(kitchen.size); // 1
console.log(kitchen.dequeue(), kitchen.dequeue()); // 102 undefined#items va #head — private maydonlar: tashqaridan o'zgartirib bo'lmaydi. Bo'sh navbatdan olish undefined qaytaradi — xuddi [].shift() kabi. Bu chegaraviy holatni oldindan o'yladik.
Bu yechimning bitta kamchiligi bor: olingan buyurtmalar massivda qolib ketadi va xotira egallaydi. Uni 3-mashqda tuzatasiz. To'liq navbat (deque) tuzilmasini Queue va deque darsida quramiz.
Tekshirib ko'ring: 50 000 ta buyurtmali massivda
orders.pop()vaorders.shift()— qaysi biri qimmat va nega?
Javob
Qimmati — shift. Oxirgi elementni olganda (pop) qolganlarning indeksi o'zgarmaydi, O(1). Boshidan olganda esa 49 999 ta element bittadan chapga surilishi kerak — O(n). Massivda 16 383 dan ko'p element bor, demak V8 hiylasi ham yordam bermaydi (Node 24 dagi o'lchovimiz bo'yicha).
3. Qidirish: includes va Set.has
Bu farqni Big-O notatsiyasi darsida o'lchagan edik. Qisqacha eslatamiz:
| Amal | Narxi | Nega |
|---|---|---|
arr.includes(x), indexOf, lastIndexOf |
O(n) | boshidan birma-bir solishtiradi |
arr.find, findIndex, some, every |
O(n) | har elementga callback chaqiradi |
set.has(x), map.get(k), map.has(k) |
O(1) o'rtacha | kalitdan "manzil" hisoblanadi |
obj[k], k in obj, Object.hasOwn(obj, k) |
O(1) o'rtacha | Map ga o'xshash |
find va some topgan joyida to'xtaydi — eng yaxshi holat O(1). Lekin eng yomon holatda (topilmasa) hammasini ko'radi. Set ning "o'rtacha" so'zi nima uchun turganini Hash table ichidan darsida ko'ramiz.
Qoida: bitta qidiruv uchun includes yetadi. Sikl ichida ko'p qidirsangiz — oldin bir marta Set yasang (O(n)), keyin har qidiruv O(1). Kodning murakkabligini hisoblash darsida 40 000 qidiruvda bu taxminan 75 baravar farq berdi.
4. Nusxa oladigan amallar
4.1 Har nusxa — n ta element
slice, spread ([...arr], {...obj}), concat, Array.from, map, filter, toSorted, toReversed — hammasi yangi massiv yoki obyekt yasaydi. Yangi massivga hamma elementni bittadan ko'chirish kerak. Demak, har biri O(n) vaqt va O(n) qo'shimcha xotira.
Bitta nusxa — muammo emas. Muammo nusxa siklga yoki rekursiyaga tushganda boshlanadi. Mana chiroyli ko'rinadigan rekursiv yig'indi. Har chaqiruvda nechta element ko'chirilganini sanaymiz:
let copied = 0;
function sumPrices(prices) {
if (prices.length === 0) return 0;
const rest = prices.slice(1); // birinchisidan boshqa hammasi
copied += rest.length;
return prices[0] + sumPrices(rest);
}
for (const n of [10, 100, 1000]) {
copied = 0;
const prices = Array.from({ length: n }, () => 5000);
const total = sumPrices(prices);
console.log(`n=${n}: jami ${total}, ko'chirildi ${copied}`);
}Konsolda:
n=10: jami 50000, ko'chirildi 45
n=100: jami 500000, ko'chirildi 4950
n=1000: jami 5000000, ko'chirildi 499500Yana o'sha yig'indi: (n − 1) + (n − 2) + … — n · (n − 1) ÷ 2. Yig'indini hisoblash O(n) bo'lishi kerak edi, slice uni O(n²) qildi. Xotira ham O(n²): har chaqiruvning nusxasi stekda kutib turadi.
Tuzatish — nusxa o'rniga indeksni uzatish:
function sumFrom(prices, i = 0) {
if (i === prices.length) return 0;
return prices[i] + sumFrom(prices, i + 1); // nusxa yo'q
}
console.log(sumFrom([35000, 28000, 30000])); // 93000Endi har chaqiruv O(1) ish qiladi — jami O(n). Rekursiya chuqurligi baribir n, shuning uchun juda uzun ro'yxat uchun oddiy sikl yaxshiroq (Xotira murakkabligi).
4.2 reduce + spread — eng mashhur yashirin O(n²)
Immutability darslarida "eski massivni o'zgartirmang, yangisini yasang" dedik. Bu yaxshi qoida. Lekin uni reduce ichida qo'llash tuzoqqa olib boradi:
const prices = [35000, 28000, 30000];
// ❌ har qadamda butun acc nusxalanadi
const slow = prices.reduce((acc, p) => [...acc, p * 1.12], []);
// ✅ acc — reduce'ning o'z massivi, uni to'ldirsak bo'ladi
const fast = prices.reduce((acc, p) => {
acc.push(p * 1.12);
return acc;
}, []);
console.log(slow.length, fast.length); // 3 3Ikkalasi bir xil natija beradi. Lekin birinchisi har qadamda acc ni to'liq nusxalaydi: 0 + 1 + 2 + … + (n − 1) ta ko'chirish — O(n²). Ikkinchisida acc — reduce ichida yaratilgan massiv, uni hech kim boshqa ko'rmaydi. Uni to'ldirish xavfsiz va O(n). Bu holatda eng sodda yo'l esa — prices.map((p) => p * 1.12).
Obyekt bilan bu tuzoq yanada qimmat. Taomlar ro'yxatidan "nom → narx" obyektini yasaymiz:
// ❌ har qadamda butun obyekt nusxalanadi
pairs.reduce((acc, [name, price]) => ({ ...acc, [name]: price }), {});
// ✅ bitta obyekt, har kalit bir marta yoziladi
const byName = {};
for (const [name, price] of pairs) byName[name] = price;O'lchov (n — taomlar soni, 5 o'lchov medianasi):
| n | Massiv: spread | Massiv: push |
|---|---|---|
| 1 000 | ≈ 0,9 ms | ≈ 0,09 ms |
| 2 000 | ≈ 2,7 ms | ≈ 0,18 ms |
| 4 000 | ≈ 12 ms | ≈ 0,2 ms |
| 8 000 | ≈ 69 ms | ≈ 0,26 ms |
| n | Obyekt: spread | Obyekt: tayinlash |
|---|---|---|
| 1 000 | ≈ 80 ms | ≈ 0,4 ms |
| 2 000 | ≈ 570 ms | ≈ 0,5 ms |
| 4 000 | ≈ 2 400 ms | ≈ 1,5 ms |
| 8 000 | ≈ 10 600 ms | ≈ 2,2 ms |
8 000 ta taomli obyektni spread bilan yig'ish — 10 soniyadan ko'p. Tayinlash bilan — 2 millisekund. Farq 5 000 baravar. Spread ustunlarida n ikki baravar — vaqt to'rt baravardan ham ko'p (kvadratik va undan yomon: katta obyekt nusxasi sekinroq). O'ng ustunlarda esa vaqt deyarli o'smaydi.
Tekshirib ko'ring:
const all = [...lunch, ...dinner];— bu ham spread. Bu yerda ham O(n²) bormi?
Javob
Yo'q. Bu bitta nusxa: tushlik va kechki buyurtmalarni bir marta yangi massivga ko'chiradi — O(a + b). Spread o'zi yomon emas. Yomoni — spread'ning siklda takrorlanishi, har safar kattalashib borayotgan narsani nusxalashi.
5. sort — O(n log n) va joyida
sort massivni joyida saralaydi: yangi massiv yasamaydi, asl massivni o'zgartiradi. toSorted esa nusxa qaytaradi (toSorted). Ikkalasining vaqti O(n log n) — Node 24 (V8 13.6) TimSort ishlatadi (Asosiy murakkablik sinflari). Xotira: toSorted — O(n) nusxa, sort ham TimSort uchun yordamchi joy oladi, lekin nusxa qaytarmaydi.
O'lchov (urug'li tasodifiy sonlar, sort((a, b) => a - b)):
| Sonlar (n) | Vaqt | Nisbat |
|---|---|---|
| 100 000 | ≈ 28 ms | — |
| 200 000 | ≈ 58 ms | × 2,05 |
| 400 000 | ≈ 123 ms | × 2,11 |
| 800 000 | ≈ 256 ms | × 2,08 |
n ikki baravar — vaqt ikki baravardan biroz ko'p. Bu n · log n ning belgisi.
Saralash arzon emas. Shuning uchun uni faqat tartib kerak bo'lganda ishlating. Eng arzon taomni topish uchun saralash — ortiqcha ish:
const menu = [35000, 28000, 30000, 5000];
// ❌ O(n log n) va asl massiv buzildi
// const cheapest = menu.sort((a, b) => a - b)[0];
// ✅ O(n), massiv o'zgarmaydi
let cheapest = menu[0];
for (const price of menu) {
if (price < cheapest) cheapest = price;
}
console.log(cheapest); // 5000Sikl ichida saralash — undan ham yomon: n marta O(n log n) — O(n² log n). Masalan, har yangi buyurtma kelganda butun ro'yxatni qayta saralash. Bunday holatda "doim eng kichigini beradigan" tuzilma kerak — Heap.
Diqqat: Taqqoslash funksiyasisiz
sortsonlarni matn sifatida saralaydi:[10, 9, 1].sort()→[ 1, 10, 9 ]. Bu narx emas, xato.sortva taqqoslash funksiyasi darsida ko'rgan edik.
6. Satr birlashtirish: += yoki join?
Eski maqolalarda shunday maslahat bor: "Siklda satrni += bilan yig'mang, massivga push qilib, oxirida join qiling". Sababi mantiqiy tuyuladi: satr o'zgarmas (String asoslari), demak har += butun satrni nusxalaydi — O(n²).
Tekshirib ko'ramiz. Chekka n ta "osh," so'zini qo'shamiz:
| Bo'laklar (n) | s += "osh," |
push + join(",") |
|---|---|---|
| 250 000 | ≈ 2,9 ms | ≈ 4,1 ms |
| 500 000 | ≈ 5,8 ms | ≈ 12 ms |
| 1 000 000 | ≈ 9,8 ms | ≈ 21 ms |
| 2 000 000 | ≈ 19 ms | ≈ 40 ms |
Ikkalasi ham chiziqli, += hatto ikki baravar tezroq. Sababi: V8 += da satrni nusxalamaydi. U ikki bo'lakni "arqon" (rope) qilib bog'laydi — "chap qism + o'ng qism" degan kichik yozuv. Haqiqiy uzluksiz satr faqat kerak bo'lganda (indeks bilan o'qish, chiqarish) bir marta yig'iladi. Satr o'zgarmasligicha qoladi — dvigatel shunchaki nusxalashni kechiktiradi.
Xulosa: zamonaviy dvigatelda ikkalasi ham O(n). O'qish uchun qulayini tanlang. Ajratuvchi kerak bo'lsa (", ") — join qulayroq, chunki oxirida ortiqcha vergul qolmaydi.
7. Object.keys va do'stlari
Object.keys, Object.values, Object.entries har chaqiruvda yangi massiv yasaydi va unga hamma kalitni yozadi. Obyektda k ta kalit bo'lsa — O(k) vaqt va xotira. Ko'pincha muammo yo'q. Muammo — ularni sikl ichida chaqirish:
// ❌ har buyurtmada butun menyu kalitlari qayta yig'iladi: O(n · k)
for (const order of orders) {
if (Object.keys(menu).includes(order.dish)) count++;
}
// ✅ kalit borligini to'g'ridan-to'g'ri so'rang: O(n)
for (const order of orders) {
if (Object.hasOwn(menu, order.dish)) count++;
}Birinchi variantda ikki yashirin sikl bor: Object.keys (O(k)) va includes (O(k)). O'lchov: Object.keys(menu) ni 1 000 marta chaqirdik. Menyuda 2 000 kalit — 139 ms, 4 000 — 417 ms, 8 000 — 906 ms. Har chaqiruv kalitlar soniga proporsional, kutilganidek. Object.hasOwn esa kalitlar soniga bog'liq emas — o'rtacha O(1).
8. Narxlar jadvali
Hammasini bitta jadvalga yig'amiz. n — massiv uzunligi, k — kalitlar soni yoki nusxalanadigan qism uzunligi:
| Amal | Vaqt | Qo'shimcha xotira |
|---|---|---|
arr[i], arr.at(i), arr.length |
O(1) | O(1) |
push, pop |
O(1) amort. | O(1) |
shift, unshift |
O(n) | O(1) |
splice (o'rtadan) |
O(n) | O(k) |
includes, indexOf, find, some |
O(n) | O(1) |
slice, spread, concat, Array.from |
O(n) | O(n) |
map, filter, flat |
O(n) | O(n) |
forEach, reduce (yig'uvchi son bo'lsa) |
O(n) | O(1) |
reverse / toReversed |
O(n) | O(1) / O(n) |
sort / toSorted |
O(n log n) | O(n) |
join, str.split, += siklda |
O(n) | O(n) |
Set.has, Map.get, obj[k], Object.hasOwn |
O(1) o'rt. | O(1) |
new Set(arr), new Map(entries) |
O(n) | O(n) |
Object.keys, values, entries |
O(k) | O(k) |
JSON.stringify, structuredClone |
O(n) | O(n) |
Jadvalni yodlash shart emas. Bitta savol kifoya: "Bu amal har elementga tegadimi?" Tegsa — O(n). Yangi massiv qaytarsa — xotira ham O(n). splice ni keyingi darsda xotira nuqtai nazaridan ko'ramiz.
9. Chegaraviy holatlar
O'rnatilgan amallar bo'sh massivda xato bermaydi, lekin kutilmagan qiymat qaytaradi:
console.log([].shift()); // undefined
console.log([].pop()); // undefined
console.log(Math.max(...[])); // -Infinity
console.log([].includes(undefined)); // false
console.log([, 1].includes(undefined)); // trueMath.max(...[]) — -Infinity: "eng katta" topilmadi, lekin xato ham yo'q. Bo'sh ro'yxatni oldindan tekshiring. Oxirgi qator — teshikli massiv (Teshikli massivlar): includes bo'sh katakni undefined deb ko'radi. Juda katta massivni spread qilish esa (Math.max(...big)) RangeError beradi — buni Xotira murakkabligi darsida ko'rgan edik.
10. Ko'p uchraydigan xatolar
10.1 "Bir qator — O(1)"
orders.includes(id) bitta qator, lekin O(n). Tuzatish: har metodni jadvaldan tekshiring yoki "har elementga tegadimi?" deb so'rang.
10.2 Navbatni shift bilan yozish
Kichik sinovda tez, katta ma'lumotda O(n²). Tuzatish: head indeksli navbat yoki deque.
10.3 reduce ichida spread
[...acc, x] va { ...acc, [k]: v } siklda — O(n²). Tuzatish: map, oddiy sikl yoki acc ni to'ldirish. Object.fromEntries(pairs) ham O(n).
10.4 Faqat eng kichigi kerak, lekin saralash
sort(...)[0] — O(n log n) va asl massiv buziladi. Tuzatish: bitta sikl, O(n).
10.5 Eski maslahatlarga ko'r-ko'rona ishonish
"+= sekin", "for har doim forEach dan tez" — dvigatellar o'zgaradi. Tuzatish: shubha bo'lsa — o'lchang (Performansni o'lchash).
11. Mashqlar
1-mashq (oson): Narxini ayting
Har qatorning vaqt murakkabligini yozing (n — orders uzunligi):
- (a)
orders.at(-1) - (b)
orders.indexOf(105) - (c)
orders.slice(0, 10) - (d)
orders.toSorted((a, b) => a - b) - (e)
new Set(orders).has(105) - (f)
orders.unshift(100)
Yechim
(a) O(1) — oxirgi indeks bilan olish. (b) O(n) — boshidan qidiradi. (c) O(10) = O(1): slice faqat ko'chiradigan qismiga proporsional, bu yerda doim 10 ta. Umumiy holda O(k). (d) O(n log n). (e) O(n) — Set ni yasash har elementga tegadi; has ning o'zi O(1), lekin qatorni bir butun deb olsak — O(n). Bir marta yasab, ko'p marta so'rasangiz — foyda. (f) O(n) — hamma element o'ngga suriladi.
2-mashq (o'rta): Yashirin sikllarni toping
Sardor kunlik buyurtmalardan takrorlanmas taomlar ro'yxatini yasadi:
function uniqueDishes(orders) {
let result = [];
for (const dish of orders) {
if (!result.includes(dish)) result = [...result, dish];
}
return result;
}
console.log(uniqueDishes(["osh", "manti", "osh", "lag'mon"]));Konsolda:
[ 'osh', 'manti', "lag'mon" ]Bu kodda nechta yashirin sikl bor? Umumiy murakkablik qancha? Uni O(n) ga tushiring — tartib saqlansin. Ishora: Set qo'shilish tartibini eslab qoladi (Set).
Yechim
Ikki yashirin sikl: includes (O(n)) va spread (O(n)). Ikkalasi ham tashqi sikl ichida — O(n²). Takrorlanmas taomlar ko'p bo'lsa, ikkalasi ham to'liq ishlaydi. Tuzatilgani:
function uniqueDishes(orders) {
return [...new Set(orders)]; // bitta o'tish va bitta nusxa
}
console.log(uniqueDishes(["osh", "manti", "osh", "lag'mon"]));Konsolda:
[ 'osh', 'manti', "lag'mon" ]new Set(orders) — O(n), har taom bir marta qo'shiladi, takrorlari e'tiborsiz qoladi. Spread — yana O(n). Jami O(n). Natijadagi qo'shtirnoq farqi ('osh' va "lag'mon") — Node'ning ko'rsatish usuli: ichida apostrof bor satrni qo'shtirnoq bilan chiqaradi.
3-mashq (qiyin): Xotirasi tozalanadigan navbat
Darsdagi OrderQueue da olingan buyurtmalar #items da qolib ketadi: million buyurtma o'tsa, massivda million eski yozuv turadi. Shu kamchilikni tuzating. Faylingiz — kurs/mashqlar/14/05-narx/navbat.test.mjs. Unda OrderQueue ni yaxshilang: head massivning yarmidan oshganda, qolgan elementlarni boshiga ko'chirib, eskilarini tashlang. Bu ko'chirish O(n), lekin kamdan-kam bo'ladi — amortizatsiya darsidagi kabi o'rtacha O(1). Testlash uchun stored getter qo'shing — #items.length. Testlar (node:test):
- Navbat FIFO: 101, 102, 103 qo'shilsa — shu tartibda chiqadi.
- Bo'sh navbatdan
dequeue()—undefined,size— 0. - 1 000 qo'shib, 999 tasini olgandan keyin
stored10 dan kichik (eski yozuvlar tashlangan), qolgan buyurtma to'g'ri. - Aralash ish: 10 000 marta "ikkita qo'sh, bittasini ol" — tartib buzilmaydi,
size10 000.
Ishora: siqishni dequeue ichida qiling: if (this.#head * 2 >= this.#items.length) — #items = this.#items.slice(this.#head) va #head = 0.
Yechim
// kurs/mashqlar/14/05-narx/navbat.test.mjs
import { test } from "node:test";
import assert from "node:assert/strict";
class OrderQueue {
#items = [];
#head = 0;
enqueue(order) {
this.#items.push(order);
}
dequeue() {
if (this.#head === this.#items.length) return undefined;
const order = this.#items[this.#head];
this.#head++;
// yarmidan ko'pi "o'lik" bo'lsa — siqamiz (kamdan-kam, O(n))
if (this.#head * 2 >= this.#items.length) {
this.#items = this.#items.slice(this.#head);
this.#head = 0;
}
return order;
}
get size() {
return this.#items.length - this.#head;
}
get stored() {
return this.#items.length;
}
}
test("FIFO tartib", () => {
const q = new OrderQueue();
for (const id of [101, 102, 103]) q.enqueue(id);
const out = [q.dequeue(), q.dequeue(), q.dequeue()];
assert.deepEqual(out, [101, 102, 103]);
});
test("bo'sh navbat", () => {
const q = new OrderQueue();
assert.equal(q.dequeue(), undefined);
assert.equal(q.size, 0);
});
test("eski yozuvlar tashlanadi", () => {
const q = new OrderQueue();
for (let i = 0; i < 1000; i++) q.enqueue(i);
for (let i = 0; i < 999; i++) q.dequeue();
assert.ok(q.stored < 10, `saqlangan: ${q.stored}`);
assert.equal(q.dequeue(), 999);
});
test("aralash ish — tartib buzilmaydi", () => {
const q = new OrderQueue();
let next = 0;
let expected = 0;
for (let i = 0; i < 10000; i++) {
q.enqueue(next++);
q.enqueue(next++);
assert.equal(q.dequeue(), expected++);
}
assert.equal(q.size, 10000);
});Papkada node --test ni ishga tushiring. Bizda (Node 24.21.0) shunday chiqdi — millisekundlar sizda boshqacha:
✔ FIFO tartib (1.2875ms)
✔ bo'sh navbat (0.1704ms)
✔ eski yozuvlar tashlanadi (0.9905ms)
✔ aralash ish — tartib buzilmaydi (2.0664ms)
ℹ tests 4
ℹ suites 0
ℹ pass 4
ℹ fail 0
ℹ cancelled 0
ℹ skipped 0
ℹ todo 0
ℹ duration_ms 115.6174Uchinchi testda 999 ta dequeue dan keyin stored — 1: navbatda bitta buyurtma qoldi va massivda ham faqat u. Siqish faqat head yarmidan oshganda bo'ladi, shuning uchun har siqishdan oldin kamida shuncha arzon dequeue o'tgan — jami ish O(n). Bu push dagi sig'im o'sishining aynan teskarisi.
4-mashq: Amaliy tajriba — narxlar jadvali
kurs/mashqlar/14/MURAKKABLIK.md ga yangi bo'lim qo'shing: "JS amallari". Darsdagi narxlar jadvalidan kamida 8 qatorni o'z so'zingiz bilan yozing va har biriga "Sikl ichida bo'lsa" ustunini qo'shing. Asosiy jadvalga ikki qator: navbat shift bilan va head bilan.
Yechim
## JS amallari
| Amal | Vaqt | Xotira | Sikl ichida bo'lsa |
|---|---|---|---|
| push / pop | O(1) amort. | O(1) | O(n) — muammo yo'q |
| shift / unshift | O(n) | O(1) | O(n²) — head indeks |
| includes / indexOf | O(n) | O(1) | O(n²) — avval Set |
| Set.has / Map.get | O(1) o'rt. | O(1) | O(n) |
| slice / spread | O(n) | O(n) | O(n²) — indeks uzating |
| sort | O(n log n) | O(n) | O(n² log n) — heap |
| Object.keys | O(k) | O(k) | O(n·k) — Object.hasOwn |
| s += "..." | O(1) amort. | — | O(n) — V8 rope |
| Masala | Yechim | Vaqt | Xotira |
|---|---|---|---|
| Navbat | shift bilan | O(n²) | O(1) |
| Navbat | head indeks | O(n) | O(n) |git add 14/MURAKKABLIK.md 14/05-narx
git commit -m "14/05: JS amallari narxi, siqiladigan navbat"12. Real ishda
- Kod ko'rib chiqish (code review). Tajribali dasturchilar PR'da birinchi navbatda sikl ichidagi
includes,find, spread vaObject.keysni qidiradi. Bu eng ko'p uchraydigan performans xatolari. - Frontend holati. React kabi kutubxonalarda holat (state) yangilanganda spread bilan yangi obyekt yasash odatiy (React — 17-qismda). Bitta yangilanishda bu O(n) va normal. Lekin minglab elementni siklda spread qilish sahifani qotiradi.
- Server va loglar. Navbat bilan ishlovchi skriptlar (loglar, xabarlar, fayllar) ko'pincha
shifttufayli katta ma'lumotda sekinlashadi. Node'da tayyor navbat yo'q — shu darsdagiheadusuli yoki kutubxona ishlatiladi. - Intervyu. "
shiftning murakkabligi?", "Bureducenega sekin?", "includeso'rniga nima ishlatasiz?" — boshlang'ich darajadagi Big-O savollarining yarmi shu jadvaldan.
Xulosa
- Bir qatorlik amal ham O(n) yoki undan qimmat bo'lishi mumkin. Savol: "har elementga tegadimi?"
- Massiv oxiri (
push,pop) — O(1), boshi (shift,unshift) — O(n). V8 kichik massivdashiftni tezlashtiradi, lekin Node 24 (V8 13.6) da 16 383 va undan ko'p elementli massivda u haqiqatan O(n) — 20 000 da 900 baravar sekinlashish o'lchandi. - Navbatni
headindeks bilan yozing — har olish O(1). - Nusxa oladigan amallar (
slice, spread,map,filter,Object.keys) — O(n) vaqt va xotira. Siklda yoki rekursiyada — O(n²): 8 000 kalitli obyektni spread bilan yig'ish 10 soniyadan ko'p oldi. sort— O(n log n); faqat eng kichigi kerak bo'lsa — bitta sikl. V8 da satrni+=bilan yig'ish ham O(n).
Keyingi dars: Massiv xotirada va joyida amallar — massiv nega indeks bilan tez, o'rtaga qo'shish nega sekin, joyida (in-place) ishlash va protsessor keshi tezlikka qanday ta'sir qiladi.
Manbalar
- MDN:
Array.prototype.shift(),Array.prototype.splice(),Object.keys(),Object.hasOwn()— developer.mozilla.org - V8 manba kodi:
Heap::CanMoveObjectStart(katta obyektlar boshini siljitib bo'lmaydi),kMaxRegularHeapObjectSize(128 KB) — github.com/v8/v8 - V8 blogi: "Getting things sorted in V8" (2018) —
sortuchun TimSort — v8.dev/blog/array-sort - ECMAScript 2025 spetsifikatsiyasi:
Array.prototype.shiftalgoritmi (har element bittadan suriladi) — tc39.es/ecma262
Izohlar (0)
Izoh yozish uchun kiring.
- Hozircha izoh yo'q. Birinchi bo'ling!