IlmHamroh
JavaScript Full-stack/10-qism. JavaScript chuqur8/65-dars24 daqiqa
Mundarija (31)

JavaScript Object.groupBy va Map.groupBy: massivni guruhlarga ajratish

Qisqacha: Object.groupBy(massiv, callback) har element uchun callback'ni chaqiradi va u qaytargan kalit bo'yicha elementlarni guruhlarga yig'adi: Object.groupBy(menyu, (t) => t.tur) → { taom: [...], ichimlik: [...] }. Natija — prototipsiz oddiy obyekt; bo'sh guruh yaratilmaydi. Kalit satr bo'lmasligi kerak bo'lsa — Map.groupBy.

Bu darsda

  • Object.groupBy bilan ro'yxatni bitta chaqiruvda guruhlarga ajrata olasiz.
  • Callback qanday kalit qaytarishi kerakligini va kalit satrga aylanishini bilasiz.
  • Natija nega [Object: null prototype] ekanini va yo'q guruh nimaga olib kelishini tushuntira olasiz.
  • Guruhlardan statistika — son va yig'indi — chiqara olasiz; Map.groupBy qachon kerakligini bilasiz.
  • vazifalar hisobiga "nechta bajarildi" ni qo'shasiz.

Oldin bilishingiz kerak: reduce, Obyekt bo'ylab yurish, ??, ?. va mantiqiy tayinlash, Obyekt: kalit-qiymat juftliklari, flat, flatMap, fill va Array.from.

1. Nega bu kerak?

reduce darsida "taomlarni narx toifasi bo'yicha guruhlash — reduce ning klassik ishi" degan edik. Qanday yozilishini eslaylik. Menyuni tur bo'yicha — taomlar va ichimliklar — ajratamiz:

js
const menyu = [
  { nom: "Osh", narx: 35000, tur: "taom" },
  { nom: "Ko'k choy", narx: 5000, tur: "ichimlik" },
  { nom: "Lag'mon", narx: 28000, tur: "taom" },
];

const turlar = menyu.reduce((natija, taom) => {
  natija[taom.tur] ??= [];
  natija[taom.tur].push(taom);
  return natija;
}, {});

console.log(Object.keys(turlar)); // [ 'taom', 'ichimlik' ]

Ishlaydi. Lekin har safar besh qator: bo'sh obyekt, "guruh yo'q bo'lsa yarat" (??= — mantiqiy tayinlash), push, return. Faqat bitta narsa haqiqatan muhim — qaysi maydon bo'yicha guruhlayapmiz: taom.tur. Qolgani har safar takrorlanadigan qolip.

Bu qolip shunchalik ko'p yozilganki, 2024-yilda (ES2024) JavaScript'ga alohida funksiya qo'shildi:

js
const menyu = [
  { nom: "Osh", narx: 35000, tur: "taom" },
  { nom: "Ko'k choy", narx: 5000, tur: "ichimlik" },
  { nom: "Lag'mon", narx: 28000, tur: "taom" },
];

const turlar = Object.groupBy(menyu, (taom) => taom.tur);

console.log(Object.keys(turlar)); // [ 'taom', 'ichimlik' ]

Bir qator. Niyat nomda: "group by" — "... bo'yicha guruhla".

Hayotdan o'xshatish: kechqurun Otabek yuvilgan idishlarni javonlarga teradi. Har idishni qo'liga olib, bitta savol beradi: "bu nima?" Kosa — kosalar javoniga, choynak — choynaklar javoniga, piyola — piyolalar javoniga. Javon nomi — kalit, idishlar — elementlar, Otabekning savoli esa — callback. Yangi turdagi idish chiqsa, yangi javon ochiladi. Hech qachon ishlatilmagan javon esa umuman ochilmaydi.

Reja: avval Object.groupBy natijasini to'liq ko'ramiz va uning uchta qoidasini yozib olamiz. Keyin callback qanday kalit qaytarishi kerakligi va funksiyaning "ichi". So'ng ikki muhim xususiyat: natija — prototipsiz obyekt, va bo'sh guruh umuman yaratilmaydi. Oxirida guruhlardan statistika, Map.groupBy va vazifalar hisobiga yangi son. O'xshatishdagi oxirgi gapni eslab qoling — "ishlatilmagan javon ochilmaydi" — darsdagi eng ko'p uchraydigan xato aynan shundan chiqadi.

2. Birinchi Object.groupBy

2.1 Natija qanday ko'rinadi

To'rt taomli menyuni tur bo'yicha guruhlaymiz va natijani to'liq chiqaramiz:

js
const menyu = [
  { nom: "Osh", narx: 35000, tur: "taom" },
  { nom: "Ko'k choy", narx: 5000, tur: "ichimlik" },
  { nom: "Lag'mon", narx: 28000, tur: "taom" },
  { nom: "Kompot", narx: 8000, tur: "ichimlik" },
];

console.log(Object.groupBy(menyu, (taom) => taom.tur));

Konsolda:

text
[Object: null prototype] {
  taom: [
    { nom: 'Osh', narx: 35000, tur: 'taom' },
    { nom: "Lag'mon", narx: 28000, tur: 'taom' }
  ],
  ichimlik: [
    { nom: "Ko'k choy", narx: 5000, tur: 'ichimlik' },
    { nom: 'Kompot', narx: 8000, tur: 'ichimlik' }
  ]
}

Boshidagi [Object: null prototype] ni hozircha e'tiborsiz qoldiring — «Natija: prototipsiz obyekt» bo'limida tushuntiramiz. Qolgani — oddiy obyekt: har kalit — guruh nomi, qiymati — shu guruhga tushgan elementlar massivi.

Sxemada jarayon: callback har elementga "kalitingni ayt" deydi va element o'sha kalitli massivga tushadi.

flowchart LR
  O["Osh"] -- "tur: taom" --> T["taom: [Osh, Lag'mon]"]
  L["Lag'mon"] -- "tur: taom" --> T
  C["Ko'k choy"] -- "tur: ichimlik" --> I["ichimlik: [Choy, Kompot]"]
  K["Kompot"] -- "tur: ichimlik" --> I

2.2 Uchta qoida

Natijaga qarab, uchta qoidani yozib qo'yamiz:

  1. Guruh ichida tartib saqlanadi. Osh lag'mondan oldin edi — guruhda ham oldin.
  2. Guruhlar birinchi uchrash tartibida. taom birinchi elementda paydo bo'ldi, ichimlik — ikkinchida. Kalitlar tartibi ham shunday.
  3. Elementlar nusxalanmaydi. Guruhdagi Osh — menyudagi o'sha obyekt:
js
const menyu = [{ nom: "Osh", tur: "taom" }];
const turlar = Object.groupBy(menyu, (t) => t.tur);

console.log(turlar.taom[0] === menyu[0]); // true

filter dagidek: yangi massivlar, lekin ichida o'sha obyektlar. Guruh orqali osh narxini o'zgartirsangiz, menyudagisi ham o'zgaradi.

2.3 Object.groupBy — massiv metodi emas

E'tibor bering: menyu.groupBy(...) emas, Object.groupBy(menyu, ...). Bu Object ning funksiyasi va massiv birinchi argument sifatida beriladi — xuddi Array.from (oldingi dars) yoki Object.keys kabi.

Nega shunday? JavaScript'ga yangi narsa qo'shadigan qo'mita (TC39) avval massivning o'ziga group metodini qo'shmoqchi bo'lgan. Lekin bu nom ba'zi eski saytlar kodi bilan to'qnashdi: o'sha saytlar buzilib qolishi mumkin edi. Shuning uchun funksiya Object ga ko'chirildi.

Bu joylashuvning bitta foydasi ham bor: Object.groupBy faqat massiv emas, istalgan iterable ni qabul qiladi — masalan satrni (Array.from darsidagi atama):

js
const harflar = Object.groupBy("salom", (h) =>
  "aeiou".includes(h) ? "unli" : "undosh",
);

console.log(harflar.unli); // [ 'a', 'o' ]

Tekshirib ko'ring: Object.groupBy([5, 12, 8, 30], (n) => (n > 10 ? "katta" : "kichik")) natijasida kichik guruhi qanday?

Javob

[ 5, 8 ]. Guruh ichida asl tartib saqlanadi: 5 birinchi, 8 uchinchi edi. katta — [ 12, 30 ]. Kalitlar tartibi: avval kichik (birinchi element — 5), keyin katta.

3. Callback va kalit

3.1 Imzo: (element, index)

Callback ikki argument oladi — element va indeks. Uchinchisi, butun massiv, yo'q: Object.groupBy istalgan iterable bilan ishlaydi, iterable'ning esa "butun massivi" bo'lmasligi mumkin. Odatda faqat element kerak.

Indeks bilan, masalan, navbatni ikki kassaga bo'lish mumkin — juft o'rindagilar birinchi kassaga, toq o'rindagilar ikkinchisiga:

js
const navbat = ["Ali", "Malika", "Jasur", "Bobur"];

const kassalar = Object.groupBy(navbat, (_, i) =>
  i % 2 === 0 ? "1-kassa" : "2-kassa",
);

console.log(kassalar["1-kassa"]); // [ 'Ali', 'Jasur' ]
console.log(kassalar["2-kassa"]); // [ 'Malika', 'Bobur' ]

Element bizga kerak emas, shuning uchun birinchi parametr — _ (Callback imzosi darsidagi kelishuv: "bu argumentni ishlatmayman"). Kalitda chiziqcha bor — shuning uchun nuqta bilan emas, kvadrat qavs bilan o'qiymiz (Obyekt: kalit-qiymat juftliklari).

3.2 Kalit satrga aylanadi

Obyekt kalitlari doim satr. Callback son yoki boolean qaytarsa, u satrga aylantiriladi:

js
const menyu = [
  { nom: "Osh", narx: 35000 },
  { nom: "Ko'k choy", narx: 5000 },
];

const guruhlar = Object.groupBy(menyu, (t) => t.narx > 20000);

console.log(Object.keys(guruhlar)); // [ 'true', 'false' ]

Ishlaydi, lekin guruhlar.true va guruhlar.false ni o'qigan odam "nima true?" deb so'raydi. Yaxshisi — kalitga ma'noli nom bering. Bir nechta guruh bo'lsa, kalitni alohida nomli funksiyada hisoblang:

js
function narxToifasi(taom) {
  if (taom.narx >= 30000) {
    return "qimmat";
  }
  if (taom.narx >= 10000) {
    return "o'rta";
  }
  return "arzon";
}

const menyu = [
  { nom: "Osh", narx: 35000 },
  { nom: "Ko'k choy", narx: 5000 },
  { nom: "Lag'mon", narx: 28000 },
];

const toifalar = Object.groupBy(menyu, narxToifasi);
console.log(toifalar["o'rta"].map((t) => t.nom)); // [ "Lag'mon" ]

O'zingiz to'ldiring. Yuqoridagi menyuda toifalar.qimmat guruhida nechta taom bor — [:1]. Ko'k choy qaysi kalitli guruhga tushdi — [:arzon].

narxToifasi — callback, uni qavssiz beramiz. U faqat bitta parametr oladi, shuning uchun ikkinchi argument — indeks — hech narsani buzmaydi (Callback imzosi dagi tekshiruv). Funksiya nomi — natija nomi: "taomning narx toifasi".

Tekshirib ko'ring: Callback return siz yozilsa nima bo'ladi? Object.groupBy(menyu, (t) => { t.tur; })

Javob

Hamma element bitta guruhga — undefined kalitli guruhga — tushadi: { undefined: [ ...hammasi ] }. Callback har safar undefined qaytardi, u "undefined" satriga aylandi. Xato xabari yo'q. Jingalak qavsli arrow'da return ni unutmang.

4. groupBy ichidan

Oldingi darslardagidek, Object.groupBy ning soddalashtirilgan nusxasini yozamiz. U «Nega bu kerak?» bo'limidagi reduce qolipining o'zi — faqat kalitni callback beradi:

js
function guruhla(royxat, kalitOl) {
  return royxat.reduce((natija, element, i) => {
    const kalit = kalitOl(element, i);
    natija[kalit] ??= [];
    natija[kalit].push(element);
    return natija;
  }, {});
}

const menyu = [
  { nom: "Osh", tur: "taom" },
  { nom: "Ko'k choy", tur: "ichimlik" },
  { nom: "Lag'mon", tur: "taom" },
];

const turlar = guruhla(menyu, (t) => t.tur);
console.log(turlar.taom.length, turlar.ichimlik.length); // 2 1

Uchta qadam: kalitni ol, guruh yo'q bo'lsa — bo'sh massiv yarat, elementni guruhga qo'sh. Qadamlarni birma-bir kuzating — eng muhimi ??= qatori: u birinchi taom da guruh yaratadi, ikkinchisida esa hech narsa qilmaydi.

Akkumulyatorni o'zgartirish bu yerda xavfsiz — u reduce boshida yangi yaratilgan (reduce darsidagi qoida).

Bizning nusxa haqiqiysidan bitta muhim joyda farq qiladi: boshlang'ich qiymat — oddiy {}. Bu farq nimaga olib kelishini keyingi bo'limda ko'ramiz.

5. Natija: prototipsiz obyekt

5.1 Oddiy obyektning "meros" kalitlari

Obyekt: kalit-qiymat juftliklari darsida "toString" in choynak — true chiqqan edi: har oddiy obyekt toString, constructor kabi tayyor xususiyatlarni meros qilib oladi. Odatda bu bezarar. Lekin kalitlar foydalanuvchi matnidan kelsa — muammo.

Mijozlar sharhlarga teg yozadi, biz teglarni guruhlaymiz. Kimdir tajriba uchun constructor deb yozdi. Bizning guruhla nusxamiz:

js
function guruhla(royxat, kalitOl) {
  return royxat.reduce((natija, element) => {
    const kalit = kalitOl(element);
    natija[kalit] ??= [];
    natija[kalit].push(element);
    return natija;
  }, {});
}

const teglar = ["mazali", "constructor", "arzon"];
console.log(guruhla(teglar, (t) => t));
text
TypeError: natija[kalit].push is not a function

Tarjimasi: "natija[kalit].push — funksiya emas". Nega? Bo'sh {} da ham constructor xususiyati bor — u meros bilan keladi va qiymati funksiya. Shuning uchun natija["constructor"] undefined emas edi. ??= "qiymat bor" deb bo'sh massiv yaratmadi. Keyin massiv o'rniga funksiyaga push qilmoqchi bo'ldik. Bitta teg butun dasturni to'xtatdi. Hujumchi bunday "maxsus" kalitlarni ataylab yuboradi — bu xavfning jiddiyrog'i Prototype pollution darsida.

5.2 null prototip

Object.groupBy bu muammodan himoyalangan:

js
const teglar = ["mazali", "constructor", "arzon"];
const guruhlar = Object.groupBy(teglar, (t) => t);

console.log(guruhlar.constructor); // [ 'constructor' ]
console.log("toString" in guruhlar); // false

U natijani meros xususiyatlarisiz obyekt sifatida yaratadi. Node buni [Object: null prototype] deb ko'rsatadi. Prototip (prototype) — obyekt meros xususiyatlarini oladigan "ota" obyekt; uni Prototip va prototip zanjiri darsida o'rganamiz, hozir bilish shart emas. null prototip — "otasi yo'q": obyektda faqat siz qo'ygan kalitlar bor. Shuning uchun constructor ham, toString ham — oddiy guruh nomlari.

Chrome konsoli [Object: null prototype] yozuvini chiqarmaydi. Obyektni ochib ko'rsangiz, oddiy obyektlarda doim bo'ladigan [[Prototype]] qatori yo'qligini ko'rasiz. Ma'nosi bir xil.

5.3 Nimalar ishlaydi, nimalar — yo'q

Demak groupBy natijasi bilan ishlashda faqat bitta narsani yodda tuting: unga oddiy obyektning meros metodlarini chaqirmang. Kalitlar bilan ishlashning odatiy vositalari esa to'liq ishlaydi: Object.keys, Object.entries, Object.hasOwn, for...in, nuqta va kvadrat qavs. Ishlamaydigani — meros metodlari. Eng ko'p uchraydigan holat — natijani satr ichiga qo'yish:

js
const guruhlar = Object.groupBy(["osh"], (t) => t);
console.log(`Natija: ${guruhlar}`);
text
TypeError: Cannot convert object to primitive value

Tarjimasi: "obyektni primitiv qiymatga aylantirib bo'lmaydi". Oddiy obyekt satr ichida [object Object] bo'lardi — buni meros toString qiladi. Bu obyektda toString yo'q, shuning uchun JavaScript uni satrga aylantira olmadi. Natijani ko'rish uchun console.log(guruhlar) yoki Object.keys(guruhlar) ishlating.

Tekshirib ko'ring: Object.hasOwn(guruhlar, "taom") null-prototip obyektda ishlaydimi? guruhlar.hasOwnProperty("taom")-chi?

Javob

Birinchisi ishlaydi: Object.hasOwn — Object ning funksiyasi, u obyektga tashqaridan qaraydi. Ikkinchisi — TypeError: hasOwnProperty meros metod, bu obyektda esa meros yo'q. Obyekt darsida Object.hasOwn ni zamonaviy usul deb tavsiya qilganimizning yana bir sababi shu.

6. Bo'sh guruh yaratilmaydi

Callback hech qachon qaytarmagan kalit natijada yo'q. Menyuda shirinlik bo'lmasa, shirinlik guruhi ham yo'q — bo'sh massiv emas, undefined:

js
const menyu = [{ nom: "Osh", tur: "taom" }];
const turlar = Object.groupBy(menyu, (t) => t.tur);

console.log(turlar.shirinlik.length);
text
TypeError: Cannot read properties of undefined (reading 'length')

Tarjimasi: "undefined ning xususiyatlarini o'qib bo'lmaydi (length ni o'qishda)". turlar.shirinlik — undefined, undan length so'radik. Bu xato kodda emas — ma'lumotda. Kecha menyuda shirinlik bor edi, kod ishlardi; bugun yo'q — ertalab dastur to'xtaydi.

Kutilgan har guruhni xavfsiz o'qing (?? va ?.):

js
const menyu = [{ nom: "Osh", tur: "taom" }];
const turlar = Object.groupBy(menyu, (t) => t.tur);

console.log(turlar.shirinlik?.length ?? 0); // 0
console.log(turlar.shirinlik ?? []); // []

?.length — guruh yo'q bo'lsa undefined qaytaradi, xato tashlamaydi. ?? 0 uni nolga aylantiradi. Ikkinchi qatorda — guruh o'rniga bo'sh massiv. Qoida: groupBy natijasidagi guruhni o'qishdan oldin "bu guruh har doim bormi?" deb so'rang. Javob "yo'q" bo'lsa — ?. va ??.

Nega standart bo'sh guruhlarni o'zi yaratmaydi? Chunki u qanday guruhlar bo'lishi mumkinligini bilmaydi. Callback istalgan qiymat qaytarishi mumkin: "taom", "ichimlik", ertaga — "shirinlik", indinga — "salat". Barcha mumkin bo'lgan kalitlarni faqat siz bilasiz. Shuning uchun groupBy faqat haqiqatan uchragan kalitlarni yozadi, kutilganlarini esa o'qishda o'zingiz himoyalaysiz.

Tekshirib ko'ring: Menyuda faqat ichimliklar qoldi. Object.keys(Object.groupBy(menyu, (t) => t.tur)) nima qaytaradi?

Javob

[ 'ichimlik' ] — bitta kalit. taom guruhi yaratilmadi, chunki birorta element "taom" qaytarmadi. Bo'sh menyu uchun esa natija — umuman kalitsiz obyekt, Object.keys — [].

7. Guruhlardan statistika

Guruhlash ko'pincha oxirgi qadam emas. Undan keyin har guruh uchun son yoki yig'indi kerak bo'ladi. Jasur akaning kunlik hisoboti: qaysi taom necha marta sotildi va qancha tushum berdi.

js
const buyurtmalar = [
  { taom: "osh", summa: 35000 },
  { taom: "choy", summa: 5000 },
  { taom: "osh", summa: 70000 },
  { taom: "manti", summa: 30000 },
  { taom: "choy", summa: 10000 },
];

const taomlar = Object.groupBy(buyurtmalar, (b) => b.taom);

for (const [taom, royxat] of Object.entries(taomlar)) {
  const tushum = royxat.reduce((jami, b) => jami + b.summa, 0);
  console.log(`${taom}: ${royxat.length} ta, ${tushum} so'm`);
}

Konsolda:

text
osh: 2 ta, 105000 so'm
choy: 2 ta, 15000 so'm
manti: 1 ta, 30000 so'm

Ikki bosqich: groupBy — "kim qaysi guruhda", keyin har guruh ustida length (sanoq) va reduce (yig'indi). Obyekt bo'ylab yurish darsidagi Object.entries va Massiv destructuring guruh nomini va ro'yxatini bitta qatorda ajratdi.

Natija ekranga emas, yangi obyektga kerak bo'lsa — Object.fromEntries:

js
const taomlar = {
  osh: [{ summa: 35000 }, { summa: 70000 }],
  choy: [{ summa: 5000 }],
};

const tushumlar = Object.fromEntries(
  Object.entries(taomlar).map(([taom, royxat]) => [
    taom,
    royxat.reduce((jami, b) => jami + b.summa, 0),
  ]),
);

console.log(tushumlar); // { osh: 105000, choy: 5000 }

entries → map → fromEntries — filter darsida obyektni filtrlagan qolipning o'zi, faqat bu safar map bilan. Bunday ketma-ket bosqichlarni Metod zanjiri darsida "pipeline" deb ataymiz.

Guruhlashni bir nechta filter bilan ham qilsa bo'ladi: buyurtmalar.filter((b) => b.taom === "osh"), keyin "choy" uchun, keyin "manti" uchun. Lekin bunda ikki muammo bor. Birinchisi — taomlar ro'yxatini oldindan bilish kerak; yangi taom qo'shilsa, kod uni ko'rmaydi. Ikkinchisi — har filter butun ro'yxatni boshidan aylanadi: o'nta taom — o'nta to'liq o'tish. groupBy esa ro'yxatni bir marta aylanadi va guruhlarni ma'lumotning o'zidan topadi. Bitta guruh kerak — filter; hammasi kerak — groupBy.

Faqat son kerak bo'lsa, guruhlash shart emas: reduce darsidagi sanoq (natija[taom] = (natija[taom] ?? 0) + 1) massivlar yaratmaydi va xotirani tejaydi. groupBy guruhning o'zi — elementlari — ham kerak bo'lganda yaxshi.

Tekshirib ko'ring: Yuqoridagi hisobotda "choy" ikkinchi qatorda chiqdi, garchi alifboda "manti" dan keyin bo'lsa ham. Nega?

Javob

Guruhlar birinchi uchrash tartibida: buyurtmalarda osh birinchi, choy ikkinchi, manti to'rtinchi o'rinda paydo bo'ldi. groupBy saralamaydi. Alifbo tartibi kerak bo'lsa — Object.entries(taomlar).toSorted(...) (Nusxa bilan o'zgartirish).

8. Map.groupBy — kalit satr bo'lmasa

Object.groupBy ning egizagi — Map.groupBy. U natijani oddiy obyekt emas, Map sifatida qaytaradi. Map — kalit-qiymat juftliklarini saqlaydigan maxsus to'plam; uni Map darsida to'liq o'rganamiz, hozir bilish shart emas. Bu yerda uning bitta xususiyati muhim: kalit satrga aylantirilmaydi.

js
const menyu = [
  { nom: "Osh", narx: 35000 },
  { nom: "Ko'k choy", narx: 5000 },
  { nom: "Lag'mon", narx: 28000 },
];

const guruhlar = Map.groupBy(menyu, (t) => t.narx > 20000);

const qimmatlar = guruhlar.get(true);
console.log(qimmatlar.map((t) => t.nom)); // [ 'Osh', "Lag'mon" ]
console.log(guruhlar.get(false).length); // 1
console.log(guruhlar.size); // 2

Kalitlar — haqiqiy true va false, "true" satri emas. Guruhni olish — get(kalit) metodi bilan, guruhlar soni — size xususiyati. Node Map ni Map(2) { true => [...], false => [...] } ko'rinishida chiqaradi.

Qachon Map.groupBy kerak?

  • Kalit — obyekt. Masalan, taksi safarlarini haydovchi obyekti bo'yicha guruhlash:
js
const shuhrat = { ism: "Shuhrat" };
const olim = { ism: "Olim" };
const safarlar = [
  { raqam: 1, haydovchi: shuhrat },
  { raqam: 2, haydovchi: olim },
  { raqam: 3, haydovchi: shuhrat },
];

const obyektda = Object.groupBy(safarlar, (s) => s.haydovchi);
console.log(Object.keys(obyektda)); // [ '[object Object]' ]

const mapda = Map.groupBy(safarlar, (s) => s.haydovchi);
console.log(mapda.size); // 2
console.log(mapda.get(shuhrat).map((s) => s.raqam)); // [ 1, 3 ]

Object.groupBy da ikkala haydovchi ham "[object Object]" satriga aylandi va uchala safar bitta guruhga tushdi — xato xabarisiz. Map.groupBy esa obyektlarning o'zini kalit qildi: ikki haydovchi — ikki guruh.

  • Kalit — son yoki boolean, va uni satrga aylantirmaslik muhim (get(1) va get("1") — Map da ikki xil kalit).

Boshqa hamma narsada ikkalasi bir xil: callback o'sha (element, index), guruh ichidagi va guruhlar orasidagi tartib ham o'sha, bo'sh guruh ham yaratilmaydi. Qolgan hollarda — kalit oddiy so'z bo'lsa — Object.groupBy qulayroq: nuqta bilan o'qiladi, Object.entries bilan aylanadi.

9. Qayerda ishlaydi

Object.groupBy va Map.groupBy — ES2024. Brauzerlarda: Chrome va Edge 117, Firefox 119, Safari 17.4 dan; Node'da — 21-versiyadan. Baseline ma'lumotiga ko'ra, ular 2026-yil 5-sentabrdan Baseline widely available — atigi bir oy oldin.

Amaliy ma'nosi: iOS 17.4 dan eski iPhone'larda Object.groupBy is not a function xatosi chiqadi. Saytingiz foydalanuvchilari orasida eski telefonlar ko'p bo'lsa, «groupBy ichidan» bo'limidagi reduce varianti — har joyda ishlaydigan zaxira. Faqat boshlang'ich qiymatni Object.create(null) qilib, null-prototipni o'zingiz yarating — unda «constructor» muammosi ham yo'qoladi. Object.create ni Object.create va prototipal meros darsida o'rganamiz. Serverda (Node 22, 24) bu muammo yo'q.

10. Ko'p uchraydigan xatolar

10.1 Yo'q guruhni o'qish

«Bo'sh guruh yaratilmaydi» bo'limi: turlar.shirinlik.length — TypeError: Cannot read properties of undefined (reading 'length'). Tuzatish: turlar.shirinlik?.length ?? 0.

10.2 groupBy ni massiv metodi deb chaqirish

js
const menyu = [{ nom: "Osh", tur: "taom" }];
console.log(menyu.groupBy((t) => t.tur));
text
TypeError: menyu.groupBy is not a function

Tarjimasi: "menyu.groupBy — funksiya emas". Massivda bunday metod yo'q — menyu.groupBy undefined, uni chaqirib bo'lmaydi. Tuzatish: Object.groupBy(menyu, (t) => t.tur).

10.3 Boolean kalitni satr deb unutish

Object.groupBy(menyu, (t) => t.narx > 20000) — kalitlar "true" va "false" satrlari. guruhlar[true] ham ishlaydi (u ham satrga aylanadi), lekin kod sirli bo'ladi. Tuzatish: ma'noli satr qaytaring ("qimmat") yoki Map.groupBy ishlating.

10.4 Natijani oddiy obyekt deb ishlatish

${guruhlar} — TypeError: Cannot convert object to primitive value; guruhlar.hasOwnProperty(...) — TypeError. Tuzatish: Object.keys, Object.entries, Object.hasOwn.

11. Mashqlar

1-mashq (oson): O'tdi yoki o'tmadi

Imtihonda 60 va undan yuqori ball — "o'tdi". Talabalarni ikki guruhga ajrating va har guruhdagi ismlarni chiqaring.

js
const talabalar = [
  { ism: "Ali", ball: 78 },
  { ism: "Malika", ball: 54 },
  { ism: "Jasur", ball: 91 },
  { ism: "Nodira", ball: 60 },
];
Yechim
js
const talabalar = [
  { ism: "Ali", ball: 78 },
  { ism: "Malika", ball: 54 },
  { ism: "Jasur", ball: 91 },
  { ism: "Nodira", ball: 60 },
];

const natija = Object.groupBy(talabalar, (t) =>
  t.ball >= 60 ? "otdi" : "otmadi",
);

const ismlar = (royxat) => royxat.map((t) => t.ism);
console.log(ismlar(natija.otdi)); // [ 'Ali', 'Jasur', 'Nodira' ]
console.log(ismlar(natija.otmadi)); // [ 'Malika' ]

Kalitlar apostrofsiz — otdi, otmadi — shunda nuqta bilan o'qish mumkin. >= — Nodiraning 60 bali ham o'tadi. Agar hamma o'tganida, natija.otmadi — undefined bo'lardi; real kodda ?? [] qo'shing.

2-mashq (o'rta): Taksi haydovchilarining kunlik daromadi

Taksi ilovasining bir kunlik safarlari berilgan. Har haydovchi uchun safarlar soni va jami daromadni chiqaring: Shuhrat: 2 ta safar, 63000 so'm.

Ishora: «Guruhlardan statistika» bo'limidagi for...of + Object.entries + reduce.

js
const safarlar = [
  { haydovchi: "Shuhrat", narx: 25000 },
  { haydovchi: "Olim", narx: 18000 },
  { haydovchi: "Shuhrat", narx: 38000 },
  { haydovchi: "Olim", narx: 12000 },
  { haydovchi: "Zafar", narx: 40000 },
];
Yechim
js
const safarlar = [
  { haydovchi: "Shuhrat", narx: 25000 },
  { haydovchi: "Olim", narx: 18000 },
  { haydovchi: "Shuhrat", narx: 38000 },
  { haydovchi: "Olim", narx: 12000 },
  { haydovchi: "Zafar", narx: 40000 },
];

const haydovchilar = Object.groupBy(safarlar, (s) => s.haydovchi);

for (const [ism, royxat] of Object.entries(haydovchilar)) {
  const daromad = royxat.reduce((jami, s) => jami + s.narx, 0);
  console.log(`${ism}: ${royxat.length} ta safar, ${daromad} so'm`);
}

Konsolda:

text
Shuhrat: 2 ta safar, 63000 so'm
Olim: 2 ta safar, 30000 so'm
Zafar: 1 ta safar, 40000 so'm

Kalit — haydovchi ismi, ya'ni ma'lumotning o'zidan. Haydovchilar ro'yxatini oldindan bilish shart emas: yangi haydovchi kelsa, yangi guruh o'zi ochiladi. Ismlar takrorlanishi mumkin bo'lgan real ilovada kalit sifatida id olinadi.

3-mashq (qiyin): Eng ko'p tushum bergan stol

Kun davomidagi buyurtmalar stol raqami bilan saqlangan. Har stolning jami summasini hisoblang va eng ko'p tushum bergan stolni toping. Natijani { stol, summa } obyektlari massivi sifatida, summa kamayishi tartibida chiqaring.

Ishora: groupBy → Object.entries → map (har stol uchun { stol, summa }) → toSorted (Nusxa bilan o'zgartirish). Kalit satr bo'lib qoladi — stol raqamini Number bilan qaytaring.

js
const buyurtmalar = [
  { stol: 3, summa: 75000 },
  { stol: 1, summa: 40000 },
  { stol: 3, summa: 33000 },
  { stol: 7, summa: 120000 },
  { stol: 1, summa: 28000 },
];
Yechim
js
const buyurtmalar = [
  { stol: 3, summa: 75000 },
  { stol: 1, summa: 40000 },
  { stol: 3, summa: 33000 },
  { stol: 7, summa: 120000 },
  { stol: 1, summa: 28000 },
];

const stollar = Object.groupBy(buyurtmalar, (b) => b.stol);

const hisobot = Object.entries(stollar)
  .map(([stol, royxat]) => ({
    stol: Number(stol),
    summa: royxat.reduce((jami, b) => jami + b.summa, 0),
  }))
  .toSorted((a, b) => b.summa - a.summa);

console.log(hisobot);
console.log(`Eng yaxshi: ${hisobot[0].stol}-stol`);

Konsolda:

text
[
  { stol: 7, summa: 120000 },
  { stol: 3, summa: 108000 },
  { stol: 1, summa: 68000 }
]
Eng yaxshi: 7-stol

Kalit 3 raqami "3" satriga aylangan edi — Number(stol) uni songa qaytardi. Bu yerda Map.groupBy ham mos kelardi (kalit son bo'lib qolardi), lekin Map hali o'tilmagan. Zanjirdagi har qadam yangi massiv qaytardi, shuning uchun toSorted o'rniga sort ham xavfsiz edi — lekin toSorted niyatni aniqroq aytadi.

4-mashq: Vazifalar qadami — "nechta bajarildi"

Hozir hisob faqat faollarni ko'rsatadi: "2 ta vazifa qoldi". Talab — ikkala son: "2 ta vazifa qoldi, 1 ta bajarildi".

Yangi branch: git switch -c feature/statistika. reduce darsida yozilgan faollarSoni o'rniga statistika() funksiyasini yozing. U Object.groupBy bilan vazifalarni "faol" va "bajarilgan" guruhlariga ajratsin va { qoldi, bajarildi } sonlarini qaytarsin — ekrandagi so'zlar bilan bir xil. Guruh nomlari esa — filtr tugmalaridagi data-filtr qiymatlari bilan bir xil.

Ishora: bo'sh ro'yxat yoki hamma vazifa bajarilgan holatini unutmang — «Bo'sh guruh yaratilmaydi».

Yechim

"3. Hisoblovchi funksiyalar" da faollarSoni o'rniga:

js
function guruhNomi(vazifa) {
  return vazifa.bajarildi ? "bajarilgan" : "faol";
}

function statistika() {
  const guruhlar = Object.groupBy(holat.vazifalar, guruhNomi);
  return {
    qoldi: guruhlar.faol?.length ?? 0,
    bajarildi: guruhlar.bajarilgan?.length ?? 0,
  };
}

render() oxiridagi hisob qatori:

js
const { qoldi, bajarildi } = statistika();
matnniYoz(
  hisob,
  `${qoldi} ta vazifa qoldi, ${bajarildi} ta bajarildi`,
);

Ikki xil nomga e'tibor bering. Guruh kalitlari — faol va bajarilgan (filtr nomlari kabi). Natija maydonlari — qoldi va bajarildi (ekrandagi so'zlar kabi). statistika() — hisob uchun yagona manba: keyingi darslarda "nechta qoldi?" savoli ham shu funksiyadan javob oladi.

?.length ?? 0 — eng muhim joy. Ochilishda ikkala guruh bor. Lekin hamma vazifani belgilasangiz, faol guruhi yo'qoladi. Hammasini o'chirsangiz — ikkalasi ham. ?. siz birinchi holatda dastur to'xtardi.

Ikki chegaraviy holatni Node'da sinang — eski faollarSoni bilan solishtirib:

js
function guruhNomi(vazifa) {
  return vazifa.bajarildi ? "bajarilgan" : "faol";
}

function statistika(vazifalar) {
  const guruhlar = Object.groupBy(vazifalar, guruhNomi);
  return {
    qoldi: guruhlar.faol?.length ?? 0,
    bajarildi: guruhlar.bajarilgan?.length ?? 0,
  };
}

function faollarSoni(vazifalar) {
  return vazifalar.reduce((s, v) => (v.bajarildi ? s : s + 1), 0);
}

const holatlar = [
  [],
  [{ bajarildi: true }, { bajarildi: true }],
  [{ bajarildi: false }, { bajarildi: true }, { bajarildi: false }],
];

for (const royxat of holatlar) {
  const { qoldi, bajarildi } = statistika(royxat);
  console.log(faollarSoni(royxat), qoldi, bajarildi);
}

Konsolda:

text
0 0 0
0 0 2
2 2 1

Birinchi ikki ustun har qatorda teng — faollar soni avvalgidek. Uchinchisi — yangi son. Node'da statistika vazifalar ni parametr sifatida oldi, brauzerdagi esa holat.vazifalar ni o'qiydi — mantiq bir xil.

Brauzerda tekshiring:

  • Ochilganda: "2 ta vazifa qoldi, 1 ta bajarildi".
  • "Non olish" ni belgilang — "1 ta vazifa qoldi, 2 ta bajarildi". Qolgan faolni ham — "0 ta vazifa qoldi, 3 ta bajarildi", xato yo'q.
  • Hammasini o'chiring — "0 ta vazifa qoldi, 0 ta bajarildi" va "Hozircha vazifa yo'q…".
  • Hisob aria-live="polite" hududida: NVDA bilan ishlaydigan Malika har o'zgarishdan keyin ikkala sonni eshitadi.

Commit va main ga qo'shish:

bash
git add assets/js/vazifalar.js
git commit -m "Vazifalar: hisobda bajarilganlar soni (Object.groupBy)"
git switch main
git merge feature/statistika
git branch -d feature/statistika
git push

12. Real ishda

  • Hisobotlar. "Kategoriya bo'yicha savdo", "oy bo'yicha xarajatlar", "viloyat bo'yicha mijozlar" — har biri groupBy + yig'indi. Admin paneldagi diagrammalarning ma'lumoti ko'pincha shunday tayyorlanadi.
  • Ro'yxatni bo'limlarga bo'lish. Telegram'dagi chatlar "Bugun", "Kecha", "O'tgan hafta" bo'yicha; kontaktlar bosh harf bo'yicha; do'kon mahsulotlari kategoriya bo'yicha — UI'da bo'limlar groupBy natijasidan chiziladi.
  • Server. Bazadan kelgan qatorlarni guruhlash ko'pincha bazaning o'zida (GROUP BY — SQL qismida) qilinadi. Kichik ro'yxatlarni esa JavaScript'da groupBy bilan guruhlash qulay.
  • Kutubxonalar. Lodash kutubxonasidagi _.groupBy — eng mashhur funksiyalaridan biri edi. Endi uning o'rnini standart Object.groupBy egallamoqda — yangi loyihalarda kutubxona shart emas.
  • Intervyu. "Object.groupBy nima qaytaradi?", "reduce bilan guruhlashni yozing", "Map.groupBy qachon kerak?" — bugun uchala savolga javob oldingiz.

Xulosa

  • Object.groupBy(royxat, f) — f qaytargan kalit bo'yicha elementlarni guruhlarga yig'adi; guruh ichida va guruhlar orasida birinchi uchrash tartibi saqlanadi.
  • Callback (element, index) oladi; kalit satrga aylantiriladi — boolean o'rniga ma'noli so'z qaytaring.
  • Natija — null-prototip obyekt: constructor kabi kalitlar xavfsiz, lekin toString, hasOwnProperty yo'q.
  • Bo'sh guruh yaratilmaydi: guruhlar.x?.length ?? 0.
  • Statistika: groupBy → Object.entries → length/reduce. Faqat son kerak bo'lsa — reduce sanoq'i yetarli.
  • Map.groupBy — kalit obyekt, son yoki boolean bo'lib qolishi kerak bo'lganda.

Keyingi dars: Metod zanjiri va ma'lumot pipeline — filter, map, toSorted, groupBy va reduce ni bitta o'qiluvchan zanjirga ulaymiz va qachon oddiy sikl tezroq ekanini o'lchaymiz.

Manbalar

  • MDN: "Object.groupBy()", "Map.groupBy()" — developer.mozilla.org
  • TC39: "Array Grouping" taklifi (ES2024) — github.com/tc39/proposal-array-grouping
  • web-features: "Array grouping" — Baseline widely available, 2026-09-05
Ulashish:Telegram'da

Izohlar (0)

Izoh yozish uchun kiring.

  • Hozircha izoh yo'q. Birinchi bo'ling!
JavaScript Object.groupBy va Map.groupBy: massivni guruhlarga ajratish — IlmHamroh