Gleichungen umformen - Kritik erwünscht!

Code-Stücke können hier veröffentlicht werden.
Antworten
Joshuah992
User
Beiträge: 16
Registriert: Samstag 3. März 2018, 13:27

Dienstag 10. April 2018, 21:08

Hi! :D

Wie ihr ja vielleicht schon wisst, versuche ich seit einer Weile, Programmieren zu lernen. Und wie tut man das am Besten? Man programmiert was. Also habe ich mich vor einer Woche oder so hingesetzt und an mein erstes (für meine Maßstäbe) größeres Skript gewagt. Nebenbei gesagt tut es etwas, das (wahrscheinlich deutlich anders gelöst) standardmäßig in entsprechenden Libraries implementiert ist, aber ich habe es mir eben als kleine Herausforderung ausgesucht.

Okay, ich will euch nicht weiter auf die Folter spannen: Mein Skript definiert eine mathematische Gleichung. Der User kann sie dann umformen, wie in der Schule, nur dass es eben automatisch geht. Einfach "+5" oder so eintippen, das Skript formt die Gleichung automatisch um und zeigt sie an.

So zumindest der Plan (*hust*). Bislang kann das Skript Addition, Subtraktion und Multiplikation, und zwar von Integers und floats. Nicht von Variablen, was das Skript noch ziemlich nutzlos macht, aber genau deshalb dachte ich, etwas Hilfe wäre nicht schlecht. Ich hatte z.B. mit Objektorientierung bis zu diesem Skript nie etwas zu tun, und ich muss sagen, dass mein Einstieg doch etwas holprig verläuft.

Also, über Kritik und den Blickwinkel eines erfahrenen Programmierers würde ich mich sehr freuen :P

Hier mal der Code:

Code: Alles auswählen


class object:
    def __init__(self, vorzeichen, multiplikator, basis, exponent, teiler):
        self.vorzeichen = vorzeichen
        self.multiplikator = multiplikator
        self.basis = basis
        self.exponent = exponent
        self.teiler = teiler

    def solve(self):
        if type(self.basis) == str:
            self.multiplikator = (self.vorzeichen * self.multiplikator) / self.teiler
            self.vorzeichen = 1
            self.teiler = 1
        else:
            self.basis = (self.vorzeichen * self.multiplikator * self.basis**self.exponent) / self.teiler
            self.vorzeichen = 1
            self.multiplikator = 1
            self.exponent = 1
            self.teiler = 1

    def rechnen_mal(self, multiplikator):
        if multiplikator == self.basis:
            self.exponent += 1
        else:
            self.multiplikator *= multiplikator

    def rechnen_geteilt(self, divisor):
        if divisor == self.basis:
            self.basis = 1
            self.teiler = 1
        else:
            self.teiler *= divisor
    def rechnen_potenz(self, exponent):
            self.exponent *= exponent



# ---------------------------------------------------------


def multiply(gleichung, multiplikator):
    for term in gleichung:
        for objekt in term:
            objekt.rechnen_mal(multiplikator)


def divide(gleichung, divisor):
    for term in gleichung:
        for objekt in term:
            objekt.rechnen_geteilt(divisor)


def rais(gleichung, exponent):
    for term in gleichung:
        for objekt in term:
            objekt.rechnen_potenz(exponent)

def addsubtract(gleichung, summand):
    gleichung[0].append(object(1, 1, summand, 1, 1))

    gleichung[1].append(object(1, 1, summand, 1, 1))



# ---------------------------------------------------------



def unify(term, objekt1, objekt2):
    if (type(objekt1.basis) == int or type(objekt1.basis) == float) and\
        (type(objekt2.basis) == int or type(objekt2.basis) == float):
        objekt1.solve()
        objekt2.solve()

        objekt1.basis += objekt2.basis

        term.remove(objekt2)
    elif type(objekt1.basis) == str and objekt2.basis == objekt1.basis and objekt2.exponent == objekt1.exponent:
        objekt1.solve()
        objekt2.solve()

        objekt1.multiplikator += objekt2.multiplikator

        term.remove(objekt2)

    else:
        pass    # In diesem Fall haben beide Objekte keine Gemeinsamkeiten




# ---------------------------------------------------------



def allsolve(gleichung):
    for term in gleichung:
        for objekt in term:
            objekt.solve()


def termunify(term):        # termunify() sortiert die Objekte eines Terms nach Zahlen und Variablen, cleart den Term vorübergehend.
                            # durch ein komplexes System werden einzelne Variablengruppen vereint. Daran beteiligt sind die Listen
                            # 'variablen' und 'term_auslagerung_vars_temp'.
                            # Am Ende jedes Schrittes wird der Ursprungsterm wieder durch die vereinigten Objekte
                            # erweitert.

    term_auslagerung_0 = []
    term_auslagerung_vars = []
    term_auslagerung_vars_temp = []
    variablen = []

    for i in term:          # For-Schleife sortiert Objekte mit variablen als Basis aus, Zahlen kommen in eine Liste, Variablen in eine andere.
        if type(i.basis) == int or type(i.basis) == float:
            term_auslagerung_0.append(i)
        else:
            term_auslagerung_vars.append(i)

    term.clear()  # Ursprünglicher Term wird vorerst geleert. Nachdem alle anderen Listen vereinigt sind, kehren vereinigte Objekte zurück in den Term.

    for i in term_auslagerung_vars:  # "variablen" ist die Liste aller vorkommenden Variablen.
        if i.basis not in variablen:
            variablen.append(i.basis)

    # --------------------------------------------------------------------

    for i in variablen:  # for-schleife angelt Objekte mit Basis i und packt sie in ...temp-Liste.
        for o in term_auslagerung_vars:
            if o.basis == i:
                term_auslagerung_vars_temp.append(o)

        while len(term_auslagerung_vars_temp) > 1:  # temp-Liste wird vereinigt
            unify(term_auslagerung_vars_temp, term_auslagerung_vars_temp[0], term_auslagerung_vars_temp[1])

        term.append(term_auslagerung_vars_temp[
                        0])  # vereinigte Objekte aus temp-liste werden in den ursprünglichen Term verschoben

        term_auslagerung_vars_temp.clear()  # temp- liste wird für die nächste Variable geleert.

    while len(term_auslagerung_0) > 1:
        unify(term_auslagerung_0, term_auslagerung_0[0], term_auslagerung_0[1])

    term.append(term_auslagerung_0[0])

def allunify(gleichung):
    for termm in gleichung:
        termunify(termm)

print ('Allsolve und Allunify erfolgreich definiert!')

# ---------------------------------------------------------

# INITIALISIERUNG

term0 = []
term1 = []

gleichung0 = [term0, term1]

term0.append(object(1, 5, 'a', 2, 1))
term0.append(object(1, 5, 'b', 1, 1))
term0.append(object(1, 1, 10, 1, 1))

term1.append(object(1, 3, 'a', 1, 1))
term1.append(object(1, 1, 5, 1, 1))
term1.append(object(1, 1, 15, 1, 1))

print ('Erfolgreich initialisiert!')

# ---------------------------------------------------------

# USER INTERFACE

# todo - Rechenart Division funktioniert nicht.

while True:

    allsolve(gleichung0)
    allunify(gleichung0)



    darstellung_term0 = ''



    for objekt in gleichung0[0]:
        if type(objekt.basis) == str:
            darstellung_term0 += '(' + str(objekt.multiplikator) + str(objekt.basis) + ')' + ' + '
        else:
            darstellung_term0 += '(' + str(objekt.basis) + ')' + ' + '

    darstellung_term0 = darstellung_term0[:-3]



    darstellung_term1 = ''
    for objekt in gleichung0[1]:
        if type(objekt.basis) == str:
            darstellung_term1 += '(' + str(objekt.multiplikator) + objekt.basis + ')' + ' + '
        else:
            darstellung_term1 += '(' + str(objekt.basis) + ')' + ' + '

    darstellung_term1 = darstellung_term1[:-3]

    print('GLEICHUNG:')
    print(darstellung_term0 + ' = ' + darstellung_term1 + '\n')

    user_input = input()

    if user_input[0] == '+':
        addsubtract(gleichung0, float(user_input[1:]))
    elif user_input[0] == '-':
        addsubtract(gleichung0, -(float(user_input[1:])))
    elif user_input[0] == '*':
        multiply(gleichung0, (float(user_input[1:])))   # todo - multiplizieren: Eventuelle Klammern eliminieren beim Einlesen!
    elif user_input[0] == '/':
        pass
    

Benutzeravatar
MagBen
User
Beiträge: 768
Registriert: Freitag 6. Juni 2014, 05:56
Wohnort: Bremen
Kontaktdaten:

Donnerstag 12. April 2018, 07:52

Es bringt mehr bei Fragen kürzere Beispiele zu posten (200 Zeilen sind mir z.B. zu viel).

Wenn du mit Python analytisch rechnen willst ("Gleichungen umformen"), dann schau dir mal Sympy an: http://www.sympy.org/en/index.html

Wenn du Objektorientierung lernen willst, dann schau dir PyQt an. PyQt ist eine GUI Klassenbibliothek, die objektorientierte Programmierung fast schon erzwingt.
a fool with a tool is still a fool, www.magben.de, YouTube
Sirius3
User
Beiträge: 7919
Registriert: Sonntag 21. Oktober 2012, 17:20

Donnerstag 12. April 2018, 08:18

@Joshuah992: ehrlich gesagt, verstehe ich nicht, wie Du was umformst; von daher nur eine rein Kritik an der Form. `object` ist eine eingebaute Klasse, von der alle anderen Klassen erben und man sollte sie nicht überschreiben. Klassennamen werden generell in CamelCase geschrieben. `vorzeichen`,`multiplikator` und `teiler` kann man zu einer Zahl zusammenfassen. Wenn Du mit Bruchzahlen rechnen willst, nimm fraction.Fraction. Das macht `solve` auch überflüssig. `basis` sollte immer eine Unbekannte sein und nie eine Zahl (denn dafür gibt es ja schon den Multiplikator). Gibt es keine basis, kann dies ja None sein und der Exponent 0.
Da Du nur eine Basis hast, kannst Du nur mit einer Unbekannten rechnen. `rechnen_potenz` und `rais` sind falsch. Die anderen Rechnen-Methoden kannst Du als __mul__, __div__, etc. implementieren.

Beim Design von Klassen sollte man sich überlegen, ob man nicht unveränderliche Klassen schreibt, weil diese viel einfacher zu handhaben sind. Typische Beispiele in Python sind Zahlen und Strings. Dann erzeugst Du immer eine neue Instanz Deiner Terme, bzw. Summen, bzw. Gleichungen. Das ist für den Nutzer viel verständlicher und ist gibt weniger Möglichkeiten Fehler zu machen.

Typen von Variablen prüft man immer mit isinstance. Bei Zahlen prüft man gegen den abstrakten Typ number.Number, oder einer seiner Verwandten.
Bei `unify` tritt der erste Fall aber gar nicht mehr auf, wenn Du die oben beschriebenen Veränderungen umsetzt, eine Typprüfung ist also nicht nötig.

`termunify` habe ich mir nicht wirklich angeschaut, die Funktion ist mir zu wirr und zu lang. Variablennamen mit einem Buchstaben vermeiden.

Zeile 190ff: Term-Objekte sollten sich selbst darstellen können (__str__-Methode). Statt Strings mit + zusammenzustückeln nimmt man .format.
Joshuah992
User
Beiträge: 16
Registriert: Samstag 3. März 2018, 13:27

Donnerstag 12. April 2018, 17:10

Hi Leute^.^

Vielen Dank für die Antworten! War im Nachhinein doch etwas blauäugig, meinen kompletten 220-Zeilen-Erguss hier zu posten, umso mehr freut es mich, dass sich mindestens Sirius3 die Mühe gemacht hat, da vieles von durchzugucken, das find ich echt stark.

Stimmt, mit nur einer Basis kann ich in einem Element nur mit einer einzigen Variablen rechnen. Da hatte ich gar nicht dran gedacht. Im ursprünglichen Kontext dieses Skripts - in dem es freilich nicht funktionierte, es war nur die Idee da - sollte es nur im Hintergrund Gleichungen definieren und umformen, der User sollte nichts davon mitbekommen. Meine Idee war nämlich ein Skript, das als Input Punkte in einem Koordinatensystem bekommt und dann eine mathematische Funktion ausspuckt, die diese Punkte verbindet / die einen Graphen beschreibt, in dem diese Punkte enthalten sind. Für den Anfang eine lineare Funktion, um genau zu sein ( f(x)=ax+b )

So etwa:
Input:
p1) x=1; y=5
p2) x=2; y=9

Output:
f(x)=4x+1

Geht viel einfacher, solange man sich nur im Bereich von linearen Funktionen bewegt. Aber was ist mit höheren Graden? Dafür fiel mir als Nichtmathematiker nur der Weg über das Umformen von Gleichungen (Punkte einsetzen, nach bestimmten Variablen auflösen, gleichsetzen) ein. Und das hab ich eben versucht, so zu modellieren.

Aber dank euch hab ich jetzt erstmal einen Haufen neuer Ansatzpunkte! Bin gespannt wo mich das hinführt :D
Sirius3
User
Beiträge: 7919
Registriert: Sonntag 21. Oktober 2012, 17:20

Donnerstag 12. April 2018, 17:41

@Joshuah992: ich dachte, es ist für Dich nur eine Spielerei, um Klassen, oder Formeln usw. zu üben. Wenn Du eine konkrete Anwendung hast, dann gibt es dafür schon etwas fertiges. Also, wenn es um Gleichungen geht, das von MagBen angesprochene Sympy. Wenn es aber um Punkte geht, die durch eine Gleichung beschrieben werden sollen, dann ist das numpy.polyfit, wo aber gar keine Gleichung gebildet wird, sondern es werden die Koeffizienten in Form eines Zahlenvektors [4, 1] berechnet, die Interpretation (numpy.polyval) ist in diesem Spezialfall nämlich klar.
Joshuah992
User
Beiträge: 16
Registriert: Samstag 3. März 2018, 13:27

Donnerstag 12. April 2018, 17:49

@Sirius3 Das hast du genau richtig verstanden^^ Diese "konkrete Anwendung" ist nichts, was ich persönlich brauche, sondern etwas, das ich mir als Übung ausgesucht habe - das größere Skript, das eine Funktion ausgibt, genauso wie das Skript aus dem Eingangspost, das ein Teil davon ist.
Antworten