Python benzer kelimeler içeren listedeki tekrarları silme. (removing similar duplicates)

Diyelim ki string’ten oluşan bir list 'im var. Bu liste’de benzer kelimeler var. Tekrar edenleri listeden kaldırmak istiyorum. Difflib diye bir kütüphane buldum. İki kelime arasındaki benzerliğin oranını geri döndürüyor. Difflib kütüphanesinin sequence matcher fonksiyonunu kullanarak bir kod yazdım. Ama beceremedim gibi hissediyorum. Loop(döngü)'u kuramadım bir türlü. Umarım doğru anlatabilmişimdir. Velhasıl günün sonunda [“paralar”, “kapıcı”, “kapı”, “Özdilek Park Gucci”, “geldiğin yere geri dön”] gibi birşey döndüren bir kod yamaya çalışıyorum.

list = ["PARALAR", "paralar", "kapıcı", "parasız", "kapı", "geldiğin yere geri dön", "geldiğin yere geri dönme", "Özdilek Park Gucci", "Özdilek Park 2.Kat Gucci"]

> from difflib import SequenceMatcher    
> newlist= []
>     for item in list:
>         for itemcompare in list: 
>             if SequenceMatcher(None, item, itemcompare).ratio() < 0.8:
>                 if item not in newlist:
>                     newlist.append(item)

Merhaba. Öncelikle bu fonksiyonun tam olarak istediğiniz gibi çalışmadığını fark etmeniz lazım:

>>> SequenceMatcher(None, 'PARALAR','paralar').ratio()
0.0

Fonksiyon kelimenin genelinden ziyade karakterlere baktığı için bu ikisini tamamen ayrı olarak alıyor. Bunun önüne geçmek için hepsini küçük karakter haline getirebilirsiniz:

>>> SequenceMatcher(None, 'PARALAR'.lower(),'paralar'.lower()).ratio()
1.0

Ama bunu yapsanız bile kodunuz aynı sonucu verecektir. Sonuç listesi hem PARALAR hem de paraları barındaracaktır. Çünkü kodunuz ikisini de for item in list: kısmında deniyor ve if item not in newlist: şartı her zaman sağlanıyor. newlist'de PARALAR olabilir ama paralar yok, bu yüzden o da ekleniyor.

2 Beğeni

Anladım biraz. Büyük Harf, küçük harf ayrıntısını fark etmemiştim. Ama hala istediğim sonuca ulaşamadım. Yaptığım iyileştirmeler sonucu aşağıdaki kodu oluşturdum. Sadece Listenin içindeki ilk üçünü duplicate olarak algılaması lazım bu kodla ama nedense 4 ve 6. sıradaki item(element)’ leri de siliyor.

list = [
‘1/F, 162-164, Jl. MH Thamrin\nKav, 10350\nJakarta\n10350\n\nIndonesia\n(021) 2992 380’,
‘1/F, 62-64, Jl. MH Thamrin\n\nJAKARTA\n10350\n\nIndonesia\n+021 2992 3808’,
‘1/F, 162-164, Jl. MH Thamrin\nKav, 10350\nJakarta\n10350\n\nIndonesia\n(021) 2992 380’,
‘9600 Wilshire Blvd\n\nBEVERLY HILLS\n90212\nCALIFORNIA\nUnited States\n(310) 275-4211’,
‘United States’,
‘2/F, 25220, Yeon-Dong\n\nJEJU\n\n\nKorea\n+82 6 4710 7224’,
‘1/F, 202, 2-Ga,\nJangchung-dong, Jung-gu \nSEOUL\n\n\nKorea\n+82 2 2230 3898’
]

> from difflib import SequenceMatcher     
> for item in list:
>         for itemcompare in list: 
>             if SequenceMatcher(None, item.lower(), itemcompare.lower()).ratio() > 0.7:
>                 list.remove(itemcompare)
> list

Cunku her elementi kendisiyle de karsilastiriyorsun. Butun elemanlarin silinmemesi sans. (Ve for-for-remove mantigindaki buga isaret ediyor.)

itertools.combinations kullanmak isteyebilirsin.

Bu arada hem tartismayi hem de denemeler yapmayi kolaylastirmak icin:

  • kodu alinti olarak veya basinda ‘>’ ile degil, calistirilabilir kod olarak paylasmani
  • benzerlik belirleyen kritik kodu iki kelime alip boolean donduren bir isimli fonksiyona cikartmani

tavsiye ediyorum.

2 Beğeni

itertools built-in kütüphaneyi tavsiye ettiğin için teşekkürler. Hem şuan hem de ileride her zaman aklımda olacak bir fonksiyon olacak. Itertools ile birlikte yeni bir kod yazdım. Itertools listenin elemanlarının kombinasyonlarını geri döndürüyor. Dolayısıyla tüm ikili olasılıkları veriyor. Sonra difflib ile bu iki olasılıkların benzerlik oranını döndürüyorum. eğer benzerlik oranı 0,8’in üstünde ise, elemanlardan birini silmek istiyorum. Bu son silme kısmı yazıldığından daha zor. Şöyle yaptım. 0,8’in üzerindeyse karşılaştırdığı kelimlerden birini yeni bir listeye ekliyor>>list_remove. Daha sonra ana listemizdeki kelimelerden yeni bir liste oluşturuyorum eğer list_remove’da değilse>>[x for x in list_df if x not in list_remove]

kodu çalıştırılabilir olarak nasıl paylaşacağımı bilmiyorum ama büyüktür işaretini(‘>’ ) sildim.

from difflib import SequenceMatcher
from itertools import combinations

list_remove = []
list = list(set(list_df)) #remove duplicates
listcomb = list(combinations(list_df, 2))
newest_list = []
for birtuple in listcomb:
    item1 = birtuple[0]
    item2 = birtuple[1]
    if SequenceMatcher(None, item1.lower(), item2.lower()).ratio() > 0.8:
        list_remove.append(item2)
list_difference = list(set(list_remove))
list_final = [x for x in list_df if x not in list_difference]

Super, ellerine saglik.

Calistirilabilirden kastim su, eger yukaridaki kod blogunu (from diffliblist_final) kopyalayip bir dosyaya veya Python interpreter’ina yapistirirsam calismasi. Hal-i hazirda bir error aliyorum:

Traceback (most recent call last):
  File "a.py", line 5, in <module>
    list = list(set(list_df)) #remove duplicates
NameError: name 'list_df' is not defined

(duzeltince devami da geliyor ama sorun degil)

Cok iyi, dogrusu bu. Dogrudan gerektigi yerde silmek daha kolay gozukse de, gordugum sistemlerin cogunda bu sekilde sonradan/toplu silim yapmak daha mantikli.

Sirada bu fonksiyonu bir def bloguna almak var.

1 Beğeni

Hımm, anladım. Bundan sonra mutlaka dikkat ederim. Def bloguna aldım. Ama nasıl mutluyum, kafam nası güzel. fonksiyona isim bulurken zorlandım. biraz düşündüm. remove duplicate olsa tam karşılamıyor. remove_similar_duplicates uzun oluyor. Sonra dedim böyle bu aslında pembemsi, yeşilimsi gibi birşey. pinkish, greenish>>>>duplicatish :smiley: ahahahaha

from difflib import SequenceMatcher
from itertools import combinations
def remove_duplicatish(alist):
    list_remove = []
    list_final = []
    alist = list(set(alist))
    listcomb = list(combinations(alist, 2))
    for birtuple in listcomb:
        item1 = birtuple[0]
        item2 = birtuple[1]
        if SequenceMatcher(None, item1.lower(), item2.lower()).ratio() > 0.8:
            list_remove.append(item2)
    list_difference = list(set(list_remove))
    list_final = [x for x in alist if x not in list_difference]
    print("finished")
    deletednumber = len(alist)-len(list_final)
    print("Number of items deleted: "+ str(deletednumber))
    return list_final

Algoritmanız biraz karışık olmuş, bu da iş görür:

from difflib import SequenceMatcher 

liste = ["PARALAR", "paralar", "kapıcı", "parasız", "kapı", "geldiğin yere geri dön", "geldiğin yere geri dönme", "Özdilek Park Gucci", "Özdilek Park 2.Kat Gucci"]

def eliminate_duplicates(liste, ratio = 0.8):
    newlist= []
    for item in liste:
        for itemcompare in newlist:
            if SequenceMatcher(None, item.lower(), itemcompare.lower()).ratio() > ratio:
                break
        else:
            newlist.append(item)
    return newlist

print(eliminate_duplicates(liste))

Ayrıca bu fonksiyon sizinkinden daha hızlı.

Sadece tek bir bilgisayarda; paralelize edilemiyor. (Yukaridaki deklarative yakin kodun imperatif bir optimizasyonu gibi.)

Tam olarak anladığımı söylesem yalan olur :slightly_smiling_face:

Hemen aciklayayim :slight_smile:

Her halukarda N×(N-1) comparsion yapilacak ya? Ilk kod ciftleri en basta cikartiyor, bundan sonra N*N-1’e kadar farkli bilgisayara dagitilabilir islem. Gelen duplike item’lar bir listede birlestirilip yine merkezi veya daginik olmak uzere ana listeden cikartilabilir.

Ikinci kod her iterasyonda farkli bir newlist kullaniyor. Bu newlist degeri bir onceki iterasyona bagli. O yuzden ilk for sirali olarak calistirilmak zorunda.

Bu arada ikinci kodda biri surekli degisen (ve buyudugu icin realokasyon sebepli cache invalidasyonuna tabi) iki liste var. Ilk kodda -iki farkli indisle erisilen- tek liste var. Yani aslinda ilkinin yavasligi remove asamasinda.


Tabi bu arada iki algoritma da sorunlu; siraya baglilar. Sebep is_similar fonksiyonunun (ikiniz de hala yazmamissiniz fonksiyonu, tesekkur ederim :unamused:) gecissiz olmasi. Cozumune kafa yormadim ama benzeyen item’lardan birini rastgele secmek bir baslangic olabilir.

1 Beğeni

Parelelize olayını şimdi anladım.

Bu kadar da ayrıntılı düşünmeye gerek var mı bilmiyorum :slightly_smiling_face: Olmadı giriş listesi ile aynı uzunlukta bir liste oluşturulur.

O ayrı bir konu zaten, benzer elemanlardan hangisini eleyeceğimiz biraz sıkıntı. Sorunun daha iyi tanımlanması lazım.

Yani is_similar kısmı zaten

bölümünde yapılmıyor mu? Ayrı bir fonksiyona çıkarma gereği görmedim.

Gerçekten sizinki çok ilginç bir algoritma @EkremDincel . Bu şekilde yazmak hiç aklıma gelmezdi. Beynimdeki bir takım yolların açılmasını sağladı. Daha önce hiç for/else kullanmadım. @aib 'in dediklerine ben de vakıf değilim. Ama “cache invalidasyonu” diye bir terim kullanıyorsa herhalde doğru söylüyordur :joy:

Bu arada, ikinize de teşekkürler, ilgilendiğiniz için.

Son konuşulanlar dahilinde tekrar güncelledim kodu. Is_similar’ı yazmadığımız noktasını ben de anlamadım

from difflib import SequenceMatcher
from itertools import combinations
import random
def remove_duplicatish(alist,ratio=0.8):
    list_remove = []
    alist = list(set(alist))     #delete exact same duplicates
    listcomb = list(combinations(alist, 2))  #create a list with all possible combinations of two.
    for birtuple in listcomb:                   #for each paired item in listcomb.
        item1,item2 = birtuple[0],birtuple[1]    
        if SequenceMatcher(None, item1.lower(), item2.lower()).ratio() > ratio: #change this ratio
            list_remove.append(random.choice([item1,item2])) #add random item to list_remove if items are similar.
    list_difference = list(set(list_remove)) #delete exact duplicates from list_remove.
    list_final = [x for x in alist if x not in list_difference] #remove list_remove from alist.
    return list_final
1 Beğeni

Ahahaha :slight_smile:

Bence de yok bu arada, ama hiz konusu acilinca akla geliyor iste.

Tabi en bastan Python’dayiz zaten, bir buyugumuzun deyisiyle “kafa yormaya gerek yok.”

Evet, evet. Gerek de yok. (Retorik gibi ama “havada kalmasin” dedim)

1 Beğeni