Herkese merhaba,
Bu toplulukta programlama konusunda benden daha tecrübeli arkadaşlar var. Onlara selamlar ve saygılarımı sunarım öncelikle. Konuya girmeden önce, kısa bir giriş yapmama müsaade edin.
Benim programlamaya merakım otuz yaşından sonra ortaya çıktı ve şu an otuz yedi yaşındayım. Yani, anlayacağınız gibi yaklaşık yedi senedir program nedir, programlama nedir, algoritma nedir gibi sorulara, daha çok Python dilini kullanarak kendimce cevaplar bulmaya çalışıyorum. C dili ile bu yedi sene içerisinde elbette karşılaştım. Ancak hali hazırda Python’ı keşfetmeye devam ettiğim için ve sıklıkla kullanmak da alışkanlığa yol açtığı için sürekli Python kullanmaya devam ettim.
Şimdi size bu başlık altında 0’dan C anlatacak değilim. Böyle bir iddiam yok. Benim bu başlıkta yapmayı hedeflediğim şey, hali hazırda Python bilen ama C dilini hiç kullanmamış kişilere, C’nin Python diliyle olan meşhur farklılıklarını biraz olsun tanıtabilmek ve bu iki dili birlikte nasıl kullanabileceğimizi göstermek.
Özetle bu başlık, dünya için yeni olmasa da, bazı arkadaşlar için yeni olacak bazı kavramları tanıtmayı hedefliyor.
Şimdi, özel olarak seçtiğim bir örnek üzerinden Python ve C arasındaki farklara odaklanmaya çalışalım:
Python’daki itertools.combinations
sınıfının işlevini biliyorsunuzdur. Bilmeyenler için basit bir kullanım ile ne yaptığını gösterelim:
import itertools
print(*itertools.combinations([1, 2, 3, 4], 2))
Yukardaki kodun üreteceği çıktıdan anlaşılacağı üzere, itertools.combinations
sınıfı, herhangi bir kümenin n
elemanlı alt kümelerini bulur. Peki bu alt kümeleri bulan sınıf için nasıl bir algoritma izliyor? Bunun cevabını Python dokümanını inceleyerek bulabiliriz. Ancak gelin, birlikte başka bir combinations
algoritmasını inceleyelim.
def combinations(arr, r):
if not r:
yield ()
return
for i in range(len(arr)):
for comb in combinations(arr[i + 1:], r - 1):
yield arr[i], *comb
print(*combinations(arr, 2))
Şimdi, yukardaki algoritmayı adım adım anlamaya çalışalım:
Her bir öz-yineleme adımının hangi scope
'da gerçekleştiğini ve o scope’da yer alan değişkenlerin değerlerinin ne olduklarını, hepinizin çok iyi bildiği girintilerle gösterelim:
combinations
fonksiyonunu arr
ve r
argümanlarıyla çağırdık diyelim:
Fonksiyonumuzun yapacağı işlemleri adım adım açmaya çalışalım:
arr = [1, 2, 3, 4]
r = 2
if not r:
yield ()
return
for i in range(len(arr)):
# i = 0 için:
for comb in combination(arr[i + 1:], r - 1):
arr = [2, 3, 4]
r = 1
if not r:
yield ()
return
for i in range(len(arr)):
# i = 0 için:
for comb in combination(arr[i + 1:]):
arr = [3, 4]
r = 0
if not r:
yield ()
return
# Buradan () üretilir.
# Daha ileri gidilmez.
yield (arr[i], *comb)
# Yukardaki ifadeyi açalım:
# yield (2, *())
yield (arr[i], *comb)
# yield (1, *(2, *())
yield (1, 2) # Ekrana yazdırılır.
# i = 1 için:
for comb in combination(arr[i + 1:]):
arr = [4]
r = 0
if not r:
yield ()
return
# Buradan () üretilir.
# Daha ileri gidilmez.
yield (arr[i], *comb)
# Yukardaki ifadeyi açalım:
# yield (3, *())
yield (arr[i], *comb)
# yield (1, *(3, *())
yield (1, 3) # Ekrana yazdırılır.
# i = 2 için:
for comb in combination(arr[i + 1:]):
arr = []
r = 0
if not r:
yield ()
return
# Buradan () üretilir.
# Daha ileri gidilmez.
yield (arr[i], *comb)
# Yukardaki ifadeyi açalım:
# yield (4, *())
yield (arr[i], *comb)
# yield (1, *(4, *())
yield (1, 4) # Ekrana yazdırılır.
# i = 1 için:
for comb in combination(arr[i + 1:], r - 1):
arr = [3, 4]
r = 1
if not r:
yield ()
return
for i in range(len(arr)):
# i = 0 için:
for comb in combination(arr[i + 1:]):
arr = [4]
r = 0
if not r:
yield ()
return
# Buradan () üretilir.
# Daha ileri gidilmez.
yield (arr[i], *comb)
# Yukardaki ifadeyi açalım:
# yield (3, *())
yield (arr[i], *comb)
# yield (2, *(3, *())
yield (2, 3) # Ekrana yazdırılır.
# i = 1 için:
for comb in combination(arr[i + 1:]):
arr = []
r = 0
if not r:
yield ()
return
# Buradan () üretilir.
# Daha ileri gidilmez.
yield (arr[i], *comb)
# Yukardaki ifadeyi açalım:
# yield (4, *())
yield (arr[i], *comb)
# yield (2, *(4, *())
yield (2, 4) # Ekrana yazdırılır.
# i = 2 için:
for comb in combination(arr[i + 1:], r - 1):
arr = [4]
r = 1
if not r:
yield ()
return
for i in range(len(arr)):
# i = 0 için:
for comb in combination(arr[i + 1:]):
arr = []
r = 0
if not r:
yield ()
return
# Buradan () üretilir.
# Daha ileri gidilmez.
yield (arr[i], *comb)
# Yukardaki ifadeyi açalım:
# yield (4, *())
yield (arr[i], *comb)
# yield (3, *(4, *())
yield (3, 4) # Ekrana yazdırılır.
# i = 3 için:
for comb in combination(arr[i + 1:]):
# arr[i + 1:] boş bir liste olduğu için işlem yapılmaz.
Yukarda, combinations
fonksiyonunun arr = [1, 2, 3, 4]
ve r = 2
için hangi adımları izlediğini, Pythonic bir pseudocode üzerinden anlatmaya çalıştım. Umarım yeterince açıktır. Benzer bir öz-yineleme yöntemini kullanarak istersek itertools.permutations
, istersek de itertools.product
sınıflarının yaptığı işlemleri yapan fonksiyonları oluşturabiliriz. Ama bu başlıkta bunları anlatmayacağım.
Şimdi, umuyorum ki, şu ana kadar anlatılan şeyler, recursion
mantığının Python dilinde nasıl işlediğini açıkça ortaya koyan bir örnektir. C dili Python dili arasındaki karşılaştırmayı daha basit bir örnek üzerinden de gösterebilirdim tabi. Ama dizilerle işlem yapan bir örnek yapıp, C dili ile Python dili arasındaki bir kaç önemli farklılığı vurgulamak için, böyle bir örnek seçmek geldi aklıma. Hem böylece, C’de dizi oluşturmak demek ne demek bunu da görebiliriz.
Şimdi, aynı işlemi yapan bir fonksiyonu C’de oluşturmaya çalışacağız. Ancak, bu kez, öz-yineleme
'nin ayrıntılarını Python örneğinde olduğu gibi göstermeyeceğim. Çünkü, hemen hemen aynı akışı izliyor.
Geçenlerde, class
konusuna yönelik sözler söylerken, soyutlama kavramından bahsetmiştik. Python, C’ye göre oldukça soyuttur. Bu cümle, Python’ın C’ye göre daha karmaşık olduğu anlamına da gelir. C dilindeki kavramların esneklikleri, Python’daki kadar fazla değildir. Örneğin, C’de tipleri açıkça tanımlamak zorundayız. Her bir değişkenin belirli bir tipi olur ve o değişken yalnızca o tipe ait değerler edinebilir. Python ise bu konuda esnektir; bir değişkenin, herhangi bir tipi ve herhangi bir tipe ait bir değeri olabilir. Mantık olarak bakarsanız, değişken
kavramına Python dilinde daha çok yaklaştığımızı görebiliriz.
Devam etmeden önce, bir husustan bahsedeyim. C’de bir dizinin boyutunu açıkça ölçebilmek her zaman mümkün olmayabilir. Açıkça dizi tanımlamak yerine, dizi gibi davranan pointerlar tanımlayıp, o pointerlar ile işlemler yapmak C’de yaygın olarak izlenen yaklaşımlardan birisidir. Ancak bir dizinin nerede bittiğini tespit etmek Python’daki gibi kolay olmayabilir.
Madem öyle, yavaş yavaş kodlamaya geçelim:
Önce gerekli kütüphaneleri programa aktaralım. Değişkenlere hafızadan yer ayırmak için stdlib.h
kütüphanesindeki malloc
fonksiyonuna ve ayrılmış hafızayı serbest bırakmak için de free
fonksiyonuna ihtiyacımız olacak. Sonuçları görebilmek için de stdio.h
kütüphanesindeki printf
fonksiyonunu kullanacağız.
#include <stdio.h>
#include <stdlib.h>
Python’da oluşturduğumuza benzer bir fonksiyon oluşturmaya çalışacağız şimdi. Python’da fonksiyonumuz 2 boyutlu bir dizi geri döndürüyordu. O zaman biz de 2 boyutlu bir dizi döndüren bir fonksiyon tanımlayalım.
C’de diziler pointer ile gösterilebilir demiştik. Peki, elemanları int
tipinde olan tek boyutlu bir pointer’ı nasıl ifade edebiliriz?
int *arr = (int*)malloc(n * sizeof(int))
Yukardaki ifade, n
kadar int
veri tipindeki verinin kapladığı alanın hafızadan ayrılacağını ve bu ayrılmış alanın da arr
isminde bir pointer tarafından işaret edileceğini gösterir. Bu ifadeyi yazdıktan sonra, hafızadan yer ayırma işleminin gerçekleşip gerçekleşmediğini kontrol etmek gerekir.
if (arr == NULL) {
printf("Bellek Hatası!\n");
}
Yukarda, tek boyutlu bir diziye işaret eden bir pointer tanımlamak için nasıl bir ifade yazmamız gerektiğini gördük. Peki dizimiz 2 boyutlu olsaydı nasıl yapardık?
int **arr = (int**)malloc(n * sizeof(int*))
Yukardaki ifadeyi biraz açayım. Burada, elemanları pointer olan bir pointer tanımlıyoruz. Ama gördüğünüz gibi, pointer’ın işaret ettiği pointer’lar için hafızadan ne kadarlık yer ayırmamız gerektiğini henüz tanımlamadık. Peki her bir pointer’ın hafızada ne kadarlık yer kaplaması gerektiğini nasıl hesaplayacağız? Dahası yukardaki n
sayısını nasıl bulacağız?
C’de işleri istenen hale getirmek için, tekerleği yeniden icat etmek gerekir.
n değeri, arr = [1, 2, 3, 4]
ve r = 2
için 6 olmalıdır. Çünkü 4 elemanlı bir kümenin 2 elemanlı alt küme sayısı 6’dır. O halde, bir fonksiyon yazalım. Bu fonksiyon hafıza ayırırken ihtiyaç duyacağımız n
değerini bulsun.
Normalde önce bir tane faktöryel fonksiyonu yazıp, sonra bu faktöryel fonksiyonunu kullanan bir kombinasyon fonksiyonu yazabilirdik.
int factorial(int n) {
return (n == 0) ? 1 : n * factorial(n - 1);
}
int combination(int n, int r) {
return factorial(n) / factorial(n - r) / factorial(r);
}
Ancak işlem karmaşıklığı açısından ondan az karmaşık başka bir algoritma kullanalım:
int combination(int n, int r) {
int res = 1;
for (int i = 0; i < r; i++) {
res *= (n - i);
res /= (i + 1);
}
return res;
}
O halde, 2 boyutlu dizimizin uzunluğunu hesaplayabiliriz artık. Bu arada artık yavaş yavaş combinations
fonksiyonunu oluşturmaya başlayabiliriz sanırım.
Fonksiyonumuz 2 boyutlu bir dizi döndürecek, argüman olarak da; tek boyutlu, elemanları int
olan bir dizi ve 2 adet integer değer alacak.
int **combinations(int *arr, int n, int r) {
}
Yukardaki fonksiyona daha sonradan argümanları biz şöyle vereceğiz:
int arr[] = {1, 2, 3, 4};
int n = sizeof(arr) / sizeof(int);
int r = 2;
combinations(arr, n, r);
combinations
fonksiyonunun içini doldurmaya devam edelim:
int **combinations(int *arr, int n, int r) {
int **combs = (int**)malloc(combination(n, r) * sizeof(int*);
}
Anlaşılır oldu değil mi? arr
kümesinin r
elemanlı alt kümeleri neler bunları bulmadan önce, kaç tane alt küme oluşturacağımızı combination(n, r)
ile hesaplarız. Bu fonksiyondan dönen değer ile de sizeof(int*)
'i çarparsak, kaç tane pointer için yer ayrılması gerektiğini belirlemiş oluruz. Bu pointerlar da bildiğiniz gibi bir serideki sayılara işaret ediyorlar. O halde, şimdi, her bir pointerımızı temsil etmesi için r
uzunluğunda 1 boyutlu bir pointer tanımlayalım. Her birisi için böyle bir tane pointer tanımlamaya gerek yok. Bir tane tanımlayıp, pointer’ın işaret ettiği değeri değiştirmek yeterli olur.
int **combinations(int *arr, int n, int r) {
int **combs = (int**)malloc(combination(n, r) * sizeof(int*);
int *comb = (int*)malloc(r * sizeof(int));
}
Tabi, hafızadan yer ayırma işlemi başarılı oldu mu olmadı mı kontrol etmemiz gerekiyor.
int **combinations(int *arr, int n, int r) {
int **combs = (int**)malloc(combination(n, r) * sizeof(int*);
int *comb = (int*)malloc(r * sizeof(int));
if (combs == NULL || comb == NULL) {
printf("Bellek Hatası!\n");
return NULL;
}
}
combs
ve comb
pointerlarını bulacağımız kombinasyonları hafızada tutsunlar diye oluşturduk. Peki ama kombinasyonları bulma işlemini nasıl yapacağız? Python’daki öz-yineleme nasıldı hatırlamaya çalışalım.
Fonksiyon çalışmaya başlıyordu, r
ile ilgili bir sorgu yapılıyordu sonra da arr
üzerinde bir döngü kuruluyordu ve her bir elemana göre de öz-yineleme gerçekleşiyordu. Burada da aşağı yukarı benzer bir şey yapacağız. Ama C’de Python’daki gibi indeksleme kolaylığı yoktur. Yani a[1:]
şeklinde dilimleme yapamayız. Ayrıca Python’daki yield
deyimi gibi çalışan bir deyim de yoktur. Ve *
işaretinin Python’daki ‘diziyi açma’ rolü, C’de mevcut değildir.
O zaman nasıl yapacağız?
Şimdi biz temelde ne yapmaya çalışıyoruz? Bir takım hesaplamalar sonucunda, 2 boyutlu bir diziye eleman eklemeye çalışıyoruz. 2 boyutlu bir dizi demek, satır ve sütunlar demektir. Bir önceki Python örneğinde gördüğümüz gibi, r
sayısı, bizim her bir kombinasyonun uzunluğunu veren sayı. O halde r
, sütun sayısına karşılık geliyor. combination(n, r)
ifadesi ile n
elemanlı bir kümenin r
elemanlı alt küme sayısını buluyorduk. Bu alt küme sayısı da satır sayısına karşılık geliyor.
Şu ana kadar, 2 tane değişken saydım. Satır sayısı ve sütun sayısı. Öz-yinelemede bunlar için parametre yazmamız gerekecek.
Sonra, arr
dizisinin içinde dolaşmak için de ayrıca bir parametreye ihtiyacımız olacak.
O halde, öz-yineleme fonksiyonunda şu parametrelerin olması gerekiyor:
- combs’a veri eklemek için,
int **combs
adında bir parametre, - comb’a veri eklemek için
int *comb
adında bir parametre, - arr’dan veri çekebilmek için
int *arr
adında bir parametre, - arr’ın uzunluğu için
int n
adında bir parametre, - Alt kümelerin eleman sayısı için
int r
adında bir parametre, - combs’a veri eklemek için
int row
adında bir parametre, - comb’a veri eklemek için
int col
adında bir parametre, - arr’da dolaşmak için
int index
adında bir parametre
Yukarda, hangi parametreye hangi amaçla ihtiyaç duyulacağı umarım yeterince anlaşılırdır. Şimdi, öz-yineleme
fonksiyonumuzu oluşturmaya başlayalım:
int recursive_combinations(int **combs, int *comb, int *arr, int n, int r, int row, int col, int index) {
}
Şimdi fonksiyonun içini doldurmaya başlayalım. Python fonksiyonunda yazdığımız sorguya benzer bir sorguyu burada da yazacağız. Bu kez r
'yi sıfırlamayacağız, bunun yerine col
değişkenimizi artıracağız ve col
değişkeni r
'ye eşit olunca fonksiyon öz-yinelemeyi durduracak.
int recursive_combinations(int **combs, int *comb, int *arr, int n, int r, int row, int col, int index) {
if (col == r) {
}
}
Python fonksiyonunda bu durumda boş bir tuple verisini üretip, öz-yinelemeyi sonlandırıyorduk. Ama bizim C
'de yield
gibi bir ifademiz yok. Dolayısıyla ne burada, ne de döngülerin altında, yield
deyimini kullanamayacağız. Peki ne yapacağız? col == index
durumunda, pointerlara hangi değerleri işaret etmeleri gerektiğinin atamalarını yapacağız. Çünkü yukardaki Python fonksiyonundaki adımları açarken göstermeye çalıştığım gibi, r
, bir çok defa (aslında tam olarak combination(n, r) kere) sıfırlanıyor. Burada da bir çok defa (combination(n, r) kere) col == index
durumuyla karşılaşacağız. Dolayısıyla bu koşulun sağlandığı durumlarda, öz-yineleme sonlanacak ve pointerların işaret ettiği değerleri kaybedeceğiz. O halde bu kısımda atama işlemlerini yapmamız gerekiyor.
Atama işlemlerini de dahil ederek fonksiyonu tekrar yazıyorum:
int recursive_combinations(int **combs, int *comb, int *arr, int n, int r, int row, int col, int index) {
if (col == r) {
combs[row] = (int*)malloc(r * sizeof(int));
if (combs[row] == NULL) {
printf("Bellek Hatası!");
return 1;
}
for (int j = 0; j < r; j++) {
combs[row][j] = comb[j];
}
return row + 1;
}
}
Şimdi burada ne yaptık?
Daha önce combs
pointerlara işaret eden bir pointer demiştik değil mi? Bu pointerların hafızada ne kadarlık yer ayırması gerektiğini hesaplamamıştık hatırlıyorsanız. Şimdi bunları hesaplama sırası geldi.
combs[row] = (int*)malloc(r * sizeof(int));
ifadesi ile combs
'un ilgili satırı için yeterli alanı ayırıyoruz. Sonra hafıza ayırma işlemi yapılmış mı yapılmamış mı kontrol ediyoruz.
Daha sonra da, comb
'dan verileri alıp, combs
'un ilgili satırının ilgili sütununa ekliyoruz. Sonra da row
'u 1 birim artırıp geri döndürüyoruz. O halde fonksiyonun geri kalan kısmında da comb
'a ekleme yapmamız gerek. Fonksiyonu doldurmaya devam edelim.
int recursive_combinations(int **combs, int *comb, int *arr, int n, int r, int row, int col, int index) {
if (col == r) {
combs[row] = (int*)malloc(r * sizeof(int));
if (combs[row] == NULL) {
printf("Bellek Hatası!");
return 1;
}
for (int j = 0; j < r; j++) {
combs[row][j] = comb[j];
}
return row + 1;
}
for (int i = index; i < n; i++) {
comb[col] = arr[i];
row = recursive_combinations(combs, comb, arr, n, r, row, col + 1, i + 1);
}
return row;
}
Burada, neden ilk kısımda, row + 1
'i döndürdük, alttaki döngüde neden öz-yinelemeden dönen sonucu row
'a eşitledik anlamaya çalışalım. Madem ki, öz-yineleme durunca veriler kaybolacak, row
'u artırıp, yeni değeri haline getirmezsek, hep 0. satıra ekleme yaparız. O halde satır sayısını artırmamız lazım. Öz-yineleme durduğunda row = row + 1 olur ve yani col = r
olduğunda, row
1 birim artmış olur.
Python’daki fonksiyonumuz ile benzer bir algoritmaya sahip bir algoritmayı C’de bu şekilde yazabiliriz. Şimdi sıra geldi, daha önce yazdığımız int **combinations
fonksiyonunda int recursive_combinations
fonksiyonunu çağırmaya.
Fonksiyonun geri kalan kısmını tamamlayalım. Ama önce, bütün işlemler bittikten sonra yapmamız gereken işlemleri yapacak olan, hafızayı serbest bırakma fonksiyonunu yazalım.
void free_memory(int **arr, int length) {
for (int i = 0; i < length; i++) {
free(arr[i]);
}
free(arr);
}
Buradaki free
fonksiyonu stdlib.h
'dan gelen fonksiyondur.
Şimdi, int **combinations
fonksiyonunun kalan kısmını yazabiliriz.
int **combinations(int *arr, int n, int r) {
int **combs = (int **)malloc(combination(n, r) * sizeof(int *));
int *comb = (int *)malloc(r * sizeof(int));
if (combs == NULL || comb == NULL) {
printf("Bellek Hatası!\n");
return NULL;
}
recursive_combination(combs, comb, arr, n, r, 0, 0, 0);
free(comb);
return combs;
}
Evet, programımızı oluşturduk ama bir test yapalım hemen. Aşağıda, main
fonksiyonu eklenmiş bir program görüyorsunuz. Bu programı derleyip çalıştırabilirsiniz. Bu arada sadece combination
fonksiyonundan dönen değerin tipini int
'ten unsigned long long
'a çeviriyorum ki yüksek n değerlerinde hafıza problemi yaşamayalım.
#include <stdio.h>
#include <stdlib.h>
unsigned long long combination(int n, int r) {
unsigned long long result = 1;
for (int i = 0; i < r; i++) {
result *= (n - i);
result /= (i + 1);
}
return result;
}
int recursive_combination(int **combs, int *comb, int *arr, int n, int r, int row, int col, int index) {
if (col == r) {
combs[row] = (int*)malloc(r * sizeof(int));
if (combs[row] == NULL) {
printf("Bellek Hatası!");
return 1;
}
for (int j = 0; j < r; j++) {
combs[row][j] = comb[j];
printf("%d ", combs[row][j]);
}
printf("\n");
return row + 1;
}
for (int i = index; i < n; i++) {
comb[col] = arr[i];
row = recursive_combination(combs, comb, arr, n, r, row, col + 1, i + 1);
}
return row;
}
int **combinations(int *arr, int n, int r) {
int **combs = (int **)malloc(combination(n, r) * sizeof(int *));
int *comb = (int *)malloc(r * sizeof(int));
if (combs == NULL || comb == NULL) {
printf("Bellek Hatası!\n");
return NULL;
}
recursive_combination(combs, comb, arr, n, r, 0, 0, 0);
free(comb);
return combs;
}
void free_memory(int **arr, int len) {
for (int i = 0; i < len; i++) {
free(arr[i]);
}
free(arr);
}
int main() {
int arr[] = {1, 2, 3, 4, 5, 6};
int n = sizeof(arr) / sizeof(int);
int r = 2;
int **test = combinations(arr, n, r);
free(test);
return 0;
}
Bu kodu derleyip çalıştırdığımızda şöyle bir çıktı alırız:
1 2
1 3
1 4
1 5
1 6
2 3
2 4
2 5
2 6
3 4
3 5
3 6
4 5
4 6
5 6
Evet, Python’da yazdığımız fonksiyonu C’de de yazdık. Şimdi şöyle bir şey yapmak istiyorum. Bir struct
oluşturmak istiyorum. Bu struct
, hem arr
dizisini, hem n
değerini, hem r
değerini, hem de kombinasyonların değerlerini tutsun. Sonra da bu struct
'u Python’da kullanalım.
Şöyle bir struct
tanımlıyorum.
typedef struct class_combinations {
int *arr;
int n;
int r;
unsigned long long len;
int **combinations;
} class_combinations;
Buradaki len niteliği, combination(n, r)
'den gelen değer olacak. O halde örnek yapıcı fonksiyonumuzu oluşturmaya geçelim şimdi. Şimdi, istersek, doğrudan class_combinations
yapısını döndüren bir fonksiyon yazabiliriz. Veya bu struct
'u işaret eden bir pointer
'ı döndüren bir fonksiyon da yazabiliriz.
Aşağıda, class_combinations
yapısını işaret eden bir pointer
'ı döndüren bir fonksiyon yazdık, adına da Combinations dedik.
class_combinations *Combinations(int *arr, int n, int r) {
class_combinations *self = (class_combinations*)malloc(sizeof(class_combinations));
if (self == NULL) {
printf("Bellek Hatası!");
return NULL;
}
unsigned long long len = combination(n, r);
int **data = combinations(arr, n, r);
self -> arr = arr;
self -> n = n;
self -> r = r;
self -> len = len;
self -> combinations = data;
return self;
}
Burada, class_combinations
ifadesinin artık int
veya char
gibi bir tipe karşılık geldiğini dikkatiniz çekmiştir sanıyorum. Dolayısıyla class_combinations *Combinations()
ifadesi, class_combinations
sınıfından bir veriyi tutan bir pointerı döndüren bir fonksiyondur.
Şimdi, sıra geldi hazırladığımız bu struct
'ü Python’a aktarmaya. Önce bir tane paylaşılabilir bağlantı kitaplığı oluşturalım.
Python scriptine ctypes’ı import edelim.
import ctypes
Sonra da, C
'de tanımladığımız struct
'un verilerini aktaracağımız, ctypes.Structure
sınıfını miras alan bir sınıf ve bu sınıfın niteliklerini tanımlayalım.
import ctypes
class Combinations(ctypes.Structure):
_fields_ = [
("arr", ctypes.POINTER(ctypes.c_int)),
("n", ctypes.c_int),
("r", ctypes.c_int),
("len", ctypes.c_ulonglong),
("combinations", ctypes.POINTER(ctypes.POINTER(ctypes.c_int)))
]
C’de yazdığımız struct
'a benzer bir structu
Python’da bu şekilde yazarız.
Şimdi, sıra geldi, dinamik bağlantı kitaplığımızı oluşturmaya, bu kitaplığı şöyle oluşturuyoruz:
gcc -shared -o combinations.dll combinations.c
Burada c dosyanın adının combinations.c
olduğu varsayılmıştır. Neyse, dinamik bağlantı kitaplığımız oluştu. Python kodlarını yazmaya devam edelim. Önce kitaplığı okutalım.
lib = ctypes.CDLL("./combinations.dll")
Şimdi, Combinations
fonksiyonunun argüman tiplerini ve ne tip bir değer döndürdüğünü belirtelim. Ayırca, free_memory
fonksiyonunun da argüman tiplerini tanımlayalım ki, sonradan bu fonksiyonu kullanarak, hafızayı serbest bırakalım.
lib = ctypes.CDLL("./combinations.dll")
lib.Combinations.argtypes = [ctypes.POINTER(ctypes.c_int), ctypes.c_int, ctypes.c_int]
lib.Combinations.restype = ctypes.POINTER(Combinations)
lib.free_memory.argtypes = [ctypes.POINTER(ctypes.POINTER(ctypes.c_int)), ctypes.c_int]
Biraz açıklamak gerekirse, Python’daki ctypes.POINTER(ctypes.c_int)
, C’deki (int*)
ifadesi ile eşleşir. ctypes.POINTER(ctypes.POINTER(ctypes.c_int))
ifadesi de (int**)
ifadesi ile eşleşir.
Şimdi, hemen hemen herşeyi hazırladık. Fonksiyonumuzu birazdan kullanabiliriz ama önce ufak bir sorunu da halletmemiz lazım.
Biz bir Python listesini, bir pointer olarak nasıl kullanacağız?
Onu da aşağıdaki kodda gösterelim:
# arr = range(1, 5)
# n = len(arr)
# r = 2
# c_arr = (ctypes.c_int * n)(*arr)
Gördüğünüz gibi, arr
'ı, (ctypes.c_int * n)(*arr)
ifadesiyle bir pointer’a çevirdik. Şimdi kodun geri kalan kısmını yazalım:
arr = range(1, 5)
n = len(arr)
r = 2
c_arr = (ctypes.c_int * n)(*arr)
test = lib.Combinations(c_arr, n, r)
for i in range(test.contents.len):
for j in range(test.contents.r):
print(test.contents.combinations[i][j], end=" ")
print()
lib.free_memory(test.contents.combinations, test.contents.len)
Son yazdığımız Python kodunu düzgün bir şekilde yazalım:
import ctypes
class Combinations(ctypes.Structure):
_fields_ = [
("arr", ctypes.POINTER(ctypes.c_int)),
("n", ctypes.c_int),
("r", ctypes.c_int),
("len", ctypes.c_ulonglong),
("combinations", ctypes.POINTER(ctypes.POINTER(ctypes.c_int)))
]
lib = ctypes.CDLL("./combinations.dll")
lib.Combinations.argtypes = [ctypes.POINTER(ctypes.c_int), ctypes.c_int, ctypes.c_int]
lib.Combinations.restype = ctypes.POINTER(Combinations)
lib.free_memory.argtypes = [ctypes.POINTER(ctypes.POINTER(ctypes.c_int)), ctypes.c_int]
arr = range(1, 5)
n = len(arr)
r = 2
c_arr = (ctypes.c_int * n)(*arr)
test = lib.Combinations(c_arr, n, r)
for i in range(test.contents.len):
for j in range(test.contents.r):
print(test.contents.combinations[i][j], end=" ")
print()
lib.free_memory(test.contents.combinations, test.contents.len)
Bu örnekte, C’de yazdığımız struct’a işaret eden bir pointer kullandık. Peki struct
'u kaldırıp, sadece fonksiyonu kullanmak isteseydik?
#include <stdio.h>
#include <stdlib.h>
unsigned long long combination(int n, int r) {
unsigned long long result = 1;
for (int i = 0; i < r; i++) {
result *= (n - i);
result /= (i + 1);
}
return result;
}
int recursive_combination(int **combs, int *comb, int *arr, int n, int r, int row, int col, int index) {
if (col == r) {
combs[row] = (int*)malloc(r * sizeof(int));
if (combs[row] == NULL) {
printf("Bellek Hatası!\n");
return 1;
}
for (int j = 0; j < r; j++) {
combs[row][j] = comb[j];
}
return row + 1;
}
for (int i = index; i < n; i++) {
comb[col] = arr[i];
row = recursive_combination(combs, comb, arr, n, r, row, col + 1, i + 1);
}
return row;
}
int **combinations(int *arr, int n, int r) {
int **combs = (int **)malloc(combination(n, r) * sizeof(int *));
int *comb = (int *)malloc(r * sizeof(int));
if (combs == NULL || comb == NULL) {
printf("Bellek Hatası!\n");
return NULL;
}
recursive_combination(combs, comb, arr, n, r, 0, 0, 0);
free(comb);
return combs;
}
void free_memory(int **arr, int len) {
for (int i = 0; i < len; i++) {
free(arr[i]);
}
free(arr);
}
Bunu da Python’a şöyle aktarırdık:
import ctypes
lib = ctypes.CDLL("./combinations.dll")
lib.combinations.argtypes = [ctypes.POINTER(ctypes.c_int), ctypes.c_int, ctypes.c_int]
lib.combinations.restype = ctypes.POINTER(ctypes.POINTER(ctypes.c_int))
lib.free_memory.argtypes = [ctypes.POINTER(ctypes.POINTER(ctypes.c_int)), ctypes.c_int]
arr = range(1, 5)
n = len(arr)
r = 2
c_arr = (ctypes.c_int * n)(*arr)
test = lib.combinations(c_arr, n, r)
print(test[0][0])
Evet arkadaşlar, buraya kadar anlattıklarım buzdağının görünen kısmından ibaret. Konuyu mümkün olduğunca açmaya çalıştım. Elbette değinmediğim ayrıntılar da var. Eğer, benzer bir katkı vermek isterseniz, konudan devam edebilirsiniz.
Okuduğunuz için teşekkürler, hatalı kısımları bildirirseniz, o kısımları düzeltiriz.
Herkese iyi çalışmalar.