Kapsamlı Bilgisayar Mühendisliği Eğitimi Rehberi 7 - Programlama Dilleri



Öncelikle tebrikler, üçüncü sınıfa geldiniz! Gerçek bilgisayar mühendisliğiyle, zevkli ve zorlayıcı derslerle bu yıl karşılaşacaksınız.

3. Sınıf 1. Dönem 

CS 315 - Programming Languages

Üniversiteye yeni başladığımda oryantasyonda bölüm başkanı bir konuşma yapmıştı. "Biz size programlama dili öğretmeyeceğiz. Biz size öğrenmeyi öğreteceğiz. " Yıllar sonra herhangi bir programlama dilinde ödev verdiklerinde ödevi yaparken dili de öğrenecek hale gelecekmişiz. 

Adam haklı.

Programlama dili diyince akla İngilizce, Çince gibi dil öğrenmek geldiği için bana gelen sık sorulardan biri "Kaç programlama dili öğretiyorlar? Hangileri?". Bu soru edebiyat okuyana "Kaç dil öğretiyorlar? Hangileri?" demekle aynı şey. Neden olduğunu bu derste anlıyoruz.

Aslında programlama dillerinin bir çoğunun işlevi aynıdır, aynı şeyi yaparlar. Fakat aynı şeyi yaparken karşılaşılan sorunları farklı bir şekilde çözerler. Bu derste de programlama dillerinin bu çözümleri nasıl uygulayabileceği örneklerle anlatılır. Yani programlama dilleri değil onların kökü, onların yapılışındaki kalıplar anlatılır ki yeni bir programlama dili çıktığında "Bu bunu nasıl yapmış?" şeklinde sorular sorarak programlama dilini çabucak kapasınız.

Örneğin bu java koduyla merhaba yazmak:

System.out.println ("Merhaba");

python:

print ("Sa")

Java kodundaki System bir class. (Classları birinci yazıda anlatmıştım.) Bunun içinde out diye bir parametre var, bu da aynı zamanda nesne (GidenAraba classında arabanınHızı vardı ya bunun Hız classının nesnesi olduğunu düşünün). Bunun bir de fonksiyonu var println diye. Anca onu çağırınca ekrana bir şeyler basabiliyoruz. Ölme eşşeğim ölme. Python çözmüş işi print() diye.

Java'da satırın bittiğini ; ile gösterirsiniz. Bu sayede aslında tek satıra bir sürü satır yazabilirsiniz. System.out.println("Merhaba");System.out.println("Naber");System.out.println("Anan nasıl");System.out.println("Baban nasıl"); diye. Python'da ; 'e gerek yoktur, Python satırın nerede bittiğini kendi anlar. Fakat eğer yukarıdaki gibi her şeyi bitişik yazarsanız anlayamaz. Dolayısıyla her şeyi bitişik yazamazsınız.

Aynı zamanda java'da kodun nerede başlayıp bittiğini { } ile gösterirsiniz.

Örnek:

if ( a > b )
{
    System.out.println("a b'den büyük");
}

System.out.println("bitti");

Burada { } ile if'in kapsamını yani kodun hangi kısmını koşula bağladığını gösteririz.

Yani bu durumda "a b'den büyük" cümlesi sadece a b'den büyük olduğunda ekrana yazdırılır fakat "bitti" her halükarda yazılır.

Python'da ise bunu yapmak için şunu yazmalısınız:

if ( a > b):
    print("a b 'den büyük")

print("bitti")

Bir fark yok gibi gözüküyor değil mi? Olay şu: her hangi bir programlama dilinde yazarken if'in neyi kapsadığını belirtmekle beraber kod okunaklı olsun diye taba basar veya dört tane boşluk bırakırık. Halbuki java boşlukları zaten sallamaz. Yani şu kod da pekala çalışır javada:

if ( a > b )
{
System.out.println("a b'den büyük");
}
System.out.println("bitti");

Ama bu kod pythonda çalışmaz:

if ( a > b):
print("a b 'den büyük")
print("bitti")

Çünkü python bu dört boşluk olayını kendine entegre etmiştir ve programcıları bunu kullanmaya zorlamaktadır, aynı zamanda kendi de if'in kapsamını anlamakta kullanmaktadır.

Bu dersin içeriğini uzun uzun anlatmak isterdim ama anlatması da anlaması da zor olacak. Kısaca özet geçiyorum o yüzden konuları. Ama iddia ediyorum bilgisayar mühendisliği okursanız en sevdiğiniz ders olacak.

Regular expressionlar

Harfler üzerinde işlem yapacağız diyelim. İşlemimize vereceğimiz bilinmeyen birden fazla a'dan veya a ile b'nin yan yana gelmesinden oluşmak zorunda. Yani a kabul, aa kabul, aaaaaaaaaa kabul, ab kabul. bbbbbbbb kabul değil. e^x kabul değil. Boş da kabul değil.

Tabii bunu olabilecek her a b kombinasyonu için if koyarak kontrol edemeyiz. 
if ( bilinmeyen == "a")
else if (bilinmeyen == "aa") 
olmaz yani.

Bunu aa*|ab olarak gösteririz ve verilen bilinmeyenin bu kalıba uyup uymadığına bakarız. Burada * "sonlu sayıda tekrar" demek yani yanyana sıfır veya sıfırdan fazla a. Başına bir tane a attık çünkü bilinmeyenin boş olmasını da istemiyoruz. | veya demek. Yani ya soldakini seçeceğiz ya sağdakini. ab'yi bitişik yazınca a'nın yanında b her zaman bonus geliyor.

Yani aa*|ab

a
aa
aaa
aaaaaaaaaaaaaaaaaa
ab

olabilir.

aa*|ab'ye regular expression denir.

Context free grammar 
Bu gramerler sayesinde kodun ne anlatmak istediği anlaşılır.

Bir gramer örneği:

ifade -> ifade toplama_işlemi terim | terim
terim -> terim çarpma_işlemi faktör | faktör
faktör -> '-' faktör | '(' ifade ')' | İSİM | SAYI

toplama_işlemi -> '+' | '-'
çarpma_işlemi -> '*' | '/'

Burada olay şu, ifade (bir satır koda yani sonu ; ile biten koda ifade deniyor) -> 'nın sağındaki şeye dönüşüyor, yani ya "ifade toplama_işlemi terim"e ya da sadece terime. terim ->'dan sonrasına, faktör de en sonunda İSİM veya SAYIya dönüşüyor. Bu kalıbı kullanarak bilgisayar kodu okuyor. 

Örneğin

elmasayısı + 5 * 3

Bu bir ifade. Sağdan başlayarak bunu en basit hale indirgiyoruz yani İSİM SAYI + - * /'ya indirgemeye çalışıyoruz.

ifade -> ifade toplama_işlemi terim oldu.

en sağdaki terim 'terim çarpma_işlemi faktör'e indirgendi. Faktörden de SAYI yani 3'e indirgendi.

ifade toplama_işlemi terim çarpma_işlemi 3 var elimizde.

çarpma_işlemi oluyor * (ifade toplama_işlemi terim * 3)

terim oluyor faktör, faktör oluyor 5.

toplama_işlemi oluyor +

son olarak ifade oluyor terim terim oluyor faktör faktör oluyor İSİM yani elmaSayısı

elmasayısı + 5 * 3 ifadesini elde ediyoruz.

Kısacası bilgisayar bu şekilde okuyor kodu. Kod İngilizce'den 010110111'lere bu şekilde çevriliyor.

Not: Niye ifade direkt 5 olmuyor da önce terim sonra faktör sonra 5 oluyor? Bunun nedeni işlem önceliği. Çarpma işlemiyle sadeleşen ifadeyi tanımlamak için faktör demeseydik hepsine aynı ismi verseydik bilgisayar aynı şeyi rastgele olarak farklı yorumlayabilirdi. Grameri bu şekilde oluşturunca muğlaklık gitmiş oldu. Detayları merak edenler için anahtar kelime: unambiguous grammar

Dersin ilk kısmı bu gramerleri yazabilmek veya koda çevirebilmekle geçer.

*

Ardından değişken isimleri ve yukarıda bahsettiğim kapsamlardan bahsedilir. Örneğin elmaSayısı diye bir değişken tanımladım ama bu elmaSayısı ismi nelerde geçerli. Örnek:

if ( bir şeyler) {
    int elmaSayısı = 10;
}

Örneğin bu java kodunda elmaSayısı sadece {} arasında tanımlıdır ve bunun dışında burada tanımladığınız elmaSayısını kullanamazsınız, ama {} dışında bir elmaSayısı varsa o kullanılır, tabii içindeki değer 10 olmayabilir bu durumda. Sıkıcı ve çok teknik olduğu için bu konuyu burada bırakıyorum.

Ardından ilk yazılarda bahsettiğim tiplerden ve bunların dilden dile nasıl değiştiğinden bahsedilir. örneğin java'da elmaSayısını "int elmaSayısı =10;" diye tanımlamanız gerekir ve ardından "elmaSayısı = "naberlen";" diyemezsiniz. Python'da ise elmaSayısı = 10 diye tanımlayıp ardından üzerine istediğinizi yazabilirsiniz. Tabii arkada çalışan işlemler farklıdır. Python eski elmaSayısını silip yerine string tutan bir elmaSayısı değişkeni tanımlar mesela ama çaktırmaz. Java'da bunları elle yapmanız gerekir.

Ardından fonksiyonların nasıl çalıştığından bahsedilir. Fonksiyonlar iç içe geçebilir (ikinci yazıda recursion kısmını hatırlayın) ve bu olay yüzünden farklı dillerde farklı davranabilirler çünkü. Geçtik. Son olarak exceptionlar anlatılır ama o kısmı boşverin.

Sıradaki yazıda görüşmek üzere.

Kapsamlı Bilgisayar Mühendisliği Eğitimi Rehberi 6 - Matematik ve Uygulamaları


Güncelleme: Lise müfredatından matris ve determinantlar kalktı diyenler oldu. Yazıda determinant yok. Matrisi kısaca açıklayayım, içine sayılar koyduğumuz kutulara matris denir. 3x4 boyutta bir matriste 3 satır 4 sütun bulunur. Birden fazla denklem çözerken bilinmeyenlerin katsayılırını kutulara yazarak çözebilir. İki matris çarpılıyorsa soldaki matrisin sütun sayısıyla sağdakinin satır sayısı eşit olmalı. Yani 2x4 matris ile 4x5 matris çarpılabilir. Oluşan matrisin boyutu 2x5 olur. Bu yazdıklarıma rağmen hala matrisli kısımları anlayamıyorsanız es geçebilirsiniz de, ziyanı yok.

Birinci sınıftaki Calculus dersleri pek önemli olmadığından kısa geçmiştim. Şimdi bilgisayar mühendisliğinin bazı temel taşlarını içeren Linear Algebra ve Probability & Statistics derslerinden ve buradaki matematik konseptlerinin bilgisayar bilimindeki uygulamalarından bahsedeceğim.

2. Sınıf 2. Dönem

Linear Algebra and Differential Equations

Endüstricilerle beraber aldığımız bu ders iki al bir öde şeklinde hazırlanmış. İçinde birbirinden bağımsız iki ders var yani.

Differential Equations: İçinde türev geçen denklemleri yani diferansiyel denklemleri çözmekle alakalı bir derstir. Lisede f(x) = x + 5 ise f'(x) kaçtır diye sorulurken bu derste f(x) = f'(x) + 5 ise f(x) nedir sorusunu sorarsın. Çözüm f(x) = C*e^x + 5 'tir. (C herhangi bir sabit değer) f(x)'in türevini alırsanız (C*e^x) elde ettiğinizi 5 ile toplayınca f(x)'e eşit olduğunu göreceksiniz. Çözüm tekniği ise x'li terimleri bir tarafa sabit değerleri bir tarafa alıp integral almaktan geçer. (Daha fazla bilgi için "separable" yani ayrılabilir diferansiyel denklemleri inceleyin.)

Diferansiyel denklemler çok bir işe yaramamaktadır. Benim karşıma bir daha çıkmadı. O yüzden çok üzerinde durmuyorum.

Linear Algebra: Lineer Cebir lise üçteki matris ve determinantların gelişmişidir. Çok fazla yeni konsept öğrenip kavramak gerektiğinden oldukça sıkıcıdır. Tabii ki daha kendim dinlerken uyuduğum kavramlardan burada bahsederek sizi sıkmayacağım. Dersin bu kısmında öğrendiğim ve sonradan karşıma çıkan iki önemli konsept var, anlatması da kolay, anlatıyorum.

- Denklem sistemleri ve çözümleri
Birden fazla bilinmeyen içeren denklemlere denklem sistemleri denir ve bunları lisede yaptığımız gibi sezgisel olarak çözmesi zordur, hele bilinmeyen sayısı arttıkça. O yüzden bu derste "Gauss İndirgeme Yöntemi" veya "Gauss Jordan Eliminasyonu" denilen yöntem öğretilir. (İngilizcesi Gaussian Elimination veya Gauss Jordan Method) Yöntem basittir; denklemleri al matris olarak yaz, sonra soldan başlayarak her sütundaki sayıların ilki 1 diğeri 0 olacak şekilde denklemleri düzenle.

Örnek:

x + y + z = 10

x + 2y + 2z = 20

x + 2y + 3z = 23

Oluşturalım matrisimizi:

[ 1 1 1 | 10 ]
[ 1 2 2 | 20 ]
[ 1 2 3 | 23 ]

Eşitliğin sağ tarafı da matrisin içinde, orayı | ile ayırdım.

Bu durumda matrisin satırlarını bir şeylerle çarpmak veya satırları birbirinden çıkarmakla lisede denklem çözerken denklemi ikiyle üçle çarpıp üsttekinden çıkarmak aynı şey. (Bu Kimya dersinde de yapılıyor hatta.) Bu durumda ikinci ve üçüncü satırdan ilk satırı çıkarırsak şu olur:

[ 1 1 1 | 10 ]
[ 0 1 1 | 10 ]
[ 0 1 2 | 13 ]

İlk sütun için istediğimizi elde ettik, sayıların ilki 1 diğerleri sıfır oldu. İkinci ve üçüncü sütun için tekrarlayalım, birinci ve üçüncü satırdan ikinci satırı çıkartalım:

[ 1 0 0 | 0 ]
[ 0 1 1 | 10 ]
[ 0 0 1 | 3 ]

Üçüncü sütun kaldı:

[ 1 0 0 | 0 ]
[ 0 1 0 | 7 ]
[ 0 0 1 | 3 ]

Bu durumda aslında şu denklemleri elde etmiş olduk:

1*x + 0*y + 0*z = 0
0*x + 1*y + 0*z = 7
0*x + 0*y + 1*z = 3

Buradan da çok açık olduğu üzere x = 0, y = 7 ve z = 3.

*

Tabii gerçek hayatta denklem sistemleri bu kadar kolay değil. Gerçek problem karşınıza çıkınca her zaman Gauss metoduyla kurtaramıyorsunuz ve başka metodlar da bilmek gerekiyor. Ama bundan daha önemlisi şu ki denklem sistemini kurabilmek.

Gauss'un adı başka yerlerde de geçti ama hakiki denklem sistemi kurduğumuz ders IE 400 yani Principles of Engineering Management oldu, yani bilgisayarcılar için endüstri mühendisliği dersi. Bu derste verilen problemlerden biri:

* Bir şirket A ve B makinelerini kullanarak X ve Y isimli iki ürün üretmektedir. X ürünü A makinesinde 50 dakikada, B makinesinde 30 dakikada üretilir. Y ürünü A makinesinde 24 dakikada, B makinesinde 33 dakikada üretilir.

Haftanın başında elde 30 tane X ve 90 tane Y vardır. A makinesi en fazla 40 saat, B makinesi en fazla 35 saat çalışabilir. Bu hafta X ürününden 75 tane, Y ürününden 95 tane gerekmekte. X ve Y ürünleri toplamı en fazla olacak şekilde denklem sistemi kurun ve bunu çözüp X ve Y'den ne kadar üretileceğini bulun.

Çözüm:

Nasıl çözüleceğini göstermeyeceğim, zaten ben de bilmiyorum :P Denklem sistemini yazayım:






  • 50x + 24y <= 40(60) ( A makinesinin çalışma süresi, dakika cinsinden)
  • 30x + 33y <= 35(60) ( B makinesinin çalışma süresi, dakika cinsinden)
  • x >= 75 - 30 = 45 (Üretilebilecek X stoğu, en az 75 olmalı ama elde 30 tane varmış zaten)
  • y >= 95 - 90 = 5 (Bu da Y stoğu)

  • Görev: Bu fonksiyonu maksimize etmek: (x+30-75) + (y+90-95) yani (x+y-50) yani x + y

    Bu en temel ve kolay örnekti, konumuz endüstri mühendisliği olmadığı için daha zorlarıyla uğraşamayacağım. Şuna bağlayacağım: burada iki bilinmeyenli bir sürü denklem elde ettik ve bunu çözmek için bir algoritmaya ihtiyacımız var. Her ne kadar burada işimize yaramadıysa da Gauss Jordan Eliminasyonu da bunların en basitlerinden biri.

    - Özvektörler

    Literatürde "Eigenvector" diye geçer. Vektör demek sadece satır ve sütundan oluşan matris demektir, kafanız karışmasın. 4x1 matris de vektördür, 1x4 matris de vektördür. 4x4 ise kare matristir.

    Eigenvector kısaca şu: Atıyorum 4x4 bir matrisiniz ve 4x1 bir vektörünüz var. Bu ikisini çarparsanız 4x1 vektör elde ederseniz doğru mu? Aynı şekilde herhangi bir 4x1 vektörü tamsayıyla çarparsanız yine 4x1 vektör elde edersiniz yani liseden bildiğiniz şeyler yine.

    4x4 matrise A
    4x1 vektöre v
    Tamsayıya C

    diyelim.

    A*v = C*v

    denkleminde v'ye A'nın eigenvector'u, C'ye de eigenvalue'su denir. Tahmin edemeyeceğiniz üzere bunu sağlayan az sayıda eigenvalue ve eigenvector vardır.

    Bu derste bunun çözümü falan da anlatılır da boşverin onları şimdi.

    Ne işe yaradığına gelelim.

    Daha önce Makine Öğrenmesi (Machine Learning) hakkında kısa bilgi vermiştim ama kaçıranlar ve tekrar okumak isteyenler için kısaca özetleyeyim. Makine öğrenmesinde bir sürü veri toplayıp o verinin özellikleri arasındaki bağlantıya bakıp yeni veriler üzerinde tahmin yaparsınız. Örneğin kişinin boyu, kilosu ve yağ oranından kaslı gözüküp gözükmemesi üzerine analiz kasıyoruz. Boy 180 kilo 60 yağ oranı %10 atıyorum, bunu bir vektörde gösteriyoruz ve vektörün son elemanı da kaslı için 1 kassız için 0 oluyor yani (180, 60, 10, 1). Çeşitli işlemlerle boyu 190 kilosu 50 yağ oranı 5 olan bir kişinin kaslı gözüküp gözükmediğini tahmin edeceğiz yani (190, 50, 5, x) vektöründe x'i bulacağız. Şimdi bu tahmini yapmak için bir milyon tane veri olduğunu düşünün. Her veri için 4 elemanlı vektör üzerinde işlem yapmamız gerek, kabaca 4 * bir milyon. Fakat sadece iki verimiz olsa işlem sayımız yarıya düşer iki milyon olurdu ve bilgisayar daha az çalışıp daha az ısınırdı. Lâkin ki boy, kilo ve yağ oranı üçü de önemli hangi birini atacağız? Cevap: hiç birini. Bunları aynı matrisin eigenvalue'sunu bulurken yaptığımız gibi tek bir sayıya indirgeyeceğiz. Basit bir mantık yürütürsek: bir insanın 180 cm boyunda 50 kiloda olup da %90 yağ oranına ulaşma imkanı yok. 150 cm boyunda 90 kilogram olup %10 yağ oranına da sahip olamaz değil mi ama? Özetle bu üç sayı arasında bir bağlantı var ve eigenvector ve eigenvaluelar ile bu bağlantıyı kullanıp üç bilinmeyeni tek bir bilinmeyende eritip ona göre işlem yapabiliyoruz yani vektörün boyutunu düşürüyoruz. Buna boyut küçültme yani dimensionality reduction deniyor. 

    Anlaşılmadıysa çok çok daha basit bir örnek vereyim (bu yeni aklıma geldi ama yukarıdakini silmeye üşendim) bir hayvanın aslan mı yarasa mı olduğunu tahmin edeceğiz ve elimizde iki özellik var: hayvanın rengi ve büyüklüğü. Renkler sarı ve siyah, büyüklük büyük ve küçük. Tabii ki bütün sarı ve büyük hayvanlar aslan, siyah ve küçük hayvanlar yarasa. Umarım sarı yarasa yoktur. Bu durumda eigenvektörümüz boyutu küçültüyor ve tek seçeneğimiz "siyah ve küçük" olmak veya "sarı ve büyük" olmak oluyor.

    Bu yaz sıcağında matematik çalışıp eigenvektörün bunu nasıl yaptığını anlatmaya üşendim.

    Probability and Statistics for Engineers

    Bu derste Probability yani Olasılık ve Statistics yani İstatistik olmak üzere iki kısımdan oluşur ve makinecilerle beraber alınır. Makine Öğrenmesinin belkemiğini oluşturur diyebiliriz.

    Bu ders üzerine de anlatılacak çok şey var, zevkli de bir derstir ama ben sadece Naif Bayes nedir onu anlatıp geçeceğim.

    Önce koşullu olasılık nedir onu açıklayayım, koşullu olasılık bir olayın gerçekleşeceği farz edildiğinde diğer olayın olma olasılığıdır. Örneğin bozuk paranın dik gelme olasılığı normalde 0'dır. Fakat bozuk para hileliyse sıfır değildir, bozuk para hileli olduğunda dik gelme ihtimalini bozuk paranın hem hileli olma hem dik gelme ihtimalini bozuk paranın hileli olma ihtimaline bölerek buluruz.

    Yani şu formül:


    Eğer A ve B bağımsız olaysa yani birinin olması diğerini etkilemiyorsa (zarın 6 gelme ihtimaliyle paranın tura gelme ihtimali) P(A kesişim B) = P(A) * P (B) olur P(B)'ler sadeleşir dolayısıyla A ve B bağımsızsa P(A|B) = P(A)'dır.

    P(B)'yi karşıya atarak aynı formülü B olayına uygulayabiliriz. Yani.


    Ortadaki fazlalığı atarsak ismi Bayes teoremi olan şu formülü elde ederiz:


    Gördüğünüz gibi bu formülde koşullu olasılığın içindeki bilinmeyenler yer değiştirdi.

    Şimdi diyelim adamın boyu, kilosu, yağ oranı, göz rengi bilmemnesi X1, X2, X3 diye gidiyor. Kaslı gözüküp gözükmemesi ise Y. Bu durumda adamın özellikleri ışığında kaslı gözüküp gözükmemesi P(Y | X1, X2, X3,....) diye uzayıp giden bir koşullu olasılıkla gösterilir. Bayes teoreminde bunu sol tarafa koyunca sağ tarafa da P(X1, X2, X3.... | Y) gibi bir şey yazarız fakat bunu hesaplamamıza pek imkan yoktur.

    Naive Bayes Y'nin olduğu farzedildiğinde X'lerin bağımsız olduğunu söyler. Adamın göz rengiyle kilosu alakasızdır pekala, adam kaslı olsa da olmasa da. Olaylar bağımsız olduğu için tüm olasılığı hepsini tek tek çarparak buluruz. Yani P(X1, X2, X3.... | Y) = P(X1 | Y) * P(X2 | Y) * P(X3| Y) *..şeklinde bulunur. *... kısmını çarpım işaretiyle gösterebiliriz.

    Bayes teoreminde paydadaki P(B) yani P(X1, X2, X3....) olasılığı adamın kaslı olup olmamasına bağlı olmadığı ve aslen sabit olduğu için ihmal ediliyor. Yani sadece pay hesaba katılıyor.

    Dolayısıyla

    P(Y | X) = P(X1, X2, X3,....|Y) * P(Y) = P(X1 | Y) * P(X2 | Y) * P(X3| Y)*... * P(Y)

    yani

    P ( Kaslı olma ihtimali | adamın kilosu, boyu, rengi) = P( göz rengi | kaslı olma ihtimali) * P (kilosu | kaslı olma ihtimali) * P (kaslı olma ihtimali)

    Atıyorum elimizde 20 tane nefer var. Bunların 8'i kaslı. 20 kişinin 15'i yeşil gözlü. Kaslıların yarısı yeşil gözlü. 10 kişi 80 kilo, 10 kişi 50 kilo. Kaslıların hepsi 80 kilo. Araya 21. bir adam geldi, bu adam 80 kilo ve yeşil gözlü. Bu adamın kaslı olma ihtimalini şöyle hesaplarız.

    P ( Kaslı olma ihtimali | adamın kilosu, boyu, rengi) = bu hesaplayacağımız şey zaten.

    P( göz rengi = yeşil | kaslı olma ihtimali) = kaslıların yarısı yeşil gözlü demiştik di mi? P ( yeşil göz kesişim kas) = 4 / 20. Bunu P( kaslı) yani 8/20'ye böleriz. Oldu sana 1/2. Gerçi kaslıların yarısı yeşil gözlü olduğu için direkt 1/2 de diyebiliriz.

    P( kilo = 80 | kaslı olma ihtimali) = 1 , bütün kaslılar 80 kilo demiştik.

    P (kaslı) = 8 / 20 herhangi bir kişinin kaslı olma ihtimali 0.4

    Yukarıdaki formüle göre bunları çarpıyoruz. 0.5 * 1 * 0.4 = 0.2

    Yani araya katılan bu 21. kişinin kaslı olma ihtimali 0.2. Bu kadar düşük olma ihtimalinin sebebi kaslı kişilerin nispeten az olması ve sadece yarısının yeşil gözlü olması.

    *

    Yazması zor ve sıkıcı bir yazı oldu. Umarım derdimi anlatabilmiştir, bu yazıyı okuyan olursa geri bildirimlerini kesinlikle almak isterim.

    Önümüzdeki yazıda görüşmek üzere.






    Döndüm

    Arkadaşlar dün döndüm. Planladığımdan erken oldu süper kupa maçı yüzünden, maç Üsküp'teydi ve kalacak yer yoktu, sokakta kalacağıma otobüse atlayıp İstanbul'a gideyim dedim.

    Sorularınızı elimden geldiğince cevapladım şimdi. 20 gün sonra İsviçre'ye taşınacağım, o zamana kadar bir şeyler yazmaya çalışırım.

    Arnavutluk'tan Selamlar

    Arkadaslar merhaba gezi biraz uzadi. 8-10 icinde donmus olmayi planliyorum. Tercih donemi elimden geldigince soru cevapladim, yardimci olamadigim varsa affetsin. Geri kalan sorulari donunce cevaplayacagim. Her ne kadar tercih donemi bitmis olsa da bilgisayar muhendisligi yazilarina devam edecegim seneye girecekler faydalansin diye.

    Mola

    Selam, uzun soluklu bir geziye çıktığım için gelen sorulara cevap yazmakta zorlanıyorum. Belirli aralıklarla cevaplayacağım. Yazılara devam etmek ise şu an için zor gözüküyor.

    Kapsamlı Bilgisayar Mühendisliği Eğitimi Rehberi 5 - Veri Yapıları ve Algoritmalar (Google Bunlardan Soruyormuş)

    İlk dört yazıda sürekli "dokundurulan" veri yapıları ve algoritmaların ne olduğundan bahsedeyim artık.

    CS 201 - Fundamental Structures of Computer Science 

    (Bilgisayar Biliminde Temel Yapılar)

    Bir dakika, önce pointer denen nanenin ne olduğunu öğrenmek gerekiyor. Java'da pointer yok, C, C++ gibi eski dillerde var. Dolayısıyla hocalar ikinci dönem bir anda C++ anlatmaya başlıyorlar. C++ ile Java arasındaki fark vardır ama bu farklar basit işlerle uğraşırken gözle görülebilecek düzeyde değildir, bu yazıları okurken farkı anlayamayacaksınız bile. Bu derste gösterilen ana fark pointerlar diyebiliriz hatta. 

    Pointerlar

    Birinci yazıda GidenAraba'nın bir obje olduğundan bahsetmiştik. Java'da bir GidenAraba değişkeni tanımlandığı zaman o değişkenin içinde bir obje değil adres vardır da demiştik. Bu objede de GidenAraba'da ne tutuluyorsa onlar vardır, GidenAraba'da dört tane int vardı yani bir GidenAraba objesi dört tane int tutar, 4*4 byte = 16 byte yer kaplar diyebiliriz.

    Pointer da içinde adres tutan değişken demektir. Bir integer pointerı şöyle tanımlanır:

    int* elmaPointerı;

    Sonra diyelim bir canım elma çekti.

    int elmaSayısı = 10;

    Şimdi elmaPointerımız elmaSayısına işaret etmiş olsun. Bunu şu şekilde yazarız:

    int* elmaPointerı;
    int elmaSayısı = 10;
    elmaPointerı = &elmaSayısı

    & demek "O değişkenin adresi" demek. &elmaSayısı diyince elmaSayısının içindeki 10'u değil, elmaSayısı değişkeninin hafızada bulunduğu adresi alıyoruz ve elmaPointerı'na atıyoruz.

    Böylece yapınca ne oldu? elmaPointerı değişkeni de elmaSayısı değişkeninin tuttuğu değerin aynısını tutmuş oldu. Aynısı derken kopyası değil, direkt aynısı. elmaSayısı'nı değiştirirsek elmaPointerının tuttuğu adresteki değer de değişmiş olur dolayısıyla bu elmaPointerını da etkiler.

    Bilgisayarınızdaki kısayollar pointerlara harika bir örnektir. Kısayolun işaret ettiği programı silerseniz kısayol çöp olur.

    Aslında bu java'daki objelerin mantığının aynısı. Hani mehmetinArabası = ahmetinArabası yapınca ikisi aynı arabadan bahsetmiş oluyordu, ahmetinAraba satılınca mehmetinAraba da gitmiş oluyordu ya. Bu o onun daha genel versiyonu. C++'da bu mantık her tip değişkene uygulanabilirmiş ama Java yapımcıları sadece objelere uygulayalım kolay olsun demişler.

    Burada önemli bir durum da şu, eğer bir değere veya obje işaret eden tüm pointerlar/değişkenler başka bir şeye işaret etmeye başlarsa Java garbage collector'u (çöpçü) çağırıp otomatik olarak objeyi yok ederken C++ bunu yapmamakta.

    Örnek Java ve C++ kodu (ikisi için de aynı)

    GidenAraba ahmetinArabası = new GidenAraba(); // Ahmete araba aldılar
    GidenAraba mehmetinArabası = ahmetinArabası; // Mehmet de aynı arabayı kullanıyor.

    ahmetinArabası = new GidenAraba(); // Ahmet büyüdü başka araba aldı. Mehmet'te hala Ahmet'in eski araba var.

    mehmetinArabası = ahmetinArabası; // Mehmet bu sefer de Ahmet'in yeni arabaya çöreklendi.

    E Ahmet'in eski arabaya ne oldu? Yazılımcı bundan bahsetmeye tenezzül etmedi. Bu durumda Java Ahmet'in eski arabayı otomatik olarak siler. C++ ise silmez. Fakat Ahmet'in eski arabaya hiçbir şekilde erişemeyiz çünkü ona işaret eden bir değişkenimiz yok. Bu şuna benziyor, bir yerde define gömülü ama bütün haritaları sobada yaktık. Buna memory leak denir çünkü silinmeyen obje hafızada boşu boşuna yer kaplamaktadır.

    Silinmesi gereken objeyi "delete" komutuyla sileriz (bu java'da yoktur doğal olarak)

    delete ahmetinArabası;

    (Tahmin edeceğiniz gibi delete ahmetinArabası diyince ahmetin kullandığı arabayla beraber mehmetin kullandığı araba da yok oldu. Dolayısıyla bir daha delete mehmetinArabası dememize gerek yok.)

    C++'da arrayler bir obje değil pointerdır. elmaSayıları arrayi aslında bir elmaSayıları için hafızada açılmış yanyana sayıların birincisine işaret eder. Array uzunluğu javadaki gibi ayrı bir yerde tutulmaz.

    Koca bir arrayin adresini kaybettiğinizde oluşacak memory leak'in farkında mısınız? Hocalar buna sınavda çok dikkat ederler. Memory leak yaptıysanız bütün sorunuzu çizerler ki koca sınavda 3-4 soru bulunur puanları da 20-30-40 diye gider. O yüzden bu ders çok kolay olduğu halde kalanı çok olur. 

    C++'da pointerlar olduğu ve garbage collection olmadığı için C++ Java'dan daha hızlıdır. (Muhtemelen başka nedenleri de vardır.) Buradan hareketle bir programlama dili karmaşıklaştıkça, yazılımcıdan yükü alıp kendi bir şeyi yapmaya başladıkça onla yazılan programlar yavaşlar diyebiliriz. Bu mantıkla en hızlı kod machine kod veya ona birebir karşılık gelen assembly kodu diyebiliriz.

    Güncelleme: Özelden şöyle bir soru geldi:

    Garbage collector memory leak'lerin etkisini azaltarak kodun hızlanmasını sağlamaz mı? Pointerların C++'ı hızlandırması tamam, ama garbage collector olsa ve yazılımcının gözünden kaçan memory leak'ler de ortadan kaldırılsa bu hızlanma sağlamaz mı? Yoksa memory leak'in hıza bir etkisi yok mu?

    Cevap: Garbage collector arkada çalışırken zaman yer, kodu yavaşlatır. Atıyorum ahmetinArabası = başkaAraba yazdık, hemen altına java otomatik olarak garbage Collector kodu koyar "ahmetin eski arabası çöp mu oldu sileyim mi" diye kontrol eder. Bu kontrol etme işlemi zaman yer. Halbuki iyi bir yazılımcı memory leaksiz kod yazabilir ve garbage collector'un yiyeceği zamandan da tasarruf etmiş olur. O yüzden büyük bilgisayar oyunları C++ yazılır, hızlı olsun diye. Tabii bu zamanda kimse bir bilgisayar oyununu sıfırdan yazmak, oyun motoru kullanır (yer çekimi gibi olayları kodlamaya gerek kalmaz böylece). Oyun motorlarından Unreal Engine'de ve CryEngine'de C++ kullanılmıştır mesela. (Unity C#)

    Veri Yapıları

    Temelde iki veri yapısı vardır: Array ve Linked List (bağlı liste). İkisinin arasındaki fark otobüs ile tren arasındaki fark gibidir; trene vagon ekleyip çıkarabilirsiniz ama otobüsü gövdesini çıkarıp şoförün önüne koyamazsınız. 

    İkisini bir görselle anlatalım:



    Üsttekinde yani arrayde ilk elemana bir pointerımız vardır, diğer elemanlara erişmek için de "Abi arkalara doğru ilerleyelim." deriz. Arrayin elemanları birbirine zamklanmıştır. Linked listte ise vagon misali birbirine iple bağlıdır. 

    Bu durumda örneğin yukarıdaki örnekte arrayin baştan ikinci yani birinci çünkü hatırlarsanız saymaya sıfırdan başlıyoruz sileceğimiz zaman üzerinde bir yazan kutunun içeriğini yani A[1]'in işaret ettiği değeri sileriz ama kutu sonra bomboş kalır öyle, A[1] kaybolmaz yani. A[0]'dan A[2]'ye otomatik olarak atlayamayız. 

    (Aslında "ArrayinBuElemanıSilindiMi()" diye bir fonksiyon tanımlayıp A[1]'i kontrol edip A[2]'ye geçebiliriz ama bu durumda atıyorum A[7] de silinmiş olur, teknik olarak hepsi silinmiş olabilir, dolayısıyla tüm elemanları kontrol etmemiz gerekir. Bu da bize zaman kaybettirir.)

    Dolayısıyla naparız, otobüsteki elemanlara "Abi şoföre doğru geri gelin buradaki eleman buharlaştı." deriz. Yani A[1] = A[2], A[2] = A[3] ... A[8] = A[9] diye ilerleriz. E A[9] ne oldu şimdi? Başta arrayimizin on elemanlı olduğunu belirtip ona göre açtığımız için bilgisayar A[9]'a dokunmaz, A[9] her zaman boş kalır, memory leak oldu.. Dolayısıyla bu problemi aslında 9 elemanlı yeni bir array açıp, A'daki elemanları yeni arraye koyup A'yı silerek çözeriz. (Not: Bu söylediğim C++ için geçerlidir, başka bir dilde A[9]'u kullanılabilir hale getirmek mümkün olabilir.)

    Linked listte ise bunu yapmak çok kolaydır. 0. elemandan 1. elemana ip sarkıyor ya? O ipi al 2. elemana sarkıt. Tabii bunu yaparsan 1. elemana sarkan ip kalmaz, memory leak olur. O yüzden önce 1. elemanı delete komutuyla siler, sonra ipi sarkıtırız. Haa dur, bunu yaparsak, sildiğimiz elemanın üçüncü elemana işaret ettiği ipi de silmiş oluruz!!! Bu sefer üçüncü elemana ve dolayısıyla diğer elemanlara olan erişimimiz kaybolur ve tam bir memory leak skandalına imza atarız. Ama birincinin ipini direkt üçüncüye sarkıtırsak da ikinci elimizde kalıyor?

    Burada ip dediğim şey tabii ki pointerdır!

    Çözümü temiz bir şekilde yazıp anlatıyorum şimdi. 

    Linked listi oluşturalım:

    int birinciEleman = 5;
    int ikinciEleman = 10;
    int üçüncüEleman = 7;

    int* birinciElemanınİpi = &ikinciEleman // Birinci elemanın ipi ikinci elemanın adresine işaret etmiş oldu

    int* ikinciElemanınİpi = &üçüncüEleman 

    (diğerlerini yazmaya gerek görmedim)

    Biz ikinciEleman'ı silmek istiyoruz. 

    Bir tane geçici pointer tanımlayalım.

    int* geçiciPointer = birinciElemanınİpi;
    (aslında bu int* geçiciPointer = &ikinciEleman demek ama normalde ikinci elemanın adresi direkt elimizde olmaz, linked listi tarayarak buluruz.)

    Şimdi ne oldu, artık ikinci elemana işaret eden yedek bir pointerımız olmuş oldu. Artık birinciElemanınİpi, ikinci elemana sarkan tek ip değil. Dolayısıyla birinciElemanınİpi istediğine sarkmakta özgür.

    birinciElemanınİpi = ikinciElemanınİpi;

    Artık ikinciElemanınİpi'nin sarktığı elemanı da biliyoruz, yani ikinciElemanınİpi'ne mecbur değiliz. O zaman ne duruyorsun? Helva yapsanaaa

    delete geçiciPointer; // geçiciPointer ikinci elemana işaret ediyor, böylece ikinci elemanı silmiş olduk.

    Bu örneğin silme işlemine bir örnekti, buradan ekleme işlemini de kendiniz çözebilirsiniz. Bir de bulma işlemi var. Bu üç temel işlem önemli ve işlemlerin yapılışı da hızı da farklı.

    CS 202 - Fundamental Structures of Computer Science II


    Algoritma Hızı ve Verimi

    Nasıl farklı? 

    Dikkat ettiyseniz linked listte bir şey silerken sadece üç dört satır bir şey yaptık. Array'de bir şey silerken ise önce yeni array açtık, sonra da gidip tüm elemanları yeni arrayi attık, her eleman için işlem yaptık.

    Yani;

    Linked list:
    Birinci elemanın ipini üçüncüye bağla.

    Array:
    Yeni array aç.
    Her array elemanı için:
         - o elemanı yeni arraye at.


    On elemanlı bir array/linked list için aradaki fark çok belli olmasa da bir de 6 000 000 elemanlı bir array/linked list düşünün.

    Bu durumda linked listte yapacağım yine bir kereye mahsus ipleri değiştirmekken array'de ise 6 000 000 iş yapmak gerekir.

    Bilgisayar bilimcileri bu durumu fark etmişler ve algoritmaları hız bakımından analiz etmeye başlamışlar. Çeşitli analiz kavramları var ama en popüleri "Worst Case"  dediğimiz en kötü durumdaki hızdır. Bir arrayde eleman silerken olacak en kötü durum o elemanın birinci arrayini silmektir çünkü ardından tüm elemanları beri getirmek gerekir. Bu da altı milyonluk bir arrayde altı milyon tane "küçük işlem" yapmak anlamına gelir.

    Worst case Big O Notation denilen koca O notasyonu ile gösterilir. (Ağlıyordu Alan Turing, Yeter artık, bunları Türkçe'ye çevirme diyordu.) Bir veya birkaç kere yapılan işleme sabit denir. Sabit de 1 sayısı ile gösterilir. Arraydeki eleman sayısı ise N (number) dir.

    Bu durumda eleman silme işlemi

    Linked Listte O(1) zamanda
    Array'de O(N) zamanda çalışır.

    diyebiliriz.

    Diyelim linked listimiz 10 saat sürecek bir işlem yapsaydı ama bu işlem elemana bağlı olarak artmasaydı, fakat array 1 saat işlem + eleman başına 1 dakika işlem yapsaydı, bu sayılar değişmezdi. Çünkü array'de eleman sayısına bağlı olarak katlanarak artarak ve belirli bir eleman sayısından sonra array'den silmek daha yavaş hale gelir.

    Bu mantıkla O(n^2 + 2393n + 204802340234)'lık bir hız bulsak, bunu yuvarlar direkt O(n^2) deriz çünkü n^2 belirli bir sayıdan sonra diğerlerini geçer.

    O zaman benden de size soru, 10 elemanlı bir arrayde 5. elemanı çekmek için kaç işlem yaparız, veya kısaca O notasyonunda hızınız ne olur?

    Cevap:
    .
    .......
    ...................
    ......................................
    .......................................................
    ......................................
    ......................
    ...........
    .




    Cevap soruda gizliydi:




    Array'in beşinci elemanını çekmek çok basit!

    int elmaSayısı = A[5]

    dediğimiz anda 5. elemanı çekmiş oluyoruz zaten.

    Dolayısıyla n. elemanı çekmek arrayde O(1)

    Linked list'te ise işler değişir. Yukarıdaki kutular sıralı değil, birinci elemanın adresi 1. sokaksa diğerinin ki 2. sokak değil Milli İrade sokağı falan olabiliyor. Dolayısıyla bizim ip cambazlığı yapıp pointerları takip ede ede beşinciye gitmemiz gerekiyor.

    Önceki problemi integer pointerı ile anlatmıştım. Burada ise Eleman objesi pointerı yaptım. Eleman objesinin Elemanınİpi pointerı var.

    public class Eleman {
          int elemanınDeğeri = 10;
          Eleman* elemanınİpi = blabla
    }

    yani

    Eleman* şimdikiEleman = birinciEleman;

    deyip sonra aşağıdaki işlemi 4 kere tekrarlamamız gerek:

    Eleman* şimdikiEleman = birinciEleman.elemanınİpi;

    En kötü durumda en sondaki elemana erişmek için N elemanlı bir linked listte N işlem yaparız.

    Cevap O(N)

    Algoritmalar

    Gelecekteki mühendislerin bu algoritma hızı bilgisini kullanarak kaplumbağa hızında yazılım yazmamaya çalışacakları beklenir. Bilgisayar tarihinde hit olmuş bazı algoritmalar öğretilerek hızlarının analizleri yapılır.

    "Dizi sıralama algoritmaları"nın özellikle üzerinde duruluyor.

    Örneğin şöyle bir arrayimiz var:

    2 1 3 6

    Bunu bana verseniz bir bakış atar sonra rasgele sayıları yerleştiririm. Bilgisayar bunları yapamaz, sayıları sıra sıra okur ve her seferinde aynı tekniği kullanır. Peki nasıl olacak bu teknik?

    En basit ama yavaş teknik bu: Selection sort, yani bilgisayar arraydeki en küçük elemanı bulur, en sola koyar, sonra en küçük ikinci elemanı bulur, soldan ikinciye koyar. Tüm elemanlar için bunu tekrarlar.

    Yani:

    1 a) 2'yi okudum. En küçük 2
    1 b) 1'i okudum. Artık en küçük 1.
    1 c) 3'ü okudum. Hala en küçük 1
    1 d) 6'yı okudum. Hala en küçük 1.

    1 ile ilk elemanın yerini değiştir. Array şöyle oldu:

    1 2 3 6.

    Artık ilk elemanı doğru yerleştirdiğimizi biliyoruz, geri kalan üç sayıda da aynı işlemi uygulayacağız.
    Sonuç değişmeyecek ama 2'yi de doğru yerleştirdiğimize emin olacağız. Bir daha, sonra bir daha.

    N'lik bir arrayde ilk adımda N tane okuma ve bir tane swap (takas) yaparız, sonra N-1 okuma bir swap, N-2 okuma bir swap.

    Okuma'ya R, Swap'e S desek.

    R*(N + N-1 + N-2.....................+1) + N*S

    parantezin içindekinin N*(N+1)/2 olduğunu lisedeki toplam sembolünden biliyorsunuz.

    O zaman toplam işlem sayısı R*N^2/2 + R*N/2 + N*S

    N^2'lar hepsini boğar. O(N^2)'de çalışır bu sıralama algoritması.

    Tabii ki açgözlü amarikalılar bununla yetinmeyip daha hızlı algoritma bulmanın yollarını aramışlar ve en sonunda O(N*logN)'de çalışan fıstık gibi iki tane algoritma bulmuşlar, bunlar Quicksort ve Mergesort. Uzun ve şu an için gereksiz olduğu için anlatmayacağım. Merak edenler verdiğim linklerden izlesin.

    Veri Yapıları:

    Bu ders kalanında ve büyük bölümünde veri yapıları incelenir. Bu yapılar arraylerle veya linked listlerle kurulur. İlk önce anlatılan ikinci yazıda anlattığım "Tree"lerdir yani ağaçlar.

    Bir ağacın babası iki tane dalı vardır. Babası olmayan ağaç elemanı köktür, dalı olmayan ağaç elemanına ise yaprak denir (ciddiyim) Örnek ağaç:


    Ağaçlar hızlı ve verimli çalışsın diye amaca özel ağaç tipleri vardır, yani istediğiniz yaprağa eklemeye yapamazsınız. Örneğin yukarıdaki bir "Binary Search Tree" yani arama kurtarma ağacı. Bir ağaç elemanın sağ dalı elemanın tuttuğu sayıdan büyük sayı tutan elemanları içeriyor. Bu durumda 14'ü 13'ün sol dalına asamazsınız, sağ dalına asmanız gerekir. Aynı şekilde 15'in de sağ dalına asamazsanız.

    AVL tree, Red-Black tree bilmemne tree anlatıldıktan sonra Hash Table, Stack, Queue falan anlatılır. Merak edenler araştırabilir.

    Google Mülakatları

    Yukarıda anlattığım şeyler google mülakatlarında sorulmakta. Google size "Windows mu Linux mu" karşılaştır diye nerdce şeyler sormaz, temel şeyleri sorar bakar bu adam üniversitede okuduğunu anlamış mı. Anlamışsa biz bunu eğitebiliriz der.

    Örneğin şu linkteki kardeşimize gelen sorulara bir göz atalım:

    İlk bunu sormuşlar:

    Why should one use merge sort over quick sort and vice-versa?
    Anlamı: Neden merge sortu quick sorta tercih etmeliyiz / veya tersi?

    En sonda da bunu:

    You need to develop the game Snake. What data structures will you use? Code your solution.
    Anlamı: Yılan oyunu yapacaksın, hangi veri yapısını kullanırsın? Kodlayarak göster.

    İnternete "Google Interview Question" yazınca "Data structures, linked list, array" gibi anahtar kelimelerle karşılacaksanız. İngilizceniz varsa "Cracking The Code Interview" kitabına bir göz atın, hep bu konulardan soru geldiğini göreceksiniz. 

    Kapsamlı Bilgisayar Mühendisliği Eğitimi Rehberi 4 - İşlemcinin Anatomisi (Resimler Tekrar Yüklendi)

    Öncelikle iyi bayramlar.

    Aslında bu yazıda algoritmaları anlatacaktım fakat en düşük seviyede programlamanın nasıl bir şey olduğunu anlarsanız sonraki yazıyı daha rahat anlarsınız diye düşündüm, o yüzden 2. sınıfın 2. dönemine atlıyorum şimdi.

    CS224 Computer Organization (Bilgisayar Mimarisi)

    Bu derste işlemcinin int elmaSayısı = 10'u nasıl işlediği anlatılır özetle. 

    Assembly Dili

    Önce hoca bir giriş yapar, adam gibi bilgisayar mühendisi olmak istiyorsanız işlemcinin donanımın nasıl çalıştığını bileceksiniz der. Haklıdır.

    Bilgisayarın tabanında 0 ve 1'ler vardır demiştik, aynı şekilde programlama dili de 0 ve 1'ler den oluşur hesapta. 0 ve 1'lerden oluşan dil en sade dildir, işlemci tarafından direkt okunabilir. Buna machine code denir. Halbuki siz java'da int elmaSayısı = 10; yazınca bilgisayarınızda yüklü java programı bunu machine code'a çevirip öyle okunur. Machine code bilgisayardan bilgisayara farklılık gösterir, o yüzden sizin bilgisayarınızda oluşturulan machine code'u başka bilgisayarda çalışmaz. Bu yüzden her bilgisayarın içinde java programı bulunmak zorundadır ki java dilinde yazılmış dosyalar çalışsın. O yüzden zırt pırt java kur uyarısı gelir.

    int elmaSayısı = 10; aslında tek bir machine code ifadesine karşılık gelmemektedir. Birden fazla machine code ifadesi bulunur. Bu machine codeları kafadan okuyup yazmak zordur ve yapana da deli denir. Machine code ifadelerle birebir eşleşen dile assembly dili denir.

    Bu derste MIPS isimli CPU'ya has dil öğretilir ama burada öğrendiklerinizi başka CPU'ların dilinde de kullanabilirsiniz. MIPS öğretilmesinin sebebi de dersin okutulduğu kitaptaki örneklerin MIPS'te yazılmış olmasıdır diyebiliriz. Önceki bilgiyi pekiştirirsek, MIPS işlemcili bir bilgisayarda MIPS kodu yazmakla aynı bilgisayarda java kodu yazmak aynı şeydir. Fark şu: MIPS yazarken kafayı yersiniz.

    MIPS isimli işlemcinin içinde bir yerlerde 32 tane "Register" isimli hafıza birimi bulunur. Register'ın yapısını anlatmamıştım ama ne olduğunu söylemiştim. Yapısını soyutlayın gitsin, önemli değil. Bir register CPU'nun dizaynına göre bit tutar, 32 bit bilgisayarda 32 bit yani 32/8 = 4 byte (1 integer kadar) tutar. Yeni 64 bit bilgisayarlarda 8 byte tutar. 

    MIPS isimli işlemcide 32 tane register vardır. Bunlar asıl bilgiyi tutmak için değil de geçici işlemler için kullanılır. Örneğin 1. register "0" sayısı için ayrılmıştır registerdır, ismi de zero'dur. $t0 - $t7 geçici değerler içindir. (t for temporary) Diğerlerinin de isimleri farklı farklıdır ve farklı görevler için kullanılır (fonksiyondan dönen registerı, fonksiyona verilen argümanlar için ayrı register vs. vs.)

    (Asıl bilgiyi tutan hafıza birimleri Cache (İşlemcinin içindeki hafıza birimi), RAM ve harddisktir. İşlemcinin hangi sırayla bilgi çekeceğini sonra anlatacağım.)

    Burada uzun uzun bu dili anlatmaya gerek yok. Merak eden MIPS'ı googlelasın.

    Ben int elmaSayısı = 5; ifadesinin MIPS versiyonunu yazayım size.

    Örnek buradan çalıntı

    # da not işareti, yani okuyan insan için yazılmış. java'da bu // idi.

    Not: Bilgisayarın en küçük işlem birimi byte demiştim hani, bir işlemcinin en küçük işlem birimine de word denir. İşlemciden işlemciye fark etmekle MIPS işlemcisinde 1 word 4 bytetır. Yani hafızadan en az bir word çekebilirsiniz, bir byte çekmek isterseniz yanında 3 byte bonus gelir. Bir tam sayının 4 byte olduğunu söylemiştik, e mantıklı o zaman.

    li = load immediate = soldaki registera direkt bi sayı (immediate demişler buna) koy
    sw = store word = soldaki registerın içindekilerini sağdaki değişkene ata ve hafızaya at.
    Yukarıdakilere "instruction" denir.

    example:
     .data                   # Bu "beyler burada değişken tanımlayacağım" demek.
    elmaS: .word   # elmaS değişkenini (wordunu)tanımladım, içine önce 10 koydum.
    
     .text                   # bu da "beyler kodu yazıyorum sıkı durun." demek
    __start: 
     li $t1, 5    #  $t1 = 5   ("load immediate")
     sw $t1, elmaS #  register $t1'dekileri elmaS'a at ve hafızaya depola yani :  var1 = $t1
     done               # assembly'im bitti
    
    Burada uzun uzun diğer instructionları anlatacak halim yok tabii. Buradan çıkarmanız gereken iki sonuç var:

    1- int elmaSayısı = 5 aslında o kadar basit değil.
    2- tek satır olan iki kod aynı basitlikte değil.

    Örneğin yukarıdaki örnekte önce bir registera 5 sayısını yükleyip ondan sonra o sayıyı hafıya atmak zorunda kaldık. İşlemci iki kez çalıştı. Bu mantıkla int elmaSayısınınKaresi = elmaSayısı * elmaSayısı yapsaydık önce elmaSayısını memoryden çekip $t1'e koyacaktık. Ondan sonra $t1 ile yine $t1'i çarpma instructionıyla çarpacaktık ve çıkan sonucu $t2'e koyacaktık. Buradan da bir işlem geldi. En son olarak $t2'yi elmaSayısınınKaresi değişkenine sw kullanarak atacaktık. Etti üç. Demek ki int ElmaSayısınınKaresi işlemi çok işlemci yakan bir işlemmiş.

    Bu örnek kolaydı da, daha karmaşık kodlarda assembly okuyucu gibi düşünmek gerekiyor, işler karışıyor. Bu arada instruction ezberlemeye gerek yok çünkü bu dersin sınavları kitap açık yapılır.

    Assembly dili özetle böyle bir şey. 

    İşlemci Nasıl Çalışır?

    Bu dil öğrenildikten sonra bu dildeki bir instructionın işlemcide nasıl çalışılacağı anlatılır.

    Şöyle bir şeydir:


    Durun! Hemen korkup ekranı kapatmayın! Parça parça anlatınca anlaşılacak.

    Öncelikle şunu söyleyeyim, işlemci bir anda çalışmaz, adım adım çalışır. (MIPS işlemcisinin beş adımı var ama günümüzdeki işlemciler yirmi beş tane falan olabilir.) Her adımın süresi aynıdır ve bu süre de en yavaş adım tarafından belirlenir. Yani en yavaş adım atıyorum 50 pikosaniyede çalışıyorsa tüm adımlar 50 pikosaniyede çalışır ki en yavaş adım geride kalmasın. (1 saniye = 10^12 pikosaniye)

    Yukarıda gördüğünüz yeşil şeritler register, o adım bitene kadar çıkan değerleri tutar. Yani atıyorum Insturction Fetch adımından 5 ve 10 değerleri çıktı, diğerleri de çalışmayı bitirene kadar o IF/ID registerı 5 ve 10'u tutar sonra bu değerleri sonraki adıma paslar.

    Üstteki örnekteki li $t1, 5 instructionını kullanarak anlatayım. Yani işlemci $t1 registerına 5 koyacak.

    Intruction Fetch:

    En soldaki "Address" registerında sıradaki instructionın (bu durumda li $t1, 5'in) adresi bulunur. Bu address Instruction Memory dediğimiz kodumuz yer aldığı hafızaya girer, Memory adresi okur ve o adreste tutulan instructionı sıradaki adıma gönderir. Bu arada adres "Adder" yardımıyla artırılır ki işlemci bir daha çalıştığında sıradaki adres memory'e yüklensin böylece sıradaki instruction işlensin. (Gereksiz ek bilgi: bazı durumlarda, örneğin if-else ve looplarda, işlemcinin hemen altındaki satırın değil başka bir satırı işlemesi gerekir. En soldaki "Adder"dan çıkan kabloyu takip ederseniz göreceksiniz ki bir MUX'a bağlanıyor. MUX seçiyor sıradaki işlemi mi okutayım başka işlemimi. Olay bu.)

    Instruction Decode, Registration Fetch:

    Memoryden li, $t1 ve 5 bilgileri çıktı. $t1 registerın adresi yani kaçıncı register olduğu, bu registerlar derneğine gidiyor yani registerlara "Ben $t1 adresindeki registera yazı yazacam ha!" bilgisi gidiyor. Aynı zamanda 5 sayısı imm olarak çıkıyor. Burada bir sıkıntı var o da şu, 5 sayısı direkt olarak instructionın içinde. Yani instruction aslında 1 word = 4 byte = 32 bitten oluşuyor ya, bunun 5 tanesinde $t1 adresi tutuluyor (Çünkü 32 register var, 2^5 = 32) atıyorum 16'sında ise 5 sayısı tutuluyor. Halbuki bir tam sayı = 4 byte = 32 bit. Yukarıdaki "SignExtend" de bu sayıyı 32 bite çeviriyor.

    Yukarı baka baka şaşı olmayın diye resmi tekrar koyuyorum:



    Execute

    Burada normalde toplama çıkarma yapacaktı ALU abi ama toplama çıkarma yapacak bir şey yok. ALU'ya 5 sayısı girer ve aynen çıkar. $t1'dekiyle 5 toplasaydık ikisini ALU'ya sokar ALU'ya da toplama yap derdik.

    (Aradaki Zero? neydi unuttum)

    Memory
    
    
    Burada da hafızayla ilgili işleri yapıyor. Hafızaya koyacaksa "Memory"e adres ve değer giriyor. Yok hafızadan çekecekse adresi giriyor, Memory'den de değer çıkıyor. 

    Writeback:

    Duruma göre hafızadan çıkan değer registerlara geri yazılıyor. WB Data'dan çıkan oku takip edin.

    İşlemci özetle böyle çalışıyor. Tabii her şeyi söylemedim, yoksa bu yazı uçar gider. Ama şu an bildikleriniz yanlış değil. Ve işlemci hakkında genel kültür edinmiş oldunuz.

    Pipelining

    Pipeline boru hattı demek. Bildiğiniz gibi boruda su küp küp akmaz!! Su akarken arkadan da su gelir, ta ki akacak su kalmayana kadar!!! Yukarıda da aynı mantık. Sadece bir tane instructionın sırayla Fetch, Decode vs. diye beş adımdan geçmesini bekleyip sonra en son yeni instructionı gönderirsek hapı yutarız.

    Onun yerine ilk gönderdiğimiz instruction Fetch'ten Decode'a geçtiğimiz anda yani instructionı göndeririz.

    Bir adım 100 pikosaniye olsa, tek tek gönderdiğimizde bir instruction için 5*100 = 500 pikosaniye harcarız. Bu on instruction için 5000 pikosaniye eder.

    Halbuki on instructionda ilk instruction beşinci adıma geldiğinde tüm adımlar çalışır hale gelir. İşlemci toplam 14 kere aktif olur, 14*100 = 1400 pikosaniyede biter iş. 1400 <<<< 5000 pikosaniye. Kod bir anda 3.5 kat hızlandı. 

    Fakat bu da sıkıntılar çıkarır. Sonradan gelen instruction, önceki instructionın registerına salça olabilir. Veya birinci instruction $t1 registerına yazarken ikinci instruction $t1 registerına okuyorsa, birinci instruction daha $t1'e yazamadan öbürü okumuş olur, sıkıntı çıkar. Bu tip sıkıntılara çeşitli çözümler var, derste de genel olarak bunlar anlatılır. En basit örnek şu: bilgisayarınızdaki java programı (derleyici denir buna aslında) java kodunu 1'lere 0'lara dökerken bu örnekteki gibi aynı register peşpeşe yazılıp okunuyorsa önce kodların arasında başka kodlar sokuşturmaya çalışır, beceremezse araya "nop" diye boş kodlar koyup işlemciyi oyalar. 

    Hafıza

    Üç tip hafıza tipi var diyebiliriz. Birincisi cache, işlemci en yakın olanı dolayısıyla en hızlı çalışanıdır. Oldukça küçüktür, fazla bir şey depolayamaz. Çok pahalıdır. RAM Cache'dan yavaştır ama yine de hızlıdır. Elektrik gidince içindeki bilgiler de gider. Harddisk içlerinden en yavaşıdır. Harddisk adı üzerinde disktir, dönerek çalışır, bilgisayardan vızzzzz sesini duyarsanız bilin ki harddisk çalışıyordur. Dolayısıyla çok da yavaştır. Dünyaları içine alabilir.

    İşlemci hafızadan bir şey çekeceği zaman önce cache'e bakar. Cache'de yoksa (buna cache miss) denir anlar ki iş uzun sürecek. O ara memory'i kapatır başka instructionlara, yani aynı anda farklı instructionlar için bir şeyler çekemez. Yine bulamazsa harddiske gider.

    Bilgilerin cache'te nasıl depolanacağı önemlidir çünkü çok vakit kazandırır. Bilgiler cache'ten tane tane değil bloklar halinde çekilir. Örneğin x marka cache'tan bir word (4 byte) bilgi çekilirse ondan hemen sonra gelen 3 word (12 byte) da peşinden gelir. Atıyorum bir dizinin ilk dört elemanını kullanan bir kod yazdık, cache'in bu özelliği sayesinde dizi[0] diyip ilk elemanı hafızadan istediğimiz anda diğer üç elemanı da istemiş oluruz ve zaman kazanırız.

    Cache'te yer sınırlı olduğundan belli bir kurala göre cache kendi içeriğini çeker. En son kullanılanı sil veya en başta kullanılıp bir daha el sürülmeyenleri sil gibi kurallar vardır. Cache'in hangi kuralda çalıştığını bilip ona göre kod yazmak önemlidir. Örneğin atıyorum cache'imiz sadece 3 dizi/array elemanını depolayabiliyor, sonra en eski kullanılandan itibaren başlıyor. 

    Bu durumda şu kodu yazmak büyük mallık olur:

    int elmalar = new int[4];
    int döngüSayısı = 100; // yüz kere dönsün
    int gereksizSayı = 0;

    while (döngüSayısı > 0) {
       gereksizSayı = elmalar[0];
       gereksizSayı = elmalar[1];
       gereksizSayı = elmalar[2];
       gereksizSayı = elmalar[3];

    }

    Bu durumda şu olacak, cache'e önce elmalar[0], sonra elmalar[1], sonra elmalar[2] depolanacak. Sonra cache'te yer kalmadığı için cache elmalar[0]'ı silip yerine elmalar[3]'ü yazacak. Ardından elmalar[0] için cache'e bakacak, baktı yok, elmalar[1]'i silip yerine elmalar[0]'ı yazacak. Bu böyle gidecek. Hiçbir zaman cache'i kullanamayacağız. 

    Hafıza kısmında özet olarak bu ve bunun gibi mühendislik problemleri anlatılır. Çeşitli cache tipleri verilir ve verilen kodda cache'e ne olur o sorulur, sıkıntıları gidermek için çözüm istenir.

    I/O Cihazları

    Bu kısım "İşlemcinin her bir şeyini anlattık, bari bunu da anlatalım." diye anlatılır, mühendislik yoktur, tamamen ezberdir ve sıkıcıdır. Tam da havaların ısındığı zamana denk geldiği için pek dinlenemez de. Ben kitaptaki örneği değiştirip yazmıştım. Anlatmaya gerek yok, unuttum zaten. Tek hatırladığım, mouse takılan yere karşılık gelen bir değişken var, onun sayılarını falan değiştiriyoruz.

    Tebrikler! Artık işlemci ne biliyorsunuz! 

    Bu derste de haftalık lablar olur ve öğrencilerin anasını ağlatır. MIPS'le java kodu, Verilogla pipeline yazmak oldukça zordur. Ayrıca FPGA ile tetris yazmak gibi zor projeleri vardır. Ben aldığımda ders partnerliydi, partnerim çok zekiydi ve verilogları takır takır yapıyordu, ben MIPS varsa yapıyordum yoksa yatıyordum çünkü önceki dönem partnerim tüm labları arkadaşına yaptırdığı için Verilog öğrenememiştim. Sonra bireysel oldu iyice zorlaştı. Alacaklara geçmiş olsun.

    Kapsamlı Bilgisayar Mühendisliği Eğitimi Rehberi 3 - 0 ve 1'lerden Bilgisayara

    2. Sınıf 1. Dönem

    Müfredatı hatırlayalım.

    HIST200 History of Turkey (Tarih işte) : Sanırım Türk üniversitelerinde olması zorunlu tarih dersinin Bilkent ayağıdır, yalnız yabancı öğrencilere de zorunlu bu :P Bilkent farklı işler bunu, tarih araştırması falan yaptırır, birileriyle röportaj yaparsın, röportaj yapacağın kişi çok yaşlıysa proje bitene kadar ölmesin diye dua edersin vs. En sonunda da makale yazarsın. Geçiyorum.

    HUM111 Cultures Civilizations and Ideas I (Kültürler, Medeniyetler ve Fikirler): Kısaca Humanities diye tabir edilen ve biraz insanlık öğrenin diye verilen bu derste medeniyet tarihi okutulur ve medeniyetten kasıt Batı medeniyetidir. Amerika'da okutuluyor biz de okutalım mantığıyla verilse de çok güzel bilgiler edinebilirsiniz bu dersten. Gılgamış destanıyla (semavi dinlerle alakalı çıkarımlar yapabilirsiniz bu destan) başlar, İlyada ile devam eder (güzel ahlaklı "iyi" ile yiğit, korkusuz "iyi" arasındaki farkı çatışmayı görürsünüz.) En güzeli ise Platon'un "Devlet"idir, Platon çürümüş demokrasinin yerine alternatif yönetim biçimi sunar. Hitabı iyi ve espirili bir hocayla keyifli geçer bu ders.

    PHYS101 General Physics I (Kuvvet ve Hareket): Bildiğimiz lise fiziği. Sıkıcıdır. Tüm mühendislik ve temel bilim bölümleriyle beraber alırsınız.

    CS223 Digital Design

    0 ve 1'lerden bilgisayara evrimi anlatır. Nasıl evriliyor uzun uzun anlatalım.

    Abstraction (Soyutlama)

    Bu dersin en başında "Abstraction"  nedir ondan bahsedilir. Abstraction şudur, siz bir konuda uzman olursunuz ama uzman olduğunuz bazı alt kısımları bilmenize gerek yok. Örneğin mutfakta kek yapmakta anneninizi düşünün. Un, yumurta ve sütü karıştırıp fırınlıyıp kek yapıyor. Anneniz kek yapmayı "biliyor." Anneniz buğday toplamayı biliyor mu? Hayır. Un elemeyi? Hayır. Tavuk besliyor mu? Hayır. İnek sağıyor mu? Hayır. Demek ki anneniz kek yapmayı o kadar da bilmiyormuş. "Ama kek yapmak için bunları bilmeye gerek yok ki hazır alabilirsin." de diyebilirsin. Fakat ya bunları da bilseydi daha güzel kek yapabilirdi dersem?

    Bu dersin başında soyutlama anlatılır ki aslında elektroniğin alanına giren bu derste bilgisayarcılar kendini elektronikçi sanıp elektronlarla transistörlerle kafayı yemesin, bir yazılımcı için gerekenleri bilsin, gerisini soyutlasın. Hayatımda sadece bir kere özel ders verdim onda da bunun dersini verdim, çocuk ıncık cıncık sorular sormaya başladığında mecbur kalıp "O kadarı önemli değil elektronikçilerin işi." diyordum. Çünkü öyle.

    İkilik Tabanda İşlemler

    Dersi anlatmadan önce bunları da hatırlatmakta fayda var, bu derste işlemler ikilik tabandadır. Yani 1 + 1 = 10. Ona göre okuyun :D

    Bunu ve 2 tabanlı işlemleri derste de anlatırlar. Bir tane 1 veya 0'a bit denir. 1 byte sekiz tane bitten oluşur ve en küçük işlem biridir, bir bilgisayar bir byte'ın altında işlem yapamaz. 1 kilobyte 2^10 byte yani 1024 byte'dır ama kolaylık olsun diye 1000 byte denir. 1 megabyte 2^20 bytetır. Böyle gider bu. İndirme yaparken 8 megabit internetle 1024 mb/s'nin üzerine pek çıkamazsınız çünkü 8 megabit internet aslında 1 megabyte internettir. İnternet hızı eskiden bit olarak ölçüldüğü için hızlar hala megabit olarak söyleniyor, tabii bu da TTnet'in işine geliyor :P

    0 ve pozitif tam sayı tutmak kolaydır, üç basamaklı bir sayınız varsa 2^3 = 8 yani 1'den 7'ye kadar sayıları tutabilirsiniz. Peki negatif tam sayıları tutarken ne yapacağız? Bir çözüm şu, ilk basamağı rezervetmek, ilk basamak 1'se negatif değilse pozitif diye okumak. Böylece 000-011 yani 0'dan 3'e kadar ve 100-111 yani -0'dan (???) -3'e kadar toplam 7 sayı tutabiliriz. -0 da bonus gelir. Tabii açgözlü amarigalılar "Ben o 0'ı da kullandırırım." der ve ortaya "Two's Complement Number" diye bir şey çıkar. Bir sayının negatifini bulmak için onun tüm bitlerini tersine çevirir sonra sayıya bir ekleriz.

    Örneğin 10 tabanındaki 1 = 001. -1'i bulmak için önce bunu 110 yapar sonra çıkan sayıya 1 ekleriz 111 olur. Sonra bunu on tabanına çevirmek için ilk basamağı -'le çarparız gerisi normal. Yani -2^2 + 2^1 + 2^0 = -4 + 2 + 1 = -1 olur. Bu işlemi istediğimiz kadar basamakta sayıya uygulayabiliriz. Hepsinde de 111111111.....1 -1'e denk gelir.

    Böylece -4'ten +3'e kadarki sayıları iki tabanında üç basamaklı sayıda gösteriyor olabildik.

    Birinci yazıda gördüğümüz int elmaSayısı'nda int aslında 4 byte yani 4*8=32 basamaklıdır. 31 basamağı normal sayı için, ilk basamak negatif için (yukarıdaki metodla tabii). Yani intle gösterebileceğimiz maksimum sayılar -2^31...0....2^+31 -1 'dir.

    Ondalık sayılar için de metot var da anlatması uzun şimdi, geçiyorum. (Virgülden sonra kaç basamak olduğunu belirliyorsunuz ona göre x*2^-1 + x*2^-2 diye gidiyor)

    0 ve 1

    Peki elektrik nasıl üretiyor bu 0 ve 1'leri. Çok basit: devre elemanına yüksek voltaj gelirse devre elemanı onu 1 diye okur, düşük gelirse 0 diye.
    Elektrik sinyalleri devamlıdır, 4.8V, 3.7V, 0.9V, 1.2V diye gider, 1 ve 0 diye sinyal üretilmez. Fakat okuyan bunları "Ayrık" algılar ve 0 ile 1'lere çevirir. Buna dijital sinyal denir. Az önceki örnekte de devre elemanı 1 1 0 0 diye okur ve bilgisayara bu iletilir.

    Peki ne yapacağız bu 1 1 0 0'larla. 1 ve 0'ları "Logic Gate" dediğimiz mantık kapılarına (diyorum işte İngilizce kullanmak daha iyi diye) sokup mantıklı bir şeyler elde edebiliriz. İki değişken var diyelim X ve Y. Bu durumda X'den 1 içeren dijital sinyal, Y'den 0 içeren dijital sinyal geldiği anda X AND Y'den 0 dijital sinyali yürür. (Mantık kapıları transistörlerle üretilir, bunun da nasıl oluşturulduğu anlatılır ama sonra bir daha kullanılmaz, soyutlanır.)



    AND, NOT, OR (ve, değili, veya) diye sizin de bildiğiniz mantık kapıları yanı sıra XOR (gelen sinyallerin toplamı tek sayıysa doğru çift sayıysa yanlış, örneğin 1 XOR 0 = 1), XNOR, NAND gibi abidik gubidik kapılar mevcut.

    MUX diye bir kapı vardır örneğin, 3 tane değişken girer, bir tanesi hangi değişkenin çıkacağını söyler.

    Decoder vardır, iki değişken girer, dört değişken çıkarır. 00 girerse 0000, 01 0010, 10 0100, 11 1000 anlaşıldı herhalde.

    Bu son iki kapı da and ve orların birleşimidir aslında.

    Sınavlarda da bunları çizersiniz evet. Tablo falan yaparsınız.

    Karışık Mantık Soruları

    Derste lisede gördüğünüz karışık mantık problemleri verilir ve sadeleştirmeniz istenir. Bunun için sonradan "Karnaugh Map" diye bir teknik öğretirler. Mülakatlar dışında bir önemi olmadığı için geçiyorum.

    Finite State Machine (Sonlu-durum Makineler)

    Sonlu durumu olan makinelerdir, Allah'ım zekâ fışkırıyor benden.

    Yukarıdaki mantık kapılarını kullanarak makine yaparsınız özetle. Mantığı şudur: bir makinemiz var bunun durumları var. Mutlu, kızgın, azgın vs. Değişkenlerimiz var. Bu değişkenlerin değerine göre başka durumlara geçiş yapabilir, bir durumda beklerken veya tam geçiş yaparken de "output" basar. Anlaşılmadı, resimli anlatalım.


    Bir oyun geliştirme sitesinden aldığım yapay zekanın makinesi bu şekilde.

    Çizen adam başlangıcı koymamış ama hadi diyelim başlangıç durumu "Wander" olsun yani yapay zeka "Kendince Takıl" modunda. Sonra yapay zekaya yön verecek değişken "Player is near" yani "Oyuncu yakınında" değerini alıyor ve makine "Saldırı" moduna geçiyor, yapay zeka saldırıyor.

    Bunu oluşturmak için "Durumların" tek tek mantık formüllerini yazıyoruz. Durumlar eşitliğin sol tarafında da sağ tarafında da olabiliyor.

    Oyuncu değişkeni tanımlayalım, player is near olunca 1, player is out of sight (oyuncu etrafta değil) olursa 0 olsun.

    Makinenin durumları da

    Wander = 00
    Attack = 01

    (öbürlerini anlatmayacağım)

    Durum1 = birler basamağındaki sayı.

    (öbürlerini anlatacak olsam bir de Durum2 tanımlamam gerekirdi çünkü iki basamak var)

    Durum1 0'sa Durum Wander olur, 1'se Attack olur.

    Yukarıdaki şekilde Durum Wander'ı gösterirken input "Player is Near" gelirse Durum Attack'a dönüşüyor.

    Yani Durum1 0 (WANDER) ve OYUNCU 1 (PLAYER IS NEAR) ise Durum1 1'e çevrilmeli.
    Durum1 1 (ATTACK) ama OYUNCU 0 (PLAYER IS OUT OF SIGHT) ise Durum1 0'a çevrilmeli.

    2 değişkenimiz var yani aslında 2*2 = 4 durumumuz var.

    WANDER ve PLAYER IS OUT OF SIGHT olur ne olacak? Bunu yazmamışlar ama ne olacağı belli, WANDER olmaya devam edecek. (yapay zekanın havayı yumruklamasını istemiyorsanız tabii)

    ATTACK ve PLAYER IS NEAR olursa ne olacak? Yapay zekanın İrlandalı boksçu gibi otele girip dinlenip sonra dövmeye devam etmesini istemiyorsanız bu da ATTACK kalmalı.

    Mantık tablosu yapalım lisedeki gibi.


    Eski Durum1
    Oyuncu
    Son Durum1
    0
    0
    0
    0
    1
    1
    1
    0
    0
    1
    1
    1

    Bu durumda Durum1 ve Oyuncuyu AND kapısına sokup Durum1'e eşitlersek doğru sonucu alıyoruz ama Durum1 0 Oyuncu 1 olunca da Durum1 oluyor.

    Yani formül

    Durum1 = (Durum1 AND Oyuncu) OR (NOT Durum1 AND Oyuncu) oldu.

    Burada Aynştaylığımızı konuşturup "İyi de eski durum ne olursa olsun Oyuncu 1 olunca Durum1 1, Oyuncu 0 olursa Durum1 0 oluyor." diyoruz.

    Dolayısıyla durum formülümüz aslında:

    Durum1 = Oyuncu oluyor.

    Find Aid ve Evade'i kullansaydım daha eğlenceli bir formül bulabilirdim belki, böyle sıkıcı oldu :(

    Bunu mantık kapısı çizerek falan yapabiliriz ama daha fazla uzatmaya gerek yok hiç.

    Basit İşlemler

    Bu and ve orlar birleşir bildiğimiz toplama işlemi yapmaya başlar. Örneğin buna Full Adder denir.


    Burada A ve B toplayacağınız basamaktaki rakamlar, C komşudan gelen birlik, basamağa yazacağımız yeni sayı, Cout ise komşuya gidecek elde var bilmemne.

    S = A XOR B XOR C. Hepsi 1 olursa 1 + 1 + 1 = 3 = 11. S'ye 1 yazarız ama elde var biri de doğru hesaplamamız gerekir.

    Cout yani elde formülü ise (A AND B) OR (B AND C) OR (A AND C) olup yorumu çok basit, eğer herhangi iki 1 ise kesinlikle elde var deriz ve Cout'a 1 yazarız.

    Diğer Basit İşlemler

    Çarpma, bölme, çıkarma vs. hepsinin bir karşılığı bulunur. Bunların karşılıklarını öğrendikten belli bir süre sonra kutu çizim üzerine + koyup onları da soyutlamaya başlarsınız. Sonra ALU diye bir şeyle karşılaşırsınız (Arithmetic Logic Unit) bu ALU'da basit işlemlerin hepsi bulunur.



    Bu ALU'da 8 tane işlem var, yani o işlemleri yapabilecek bir sürü mantık kapısı var içinde full adder dahil. Hangi işlemin yapılacağını OPCODE belirliyor. Sonra iki tane değişkeni sokup cevabı alıyorsunuz.

    İşlemci

    Çeşitli hafıza birimlerimiz var, uzun uzun yapısını anlatmaya gerek yok. Buna bir değişken veriyorsunuz, o değişkenden adresi okuyup değeri dönüyor. Tam nasıl çalıştığını "Computer Organization" dersini anlatırken söyleyeceğim.

    Şimdi bir de hangi işlemi yapacağınızı söyleyen ve yine 0 ve 1'lerden oluşan "Programlama ifadeleri" miz var, bunu Control Unit dediğimiz şey okuyor ve ALU'ya ona göre OPCODE'u yolluyor, hafızanın neresinden (hangi adresten) çekileceğini de o belirliyor.

    I/O yani Input / Output cihazları için arayüz var. Geçen yazıda "Interface"den bahsettiğim ya aynı o mantık. Control Unitimiz monitor.masmaviOl() fonksiyonunu çağırıyor, monitör olarak taktığımız şey her neyse Monitor interface'ini kullandığı için otomatikman masmaviOl() fonksiyonu tanıyıp kendi içinde çağırıyor ve ekranınız bir anda mavi ekran veriyor!

    Tebrikler! Artık işlemcinin ne olduğunu biliyorsunuz! ALU, Memory, (ram ve harddisk değil) I/O Arayüzü ve Control Unit işlemciyi oluşturur!



    Dersin haftalık labları olur ve bu lablarda yukarıda anlattığım şeyleri Verilog dilinde kodlarsınız. (Elektronikçiler aynı dersi alır ve VHDL dilinde kodlar.) Başlarda yaptıklarınızı simüle edersiniz, sonra FPGA adını verdiğimiz programlanabilir cihaza yazarsınız, isterseniz cihazın işlemcisini kendiniz kodlayabilirsiniz, şarkı besteleyip bölümünüzdeki kızlara (varsa tabii) serenad yapabilirsiniz. Şu tip karmaşık işlerle uğraşırsınız yani:


    İlk sene sürekli "dokundurulan" Algoritmaları anlatacağım dördüncü yazıda görüşmek üzere.

    Kapsamlı Bilgisayar Mühendisliği Eğitimi Rehberi 2 - Ayrık Matematik ve Ivır Zıvır

    ENG 102 (İngilizce 2): ENG101 çok tutulunca devamının çekilmesine karar verilir ve ortaya bu ders çıkar. Bu ders öncekinden biraz daha hafiftir, yine dersteki kaynakları kullanırsınız ama bir de ek kaynak bulup ona göre "Research Paper" (makalenin daha kapsamlısı) yazarsınız. Bu kadar.

    MATH 102 (Calculus II): Çift integral öğretilir, bilgisayar mühendisleri için gereksizdir geçiyorum.

    TURK 102 (Türkçe II): Önceki Türkçe'nin aynısı.

    MATH 132 Discrete Mathematics (Ayrık Matematik)

    Ayrık'tan kasıt tam sayılardır, yani sayılar birbirinden ayrıdır, 1 ile 2 arasında kesin çizgiler bulunur. (Devamlı Matematik olsaydı bu olmazdı, 1'den sonra gelen sayı 1+x limit x 0'a giderken olurdu.) Bu temadaki konular dersinin hocasının kafasına göre verilir. Bize Graph Theory, Permütasyon, Kombinasyon, Tümevarım ve Coding Theory dersi verildi. Bu dersler Bilgisayar Biliminin "Teori" alt alanında çalışacaklar için elzem olup Google mülakatlarına girecekler için de hayati önem taşır. Ben pek beceremediğimden ikisini de atlamıştım. Bilgisayar olimpiyatlarına giren arkadaşlar iyi biliyorlardı.

    Permütasyon ve kombinasyonu biliyorsunuz, tümevarım = ispat yöntemi, geçiyorum.

    Coding Theory'de derste öğrendiğimiz ve şu an hatırladığım kısım şuydu, örneğin A noktasındasınız B noktasına tebrik kartı göndereceksiniz. "Ramazan Bayramınız Mübarek Olsun" yazdınız kartı attınız. İyi de ya yolda kartın üzerindeki yazılar silinirse ve yazı bir anda "Ayranınız Mübarek Olsun" olursa? Tabii gerçekte absürt oluyor ama dijital ortamda bu hata çok kötü sonuçlara gebe olabilir. Dolayısıyla bir çeşit şifre kullanarak mesajın doğru gidip gitmediğini test ediyoruz. En basit yöntem gönderilen sayının (sayı 0 ve 1'lerden oluşuyor) rakamları toplamının tek mi çift mi olduğu. Örneğin 10101 bu tek, sonuna bir de 1 ekleyip gönderiyoruz. 101011. Alan kişi bakıyor kendisine 11101 gelmiş ama rakamları toplamı tek. Mesajın bir tarafları oynamış, bana yeniden gönder diyor. Coding theory bu tip şifreleme işleriyle ilgileniyor işte. Bunu da detaylı olarak Kriptografi ve Bilgisayar Ağları dersine öğreniyorsunuz.

    Graph Theory: Bu derste bir sürü hayali grafik türü ve bunların özellikleri ve buna bağlı problemler var, ilkokuldan beri hayali üçgenlerle dikdörtgenlerle uğraşırız ya burada da hayali ağaçlar, çemberler falan var. Grafikler noktalar ve onları birleştiren kenarlardan oluşuyor. Her hangi bir noktadan başlayıp kenarlar üzerinde gidip başladığınız noktaya dönemiyorsanız o grafik bir ağaçtır mesela. Geçebiliyorsanız içinde bir tane çember (circle) vardır. Örnek ağaç:


    Kulağa oldukça tırıvırı gelen bu kavramlar karışık bilgisayar problemlerini kullanmakta kullanılır. Örneğin çok ünlü bir problem olan "Traveling Salesman Problem" yani Gezgin Satıcı problemi. Bu problemde gezgin satıcımızın gitmesi gereken şehirler ve bu şehirlerin arasındaki bağlantı ve bu bağlantıların uzunlukları bulunur. Problem kısaca en kısa yolu sorar. Bu problemi çözmek için önce şehirler nokta, aralardaki uzaklıklar ise kenar olacak şekilde grafik çıkarmak gerekir. Bu probleme bulunan çözümlerin çalışma hızı kestirilemediğinden (yani çözümün sonsuza kadar çalışma ihtimali olduğundan) kestirilsin diye grafik "Minimum Spanning Tree" isimli ağaç türüne çevrilir falan fisman. Sıkılmadıysanız ve bu tip problemlerle uğraşmayı seveceğinize inanıyorsanız bilgisayar mühendisliği çok güzel, gelsene. Sıkılanlar için (ben de sıkıldım) geçiyorum.

    CS 102 Introduction to Programming and Algorithms II : 

    Bu derste sırasıyla: Nesne Tabanlı Yazılım temel ögelerinden Inheritance (Miras) ve Interface (Arayüz), Grafik Arayüzü verilir. Algoritma ve veri yapılarına da şöyle bir dokundurulmaya devam ettirilir.

    Inheritance

    Geçen yazıda GidenAraba objesi oluşturmuştuk ya. Diyelim biz şimdi bir de GidenKırmızıAraba objesi oluşturmak istiyoruz ama zaten bu da bir tür GidenAraba oluşturacağı için her şeyi baştan yazmakla uğraşmıyoruz. Şöyle yaparız:

    public class GidenKırmızıAraba extends GidenAraba {
        String renk = "Kırmızı";
    }

    Böyle yazınca GidenKırmızıAraba GidenAraba'nın tüm değişkenlerini, constructorlarını, fonksiyonlarını miras edinmiş olur.

    Bunun güzel yanı şu, GidenAraba'nın fonksiyonlarını GidenKırmızıAraba'da hatta bir de GidenÖküzArabası objesi yaratırsak onda, kullanabiliriz.

    int hız = GidenKırmızıAraba.tekerleklerinHızıToplamı();

    veya

    int hız = GidenÖküzArabası.tekerleklerinHızıToplamı();

    yalnız bunu yapmak için objeyi

    GidenKırmızıAraba yeniAraba = new GidenKırmızıAraba();

    diye değil

    GidenAraba yeniAraba = new GidenKırmızıAraba(),

    diye oluşturmak gerek ki bilgisayar oluşturduğumuz objenin GidenAraba olduğunu anlayıp fonksiyonu kullanmamıza izin versin.

    Interface

    Interface bir fonksiyonlar listesidir. Evet bu kadar. Örnek interface:

    interface GidenHerhangiBirşey {
        public void hızıNe();
        public void maksimumHızıNe();
    }

    Not: void boş dönmek yani bir şey dönmemek demek. Hatırlarsanız int döneceğimiz zaman oraya int yazardık.

    Bu interface'i kullanan herhangi bir class bunu belirtmek için implements GidenHerhangiBirşey demelidir ve o fonksiyonları tanımlayıp içini doldurmalıdır.

    Örneğin

    public class GidenAraba implements GidenHerhangiBirşey {
         // blablabla
        public void hızıNe() {
            // asdadadasd
        }
        public void maksimumHızıNe() {
            // asdadadasd
        }
    }

    Bir çok öğrenci ilk bakışta niye böyle bir şey yaptığımızı anlayamaz çünkü görünen o ki interface'i ha yazmışsın ha yazmamışsın. Ama interface yine Nesne Tabanlı Yazılım'ın temel taşlarındandır. Örneğin ben mühendis olsam ve bir program yazacak olsam, bu programda olan bir değişiklik on tane farklı ekranı etkilese, şöyle yaparım; ekibime döner arkadaşlar bana on tane ekran yazın ama hepsi bu interface'deki fonksiyonları kullansın derim. Atıyorum doların değişimini kaydeden bir program yazıyorum, bu değişim 1. Televizyon ekranında altyazı geçiyorum, 2. Sitede yayımlanıyor, 3. Teletext'te (liseliler bilmez gerçi) yayınlanıyor. Interface kullanmasaydım ekibimdeki dingolar bu güncellemeyi nasıl yaptıklarına, hangi fonksiyon isimlerini kullandıklarına bakmam gerekirdi ama Interface kullanmışlarsa buna gerek yok.

    interface Ekran{
        public void güncelle();
    }

    Benim kodum:
    televizyonEkranı.güncelle();
    site.güncelle();
    teletext.güncelle();

    Hatta bunu daha da abartabiliriz de çünkü Interface bize Inheritance imkanı sunar. Bir "Ekran" interface'i kullanan ekranlar derneği, aman şey dizisi yaratırız, oradan while döngüsüyle yardırırız.

    Ekran[] ekranlar = {televizyonEkranı, site, teletext};

    int güncellenenEkranlar = 0;
    while (güncellenenEkranlar < ekranlar.length) {
        ekranlar[0].güncelle();
        güncellenenEkranlar++;
    }

    Not: ++ yapınca otomatik olarak sayıyı bir arttırır. yani güncellenenEkranlar 0 ise 1 olur.

    GUI (Grafik Arayüzü)

    Bu derste öğrenciler öğrendiklerini uygulayabilsinler diye proje yaptırırlar, tabii proje de güzel bir şey yapsınlar resimli mesimli olsun isterler, öğrenciler de nereden başlayacağını bilsin diye koca koca profesörler bir web tasarımcı edasıyla GUI anlatır. Avantajı ileride GUI'li iş yaparken mantığını kavramak kolay olur. Burada GUI anlatmanın bir yararı olmayacağı için geçiyorum. Zaten Javanın grafik kütüphanelerinin hiçbirinin yüzüne bir daha bakmadım :)

    Recursion

    Recursion çok zevkli ama hayli zordur. Farklı bir düşünme şekli gerektirir. Recursion sorusunu çözen veya çözemese bile problemin çözümünü gören bir bilgisayar mühendisi adayı keyif almıyorsa kariyerini bir daha gözden geçirmelidir. Biraz net konuştum ama bence öyle.

    Recursion'ı en iyi linkteki ekşi sözlük yazarı anlatmış.

    Geniş özet: Hani fonksiyonlara return diyip döneceğimiz şeyi yazıyorduk ya, istersek fonksiyonun kendisini de yazabiliyoruz.

    public int naber(){
        return naber();
    }

    Böyle olunca fonksiyon sürekli kendini çağırıp duruyor ve belli bir süre sonra bilgisayarımız çöküyor, geçmiş olsun tamire vermeye gidin. Ben şu ana kadar altı bilgisayar harcadım recursion yüzünden.

    Şaka şaka, program duruyor ve "Stack overflow!" diyor.

    Recursion en güzel faktöriyel hesaplamakta kullanabiliriz.

    public int faktöriyel (int sayı) {
       if (sayı == 1)
          return 1;
       else
          return sayı * faktöriyel (sayı - 1);
    }

    Yani şöyle oluyor, önce fonksyion verilen sayı 1 mi diye bakıyor, değilse o sayıyı alıyor ve bir eksiğinin faktöriyeliyle çarpıyor, tabii o faktöriyeli bulmak için bir daha fonksiyona sokuyor. 6 * 5! = 6 * 5 * 4! diye gidiyor.

    Algoritmalar ve Veri Yapıları

    Bu dönem de aslında ikinci sınıf konuları olan bu konulara dokundurma var, ikinci sınıfı anlatırken anlatacağım.