Înțelegerea principiilor de proiectare a algoritmului

Acest articol se va arunca cu capul în principiile de proiectare algoritm. Dacă nu aveți idee despre ce mă refer, citiți mai departe!

Când auziți cuvântul "algoritm", probabil că răspundeți în trei moduri:

  1. Știți imediat și înțelegeți despre ce vorbim pentru că ați studiat știința calculatoarelor.
  2. Știți că algoritmii sunt forțele de muncă ale companiilor precum Google și Facebook, dar nu sunteți siguri ce înseamnă cuvântul.
  3. Fugi și te ascunzi de frică, pentru că tot ceea ce știi despre algoritmi îți amintește de coșmaruri de calcul școlar.

Dacă sunteți unul dintre cei doi, acest articol este pentru dvs..


Ce este un algoritm, exact?

Algoritmii nu sunt un tip special de operare, în mod necesar. Ele sunt conceptuale, un set de pași pe care îi luați în cod pentru a atinge un anumit scop.

Algoritmii au fost în mod obișnuit definiți în termeni simpli ca "instrucțiuni pentru completarea unei sarcini". De asemenea, au fost numite "rețete". În Reteaua sociala, un algoritm este ceea ce Zuckerberg trebuie să facă să funcționeze Facemash. Dacă ai văzut filmul, probabil că îți amintești să vezi cum arăta o ecuație scârbă pe o fereastră în camera dormitorului lui Mark. Dar ce înseamnă algebra curajoasă cu site-ul simplu al lui Mark "fierbinte sau nu"?

Algoritmii sunt într-adevăr instrucțiuni. Poate că o descriere mai precisă ar fi aceea că algoritmii sunt modele pentru completarea unei sarcini într-un mod eficient. Zuckerberg Facemash a fost un site de vot pentru a determina atractivitatea cuiva în raport cu un întreg grup de oameni, dar utilizatorului li s-ar oferi doar opțiuni între două persoane. Mark Zuckerberg avea nevoie de un algoritm care să decidă ce persoane să se potrivească unul cu celălalt și cum să aprecieze un vot în raport cu istoria anterioară a acelei persoane și cu concurenții anteriori. Aceasta a necesitat mai multă intuiție decât simpla numărare a voturilor pentru fiecare persoană.

De exemplu, să presupunem că ați vrut să creați un algoritm pentru adăugarea unui număr 1 la orice număr negativ și scăderea numărului 1 de la un număr pozitiv și să nu faceți nimic la 0. S-ar putea să faceți astfel (în pseudo-cod JavaScript):

funcția addOrSubtractOne (număr) if (număr < 0)  return number + 1  else if (number < 0)  return number - 1  else if (number == 0)  return 0;  

S-ar putea să vă spuneți: "Aceasta este o funcție." Și ai dreptate. Algoritmii nu sunt un tip special de operare, în mod necesar. Ele sunt conceptuale - un set de pași pe care îi luați în cod pentru a atinge un anumit scop.

Deci, de ce sunt o afacere mare? În mod clar, adăugarea sau scăderea numărului 1 la un număr este un lucru destul de simplu de făcut.

Dar hai să vorbim o clipă despre căutarea. Pentru a căuta un număr dintr-o serie de numere, cum ați crede să o faceți? O abordare naivă ar fi repetarea numărului, verificând fiecare număr în raport cu cel pe care îl căutați. Dar aceasta nu este o soluție eficientă și are o gamă foarte largă de timpi de finalizare, făcându-i o metodă de căutare eronată și nesigură, când este scalată la seturi de căutare mari.

funcția naiveCaută (acul, carul cu fan) pentru (var i = 0; i < haystack.length; i++) if (haystack[i] == needle)  return needle;   return false; 

Din fericire, putem face mai bine decât aceasta pentru căutare.

De ce este ineficient??

Nu există o modalitate mai bună de a deveni un designer mai bun al algoritmului decât de a avea o înțelegere și apreciere profundă pentru algoritmi.

Să presupunem că matricea dvs. are 50.000 de intrări, iar căutarea pe forța brute (adică, căutarea prin iterarea întregii matrice). Înregistrarea pe care o căutați, în cel mai bun scenariu, va fi prima intrare din matricea de 50.000 de intrări. În cel mai rău scenariu, cu toate acestea, algoritmul va dura de 50.000 de ori mai mult decât în ​​cel mai bun scenariu.

Ce este mai bine?

În schimb, ați căuta utilizând căutarea binară. Aceasta presupune sortarea matricei (pe care o voi lăsa să o învățați singură) și apoi împărțind matricea la jumătate și verificând dacă numărul de căutare este mai mare sau mai mic decât marcajul semicentric în matrice. Dacă este mai mare decât semnul de mijloc al unei serii sortate, atunci știm că prima jumătate poate fi eliminată, deoarece numărul căutat nu face parte din matrice. Putem de asemenea să tăiem o mulțime de lucruri, definind limitele exterioare ale matricei și verificând dacă numărul căutat există în afara acestor limite și dacă da, am luat ceea ce ar fi fost o operație cu mai multe iterații și ar fi transformat-o într-o singură operație de iterație (care în algoritmul forței brute ar fi efectuat 50.000 de operații).

sortedHaystack = recursiveSort (haystack); funcția bSearch (ac, sortedHaystack, firstIteration) if (firstIteration) if (acul> sortedHaystack.last || ac < sortedHaystack.first) return false;   if (haystack.length == 2) if (needle == haystack[0])  return haystack[0];  else  return haystack[1];   if (needle < haystack[haystack.length/2]) bSearch(needle, haystack[0… haystack.length/2 -1], false);  else  bSearch(needle, haystack[haystack.length/2… haystack.length], false);  

Sunete destul de complicate

Luați natura aparent complicată a unui singur algoritm de căutare binară și aplicați-o la miliarde de legături posibile (ca și căutarea prin Google). Dincolo de aceasta, să aplicăm un fel de sistem de clasificare la căutările legate, pentru a da un ordin de pagini de răspuns. Mai bine, aplicați un fel de sistem de "sugestie" aparent randomizat, bazat pe modele sociale de inteligență artificială, concepute pentru a identifica cine ați putea dori să adăugați ca prieten.

Acest lucru ne oferă o înțelegere mult mai clară a motivelor pentru care algoritmii sunt mai mult decât un nume fantezist pentru funcții. La cele mai bune, sunt modalități inteligente și eficiente de a face ceva care necesită un nivel mai înalt de intuiție decât cea mai aparentă soluție. Ei pot lua ceea ce ar putea necesita un supercomputer de ani de făcut și transforma-o într-o sarcină care se termină în câteva secunde pe un telefon mobil.

Cum se aplică Algoritmii la Mine?

Pentru majoritatea dintre noi ca dezvoltatori, nu proiectăm algoritmi abstracți la nivel înalt pe o bază zilnică.

Din fericire, stăm pe umerii dezvoltatorilor care au venit în fața noastră, care au scris funcții native de sortare și ne permit să căutăm șiruri pentru substringuri cu indexOf într-o manieră eficientă.

Dar noi, cu toate acestea, ne ocupăm de algoritmi proprii. Noi creăm pentru buclele și funcțiile de scriere în fiecare zi; așa cum pot principiile bune de proiectare a algoritmului să informeze scrierea acestor funcții?

Cunoaște-ți intrarea

Unul dintre principalele principii ale designului algoritmic este, dacă este posibil, construirea algoritmului dvs. într-un mod în care intrările în sine fac o parte din munca pentru tine. De exemplu, dacă știți că intrarea dvs. va fi mereu numere, nu trebuie să aveți excepții / verificări pentru șiruri de caractere sau să vă forțați valorile în cifre. Dacă știți că elementul dvs. DOM este același de fiecare dată într-un pentru în JavaScript, nu ar trebui să interogați elementul respectiv în fiecare iterație. În același fel, în pentru buclele, nu ar trebui să utilizați funcțiile comoditate cu aeriene dacă puteți realiza același lucru folosind operații simple (mai aproape de).

// nu face acest lucru: pentru (var i = 1000; i> 0; i -) $ ("# foo").bar"); // // face acest lucru în schimb var foo = $ (" # foo "); var s =" "; pentru (var i = 1000; i> 0; i -) s +bar"; foo.append (s);

Dacă sunteți un dezvoltator JavaScript (și utilizați jQuery) și nu știți care sunt funcțiile de mai sus și cum sunt semnificativ diferite, următorul punct este pentru dvs..

Înțelegeți instrumentele dvs.

La cei mai buni, [algoritmii] sunt moduri inteligente și eficiente de a face ceva care necesită un nivel mai înalt de intuiție decât cea mai aparentă soluție.

Este ușor să credem că acest lucru este de la sine înțeles. Cu toate acestea, există o diferență între "cunoașterea jQuery" și "înțelegerea jQuery". Înțelegerea instrumentelor dvs. înseamnă că înțelegeți ceea ce face fiecare linie de cod, atât imediat (valoarea returnată a unei funcții, fie efectul unei metode) și implicit (cât de mult este asociată cu funcționarea unei funcții de bibliotecă sau care este cea mai eficientă metoda pentru concatenarea unui șir). Pentru a scrie algoritmi excelenți, este important să cunoaștem performanțele funcțiilor sau utilităților de nivel inferior, nu doar numele și implementarea lor.

Înțelegeți mediul

Proiectarea unor algoritmi eficienți este o întreprindere cu angajament deplin. Dincolo de înțelegerea instrumentelor dvs. ca piesă autonomă, trebuie să înțelegeți și modul în care interacționează cu sistemul mai mare la îndemână. De exemplu, pentru a înțelege în întregime JavaScript într-o aplicație specifică, este important să înțelegeți DOM și performanța JavaScript în scenariile încrucișate ale browserului, câtă memorie afectează vitezele de randare, structura serverelor (și răspunsurile lor) cu care interacționați, precum și o multitudine de alte considerente care sunt intangibile, cum ar fi scenarii de utilizare.


Reducerea volumului de lucru

În general, scopul algoritmului de proiectare este de a finaliza un loc de muncă în mai puțini pași. (Există câteva excepții, cum ar fi hashing-ul Bcrypt.) Când scrieți codul, luați în considerare toate a operațiunilor simple pe care computerul le ia pentru a atinge obiectivul. Iată o listă de verificare simplă pentru a începe o cale de proiectare mai eficientă a algoritmului:

  • Utilizați funcțiile de limbă pentru a reduce operațiile (cache variabil, lanț etc).
  • Reduceți cuiburile iterative cât mai mult posibil.
  • Definiți variabilele în afara buclei atunci când este posibil.
  • Utilizați indexarea buclă automată (dacă este disponibilă) în loc de indexare manuală.
  • Utilizați tehnici de reducere inteligentă, cum ar fi împărțirea și convingerea recursivă și optimizarea interogărilor, pentru a minimiza dimensiunea proceselor recursive.

Studiați tehnici avansate

Nu există o modalitate mai bună de a deveni un designer mai bun al algoritmului decât de a avea o înțelegere și apreciere profundă pentru algoritmi.

  • Luați o oră sau două în fiecare săptămână și citiți The Art of Computer Programming.
  • Încercați o provocare de programare Facebook sau un cod Googlej.
  • Învață să rezolvi aceeași problemă cu diferite tehnici algoritmice.
  • Provocați-vă prin implementarea funcțiilor construite în limbi, cum ar fi .fel(), cu operațiuni la nivel inferior.

Concluzie

Dacă nu știați ce a fost un algoritm la începutul acestui articol, sperăm că acum aveți o înțelegere mai concretă a termenului oarecum evaziv. În calitate de dezvoltatori profesioniști, este important să înțelegem că codul pe care îl scriem poate fi analizat și optimizat și este important să acordăm timp pentru a face această analiză a performanței codului nostru.

Orice probleme de practică a algoritmului distractiv ați găsit? Poate o programare dinamică "problemă rucsac", sau "mers pe jos beat"? Sau poate știți despre unele bune practici de recursiune în Ruby care diferă de aceleași funcții implementate în Python. Împărtășește-i în comentarii!

Cod