Wednesday, August 3, 2016

Таны амьдралыг үндсээр нь өөрчилсөн тохиолдол юу байсан бэ?

1986 онд, би амьдралд минь хамгийн их нөлөө үзүүлсэн хүнтэй хамт ажилладаг байлаа. Нэрийг нь Билл гэдэг, манай компаний борлуулагч нарын нэг. Гэхдээ зүгээр ч нэг борлуулагч биш №1 борлуулагч нь байсан юм. 190см өндөр, баатар шиг биетэй, гал улаан үс, ногоон нүдтэй, үргэлж албаны хувцасладаг хүн байлаа. Хичнээн ажил ихтэй, оройн цагаар сууж ажиллаж байсан ч, ядаж нэг удаа "зангиагаа сулласан" байхыг нь хараагүй юм.

Хаа ч очсон тэр газрынхаа эзэн нь юм шиг л том том алхлан явж ордог, замд нь байгаа ямар ч хүн зай тавьж өгдөг, ярьсан яриаг нь бүгд анхааралтай сонсдог нэгэн байлаа. Бас тэр захиралтай ч, надтай ч, хамтран ажиллагсадтайгаа ч, тэр ч бүү хэл цайны газрын зөөгчтэй ч яг ижилхэн үргэлж инээмсэглэсэн найрсаг харьцдаг байсан юм. Миний л амьдралдаа харсан хүмүүсээс хамгийн гоё хандлага, харьцаатай хүн байсан юм. Түүний мэндийг мэдэж “сайн байна уу?” гэж асуух болгонд "гайхалтай сайн байна, үүнээс дээр байсангүй." гэж хариулдаг байлаа. Бүр чин сэтгэлээсээ шүү...

Нэг өдөр тэр хүүхдийнхээ талаар маш муу мэдээ аваад хэдэн өдөр ажилдаа ирсэнгүй. Хэд хоногийн дараа, зангиагаа хоолойндоо тултал татаж, нүүр дүүрэн инээмсэглэлтэй, гар цүнхээ барьсаар том том алхлан орж ирэв. Тухайн үед бүгд л түүнтэй найз байхыг хүсдэг байсан ч, ойр ажилладгийн хувьд би өөрийгөө илүү дотно найз нь гэж боддог байлаа. Тийм ч учраас тэр өглөө би түүний өрөөнд ороод хаалгыг хаангаа, "За, сайн уу хө? Яажшуухан байна? Миний хийж чадах зүйл байна уу? Юу ч байсан хамаагүй зүгээр л хэлэхэд болно шүү." гэлээ. Өөдөөс минь шүдээ ярзайлган, "Гайхалтай сайхан байна, үүнээс дээр байсангүй." гээд хоосорсон нэг удаагийн кофены аягаа хогийн сав руу шидэв. Би хэсэг зогсож байгаад, "Яагаад? Яаж та ийм цоглог байж чадаж байна аа? Яаж одоо байгаа чинь өмнөхөөсөө илүү сайхан байдаг юм?" гэлээ.

Нүүрэндэх инээмсэглэл нь тэр дороо арилж, ширээн дээрээ тохойлдон урагш бөхийж надруу хуруугаараа чичилж байгаад... (Тэгэхэд тэр маш их ууртай харагдаж байлаа, би түүнийг уурлаж байхыг нь огт хараагүй байсан болоод тухайн үед эргэж хараад зугтчихмаар л байлаа)

"За чи намайг сонс, сайн сонс. Чи миний талаар болон миний амьдралын талаар үнэндээ юу ч мэдэхгүй. Чи зүгээр л мэддэг гэж л бодож байгаа. Би чамд нэг юм хэлье, чи харин сайн санаж яваарай. Чиний зан, хандлага чинь үнэндээ авах юм алга. Хамт ажиллагсад чинь яаж тэсч байгааг ойлгохгүй байна. Би лав тэгж тэсч чадахгүй. Чиний хандлага бол чиний бүх юм. Чиний амьдралыг чиглүүлж бүтээдэг зүйл чинь юм. Одоо болж буй бүх зүйл муу байна бүтэхгүй байна гэж бодож байна уу? Чамд л гэж хэлэхэд, бүүр ч муу байж болох л байсан. бүүр долоон дор ч байж болно. Чи одоо энд наад хямдхан цамц зангиатайгаа зогсчихоод, хурал дээр олигтой байсангүй гэж гомдоллож байна уу? Чи гараад бохирын нүх ухаж үзчихээд дараа нь өнөөдрийн энэ хурлаа дүгнээд үзвэл юу гэхүү? Эсвэл чи ажлаасаа халагдаад бохирын нүхэнд амьдарч, хогийн савнаас хоол идснийхээ дараа юу гэж харах бол? Чи үнэндээ юу ч мэдэхгүй байна. Би чамд энийг одоо л, ганцхан удаа л хэлчихье, дахиж энэ талаар ярихгүй, тиймээс сайн сонс, залуу минь. Хэн нэгний 'ямархуу байна' гэсэн асуулт нь ГАНЦХАН л хариулттай. Тэр нь 'ҮҮНЭЭС ДЭЭР БАЙСАНГҮЙ'. Энийг үнэн гэж итгээд л амьдрах хэрэгтэй. Яагаад гэвэл, чамд хичнээн онцгүй байсан ч, байдал тэрнээс чинь долоон дор байж болох л байсан. Тиймээс наад зүүднээсээ сэрээд, хандлагаа өөрчил, ЯГ ОДОО!"

Ингэж хэлээд буцаад сандлаа налаад юу ч болоогүй юм чинь инээмсэглэж эхлэв, би ч зог тусан байсан газраа чимээгүй зогсож байлаа. Хамаг бие маань хөлөрч цамц маань норж байсныг санадаг юм. "Дахиад нэг кофе уумаар санагдчихлаа, чи надтай цуг явах уу? Би даая." гэхээр нь, дагаад явлаа. Тэр юу ч болоогүй юм шиг л байлаа. Харин миний хувьд бүх зүйл өөрчлөгдсөн байсан юм.

Би ч тэр ажилдаа түүний хүссэн шиг байж чадахгүй байгаагаа мэдээд, ажлаасаа гараад өөр ажилд орохоор болов. Өмнөх ажилдаа бараг 10 жил ажиллачихсан байсан болохоор их л тэвдэж байсан юмдаг. Шинэ компанид орох нь их том өөрчлөлт байлаа. Намайг тэр ажил руу анх ороход ресепшн нь инээмсэглэн надаас хэр байгааг минь асуув, Би ч нүүр дүүрэн инээмсэглэн "Үүнээс илүү сайхан байсангүй" гэлээ. Хариуд нь "За тэгвэл сайн байна, та үргэлж ийм байж чадвал энд яг тохирох юм байна" гэв.

Ингээд ажил ч эхэлж, би компани доторх хамгийн сэргэлэн, цоглог хүн нь болов. Би наргианы эзэн, онигооны бай нь байлаа. Гэхдээ тухайн үедээ мэдээгүй ч би дампуурлын ирмэг дээр байгаа компанид орцон байлаа. Компаний байдал муудах тусам миний харьцаа улам л анзаарагдаж байлаа. Хэдий тэдний технологийн талаар юу ч мэддэггүй байсан ч, би их хурдан зэрэг ахидаг байсан юм. Хэсэг хүн цомхотгов, дараа нь бүүр ихийг цомхотгов, дахиад л бүүр ихийг... Гэсэн ч би хамгийн шинэ хүн нь байсан мөртлөө л бүх цомхотголоос мултарч чадаж байлаа. Сүүлдээ гайхаад менежерээсээ яагаад би үлдээд байгааг асуув. Менежер маань,

"Юу л даа, хурал дээр хэнийг цомхотгох талаар ярилцах үед чиний нэр үргэлж хамгийн түрүүнд гарч ирдэг байсан юм. Чи шинэ, бас ямар ч туршлагагүй учраас чамайг явуулах нь зөв сонголт нь байсан л даа. Гэхдээ яг санал хураахад бүгд л чамайг үлдээх хүсэлтэй байлаа. ЧИНИЙ ХАРЬЦАА ХАНДЛАГА ЧИНЬ ҮНЭХЭЭР ЭЕРЭГ БАЙСАН БОЛОХООР БҮГД ЧАМАЙГ ҮЛДЭЭХ ХҮСЭЛТЭЙ БАЙСАН. Ерөнхий захирал хүртэл, гутарсан таван эксперттэй байснаас ганц цоглог шинековтой байсан нь дээр гэж байсан. Ер нь хүний хандлага л хамгийн чухал нь байгаа юм л даа. Чи санаа зоволтгүй дээ, намайг явахад хүртэл чи энд байсаар л байна..." гэж билээ.

Байдал дордох тусам би ахиж, 18 сарын дотор хэлтсийнхээ ахлах нь болов. Эцэст нь компани авралгүй болж, хуучин менежер маань надад өөр ажил санал болгож би ч саналыг нь авч ажлаасаа гарав. Энэ бүхний эцэст, би ажилдаа амжилттай байхыг хүсч буй ажилтан бүрийн дагах 3 зүйлсийг бодож олсон юм. Энийгээ орсон компани болгоныхоо ханан дээр наадаг байлаа, олон ч хүн энийг хуулж авч байсан юм. Тэд маань:

  • Удирдлагын чинь хувьд: Чиний хамгийн их анхаарах зүйл бол яг дээд талынхаа удирдах албан тушаалтныг л сайхан харагдуулах
  • Компаний чинь хувьд: Чиний хамгийн их анхаарах зүйл бол компаний олох орлогыг ихэсгэх, №2 нь ашгийг нь сайжруулах
  • Өөрийн чинь хувьд: Чиний хамгийн их анхаарах зүйл бол үргэлж, ямар ч эргэлзээгүйгээр, эерэг хандлагатай байх, өөртөө итгэх, амжилттай харагдах. Энэ л чиний карьер болон амьдралыг өөрчлөнө. Өөр ямар ч зүйл, ямар ч боловсрол, ямар ч туршлага мэдлэг чамд үүнээс илүү хэрэг болохгүй. “Эерэг хандлагатай дундаж ажилтан гуньсан 5 экспертээс илүү”. Ямар ч үнээр хамаагүй үргэлж эерэг хандлагатай байснаар л чи амжилтанд хүрэх болно.

Энэ олон жилийн хугацаанд, дээрх 3 дүрмийг баримталж чадсан газар болгондоо би амжилт гаргаж, чадаагүй газар болгондоо ялагдаж шаналж явжээ. Хандлага бол чиний амжилтыг тодорхойлох №1 хүчил зүйл шүү. Байдал яаж ч муу байсан гэсэн, тэрнээс илүү муу байж болох л байсан гэдгийг санаж үргэлж эерэг хандлагатай зөв харьцаатай байгаарай.

Эх хувилбар:
http://qr.ae/RiJsMU

Friday, July 29, 2016

Програмчлалын гурван ойлголт

Сүүлийн 40 жилд, техник технологи хөгжихийн хэрээр, компьютерийн хүчин чадал арвын 20 зэргээр үржүүлсний дайтай нэмэгдэж, тухайн үедээ тооцооллын мангас гэгдэж байсан 70аад оны фреон хөргүүртэй супер компьютер, одоо бидний алганд багтахаар болчихож. Тэрийг нь бид гар утас болгон ашиглаж, дээр нь Angry Birds (Candy Crash) тоглож байна.

Гэвч энэ 40 жилд, програм хангамжийн салбар тэгтлээ их өөрчлөгдсөнгүй. Бид 60-аад онд ч ашиглаж байсан нөгөө л нэг if нөхцөл, while давталт, утга оноох үйлдлээ одоо ч бичсээр л байна. Хэрэв цаг хугацаагаар аялж 1960 оны үеийн програмистыг одоо үед авчраад laptop бариулан код бич гэвэл, нээх удалгүй бичиж л орхино (яахав шокноосоо гарах гэж хэдэн цаг болох байх л даа). Консепт нь бараг өөрчлөгдөөгүй гэсэн үг.

Харин яг “код бичихтэй” холбоотой гурван янзын өөрчлөлт гарсан байдаг. 40 гаран жилийн өмнө ар араасаа цувран гарч, бидний код бичих аргыг эвдсэн эдгээр өөрлчлөлтүүдийг програмчлалын ойлголт/загвар/парадигм гэж нэрлэж болно.

1968 онд, Structured Programming (SP). Edsger Dijkstra “GoTo илэрхийллээс зайлхий” болон бусад нийтлэлүүдээрээ нэг л зүйлийг санал болгож байв. Тэр нь goto-г ашиглахгүй байх, оронд нь if, while зэргийг ашиглаж програмынхаа урсгалыг хянах юм. (Програмыг тодорхой бүтэцэд оруулж, урсгал нь тэр бүтцийн дагуу явна гэсэн үг)

1966 онд, Object Oriented Programming (OOP). Ole-Johan Dahl, Kristen Nygaard хоёр Algol хэлээр оролдож байгаад, обьект-г “нээн илрүүлж”, улмаар анхны Обьект Хандалтат Хэл болох Simula-67-г зохиосон гэдэг. Хэдий энэ ололт нь олон гүнзгий утга санааг авчирч байгаа ч, үнэндээ кодонд ямар ч шинэ чадвар нэмж өгөөгүй юм. Харин ч эсрэгээрээ функцууд руу шууд заагч ашиглаж хандах боломжийг хааж оронд polymorphism-г санал болгов. (тухайн функцийг шууд дуудаад ажиллуулчихаж болохгүй, ямар class-н instance вэ гэдгээс нь хамаарч тэр функцийнхээ өөр хувилбаруудаас дуудна гэсэн)

1957 онд, Functional Programming (FP). Alonzo Church-н 30-аад онд томъёолсон Lambda Calculus дээр үндэслэж, John McCarthy анхны функиональ хэл болох Lisp-г зохиожээ. FP мөн ялгаагүй олон шинэ утга санаа авчирсан ч бас л нэг хязгаарлалттай. Утга оноох үйлдэлийг ашиглаж болохгүй.

Гурван ойлголт гурван хязгаарлалтыг дагуулна. SP нь goto-г, OOP нь функцуудын заагчийг (function pointer), FP нь утга оноохыг... Гурвуул шинэ юм нэмэхгүй мөртөө ямар нэг юм хасна. Дэг журмыг (сахилга батыг) нь ихэсгэж, боломжийг нь багасгана...

Бидэнд дахиад шинэ ойлголт гарч ирэх болов уу? Өөрөөр хэлбэл одоо хасах юм үлдсэн болов уу? Сүүлийн 40 жилд шинээр ойлголт гарч ирээгүйг бодоход, ер нь л гарч ирэхгүй нь бололтой.

Тэгвэл бид эдгээрийг бүгдийг нь ашиглах ёстой юу? Бүгд тийм хэрэгтэй гэж үү... Шинээр хэлүүд зохиогдохын хэрээр, goto-г халж, функц заагчийн оронд polymorphism-г нэвтрүүлээд эхэллээ. Ингэснээр бид хүссэн хүсээгүй эхний 2ыг аль хэдийн ашиглаад эхэлсэн гэсэн үг.

Үлдэж байгаа нь функциональ програмчлал... Ингэхэд бидэнд нэг ч утга оноохгүйгээр код бичих шаардлага гарах болов уу? Магадгүй... Хэдийн олон-цөмтэй процессор дээр код бичээд эхэлсэнийг харахад тийм л бололтой. Ядаж байхад цөмийнх нь тоо нь туулай шиг үржихийг нь яана... Миний одоо ашиглаж байгаа лаптоп маань 4 цөмтэй, дараагийн авах лаптоп маань бодвол 8 цөмтэй байх боловуу... Тэрний дараагийнх нь 16… Гэх мэтээр явсаар нэг мэдэхэд л бид bus-руу зэрэг зэрэг уралдан хандах 4096 процессор дээр код бичих шаардлагатай болно… Яаж бид үүндээр найдвартай код бичнэ ээ? Хоёрын арван хэдэн зэргээ больё гэхэд зүгээр хоёр thread-г л арай хийж зэрэг ажиллуулж байна шүү дээ...

Эндээс аврах гарц нь функциональ програмчлал юм. Яаж гэж үү? Хариулт нь их энгийн, утга оноох үйлдэл байдаггүй болохоор функциональ проограмчлалд гаж нөлөө (side effect) гэж байдаггүй, гаж нөлөө байхгүй болохоор хоёр өөр thread нэг хувьсагчийг засах тохиолдол (concurrent modification) гарахгүй гэсэн үг. (Ядаж л онолын түвшинд)


Жич: Энэ гурван ойлголт 50 жилийн өмнө гарсан гэхээр гайхалтай биш байна гэж үү? 50 жил шүү дээ...

Friday, July 22, 2016

Амьдрал дээр хэзээ ч хэрэглэхгүй геометр, утга зохиол (шекспир) гэхмэт хичээлүүдийг яагаад үзэх ёстой гэж?

Хэдэн жилийн өмнө би олны танил нэгэн эмэгтэй жүжигчний оролцсон ярилцлагыг үзэж байсан юм. Тэрээр цөмийн эрчим хүчийг буруушааж, 3-Mile Island-д болсон явдалтай холбоотой хэдэн баримт дэлгэж байв. Хэсэг сонсож байгаад сэтгүүлч түүний яриад байгаа баримтууд нь ямар ч үнэний оргүй гэдгийг нь хэлэхэд бүсгүй ихэд дургүйцэж “Үнэн худал нь биш, төрсөн сэтгэгдэл нь чухал шд” гэж байж билээ.

Олон хүн (бараг ихэнх хүмүүс) амьдралд иймэрхүү байдлаар ханддаг. Энэний талаар Alan Cromer “Uncommon Sense” номондоо гайхамшигтайгаар тодорхойлсон байдаг. Хүмүүс ихэвчлэн, тооцоолж бодсон шинжилгээнд биш, төрсөн сэтгэгдэл мэдрэмжиндээ тулгуурлаж шийдвэр гаргадаг. Үнэндээ “сайн шийдвэр гаргах”-тай, “зөн билэгтээ найдах” шиг их зөрчилддөг зүйл гэж үгүй.

Миний бодлоор, хүнийг үр бүтээлтэй амжилттай амьдрахад, Геометр Шекспир 2 л хамгийн чухал хичээлийг заадаг мэт. Хэрэв зөв заавал, эдгээр хичээлүүд нь чамайг яаж бодоход, яаж өгөгдөл цуглуулахад, яаж тэднийгээ шинжлэхэд мөн яаж дүгнэлт хийхэд сургадаг юм. Berkeley Их сургуулийн Проф. Hugh Richmond-н заадаг Шекспир-н хичээл миний хувьд хамгийн соонирхолтой нь байлаа. Хэдий би Berkeley-д Физик-н Ph.D-р сурч байсанч энэ хичээл надад маш чухал байсан учир, би бүх лекцэнд нь сууж, бүх унших даалгаварыг гүйцээсэн юм. Тиймдээ ч би профессор Richmond-г харах бүрдээ түүний энэ хичээлд нь баярлаж явдагаа хэлдэг байлаа. Хүний ертөцийн талаар эсвэл биднийг итгүүлэн, даган биширтэл бичих Шекспирын энэ арга барилын талаар өөр ямар ч хичээл надад ийм гүнзгий ойлголтыг өгөөгүй юм.

Антонийн алдартай яриа-г сана л даа, яаж тэр үгээрээ үймсэн олныг өөрийнхөө араас дагуулж байлаа? Өөр ямар хичээл чамд ийм юм зааж байв? Энэ чиний ирээдүйд хэрэг болох чадвар биш гэж үү? Өөрөөр асууя л даа, энэнээс илүү чухал чадвар бий гэж үү?

Антоний яаж тэгж байгаа нь ч биш, ерөөсөө тэгж чадаж байгаа гэдэг нь л өөрөө гайхамшигтай юм. Энийг тогтоож аваарай, тэгвэл физик эсвэл инженерийн ангиас хэзээ ч олж авахгүй амьдралын тэр л талыг мэдэж авах болно.

Бичиглэлийн хувьд… Шекспир энэ салбарын стандартыг тогтоосон юм… Зүгээр нэг уран цэцэн үгсээр биш, харин чамд юу ойлгох ёстойг чинь шууд л ойлгуулах тийм тодорхой оновчтой үгсээр. Тийм ч учраас яг эх хувилбарыг нь олж унших (жүжгийг нь үзэх) хэрэгтэй юм. Зүгээр шалгалтын асуултанд зориулж товчилсон тойм, сурах бичигүүдийг биш шүү, яг эх хувилбарыг нь...

Амьдралын тухай, хайрын тухай “Much Ado About Nothing”-с юу сурснаа сана даа? Яаж бие бие үздэггүй байсан хоёр хүн өөрчлөгдөж, нэг нэгнээ гүн гүнзгий мэдэрч хайрлан дурлаж байгааг… гэх зэргээр бичээд байвал бичээд л байна… Хэрэв Шекспирийнхээ хичээлд сайн суусан бол чи ч бас ойлгож л байгаа байх. Шекспирийнхтэй харьцуулах хэмжээний бусад алдартай зохиолууд ч бас бий. Жишээ нь би “Дайн ба Энх” гэх мэт Оросын зохиолуудад дуртай. Заримдаа “Moby Dick”-г дахин нээж умбах үе ч байна.

Геомтер биднийг логик сэтгэлгээнд сургадаг, дүгнэлт гаргана гэж яадгийг, үнэн гэж юуг хэлэх, зөв буруу гэдгийг нь яаж шалгах талаар. Бодит амьдрал дээрх зүйлс тэр болгон бидний геометрт хэрэглэдэг шиг энгийн теоремд буух боломжгүй байдаг. Гэхдээ энэ ухаанд суралцснаар бид, зарим зүйлс үнэн байдгийг, зарим таамаг худлаа гэдгийг мөн анхааралтай сэтгэж шинжилсний дүнд үнэн худлын ялгааг олох боломжтойг (байнга биш ч) мэдэж авдаг.

Мэдээж энэ хичээлүүдээс тодорхой хэмжээнд л юм сурна. Энэ хичээлүүд нь логик сэтгэлгээний талаар, утга зохиолын талаар, тууж жүжгийн талаар, хүмүүсийн болон итгэл үнэмшлийн талаар, баримтад суурилсан мэдлэгийн талаар амьдралынхаа туршид суралцахад, амьдралынхаа туршид хөгжиж боловсроход хөшүүргийн л үүрэгтэй юм. Хорь гучин жил ингээд суралцчих, дараа нь чи өөрөө ойлгоод ирнэ. Бас хүрээлэн байгаа ертөнцөөө хянах нөлөөлөх чадвартай болоод ирнэ ...

Дээр хэлсэн эмэгтэй хэрэв геометр үзсэн байсан бол, тэр үнэн худлын талаар ингэж дураараа ярихгүй л байсан байх. Хэрэв тэр шекспирийг судалсан бол, төрсөн сэтгэгдэлдээ бүрэн найдах талаар тэгж хамаагүй ярихгүй л байсан байх.

Нас дээр гарчихсан хойно хорвоо чамд ойлгомжгүй санагдаж байгаа бол, чи өөрөө ямар ч хүч чадалгүй мэт, яг л амьдрал чамаар тохуурхаад байгаа мэт санагдаж байгаа бол, магадгүй чи залуудаа шекспир юм уу геометрийг судлаагүйнх байх. Эсвэл чи тунгааж өөрийн болголгүй зүгээр л нэг чихээрээ оруулаад нөгөө чихээрээ гаргасных байх.

Эх хувилбар:
http://qr.ae/1ZD2bt

Monday, September 29, 2014

Equals, HashCode and HashMap

java.lang.Object class-н equals(), hashCode() методууд болон HashMap-н талаар жаахан ярилцъя.

java.lang.Object нь Java-н бүхий л (бүр байж болох бүх) обьектуудын эх(эцэг ч юм уу) нь бөгөөд, бүгд энэ л
class-с удамшсан байдаг учраас энэ class-т тодорхойлогдсон методууд бүх class-уудад дамжиж очно. Тэдний нэг
нь equals юм. Equals нь 2 instance-н хоорондын тэнцэлийг шалгаад true false буцаадаг функц юм.
Эндээс "Обьектын тэнцэл гэж юу вэ" гэсэн асуулт гарч ирнэ.

Обьектын тэнцэл...

Java-д тэнцлийг тэнцүүгийн тэмдэг(==) болон equals методоор шалгадаг. Тэнцүүгийн тэмдэг нь primitive
төрлүүдийн хооронд тэнцүү эсэхийг шалгах ганц арга. Харин референс төрлүүдийн хувьд энэ операторыг
ашиглавал, 2 референсийг нэг обьект руу зааж буй эсэхийг л шалгана. Тэр 2 референс ижилхэн санах ойн хаяг
зааж байгаа эсэхийг шалгана гэсэн үг. (Хувьсагч гэдгийг 1-8  byte-н урттай, санах ойн нэг хэсэг гээд үзвэл,
референс болон primitive-үүдийн хооронд тэнцүүгийн тэмдэг маань нээх тийм ялгаатай үйлдэл хийхгүй л
байгаа юм.)
Харин цаад обьектын хувьд, тэнцүү эсэхийг шалгах ганц арга нь equals метод юм. Гэхдээ class болгоны "тэнцэх" логик нь өөр өөр учир, class болгон өөрийн гэсэн equals методтой байх (override хийх) хэрэгтэй юм. Default implementation буюу Object дээр байдаг хувилбар нь яг == шиг ажилладаг. Өөрөөр хэлбэл 2 обьект гээд байгаа маань, яг үнэндээ санах ойдоо нэг л обьект бол true үгүй бол false буцаадаг. Мэдээж, "Best code is no code" гэдэг шиг, онцын шаардлагагүй үед энэ метод-г override хийгээд байх хэрэггүй бөгөөд дутуу үүсгэснээс огт үүсгээгүй байх нь илүү дээр. Онцийн шаардлага гэдэг нь, тухайн class-н field-үүд нь ямар нэг утга илэрхийлдэг тэр утгыг нь ашиглаж, 2 өөр газраас үүссэн (санах ойд 2 өөр газар байрлаж байгаа) instance-г харьцуулах, эсвэл list, map, set-д ашиглах бол override хийнэ гэсэн үг. Тэгвэл ямар үед equals-г override хийх шаардлаггүй вэ? - Class-н instance-ууд нь хоорондоо, логик тэнцэтгэл шаардагдахгүй бол. Жишээ нь java.util.Random class-ын instance бүр нь өөр өөр байх ёстой учраас, equals-г implement хийх нь шал утгагүй юм. - Superclass нь хэрэгцээтэй логик-г нь equals дээрээ implement хийгээд өгцөн бол. Жишээ нь, ихэнх Set-н implementation-ууд AbstractSet-н, List-нхэн AbstractList-н equals-г ашигладаг. (Ашигладаг гэдэг нь, зүгээр л юу ч бичилгүй орхичиход л өөрөө ашиглачихна гэсэн үг. Тэрнээс ашиглах гээд ямар нэг юм хийгээд байх хэрэггүй.) - Class нь зөвхөн тодорхой хүрээнд ашиглагдах, тэр хүрээндээ equals нь ашиглагдахгүй гэдэг нь ойлгомжтой үед. Жишээ нь, private эсвэл default access modifier-тай class. - Class нь зөвхөн ганц л instance-тай байх тохиолдолд. Жишээ нь singleton. За тэгвэл equals метод маань ямар байх ёстой гэсэн үг үү? 10 жилийн математик-н хичээл дээр гардаг дүрэмтэй ер нь бол их төстэй. - reflexive, a = a, null биш байх бүх x-н хувьд x.equals(x) үргэлж true байна. - symmetric, if a = b then b = a, null биш байх бүх x, y-н хувьд x.equals(y) нь ямар утга буцаана y.equals(x) ч мөн тийм утга буцаана. - transitive, if a = b, b = c then a = c, null биш байх бүх x, y, z-н хувьд x.equals(y) болон y.equals(z) нь true бол x.equals(z) ч мөн true байна. - consistent, equals-т ашиглагдаж буй 2 instance-д өөрчлөлт ороогүй л бол, equals-н утга ямагт ижил байна. (цаг хугацаанаас хамаарч өөрчлөгдөхгүй) - null биш байх бүх x-н хувьд, x.equals(null) үргэлж false байна. Эдгээр дүрмийг баримтлалгүй "алдаатай" бичигдсэн equals нь програмд тодорхойгүй асуудал үүсгэх ба асуудлын шалтгааныг олох их хугацаа авсан ажил болох болно. Жишээ авч үзвэл, 1. Энгийнээр хэлбэл бүхий instance өөрөө өөртэйгөө тэнцүү байх ёстой. Энийг баримтлаагүй тохиолдолд, List рүү дөнгөж нэмчихээд, нэмсэн reference-ээ ашиглаад contains-р шалгахад л false буцаана гэсэн үг. 2. Жишээ болгож доорх class-г харъя, Энэхүү Class-н хувьд cis.equals(s) нь үнэн байх боловч, s.equals(cis) нь худлаа байна. Алдаа нь юундаа байна гэхээр String обьект-н equals методыг нь өөрчилж чадахгүй мөртлөө, чаддаг юм шиг String-тэй харьцуулсанд байгаа юм. 3. Доорх 2 class-г хараарай, Point-н хувьд асуудалгүй ч, түүнээс удамшсан ColorPoint-д нь асуудал байна. Энэ жишээн дээр, нэг ижил цэг дээр байгаа, 3 өөр point-н талаар гарч байна. p1 нь p2-тойгоо, p2 нь p3-тайгаа тэнцүү боловч, p1 нь p3-тайгаа тэнцэхгүй байна. Гэхдээ java дээр ч иймэрхүү жишээ байдаг байна. (Date болон Timestamp). Энэхүү жишээний шийдэл нь "aggregation over inheritance" гэсэн Object-oriented-design-н зарчим юм. Өөрөөр хэлбэл, Point гэсэн class-с ColorPoint-г удамшуулж үүсгэхийн оронд, ColorPoint дотор Point-н нэг реферэнс-г байршуулах байдлаар шийдэж болох байлаа. HashCode Equals-тай адил мөн л "цаанаасаа ирдэг" метод. Гол санаа нь, instance бүхэн өөрийгөө тодорхойлох ямар нэг sтоон утгатай байх юм. Тэр тоон утга нь, хоорондоо "тэнцүү" 2 instance-н хувьд тэнцүү байх ёстой гэсэн "бичигдээгүй хуультай". Энэ нь equals болон hashCode-н хоорондын уялдаа нь юм. Гэхдээ эсрэгээрээ, ижилхэн hashCode-той 2 instance "тэнцүү" байх албагүй. Өөрөөр хэлбэл symmetric биш. Албагүй гэсэн болохоос, ёсгүй гээгүй болохоор, hashCode, equals 2 маань symmetric ч байж болно, symmetric байвал бүүр л "сайн". (Гэхдээ symmetric байна гэдэг боломжгүй зүйл л дээ.) Тэгэхээр доторх логик нь өөрчлөгдснөөр хоорондын уялдаа нь алдагдах учир equals-г override хийх бүрдээ бас hashCode-оо "янзлах" хэрэгтэй болно. Мөн equals-н consistent гэдэг дүрэм энд ч бас хамааралтай, тухайн instance-т өөрчлөлт ороогүй л бол, түүний hashCode нь үргэлж нэг ижил л байх ёстой. Жишээ болгож String-н hashCode-г харцгаая, Арай хураангуй байдлаар гэвэл, HashMap HashMap нь өөр өөрийн гэсэн давтагдахгүй дугаар бүхий "сагс"-нуудын цуглуулгатай бөгөөд сагс бүр нь LinkedList-д түлхүүр-утгаа хадгалдаг. Шинээр түлхүүр-утга орж ирэх үед, түлхүүрийнх нь hashCode-г аваад тэр дугаартай сагсанд очно, хэрэв сагс хоосон биш бол, түлхүүртэй "тэнцэх" элемент хүртэл явна. Хэрэв тийм элемент олдвол, утгаа дараад бичнэ. Хэрэв олдохгүй бол, листэнд нэмж өгнө. Түлхүүр ашиглан HashMap-с утга авах нь ч мөн адил, эхлээд түлхүүрийн hashCode бүхий дугаартай сагсан дээр очоод, equals методын true буцаах елементийг олтлоо linkedList-р хэсээд олбол утгыг нь буцаагаад, олохгүй бол null буцаана. Яг эндээс hashCode болон equals-н "уялдаа" нь харагдах ёстой юм. Тэнцүү биш обьектууд ижил hashCode-той байхыг давхцал гэх юм бол, хэдий чинээ их давхцалтай байна, сагсан дахь элементүүд төдий чинээ их байна. Сагсан дахь элементүүд их байх тусам "хэсэх" шаардлага ихсэнэ. Тэр чинээгээрээ get болон put методууд удаашрана. Харин давхцал үгүй бол, HashMap нь O(1) гэсэн хурдаар олно. References http://mdasgin.blogspot.com/2011/12/equals-metodu-nasl-yazlmal.html http://stackoverflow.com/a/256447/1787373 http://eclipsesource.com/blogs/2012/09/04/the-3-things-you-should-know-about-hashcode/ http://stackoverflow.com/questions/15518418/whats-behind-the-hashcode-method-for-string-in-java http://en.wikipedia.org/wiki/Java_hashCode() https://infinitescript.com/2014/09/hashmap-internal-implementation-analysis-in-java/ http://www.dineshonjava.com/2013/06/how-does-java-hashmap-work-internally.html#.VCoY_fkab4R

Saturday, April 5, 2014

Design Patterns : Singleton

"Gang Of Four" (GOF)-н 1994 онд гаргасан "Design Patterns: Elements of Reusable Object-Oriented Software" номны
23 pattern-н бараг хамгийн энгийн ойлгомжтой нь гэж хэлж болохоор нь энэ Singleton Pattern юм.

Singleton Pattern (SP)-г ямарваа нэг class-с тухайн системд (ажиллаж буй програм дотор) зөвхөн ганц л instance 
байлгах шаардлагатай үед ашигладаг. Өөрөөр хэлбэл, програмыг ажиллаж эхлээд дуусах хүртэл, тухайн class-с нэгээс
олон instance үүсэхгүй гэсэн үг. Системийн онцлогоос шалтгаалж, үнэхээр ганц л байх зайлшгүй шаардлагатай 
тохиолдолуудад, үүнийг ашиглана. Хамгийн өргөн хэрэглэгддэг жишээ бол Logger class юм. Програмаас гарч буй бүхий
л лог зөвхөн нэг л газраар дамжиж нэг л байдлаар хадгалагдана. Мөн өөр нэг хэрэглээ нь, тухайн програмын ажиллаж
буй орчины талаарх мэдээлэл байж болно. Java-н java.lang.Runtime class нь жишээ нь Singleton юм. Мөн ADF-н 
ADFFacesContext, ADFContext нь мөн л ялгаагүй Singleton.

Энэ нь private constructor, static method/variable зэргийг ойлгоход (хэрэглээ талаас нь) их хэрэг болох pattern
байгаа юм.


Бүтэц нь гэвэл их энгийн. Class-н constructor нь заавал private байна. Ингэснээрээ, тухайн class-н instance-г 
гаднаас үүсгэх боломжгүй болох юм. Мөн instance үүсгэх болон үүссэн instance-г нь авах зорилго, public 
хандалттай (хүсвэл өөр байж болно.) static method байна. Энэ method-н үүрэг  нь, тухайн class-с ямар нэг 
instance үүссэн эсэхийг шалгаад үүсээгүй бол үүсгээд түүнийгээ буцаах юм. Яагаад static байх шаардлагатай вэ 
гэхээр, static байж байж, ямар нэг instance-р дамжихгүйгээр(угаасаа байхгүй шд) шууд энэ method-г дуудах 
боломжтой болох юм. Энэ method-н нэрийг ихэвчлэн getInstance() гэж нэрлэдэг. Нэгэнт үүсгэсэн instance-аа нэг 
газар хадгалаад, дараагийн дуудалтан дээрээ буцаах хэрэгтэй учраас, private static variable ашиглана.


Implementations:

1.  Singleton class-н хамгийн энгийн implementation доорх хэлбэртэй байж болно.

    public class Singleton {
        private static Singleton final theInstance = new Singleton();

        public static Singleton getInstance() {
            return theInstance;
        }

        private Singleton() {
            System.out.println("constructor");
        }
    }

    Тухайн class load хийгдэх мөчид, шинэ instance үүсээд theInstance variable-д утгаа өгчихнө. Дараа нь 
    шаардлага гараад getInstance method дуудагдах үед зүгээр л тэр variable-нхаа утгыг буцаачихна. 

2.  Lazy-loading. Class-н шинэ instance үүсгэх процесс нь их хүнд (үнэтэй) бол, мөн class програм ажиллах
    бүрд ашиглагдахгүй байх магадлалтай тохиолдолд, дээрх арга тийм зөв сонголт болохгүй байх. Харин зөвхөн
    шаардлагатай үед л шинэ instance үүсгэдэг доорх арга илүү зөв болох талтай. Хамгийн эхний getInstance
    дуудагдах үед, шинээр instance үүснэ. Тэрний дараагаас зүгээр л variable-нхаа утгыг буцаана. 
    
    public class Singleton {
        private static Singleton final theInstance = null;

        public static Singleton getInstance() {
            if (theInstance == null) {
                theInstance = new Singleton();
            }

            return theInstance;
        }

        private Singleton() {
            System.out.println("constructor");
        }
    }

    Thread-Safe. Гэвч дээрх аргыг олон thread зэрэг хандах орчинд ашиглавал, нэгээс олон instance үүсчих
    боломжтой. Тэгэхээр getInstance() method-г synchronized болгосноор асуудлыг шийдэж болно.
    
        public static synchonized Singleton getInstance() {

    Гэвч, хамгийн эхний удаад л instance үүсгээд, бусад бүх дуудалтанд зүгээр бэлэн утга буцаадаг энэ method-г 
    synchronized болгосноороо, performance талаасаа их асуудалтай болж ирнэ. Зүгээр нэг утга буцаахын төлөө 
    lock/unlock/wait хийгдэнэ гэсэн үг. 

    Double-Checked-Locking. Дээрх асуудлыг шийдэхийн тулд, бид эхлээд variable-нхаа утгыг шалгаад, хэрэв
    instance үүссэн бол шууд буцаадаг, үүсээгүй бол synchronized block руу ордогоор хийе.

    public class Singleton {
        private static Singleton final theInstance = null;

        public static Singleton getInstance() {
            if (theInstance == null) {
                synchronized(Singleton.class) {
                    if (theInstance == null) {
                        theInstance = new Singleton();
                    }
                }
            }

            return theInstance;
        }

        private Singleton() {
            System.out.println("constructor");
        }
    }
    
    Ингэснээрээ, эхнийхээс бусад бүх дуудалт ямар нэг саадгүйгээр хурдан хэрэгжинэ. Харин эхний удаад, эхлээд
    null эсэхийг нь шалгаад, дараа нь synchronized block руу ороод, дахиад null эсэхийг шалгаад, тэгээд
    instance-аа үүсгэнэ. Яагаад яг адилхан шалгалтыг 2 удаа хийгээд байгаад гайхаж магадгүй. Яагаад гэвэл, жишээ
    нь t1 thread эхний шалгуурыг даваад, synchronized block руу орохын өмнөхөн нь t2 thread бас тэр шалгуурыг
    даваад орох ирэх боломжтой. Энэ үед хэрэв дотор талын шалгуур байхгүй байсан бол, 2 thread ээлжээр
    synchronized block руу орж instance-аа үүсгэх байсан юм. Ингээд бидний Singleton худлаа болох байлаа. Давхар
    шалгуур тавьж өгснөөрөө 2дахь thread synchronized block руу орсон ч, өмнө нь орсон thread нь үүсгэсэн байна
    уу, гэдгийг шалгаж байгаа юм. 

    Java-н хувьд Double-Checked-Locking арга нь "эвдэрхий" гэж үзэгддэг байна. Яагаад гэвэл, 
        1.  Thread A нь instance үүсээгүй байна гэж бодоод lock хийгээд instance-аа үүсгэж эхэллээ гэж бодъё
        2.  Java-д ялангуяа, тухайн constructor ажиллаж эхлэх үед л, санах ойд авсан (allocate) хаягаа шууд 
            хувьсагчдаа (theInstance) оноочихдог байна. Thread A-г instance-аа бүрэн үүсгэж дуусаагүй байх үед
            шүү дээ. Өөрөөр хэлбэл, манай static хувьсагч дутуу үүссэн обьектыг зааж байна гэсэн үг.
        3.  Яг энэ үед Thread B getInstance() method руу орж ирээд хувьсагчийнхаа утгыг хоосон байна уу гэж 
            шалгана. Мэдээж тэнд дутуу ч гэсэн нэг хаяг оноочихсон байгаа учир, түүнийгээ аваад буцна. Тэгээд 
            "дутуу үүссэн" instance-г ашиглах гэж оролдоод алдаа заана.

    Үүнээс үүдэн Java 5.0-н memory model-д өөрчлөлт орж, volatile түлхүүр үгийн ажиллах зарчим ч шинэчлэгдэж. 
    Volatile-г нэмж ашиглан дээрх аргыг алдаагүй болгосон байна.

3.  On-Demand-Holder. Нэгэнт double-checked-locking нь эвдэрхий гэж үзэгдсэн учир, мөн synchronized,
    volatile гэх мэт их "үнэтэй" аргуудыг ашиглаж буй учир, илүү хөнгөн аргыг бодож олцгоожээ. Энэ нь бидний
    эхний аргыг inner class ашиглан хийх юм. 
    
    public class Singleton {
        private static class SingletonHolder {
            private static final Singleton theInstance = new Singleton();
        }

        public static Singleton getInstance() {
            return SingletonHolder.theInstance;
        }

        private Singleton() {
            System.out.println("constructor");
        }
    }

    Singleton class нь load хийгдэж байх үед, SingletonHolder class load хийгдэхгүй. Хэзээ load хийгдэх вэ
    гэхээр, getInstance() method дуудагдах үед л хийгдэнэ. Тэр бол ямар ч Singleton-н instance үүсэхгүй гэсэн
    үг. Энэ нь lazy-loading гэдгийг нь батална. Харин getInstance() method дуудагдахад, JVM SingletonHolder-г
    load хийх хэрэгтэйгээ мэдээд (мэдээж өмнө нь хийгээгүй бол шүү дээ) load хийж эхэлнэ. Java-н ямар нэг 
    class-г load хийх үйлдэл нь өөрөө serial буюу давхар дуудагдах асуудалгүй учир бид SingletonHolder-н load
    хийгдэж, доторх Singleton-н reference-дээ шинэ intance үүсгэжн оноож өгөх процесс нь аюулгүй гэдэгт бүрэн
    итгэж болно. Энэ нь thread-safe гэдгийг нь харуулна. Нэгэнт class load хийгдэж, бүрэн хамгаалалтын доор шинэ
    instance-аа үүсгэсэн болохоор, getInstance() method-н хийх ажил нь ердөө л тэр reference-н утгыг буцаах юм.

4.  Enum. "Effective Java" номонд Joshua Bloch-н танилцуулсан арга. Нэгэнт Java-н enum нь lazy байдлаар,
    classload хийгдэх үед ажилладаг болохоор, мөн тухайн enum доторх тооны л instance-ууд үүсдэг учир, ганцхан
    элементтэй enum үүсгэх нь Singleton-г хэрэгжүүлэх сонгодог арга болж байгаа юм.

    public enum Singleton {
    INSTANCE;
        public void execute (String arg) {
                // perform operation here 
        }
    }

    Энд ямар ч getInstance гэх мэт static method, variable хэрэггүй, дуудах, ашиглахад ч амар.


Singleton pattern-ы талаар ерөнхийд нь гэвэл нэг иймэрхүү, мэдээж үлдээсэн, дутуу мэдээлэл зөндөө байгаа, мөн
ухаад л байвал ухаад л байхаар юм билээ.

Бусад pattern-ы адилаар энэ pattern-г ашиглах газар гэж бий, ашиглахгүй газар гэж бий. Буруу газраа ашиглах нь 
огт ашиглаагүйгээсээ илүүтэй хортой байж болох. Доорх линкүүдэд жишээ нь өгүүлсэн байгаа. 

References:
http://serkank.wordpress.com/2009/04/16/singleton-pattern-uzerine/
http://tech.puredanger.com/2007/07/03/pattern-hate-singleton/
http://tech.puredanger.com/2007/06/15/double-checked-locking/
http://en.wikipedia.org/wiki/Singleton_pattern
http://en.wikipedia.org/wiki/Double-checked_locking

Thursday, October 24, 2013

Listagg and Row_number

1. Шаардлага №1

Listagg
Өмнөх бичлэг дээр байсан table-үүдийг ашиглаад бас өөр нэг шаардлагыг хэрэгжүүлж үзье. Энэ нь сурагчийн жагсаалтыг харуулангаа, түүний авч байсан дүнгүүдийг огнооны дарааллаар нь цувуулан харуулах юм.

Энд listagg хэмээх oracle-н үндсэн функцийг ашигласан байгаа. Энэ функцийн зориулалт нь угаасаа энэ юм. тодорхой талбараар груплээд, тэндээсээ өөр нэг талбарыг жагсаан харуулах. to_char ашигладаг нь ямар учиртай вэ гэвэл listagg нь
nvarchar2 төрлийн хувьсагчийн харуулахдаа дөрвөлжин дүрсүүд болгочихоод байдаг учраас ингэсэн юм.

Ингээд жишээ үр дүнг харцгаая

2. Шаардлага №2

Эндээс үүдээд, сурагчдын жагсаалт дээр хамгийн авсан дүнг нь хичээлийн нэр, огноотой нь харуулъя. (Өөр нэг жишээ гэвэл тухайн барааны анх худалдаж авсан дүн байж болно).

Listagg-г жаахан string manipulation-той холиод доорх query-г бичиж болно

Үр дүн нь


Сул тал
listagg маань varchar төрөл дээр ажилладаг учир, 4000-с дээш урттай текст бүтээх явцдаа алдаа заадаг сул талтай. Мэдээж том жижиг бүхий л бааз дээр ажиллах зүйл хийх шаардлагатай учир өөр арга хайж олъё.

Row_number

Дээрх сул талыг шийдэхийн тулд row_number() ашиглая. энэ функц нь result set-г нэг талбараар бүлэглэхдээ, тэр бүлэг доторх мөрүүдийг нь дугаарлаж өгдөг юм. Жишээ нь

Энэ query-ний үр дүн нь :

Шаардлага №2-ынхоо query-г дахиад бичье, энэ удаа row_number() ашиглаж:

Хамгийн дотор талд, дээр ашигласан query-ээ хийж өгөөд, тэндээсээ зөвхөн 1 гэсэн grade_order-той мөрүүдийг нь аваад тэрийгээ join-уудаар баяжуулаад өгөхөд л үрдүнгээ авч чадаж байна.
Энэний давуу тал нь, 4000 тэмдэгтийн алдаа гарахгүй, нэг сурагч хичнээ мянга дүн авсан ч болно, мөн group by ашиглахгүй, арай цэвэрхэн болсон байгаа. Сул тал нь гэвэл group by ашиглаагүй учраас, aggregate функц-ээ хамтад нь бичиж
болохгүй болж байгаа юм (Жишээ нь өмнө нь Listagg хийхдээ хамтад нь count() хийж болох байсан).

Нэгэнт л 4000 тэмдэгтийн алдаа Шаардлага №1-н query дээр гарах болохоор, мөн хүн тийм урт юмыг (нэг сурагч хэдэн мянган дүн авах боломжтой бол) хараад байх боломжгүй (эсвэл хүсэхгүй) учраас тэр query-ээ дахиад бичье, ингэхдээ, тухайн
сурагчийн эхний 10 дүнг л харуулдаг болгоё. Listagg болон Row_number-г хамтад нь ашиглаж.

10-р мөр дээр заавал case ашиглаад байх шаардлага байхгүй, доор нь WHERE GRADE_ORDER <= 10 гэж бичихэд л хангалттай. Гэвч бид хамтад нь нийт хэдэн дүн авсаныг нь бас харуулъя гэсэн болохоор ингэж хийлээ. Ингээд үр дүн нь :

Wednesday, October 16, 2013

SUM - CASE (Pivot like query)

1. Оршил

Ямар нэг систем хөгжүүлэх явцад (эсвэл жирийн нэг бааз дээр ажиллах явцад) гардаг шаардлагын нэг нь пивот маягийн үр дүнг харуулах query юм. Жишээ нь нийт сурагчдын ямар ямар дүнг хэдэн удаа авсан байна эсвэл барааны тухайн огноо хүртэлх үлдэгдэл болон эцсийн (нийт) үлдэгдэл гэх мэт. Яагаад пивот шиг гэж байна гэхээр, харахад пивот table шиг мөртлөө, багана нь static (өгөгдлөөс хамаарч өөрчлөгддөггүй) болохоор тэр.

Энэний шийдэл нь их энгийн бөгөөд, хувьдаа нэлээд их ашигладаг, мөн хүмүүс ч асуугаад байдаг болохоор нь нийтлэе гэж бодлоо

2. Өгөгдөл

Эхний жишээг шууд харуулах байдлаар шийдлээ үзүүлье. Тэгэхээр бидэнд student, journal, lesson гэсэн 3 table хэрэг болно. Эдгээр нь

3. Шийдэл

Энд хичээлийн нэр шаардлагагүй байсан болохоор, хичээлийн table-г ашигласангүй. Мөн заавал sub-query ашиглаад байх шаардлага ч байхгүй л дээ, гэхдээ яг бодогдсон үр дүн, нэмэлт мэдээлэл зэргийг тусад нь харахад амар байдаг болохоор ингэж ашиглаад сурчихсан юм.

Ингээд жишээ үр дүн нь :