Düzenli İfadeler – Regular Expression(Regex)
Düzenli İfadeler(Regular Expression) ya da daha sık karşılaştığımız şekliyle Regex hemen hemen tüm dillerde kullanılan ve bir örüntüyü(belirli bir kurala göre tekrar eden şablon) ortaya çıkartarak metinler üzerinde arama, sınıflandırma, veri çekme gibi bir çok işlemi yapmamızı sağlayan kurallardır.
Python’da karakter dizileri diye de adlandırdığımız metinler üzerinde; hem kendi hazır fonksiyonları aracılığıyla arama, sınıflandırma gibi işlemleri yapabiliyor, hem de Python’da her metin bir dizi olarak ele alınabildiği için dizi fonksiyonlarını metinler üzerinde uygulayabiliyoruz. Ancak bazen daha gelişmiş desen eşleştirme yeteneklerine ihtiyaç duyabilirsiniz. İşte bu noktada devreye Regex giriyor.
Düzenli İfadeyi(Regular Expression) kaynak veri ile birlikte RegEx işlemcisine verdiğiniz bir desen olarak düşünebilirsiniz. Daha sonra işlemci bu deseni kullanarak kaynak veriyi ayrıştırır ve size istediğiniz parçayı geri döndürür.
Bunu yapmaktaki amaçlarımız şunlar olabilir;
- Kaynak veri içinde aradığınız desenin mevcut olup olmadığını öğrenmek
- Kaynak metinden karmaşık bir desene uyan tüm verileri almak
- Desenleri kullanarak genellikle metinleri parçalara bölme yoluyla kaynak verinizi temizlemek
RegEx, veri bilimi uygulamalarında veri temizliği için genel bir tekniktir. Ayrıca, veri bilimi uygulamalarında metin verilerini hızlı ve verimli bir şekilde kullanmanızı sağlarlar.
Eğer daha detaylı olarak öğrenmek isterseniz RegEx ile ilgili geniş kapsamlı eğitimlere katılabilirsiniz. Biz ihtiyacımız olduğu kadarıyla temel seviyede nasıl çalıştığı ve kullanıldığı ile ilgileneceğiz. Bu dersin sonunda; Düzenli İfadelerin(RegEx) temellerini anlamış, eşleşme için desenleri nasıl oluşturacağınızı, bu desenleri metinlere nasıl uygulayacağınızı, veri işlemede bu desenlerin sonuçlarını nasıl kullanacağınızı öğrenmiş olacaksınız.
Ayrıca öğrenmenin en iyi yolunun uygulamak olduğunu unutmayınız.
Şimdi çalışmaya başlayalım.
Python’da bu tür Regex işlemleri için kullanabileceğiniz bir modül var; re. Bu modül içinde kullanabileceğiniz bir çok fonksiyon mevcut ama tabii ki bu fonksiyonlara ulaşabilmek için öncelikle bu modülü kodunuza eklemeniz gerekmektedir;
import re
Bu noktadan sonra modülün içinde bulunan tüm fonksiyon ve method’lara erişebilirsiniz.
re içinde sıkça kullanılan bir kaç temel fonksiyon vardır. Bunlardan ilki olan match() fonksiyonu, metnin başında bir eşleşme olup olmadığını kontrol ederek Boolean, yani True ya da False değerini döndürür. Benzer şekilde search() fonksiyonu metnin tamamında bir eşleşme olup olmadığını kontrol eder.
Şimdi bir örnek yapalım ve bunları kullanalım. Öncelikle bir metin bulalım. Web üzerinde sıkça kullanılan dolgu metin oluşturma tekniği ile Lorem ipsum olarak adlandırılan metni kullanalım.
import re metin = "Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum." if re.search("laboris",metin): print("Aradığınız terim metin içinde mevcut!") else: print("Aradığınız terim metin içinde mevcut değil!")
Kodun çıktısı;
Aradığınız terim metin içinde mevcut!
Koşulları kontrol etmenin yanı sıra, metni parçalara da ayırabiliriz. Düzenli İfadelerin(RegEx) yaptığı bu işleme Belirteç Oluşturma(Tokenizing) adı verilir.
Metin belirli kalıplara dayalı alt metinlere ayrılır. Belirteç Oluşturma(Tokenizing) Doğal Dil İşleme(NLP-Natural Language Processing) alanında temel bir etkinliktir.
findall() ve split() fonksiyonları metinleri ayrıştırır ve metin parçalarını geri döndürür. split() fonksiyonunu daha önce karakter dizileri ve metinler konusunda kullanmıştık. Dolayısıyla nasıl çalıştığını biliyor olduğunuzu varsayıyorum. Metni verilen koşula göre parçalara ayırarak, parçalarını bir listeye depolayarak size döndürür. Örneğin metin içindeki her bir kelimeyi ayrı ayrı elde etmek istersek koşul olarak boşluk kullanabiliriz;
kelimeler=re.split(" ", metin) print(kelimeler) print(len(kelimeler))
Çıktı:
['Lorem', 'ipsum', 'dolor', 'sit', 'amet,', 'consectetur', 'adipiscing', 'elit,', 'sed', 'do', 'eiusmod', 'tempor', 'incididunt', 'ut', 'labore', 'et', 'dolore', 'magna', 'aliqua.', 'Ut', 'enim', 'ad', 'minim', 'veniam,', 'quis', 'nostrud', 'exercitation', 'ullamco', 'laboris', 'nisi', 'ut', 'aliquip', 'ex', 'ea', 'commodo', 'consequat.', 'Duis', 'aute', 'irure', 'dolor', 'in', 'reprehenderit', 'in', 'voluptate', 'velit', 'esse', 'cillum', 'dolore', 'eu', 'fugiat', 'nulla', 'pariatur.', 'Excepteur', 'sint', 'occaecat', 'cupidatat', 'non', 'proident,', 'sunt', 'in', 'culpa', 'qui', 'officia', 'deserunt', 'mollit', 'anim', 'id', 'est', 'laborum.']
69
Bu arada şimdi edindiğimiz bu bilgiyi daha önceki listeler ile ilgili bilgilerimiz ile birleştirerek elimizdeki metni oluşturan 69 kelime olduğunu da görmüş olduk.
split() fonksiyonunu düzenli ifadeler(regex) ile kullanırken desen olarak tekrar eden bir kelimeye göre parçalara ayırabiliriz.
dolor=re.split("dolor",metin) print(dolor)
Çıktı:
['Lorem ipsum ', ' sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et ', 'e magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure ', ' in reprehenderit in voluptate velit esse cillum ', 'e eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.']
Seçilen kelimenin oluşturulan liste içinde bulunmadığına dikkat ediniz. Ayrıca dikkatinizi çekmek istediğim bir önemli nokta da; verdiğiniz koşulun kelime olarak algılanmayıp, bir kalıp olarak algılandığıdır. O nedenle sadece dolor kelimesini hedeflemek yerine dolore kelimesi içinde de dolor kalıbı olduğu için işleme alındı.
Eğer bir ifadenin metin içinde kaç kez kullanıldığını öğrenmek istersek de findall() fonksiyonundan faydalanırız;
dolor_sayisi=re.findall("dolor",metin) print(len(dolor_sayisi))
Çıktı:
4
Gördüğünüz gibi dolor kalıbını 2 kez dolor kelimesi olarak 2 kez de dolore kelimesi içinde bulduğu için 4 sonucunu verdi.
RegEx metindeki kalıpları tanımlamak için bir işaretleme dili kullanır. Bu dilin ilk kavramı olan Çapalar(Anchors)dan bahsedelim. Çapalar, eşleştirme aradığınız kalıbın başını ve sonunu belirler. Şapka( ^ ) karakteri kalıbın başlangıcı anlamına gelir. Dolar( $ ) işareti ise kalıbın sonu anlamına gelir.
Şimdi metnimizin hangi ifadeyle başladığını bulalım;
print(re.search("^dolor",metin)) print(re.search("^lorem",metin)) print(re.search("^Lorem",metin)) if re.search("^Lorem",metin): print("Bu metin verdiğiniz ifade ile başlıyor.") else: print("Alakası yok!")
Çıktı:
None
None
<re.Match object; span=(0, 5), match='Lorem'>
Bu metin verdiğiniz ifade ile başlıyor.
Burada önce metnimizin dolor kalıbıyla başlayıp başlamadığını re modülünün search() fonksiyonundan faydalanarak ve Düzenli İfadelerin ^ çapasını kullanarak sorguladık ama bize None değerini döndürdü. Daha sonra lorem kalıbını sorguladık ama sonuç yine aynı oldu. Demek ki büyük küçük harfe duyarlı çalışmak gerekiyor.
Lorem kalıbını sorguladığımızda ise bize re.Match nesnesi döndürdü. Bu nesneyi if koşullu ifadesi birlikte kullanabiliriz. Nitekim öyle yaptığımızda nesnenin değerinin Boolean olarak True olduğunu gördük.Bu da başlangıcı ifade eden ^ çapasının bize doğru değer döndürdüğü anlamına geliyor. Yani metnimizin Lorem kalıbıyla başladığını öğrenmiş bulunuyoruz.
Regex Desenleri ve Karakter Sınıfları
Desenler ve karakter sınıflarına detaylıca değinelim. Bir kurs dönemi boyunca bir öğrencinin ödevlerden aldığı notları içeren bir metin oluşturalım.
notlar=”ACAAAABCBCBAA” olsun.
Eğer öğrencinin kaç B notu olduğunu öğrenmek istiyorsak, desenimiz B olacaktır.
notlar="ACAAAABCBCBAA" bnotu=re.findall("B",notlar) print(len(bnotu))
Çıktı:
3
Eğer öğrencinin aldığı A ve B notlarının kaç tane olduğunu öğrenmek istersek desen olarak AB kullanamayız. Çünkü metin içinde desenlerin aynısı arandığından dolayı AB deseni bize sadece A ile B’nin ardarda yapışık olduğu sonuçları getirir. Bunun yerine köşeli parantez içinde veririz;
avebnotu=re.findall("[AB]",notlar) print("Öğrencinin {} notundan {} tanesi A ve B notlarından oluşuyor.".format(len(notlar),len(avebnotu)))
Çıktı:
Öğrencinin 13 notundan 10 tanesi A ve B notlarından oluşuyor.
Eğer öğrencinin A aldığı bir ödevden sonraki ödevde düşen notlarını merak ediyorsanız iki köşeli parantez kullandığınızda desenleri birleştirebilirsiniz;
ailebvec=re.findall("[A][B-C]",notlar) print(ailebvec) print("Öğrenci A aldıktan sonra {} kez notu düşmüş".format(len(ailebvec)))
Çıktı:
['AC', 'AB'] Öğrenci A aldıktan sonra 2 kez notu düşmüş
Aynı sonucu “ya da” anlamına gelen | karakteri ile desenleri birleştirerek de elde edebilirdik;
abyadaac=re.findall("AB|AC",notlar) print(abyadaac)
Çıktı:
['AC', 'AB']
Belirli bir sonucu tersine çevirmek için de ^ şapka simgesini kullanabiliriz. Şöyle ki, eğer öğrencinin A olmayan notlarını merak ediyorsak, A olanları bulmamızı sağlayan ifadenin önüne şapka( ^ ) simgesini getirip değilini alabiliriz.
aolmayanlar=re.findall("[^A]",notlar) print(aolmayanlar) print("Öğrencinin {} notu içinde A olmayan {} notu mevcut.".format(len(notlar),len(aolmayanlar)))
Çıktı:
['C', 'B', 'C', 'B', 'C', 'B']
Öğrencinin 13 notu içinde A olmayan 6 notu mevcut.
Dikkat ettiyseniz burada kullandığımız şapka( ^ ) simgesi daha önce bir metnin başlangıcını ifade ediyordu ama bir fonksiyonun içindeki parametrenin dizi parantezi içinde kullanıldıklarında görevlerini değiştirirler.
Örneğin, her iki durumu birlikte kullandığımız şu kod ile neyi bulmayı hedeflediğimizi söyleyebilir misiniz?
print(re.findall("^[^A]",notlar))
Çıktı:
[]
Bize boş bir dizi döndürdü. Peki, ama bu ne demek?
Burada düzenli ifadelerde findall() metodu ile notlar metninin başlangıcında yer alan harfin A’dan farklı bir harf olup olmadığını kontrol etmiş olduk. Bize boş bir dizi döndürdü, çünkü notlar metnindeki ilk harf A’dır.
Nicelik Belirteçleri(Quantifiers)
Şimdiye kadar çapalar(anchors) ve bir kalıbın başında ya da sonundaki eşleşmelerden bahsettik. Karakter kullanımı ya da [ ] operatörü kullanmayı inceledik. Ayrıca | karakterini kullanarak ya da bağlacı ile birden fazla karakteri araştırmayı öğrendik.
Şimdi Nicelik Belirteçleri(Quantifiers) ile çalışmaya başlayacağız.
Nicelik belirteçleri bir desenin kaç kez uyduğu ile ilgili sayısal değeri bize döndürür. En temel nicelik belirteci e{m,n} olarak karşımıza çıkar ve burada e aranan ifadeyi ya da karakteri, m eşlenecek minimum sayıyı, n ise eşlenecek maksimum sayıyı göstermektedir.
Öğrencimiz kaç kez arka arkaya A notu serisi oluşturdu?
Peki kaç kez arka arkaya BC notları almış?
print(re.findall("A{2,5}",notlar)) print(re.findall("BC{1,5}",notlar))
Çıktı:
['AAAA', 'AA']
['BC', 'BC']
Görünen o ki bir kez arka arkaya 4 A almış, daha sonra da bir kez arka arkaya 2 A almış.
Öğrenci 2 kez de arka arkaya BC notu almış.
Benzer şekilde pek çok tekrarlayan deseni bulma konusunda Nicelik Belirteçleri bize yardımcı olurlar;
print(re.findall("A{1,1}A{1,1}",notlar)) print(re.findall("A{1,1}BC{1,1}",notlar))
Çıktı:
['AA', 'AA', 'AA']
['ABC']
Yalnız Nicelik Belirteçlerini kullanırken süslü parantezler arasındaki terimlerin boşluk bırakmadan kullanılmasına dikkat etmelisiniz, yoksa aradığınız deseni bulamadığını belirtecek şekilde boş bir dizi döndürürler.
print(re.findall("A{1, 1}",notlar))
Çıktı:
[]
Tabii daha önceki kullanımlarda da fark etmiş olacağınız üzere eğer nicelik belirteçlerini kullanmazsak varsayılan olarak {1,1} alınacaktır. Örneğin;
print(re.findall("A{1,1}A{1,1}",notlar)) print(re.findall("AA",notlar)) if re.findall("A{1,1}A{1,1}",notlar) == re.findall("AA",notlar): print("Evet. Bu iki ifade birbirinin aynı sonucu verir.")
Çıktı:
['AA', 'AA', 'AA']
['AA', 'AA', 'AA']
Evet. Bu iki ifade birbirinin aynı sonucu verir.
Eğer süslü parantezler arasında tek bir sayı verirseniz de bu hem m hem de n yerine geçecektir;
print(re.findall("A{1}",notlar)) print(re.findall("A{2}",notlar)) print(re.findall("A{3}",notlar)) print(re.findall("BC{1}",notlar))
Çıktı:
['A', 'A', 'A', 'A', 'A', 'A', 'A']
['AA', 'AA', 'AA']
['AAA']
['BC', 'BC']
Peki, “Öğrencimizin sürekli düşüş trendi yaşadığı bir dönem olmuş mu?” diye merak edersek eğer;
print(re.findall("A{1,10}B{1,10}C{1,10}",notlar)) print(re.findall("ABC{1,10}",notlar))
Çıktı:
['AAAABC']
['ABC']
Bunu 2 farklı şekilde tespit ettik:
Buradaki ilk örneğimizde maksimum değerini yüksek tuttuk. Böylece ard arda ABC gelmesini değil de, AAABBC ya da AAAABCCC gibi sonuçları da bulmayı hedefledik.
Bu şekilde araştırma yaptığımızda faydalanabileceğimiz kısayollar sunan simgeler de mevcuttur. Örneğin *, 0 ya da bir çok kez anlamında kullanılabilir. ? ile + simgesi ise bir ya da bir çok kez anlamına gelir. Şimdi bunları kullanabileceğimiz daha kompleks örneklere göz atalım. Bu örneklerde wikipedia’nın Aile Eğitim Hakları ve Mahremiyet Yasası(Family Educational Rights and Privacy Act) sayfasından alınmış olan bir metin verisini kullanacağız. Bu veri elimizde ferpa.txt dosyası olarak mevcut.
Öncelikle Python kodumuz içinde dosyayı açalım ve ferpa isimli bir değişkende depolayalım. Sonra da bu veriyi ekrana yazdıralım ki başarılı bir işlem gerçekleştirdiğimizi görebilelim.
with open ("ferpa.txt") as dosya: ferpa=dosya.read() print(ferpa)
Çıktı:
Overview[edit]
FERPA gives parents access to their child's education records, an opportunity to seek to have the records amended, and some control over the disclosure of information from the records. With several exceptions, ....
Bu dokümana göz attığımızda başlıkların [edit] ifadesi ile bittiğini ve sonra satırbaşı yaptığını görüyoruz. Öyleyse bunu kullanarak metinde geçen tüm başlıkları bulabiliriz.
with open ("ferpa.txt") as dosya: ferpa=dosya.read() basliklar=re.findall("[a-zA-Z]{1,100}\[edit\]",ferpa) print(basliklar)
Çıktı:
['Overview[edit]', 'records[edit]', 'records[edit]']
Gördüğünüz gibi başlıkların sadece [edit] ifadesinden önce yer alan son kelimesini aldı. Bu çok da işe yarar bir sonuç gibi görünmüyor. Bunu adım adım geliştirebiliriz. Öncelikle; \w kullanarak rakamlar gibi sonuçları da dahil ederek her bir karakterin eşleşmesini sağlayabiliriz.
Böylece sadece küçük-büyük harfler değil tamamı eşleşecektir.
with open ("ferpa.txt") as dosya: ferpa=dosya.read() basliklar=re.findall("[\w]{1,100}\[edit\]",ferpa) print(basliklar)
Çıktı:
['Overview[edit]', 'records[edit]', 'records[edit]']
Aynı sonucu verse de burada yeni bir kullanım şekliyle tanıştık. \w ifadesi metakarakter olarak isimlendirilir ve herhangi bir harf ya da rakamdan oluşan bir deseni ifade eder. Düzenli İfadelerin(Regex) dokümantasyonuna göz atacak olursanız benzer metakarakterlerle de karşılaşacaksınızdır. Örneğin; \s boşluk karakterleri ile eşleşir.
Daha önce çalışmalarımızda işimizi kolaylaştıran ve kısaltan 3 farklı Nicelik Belirteçleri olduğundan bahsetmiştik. Bunlardan “hiç ya da bir çok kez eşleşme anlamına gelen” * simgesini kullanalım;
with open ("ferpa.txt") as dosya: ferpa=dosya.read() basliklar=re.findall("[\w]*\[edit\]",ferpa) print(basliklar)
Çıktı:
['Overview[edit]', 'records[edit]', 'records[edit]']
Şimdi de boşluk karakterini kullanarak ifademizi biraz daha kısa ve kullanışlı hale getirelim;
with open ("ferpa.txt") as dosya: ferpa=dosya.read() basliklar=re.findall("[\w ]*\[edit\]",ferpa) print(basliklar)
Çıktı:
['Overview[edit]', 'Access to public records[edit]', 'Student medical records[edit]']
Şimdi tam başlıklara ulaşmış olduk. Artık hafızamızı tazeleyerek listelerde split() metodunun kullanımını kodumuzda kullanarak ve bir döngü yardımıyla tüm listeye uygulayarak sadece başlıkları elde edebilir ve istediğimiz yerde (belki yeni bir doküman oluşturmak için) kullanabiliriz.
with open ("ferpa.txt") as dosya: ferpa=dosya.read() basliklar=re.findall("[\w ]*\[edit\]",ferpa) print(basliklar) for baslik in basliklar: print(re.split("[\[]",baslik)[0])
Çıktı:
Overview
Access to public records
Student medical records
Gruplar
Kullandığımız teknikle kodumuz çalışıp sonuca bizi ulaştırıyor ama bunun daha kolay bir yolu da var. Farklı desenleri bir arada kullanmanızı sağlayan gruplama yöntemini de kullanabilirsiniz. Tıpkı matematikte ki gibi bir arada yapılmasını istediğiniz işlemleri parantez içine alarak gruplayabilirsiniz. Şimdi önceki örnekte yer alan kodumuzu gruplar ile yeniden yazalım;
with open ("ferpa.txt") as dosya: ferpa=dosya.read() basliklar=re.findall("([\w ]*)(\[edit\])",ferpa) print(basliklar) for baslik in basliklar: print(baslik[0])
Çıktı:
[('Overview', '[edit]'), ('Access to public records', '[edit]'), ('Student medical records', '[edit]')]
Overview
Access to public records
Student medical records
Çıktımızın ilk parçasında gördüğünüz gibi re modülü sonuçları gruplayarak döndürdü. Şimdiye kadar findall() metodunun metin(string) türünde veri döndürdüğünü gördük, search() ve match() metodları da Match nesnesi döndürüyor. Peki bu Match nesnesinden sonuçları liste olarak almak istersek ne yapmalıyız? Bu durumda, finditer() metodunu kullanabiliriz.
with open ("ferpa.txt") as dosya: ferpa=dosya.read() basliklar=re.finditer("([\w ]*)(\[edit\])",ferpa) for baslik in basliklar: print(baslik.groups())
Çıktı:
('Overview', '[edit]')
('Access to public records', '[edit]')
('Student medical records', '[edit]')
Burada groups() metodu grupların bir demetini döndürüyor. Onun yerine group() metodunu kullanarak ilgilendiğimiz eşleşmeyi elde edebiliriz. group() metoduna 0 parametresini verdiğinizde tam eşleşmeyi size döndürür, 1 parametresini verdiğinizde ilk veriyi, 2 parametresini verdiğinizde ise ikinci veriyi döndürür;
with open ("ferpa.txt") as dosya: ferpa=dosya.read() basliklar=re.finditer("([\w ]*)(\[edit\])",ferpa) basliklar2=re.finditer("([\w ]*)(\[edit\])",ferpa) basliklar3=re.finditer("([\w ]*)(\[edit\])",ferpa) for baslik in basliklar: print(baslik.group(0)) print() for baslik in basliklar2: print(baslik.group(1)) print() for baslik in basliklar3: print(baslik.group(2))
Çıktı:
Overview[edit]
Access to public records[edit]
Student medical records[edit]
Overview
Access to public records
Student medical records
[edit]
[edit]
[edit]
Düzenli ifadelerde kullanılan gruplar ile ilgili kullanışlı olabilecek bir başka özellik ise gruplarınızı isimlendirmek olabilir. Bunun için ?P<isim> şablonu kullandığınız desenin başına yerleştirilir. Bu yazım şekli size bir sözlük döndüreceği için nesne elemanlarına ulaşırken de groupdict() metodunu kullanmalısınız. Örneğin;
with open ("ferpa.txt") as dosya: ferpa=dosya.read() basliklar=re.finditer("(?P<Başlık>[\w ]*)(?P<Düzenleme_Bağlantısı>\[edit\])",ferpa) for baslik in basliklar: print(baslik.groupdict()["Başlık"])
Çıktı:
Overview
Access to public records
Student medical records
Karakter desenlerini köşeli parantezler [ ] ile nasıl eşleştirdiğimizi, birden fazla eşleşmeyi ( ) parantezler kullanarak nasıl gruplandırdığımızı, * ? ya da m{n} gibi nicelik belirteçlerini nasıl kullandığımızı öğrendik.
Önceki örneklerde \w ifadesinin herhangi bir kelime anlamına geldiğini de gördük. Düzenli ifadeler(Regex) ile buna benzer bir çok kısa gösterim farklı karakterler için kullanılabiliyor. Örneğin;
- . ifadesi satırbaşı olmayan her bir karakteri simgeliyor
- \d ifadesi herhangi bir sayısal karakteri
- \s ise herhangi bir boşluğu(boluk ya da tab olabilir) simgeliyor.
Öncesi – Sonrası
Regex’de aşina olmamız gereken bir diğer kavram ise Öncesi – Sonrası eşleşmeleridir. İlgilendiğiniz deseni tanımlar ve bulursunuz, daha sonra bu desen içinde ayırmak istediğiniz kısmı ?= ile bilirterek çıkartır ve asıl istediğiniz sonuca ulaşırsınız. Örneğin biz örnek verimizde başlıkları alabilmek için onlara eklenmiş eşsiz bir seçici olan [edit] ifadelerinden yararlanmıştık. Ama sonuçta bu ifadeler değil ondan önce yer alan başlıklar asıl ulaşmak istediğimiz veriydi. Bu durumda isteğimizi şu şekilde belirtebilir ve [edit] ifadesinden öncesini eşleştirmek ve almak istediğimizi belirtebiliriz;
with open ("ferpa.txt") as dosya: ferpa=dosya.read() basliklar=re.finditer("(?P<Başlık>[\w ]+)(?=\[edit\])",ferpa) for baslik in basliklar: print(baslik)
Çıktı:
<re.Match object; span=(0, 8), match='Overview'>
<re.Match object; span=(2715, 2739), match='Access to public records'>
<re.Match object; span=(3692, 3715), match='Student medical records'>
Aynı koda tek bir ekleme ile match nesnesi yerine ilgilendiğimiz veriyi alabiliriz;
with open ("ferpa.txt") as dosya: ferpa=dosya.read() basliklar=re.finditer("(?P<Başlık>[\w ]+)(?=\[edit\])",ferpa) for baslik in basliklar: print(baslik[0])
Çıktı:
Overview
Access to public records
Student medical records
Ayrıntılı Çalışma(VERBOSE) Modu
Şimdi bir başka Wikipedia verisi ile gerçek hayattan alınma örneklerimizi çeşitlendirelim.
Amerika’da yer alan Budist Üniversiteler hakkında wikipedia’dan alınan verimiz buddhist.txt dosyamızda kayıtlı.
Önce bu dosyayı Python kodumuz içinde açmalı, okumalı ve kod içinde kullanabilmek için bir değişkene depolamalıyız.
with open ("buddhist.txt") as dosya: budist=dosya.read()
Bu dosyada her üniversite benzer bir kalıp ile yer alıyor. Üniversite adı – seperatörü ile ayrılmış şekilde “located in” kelimeleri ve üniversitenin konumu kalıbı kullanılmış.
Örneğin; Ewam Buddhist Institute – located in Arlee, Montana
Bu örneğimizde Python’da Düzenli İfadelerin ayrıntılı(verbose) modunu öğrenebiliriz. Ayrıntılı(Verbose) mod, birden çok satırlı düzenli ifade yazmanıza izin verir ve bu sayede okunurluğu artırır. Bu mod için, tüm boşluk karakterlerini açıkça belirtmeli, onları \ ya da \s özel ifadelerini kullanarak ayırmalıyız. Bu, düzenli ifadelerimizi biraz daha koda benzer şekilde yazabileceğimiz ve hatta # ile bilgilendirme satırları ekleyebileceğimiz anlamına geliyor.
with open ("buddhist.txt") as dosya: budist=dosya.read() desen=""" (?P<universite>.*) # Üniversitenin adı (–\ located\ in\ ) # konumdan önce gelen sabit ifade (?P<sehir>\w*) # Üniversitenin bulunduğu şehir (,\ ) # Şehir ile Eyalet adı arasındaki ayraç (?P<eyalet>\w*) # Üniversitenin bulunduğu eyalet""" universiteler=re.finditer(desen,budist,re.VERBOSE) for uni in universiteler: print(uni.groupdict())
Çıktı:
{'universite': 'Dhammakaya Open University ', 'sehir': 'Azusa', 'eyalet': 'California'}
{'universite': 'Dharmakirti College ', 'sehir': 'Tucson', 'eyalet': 'Arizona'}
{'universite': 'Dharma Realm Buddhist University ', 'sehir': 'Ukiah', 'eyalet': 'California'}
{'universite': 'Ewam Buddhist Institute ', 'sehir': 'Arlee', 'eyalet': 'Montana'}
{'universite': 'Institute of Buddhist Studies ', 'sehir': 'Berkeley', 'eyalet': 'California'}
{'universite': 'Maitripa College ', 'sehir': 'Portland', 'eyalet': 'Oregon'}
{'universite': 'University of the West ', 'sehir': 'Rosemead', 'eyalet': 'California'}
{'universite': 'Won Institute of Graduate Studies ', 'sehir': 'Glenside', 'eyalet': 'Pennsylvania'}
Kodumuzda isimlendirme kullandık ve re.VERBOSE modu ile birden çok satıra sahip olan bir düzenli ifade oluşturduk ki bu şekilde daha okunur olmasının yanı sıra çıktı olarak bize dönen değişken adı: değeri ikilileriyle sözlük verisi üzerinde çalışmak çok daha kolay bir hal almış oldu.
Şimdi de bir başka gerçek hayat örneğine göz atalım. New York Times haber sitesinin sağlıkla ilgili Tweet’lerinden faydalanacağız. Ama sadece düzenli ifadeler(RegEx) ile çalışacağız, yani veriyi bulunduğu ortamdan alma ve depolama işlemi farklı bir konu. Biz bu veriyi UC Irvine Machine Learning web sitesinden aldık. burada üzerinde çalışmak için güzel veri kaynakları mevcut.
Aldığımız veri çalışma dizinimizde nytimeshealth.txt dosyası olarak elimizde mevcut. Tabii her zamanki gibi öncelikle bu dosyayı Python kodumuz içinde açıp, okumalı ve bir değişkende depolamalıyız.
with open ("nytimeshealth.txt") as dosya: tweets=dosya.read() print(tweets)
Çıktı:
.....
482726406934511617|Sat Jun 28 03:25:18 +0000 2014|Report Finds Health Unit of V.A. Needs Overhaul http://nyti.ms/1lyNLq9
Çıktımız çok fazla olduğu için burada sadece son satırını aldım. Bize gelen verinin genel formatı bu şekildedir. Gördüğünüz gibi verideki farklı bölümlerin araları boru(pipes) karakteri olarak adlandırılan | karakteri ile birbirlerinden ayrılmış. Bu veri içindeki tüm hashtag’lerin listesini ayıklamaya çalışalım.
Veri içindeki hashtagler bir boşluk karakteri ile devam ediyor. Dolayısıyla bir hashtag’in yapısı şu desene uyuyor; Önce hashtag sembolü sonra belirsiz bir sayıda harf ve rakamlardan oluşabilecek karakter grubu ve sonra bir boşluk. Bunu düzenli ifadeler ile yazmak istersek;
desen="#[\w\d]*(?=\s)"
Burada desenimizden dönen değer içinde sondaki boşluk karakterine ihtiyacımız yok. Yani onu kullanarak veriyi bulacağız ama veriyi döndürürken sonunda o boşluk karakterinin olmasını istemiyoruz. Ayrıca + yerine * simgesini kullanarak hem harf hem de rakam karakterleri seçtiğimize dikkat ediniz. Eğer + kullansaydık bunlardan yalnızca birinin eşleşmesi yeterli olurdu. Aradaki farkı deneyerek görebilirsiniz.
Şimdi veri dosyasında yer alan tüm hashtag’leri görelim;
with open ("nytimeshealth.txt") as dosya: tweets=dosya.read() desen="#[\w\d]*(?=\s)" hashler=re.findall(desen,tweets) print(hashler)
Çıktı:
['#askwell', '#pregnancy', '#Colorado', '#VegetarianThanksgiving', '#FallPrevention', '#Ebola', '#Ebola', '#ebola', '#Ebola', '#Ebola', '#EbolaHysteria', '#AskNYT', '#Ebola', '#Ebola', '#Liberia', '#Excalibur', '#ebola', '#Ebola', '#dallas', '#nobelprize2014', '#ebola', '#ebola', '#monrovia', '#ebola', '#nobelprize2014', '#ebola', '#nobelprize2014', '#Medicine', '#Ebola', '#Monrovia', '#Ebola', '#smell', '#Ebola', '#Ebola', '#Ebola', '#Monrovia', '#Ebola', '#ebola', '#monrovia', '#liberia', '#benzos', '#ClimateChange', '#Whole', '#Wheat', '#Focaccia', '#Tomatoes', '#Olives', '#Recipes', '#Health', '#Ebola', '#Monrovia', '#Liberia', '#Ebola', '#Ebola', '#Liberia', '#Ebola', '#blood', '#Ebola', '#organtrafficking', '#EbolaOutbreak', '#SierraLeone', '#Freetown', '#SierraLeone', '#ebolaoutbreak', '#kenema', '#ebola', '#Ebola', '#ebola', '#ebola', '#Ebola', '#ASMR', '#AIDS2014', '#AIDS', '#MH17', '#benzos']
Son olarak Düzenli İfadeler(RegEx) ile çalışmalarınızda test ortamı olarak kullanabileceğiniz bir online araç olan regex101 ve pyregex web sitelerini tavsiye ederek dersimizi sonlandıralım.