GLib Tek Bağlı Liste ile Örnek Proje, Kelime Sayacı

Açık Kaynak, Programlama, tutorial, Uzun Kod İçerikli, ytulinux

Geçen gün farketmemişim, glib kütüphanesinde tek bağlı liste de bulunuyormuş. Onu da anlatmadan geçmenin pek mantıklı olmayacağını düşündüm, ancak çift bağlı liste ile çoğunlukla aynı özellikleri taşıdığı için örnek bir proje yapıp olayı onun üstünden anlatmak daha mantıklı olacaktır.

Önceki makaleyi okumayanların, http://www.huseyinalb.com/glib/ adresinden okumalarını tavsiye ediyorum. Orada anlatılan bir çok şeyi burada atlayacağımdan emin olabilirsiniz :)

Öncelikle GSList, yani tek bağlı listenin struct‘ına bakalım.

typedef struct {
gpointer data;
GSList *next;
} GSList;

Görüleceği üzere, çift bağlı listeden tek farkı kendisinden önceki elemana atıf vermemesi.

Örnek projemiz, bir dosyadan alınan yazıda bulunan kelimeleri sayıp, miktarlarına göre sıralamak. Öncelikle, önceki anlatımdan bunun önemli bir farkını belirtmekte yarar var. Bu sefer glib’de bulunan hazır fonksiyonlardan tamamıyla faydalanmamız pek mümkün değil, çünkü kendi tanımladığımız bir struct’ı kullanmak zorundayız.

typedef struct
{
char * kelime;
int miktar;
} kelimeTip;

Bu struct’ta kelimeler ve bu kelimelerden kaç tane bulunduğu saklanacak. Okuduğumuz yazıdan her gelen kelime, listede aranacak, eğer zaten varsa listedeki kelimenin miktarı arttırılacak, eğer yoksa listeye eklenecek. Bunun için gereken fonksiyonların prototiplerini yazmak gerekirse;

Dosyadaki tüm yazıları okuyup bir (char *) işaretçiye atan fonksiyonumuz:
char * dosyaOku(char *);

Dosyadan okunmuş olan yazıdan her seferinde bir kelime geri döndüren fonksiyonumuz: (burada static değişken ile ilgili bir güzellik öğreneceksiniz)
kelimeTip * kelimeAl(char * yazi);

Her gelen kelimeyi listeye ekleyen fonksiyon:
GSList * ekle(GSList * ,kelimeTip *);

Daha önce glibde bulunan bir fonksiyon ile karşılaştırma işini halletmiştik. Ancak bu sefer kendi tanımladığımız struct ile bu işi yaptığımız için, yani doğrudan char* değişkeni eklemek yerine listeye kelimeTip * değişkeni eklediğimiz için kendi karşılaştırma fonksiyonlarımızı tanımlamak zorundayız ki sıralama ve bulma fonksiyonlarımız düzgün çalışsın. koda bakınca bunlar daha iyi anlaşılacaktır.(amin :D)
gint karsilastirici(gpointer, gpointer);
gint mkarsilastirici(gpointer, gpointer);

En sonda hangi kelimeden kaç tane bulunduğunu yazdıran fonksiyonumuz:
void yazdir (gpointer);

Evet genel itibariyle program bunlardan oluşuyor.

Programın ana kısmını olabildiğince basit halde tutmaya çalıştım. Zaten kütüphane kullanmanın en önemli avantajı, sizi fonksiyon yazmaya zorlayıp, main fonksiyonunda az komut kullanmanızı sağlamaları.

Programın ana kısmını incelemek gerekirse:

// Açacağımız dosyanın ismini konsoldan alıyoruz

int main (int argc, char * argv[])
{
// dosyadan alacağımız yazı
char * yazi;
// tek bağlı listemiz
GSList * liste = NULL;
// her seferinde dosyadan okuyacağımız kelimeye ait struct

kelimeTip * kelime;
// eğer komut düzgün girilmediyse uyarı verilmeli
if (argc != 2)
{
printf(“Kullanim: ./tlinkli.o dosyaadi\n”);
return 0;
}
// kullanıcının girdiği dosya yok
if (!(yazi = dosyaOku(argv[1])))
{
printf(“Dosya acilamadi\n”);
return 0;
}
// Ana döngümüz. Her seferinde bir kelime alınıyor, alınan kelime listeye ekleniyor.
while (kelime = kelimeAl(yazi))
{
liste = ekle(liste,kelime);
}
// liste sıralanıyor. Bakın burada kendi yazdığımız karşılaştırma fonksiyonunu kullanıyoruz.
liste = g_slist_sort(liste, ((GCompareFunc) mkarsilastirici));
// Listeyi ekran yazıyoruz.
g_slist_foreach(liste,(GFunc)yazdir,NULL);
// aldığımız bellek alanını bırakıyoruz.
g_slist_free(liste);
free(yazi);
return 0;
}

DosyaOku fonksiyonuna geçelim. Bu fonksiyonda göreceğiniz üzere herhangi bir numara yok.
char * dosyaOku(char * ad)
{
// Tampon, yukarıda #define TAMPON 512 şeklinde tanımlanmış durumda, bunu tam koddan görebilirsiniz, en sona eklenecek.
char tampon[TAMPON];
FILE * dosya1;
char * yazi;
int n;
yazi = (char *) malloc (sizeof(char)*TAMPON);
yazi[0]=’\0′;
if (!(dosya1 = fopen(ad,”r”)))
{
return 0;
}

while (n = fread(tampon, 1, sizeof(char)* TAMPON-1, dosya1))
{
tampon[n]=’\0′;
strcat (yazi,tampon);
yazi = (char *) realloc(yazi,strlen(yazi)+TAMPON-1);
}
return yazi;
}

kelimeAl fonksiyonumuzda ise, esasında GSList ile ilgili olarak söylenecek pek söz yok, ancak static değişken kullanarak kalınan yerden devam etmek için yapabildiğimiz bir çakallığa örnek göstermek açısından güzel bir mantık dahilinde çalışıyor.

kelimeTip * kelimeAl(char * yazi)
{

// burada yapmaya çalıştığımız şey şu, her seferinde char * yazi değişkenimizi yollayıp sıradaki kelimeyi alıyoruz. Bunun için sadece başta set ettiğimiz ve nerede kaldıysa oradan devam edecek static bir int i değişkenimiz var. Ancak bir sıkıntımız daha var. Ya başka bir değişken yollanırsa? o zaman y ile karşılaştırıyoruz, eğer farklıysa o zaman önceki seferden farklı bir değişken gelmiştir diyerek ilk kelimeden başlamak için i yi tekrar 0 yapıyoruz.
static int i=0;
static char* y;
int j=0;
// klm birimini oluşturuyoruz ki yazidan gelen kelimeyi ona atabilelim.
kelimeTip * klm = g_new(kelimeTip,1);
klm->kelime = (char * )malloc(sizeof(char) * 64);

if (y!=yazi){y=yazi;i=0;}
// is alpha numeric fonksiyonu, gelen karakter eğer harf ya da sayı ise 1 döndürür. Burada ilk karaktere kadar ilerlemeye çalışıyoruz, tabi yazıyı taşırmadan, bunun için de ‘\0’ karakterini kontrol ediyoruz.

while (!isalnum(y[i]) && y[i]!=’\0′)i++;
// ilk karakter olmayan yere kadar ilerle
while (isalnum(y[i]) && y[i]!=’\0′){klm->kelime[j]=y[i];i++;j++;}
klm->kelime[j]=’\0′;
if (klm->kelime[0]==’\0′){ g_free(klm);return 0;}
klm->kelime = (char * ) realloc(klm->kelime,sizeof(char)*strlen(klm->kelime)+1);
klm->miktar = 1;
return klm;
}

Açıkcası şu geçtiğimiz fonksiyondaki alengirli kısımlara pek kafayı takmaya gerek yok, kısaca her seferinde bir kelime alıyor. GSLib i denemek için ihtiyacımız olan kısım da bu zaten.
Ekle fonksiyonumuz ise aşağıdaki gibi.
GSList * ekle(GSList * liste, kelimeTip * klm)
{

GSList * kontrol;
// g_slist_find_custom, kendi verdiğimiz karşılaştırma fonksiyonumuz ile eleman bulmamızı sağlayan fonksiyondur. geriye GSList * döndürür ki bu liste elemanına ait işaretçidir. Karşılaştırıcı fonksiyonumuzu, bundan hemen sonra göreceğiz.
// bu arada döngüyü de anlatalım. Eğer gelen kelime daha önce eklenmiş bir kelime ise, g_slist_find_custom fonksiyonu yardımı ile nerede olduğunu öğrenmiş olduğumuz eleman yardımıyla kelimeye ulaşıp miktarını arttırıyoruz. Eğer eklenmemiş bir kelime ise o zaman listeye ekliyoruz. Tabi eklerken dikkat etmemiz gereken konu şu, kelimeye ait miktarı 1 yapmamız gerekiyor, ki zaten bunu kelimeAl fonksiyonunda yapmıştık.
if (kontrol = g_slist_find_custom(liste, klm, karsilastirici))
{
// Bakın burada önemli bir şey var. g_slist_find_custom, GSList * döndürdü. Bu durumda bizim kelimeTip * şeklindeki birimimize erişmemiz için, döndürülen işaretçinin içindeki data işaretçisini  (ilk baştaki GSList e ait struct tanımına bakın) typecast ile (kelimeTip *) a dönüştürüp o şekilde kullanmamız gerekiyor. Bu olayı akıl edebilmek tam tamına bir saatimi yedi, bir sürü hatayla boğuştuktan sonra farkettim ve typecast yaptım. Dolayısıyla üzerinde biraz düşünmenizi tavsiye ederim.
((kelimeTip *)(kontrol->data))->miktar++;

}
else
{
// kelime mevcut değil, ekliyoruz.
liste = g_slist_append(liste,klm);
}

// Burada düşünülmesi gereken bir konu daha var. Genellikle liste üzerinde küçük de olsa değişiklik yapan fonksiyonlar nedense geriye GSList * liste değişkenini döndürüyor. Oysa ki elimizde zaten başlangıç adresini tutan liste değişkenimiz var değil mi :) Ne yazık ki kazın ayağı öyle değil. Düşünün ki listeden bir elemanı sildik, veya listede bir sıralama yaptık ve ilk eleman olarak kullandığımız birim sonuncu oldu ya da ortaya kaydı. Bu durumda bize ilk elemanın adresi döndürülmeli ki onu kullanma fırsatı bulalım. Yoksa tuhaf durumlarla karşılaşabilirsiniz. Örneğin sort yaptıktan sonra listenin yarısı kaybolur gdb de onun peşinden bir oraya bir oraya sürüklenirsiniz.
return liste;
}

Ve burada da, g_slist_find_custom fonksiyonunda kullandığımız karşılaştırma fonksiyonu:

gint karsilastirici(gpointer klm1, gpointer  klm2)
{
//görüleceği üzere, önceki fonksiyonda anlattığım typecast işlemi burası için de geçerli.
return g_ascii_strcasecmp(((kelimeTip *)klm1)->kelime,((kelimeTip*)klm2)->kelime);
}

Hazır anlatmışken, diğer karşılaştırma fonksiyonunu da gösterelim. Hatırlayacağınız kadarıyla, main fonksiyonunda bir sort fonksiyonumuz vardı ve onda mkarsilastirici fonksiyonumuzu kullanmıştık. Demin yazdığımız karşılaştırma fonksiyonu kelime->kelime yani kelimenin kendisini, char * kısmı karşılaştırmak için kullandığımız fonksiyon. Bir de miktarları karşılaştıran bir fonksiyona ihtiyacımız var ki en çok kullanılan kelimeleri bulalım.

gint mkarsilastirici(gpointer klm1, gpointer klm2)
{
return ((kelimeTip *)klm1)->miktar – ((kelimeTip*)klm2)->miktar;
}
Görüleceği üzere bu fonksiyon aslında strcmp fonksiyonuna oldukça benziyor. Büyükse pozitif, eşitse 0, küçükse negatif. (strcmp de 1 ve -1 olması lazım yamulmuyorsam)

Vee gelelim listeyi yazdırmamızı sağlayan fonksiyona:
void yazdir (gpointer lst)
{
printf(“Kelime: %s, Miktar: %d\n”,((kelimeTip *) lst)->kelime,((kelimeTip *) lst)->miktar);
}
Görüleceği üzere, bir önceki makaledeki olaydan tek farkı, bizim typecast yardımı ile elemanımıza ulaşmamız, çünkü öncekinde olduğu gibi direk char * bir değişkeni değil, kendi tanımladığımız structı listede tutuyoruz.

Programın tam kodu aşağıdaki şekilde:

#include <stdio.h>
#include <glib.h>
#include <string.h>
#include <stdlib.h>
#define TAMPON 512

typedef struct
{
char * kelime;
int miktar;
} kelimeTip;

char * dosyaOku(char *);
GSList * ekle(GSList * ,kelimeTip *);

gint karsilastirici(gpointer, gpointer);
gint mkarsilastirici(gpointer, gpointer);
kelimeTip * kelimeAl(char * yazi);
void yazdir (gpointer);

int main (int argc, char * argv[])
{
char * yazi;
GSList * liste = NULL;
kelimeTip * kelime;
if (argc != 2)
{
printf(“Kullanim: ./tlinkli.o dosyaadi\n”);
return 0;
}
if (!(yazi = dosyaOku(argv[1])))
{
printf(“Dosya acilamadi\n”);
return 0;
}
while (kelime = kelimeAl(yazi))
{
liste = ekle(liste,kelime);
}

liste = g_slist_sort(liste, ((GCompareFunc) mkarsilastirici));
g_slist_foreach(liste,(GFunc)yazdir,NULL);
g_slist_free(liste);
free(yazi);
return 0;
}

char * dosyaOku(char * ad)
{
char tampon[TAMPON];
FILE * dosya1;
char * yazi;
int n;
yazi = (char *) malloc (sizeof(char)*TAMPON);
yazi[0]=’\0′;
if (!(dosya1 = fopen(ad,”r”)))
{
return 0;
}

while (n = fread(tampon, 1, sizeof(char)* TAMPON-1, dosya1))
{
tampon[n]=’\0′;
strcat (yazi,tampon);
yazi = (char *) realloc(yazi,strlen(yazi)+TAMPON-1);
}
return yazi;
}

GSList * ekle(GSList * liste, kelimeTip * klm)
{
GSList * kontrol;
if (kontrol = g_slist_find_custom(liste, klm, karsilastirici))
{
((kelimeTip *)(kontrol->data))->miktar++;
}
else
{
liste = g_slist_append(liste,klm);
}
return liste;
}

gint karsilastirici(gpointer klm1, gpointer  klm2)
{
return g_ascii_strcasecmp(((kelimeTip *)klm1)->kelime,((kelimeTip*)klm2)->kelime);
}

gint mkarsilastirici(gpointer klm1, gpointer klm2)
{
return ((kelimeTip *)klm1)->miktar – ((kelimeTip*)klm2)->miktar;
}

kelimeTip * kelimeAl(char * yazi)
{
static int i=0;
static char* y;
int j=0;
// klm birimini oluşturuyoruz ki yazidan gelen kelimeyi ona atabilelim.
kelimeTip * klm = g_new(kelimeTip,1);
klm->kelime = (char * )malloc(sizeof(char) * 64);

if (y!=yazi){y=yazi;i=0;}

while (!isalnum(y[i]) && y[i]!=’\0′)i++;
while (isalnum(y[i]) && y[i]!=’\0′){klm->kelime[j]=y[i];i++;j++;}
klm->kelime[j]=’\0′;
if (klm->kelime[0]==’\0′){ g_free(klm);return 0;}
klm->kelime = (char * ) realloc(klm->kelime,sizeof(char)*strlen(klm->kelime)+1);
klm->miktar = 1;
return klm;
}

void yazdir (gpointer lst)
{
printf(“Kelime: %s, Miktar: %d\n”,((kelimeTip *) lst)->kelime,((kelimeTip *) lst)->miktar);
}

Bu da makefile dosyası (önceki makalede anlattığım gibi, glib-2.0 yerleriniz farklı olabilir. bu yerleri whereis glib-2.0 yazarak bulabilirsiniz.):
tlinkli.o: tlinkli.c
gcc -gstabs -lglib-2.0 -I/usr/lib/glib-2.0/include -I/usr/include/glib-2.0 tlinkli.c -o tlinkli.o

Bu yazı toplamda 4298, bugün ise 0 kez görüntülenmiş

3 Comments
  • masis

    hocam k.bakmayın ama bu kadar uzun ve güzel bi kod yayımlamışsınız
    daha okunaklı renkli ve anlatımlı olsa sizce de daha iyi olmaz mıydı.
    Tekrardan tşk kod için

    • Anonymous

      deneme

  • huseyinalb

    baya bir açıklama da yazmışım aslında ama konu veri yapıları olunca çizim ile anlatmadan anlaşılması zor oluyor, tabi ve ortalığın function pointer, void pointer kaynıyor olması da cabası ama ne yazık ki pek user friendly bir adam değilim, daha da kötüsü bu makale ileri c bilgisi gerektiriyor :)
    bu makalenin amacı linkli listeyi anlatmak değil aslında, glib kullanılarak nasıl uygulanabileceğini anlatmak dolayısıyla eğer sorun buysa bu makaleden önce internetten linkli listenin c ile struct kullanarak nasıl uygulanacağını, void işaretçileri, fonksiyon işaretçilerini vesaire okuyup iyice anlamanız gerekiyor.