Programlama

puts 'Merhaba Dünya'

  • MİB

  • Bellek

  • Giriş/Çıkış


  • Programı belleğe yükle (İşletim Sistemi)

  • Denetimi programa ver (İşletim Sistemi)

  • Bellekte sırayla çalışan buyruklar


  • Sınırlı sayıda buyruklar → Buyruk kümesi (instruction set)

  • Buyruğu veya işlem sonucunu tutan kayıt alanları → Kaydediciler (registers)

  • Aritmetik ve Mantıksal işlemleri yerine getiren birim → ALB (ALU)


Oyuncak Makine

  • Kaydediciler: sadece 1 tane → Akümülatör (Birikeç)

  • Buyruk kümesi: 14 buyruk


İki sayıyı topla

start  load this
       add result
       store result
       load that
       add result
       store result
       load result
       print
       stop
this   3
that   5
result 0

Kaynak kod

Problemin çözümünü ilgili programlama dilinin sözcük ve kurallarıyla anlatan tarif


MİB'nin anladığı tek dil: makine dili

  • Programın çalıştırılması: Kaynak kodla yapılan tarifin MİB'nin dilindeki buyruklara dönüştürülmesi

Tarifin hayata geçirilmesi ("programın çalıştırılması")

  • Önce kaynak kodun tamamını makine diline çevir → Derleme (compile)

  • Kaynak kodu (tarifi) bir programa girdi olarak vererek tarifteki her cümlenin gereğinin MİB'ne bu program tarafından yaptırılmasını sağla → Yorumlama (interprete)


  • Kaynak kod bir tarifin hayata geçmesi için tek başına yeterli değil

  • Sadece makine dilinde yazılan bir tarif doğrudan yeterli (ki onda bile bir tür işlemeye ihtiyaç var, bk. örnekte yapılan bellek ilklendirmeleri)

  • Bir derleyiciye veya bir yorumlayıcıya ihtiyaç var


Derleme

(Aşırı basitleştirme içerir)

  • Kaynak kodu hedef MİB'in buyruklarından oluşan makine diline çevir

  • Bu işlem program çalıştırılmadan önce bir seferliğine yapılır

  • Derlenmiş biçimdeki program çalıştırılır

  • Bu modelde program işletim sistemi tarafından doğrudan yüklenerek çalıştırılıyor


#include <stdio.h>

static int this   = 3;
static int that   = 5;
static int result = 0;

int main()
{
        result = this + that;

        printf("%d\n", result);

        return 0;
}

Nesne kodu

Object code

  • Derleme sonucunda elde edilen imajı (ör. çalıştırılabilir kipte bir ikili program dosyası) anlatır

  • Kaynak kodun devamında yer alan bir terim

  • Terimde geçen nesneyi "Nesne Yönelimli"deki (Object Oriented) nesne ile karıştırmayın


Yorumlama

(Aşırı basitleştirme içerir; derlemeye göre daha da aşırı)

  • "Yorumlayıcı" programı belleğe yükle

  • Yorumlayıcı kaynak kodu okur; artık denetim yorumlayıcı programda

  • Yorumlayıcı, kaynak koddaki anlamlı çalıştırma cümlelerini (ör. satırlar) sırayla yorumlar

  • Yorumlama? Cümleyi anlamlandır ve gereğini MİB'ne (onun anladığı buyruklarla) yaptır

  • Bu modelde program işletim sistemi tarafından yüklenenen bir yorumlayıcının aracılığıyla çalıştırılıyor


this = 3
that = 5

result = this + that

puts result

Çalışma zamanı

Önemli bir terim: "çalışma zamanı" → runtime

  • Programın çalıştırılması süresince geçen zaman dilimini anlatıyor

  • Derlenen programlarda, derlenmiş program imajının belleğe yüklenip MİB tarafından çalıştırılmaya başlandığı andan, sonlandığı ana kadar geçen süre

  • Yorumlanan programlarda, kaynak kodun yorumlayıcı tarafından çalıştırılmaya başlandığı andan, sonlandığı ana kadar geçen süre


Dinamik programlama dilleri

  • Kaynak kod üzerinde çalışma zamanı dışında yapılan başka işlemler de var

  • Bu süreçler de farklı şekilde adlandırılabiliyor, ör. derleme zamanı (compile time)

  • Yorumlanan bir program dilinde kararlar çalışma zamanında dinamik olarak alındığından bu dillere "dinamik program dilleri" de deniliyor

  • "Dinamik" teriminin karşı tarafındaki terim: "Statik"

  • Bu nedenle kaynak kod üzerinde çalışma zamanı dışında gerçekleşen süreçler genel olarak "statik" terimiyle vasıflandırılıyor

  • Örnek: Statik kod çözümlemesi


Yüksek/alçak seviye diller

Bilinmesinde yarar olan bir terim çifti

  • Bir programlama dilinde sunulan soyutlamalarla ifade kabiliyeti ne kadar yüksek ise dil de o kadar "yüksek seviye" (high-level) bir dil oluyor

  • Karşısındaki terim "alçak seviye" (low-level); soyutlamalar daha az, donanıma daha yakın (ve bir o kadar da denetim olanağı)

  • Yüksek/alçak diyerek dilin kalitesine ilişkin bir sıfat oluşturmuyoruz, bu teknik bir tartışma

  • Bunlar göreceli terimler, mutlak anlamda kullanmayın

  • Örnek: Go, Ruby'ye göre alçak-seviye bir dildir, ama C'ye göre yüksek-seviyelidir

  • Yorumlanan (dinamik) diller derlenen dillere göre hemen hemen daima yüksek-seviyeli


Derleme/Yorumlama

  • "Hesaplama" (computing) süreçlerini anlamak için yararlı

  • Günümüzde artık çok anlamlı terimler değil (bk. JIT, bytecode, garbage collector)

  • Pek çok gerçeklemede "yorumlama" sürecinde bir tür derleme yapılıyor (çalışma zamanında)

  • Derleme bazen doğrudan MİB'i hedeflemiyor, sanal bir MİB hedefleniyor (ör. Java sanal makinesi)


  • Bu terimler programlama dilinin gerçeklemesiyle ilişkili; programlama diline iliştirilecek mutlak bir özelliği anlatmıyor

  • Bir programlama dili, en azından kuramsal olarak, hem derlenen hem yorumlanan biçimde gerçeklenebilir

  • Fakat dil (ortaya çıkışında belirlenmiş) doğası itibarıyla bir tür gerçeklemeyi daha etkin kılar veya bir tür gerçeklemeyi teknik olarak çok zorlaştırır

  • "Derlenen/yorumlanan dil" yerine "Kaynak kodun derlenerek/yorumlanarak çalıştırılması öngörülen dil"

  • Ör. Ruby, Python, Javascript yorumlanarak çalıştırılması öngörülen diller

  • Ör. C, Go, Rust derlenerek çalıştırılması öngörülen diller


Derlenen dil

Avantajlar

  • Çalışma zamanında yorumlama olmadığından (veya minimize edildiğinden) çok daha hızlı

  • Bellek kullanımı daha az

  • Sorunlar program çalışmadan önce (derleme aşamasında) yakalanabilir

  • Lojistiği daha kolay; hedef platform için derlenmiş programın kurulumu yeterli, ayrıca bir yorumlayıcı kurmanıza gerek yok

Dezavantajlar

  • Yazılması daha maliyetli (derleyiciyi mutlu etmek zorundasınız, tip bildirimleri gibi daha ayrıntılı tarifler gerekiyor)

  • Çalışma zamanı üzerinde denetiminiz olmadığından "dinamik" işler çeviremezsiniz

  • (C gibi en azından bir kısım dilde) Çalışma zamanında güvenlik açıkları


Yorumlanan dil

Avantajlar

  • Geliştirme süresi daha kısa (arada zeki bir yorumlayıcı var, daha az lafla çok iş)

  • Çalışma zamanı denetlenebildiğinden "dinamik" işler çevrilebilir

  • Çalışma zamanı denetlenebildiğinden basit güvenlik açıkları yaşanmaz

  • Daha "taşınabilir" (portable); yazdığınız kodun ilgili platformda çalışması için yorumlayıcının o platformda kurulu olması yeterli (fakat bk. lojistik)

Dezavantajlar

  • Daha yavaş

  • Daha fazla bellek tüketimi

  • Çalışma zamanında yaşanan sürpriz hatalar (derlenebilseydi çalıştırmadan önce yakalanabilirdi)

  • Artan lojistik yük (yorumlayıcı kurulumu gerekiyor)


Yorumlanması öngörülen bir dilde programın çalışma süresi ve bellek tüketimini artırmak pahasına, programın geliştirme süresini azaltıyoruz

  • Birim zamanda daha fazla iş

  • Daha çabuk hayata geçen fikirler


("Fakat" ile başlayacak eleştirilere açık bir önerme)

Önerme: Dinamik (yorumlanan) bir dilde geliştirici konforu hedeflenir

  • Sistem kaynaklarını (MİB, bellek vs) daha konforsuz bir durumda tutmak pahasına

Günümüz trendleri

  • Ayrım yine korunmakla birlikte her iki türün en iyi özellikleri dillere eklenebiliyor

  • Yorumlanan dillerde tip bildirimleri

  • Derlenen dillerde çalışma zamanını denetleyen eklemeler (ör. çöp toplayıcı)

  • Teknik olarak geçerli, fakat pratikte hatalı kod parçalarını geliştirme aşamasında yakalayan zengin statik çözümlemeler ("lint"leme)


Değişken

İsimlendirilmiş bellek hücresi

  • Bellek hücresinde (bir tür) veri var

  • Veriye anlamlı bir isimle erişiyoruz


kur = 8.96
dolar = 100.0

tl = kur * dolar

oran = 18.0 / 100
fiyat = 100.0

kdv = fiyat * oran

İsimlendirme

Söz dizimi (sentaks) kuralları

  • İlk karakter İngilizce alfabedeki küçük/büyük harflerden biri veya alt tire (_) olmalı

  • Varsa devam eden karakterlerde ilkine ilave olarak rakamlar kullanılabilir (ama ilk karakter rakam olamaz)


  • Sadece değişkenler değil, metot adları, sabitler, sınıf/modül adları da (Ruby'de bunlar da birer sabit isim) isimlendirmenin kapsamında

  • Bu isimlere genel olarak "tanımlayıcı" (identifier) deniliyor

  • İsimlendirme söz dizimi kuralları → Tanımlayıcı söz dizimi kuralları


Önerme: Uygun isimlendirme kod okunurluğunu çok artırır

  • Her program bir öykü veya (uzunluğuna göre) bir roman

  • İsimler bu öykünün kahramanları

  • Anlamlı isimler öykünün okunmasını kolaylaştırıyor


Türkçe karakterler?

çğıöşü
ÇĞİÖŞÜ
  • ı ve İ'ye dikkat! (i ve I Türkçe'ye özgü değil)
  • Değişken adlarında Türkçe karakter çoğu durumda kullanabiliriz, ama kullanmamalıyız

  • Programlama evrensel bir etkinlik

  • Programlama dillerinin anahtar kelimeleri de İngilizce

  • İsimlendirmeleri enternasyonal yapmakta yarar var; özellikle her dilden geliştiricinin katkı sunabileceği açık kaynak projelerde


exchange_rate = 8.96
usd = 100.0

tl = exchange_rate * usd

tax_rate = 18.0 / 100
price = 100.0

tax = price * tax_rate

İfadeler

  • Değerlendirmeye (evaluation) konu ögeler

  • Değerlendirme? Hesaplama, değer verme

  • Örnek: exchange_rate * usd

  • Bu bir aritmetik ifade

  • Örnek: usd = exchange_rate * usd

  • Bu (aritmetik ifade içeren) bir "atama" (assignment) ifadesi


Her ifade bir değer döner (değerlendirme sonrası)

  • Ruby'de ilkel değerlerin bizzat kendisi de ifade

  • Örnek: 100.0

  • usd = 100.0 atama ifadesinde önce sağ taraf değerlendirilir, dönen değer (100.0) sol taraftaki değişkene atanır


Önerme: Ruby'de her şey bir ifadedir

  • IRB'de girilen bir satır enter tuşu ile yorumlayıcıya gönderilir

  • IRB, satırı bir bütün halde ifade olarak yorumlar

  • İfadenin döndüğü değer #=> ile belirtilir


Aritmetik operatörler

  • Operatör → İşleç

  • Sayısal türde değerleri operatörlerle düzenleyerek dönüş değeri yine sayısal türde olan aritmetik ifadeler kurabiliyoruz

  • Sayısal tür? Tam sayı, Gerçel sayı, Rasyonel sayı

  • Aritmetik operatörler beklediğiniz gibi: +, -, *, /

  • Ayrıca iki operatör: % modülüs ve ** üs alma operatörleri


Sayısal tür

  • Tam sayı ve gerçel sayılar

  • Gerçel sayılarla kurulan ifadelere dikkat! 18.0 / 100 yerine 18 / 100 yazılırsa?


Ruby'de Rasyonel sayıların gösterimi için özel bir söz dizimi kullanılıyor

tax_rate = 18/100r
  • Daha okunur

  • Bunu nasıl kullanacağız? Göründüğü gibi, ör. 18/100r * 100.0


Tür dönüşümleri yapılabilir

  • Değer nesneleri üzerinde çalıştırılacak iki metot: to_i ve to_f

  • Bir değeri tamsayıya çevirmek için to_i, ör. 18.9.to_i #=> 18

  • Bir değeri gerçel sayıya çevirmek için to_f, ör. 18/100r.to_f #=> 0.18

  • Değer bu dönüşümü desteklemeli


Fonksiyonlar

value = Math.sin(0.5236) # 0.5236 ~ Pi/6 ~ 30 derece
  • Matematikte aşina olduğumuz bir trigonometrik fonksiyon: sin

  • <fonksiyon>(girdi listesi) → çıktı

  • Fonksiyonlara giriş değerlerini argümanlar yoluyla iletiyoruz, örnekte 30 derece sin fonksiyonuna iletiliyor

  • Fonksiyon (isminin yansıttığı) hesaplamayı yapıp bir değer dönüyor, örnekte 0.5

  • Ruby'de bir fonksiyona geçirilen argümanlar etrafında parantez kullanmanız her zaman gerekmiyor


Matematiksel fonksiyonlardan bir parça farklı olarak programlamada yazacağınız fonksiyonlar:

  • Hiç argüman istemeyebilir

  • Birden fazla argüman isteyebilir

  • Bir değer dönmeyebilir

  • Dönecekse sadece tek bir değer döner (bazı dillerde, ör. Go, birden fazla değer dönülebilir)


Fonksiyon veya Metot

Ruby gibi Nesne Yönelimli dillerde fonksiyon yerine metot adlandırması tercih ediliyor

  • Bu bir isimlendirme inceliği (bazı nedenleri var, gelecekte daha ayrıntılı değineceğiz)

  • Bundan sonra fonksiyon değil metot diyeceğiz


Metotlarla ilk karşılaşmamız:

puts 'Merhaba Dünya'
  • puts bir metot (yani fonksiyon)

  • Metotlara genel olarak bir nesne üzerinde . operatörüyle erişiyoruz

  • Fakat bu örnekte metot bir nesne üzerinden değil doğrudan çağrılıyor

  • Bu konuya gelecekte değineceğiz


Nesne Nokta Metot notasyonu

Notasyona dikkat edin! 18.to_f #=> 18.0

  • Noktanın solunda bir değer: 18, sağında ise bir metot: to_f bulunuyor

  • Noktanın solundaki "değer" aslında bir "nesne" (object)

  • Nesnelere . operatörü yoluyla bir mesaj iletiyoruz

  • Mesaj → Metot

  • Nesne mesajın gereğini yerine getiriyor (ilgili metot çağrılıyor)


Önerme: Ruby'de hemen her şey bir nesne

  • Nesneleri .<metot> söz dizimiyle uyarıyoruz

İlkel veri türleri

  • Sayısal türler ilkel (primitive) veri türlerinin en yaygın örneği

  • Pek çok programlama dilinde bir diğer önemli veri türü: "dizgi" (string)


Dizgi

message = 'Merhaba Dünya'

puts message
  • Örnekteki 'Merhaba Dünya' değeri bir dizgi (string)

  • Çift tırnak veya tek tırnak kullanabiliriz


who = 'Dünya'
message = "Merhaba #{who}"

puts message
  • Çift tırnakta Ruby dizgi değerini özel olarak yorumlar → "Dizgi Enterpolasyonu" (String Interpolation)

  • #{} arasına istediğiniz karmaşıklıkta bir Ruby kodu yazabilirsiniz

  • Yorumlayıcı #{} arasındaki kodu bir ifade olarak değerlendirir ve dönüş değerini yerine koyar

  • Bu örnekte tek tırnak kullanılsaydı message dizgisi olduğu gibi (literal) yorumlanacaktı


Dizgiler programlama dillerinde çok temel bir veri türü

  • Her bir tespih tanesi bir "karakter" (character, char) olan bir tespih gibi

Karakter

  • Dizgilerin yapıtaşları; kabaca harfler, rakamlar ve noktalama işaretleri

  • Bunlara ilave kontrol karakterleri var: boşluk, satır sonu, sekme gibi

  • Karakterler belirli sayıda bitlik bir bilgiyle kodlanıyor

  • En bilineni 7 bitlik ASCII: American Standard Code for Information Interchange

  • Türkçe gibi dile özgü karakterler ASCII tabloda yok

  • Bunun yerine günümüzde UTF-8 gibi daha evrensel kodlama standartları kullanılıyor

  • Yine de ASCII tabloya hakim olmalısınız (örneğin UTF-8 ASCII'nin bir tür üst sürümü)


ASCII Tablo

Dec  Char                           Dec  Char     Dec  Char     Dec  Char
---------                           ---------     ---------     ----------
  0  NUL (null)                      32  SPACE     64  @         96  `
  1  SOH (start of heading)          33  !         65  A         97  a
  2  STX (start of text)             34  "         66  B         98  b
  3  ETX (end of text)               35  #         67  C         99  c
  4  EOT (end of transmission)       36  $         68  D        100  d
  5  ENQ (enquiry)                   37  %         69  E        101  e
  6  ACK (acknowledge)               38  &         70  F        102  f
  7  BEL (bell)                      39  '         71  G        103  g
  8  BS  (backspace)                 40  (         72  H        104  h
  9  TAB (horizontal tab)            41  )         73  I        105  i
 10  LF  (NL line feed, new line)    42  *         74  J        106  j
 11  VT  (vertical tab)              43  +         75  K        107  k
 12  FF  (NP form feed, new page)    44  ,         76  L        108  l
 13  CR  (carriage return)           45  -         77  M        109  m
 14  SO  (shift out)                 46  .         78  N        110  n
 15  SI  (shift in)                  47  /         79  O        111  o
 16  DLE (data link escape)          48  0         80  P        112  p
 17  DC1 (device control 1)          49  1         81  Q        113  q
 18  DC2 (device control 2)          50  2         82  R        114  r
 19  DC3 (device control 3)          51  3         83  S        115  s
 20  DC4 (device control 4)          52  4         84  T        116  t
 21  NAK (negative acknowledge)      53  5         85  U        117  u
 22  SYN (synchronous idle)          54  6         86  V        118  v
 23  ETB (end of trans. block)       55  7         87  W        119  w
 24  CAN (cancel)                    56  8         88  X        120  x
 25  EM  (end of medium)             57  9         89  Y        121  y
 26  SUB (substitute)                58  :         90  Z        122  z
 27  ESC (escape)                    59  ;         91  [        123  {
 28  FS  (file separator)            60  <         92  \        124  |
 29  GS  (group separator)           61  =         93  ]        125  }
 30  RS  (record separator)          62  >         94  ^        126  ~
 31  US  (unit separator)            63  ?         95  _        127  DEL

Önerme: Ruby'de karakterler özel bir veri türü değildir

  • Ama örneğin C gibi bazı programlama dillerinde çoğunlukla char adında özel bir veri türüdür (Ruby'den farklı olarak C programlama dilinde dizgi veri türü yoktur)

Ruby'de bir karakterin ASCII tablodaki onluk tabanda kodunu öğren: .ord

'a'.ord
' '.ord
"\n".ord
"\t".ord

Onlu tabanda verilen bir kodu karakteri içeren dizgiye çevir: .chr

97.chr

Özel karakterler

  • "\n" → Satır sonu
  • "\t" → Sekme
  • Bunlar en yaygınları, bunların dışında ters bölü karakteriyle nitelendirilen başka kodlar da var

Beyaz boşluk (whitespace)

  • Kabaca; boşluk, satır sonu ve sekme karakterlerine deniliyor (ama başkaları da var)

  • Dizgi içinde kullanılmadığında, kaynak kod ayrıştırılırken bu karakterler göz ardı edilir veya kodun söz dizimsel olarak farklı parçalarını birbirinden ayırır


Temel bazı dizgi metotları

string = gets

puts string.size # string.length
puts string.empty?
puts string.chomp
puts string.chop
puts string.upcase
puts string.downcase
puts string.capitalize
puts string.tr '_', ' '
puts string.delete '_'
puts string.strip
puts string.start_with? '2021'
puts string.end_with? '2021'
puts string.delete_prefix '2021'
puts string.delete_suffix '.rb'

String birleştirme ("concatenate")

string = ''

string << 'Cezmi'
string << ' '
string << 'Seha'

puts string

# frozen_string_literal: true?

  • Bu bir pragma

  • Kaynak koddaki tüm dizgi literallerini öntanımlı olarak "değiştirilemez" yapıyor

  • Bu sayede aynı dizgi literali için bellek ayırmak gerekmiyor

  • Yorumlayıcıya verdiğiniz açık sözün denetlenmesi sağlanıyor (hata yakalama)

city = 'Samsun'.freeze
city << '55' # hata

yerine

# frozen_string_literal: true

city = 'Samsun'
city << '55' # hata

# frozen_string_literal: true yapılırsa birleştirmeler nasıl?

  • IRB'de sorunu görmeyebilirsiniz (görmek için RUBYOPT="--enable-frozen-string-literal" irb)
string = String.new '' # string = '' yerine

(Dikkat! String.new'i argümansız çalıştırırsanız karakter kodlaması ASCII oluyor)


Akış denetimi

  • Kod akışını farklı kod yollarına bölen koşul deyimleri

  • Kod bloklarının etkinleştirilmesi belirli koşulların sağlanmasına bağlanıyor


Örnek: Kuadratik denklemde çözümü

Katsayıları verilen kuadratik (İkinci derece) bir denklemde çözüm var mı?

Diskriminant pozitif olmalı (alan bilgisi)

a, b, c = 1.0, 0.0, 1.0

delta = b ** 2 - 4 * a * c

if delta >= 0.0
  puts 'Çözüm var'
end

  • if, end birer anahtar kelime

  • Koşul ifadesi: delta >= 0.0 aritmetik karşılaştırma içeren bir mantık (lojik) ifade

  • Aritmetik karşılaştırma operatörü >= "büyük veya eşit"

  • Gerçel sayı karşılaştırmalarını böyle yapmayın, sorunu görebiliyor musunuz?

  • İlk satırda paralel atama yapılıyor (kötüye kullanmayın)


Gövdesi tek satır olan if deyimlerini tek satırda yazabiliyoruz

a, b, c = 1.0, 0.0, 1.0

delta = b ** 2 - 4 * a * c

puts 'Çözüm var' if delta >= 0.0

a, b, c = 1.0, 0.0, 1.0

delta = b ** 2 - 4 * a * c

puts 'Çözüm var' unless delta < 0.0

  • Yeni anahtar kelime: unless

  • Negatif lojik için kullanılıyor

  • Özellikle ! değillemeleri içeren basit ifadelerde yararlı

  • Okunurluğu (yerine göre) bir parça arttırıyor


Örnek: Katsayıları verilen kuadratik denklemin gerçel kökleri neler?

a, b, c = 1.0, 0.0, 1.0

delta = b ** 2 - 4 * a * c

if delta >= 0.0
  delta_sqrt = Math.sqrt(delta)

  p, q = (-b - delta_sqrt) / 2 * a, (-b + delta_sqrt) / 2 * a

  puts "Kökler: (#{p}, #{q})"
else
  puts 'Çözüm yok'
end
  • Yeni anahtar kelime: else

Aritmetik Karşılaştırma İşleçleri

İşleçAçıklama
>Büyüktür
>=Büyük eşittir
==Eşittir
<Küçüktür
<=Küçük eşittir

Örnek: Verilen 3 sayı geçerli bir üçgenin kenar uzunlukları mı?

Üçgen kuralı (alan bilgisi): Sayılardan herhangi ikisinin toplamı üçüncüden daima büyüktür

a, b, c = 3, 4, 5

if a + b > c && a + c > b && b + c > a
  puts "Geçerli üçgen"
else
  puts "Geçerli üçgen değil"
end
  • Koşulda mantıksal (lojik) bir ifade, önermeler && "ve" mantık operatörüyle bağlanmış

  • Önermelerin her biri aritmetik karşılaştırma, > "büyüktür"


Mantıksal İşleçler

İşleçAçıklama
&&VE (AND)
||VEYA (OR)
!DEĞİL (NOT)

Örnek: Kullanıcıdan bir tam sayı iste

print 'Lütfen bir sayı girin: '
string = gets.chomp

if string == ''
  puts 'Hiç bir şey girmediniz.'
elsif (number = Integer(string, exception: false))
  puts "Girdiğiniz sayı #{number}"
else
  puts "Geçersiz sayı girdiniz: #{string}"
end
  • Yeni anahtar kelime: elsif, çoklu koşul deyimleri

  • Integer(string, exception: false) hatalı dönüşümde nil değeri dönüyor

  • Koşul ifadesi içinde atama yapabilirsiniz (kötüye kullanmayın), yeter ki parantezlerle niyetinizi açık hale getirin

  • Boş dizgi denetimi daha deyimsel nasıl yapılabilir?

  • Kullanıcı bir veya daha fazla sayıda beyaz boşluk girmişse?


nil

Düpedüz yokluğu veya geçerli bir değerin yokluğunu anlatan "sözde değer"

  • Mantıksal bağlamda false ile benzer sonuçlar üretiyor

  • Yani bu bir "falsy" değer

  • Diğer dillerde de kısmen benzer değerler var; ör. C, C#, Java'da null


Doğruluk/Yanlışlık

Basit iki kural

  1. Ruby'de değeri false ve nil olan her ifade yanlıştır

  2. Yanlış olmayan her şey doğrudur


number = Integer('geçersiz', exception: false) #=> nil

if number
  puts 'Doğru'
else
  puts 'Yanlış'
end

Bazen nil değerini açıkça denetlemeniz gerekebilir

number = Integer('geçersiz', exception: false) #=> nil

if number.nil?
  puts 'Evet: nil'
end

Yeri gelmişken... "Ruby'de her şey bir ifade" demiştik, örneği inceleyelim

flag =
  if x == 0
    false
  else
    true
  end
  • Herşeyin ifade olmadığı pek çok dilde böyle bir kod yazamazsınız (if pek çok dilde bir ifade değil bir deyimdir)

flag =
  if x == 0
    false
  else
    true
  end

Bu kodu basitçe şöyle yazabilirdiniz

flag = x == 0

Metot

İsmiyle çağrılarak çalıştırılabilir (bir veya çoğunlukla birden fazla satırlık) kod parçası

  • Farklı girdilerle tekrar tekrar yapılan hesaplamalar için her seferinde aynı kodu yazmanız gerekmiyor

  • Hesaplama girdileri çağırma zamanında verilen parametrelerle değiştirilebilir


Örnek: Katsayıları verilen kuadratik (İkinci derece) bir denklemin gerçel köklerini bul

def calculate_roots(a, b, c)
  delta = b ** 2 - 4 * a * c

  if delta >= 0.0
    delta_sqrt = Math.sqrt(delta)

    p, q = (-b - delta_sqrt) / 2 * a, (-b + delta_sqrt) / 2 * a

    puts "Kökler: (#{p}, #{q})"
  else
    puts 'Çözüm yok'
  end
end

a, b, c = 1.0, 0.0, 1.0

calculate_roots(a, b, c)

  • Yeni anahtar kelime: def

  • a, b ve c metot argümanları

  • Çağırma zamanında metoda bu argümanlarla değerleri geçiriyoruz


  • Metot argümanlarıyla çağırma zamanında kullanılan değişkenlerin aynı isimde olması gerekmiyor

    a2, a1, a0 = 1.0, 0.0, 1.0
    
    calculate_roots(a2, a1, a0)
    
  • Değerleri hiç bir değişken kullanmadan da geçirebiliriz

    calculate_roots(1.0, 0.0, 1.0)
    

Örnek: Verilen 3 sayı geçerli bir üçgenin kenar uzunlukları mı?

def validate_triangle(a, b, c)
  if a + b > c && a + c > b && b + c > a
    puts "Geçerli üçgen"
  else
    puts "Geçerli üçgen değil"
  end
end

validate_triangle(3, 4, 5)

Metotlar çoğunlukla bir hesap yaptıktan sonra bize bir sonuç döner

  • Her iki örnekte de bir sonuç dönmedik

  • Son örnekte aşama aşama giderek gösterelim


def validate_triangle(a, b, c)
  if a + b > c && a + c > b && b + c > a
    return true
  else
    return false
  end
end

if validate_triangle(3, 4, 5)
  puts "Geçerli üçgen"
else
  puts "Geçerli üçgen değil"
end

  • Yeni anahtar kelime: return

  • Kullanıldığı noktada metotu sonlandırarak verilen değeri çağıran tarafa dönüyor


Her metot tek bir iş yapmalı

  • İlk örnekte bu kural nasıl ihlal edilmiş?

  • Ruby zaten true/false hesabını yapıyor, biz ayrıca neden hesap ediyoruz?

  • Ruby'de metottan çıkarken etkin olan son satır aynı zamanda dönüş değeridir

  • Çoğu zaman return ile açık dönüş yapmamız gerekmez

  • Ruby'de return deyimini "erken çıkış"lar için kullanın


def validate_triangle(a, b, c)
  a + b > c && a + c > b && b + c > a
end

if validate_triangle(3, 4, 5)
  puts "Geçerli üçgen"
else
  puts "Geçerli üçgen değil"
end

Örnek: Kullanıcıdan bir tam sayı iste

def getnum
  print 'Lütfen bir sayı girin: '
  string = gets.chomp

  if string.empty?
    puts 'Hiç bir şey girmediniz.'
  elsif (number = Integer(string, exception: false))
    puts "Girdiğiniz sayı #{number}"
  else
    puts "Geçersiz sayı girdiniz: #{string}"
  end

  number
end

İsimlendirmeler çok önemli

  • Ruby'de metot adlarının sonunda ? ve ! karakterlerini kullanabilirsiniz

  • true veya false değer dönen metotlara "predicate method" diyoruz

  • ? sonlandırma karakteri bir metotun "predicate" olduğunu nitelendirmekte kullanılan bir konvansiyon

  • Bu sadece bir konvansiyon, metot adının sonunda ? karakteri olunca sihirli bir işlem gerçekleşmiyor

  • İsimlendirmeleri çok daha anlamlı yapıyor


Örnek: Katsayıları verilen kuadratik (İkinci derece) bir denklemde çözüm var mı?

Diskriminant pozitif olmalı (alan bilgisi)

def has_solution?(a, b, c)
  (b ** 2 - 4 * a * c) >= 0.0
end

if has_solution?(1.0, 0.0, 1.0)
  puts "Çözüm var"
else
  puts "Çözüm yok"
end

def triangle?(a, b, c)
  a + b > c && a + c > b && b + c > a
end

if triangle?(3, 4, 5)
  puts "Geçerli üçgen"
else
  puts "Geçerli üçgen değil"
end

Üçlü operatörü

Ternary operatörü

def has_solution?(a, b, c)
  (b ** 2 - 4 * a * c) >= 0.0
end

puts "Çözüm #{has_solution?(1.0, 0.0, 1.0) ? 'var' : 'yok'}"

def triangle?(a, b, c)
  a + b > c && a + c > b && b + c > a
end

puts "Geçerli üçgen#{triangle?(3, 4, 5) ? '' : ' değil'}"

Kapsam

a, b, c = 1.0, 0.0, 1.0

def calculate_roots(a, b, c)
  delta = b ** 2 - 4 * a * c

  if delta >= 0.0
    delta_sqrt = Math.sqrt(delta)

    p, q = (-b - delta_sqrt) / 2 * a, (-b + delta_sqrt) / 2 * a

    puts "Kökler: (#{p}, #{q})"
  else
    puts 'Çözüm yok'
  end
end

calculate_roots(a, b, c)

puts delta #=> ?

Metotlar dışarıya kapalı bir kutu gibi davranır

  • Metot gövdesi bir kapsam ("scope") belirler: yerel kapsam ("local scope")

  • Yerel kapsamdaki bir değişken dışarı sızmaz (ör. delta)

  • Benzer şekilde metot dışındaki hiç bir değer argümanlar yoluyla verilmedikçe içeri sızmaz

  • Metodun dış dünyayla yegane kontak noktaları: giriş argümanları ve dönüş değeri


İsimlendirilmiş argümanlar

def calculate_roots(a:, b:, c:)
  delta = b ** 2 - 4 * a * c

  if delta >= 0.0
    delta_sqrt = Math.sqrt(delta)

    p, q = (-b - delta_sqrt) / 2 * a, (-b + delta_sqrt) / 2 * a

    puts "Kökler: (#{p}, #{q})"
  else
    puts 'Çözüm yok'
  end
end

calculate_roots(a: 1.0, b: 0.0, c: 1.0)

  • Veriliş sırasıyla anlamlandırılan argümanlar: "pozisyonel argümanlar"

  • Argümanları veriliş sırasıyla değil de isimleriyle belirtsek?

  • Özellikle birden fazla sayıda argüman geçirmemiz gerektiğinde yararlı

  • Neyin ne olduğunu çağırma zamanında karıştırmamış oluyoruz


Öntanımlı argümanlar

def calculate_roots(a: 0.0, b: 0.0, c: 0.0)
  delta = b ** 2 - 4 * a * c

  if delta >= 0.0
    delta_sqrt = Math.sqrt(delta)

    p, q = (-b - delta_sqrt) / 2 * a, (-b + delta_sqrt) / 2 * a

    puts "Kökler: (#{p}, #{q})"
  else
    puts 'Çözüm yok'
  end
end

calculate_roots(a: 1.0, c: 1.0)

calculate_roots

Öntanımlı argümanlar "pozisyonel argümanlar" için de geçerli

def calculate_roots(a = 0.0, b = 0.0, c = 0.0)
  delta = b ** 2 - 4 * a * c

  if delta >= 0.0
    delta_sqrt = Math.sqrt(delta)

    p, q = (-b - delta_sqrt) / 2 * a, (-b + delta_sqrt) / 2 * a

    puts "Kökler: (#{p}, #{q})"
  else
    puts 'Çözüm yok'
  end
end

calculate_roots

calculate_roots(1.0, 0.0, 1.0)
  • İlk çağrıda işe yaradı

  • Fakat ikincide işe yaramıyor

  • Zorunlu olarak tüm argümanları girmek zorunda kaldık, neden?


İkinci derece denklem örneğinde bir sorun daha var

  • "Bir metot tek bir iş yapmalı" kuralı ihlal edilmiş

  • Bunu düzeltmek şu aşamada zor

  • Ruby'de metotlar sadece tek bir değer dönebilir

  • Birden fazla değeri tek bir değer halinde dönmek gerekiyor

  • Bunun yolu? Diziler


Döngü

Bilgisayarın en temel kabiliyeti: bir işlemi tekrar tekrar yapabilmek


Örnek: Kullanıcıdan geçerli bir tamsayı al

01  def getnum
02    print 'Lütfen bir sayı girin [ENTER sonlandırır]: '
03
04    while !(string = gets.chomp).empty?
05      number = Integer(string, exception: false)
06      if number
07        return number
08      end
09
10      print "Geçersiz sayı: '#{string}'.  Lütfen tekrar girin: "
11    end
12
13    nil
14  end

  • Yeni anahtar kelime: while

  • Çoğu durumda "... oldukça/olmadıkça" veya "... olduğu sürece/olmadığı sürece" gibi okuyabilirsiniz


Sözde kod

Girdi al, bu boş bir dizgi olmadığı sürece
  dizgiyi tamsayıya çevir
  eğer dönüşüm geçerli ise tamsayıyı dön

  hata görüntüle

def getnum
  print 'Lütfen bir sayı girin [ENTER sonlandırır]: '

  until (string = gets.chomp).empty?
    number = Integer(string, exception: false)
    return number if number

    print "Geçersiz sayı: '#{string}'.  Lütfen tekrar girin: "
  end

  nil
end

  • Yeni anahtar kelime: until

  • if/unless ilişkisine benzer şekilde while/until

  • Olumsuz lojik için kullanılıyor

  • Basit ifadeler kullanıldığı sürece okunurluğu bir parça arttırıyor


Örnek: Sayı tahmini

Verilen bir aralık içinde belirlenmiş bir tamsayıyı tahmin et


İyileştirmeler

  • Kod tekrarını nasıl önleriz?
  • Döngü üzerinde tam denetim nasıl kurarız?
  • Kullanıcıya ipucu verebilir miyiz? "Büyük/küçük" gibi
  • Maksimum deneme sayısını sınırlayabilir miyiz?
  • Sayı aralığını değişken yapabilir miyiz?
  • UX: Her tahminde deneme sayısını da kullanıcıya bildirebilir miyiz?
  • Sayı aralığı ve maksimum deneme sayısını program çalıştırılırken girebilir miyiz?

Yeni

  • Yeni anahtar kelime: loop: açık uçlu döngüler kurmaya yarıyor
  • Yeni anahtar kelime: break: döngü sonlandırmaya yarıyor

  • break anahtar kelimesi while, until ve gelecekte göreceğiniz tüm döngülerde, döngüyü kırmak için kullanılır

  • loop'a özgü değil


  • Sabitler: büyük harf ile başlayan (ve çoğunlukla hepsi büyük harften oluşan) tanımlayıcılar

  • ARGV: komut satırı argümanlarını tutan (sabit isimli bir) dizi

  • ||= öntanımlı değer atama özdeyişi

  • STDIN: standart girdiya karşı düşen sabit

  • STDIN.gets: daima standart girdiden (ör. klavye) okuma yapan özdeyiş


Diziler

Birbiriyle ilişkili (bir küme oluşturan) değerleri barındıran veri türü

days = ['pazartesi', 'salı', 'çarşamba', 'perşembe', 'cuma', 'cumartesi', 'pazar']
  • Kümedeki her değere tek bir tanımlayıcı üzerinden erişiyoruz, nasıl?

  • İndis kullanarak, ilk değerin indisi 0, her seferinde 1 artıyor

days[0]   #=> 'pazartesi'
days[1]   #=> 'salı'
days[6]   #=> 'pazar'
days[7]   #=> nil
days[100] #=> nil

Pek çok programlama dilinde saymaya 0'dan başlanır, 1'den değil

  • Bu durumda son elemanın indisi ne oluyor? <dizi uzunluğu> - 1

Söz dizimi

  • Birden fazla satıra yayabiliriz

    days = [
      'pazartesi',
      'salı',
      'çarşamba',
      'perşembe',
      'cuma',
      'cumartesi',
      'pazar',
    ]
    
  • Son elemandan sonra da , kullanmaya izin var (ama topluluk stilinde hoş bakılmıyor)

  • Özel olarak dizgilerden oluşan dizileri ilklendirmek için %w[] kullanabiliriz

    days = %w[pazartesi salı çarşamba perşembe cuma cumartesi pazar]
    

Değer atama

Basitçe ilgili indisteki elemana değer atamamız yeterli

days[0] = 'monday'

Temel metotlar

  • Dizi uzunluğu: days.size veya days.length (eşdeğer)

  • İlk eleman: days.first

  • Son eleman: days.last


Negatif indisler

Diziye sondan erişmek için kullanılıyor

days[-1]         #=> 'pazar'
days[-2]         #=> 'cumartesi'
days[-7]         #=> 'pazartesi'
days[-days.size] #=> 'pazartesi'
days[-8]         #=> nil
days[-100]       #=> nil

Dizi sonuna yeni eleman eklemek

Çok sık yaptığımız bir işlem

a = []
a << 3
a << 5
a << 9
a #=> [3, 5, 9]

Dizinin başına (veya seçilen bir indisten önce/sonraya) elema eklemek? Düşündüğünüz kadar sık ihtiyaç duyulacak bir işlem değil

a = []
a.unshift 3
a.unshift 5
a.unshift 9
a #=> [9, 5, 3]

Dizgiden diziye, Diziden dizgiye

split ve join: bu metot çiftine özel bir yer ayırıyoruz

  • split: bir dizgiden (tekil, skalar) bir dizi (çoğul, vektör) üretiyor

  • join : bir diziden (çoğul, vektör) bir dizgi (tekil, skalar) üretiyor

name   = 'Ahmet Yılmaz'
names  = name.split      #=> ['Ahmet', 'Yılmaz']
dashed = names.join('-') #=> 'Ahmet-Yılmaz'

Dizide dolaşmak

En sık yapılan işlem, bir tür döngü

  • Şu ana kadar öğrendiğiniz döngü deyimleriyle yapılabilir

  • Ama bunu yapmayın, Ruby'de dizilerde dolaşmak için çok daha güçlü yöntemler var


each

Ruby'de Enumerable modülünde tanımlı olan ve dizi türündeki tüm nesnelerin cevap verdiği bir metot

days.each do |day|
  puts day
end

Ruby'de dizilerde dolaşmak için daima each ve daha sonra gösterilecek Enumerable metotlarını kullanın

  • while, until, loop (ve anlatmaya gerek görmediğimiz for) gibi döngü deyimlerini kullanmayın

  • Bu deyimlere çoğunlukla dizi içermeyen düzensiz döngülerde ihtiyaç duyacaksınız

  • Bu uyarı özellikle diğer programlama dillerinden bilgi transfer edeceklere önemli


each_with_index

Dolaşırken indis bilgisine ihtiyacımız varsa?

days.each_with_index do |day, i|
  puts "#{i}: #{day}"
end

Bloklar

Metotlara geçirilen eylemler

  • Daha teknik bir anlatımla isimsiz (anonim) işlevler

  • Ruby'nin çok önemli bir özelliği


Blokları anlayabilmemiz için hikayede biraz geriye gitmek zorundayız

  • Ruby'de hemen her şey akıllı bir "nesne"

  • Uygun metotlarla uyararak nesnelerin istediğiniz davranışı göstermesini sağlayabilirsiniz

  • Örneğin dizgiler birer nesne

    irb(main):001:0> 'This is a test'.length
    => 14
    irb(main):002:0> 'This is a test'.upcase
    => THIS IS A TEST
    
  • Sayılar da öyle

    irb(main):003:0> 3.times { puts 'Test' }
    Test
    Test
    Test
    => 3
    

Son örneğe yoğunlaşalım

3.times { puts 'Test' }
  • 3 bir tamsayı nesnesi, times bu nesnenin bir metotu

  • Öyle ki bu metota hangi eylemi tekrarlayacağını bildirebiliyorsunuz

  • Nasıl? Eylemi gerçekleyen bir kod bloğuyla

    { puts 'Test' }
    

  • Kod bloklarını {...} yerine do ... end ile de yazabiliriz

    3.times do
      puts 'Test'
    end
    
  • Stil olarak tek satırlık bloklarda kıvrık parantezler, birden fazla satıra yayılan kod blokları için do ... end tercih ediyoruz


Metafor

  • İngiltere kraliçesi Türkiye'ye resmi ziyaret yapacak; Kraliçenin ülkede bulunduğu sürede yemekleri nasıl olacak?

  • 1: İngiliz protokolü Dışişleri protokolüne kraliçenin ülkedeyken yiyeceği yemeklerin listesini veya tarifini iletebilir

  • 2: Kraliçe yemekleri yapacak özel ahçısını bizzat yanında getirebilir

  • İlki klasik yöntem, bir metota (ör. yemek_hazırla) veri girilmesi (yemek listesi veya tarifler)

  • İkincisinde ise metota bir eylem veriliyor, ahçının eylemleri


Bir işleve eylemde ihtiyaç duyacağı bilgileri argümanlar üzerinden geçirebiliriz.

puts 'Test'
  • puts: Neyi görüntüleyeyim?

  • Çağıran: "Test" dizgisini


Aynı diyaloğu times için kurgulayalım.

  • times: kaç defa ne yapacağım?

Ama bu soru hatalı.

  • times metodu uyarılırken kaç defa bilgisini zaten alıyor, nasıl?

    3.times ...
    
  • Diyalog times ile değil 3 tamsayı nesnesi arasında gerçekleşmeli


Diyalog:

  • 3: Ne istiyorsun?

  • Çağıran: Sen defa (yani 3 defa) bir şey yapmanı.

  • 3: Tamam, ben defa ne yapacağım?

  • Çağıran: puts 'Test' ('Test' dizgisini görüntüle).


Sonuçlar:

  • Nesnelere sadece veri değil eylem de bildirilebiliyor

  • Bu sayede çeşitlenebilir davranışlar elde edebiliyoruz

  • Tekrarlama eylemiyle (times), tekrarlanacak eylemi (puts "Test") ayırıyoruz

  • times bir metot, puts 'Test' ise bu metota geçirilen bir blok


Bloklar metodumsu (veya fonksiyonumsu) şeyler

  • Blok argümanları (varsa) |...| karakterleri arasında (metotlardaki parantezler)

  • Blok dönüş değeri metotlardaki gibi etkin olan son satır

  • Dikkat! Bloğun dönüş değerini bloğu alan metotun dönüş değeriyle karıştırmayın

  • Nihai "dönüş değeri"ni bloğun verildiği metot belirliyor

    3.times { 'Merhaba' } #=> 3 döner
    

  • Bloklarda da erken çıkış için (return yerine) break veya next tercih ediyoruz

%w[samsun istanbul izmir adana].each do |city|
  next if city.include? 'a'

  puts city
end
%w[samsun istanbul izmir adana].each do |city|
  break unless city.include? 'a'

  puts city
end

Blok kapsamı

"Bloklar metodumsu (veya fonksiyonumsu) şeyler" demiştik

  • Blok içinde tanımlanan bir değişken bloğa özgüdür, blok dışına çıkamaz

    %w[samsun istanbul izmir adana].each do |city|
      next if city.include? 'a'
    
      puts city
    end
    
    puts city
    
  • Bu kod neden hata veriyor?

  • Düzeltmek için ne yapılabilir?


  • Bazen bir blokta üretilen bir değeri dış kapsama taşımak isteyebiliriz

    cities_with_a = []
    
    %w[samsun istanbul izmir adana].each do |city|
      cities_with_a << city if city.include? 'a'
    end
    
    puts cities_with_a
    
  • Sonuç: dış kapsama taşınacak değeri tutacak değişkeni bloktan önce tanımlayın (nil gibi bir değerle de olsa)

  • Fakat bunu yapmanın hemen hemen daima daha iyi bir yolu vardır (örnek için select veya collect)


Sayılabilirler modülü

Enumerable modülü

  • Koleksiyonlar üzerinde yapılabilecek pek çok işlemi barındıran bir "katıştırma" (mixin) modülü

  • Koleksiyon? Genel olarak Dizi ve daha sonra görülecek olan Sözlük veri yapıları

  • Koleksiyon? Daha teknik bir anlatımla each metodunu sağlayan tüm nesneler

  • İşlemler? Sıralama, seçme, eşleme, arama


  • En temel metot: each, koleksiyonlarda dolaşmak için

  • Fakat "sonuç üreten" dolaşmalarda each yerine kullanmanızın daha uygun olacağı metotlar var

  • Enumerable modülü bu metotları sağlıyor

  • Yeter ki nesne each metotunu gerçeklesin (bu metotu Enumerable sunmuyor, sadece yararlanıyor)


Seçme (Süzme): select

  • N boyutlu bir dizide belirli bir koşulu sağlayan ögeleri seçiyoruz

  • Koşul bir "blok"la ifade ediliyor: "blok" true değer üretmişse ise seç, aksi halde bırak

  • Seçilen ögelerle M boyutlu yeni bir dizi oluşturuyoruz; öyle ki M <= N


[1, 2, 3, 4, 5].select { |num| num.even? } #=> [2, 4]

%w[foo bar].select { |str| str == 'foo' } #=> ['foo']
  • select metotunun diğer adları: filter, find_all

  • Seçme işlemi yeni bir dizi üretir

  • Yeni dizinin boyutu özgün diziden büyük olamaz, genellikle daha küçüktür


Diziler üzerinde seçme yaparken each değil select tercih edin

  • Enumerable modülünü de barındıran Standart kitaplığı iyi tanıyın

  • Her iş için en uygun çözümü kullanın

  • Aksi halde kendi "kötü" çözümünüzü geliştirme tehlikesi söz konusu


İlişkili metot: reject

[1, 2, 3, 4, 5].reject { |num|  num.even? } #=> [1, 3, 5]

İlişkili metot: find

[1, 2, 3, 4, 5].find { |num|  num.even? } #=> 2
  • Bu metotun daima tek bir değer döndüğüne dikkat edin ("find" yapamamışsa nil)

  • Amacınız koşul sağlandığında devam etmeden ilgili ögeyi dönmek ise daima find kullanın

  • Diğer adı: detect


Eşleme: map

  • N boyutlu bir dizideki her ögeyi, çoğunlukla o ögeyi bir işlemden geçirerek, yeni bir ögeyle eşleştiriyoruz

  • İşlem bir "blok"la ifade ediliyor: "blok"un (girdi olarak verilen ögeyle) ürettiği değerle eşleme yap

  • Eşleşen ögelerle yine aynı boyutta (N) yeni bir dizi oluşturuyoruz


[1, 2, 3, 4, 5].map { |num|  num ** 2 } #=> [1, 4, 9, 16, 25]

%w[foo bar].map { |str| str.upcase } #=> ['FOO', 'BAR']
  • map metotunun diğer adı: collect

  • Eşleme işlemi yeni bir dizi üretir

  • Yeni dizinin boyutu özgün diziye daima eşittir (daha büyük veya küçük olamaz)


Diziler üzerinde eşleme yaparken each değil map tercih edin

  • Enumerable modülünü de barındıran Standart kitaplığı iyi tanıyın

  • Her iş için en uygun çözümü kullanın

  • Aksi halde kendi "kötü" çözümünüzü geliştirme tehlikesi söz konusu


Koleksiyon predikatörleri

all?, any?, one?, none?

%w[ant bear cat].all?  { |word| word.length >= 3 } #=> true
%w[ant bear cat].any?  { |word| word.length >= 3 } #=> true
%w{ant bear cat}.one?  { |word| word.length == 4 } #=> true
%w{ant bear cat}.none? { |word| word.length == 5 } #=> true

Diziler üzerinde mantıksal sonuç üreten bir değerlendirme yaparken each veya select değil bu metotları tercih edin

  • Enumerable modülünü de barındıran Standart kitaplığı iyi tanıyın

  • Her iş için en uygun çözümü kullanın

  • Aksi halde kendi "kötü" çözümünüzü geliştirme tehlikesi söz konusu


İyi

ok = original.all?  { |word| word.length >= 3 }

if ok
  ...
end

Kötü

original = %w[ant bear cat]
longest_than_two = original.select { |word| word.length >= 3 }

if longest_than_two.length == original.length
  ...
end

Çirkin

original = %w[ant bear cat]

ok = true
original.each do |word|
  if word.length < 3
    ok = false
    break
  end
end

if ok
  ...
end

Sıralama: sort

  • N boyutlu bir diziyi "belirli bir kurala" göre sıralıyoruz

  • Kural bir "blok"la ifade ediliyor: verilen öge çiftini karşılaştırarak "küçük, eşit, büyük" sonucu üret

  • Sıralanan yine aynu boyutta (N) yeni bir dizi oluşturuyoruz


[1, 2, 3, 4, 5].sort { |a, b| b <=> a } #=> [5, 4, 3, 2, 1]

%w[foo bar].sort #=> ['bar', 'foo']
  • Sıralama kuralı zorunlu değil: verilmemesi halinde öntanımlı kurallar uygulanıyor

  • Öntanımlı kuralllar? Dizgilerin alfabetik sıralaması, sayıların büyüklüğüne göre sıralanması


  • Sıralama işlemi yeni bir dizi üretir

  • Yeni dizinin boyutu özgün diziye daima eşittir (daha büyük veya küçük olamaz)


Diziler üzerinde sıralama yaparken each değil sort tercih edin

  • Enumerable modülünü de barındıran Standart kitaplığı iyi tanıyın

  • Her iş için en uygun çözümü kullanın

  • Aksi halde kendi "kötü" çözümünüzü geliştirme tehlikesi söz konusu


Karşılaştırma işlemi

  • Sıralama yaparken her seferinde iki ögeyi karşılaştırıyoruz: ör. a ve b

  • İşlem başarımını eniyileştirmek için öge çiftlerinin nasıl seçileceği önemli bir "algoritmik problem"

  • Buna "sıralama algoritması" diyoruz


  • Literatürde "quick, merge, heap, shell, bubble" sort gibi iyi bilinen bazı algoritmalar var

  • Standart kitaplıktaki metotları kullanarak sıralama yaparken "sıralama algoritması"yla ilgilenmiyoruz

  • Bu bir gerçekleme detayı: Ruby sizin için en uygun (optimum) veya ona yakın algoritmayı seçiyor

  • Bizim ilgilendiğimiz kraşılaştırma işlemi; ki çoğu durumda onu bile vermemiz gerekmiyor


Karşılaştırma işlemi 3 ihtimallı bir bilgiyi üretmeli: "küçüktür", "eşittir", "büyüktür"

  • "Küçüktür": negatif bir sayı, çoğunlukla -1

  • "Eşittir": 0

  • "Büyüktür": pozitif bir sayı, çoğunlukla 1


[1, 2, 3, 4, 5].sort do |a, b|
  if b < a
    -1 # herhangi bir negatif değer de olabilir
  elsif b > a
    1 # herhangi bir pozitif değer de olabilir
  else
    0
  end
end #=> [5, 4, 3, 2, 1]

Örnekte dizideki öge çiftinin a, b sırasıyla verildiğine fakat karşılaştırmanın b, a sırasıyla yapıldığına dikkat edin: "Ters sıralama"


<=>: "Spaceship" işleci

  • Örnekteki karşılaştırmayı her seferinde yapmanız gerekmiyor

  • <, > ve == karşılaştırmaları ilkel nesnelerin zaten cevap verdiği metotlar (bu bilgi nesne de zaten var)

  • O halde örnekteki karşılaştırmaları <=> isimli özel bir metotta toplayabiliriz

(Dikkatli bakılırsa cepheden bir tür "uzay gemisi" görülebilir)


def <=>(other)
  if self > other
    1 # herhangi bir negatif değer de olabilir
  elsif self < other
    1 # herhangi bir pozitif değer de olabilir
  else
    0
  end
end

  • Böyle bir <=> metotu Integer, String gibi ilkel veri türü nesnelerine ekleyebiliriz

  • Bu yapıldığında sort metotuna karşılaştırma işlemi vermek bile gerekmez

  • Neden? Ruby "türdeş" dizide dolaşırken ilgili türün <=> metotunu uyarır


sort metotunu genellikle hiç bir blok tanımlamadan kullanıyoruz

  • Öntanımlı davranışın var olmadığı veya uygun olmadığı durumlarda bir blok lullanıyoruz

  • Fakat çoğu durumda ilgili nesneye ait sınıfta özel bir <=> metotu yazmak daha doğru


Bir diziyi ters sıralamak için her seferinde önceki örneklerdeki gibi mi kod yazacağız?

[1, 2, 3, 4, 5].sort.reverse #=> [5, 4, 3, 2, 1]
  • Önce sırala, sonra tersle

  • Dikkat! Sadece reverse diziyi sıralamadan tersler


Bir dizgi dizisini dizgi uzunluklarına göre sıralamak istersek?

İlişkili metot: sort_by

%w[apple pear fig].sort_by { |word| word.length }  #=> ['fig', 'pear', 'apple']
  • Karşılaştırma işlemini ögelerin kendisiyle değil bir özelliği üzerinden yapıyoruz

  • "Blok" nesnenin kendisinden bu özelliğe ait değeri üretiyor

  • Öyle ki karşılaştırma bu değerlere ait öntanımlı <=> işleciyle gerçekleşiyor


Ünlemli metotlar

Verilen koleksiyonla eş boyutta bir koleksiyon üreten metotları ele alalım: map, sort, reverse

  • Bu metotlar her seferinde yeni bir dizi üretiyor

  • Örneğin bellekte büyük yer kaplayan 100,000 ögeli bir diziyi sıraladığımızda bellekte bir o kadar yer işgal eden yeni bir dizi üretiyoruz

  • Sıralanmamış özgün diziye sonraki aşamalarda çoğunlukla ihtiyacımız yok

  • Bellekten tasarruf için yerinde sıralasak? Yani sıralanmış dizi eskisine ayrılan bellek alanına kayıtlansa?


> a = [1, 2, 3, 4, 5] #=> [1, 2, 3, 4, 5]
> b = a.reverse       #=> [5, 4, 3, 2, 1]
> a                   #=> [1, 2, 3, 4, 5]

yerine

> a = [1, 2, 3, 4, 5]
> a.reverse! #=> [5, 4, 3, 2, 1]
> a          #=> [5, 4, 3, 2, 1]

Tek yapmamız gereken metotu sonunda ! olan çeşidiyle değiştirmek

> a = [1, 2, 3, 4, 5]
> a.reverse!
> a #=> [5, 4, 3, 2, 1]
> a.sort!
> a #=> [1, 2, 3, 4, 5]
> a.map! { |i| i ** i }
> a #=> [1, 4, 9, 16, 25]

Ters sıralamada daima ünlemli metotları kullanın

İyi

a.sort!.reverse!

Kötü

a.sort.reverse
# veya
a.sort.reverse!

  • Ruby'de sonunda ? karakteri bulunan metotları görmüştük

  • ? "doğru mu, değil mi?"yi çağrıştırıyordu

  • Sondaki ! ise genel olarak "dikkatli ol, bu metotun bir yan etkisi var" mesajını iletiyor

  • Örneklerdeki yan etki? Özgün dizinin artık yok olması, değişime uğraması

  • Bu yan etki bazen istemediğiniz bir şey olabilir

  • Örneğin: "Sıralanmamış diziye de ihtiyacım var; isteğe göre farklı kurallarla birden fazla sıralama yapmam gerekiyor"


Bu bir konvansiyon

  • Her metotun ünlemli bir karşılığı yok, sadece anlamlı durumlarda var

  • Sona ! koyduğunuzda sihirli bir şey olmuyor, bu sadece bir konvansiyon

  • Bu konvansiyon, (daima zor olan) isimlendirmelerde kullanacağınız bir enstrüman

  • Mevcut bir ismi "recycle" ederek yeniden kullanabiliyorsunuz, üstelik daha anlamlı bir seçenek olarak

  • Ünlemli metotu, eğer bu anlamlıysa, siz gerçekleyeceksiniz

  • Bunu yaparken "dikkat, yan etkisi var" semantiğine sadık kalın


Standart kitaplık

Standart kitaplık bir hazine

  • Kıymetli pek çok mücevher (metot) var

  • Bunları tanıyın (aksi halde ne olacağını biliyorsunuz)


Diğer "sayılabilirler" metotları

  • max, min, max_by, min_by, sum

  • uniq, zip, tally


Diğer dizi metotları

  • compact, flatten

  • sample, rotate

  • permutation, combination

  • product, transpose

  • intersection, union, difference


Sözlük

Fikir

  • Dizi elemanlarına tamsayı indisleriyle erişiyoruz
  • Erişmek istediğimiz bilgiyi bulmakta kullandığımız tek anahtar bir tamsayı
  • Anahtar olarak daha anlamlı bir şey kullanamaz mıyız?
  • Örneğin "dizgi"leri anahtar olarak kullansak?

Gündelik hayattan alınma örnekler

  • Arayacağımız kişiye telefon numarası yerine ismiyle erişmek
  • Bir web sitesine IP numarası yerine anlamlı bir isimle erişmek

Bilgisayar bilimlerinde yapılan en önemli keşiflerden birisi: Sözlük veri yapısı

  • Neden sözlük? Bir sözlükte kelimeleri anahtar kılarak tanımlara erişiyoruz
  • Anahtar ("key") → Değer ("value")

Söz dizimi basit, dizilerle karşılaştırın

turkish_to_english = {
  'iyi'    => 'good',
  'kötü'   => 'bad',
  'çirkin' => 'ugly'
}

turkish_to_english['iyi'] #=> 'good'
turkish_to_english['güzel'] = 'beautiful'

  • Sözlük (dictionary) yaygın kullanılan bir karşılık, fakat bununla sınırlı değil
  • Bu veri yapısı, kullandığınız programlama diline göre, farklı adlarla karşınıza çıkabilir

Terminoloji

  • "Dictionary": Sözlük (ör. Python)

  • "Associative Array": İlişkili Dizi (ör. Bash)

  • "Map" veya "Mapping": Eşlem (veya Eşleme) (ör. Go, Java)

  • "Hash table" veya "Hash": Çırpma tablosu (?) (ör. Ruby, C#)

  • (sadece) "Table": Tablo (ör. Lua)


Ruby'de "Hash" adlandırması kullanılıyor

  • Neden "Hash"? Buna daha sonra değineceğiz

  • Türkçe'ye uygunluk açısından anlatımda "sözlük" adlandırmasını tercih edeceğiz


  • İsimler farklı olsa bile kavram aynı

  • Anahtar/Değer çiftlerinden oluşan bir koleksiyon

  • Öyle ki "değer"lere (genel olarak) dizgi türünde "anahtar"larla erişiyoruz


  • Anahtarların salt dizgi (string) olması gerekmiyor

  • Belirli bir koşulu sağlayan bir tür, bir nesne de anahtar olabilir

  • Ama bu bir parça ileri bir konu

  • Değerler için hiç bir kısıtlama yok, herhangi bir tür olabilir


Temel hareketler: İlkle

rehber = {
  'annem' => '05051234567',
  'babam' => '05331234567',
  'kanka' => '05337654321'
}

Temel hareketler: oku/yaz

puts rehber['babam'] #=> '05331234567'

rehber['abim'] = '05333216547'
rehber['kanka'] = '05303216547'

Temel hareketler: Metotlar

p rehber.size
p rehber.keys
p rehber.values

p rehber.key? 'abim' #=> true
p rehber['olmayan']  #=> nil

Dikkat!

rehber['olan'] = nil
p rehber['olan']     #=> nil
p rehber.key? 'olan' # true

Temel hareketler: Dolaş

rehber.each do |isim, numara|
  puts "#{isim}: #{numara}"
end

Temel hareketler: Dolaşırken değiştir

rehber.each_key do |isim|
  rehber[isim] += '9'
end

  • each, keys ve values en temel metotlar

  • keys ve values metotlarının birer dizi ürettiğine dikkat edin

  • Bu sayede bir sözlüğün anahtarları veya değerleri arasında dolaşabiliyoruz


Küçük bir optimizasyon

  • Anahtarlar arasında dolaşmak için, keys.each yerine .each_key
  • Değerler arasında dolaşmak için, values.each yerine .each_value
  • Bu metotlar "biraz" daha hızlı sonuç veriyor

Sözlük veri yapısı neye yarar?

  • Çok, pek çok şeye yarar

  • Sözlük kullanmadan ciddi bir program yazmak çok zor

  • Böylesine önemli bir veri yapısı

  • Örnekleyeceğiz


Örnek: Tekilleştirme

meyveler = %w(
  elma armut Elma kiraz şeftali karpuz karpuz kavun şeftali ARMUT
)

pazar= {}

meyveler.each do |meyve|
  pazar[meyve.downcase] = true
end

p pazar.keys

Örnek: Kelime sıklığı

metin = <<MSG
Nush ile uslanmayanı etmeli tekdir; tekdir ile uslanmayanın hakkı kötektir.
MSG

frekans = {}

metin.split.each do |kelime|
  kelime.delete! '.,;'
  kelime.downcase!
  frekans[kelime] = 0 unless frekans[kelime]
  frekans[kelime] += 1
end

p frekans

Örnek: İl adlarını ve plaka numaralarını taşıyan plaka isimli bir sözlük

plaka = {
  # ...
  'samsun' => 55,
  # ...
}

Sözlük veri yapısının "arama" işleminde bir diziye göre çok daha çabuk sonuç verdiğini görebiliyor muyuz?


Örnek: Verilen bir şehrin listedeki varlığı?

iller_array = [
  'samsun',
  'ankara',
  'istanbul',
  'izmir'
]

iller_hash = {
  'samsun'   => true,
  'ankara'   => true,
  'istanbul' => true,
  'izmir'    => true
}

Örnek: Anagram kelime grupları

# İpucu: Bir kelimeyi harf dizisine dönüştürmek için kelime.split('')
#        kullanabilirsiniz.  Anagram kelimeyi nasıl tespit edersiniz?

kelimeler =  %w[
  demo none tied evil
  dome mode live fowl
  veil wolf diet vile
  edit tide flow neon
]

# Bulunması gereken dizi (her anagram kelime grubu bir alt dizi)
anagramlar = [
  ['demo', 'dome', 'mode'],
  # ...
]

Örnek: Basit veritabanı (evet, bir sözlük basit bir veritabanıdır)

ogrenciler = {
  'aekinci' => {
    'isim'           => 'Ahmet Ekinci',
    'yas'            => 23,
    'cinsiyet'       => 'erkek',
    'aldigi dersler' => ['Matematik','Tarih','Fizik']
  },
  'yhas'    => {
    'isim'           => 'Yusuf Has',
    'yas'            => 26,
    'cinsiyet'       => 'erkek',
    'aldigi dersler' => ['Coğrafya','Türkçe','Matematik']
  }
}

Enumerable modülünde dizilerden aşina olduğunuz pek çok metot sözlükler için de geçerli

  • select, map, all?, any?

  • Tek fark: dizide bu metotlar tek argüman alırken sözlükte bir argüman çifti (sırasıyla anahtar ve değer) alıyor

  • Bunu kavradığınızda kalanı kolay


Sözlüklerde bir işlem var ki çok önemli: merge

  • İki sözlüğe (genelleştirecek olursak, birden fazla sözlüğü) birleştirerek tek sözlük yapmak

  • Sözlüklerde o sözlüğe özgü anahtarlar sonuç olarak üretilen birleşik sözlükte de bulunur

  • Peki, ortak anahtarlar? Her iki sözlükte de aynı anahtar farklı değerlerle yer alıyorsa?


Örnek: İki sözlüğü tek bir sözlükte birleştir

redhouse = {
  'iyi'    => 'good',
  'kötü'   => 'bad',
  'çirkin' => 'ugly',
  'güzel'  => 'beautiful'
}

fono = {
  'iyi'    => 'angel',
  'kötü'   => 'evil',
  'çirkin' => 'ugly',
  'akıllı' => 'smart'
}

Sembol

  • Ruby'ye pek özgü bir veri türü

  • "Dizgimsi" bir şey: değiştirilemez ("Immutable") bir tür "dizgi"

  • Ruby öğrenenlerin anlamakta zorlanabileceği bir veri türü

  • Ruby'de sözlük anahtarları başta olmak üzere pek çok yerde kullanılıyor


'sunday'.class  #=> String
:sunday.class   #=> Symbol

'sunday'.to_sym #=> :sunday
:sunday.to_s    #=> "sunday"

Sözdizimi

  • Dizginin başına, şayet boşluk içermiyorsa, : ekliyoruz

  • Dizgide boşluk varsa tırnak içine alıyoruz

  • (Tercihen) ASCII olmayan karakterler varsa onu da bir önceki kurala uygun yazıyoruz


:sembol
:'bu bir sembol'
:türkçe
:'türkçe'

Sözlük anahtarlarında yaygın kullanıyoruz

  • Dizgi anahtarlarla

    redhouse = {
      'good'      => 'iyi',
      'bad'       => 'kötü',
      'ugly'      => 'çirkin',
      'beautiful' => 'güzel'
    }
    
    redhouse['bad'] #=> "kötü"
    
  • Sembol anahtarlarla

redhouse = {
  :good      => 'iyi',
  :bad       => 'kötü',
  :ugly      => 'çirkin',
  :beautiful => 'güzel'
}

redhouse[:bad] #=> "kötü"

Ruby 1.9 sürümünde gelen bir kısayolla :key => value yerine key: value yazabilirsiniz ve (yaygın stil olarak) yazmalısınız

redhouse = {
  good:      'iyi',
  bad:       'kötü',
  ugly:      'çirkin',
  beautiful: 'güzel'
}

redhouse[:bad] #=> "kötü"

Neden "sembol"?

İki temel nedeni var (öncelik sırasıyla):

  1. Okunurluk: Sembol kullanımı yoluyla "niyet daha iyi ifade ediliyor"

  2. Başarım: (Sözlük erişimlerinde) başarım daha yüksek


1 Okunurluk

If the contents (the sequence of characters) of the object are important, use a string. If the identity of the object is important, use a symbol.

— Jim Weirich (Ruby Cookbook)

Mealen: Dizgimsi bir ögenin içeriğiyle değil de temsil ettiği kimlikle ilgileniyorsanız sembol, aksi halde dizgi kullanın.


Aşağıdaki sembol örneklerinde ilgili kelimeler bir şeyleri temsil ediyor, kelime içeriğiyla harf harf ilgilenmiyoruz

  • Haftanın günleri: :sunday, :monday, ...

  • Durum: :enabled, :disabled

  • Nitelik: :color, :age, :gender, :weight, :fullname

  • İsim: :HTTP, :SSH


person = {
  fullname: 'Cezmi Seha Sahir',
  age:      54
}
  • :fullname sembolü person nesnesindeki bir niteliği temsil ediyor, bu nedenle 'fullname' demiyoruz

  • Öte yandan 'Cezmi Seha Sahir' dizgisi bir veri, içeriğiyle ilgileniyoruz


Sembollerin "Okunurluk" rolünün en azından bir kısmı diğer programlama dillerinde çok kaba bir benzerlik olarak "Enum" veri türüne karşı düşer

  • Ruby'de "Enum" veri türü yoktur

  • Diğer dillerden bilgi transferi yaparken bu argümanı göz önünde bulundurarak sembol kullanımını bir seçenek olarak değerlendirin


2 Başarım


require 'benchmark'

count = 10000000

symbol         = :symbol
string_mutable = "string"
string_frozen  = "string".freeze

Benchmark.bm do |bm|
  bm.report('Symbol:') do
    count.times { symbol.hash }
  end
  bm.report('String Mutable:') do
    count.times { string_mutable.hash }
  end
  bm.report('String Frozen:') do
    count.times { string_frozen.hash }
  end
end

       user     system      total        real
Symbol:  3.263617   0.000000   3.263617 (  3.263620)
String Mutable:  3.998714   0.000000   3.998714 (  3.998727)
String Frozen:  3.990479   0.000000   3.990479 (  3.990506)

  • Örnekteki başarım ölçümüne göre hash hesaplarında semboller değişmez ve değişebilir dizgilere göre %25 daha hızlı

  • Duruma göre bu başarım çok daha fazla olabilir

  • Neden?


  • Semboller aslında bir tür tamsayı

  • Ruby yorumlayıcısı kodu yüklerken tüm sembolleri tek seferliğine bir tür tamsayıya dönüştürüyor

  • "Hash" fonksiyonu zaten bir tamsayı hesaplar

  • Bir tamsayının "hash" değerini hesaplamak bir dizginin "hash" değerini hesaplamaya göre daha hızlıdır

  • Çünkü yapılması gereken işlem dizgiye göre daha basittir

  • Dizgide tüm içerik (her bir karakter) dikkate alınarak işlem yapılıyor


  • Hash fonksiyonu nerede kullanılıyor? Sözlük erişimlerinde

  • Bir sözlükte anahtar üzerinden değere erişirken önce daima anahtara bir "hash" fonksiyonu uygulanır

  • Anahtar bir sembol ise bu işlemin süresi de daha kısa oluyor


Ama unutmayın:

  • Öncelikli amacımız okunurluğu arttırmak

  • Semboller kodu okuyan kişiye "burada bir şeyi temsil ediyorum" niyetini iletiyor

  • Ruby'nin son sürümlerinde başarım açısından iki yöntem arasında devasa bir farklılık yok

  • Bu nedenle başarımdan ziyade okunurluk amacını daha kıymetli buluyoruz


  • Sembollerin kullanımı sadece sözlük anahtarıyla sınırlı değil

  • Bir değişken veya metot adı, özetle "identifier" kabul eden her işlemde ilgili tanımlayıcıyı sembolle temsil ediyoruz

%w[foo bar].map(&:upcase) #=> ['FOO', 'BAR']
  • Bakın bu örnekte eşleme yapılırken kullanılacak süzgecin (bir metot) bilgisi map metoduna bir sembolle iletiliyor: :upcase (baştaki & ayrı bir bilgilenmenin konusu)

Örnek: Varsayılan sayfa ayarları

DEFAULTS = {
  paper:     :A4,
  layout:    :portrait,
  numbering: true,
  cover:     true
}.freeze

Verilen ayarlarla çıktı alan printout adında bir metod yazalım, öyle ki bir ayar verilmediğinde varsayılan değer esas alınsın

printout 'foo.pdf', layout: :landscape

Örnek: Basit öğrenci kaydı 1

Ahmet Fuat                  1234567
Ayşe Begüm Toprak           3456712
Fatma Çetin                 2346567
Muzaffer Talha Emir Gündüz  5452135

liste.txt isimli bir dosyada bulunan öğrenci listesini ayrıştırarak basit bir öğrenci veritabanı (sözlük) oluşturalım


Sözlüklerde sıra

  • Pek çok programlama dilinde sözlük veri yapısı sıralı değildir

  • Yani anahtar veya değerleri bir dizi halinde almak istediğinizde sözlüğe ekleme sırasıyla gelmez

  • Neden? Başarım

  • İlgili programlama dilinin sözlük veri yapısının gerçeklemesindeki başarım sırasız halde daha yüksektir

  • Sözlükleri sırasız olan programlama dillerinde sıralı sözlükler için farklı düzenlemeler sunulur

  • Ayrı bir "sıralı sözlük" veri yapısı veya sırayı tutan ilave bir anahtar dizisi


Önerme: Ruby'de sözlükler sıralıdır

  • Anahtar ve değerleri ekleme sırasıyla alırsınız

Struct

Point = Struct.new :x, :y

p = Point.new 3, 5
pp p
  • Point? Struct tarafından üretilen bir sınıf

  • Struct sizin adınıza basit bir nesne (nesnenin planı olan sınıfı) oluşturuyor


  • Ruby'de veri kapları (data container) kurmanın etkili bir yolu

  • Diğer dillerde de benzer veri türleri var, ör. C'de struct

  • Sadece veri tutan, davranışın ön planda olmadığı basit nesneler oluşturmak için yararlı

  • Bu yönüyle sözlükler ve tam teşekküllü nesneler arasında bir yerde konumlanıyor


Ne zaman Struct?

  • Veri bileşenleri (nitelikler) sabit sayıda ve önceden biliniyor

  • Davranış ön planda değil: nesne metotları yok veya çok az sayıda

  • Sözlük alternatifi bir parça ilkel duruyor


  • Struct başarımı yüksek bir gerçeklemeye sahip, olağan Ruby nesnelerine göre erişimler daha hızlı

  • Neden? C ile gerçeklenmiş bir nesne gibi bakılabilir


Nitelikleri isimlendirilmiş argümanlarla verebilirsiniz

Point = Struct.new :x, :y, keyword_init: true
p = Point.new x: 3, y: 5
  • Nitelik sayısı arttığında veya okunurluğu iyileştirmek için tercih ediyoruz

  • Bu yöntemi kullandığınızda diğer yöntem (pozisyonel nitelikler) kullanılamaz, buna dikkat edin


  • Struct ile oluşturduğunuz nesnelerde davranış metotları tercihen hiç olmamalı

  • Hesap edilen nitelikleri ("computed attributes") hariç tutuyoruz, bunlar davranış değil


Person = Struct.new :name, :surname do
  def fullname
    "#{name.capitalize} #{surname.capitalize}"
  end
end

person = Person.new 'ahmet', 'fuat'
person.fullname #=> "Ahmet Fuat"
  • Bu örnekte fullname sık hesaplanacak bir nitelikse bunu nasıl hızlandırabiliriz?

Davranış varsa ("eser miktarda" olanlar belki hariç) "tam teşekküllü" bir nesne tercih edin

Point = Struct.new :x, :y do
  def distance(other)
    Math.sqrt((other.x - x)**2 + (other.y - y)**2)
  end
end

p = Point.new 3, 5
q = Point.new 6, 9

p p.distance(q)
  • Point nesnesi karmaşıklaşma eğiliminde, bugün distance yarın?

Örnek: Basit öğrenci kaydı 2

Öğrenci temsilinde sözlük (Hash) yerine Struct kullanalım


Modüller

Ruby'nin modüler programlama için ("sınıf"larla birlikte) sunduğu imkan

module A
  def self.meth
    ...
  end
end

A.meth

İki önemli amaca hizmet ediyor:

  • İsim uzayını düzenliyor

  • "Katıştırma" ("Mixins") tekniğiyle gerçeklemelerin paylaşılmasını sağlıyor


İsim uzayı

  • sin metodu Math modülünde tanımlı, ör. Math.sin(0)

Bu sayede:

  • Kod okunurluğu artıyor: modül ismi metod ismiyle sınırlı anlamı pekiştiriyor

  • İsim çakışmaları önleniyor: sin isimli bir metod yazdığımızı varsayın?


sin: "sinus", "sin" (günah)?

def sin(context)
  warn.puts "Hatalı bir eylem gerçekleşti: #{context}"
end

...

x = Math.sin(teta)
if x > 0.5
  sin("hatalı aralıkta değer üretildi: #{x}")
end

sin: "sinus", "sin" (günah)?

module Log
  ...
  def self.sin(context)
    warn.puts "Hatalı bir eylem gerçekleşti: #{context}"
  end
end

x = Math.sin(teta)
if x > 0.5
  Log.sin("hatalı aralıkta değer üretildi: #{x}")
end
  • Log modül isminin anlamı pekiştirdiğine dikkat edin

require

log.rb dosyası

module Log
  ...
  def self.sin(context)
    warn.puts "Hatalı bir eylem gerçekleşti: #{context}"
  end
end

Müşteri kodu (log.rb modül yollarında tanımlı)

require 'log'

x = Math.sin(teta)
if x > 0.5
  Log.sin("hatalı aralıkta değer üretildi: #{x}")
end
  • Bu haliyle hata verir, neden?

  • Ruby modülleri $LOAD_PATH değişkeninin gösterdiği dizinlerde aranır

  • Bulunduğunuz dizin öntanımlı olarak $LOAD_PATH'te kayıtlı değildir

  • Bulunduğunuz dizindeki log.rb modülü yüklenemez

  • require 'log' satırının çalışması için bulunduğunuz dizini yükleme yoluna ekleyin

    $LOAD_PATH.unshift __dir__
    

require_relative

Dosya yolu göreceli modülleri $LOAD_PATH değişkenine dokunmadan yükleyebiliriz

require_relative 'log'

x = Math.sin(teta)
if x > 0.5
  Log.sin("hatalı aralıkta değer üretildi: #{x}")
end

Düzenli ifade

Regular expression

  • Metin işleme için geliştirilmiş bir tür DSL (Alana özgü dil)

  • Bir metinde (bir dizgi) aradığınız bir alt metni tanımlayan bir "desen" ("pattern")

  • Bu sayede bul (find), bul/değiştir (find/replace) işlemleri yapabiliyoruz

  • Hemen hemen her programlama dilinde destekleniyor


Anlatıma örnekleyerek devam edelim:

  • https://rubular.com/
  • https://regexr.com/

Örnekler:

  • Dizgi (en azından ilk aşama doğrulama olarak) geçerli olabilecek bir e-posta mı?

  • Dizgide (ör. bir dosya adı) boşluk ve Türkçe karakter var mı?

  • Telefon numarası geçerli mi?


gsub

"Global Substitute"

  • String sınıfında tanımlı bir nesne metotu

  • Sıklıkla kullandığımız bir metot


"hello".gsub(/[aeiou]/, '*')                  #=> "h*ll*"
"hello".gsub(/([aeiou])/, '<\1>')             #=> "h<e>ll<o>"
"hello".gsub(/./) {|s| s.ord.to_s + ' '}      #=> "104 101 108 108 111 "
"hello".gsub(/(?<foo>[aeiou])/, '{\k<foo>}')  #=> "h{e}ll{o}"
'hello'.gsub(/[eo]/, 'e' => 3, 'o' => '*')    #=> "h3ll*"

Örnek: Bir dizgideki Türkçe karakterleri yaklaşık ASCII eşdeğerleriyle değiştir

asciify('Şule İsmet Yılmaz') #=> Sule Ismet Yilmaz

Örnek: CSV formatında bir öğrenci listesinden tanımlayıcı listesi üret

(Öğrenci Otomasyonundan indirilen) Girdi:

#,ID,Öğrenci No,Ad,Soyad,OMU Mail
1,11111111,77777777,MERVE,EREN,77777777@stu.omu.edu.tr
2,11111112,88888888,Qossay G. M.,ABULAYMOUN,88888888@stu.omu.edu.tr
1,11111113,999999,MEHMET AKİF,CEBECİ,99999999@stu.omu.edu.tr

Çıktı:

MERVE-EREN-77777777
QOSSAY-G-M-ABULAYMOUN-88888888
MEHMET-AKIF-CEBECI-99999999

Nesneler

"Ruby'de her şey birer nesne"

  • Nesne (object)? Nitelik ("attribute") ve davranışları/eylemleri ("method") olan "şey"

  • Nitelik ve davranışlara nasıl erişiyoruz? nesne.metot veya nesne.nitelik

    (Ruby'de . gösteriminde sağdaki kelime teknik olarak daima bir metot; ama şimdilik bunu göz ardı edelim)

  • Örneğin 'foo'.upcase'de 'foo' bir dizgi nesnesi, upcase ise bu nesne üzerinde etki gösteren bir eylem


  • Nesneyi anlamak nispeten kolay, ama... Ruby'de bir nesneyi yöneten kod nerede?

  • Beklentimiz "nesneyi yöneten" bu kodda nitelik ve metotları bulmak

  • Örneğin 'foo'.upcase'de upcase metotu nerede olabilir?

    (Evet, Ruby standart kitaplığında ama orada nerede?)


  • Dizgi nesnesi derken bir "tip"den ("type") bahsediyoruz, "dizgi" tipi

  • Aynı tipte çok sayıda nesne var olabilir

  • 'foo', 'bar' ve 'baz' dizgi nesnelerini düşünün

  • Tüm bu nesneler "dizgi" olma temelinde ortak ama içerikleri farklı

  • "Dizgi olma temelinde ortaklık"? Örneğin tüm dizgi nesneleri upcase metotuna cevap veriyor


  • Demek oluyor ki aradığımız "nesneyi yöneten kod" aslında bu "ortaklığı" yönetiyor

  • Bir yerlerde öyle bir kod var ki tüm dizgi nesnelerinde ortak olan davranışı kodluyor

  • İşte buna "class" diyoruz

  • Nesneleri ortak özelliklerine göre "sınıf"ladığımızı düşünün, ne cümleler kurabiliriz?

  • "Tam sayı sınıfında nesneler", "Dizgi sınıfında nesneler"


Bu anlatıma göre: "sınıf" ("class") ve "tip" ("type") terimlerinin neredeyse eş anlamlı

  • Evet, eş anlamlı; sadece farklı mahallelerin konuşma biçimleri

  • Mahalle? "Nesne Yönelimi Programlama" mahallesi

  • Bu konuya döneceğiz


Örnek: Düzlemde bir noktanın temsili

  • Kartezyen koordinatların kullanıldığı bir düzlemde bir p noktasını ele alalım

  • p noktasının nitelikleri? x ve y; bu niteliklere p.x, p.y olarak erişiyoruz

  • p bir "Nokta" tipinde bir nesne; bu tip başka pek çok nokta nesnesi olabilir

  • Bir resmi düşünün, nokta nesnelerinden oluşan bir başka "şey" ("nesne")


Sorular

"Bir yerlerde öyle bir kod var ki tüm nokta nesnelerinde ortak olan davranışı kodluyor"

  1. Bu yer neresi? Öyle ki x, y niteliklerini bulalım

  2. Bir nokta nesnesini nasıl oluşturacağız?


class Nokta
  def initialize(x_degeri, y_degeri)
    @x_niteligi = x_degeri
    @y_niteligi = y_degeri
  end

  def x
    @x_niteligi
  end

  def y
    @y_niteligi
  end
end

p = Nokta.new 3, 5
p.x #=> 3
p.y #=> 5

Cevaplar

"Bir yerlerde öyle bir kod var ki tüm nokta nesnelerinde ortak olan davranışı kodluyor"

  1. Ruby'de bu yer bir class (module benzerliğine dikkat edin)

    Nitelikler özel bir sözdizimiyle gösteriliyor (ör. @x_niteligi)

  2. Bir nokta nesnesi Nokta sınıfından "üretilen" bir şey: new metoduyla

    Bu şekilde istediğiniz sayıda (farklı veya aynı koordinatlara sahip) nokta nesnesi üretebilirsiniz


Sınıf

  • "Nesneyi yöneten kod"

  • (Veya) Aynı "tip"te ("sınıfta") nesneler arasındaki "ortaklığı" yönetiyor


  • "Sınıf"a bir binanın mimari çizimi gibi bakabilirsiniz

  • Elimizde bir "tip" binanın mimari planı var, örneğin TOKİ konutu

  • Bu plandan yararlanarak pek çok şehirde TOKİ konutları inşa edebiliriz


  • Tek plan (sınıf) → Çok sayıda konut (nesneler)

  • Tüm konutlar benzer özellikte (biçim, boyut vs), çünkü aynı "sınıf"da

  • Ama inşa edildiği yerlerin farklı olması yönüyle her bir konut ("nesne") eşsiz, kendine özgü


  • Plandan yararlanarak konutun inşa edilmesi? "construction"

  • İşte bu new çağrısına karşı geliyor

# Mimari plan
class TOKI
  def initialize(kent)
    @kent = kent
  end

  def metrekare
    100
  end
end

# İnşaat
samsun_toki = TOKI.new :samsun

'foo'.upcase'de upcase nerede? Standart kitaplıkta bulunan String adında bir sınıfta

(Hayali bir kod yazalım; gerçek biraz farklı)

class String
  def initialize(dizgi)
    @dizgi = dizgi
  end

  def upcase
    @dizgi.chars.map(&:ord).map { |ord| ord >= 'a'.ord ? ord - 32 : ord }.map(&:chr).join # Sağlığa zararlı kod
  end
end

Ama dizgi oluştururken new kullanmadık?

kelime = 'foo'
kelime.upcase #=> "FOO"

Evet, çünkü bu sık yapılan bir işlem ("dizgi literalleri"); bir kısayol konulmuş

kelime = String.new 'foo'
kelime.upcase #=> "FOO"

Nesne Yönelimli Programlama

Object Oriented Programming

  • Problemdeki unsurları (ve çözüm olarak sunulan unsurları) "nesneler" halinde görmeye eğilimli programlama tarzı

  • Bir programlama metodolojisi

  • Karmaşıklıkla mücadelede kullanılan bir düşünme enstrümanı


Nesne Yönelimli Programlama dersini bu konuya ayırdık


Range

(-1..-5).to_a      #=> []
(-5..-1).to_a      #=> [-5, -4, -3, -2, -1]
('a'..'e').to_a    #=> ["a", "b", "c", "d", "e"]
('a'...'e').to_a   #=> ["a", "b", "c", "d"]

Set

require "set"

Set.new([1, 2])                       #=> #<Set: {1, 2}>
Set.new([1, 2, 1])                    #=> #<Set: {1, 2}>
Set.new([1, 'c', :s])                 #=> #<Set: {1, "c", :s}>
Set.new(1..5)                         #=> #<Set: {1, 2, 3, 4, 5}>
Set.new([1, 2, 3]) { |x| x * x }      #=> #<Set: {1, 4, 9}>

require "set"

s1 = Set[1, 2]                        #=> #<Set: {1, 2}>
s2 = [1, 2].to_set                    #=> #<Set: {1, 2}>
s1 == s2                              #=> true
s1.add("foo")                         #=> #<Set: {1, 2, "foo"}>
s1.merge([2, 6])                      #=> #<Set: {1, 2, "foo", 6}>
s1.subset?(s2)                        #=> false
s2.subset?(s1)                        #=> true

case/when

if name == "Clark Kent"
  hero_name = "Superman"
elsif name == "Peter Parker"
  hero_name = "Spiderman"
elsif name == "Bruce Wayne"
  hero_name = "Batman"
else
  hero_name = "unknown"
end

case name
when "Clark Kent"
  hero_name = "Supername"
when "Peter Parker"
  hero_name = "Spiderman"
when "Bruce Wayne"
  hero_name = "Batman"
else
  hero_name = "unknown"
end

hero = case name
when "Clark Kent"   then "Supername"
when "Peter Parker" then "Spiderman"
when "Bruce Wayne"  then "Batman"
else                     "unknown"
end

ARGV, $PROGRAM_NAME

puts $PROGRAM_NAME
ARGV[0].to_i.downto(ARGV[1].to_i) { |i| puts i }