Alternatif algoritma önerebilir misiniz?

Herkese merhaba,

Programı şu adresten indirebilir ve program hakkında daha ayrıntılı bilgi edinebilirsiniz. Programı çalıştırdığınızda bazı modüller indirilip, kurulabilir.

Not: Windows kullanıcıları cmd’yi yönetici olarak çalıştırmadıkları sürece Pyswisseph modülünün kurulumu sırasında PermissionError hatasıyla karşılaşırlar.

Modüllerin kurulumu tamamlandıktan sonra, programın çok geçmeden açılması lazım.

Ama mevcut algoritmaya göre programın açılma hızı, programın bulunduğu dizinde programla uyumlu lisanslı xml dosyasının olup olmamasına bağlı. Zaman içerisinde sql veritabanındaki kayıt sayısı artmaya başlarsa, programın açılma hızı sql veritabanına da bağlı olur.

Burada program görünür hale gelene kadar faaliyet gösteren kodları paylaşacağım. Çünkü alternatifini aradığım kodlar bu mesajda paylaştığım son fonksiyona ait olan kodlar. Ondan önceki kodlar programın açılış zamanına bahsettiğim fonksiyon kadar etki etmiyorlar. Aslında programın hesaplama işlemlerinin de daha hızlı bir şekilde yapılmasını istiyorum. Ama o kısım şimdilik dursun.

  1. Modüller yüklendikten sonra ilk olarak bir sql veritabanı oluşturuluyor.
connect = sql.connect("TkAstroDb.db")
cursor = connect.cursor()

col_names = "no, add_date, adb_id, name, gender, rr, date, time, " \
            "julian_date, lat, c_lat, lon, c_lon, place, country, " \
            "adb_link, category"

cursor.execute(f"CREATE TABLE IF NOT EXISTS DATA({col_names})")
  1. Sonra, bir kaç satır sonra tanımlanacak olan işlemlerde kullanılacak olan değişkenler tanımlanıyor.
xml_file = ""

database, all_categories, category_names = [], [], []

_count_ = 0

category_dict = dict()
  1. Program daha sonra çalıştığı dizinde bir xml dosyayı olup olmadığına bakar.
for _i in os.listdir(os.getcwd()):
    if _i.endswith("xml"):
        xml_file += _i
  1. Eğer uzantısı xml olan sadece bir tane dosya varsa aşağıdaki for döngüsü çalışır.
    döngü i=999999 durumuna kadar devam edecek şekilde tanımlandı, zaten veritabanında o kadar kayıt yok, bir yerden sonra IndexError hatası yükseltilecek. Bu yüzden döngünün içinde yapılacak işlemler try except deyimi içine yazıldı.
    Bu for döngüsünde xml veritabanında bulunan her bir kayıt için user_data isminde bir liste üretilir. Her bir listeye, 13 tane, çeşitli veri tiplerinde eleman eklenecek. Veritabanındaki kayıtlara ulaşmak için bir başka döngü içinde root'un index değerleri kullanılarak bazı ortak etiketlere bakılır. Sonra bu etiketlerdeki veriler değişik veri tiplerine aktarılır ve bu 13 değişik veri tipi user_data listesine eklenir.
if xml_file.count("xml") == 1:
    tree = xml.etree.ElementTree.parse(f"{xml_file}")
    root = tree.getroot()
    for _i in range(1000000):
        try:
            user_data = []
            for gender, roddenrating, bdata, adb_link, categories in zip(
                    root[_i + 2][1].findall("gender"),
                    root[_i + 2][1].findall("roddenrating"),
                    root[_i + 2][1].findall("bdata"),
                    root[_i + 2][2].findall("adb_link"),
                    root[_i + 2][3].findall("categories")):
                _name = root[_i + 2][1][0].text
                sbdate_dmy = bdata[1].text
                sbtime = bdata[2].text
                jd_ut = bdata[2].get("jd_ut")
                lat = bdata[3].get("slati")
                lon = bdata[3].get("slong")
                place = bdata[3].text
                country = bdata[4].text
                category = [
                    (categories[_j].get("cat_id"), categories[_j].text)
                    for _j in range(len(categories))]
                for cate in category:
                    if cate[0] not in category_dict.keys():
                        category_dict[cate[0]] = cate[1]
                user_data.append(int(root[_i + 2].get("adb_id")))
                user_data.append(_name)
                user_data.append(gender.text)
                user_data.append(roddenrating.text)
                user_data.append(sbdate_dmy)
                user_data.append(sbtime)
                user_data.append(jd_ut)
                user_data.append(lat)
                user_data.append(lon)
                user_data.append(place)
                user_data.append(country)
                user_data.append(adb_link.text)
                user_data.append(category)
            database.append(user_data)
        except IndexError:
            break

xml veritabanındaki her bir kaydın categories isimli bir etiketi var. Bu etiketin içinde birden çok kategori yer alıyor. Bu kategorileri de yukarıdaki kodlardaki şu kısım bir liste ve bir sözlüğe aktarır:

                category = [
                    (categories[_j].get("cat_id"), categories[_j].text)
                    for _j in range(len(categories))]
                for cate in category:
                    if cate[0] not in category_dict.keys():
                        category_dict[cate[0]] = cate[1]

category değişkeni döngü içinde sürekli yeniden oluşturulan bir değişkendir bu yüzden category_dict isimli döngü dışında duran bir sözlük tanımlanmıştır, category değişkeni değişmeden önce bütün kategoriler bu sözlüğe aktarılır.

  1. Sonra, her iki veritabanını birleştiren bir fonksiyon tanımlanır. Bu fonksiyonun yaptığı işlem şimdilik o kadar zaman alan bir işlem değil. (Aşağıdaki kodlarda _data_ listesinin 9. ve 10. elemanları düşürülüyor. Bu elemanlar daha sonra başka işlemlerde lazım olacak. Ama burada o elemanları listeden çıkarmak gerekiyor. Çünkü xml veritabanında bu sütunlar yok.)
def merge_databases():
    global _count_
    reverse_category_list = {
        value: key for key, value in category_dict.items()
    }
    for _i_ in cursor.execute("SELECT * FROM DATA"):
        _data_ = list(_i_)[2:]
        _data_.pop(8)
        _data_.pop(9)
        new_category = []
        edit_data = _data_[:12]
        if "|" in _data_[12]:
            edit_category = _data_[12].split("|")
            for _cat_ in edit_category:
                if _cat_ in category_dict.values():
                    new_category.append(
                        (reverse_category_list[_cat_], _cat_)
                    )
                else:
                    new_category.append((str(4014 + _count_), _cat_))
                    category_dict[str(4014 + _count_)] = _cat_
                    _count_ += 1
                    reverse_category_list = {
                        value: key for key, value in category_dict.items()
                    }
        else:
            if _data_[12] in category_dict.values():
                new_category.append(
                    (reverse_category_list[_data_[12]], _data_[12])
                )
            else:
                new_category.append((str(4014 + _count_), _data_[12]))
                category_dict[str(4014 + _count_)] = _data_[12]
                reverse_category_list = {
                    value: key for key, value in category_dict.items()
                }
                _count_ += 1
        edit_data.append(new_category)
        database.append(edit_data)
  1. Yukarıdaki fonksiyondan sonra alternatif algoritma önerisi aradığım fonksiyon tanımlanır.
def group_categories():
    global category_names, all_categories
    category_names, all_categories = [], []
    for _i_ in category_dict.keys():
        _records_ = []
        category_groups = {}
        category_name = ""
        for j_ in database:
            for _k in j_[12]:
                if _k[0] == f"{_i_}":
                    _records_.append(j_)
                    category_name = _k[1]
                    if category_name is None:
                        category_name = "No Category Name"
        category_groups[(_i_, category_name)] = _records_
        if not _records_:
            pass
        else:
            if category_name not in category_names:
                category_names.append(category_name)
            all_categories.append(category_groups)
    category_names = sorted(category_names)

Bu fonksiyonda şuanlık sayısı 793 olan kategorilerin her biri için aşağıdaki değişkenler tekrar tekrar tanımlanır:

        _records_ = []
        category_groups = {}
        category_name = ""

Sonra her bir kategori için, 64338 adet elemanı olan database isimli bir liste tekrar tekrar taranır. Ayrıca her kaydın 13. sütunu, bir kategori listesi olduğu için, aranan kategori bu kategori listesi içinde mi ona bakılır. Şayet içindeyse, _records_ değişkenine kayıt eklenir. category_name değişkenine, döngü içinde sırası gelen kategorinin ismi aktarılır. Eğer kategori ismi None ise ismi No Category Name olarak değiştirilir. Sonra category_groups sözlüğünün anahtarı _i_ ve category_name olarak, değeri de bu kategoriye sahip kayıtların tutulduğu _records_ olarak belirlenir. Buradaki _i_ değişkeni int veri tipinde bir sayıdır. Eğer bu kategoride bir kayıt yoksa hiç bir işlem yapılmaz, varsa category_groups sözlüğü, döngü dışında tutulan all_categories listesine eklenir. Ayrıca daha sonra kategorileri alfabetik sıraya göre sıralamak için category_names listesine kategori ismi eklenir. Ve döngü diğer bir kategori için tekrar başa döner, en sonunda da category_names listesi alfabetik olarak sıralanır. Kategoriler kullanıcılara bu sayede alfabetk olarak sıralı bir şekilde gösterilir.

Biraz karışık anlattıysam özür dilerim.İşte bu yukarıda bahsettiğim işlemler bilgisayarımda aşağı yukarı 25 saniye sürüyor. Bu süre başka bir algoritma kullanılarak azaltılabilir mi? Önerisi olan arkadaşlara şimdiden teşekkür ederim.

Oncelikle asil yapilmasi gerekeni (profosyonel yaklasimi) yazayim, daha pratik bir cozum bulmasi zaman alir:

  1. Fonksiyonun yan etkisiz, pure bir hale getirilmesi lazim. Yani girdilerini parametre olarak alacak ve ciktilarini return value olarak dondurecek. Anladigim kadariyla category_names ve all_categories globallerini degistiriyor. Bu degerleri return ile dondurup, cagiran kodu bunlari degistirecek sekilde degistirmek lazim.
  2. Fonksiyonun yan etkisiz/global kullanmayan/pure haline unit test yazilmasi lazim. Bu testlerin fonksiyonun temel ozelliklerini test etmesi lazim. Aslinda aciklayici paragrafta yazilan her sey. Gelen isim None ise cikan isim No Category Name oluyor mu, 1+ kategoriye ait insan her kategoride gozukuyor mu, vs. vs.
  • Bu noktada fonksiyon degisiklige acik olacak, degisiklik yapan insan “acaba programi bozdum mu” diye endiselenmek zorunda kalmayacak.
  1. Fonksiyonun hizini test eden kod yazilmasi lazim. 800 kategoriye esit bolunmus 64,000 kayitta yavas mi calisiyor? Dummy bir dataset uretip bakalim ki, duzeltmek isteyen insanlar kendi makinelerinde test edebilsinler.
  • Bu noktada fonksiyon optimize edilmeye acik olacak.

Bu arada merge_databases() ve group_categories() fonksiyonları ismi Reload Database olan bir tkinter menu button’u tarafından tekrar çağrılabiliyor. Yani program açıkken, kullanıcı bu düğmeye basarsa, programda 24 saniyeliğine bir donma meydana geliyor.

Cevabınız için teşekkür ederim. Dediğiniz gibi çözüm bulması biraz zaman alacak gibi görünüyor. Fonksiyonun hızını test eden bir uygulama yapılabilir evet.
64000 kayıt 800 kategoriye eşit bir şekilde bölünmüyor, rastgele bir şekilde bölünüyor. Yani kayıtların kategori sayıları farklı olabiliyor. Veritabanında kategori ismi olmayan bir tane veri yakalamıştım, onu database'e eklememek için o kodu yazmıştım.

# aşağıdaki koşul sağlanmıyorsa 
if _k[0] == f"{_i_}"
.
.
category_groups[(_i_, category_name)] = _records_
# yukarıdaki işlemin bi anlamı olmuyor. 
# çünkü _records_ listesi koşul sağlanmadığı için boş bir liste
# olarak kalmaya devam ediyor.
# dolayısıyla bir sonra yapılacak işlem için yeni bir sorgu tanımlandı:
if not _records_:
    pass
else:
    ...

all_categories ve category_names listelerine append ile değil de return ile nasıl veriler ekleyebiliriz henüz bulamadım.

Belki soyle bir seyler:

def _group_categories(database):
    category_groups = {}
    for db_row in database:
        for db_category in db_row[12]:
            if db_category[0] not in category_groups:
                category_groups[db_category[0]] = []
            category_groups[db_category[0]].append(db_row)
    return category_groups

def group_categories():
    global category_names, all_categories
    all_categories = _group_categories(database)
    category_names = sorted(category_dict.values())

merge_databases()

import random
for p in range(64000):
    catnums = list(map(lambda _: random.randrange(800), [0] * random.randint(1, 6)))
    cats = list(map(lambda catnum: (str(catnum), "Category #%d" % (catnum,)), catnums))
    for c in cats:
        category_dict[c[0]] = c[1]
    person = ['-', 'foo' + str(p), 'M', 'A', '01 January 2000', '12:12', 2451544.925739, '', '', 'Atlas Buoy 0.00E 0.00N', '', '-', cats]
    database.append(person)

group_categories()
1 Beğeni

Şöyle bir uyarı aldım:

Traceback (most recent call last):
  File "TkAstroDb.py", line 281, in <module>
    group_categories()
  File "TkAstroDb.py", line 275, in group_categories
    category_names = sorted(category_dict.values())
TypeError: '<' not supported between instances of 'NoneType' and 'str'

Edit: Neyse aşağıdaki gibi yapınca sorun çözüldü.

category_names = sorted([i for i in category_dict.values() if i is not None])

Kıyaslanılmayacak kadar fark var, teşekkür ederim. :slight_smile:

Şöyle bir sorun oluştu.
Program açıldıktan sonra kategoriler görüntülenebiliyor.


Ancak bir kategori seçip Display Records düğmesine tıklayınca şöyle bir hata alıyorum.

Exception in Tkinter callback
Traceback (most recent call last):
  File "/usr/lib/python3.6/tkinter/__init__.py", line 1705, in __call__
    return self.func(*args)
  File "TkAstroDb.py", line 1721, in display_results
    _category_ = list(c.keys())[0][1]
AttributeError: 'str' object has no attribute 'keys'

Normalde seçilen kategorilerdeki kayıtların aşağıdaki gibi treeview’e eklenmesi gerekiyor.

c değişkeninin kullanıldığı şu işlemler var:

for c in all_categories:
        _category_ = list(c.keys())[0][1]
        if _category_ in selected_categories:
            items_ = list(c.values())[0]
            for item in items_:
                ...

AttributeError hatası yükseltilen kodlardaki c'nin ilk değeri "1928" görünüyor mesela. ama c’nin ilk değerinin aşağıdakine benzer bir sözlük olması gerekiyor:

{('1928', 'Traits : Personality : Ambitious'): 
    [
        [1, 'François I, King of France', 'M', 'AA', '12 September 1494 Jul.Cal. (21 Sep 1494 greg.)', '22:00', '2266996.417593', '45n42', '0w20', 'Cognac', 'France', 'https://www.astro.com/astro-databank/François_I,_King_of_France', [('1928', 'Traits : Personality : Ambitious'), ('262', 'Lifestyle : Financial : Wealthy'), ('151', 'Notable : Famous : Royal family')]], 
        [27, 'Andersen, Hans Christian', 'M', 'AA', '2 April 1805', '01:00', '2380413.512824', '55n24', '10e23', 'Odense', 'Denmark', 'https://www.astro.com/astro-databank/Andersen,_Hans_Christian', [('2905', 'Traits : Body : Appearance unattractive'), ('1928', 'Traits : Personality : Ambitious'), ('2909', 'Traits : Personality : Unique'), ('24', 'Diagnoses : Major Diseases : Cancer'), ('71', 'Diagnoses : Psychological : Dyslexia'), ('75', 'Diagnoses : Psychological : Phobias'), ('147', 'Family : Childhood : Disadvantaged'), ('189', 'Family : Relationship : Married late/never'), ('207', 'Family : Parenting : Kids none'), ('227', 'Passions : Sexuality : Celibacy/ Minimal'), ('245', 'Passions : Sexuality : Sexual dysfunction'), ('570', 'Vocation : Writers : Autobiographer'), ('575', 'Vocation : Writers : Fiction'), ('582', 'Vocation : Writers : Playwright/ script'), ('586', 'Vocation : Writers : Textbook/ Non-fiction'), ('453', 'Vocation : Misc. : Factory work'), ('131', 'Notable : Extraordinary Talents : For Imagination'), ('301', 'Notable : Famous : Historic figure'), ('619', 'Notable : Famous : Top 5% of Profession'), ('632', 'Notable : Book Collection : American Book')]],
        [...],
        [...],
        [...]
    ]
}

Ben hic record display edemedim. Tree view hep bos kaldi. Hata da almadim.

c degiskeninin omru for loop bitene kadar. Bana all_categories’den bahset.

"c’nin ilk degeri"nden kastin “all_categories listesinin elemanlarindan her biri” mi?


Yazmadigim, okumadigim kodlardan satir bazli ornekler verip “bu calismiyor” diyorsun. Baska bir programciyla iletisebilmek icin daha high-level konseptler uzerinden dusunmen/konusman lazim.

Mesela buradaki sorun group_categories’in yazdigi all_categories ile programin geri kalaninin bekledigi all_categories’in yapisinin farkli olmasi galiba.

all_categories’i “1720. satirdaki c’ye deger veren sey” olarak degil de “butun kategorileri id indisli tutan dictionary” olarak dusunmen lazim.

Kuvvetli, static type sistemi olan ve kullandigin degiskenleri deklare etmen gereken bir dilde kod yazmani tavsiye edebilirim; kodun planlanmis olmaktan ziyade sans eseri calisiyormus gibi gozukuyor :​).

Fonksiyonlari da “bazi satirlari bir isim altinda gruplayan seyler” olarak degil, parametre alip return value donduren islemler olarak dusunmen lazim.

Ornegin: “Benim group_categories’i { "1": "Hede", "2": Hodo } ve [["1, 'Fransua', 'M', ["1"]]]) argumanlariyla cagirdigimda x donerken seninkini cagirdigimda y donuyor.” cok net (ve aksiyon alinabilir) bir hata belirtme sekli.


Sonuc: Girdi ve cikti turlerini bilmedigim, testi olmayan, orijinalini kendi bilgisayarimda tam calistiramadigim group_categories fonksiyonunun bir taklidini yaptim. Orijinalinin yerine gecememis olabilir; normal. Zaten amac algoritmayi gostermekti.

İşte kategorideki kayıtların display edilmesi gerekiyordu. Bende de AttributeError hatası verdi.

c değişkeni all_categories’in bir elemanı ve size bir önceki mesajımda bahsettiğim gibi veri tipi bir sözlük.

c’nin ilk değeri dediğim şey all_categories[0].

Yazdığınız kodun bir yerde sorun oluşturduğundan bahsettim.
Mesela buradaki sorun group_categories 'de yazdığınız all_categories ile programin geri kalaninin bekledigi all_categories 'in veri tiplerinin farkli olması.

all_categories bir liste olarak tanımlandı. Dolayısıyla programın geri kalanı da bu listeye uyumlu olacak şekilde tasarlandı. Siz ise all_categories’i bir sözlük olarak tanımlamışsınız.

all_categories listesinin toplamda 793 tane elemanı var. Her bir eleman ise bir sözlük. Her sözlükte de farklı sayıda elemanlar var. Yani aşağıdaki veri, all_categories listesinin bir elemanı.

{('1928', 'Traits : Personality : Ambitious'): 
    [
        [1, 'François I, King of France', 'M', 'AA', '12 September 1494 Jul.Cal. (21 Sep 1494 greg.)', '22:00', '2266996.417593', '45n42', '0w20', 'Cognac', 'France', 'https://www.astro.com/astro-databank/François_I,_King_of_France', [('1928', 'Traits : Personality : Ambitious'), ('262', 'Lifestyle : Financial : Wealthy'), ('151', 'Notable : Famous : Royal family')]], 
        [27, 'Andersen, Hans Christian', 'M', 'AA', '2 April 1805', '01:00', '2380413.512824', '55n24', '10e23', 'Odense', 'Denmark', 'https://www.astro.com/astro-databank/Andersen,_Hans_Christian', [('2905', 'Traits : Body : Appearance unattractive'), ('1928', 'Traits : Personality : Ambitious'), ('2909', 'Traits : Personality : Unique'), ('24', 'Diagnoses : Major Diseases : Cancer'), ('71', 'Diagnoses : Psychological : Dyslexia'), ('75', 'Diagnoses : Psychological : Phobias'), ('147', 'Family : Childhood : Disadvantaged'), ('189', 'Family : Relationship : Married late/never'), ('207', 'Family : Parenting : Kids none'), ('227', 'Passions : Sexuality : Celibacy/ Minimal'), ('245', 'Passions : Sexuality : Sexual dysfunction'), ('570', 'Vocation : Writers : Autobiographer'), ('575', 'Vocation : Writers : Fiction'), ('582', 'Vocation : Writers : Playwright/ script'), ('586', 'Vocation : Writers : Textbook/ Non-fiction'), ('453', 'Vocation : Misc. : Factory work'), ('131', 'Notable : Extraordinary Talents : For Imagination'), ('301', 'Notable : Famous : Historic figure'), ('619', 'Notable : Famous : Top 5% of Profession'), ('632', 'Notable : Book Collection : American Book')]],
        [...],
        [...],
        [...]
    ]
}

Ben, kategori isimlerini kullanarak kayıtları treeview üzerinde görüntülüyordum. Sizin yazdığınız kodlarda, kategori isimleri yerine sadece kategori numaraları var.

Kodlar amaca uygun şekilde tasarlandı. Elbette daha değişik şekilde de yazılabilir.

Elinize sağlık, belki mevcut kodlardaki bazı kısımlar değiştirilerek, sizin yazdığınız kodlarla uyumlu bir şekilde çalışır, denemedim ama bakarım.

Yazdığınız kodları şu şekilde değiştirdim:

def _group_categories(database):
    category_groups = {}
    for db_row in database:
        for db_category in db_row[12]:
            if (db_category[0], db_category[1]) not in category_groups:
                if db_category[1] is None:
                    pass
                category_groups[(db_category[0], db_category[1])] = []
            category_groups[(db_category[0], db_category[1])].append(db_row)
    return category_groups

1728’nci satırdan 1730’uncu satıra kadar olan kodları da şu şekilde değiştirdim:

    for c, _c in all_categories.items():
        if c[1] in selected_categories:
            for item in _c:

Ve sorun çözüldü.
Yardımlarınız için teşekkür ederim.

@aib

Yazdığınız kodu düşünüyordum da, ne güzel akıl etmişsiniz. Elinize, aklınıza sağlık. :slight_smile:
Bu arada yazdığınız iki fonksiyonu tek fonksiyona çevirdim, bir de database'i argüman olarak belirtmeye gerek duymadım. Aşağıda göründüğü gibi kullandım. Zaman ayırdığınız için teşekkür ederim.

def group_categories():
    global category_names, all_categories
    category_groups = {}
    for db_row in database:
        for db_category in db_row[12]:
            if (db_category[0], db_category[1]) not in category_groups:
                if db_category[1] is None:
                    pass
                category_groups[(db_category[0], db_category[1])] = []
            category_groups[(db_category[0], db_category[1])].append(db_row)
    all_categories = category_groups
    category_names = sorted(
        [i for i in category_dict.values() if i is not None]
    )