IlmHamroh
JavaScript Full-stack/14-qism. Algoritmlar va ma'lumotlar tuzilmalari5/60-dars23 daqiqa
Mundarija (29)

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.has va Map.get esa 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.
  • shift bilan navbat nega katta ma'lumotda qotib qolishini ko'rasiz va uni ko'rsatkich bilan tuzata olasiz.
  • reduce + spread, slice bilan rekursiya kabi yashirin O(n²) tuzoqlarni taniysiz.
  • Satr birlashtirish va Object.keys haqidagi 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.

js
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.

Navbatni bo'shatish: shift va indeks
Vaqt, ms
1 9030,01540Buyurtmalar, mingshift: 5 ming → 0,3 msshift: 10 ming → 0,5 msshift: 20 ming → 473 msshift: 40 ming → 1 903 msindeks (head): 5 ming → 0,01 msindeks (head): 10 ming → 0,02 msindeks (head): 20 ming → 0,02 msindeks (head): 40 ming → 0,04 ms
  • shift
  • indeks (head)
Navbatni bo'shatish: shift va indeks
Buyurtmalarshiftindeks (head)
50,3
100,5
20473
401 903
50,01
100,02
200,02
400,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. shift ning 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:

js
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() va orders.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:

js
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:

text
n=10: jami 50000, ko'chirildi 45
n=100: jami 500000, ko'chirildi 4950
n=1000: jami 5000000, ko'chirildi 499500

Yana 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:

js
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])); // 93000

Endi 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:

js
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 3

Ikkalasi 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:

js
// ❌ 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:

js
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); // 5000

Sikl 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 sort sonlarni matn sifatida saralaydi: [10, 9, 1].sort() → [ 1, 10, 9 ]. Bu narx emas, xato. sort va 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:

js
// ❌ 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:

js
console.log([].shift()); // undefined
console.log([].pop()); // undefined
console.log(Math.max(...[])); // -Infinity
console.log([].includes(undefined)); // false
console.log([, 1].includes(undefined)); // true

Math.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:

js
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:

text
[ '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:

js
function uniqueDishes(orders) {
  return [...new Set(orders)]; // bitta o'tish va bitta nusxa
}

console.log(uniqueDishes(["osh", "manti", "osh", "lag'mon"]));

Konsolda:

text
[ '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):

  1. Navbat FIFO: 101, 102, 103 qo'shilsa — shu tartibda chiqadi.
  2. Bo'sh navbatdan dequeue() — undefined, size — 0.
  3. 1 000 qo'shib, 999 tasini olgandan keyin stored 10 dan kichik (eski yozuvlar tashlangan), qolgan buyurtma to'g'ri.
  4. Aralash ish: 10 000 marta "ikkita qo'sh, bittasini ol" — tartib buzilmaydi, size 10 000.

Ishora: siqishni dequeue ichida qiling: if (this.#head * 2 >= this.#items.length) — #items = this.#items.slice(this.#head) va #head = 0.

Yechim
js
// 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:

text
✔ 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.6174

Uchinchi 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
text
## 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) |
bash
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 va Object.keys ni 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 shift tufayli katta ma'lumotda sekinlashadi. Node'da tayyor navbat yo'q — shu darsdagi head usuli yoki kutubxona ishlatiladi.
  • Intervyu. "shift ning murakkabligi?", "Bu reduce nega sekin?", "includes o'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 massivda shift ni 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 head indeks 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) — sort uchun TimSort — v8.dev/blog/array-sort
  • ECMAScript 2025 spetsifikatsiyasi: Array.prototype.shift algoritmi (har element bittadan suriladi) — tc39.es/ecma262
Ulashish:Telegram'da

Izohlar (0)

Izoh yozish uchun kiring.

  • Hozircha izoh yo'q. Birinchi bo'ling!
JS o'rnatilgan amallarining narxi: shift, includes, slice, spread va sort qancha turadi — IlmHamroh