Python ile Metinlerde Frekans Analizi

Elimizdeki bir metinde hangi karakterlerin olduğunu, hangi karakterin kaç kere geçtiğini ve metnin yüzde kaçını oluşturduğunu ölçmek için kullanabileceğimiz bir sınıf oluşturdum. Sınıfın yaptığı, Python’da yer alan veri yapılarını kullanarak girilen metnin frekans analizini yapmak.

Ne işe yaradığına geleyim. Program herhangi bir jargonda kullanılan harf, rakam ve karakterlerin analizini yapıyor. Yani mesela çocuk hikayelerini verdiğinizde alıntılar çok olduğu için tırnak işaretlerinin frekansını ve yüzdesini bazı harflere göre daha yüksek bulurken, sayıların az olmasını bekleriz. İşte bu analizi yapıp, hangi harf daha çok kullanılıyor diye bir veri elde ediyoruz. Bunu yerine koyma ile şifrelenmiş(Substitution) metinlerin orijinal hallerini elimizde sadece şifreli metin varken bulurken kullanıyoruz.

Diğer özelliği ise sınıf Python3 ile yazıldığından dolayı, Arapça bir metinde de, Japonca bir metinde de frekans analizini yapabiliyor. Bunun için hangi dilde metin girişi yaptığınızı belirtmeniz de gerekmiyor. Ayrıca sekme, alt satır gibi ifadeler de aynı şekilde ASCII veya UTF8 içerisinde yer aldıklarından dolayı frekans analizinde karşılıklarını buluyorlar.

Yaptığım denemelerde çok uzun metinler kullanmadığım halde birkaç karakter sapmayla oldukça anlaşılır çözümler elde ettim. Tabii ki burda metnin uzunluğu önemli. Metin yeterince uzun değilse elde edeceğiniz sonuçlarda hata oldukça yüksek çıkacaktır.

Aşağıda önce sınıfın kendisi, daha sonra sınıfı kullanan bir örnek kod yer alıyor. Sormak, eklemek veya eleştirmek istediğiniz noktalar için her zaman bana ulaşabilirsiniz.

'''
Created on 4 Şub 2014

@author: Güray Yıldırım
'''

class Analiz(object):
    '''
    Frekans analizi yapar.
    '''

    def __init__(self, metin, frekanslar = {}):
        '''
        Constructor
        '''
        self.giris = metin
        self.frekanslar = frekanslar

        # Kullanıcı frekans girmediyse sen bul
        if not self.frekanslar:
            self.frekansHesapla()

    def frekansHesapla(self):
        """
        O anki frekansı hesaplar.

        Constructor tarafından çağırılır. 
        """
        self.harfler = tuple(set(self.giris))        
        self.frekanslar = {harf: self.giris.count(harf) for harf in self.harfler}

    def frekansBul(self):
        self.siraliListe =  sorted(list(self.frekanslar.items()), key = self.last, reverse = True)
        return self.siraliListe

    def last(self,x):
        """
        Eldeki metnin son karakterini döndürür.

        İsteyen fonksiyon bunu çağırabilir
        """
        return x[-1]

    def yuzdeBul(self):
        """
        Frekans yüzdelerini bulur ve döndürür.

        Parametre almaz.
        """
        # Burda frekanslar hesaplanmış olacak.
        toplam = sum(self.frekanslar.values())
        yuzdeler =  {harf: self.frekanslar[harf]*100.0/toplam for harf in self.harfler}

        self.siralanmis =  sorted(list(yuzdeler.items()), key = self.last, reverse = True)
        return self.siralanmis

Sınıfın örnek kullanımı:

B = Analiz(open("sifrelimetin", encoding="utf8").read())

# Kullanılabilecek fonksiyonlar
print(dir(B))

# Frekansları
for i,j in B.frekansBul():
    print(i, " => ", j)

print("*"*100)

# Yüzdeleri
for i,j in B.yuzdeBul():
    print("%s => %.5f" %(i,j))

Yazının altında yer alan yorumda, Python standart kütüphanesi içerisinde yer alan collections modülünün Counter sınıfını gördüm. Kodu da ona göre güncelleyince, o sınıfı kullanarak yapmak isterseniz alttaki kodu kullanabilirsiniz. Hız testi yapmadım ancak kütüphanelerin normal kodlara göre daha hızlı çalıştığını biliyoruz.

'''
Created on 4 Şub 2014

@author: Güray Yıldırım
'''

class Analiz(object):
    '''
    Frekans analizi yapar.
    '''

    def __init__(self, metin, frekanslar = {}):
        '''
        Constructor
        '''
        self.giris = metin
        self.frekanslar = frekanslar

        # Kullanıcı frekans girmediyse sen bul
        if not self.frekanslar:
            self.frekansHesapla()

    def frekansHesapla(self):
        """
        O anki frekansı hesaplar.

        Constructor tarafından çağırılır.
        """
        from collections import Counter
        self.frekanslar = Counter(self.giris)
        self.harfler = self.frekanslar.keys()

    def frekansBul(self):
        self.siraliListe =  sorted(list(self.frekanslar.items()), key = self.last, reverse = True)
        return self.siraliListe

    def last(self,x):
        """
        Eldeki metnin son karakterini döndürür.

        İsteyen fonksiyon bunu çağırabilir
        """
        return x[-1]

    def yuzdeBul(self):
        """
        Frekans yüzdelerini bulur ve döndürür.

        Parametre almaz.
        """
        # Burda frekanslar hesaplanmış olacak.
        toplam = sum(self.frekanslar.values())
        yuzdeler =  {harf: self.frekanslar[harf]*100.0/toplam for harf in self.harfler}

        self.siralanmis =  sorted(list(yuzdeler.items()), key = self.last, reverse = True)
        return self.siralanmis

 

Yukarıdaki örnek kullanımda sifrelimetin dosyasında şifrelenmiş bir metin yer alıyor. Bu metnin frekans analizinin sonucu şu şekilde çıktı:

****************************************************************************************************
****************************************************************************************************
['__class__', '__delattr__', '__dict__', '__dir__', '__doc__', '__eq__', '__format__', '__ge__', '__getattribute__', '__gt__', '__hash__', '__init__', '__le__', '__lt__', '__module__', '__ne__', '__new__', '__reduce__', '__reduce_ex__', '__repr__', '__setattr__', '__sizeof__', '__str__', '__subclasshook__', '__weakref__', 'frekansBul', 'frekansHesapla', 'frekanslar', 'giris', 'harfEslestir', 'harfler', 'last', 'yuzdeBul']
q  =>  2020
w  =>  1448
e  =>  1007
r  =>  975
t  =>  938
y  =>  761
u  =>  647
ı  =>  612
o  =>  595
p  =>  548
ğ  =>  525
ü  =>  507
a  =>  490
s  =>  456
d  =>  370
f  =>  352
g  =>  309
h  =>  289
j  =>  226
k  =>  209
l  =>  193
ş  =>  172
i  =>  167
z  =>  164
x  =>  157
c  =>  110
v  =>  100
b  =>  95
n  =>  87
m  =>  85
ç  =>  79
ö  =>  68
!  =>  51
'  =>  40
^  =>  33
+  =>  32
%  =>  22
&  =>  22
/  =>  21
(  =>  17
)  =>  14
=  =>  13
?  =>  11
>  =>  11
-  =>  11
_  =>  11
£  =>  10
#  =>  9
½  =>  8
$  =>  8
[  =>  8
{  =>  8
Ç  =>  6
Ö  =>  6
Ş  =>  5
Ğ  =>  3
İ  =>  3
Ü  =>  3
A  =>  3
S  =>  2
D  =>  2
G  =>  1
F  =>  1
****************************************************************************************************
q => 13.32805
w => 9.55397
e => 6.64423
r => 6.43310
t => 6.18897
y => 5.02111
u => 4.26894
ı => 4.03800
o => 3.92584
p => 3.61573
ğ => 3.46397
ü => 3.34521
a => 3.23304
s => 3.00871
d => 2.44128
f => 2.32251
g => 2.03880
h => 1.90684
j => 1.49116
k => 1.37899
l => 1.27342
ş => 1.13486
i => 1.10187
z => 1.08208
x => 1.03589
c => 0.72579
v => 0.65980
b => 0.62681
n => 0.57403
m => 0.56083
ç => 0.52125
ö => 0.44867
! => 0.33650
' => 0.26392
^ => 0.21774
+ => 0.21114
% => 0.14516
& => 0.14516
/ => 0.13856
( => 0.11217
) => 0.09237
= => 0.08577
? => 0.07258
> => 0.07258
- => 0.07258
_ => 0.07258
£ => 0.06598
# => 0.05938
½ => 0.05278
$ => 0.05278
[ => 0.05278
{ => 0.05278
Ç => 0.03959
Ö => 0.03959
Ş => 0.03299
Ğ => 0.01979
İ => 0.01979
Ü => 0.01979
A => 0.01979
S => 0.01320
D => 0.01320
G => 0.00660
F => 0.00660

Tabii ki şifrelenmemiş bir metin koysak en çok küçük a harfi beklerdik. O halde şu linkteki Matlab hakkında yazdığım yazıyı frekans analizine soktuğumda çıkan sonucu paylaşayım:

['__class__', '__delattr__', '__dict__', '__dir__', '__doc__', '__eq__', '__format__', '__ge__', '__getattribute__', '__gt__', '__hash__', '__init__', '__le__', '__lt__', '__module__', '__ne__', '__new__', '__reduce__', '__reduce_ex__', '__repr__', '__setattr__', '__sizeof__', '__str__', '__subclasshook__', '__weakref__', 'frekansBul', 'frekansHesapla', 'frekanslar', 'giris', 'harfEslestir', 'harfler', 'last', 'yuzdeBul']
a  =>  361
   =>  334
l  =>  180
i  =>  164
r  =>  159
e  =>  155
n  =>  141
ı  =>  136
d  =>  95
s  =>  80
m  =>  78
y  =>  72
k  =>  68
o  =>  67
u  =>  61
t  =>  57
b  =>  55
z  =>  49
h  =>  38
.  =>  35
p  =>  33
ğ  =>  32
g  =>  27
ş  =>  26
c  =>  24
ç  =>  20
ü  =>  17
f  =>  14
v  =>  12
B  =>  12
9  =>  11
ö  =>  11
,  =>  10
(  =>  7
)  =>  7
2  =>  6
M  =>  5
0  =>  5
1  =>  5
D  =>  4
-  =>  4
6  =>  4
A  =>  3
’  =>  3
K  =>  2
T  =>  2
P  =>  2
Y  =>  2
/  =>  2
*  =>  2
“  =>  2
:  =>  2
”  =>  2
x  =>  1
F  =>  1
N  =>  1
H  =>  1
U  =>  1
Ö  =>  1
W  =>  1
R  =>  1
Z  =>  1
4  =>  1
3  =>  1
′  =>  1
****************************************************************************************************
a => 13.28671
  => 12.29297
l => 6.62495
i => 6.03607
r => 5.85204
e => 5.70482
n => 5.18955
ı => 5.00552
d => 3.49650
s => 2.94442
m => 2.87081
y => 2.64998
k => 2.50276
o => 2.46596
u => 2.24512
t => 2.09790
b => 2.02429
z => 1.80346
h => 1.39860
. => 1.28819
p => 1.21457
ğ => 1.17777
g => 0.99374
ş => 0.95694
c => 0.88333
ç => 0.73611
ü => 0.62569
f => 0.51527
v => 0.44166
B => 0.44166
9 => 0.40486
ö => 0.40486
, => 0.36805
( => 0.25764
) => 0.25764
2 => 0.22083
M => 0.18403
0 => 0.18403
1 => 0.18403
D => 0.14722
- => 0.14722
6 => 0.14722
A => 0.11042
’ => 0.11042
K => 0.07361
T => 0.07361
P => 0.07361
Y => 0.07361
/ => 0.07361
* => 0.07361
“ => 0.07361
: => 0.07361
” => 0.07361
x => 0.03681
F => 0.03681
N => 0.03681
H => 0.03681
U => 0.03681
Ö => 0.03681
W => 0.03681
R => 0.03681
Z => 0.03681
4 => 0.03681
3 => 0.03681
′ => 0.03681

Beklediğimiz gibi en çok kullanılan harf küçük a çıkmış. Ayrıca bol miktarda boşluk bırakmışım.

Bu yazı da fazla uzadı, burda bırakayım. Bir sonrakinde bu sınıfa eklenen bir fonksiyonla şifreyi analiz ettirip sonra da çözdürme işini iki örneğine yaptıracağız. Umarım açıklayıcı olmuştur.

 
comments powered by Disqus