Seite 1 von 1

Warum funktioniert replace function nicht?

Verfasst: Dienstag 13. Dezember 2022, 18:53
von doctor_cocktor
#!/usr/bin/env python3

# The signatures of this class and its public methods are required for the automated grading to work.
# You must not change the names or the list of parameters.
# You may introduce private/protected utility methods though.
class ProfanityFilter:

def __init__(self, keywords, template):
self.keywords = keywords
self.template = template

def cleaned(self, keyword):

len_k = len(keyword)
len_t = len(self.template)

fits = len(keyword) // len(self.template)
rest = len(keyword) % len(self.template)

changed_word = self.template * fits + self.template[:rest]

return changed_word

def filter(self, msg):
msg_copy = msg
msg_list = msg.split()

for msg_word in msg_list:
for keyword in self.keywords:
if keyword in msg_word:
idx = msg_word.find(keyword)
cleaned_word = self.cleaned(keyword)
final_word = msg_word[:idx] + cleaned_word + msg_word[len(cleaned_word) + idx:]

msg_copy.replace(msg_word, final_word)

return msg_copy

# You can play around with your implementation in the body of the following 'if'.
# The contained statements will be ignored while evaluating your solution.
if __name__ == '__main__':
f = ProfanityFilter(["duck", "shot", "batch", "mastard"], "?#$")
offensive_msg = "abc defghi mastard jklmno"
clean_msg = f.filter(offensive_msg)
print(clean_msg) # abc defghi ?#$?#$? jklmno

Re: Warum funktioniert replace function nicht?

Verfasst: Dienstag 13. Dezember 2022, 20:04
von Sirius3
Was ist die konkrete Frage? Was erwartest Du, was passieren soll? Was passiert statt dessen?
Um das herauszufinden, gibt man am einfachsten per `print` innerhalb der Schleife die interessanten Variablen aus.

len_k und len_t werden in cleaned gar nicht benutzt. msg_copy ist gar keine Kopie von msg, ist auch gar nicht nötig, weil Strings unveränderlich sind. `replace` liefert deshalb auch einen neuen String mit der Ersetzung zurück. Dieses Index-Gehampel ist deutlich zu kompliziert, das kann man mit replace viel einfacher schreiben:

Code: Alles auswählen

final_word = msg_word.replace(keyword, cleaned_word, 1)
Aber warum soll nur die erste Erwähnung ersetzt werden?
Ist es richtig, dass "a-duck batch-shot duck-duck" zu "a-?#$? batch-?#$? ?#$?-duck" wird?

Re: Warum funktioniert replace function nicht?

Verfasst: Dienstag 13. Dezember 2022, 20:22
von __blackjack__
@doctor_cocktor: Die Methode funktioniert. Nur halt nicht wie Du anscheinend denkst. Zeichenketten sind nicht veränderbar, also kann `replace()` die Zeichenkette selbst nicht verändern, sondern nur eine veränderte Zeichenkette als Ergebnis liefern. Und mit der machst Du nichts. Das ist der Fehler an der Stelle.

Ebenfalls ein Missverständnis scheint bei Zuweisungen zu bestehen: ``msg_copy = msg`` erstellt *keine* Kopier. Danach ist das *selbe* Objekt einfach nur unter zwei verschiedenen Namen ansprechbar. Python kopiert bei einfachen Zuweisungen und bei Argumentübergaben bei Aufrufen nie von sich aus Werte.

Namen sollten keine kryptischen Abkürzungen enthalten oder gar nur daraus besteht. Wenn `message` oder `index` gemeint sind, sollte man nicht nur `msg` oder `idx` schreiben. Einbuchstabige Namen gehen in der Regel gar nicht. Ausser bei (Lauf)Indices `i`, `j`, `k` oder Koordinaten `x`, `y`, `z` reicht ein einzelner Buchstabe eigentlich nie um dem Leser die Bedeutung des Wertes dahinter zu vermitteln.

Grunddatentypen gehören auch nicht in Namen. Das ändert sich während der Entwicklung gar nicht so selten, und dann hat man entweder irreführende, falsche Namen oder muss überall die betroffenen Namen ändern. `msg_list` wäre einfach `words`. Wobei man auch nicht jedes kleine Zwischenergebnis an einen Namen binden muss. `msg_list` wird ja nur an einer Stelle benutzt, und da könnte man auch gleich ``message.split()`` einsetzen, ohne das die Lesbarkeit leidet.

Das ist auch etwas verquer umgesetzt. Es wird jedes Wort durchgegangen und dann getestet ob jedes verbotene Wort teil davon ist, um dann in der *gesamten* Nachricht das Wort zu zensieren. Das wird gegebenenfalls mehrfach gemacht, auch wenn ein Wort bereits ersetzt wurde. Das zerlegen in Worte um dann darin zu suchen ist schon unsinnig. Am einfachsten würde man einmal alle verbotenen Worte nacheinander ersetzen, egal ob die nun enthalten sind oder nicht, denn ob man erst sucht und dann ersetzt, oder einfach ersetzt, ist ja egal. Im Gegensatz zu jetzt, ist das sogar *weniger* Arbeit die verrichtet werden muss.

`len_k` und `len_t` enthalten nicht nur kryptische Abkürzungen, sondern werden auch überhaupt nicht verwendet.

Es gibt die praktische `divmod()`-Funktion.

Etwas effizienter wäre es nur einmal mit einem regulären Ausdruck zu suchen und zu ersetzen.

Code: Alles auswählen

#!/usr/bin/env python3
import re


class ProfanityFilter:
    def __init__(self, keywords, template):
        self.keyword_re = re.compile("|".join(map(re.escape, keywords)))
        self.template = template * (
            max(map(len, keywords)) // len(template) + 1
        )

    def filter(self, message):
        return self.keyword_re.sub(
            lambda match: self.template[: len(match[0])], message
        )


def main():
    profanity_filter = ProfanityFilter(
        ["duck", "shot", "batch", "mastard"], "?#$"
    )
    offensive_message = "abc defghi mastard jklmno duck-type buckshot"
    clean_message = profanity_filter.filter(offensive_message)
    print(clean_message)


if __name__ == "__main__":
    main()
Als Klasse ist das fast schon ein „code smell“ weil das neben der `__init__()` nur eine Methode hat. Das könnte man auch mit einer Funktion lösen:

Code: Alles auswählen

#!/usr/bin/env python3
import re


def create_profanity_filter(keywords, template):
    keyword_re = re.compile("|".join(map(re.escape, keywords)))
    template = template * (max(map(len, keywords)) // len(template) + 1)
    return lambda message: keyword_re.sub(
        lambda match: template[: len(match[0])], message
    )


def main():
    profanity_filter = create_profanity_filter(
        ["duck", "shot", "batch", "mastard"], "?#$"
    )
    offensive_message = "abc defghi mastard jklmno duck-type buckshot"
    clean_message = profanity_filter(offensive_message)
    print(clean_message)


if __name__ == "__main__":
    main()