22 юни 2016 г.

Защо никой не признава грешките си?

Нещо, което забелязах наскоро, е, че ми е изключително трудно да призная грешките си пред себе си и пред другите. Понякога е толкова очевидно, че греша, но просто от вътре не искам да си призная вината. Защо?



Отговорът, който намерих, е прост, но плашещ. 

Според мен не искаме да признаем грешките си, защото това има потенциал да обърка представата ни за света, за нас самите и би предизвикало хаос във вярванията ни.

Да, звучи налудничаво, но преди да затворите тази страница - ето ви един пример.

Представете си, че кажете на 80-годишен човек, който цял живот се е хранил нездравословно, "Остави пържените картофи и пробвай салата!". Макар логически този човек напълно да съзнава, че не прави правилния избор, ако той е свикнал до такава степен да се храни по такъв начин и изведнъж му се наложи да забрави сегашните си вярвания и да премине към нови - това би обезсмислило прекалено голяма част от неговия живот.

Това пък би създало нови съмнения у него - "Ако не съм бил прав за това, за колко ли други неща вероятно съм сгрешил? Какво значи това за мен?". Повечето хора не искат да се сблъскат с тази реалност, просто защото тя създава хаос в представите им за света и се изисква прекалено много енергия, за да "наместят" новите вярвания.

По същата причина повечето хора избягват всякакъв вид новости. Всичко ново е и неизвестно, а това значи, че с това може да последват и неприятни чувства. По-лесно е просто да си останем каквито сме си... Превръщаме се в растения! След като сме си осигурили най-важните неща - храна, вода, място за живеене и т.н., нямаме особено желание да поемаме рискове, тъй като нуждите ни са задоволени.

Най-плашещата част е, че мозъкът ни винаги може да намери причина, с която да обясним действията си, колкото и неправилни да са те. Много често съм чувал хора да ми говорят как с радост искат да пробват някакво ново изживяване, но когато действително имат тази възможност, си измислят оправдание, само и само да не им се налага да изразходват енергия ("ще го направя после" познато ли ви е?).

Това са т.нар. "бариери на успеха". Всеки ги има и всъщност до известна степен са здравословни - благодарение на тях развитието ни тече с нормална скорост (противоположен пример са някои деца, които стават известни като малки, психиката им не може да издържи на напрежението и често стават алкохолици или употребяват наркотици). Но идва момент, в който трябва да действаме противоположно на това, което ни казват емоциите ни...

Виждам това поведение в себе си и в почти всички около мен... всеки ден.

Какво е решението? 


Решението е изключително просто, но не и лесно. Понеже обичам всичко да е в 3 стъпки (нека даже всяка стъпка започва с буквата 'П')...

1. Признайте, че сте сбъркали
2. Планирайте как бихте предотвратили повтарянето на същата грешка
3. Приемете и забравете грешката

Никога няма да спрете да правите грешки. Аз правя грешки всеки ден, понякога за неща, които си мисля, че вече съм усвоил. Дори и да имате план за предотвратяване на грешки - пак ще правите грешки. Приемете го!

Освен това, не правете грешката (това не беше умишлено) да се съдите или да се ядосвате когато сгрешите, защото това всъщност увеличава шанса да ПОВТОРИТЕ същата грешка (ако някой е бил на диета и му се е случвало един път да се провали - знаете докъде води това).

Всъщност колкото повече неща опитвате - толкова повече грешки ще правите. Но според мен най-успелите хора са именно тези, които са направили и най-много грешки през живота си. Грешките ни учат най-добре. Аз виждам нещата така - всичко е просто игра на колекциониране на изживявания. Няма някаква магия - колкото повече изживявания имаме, толкова по-добри ставаме като хора, защото знаем как да реагираме адекватно във всяка ситуация (проста математика).

Това е от мен! А сега отивам да направя следващия си провал... :)

24 май 2016 г.

Как да подобрим алгоритмичното си мислене (в 3 стъпки)

Е, признавам си, не съм особено добър в решаването на сложни задачи по програмиране. Започнах преди едва 2 години и истината е, че все още изпитвам затруднения и се чувствам сякаш едва прохождам.

Имайки това предвид - нещата, които ще напиша тук се базират само и единствено на личния ми опит. Не съм master математик с хиляди медали от състезания и има изключително много материал, който тепърва започвам да схващам.

Но въпреки това имам прогрес. Тази година за пръв път се пуснах на олимпиада по програмиране и (по чудо!) дори се класирах за национален кръг (с отбора ми).

Искам да ви споделя как точно стана това.



Credits: http://class-central.com/



В първия семестър на първата година в университета (реално когато започнах да програмирам) се чувствах все едно съм попаднал в джунглата. Не мислех, че ще очакват от нас да "знаем" какво правим, но се оказа противоположното. Напълно липсваше този период, в който те "държат за ръката" и те потупват по рамото ако успееш да изведеш на конзолата "Hello World".

Не казвам, че са ни измъчили докрай, но определено имаше някои неща, които просто не бяха обяснени както трябва и като цяло беше много демотивиращо, защото материалът се трупаше, а реално никой не се интересуваше дали се справяме с материала на този етап или не.

На края на семестъра имахме контролно за освобождаване от изпит по програмиране. Явих се на това контролно и изживяването се оказа като шамар в лицето, защото не можах да реша нито една от задачите. Тогава осъзнах, че ако го карам по този начин, няма да стигна до никъде. Затова седнах и си казах, че активно ще решавам задачи и ще чета теория, свързана с програмирането, само и само да подобря уменията си малко или много (а и да взема успешно изпита си, разбира се), всеки ден.

Имах късмет, че поне от университета ни бяха предоставили изключително много задачи. Проблемът беше, че не знаех какво правя, нямаше кой да ми каже как и защо нещата работят по определен начин. Дори имах трудност да формулирам въпрос, който да напиша в Google (ако някой преподавател чете това - това е причината студентите никога да нямат въпроси). Самите задачи нямаха решения и като цяло ми липсваше насока...

Започнах с елементарни задачи, изискващи използването на условни конструкции, цикли, въвеждане/извеждане на информация на конзолата, масиви. Дори и това ми се стори трудно в началото, но успях да си изградя една скромна основа, с която да продължа напред. Процесът беше доста предсказуем - започвам да решавам задачата, стигам до някъде, опитвам се да намеря решението за известен период от време и търся обяснения в интернет, успешно решавам задачата или просто продължавам със следващата.

Проблемът беше, че често просто не ми идваха никакви идеи за решение на задачите. Макар да мисля, че практиката (т.е. писането на програми) е хиляди пъти по-важно от теорията, няма как да направиш асоциация от рода на "аха, тук трябва да използвам масив!" ако никога не си чувал за подобно нещо. Така че, малко или много - теорията е необходима. Но за жалост не винаги теорията ми беше поднесена в линеен, последователен вид, а често ми се налагаше да я намирам частица по частица и по този начин да оформям решението на пъзела в главата си.

Как реших този проблем? Google, форуми, разпитване, решаване, зацикляне, решаване, зацикляне. В един момент просто информацията се подреди в главата ми и по-лесно започнаха да ми идват идеи за решения. Но в никакъв случай това не се случи бързо.

И така... първото и най-основно нещо, е, разбира се...


1. Решавайте задачи (дори трудните)!


Сигурно до болка сте чували вече, че за да станеш добър в каквото и да било, се изисква много практика. Програмирането, от това, което съм видял, не е никакво изключение. 

Колкото повече задачи решавате, толкова по-добри ще станете в решаването им по-нататък. Няма кратък път, който да поемете и който ще ви направи елитни програмисти за една седмица.

Важно е да започнете с елементарните задачи. Причината е, че това просто повдига самочувствието и дава увереността, която е нужна, за да се опитаме да решим по-сложна задача. Лично при мен - ако започна със сложна задача и забележа, че не мога да я реша - това ме отказва и от лесните задачи. Моят съвет е - почнете с лесните задачи.

Другото ключово нещо е, че НЕ Е важно дали ще успеете да решите конкретната задача. Знанията ви се развиват дори и без да стигнете до крайния отговор. 

Не ползвайте това като оправдание да се откажете бързо, но просто знайте, че дори и да не стигнете до решение на задачата, сте станали по-добри, просто защото сте вложили някаква мисъл, проучили сте потенциални решения в интернет и ако не друго, поне сте развили малко "психическата издръжливост", която се изисква при решаването на сложни задачи (затова мисля, че олимпиадите по програмиране са страхотна идея). Печелите и в двата случая.

Решаването на задачи е страхотно, защото едва когато имаме задача пред себе си, мозъкът ни автоматично започва селективно да се фокусира върху нея и да търси решения. Започват да ви идват въпроси, които пък ви водят до решения. Е, ако не сте решавали много задачи, както казах, няма да ви идват много качествени въпроси или решения, но всичко това се гради с времето.

Един страхотен сайт, който в момента ползвам, за да развия собствените си умения, е HackerRank.


2. Погледнете решенията на другите!


Не е достатъчно само да пишем код, трябва и да свикнем да четем този на другите. Това е нещо, от което аз все още не съм се възползвал достатъчно, но е много добър инструмент. 

Особено за по-сложни задачи, да видиш решението на друг е много полезно, защото ти позволява да проследиш логиката ред по ред и това, от своя страна, води до тези "аха" моменти, в които просто започваме да разбираме дадена идея и можем да я приложим в собствените си програми.

Понякога имам лошия рефлекс да видя някое по-сложно решение, което не разбирам, и да търся по-просто, защото първото е някак си "плашещо". Но това, според мен, е грешен подход. 

Когато видим сложно решение, по-добрият вариант е да го третираме както бихме третирали непозната дума в текст на чужд език. Виждаме непознатото нещо и започваме да го проучваме - това за какво е, как работи и така нататък. 

Отново искам да напомня, че не е важно да разберем всичко и да го разберем на сто процента - достатъчно е да влезем малко по-дълбоко от това, с което сме свикнали и пак ще развием знанията си.


3. По-малко четене на теория (но все пак я четете)!


От моя опит, алгоритмите (и измислянето на алгоритми) не се научават с четене на теория. Разбира се, както казах, трябва да имаме някаква предварителна основа и да имаме понятие за какво става въпрос, но обикновено ни трябва много по-малко информация, отколкото си мислим.

Изключително грешно е мисленето от рода на "ако само прочета достатъчно теория ще съм перфектно подготвен и ще мога да реша всяка задача". Е, не става така, пробвах! Обикновено вместо това прочитате теорията и се заблуждавате, че разбирате материала, но когато седнете да решавате конкретна задача осъзнавате, че едва ли не сте си загубили времето във всичкото това четене, когато сте можели просто да решавате...

Истината е, че не сте загубили времето си когато сте чели теорията. Теорията е полезна. Просто без да сте решавали задачи, мозъкът ни не може да направи връзката с прочетеното. Чак когато се помъчите малко ще започнете да си спомняте прочетеното и да ви идват по-качествени идеи и решения.

Също така, не се опитвайте да научавате алгоритми наизуст. Излишно е. Ако сте имплементирали например алгоритъм за сортиране десетки пъти, няма да ви е нужно да го знаете стъпка по стъпка, защото разбирате принципа му на работа и лесно ще се сетите как да го използвате в конкретен случай.

Може би едно от най-важните неща - разписвайте алгоритмите на хартия - стъпка по стъпка. Много по-трудно е да разберете как работи даден алгоритъм само от код. На хартията може да си драскате, рисувате и е просто много по-лесно да видите визуално какво се случва. Това разбрах именно когато учих за изпита си по СДП - гледах усилено в тетрадката си примерни разписвания на алгоритми и не можех да разбера какво се случва, но седнах да го направя няколко пъти сам и лампичката в главата ми светна - всичко беше ясно.

---

Това са най-важните неща, които научих досега за развиването на алгоритмичното мислене! Както казах, и аз тепърва навлизам в дълбоките води... и на мен ми е трудно. Но смятам, че няма никаква магия в това да подобрим алгоритмичното си мислене, което е ключово за всеки програмист. Никой не се е родил научен и само с практика можем да станем по-добри.

Peace out!

19 март 2016 г.

CODE TIPS #4 - Именуване на идентификатори (aka "Спрете с 'br' и подобни")

Всеки знае, че има значение как именуваме променливите си когато пишем код. Едно просто правило - кодът трябва да е лесно четим и разбираем, защото в реалността големите проекти се разработват от екипи хора и шансът да сме единствените "свидетели" на гениалния си код е минимален. Всеки знае това... нали?

Чудех се как точно да демонстрирам някои лоши практики в именуването на идентификатори (именуване на променливи, класове, функции).  И ето че открих чудесен пример - една програма, която беше пример от учебник, който целеше да ми покаже основите на обектно-ориентираното програмиране. Страшното беше, че дори и в неграмотно състояние виждах, че това е просто бъркотия!


Просто гледайте.

  1. #include <iostream>
  2. using namespace std;
  3. class Student {
  4. public:
  5.     char Ime[11];
  6.     short Ocenki[10];
  7.     float Uspeh;
  8.     void SrUspeh();
  9.     // ...
  10. };
  11. void Student::SrUspeh()
  12. {
  13.     float s = 0;
  14.     for (int i = 0; i<10; i++) s += Ocenki[i];
  15.     Uspeh = s / 10;
  16. }
  17. class newStud {
  18.     // ...
  19. };
  20. void main()
  21. {
  22.     char KodOp; int BrLica = 0; Student Grupa[10]; newStud newGrupa[10];
  23.     do {
  24.         cin >> KodOp;
  25.         // ...
  26.     } while (KodOp != '0');
  27. }

Нека игнорираме:
- използването на именното пространство std
- public нивото на достъп на член-данните
- използването на char масив вместо std::string
- постинкрементацията на брояча във for цикъла
- декларирането на променливи от различен тип на 1 ред
- факта, че main() е от тип void, а не int, както е по конвенция

Нека забравим всичко и това и се съсредоточим само върху имената на идентификаторите.

Бих казал, че единствено името на класа Student си е OK, но това, предполагам, е абсолютна случайност.

1. Не използвайте български имена!


BAD
  1. char Ime[11];
  2. short Ocenki[10];
  3. float Uspeh;

GOOD
  1. char name[11];
  2. short grades[10];
  3. float average;

Ако бяхте от друга страна и не знаехте български език - каква щеше да е реакцията ви когато ви сервират първия вариант? Просто използвайте имена на английски. Английският е единственият универсален език в света на програмирането.

2. Не съкращавайте имената!


BAD
  1. char KodOp; 
  2. void SrUspeh();
  3. int BrLica = 0;
  4. float s = 0;

GOOD
  1. char operationCode; 
  2. void getAverage();
  3. int numberOfStudents = 0;
  4. float sum = 0;

Няма нужда от съкращения в повечето случаи. По-ценно е кодът да е лесно разбираем, отколкото да е кратък. Постоянно виждам в примери променливи с имена по една буква. Понякога това е допустимо - ако искаме да илюстрираме някаква концепция от програмирането или пък да приложим някаква математическа формула, но в повечето случаи няма нужда от това. 

Също така бих казал че изключение могат да направят например променливите за брояч във for цикъл, които имат малък обхват и няма да затруднят съществено четимостта на кода, тъй като съществуват единствено в цикъла.

3. Спазвайте конвенциите и бъдете постоянни!


Първоначално мислех да напиша за това, че обикновено класовете започват с главна буква, но проблемът се свежда до нещо по-общо - просто спазвайте конвенциите на езика, с който програмирате!

За всеки език е различно и задължение на програмиста да се информира за конкретните конвенции.

Още по-важно - бъдете постоянни. Ако ще пишете camelCase имена на променливи - добре, но се опитвайте да използвате този стил за всички променливи.

---

Това май са най-важните правила, които научих до този момент относно именуването... а сега отивам да бомбардирам всеки, който все още декларира променлива с име "br".

17 март 2016 г.

CODE TIPS #3 - Преинкрементирайте брояча във for цикли! (++C)

Когато започнах да пиша код на C++ ми отне известно време да схвана идеята зад for цикъла. Най-странната част беше това i++ накрая. В последствие разбрах, че просто означава "увеличи стойността на променливата i с 1". Супер! Но после открих и ++i , което прави същото, но с една съществена разлика.

Барт инкрементира правилно!


Ако имаме декларирана целочислена променлива i, инициализирана със стойност 0 и следния код:

  1. while (i < 5) {
  2. std::cout << i++ << " ";
  3. }

Изходът ще е: 0 1 2 3 4
Ето какво се случва - извеждаме стойността на i и чак след това стойността й се увеличава с 1.

Но пък от друга страна, ако имаме този код вместо първия:

  1. while (i < 5) {
  2. std::cout << ++i << " ";
  3. }

Изходът ще е: 1 2 3 4 5

Обяснението е просто - първо стойността на i се увеличава с 1 и след това я извеждаме на конзолата.

Но имайки предвид това как работи for цикъла, пробваме и се убеждаваме, че това няма значение, защото обновяването на брояча се случва след всяко завъртане на цикъла:

  1. // Output: 0 1 2 3 4
  2. for (int i = 0; i < 5; i++) {
  3.     std::cout << i << " ";
  4. }
  5. // Output: 0 1 2 3 4
  6. for (int i = 0; i < 5; ++i) {
  7.     std::cout << i << " ";
  8. }

Значи няма разлика, нали? Е, оказва се, че има!

При случая i++ се създава временна променлива, в която се запаметява сегашната стойност на i, стойността на временната променлива се увеличава с 1 и тази стойност се дава за нова стойност на i. При случая ++i директно се обновява стойността на i, без нуждата от първата инструкция. Тази оптимизация може да се приложи автоматично и в двата случая за примитивни типове, но не и когато използваме итератори например.

Разликата е малка и понякога несъществена, но в по-сложни програми преинкрементирането просто е добър навик.


11 март 2016 г.

CODE TIPS #2 - Stop "using namespace std;" (C++)

Много често виждам в програми/примери за C++ използването на именното пространство "std", което представлява стандартната библиотека от функции на C++.

Оказва се, че този код не е част от добрата практика:

BAD
  1. #include <iostream>
  2. using namespace std;    // avoid this

  3. int main() {
  4.     cout << "I like pickles." << endl;
  5.     return 0;
  6. }


Защо? Простото обяснение е, че могат да се появят конфликти когато използваме други библиотеки, които потенциално могат да имат идентични имена като тези на функциите (и не само) в стандартната библиотека.


Например нека имаме 2 библиотеки с имена MyLib и OtherLib.

Да речем в MyLib имаме функцията myFunction(), а в OtherLib функцията otherFunction(). Всичко е OK, спокойно можем да импортираме тези функции и да ги извикваме безпроблемно.

Но един ден се налага да обновим първата библиотека и сега сме с MyLib 2.0, която за нещастие сега също има функция, която се казва otherFunction(). Лошо! Сега, когато извикаме otherFunction() в кода си, не е ясно кой код точно ще се изпълни. Може да ни се размине ако функциите са с различни параметри, но наистина не си струва рискът.

Решението e много просто и има потенциал да ни спести много главоболия. Ако се върнем към първоначалния пример със "std", единствената промяна, която е нужна, е да назовем името на съответното именно пространство. Тоест:

GOOD
  1. #include <iostream>
  2. int main() {
  3.     std::cout << "I like pickles." << std::endl;
  4.     return 0;
  5. }


"Но това е толкова досадно, всеки път трябва да назовавам именното пространство!" - няма проблем, защото има и алтернатива:

ALSO GOOD
  1. #include <iostream>
  2. using std::cout;
  3. using std::endl;

  1. int main() {
  2.     cout << "I like pickles." << endl;
  3.     return 0;
  4. }

Аналогично може да приложите горния код за std::string или структури като std::vector, std::list и т.н. Изключително дребна промяна, която не коства нищо, а носи огромна полза.