Programm ist viel zu langsam

Wenn du dir nicht sicher bist, in welchem der anderen Foren du die Frage stellen sollst, dann bist du hier im Forum für allgemeine Fragen sicher richtig.
rogerb
User
Beiträge: 878
Registriert: Dienstag 26. November 2019, 23:24

Ich hatte zwischenzeitlich __blackjacks__ Ansatz mit dem Dictionary um itertools.combinations erweitert.

Außerdem gibt es jetzt noch die Möglichkeit den Dictionary als Pickle zu speichern. Damit spart man ca. 50% der Zeit.

Code: Alles auswählen

#!/usr/bin/env python3
import time
from collections import defaultdict
from itertools import combinations
from pathlib import Path
import pickle

ROOTFOLDER = "scrabble"
PICKLE_PATH = Path(ROOTFOLDER, "lookup_table.p")

# https://gist.github.com/MarvinJWendt/2f4f4154b8ae218600eb091a5706b5f4
DICTIONARY_PATH = Path(ROOTFOLDER, "wordlist-german.txt")

def make_canonical_form(word):
    return "".join(sorted(word))


def write_lookup_table(pickle_object):
    with PICKLE_PATH.open("wb") as pickle_file:
        pickle.dump(pickle_object, pickle_file)


def read_lookup_table():
    if PICKLE_PATH.exists():
        with PICKLE_PATH.open("rb") as pickle_file:
            return pickle.load(pickle_file)
    else:
        return dict()


def make_lookup_table():
    sorted_characters_to_words = defaultdict(set)
    with DICTIONARY_PATH.open(encoding="utf-8") as lines:
        for line in lines:
            word = line.strip().lower()
            sorted_characters_to_words[make_canonical_form(word)].add(word)
    return sorted_characters_to_words


def get_lookup_table():
    lookup_table = read_lookup_table()
    if not lookup_table:
        lookup_table = make_lookup_table()
        write_lookup_table(lookup_table)
    return lookup_table


def main():
    needle = "qwertz"
    look_up_table = get_lookup_table()

    word_parts = {
        make_canonical_form("".join(part))
        for n in range(2, len(needle) + 1)
        for part in combinations(needle, n)
    }

    results = [word for part in word_parts for word in look_up_table[part]]
    print(results)


if __name__ == "__main__":
    start = time.perf_counter()
    main()
    runtime = time.perf_counter() - start
    print(f"program took {runtime:5.3f} seconds")


"""
Ausgabe vor dem pickeln:
['er', 're', 'wert', 'rtw', 'wz', 'qwertz', 'wez', 'we', 'qr', 'terz', 'rwe', 'wer', 'erz']
program took 8.625 seconds

Ausgaben nach dem pickeln:
['qr', 'erz', 'wez', 'rtw', 'we', 're', 'er', 'wz', 'wert', 'wer', 'rwe', 'terz', 'qwertz']
program took 4.265 seconds
"""
LukeNukem
User
Beiträge: 232
Registriert: Mittwoch 19. Mai 2021, 03:40

rogerb hat geschrieben: Mittwoch 28. Juli 2021, 09:43 In diesem Fall macht eigentlich nur time.perf_counter() Sinn, da time.perf-counter() für kurze Zeitabstände wie hier, gedacht ist. So ist es ja auch in PEP 418 beschrieben.
Das steht in PEP 418, ja. Aber ich habe Probleme mit solchen Heckenausdrücken. Was genau ist ein "kurzer Zeitabstand"? ;-)
rogerb
User
Beiträge: 878
Registriert: Dienstag 26. November 2019, 23:24

@LukeNukem,

wenn ich die PEP richtig verstanden habe, stellt sie einen Leitfaden dar und spricht Empfehlungen aus. Sie erinnert mich daran, dass die gemessene Zeit mehr und mehr von der real verstrichenen Zeit abweicht. Dann gibt sie mir die Freiheit selbst zu entscheiden ob das bei meinem Vorhaben eine Rolle spielt oder nicht. Ich finde diese Wortwahl erfrischend, ungenau.
__deets__
User
Beiträge: 14545
Registriert: Mittwoch 14. Oktober 2015, 14:29

Ich fände einen Hinweis darauf, dass das lediglich unter Windows relevant ist, durchaus angebracht. Sonst ist das alles der gleiche Aufruf. Und auch eine Einordnung, bei welchen Größenordnungen man den einsetzen sollte, und ab wann er Probleme macht. Warum muss ich das erst selbst rausfinden? Und komme im Zweifel erst nach Stunden drauf?
rogerb
User
Beiträge: 878
Registriert: Dienstag 26. November 2019, 23:24

@__deets__, ich verstehe nicht was du meinst. Die PEP 418 liefert eine Menge an Informationen zum Verhalten der Timer in Bezug auf das jeweilige Betriebssystem.
LukeNukem
User
Beiträge: 232
Registriert: Mittwoch 19. Mai 2021, 03:40

rogerb hat geschrieben: Freitag 30. Juli 2021, 07:50 wenn ich die PEP richtig verstanden habe, stellt sie einen Leitfaden dar und spricht Empfehlungen aus. Sie erinnert mich daran, dass die gemessene Zeit mehr und mehr von der real verstrichenen Zeit abweicht. Dann gibt sie mir die Freiheit selbst zu entscheiden ob das bei meinem Vorhaben eine Rolle spielt oder nicht. Ich finde diese Wortwahl erfrischend, ungenau.
Hallo Rogerb,

hm, ich weiß nicht Recht, es gibt ja zwei Möglichkeiten: entweder, die gemessene Zeit hat eine (mehr oder weniger konstante) Drift. Dann bin ich in vielen Fällen, insbesondere beim Profiling, fein 'raus, denn dabei geht es ja eher darum, die langsamsten Codeabschnitte zu finden. Wenn es um die Einhaltung von SLAs geht, sieht die Sache natürlich anders aus... Die andere Möglichkeit ist, daß der Counter irgendwann überläuft und wieder bei 0 startet, was für beide Anwendungsfälle ein No-Go wäre. Aber was ist der Grund für diese Einschränkung "short", die zwar im PEP 418 und in der Internet-Dokumentation, nicht aber in der lokalen Dokumentation steht? Und wie lange ist "short"? Wenn ich als Vergleich das Alter des Universums nehme, dann sind 10.000 und sogar 100.000 Jahre sicherlich noch "short".

Nun, im Endeffekt macht es zumindest unter Nicht-Windows-Systemen keinen Unterschied, denn sowohl time.monotonic() als auch time.perf_counter() geben genau dasselbe zurück und unterscheiden sich daher in technischer Hinsich kein bisschen. Trotzdem würde ich aus Gründen der Lesbar- und Verständlichkeit für Zeitmessungen immer time.perf_counter() nehmen, aber das ist wohl Geschmacks- und Gewohnheitssache...
rogerb
User
Beiträge: 878
Registriert: Dienstag 26. November 2019, 23:24

@LukeNukem,
Trotzdem würde ich aus Gründen der Lesbar- und Verständlichkeit für Zeitmessungen immer time.perf_counter() nehmen,
Genau, das war ja auch mein Fazit.
Besonders, wenn man seinen Code mit anderen teilt, ist es eine gute Idee.
Antworten