Zespół klas do przechowywania struktury logicznej dowolnego równania matematycznego

0

Dzień dobry.

Próbuję napisać strukturę logiczną do programu przetwarzającego wzory matematyczne. Dozwolone działania, to:
+-*.^()fun, zaś operandami są liczby i zmienne. Napisałem taką strukturę logiczną:

struct Zmienna {
    string symbol;
    string potega;
    
    Zmienna(string sy, string po) {
        symbol = sy;
        potega = po;
    };
    
    string odczytajZmienna() {
        return symbol;
    };
    
    string odczytajPotege() {
        return potega;
    };
...
};
class Skladowa {
    private:
        string iloczyn;
        vector<Zmienna> zmienne;
...
};
class Obiekt {
    private:
        vector<Skladowa> skladowe;
    
    public:
        Obiekt(string il = "", string zm = "", string po = "") {
            if (zm == "")
                skladowe.push_back(Skladowa(il));
                
            else
                skladowe.push_back(Skladowa(il, zm, po));
        };
...
};

Jak widać, wszystkie dane przechowuję w stringach i obiektach, zawierających stringi. Ta struktura pozwala przechowywać i przetwarzać wzory, które nie zawierają funkcji wykładniczych. Tym bardziej wielokrotnie wykładniczych. Np. 2^x^x. Macie jakieś propozycje jak zmodyfikować klasy by można było przechowywać w nich i takie wzory? Np. x^(2+x)^(3*x). Klasa Skladowa pozwala przechowywać składowe wielomianów o wielu zmiennych. Jednak pisząc ją nie przewidziałem wykładników składających się ze zmiennych. W szczególności przy przechowywaniu wielokrotnych potęg.

Proszę o wskazówki
Dzięki
M.

1

Pomysł jest nieczytelny, choćby przez zakłamane nazwy klas.
Zmienna to zmienna, z potęgą nie ma wspólnego. Przypuszczam, ze potrzebujesz klasy Potegowanie nie kasując klasy Zmienna (ale j/w nie jestem w pełni w temacie)
Skladowa -> Mnozenie
A Obiekt nie znaczy niemal nic, użyj jakiejś terminologii matematycznej.
Złe nazwy Cię kopną wcześniej czy później, bo skierują myślenie w maliny.

Poszukaj dla inspiracji haseł podobnych do "expression gramar, bo kilku klas brakuje

2

Ale stringoza (tak w ramach pozytywnej krytyki :P)

Klasycznie (tak jak robi się to podczas pisania parserów) to rozwiązuje się to przy pomocy wzorca kompozyt czyli potrzebujesz:

  • enume Operation oznaczajacego wszystkie operacje (chociaż enuma można pominąć)
  • klasy bazowej Component
  • klasy Lift (dziedziczącej z Component) reprezentującej liść czyli liczbę lub zmienną
  • klasy Expression (dziedziczącej z Component) reprezentującej operację dwuargumentową. Zawiera ona enuma Operation raz prawy i lewy Component

Potem 2^x^x to E(O('^'),L("2"), E(O('^'), L("x"), L("x")) Tak przynajmniej w uproszczeniu bym to widział

3

To jest tak bardzo oklepane, że aż boli. Ogarnij co to jest AST, możesz wziąć do ręki SICP i poczytać, a później wyjdzie oczywiste, że w zasadzie potrzebujesz prosty język z Binary i Unary expression. Parsowanie to odrębny temat, ale dla tego problemu tez powinno wejść lekko. Ewaluacja wyrażeń kolejny prosty temat, odrębny. Wszystko jest w SICP.

0
KamilAdam napisał(a):
  • klasy Expression (dziedziczącej z Component) reprezentującej operację dwuargumentową. Zawiera ona enuma Operation raz prawy i lewy Component

Potem 2^x^x to E(O('^'),L("2"), E(O('^'), L("x"), L("x")) Tak przynajmniej w uproszczeniu bym to widział

A w "nie uproszczeniu" może pojawić się zróżnicowanie "expression" ze względu na priorytet. Zbudowane drzewo już tego nie potrzebuje, ale parsing jeśli ma być ładny, to tak

0

Może pokażę co mam, bo może nie do końca się rozumiemy, albo jestem za głupi :P

Mam klasę Token, która opakowuje liczby, zmienne i operacje +-*/()^fun w obiekty. Potem te obiekty, zamieniam na kolejność ONP. Następnie w funkcji ostatecznej, mam stos na Obiekt (vector) na który odkładam przetworzone pojedyncze wyniki operacji na Tokenach / Obiektach. Jednak niestety nie mogę zapisać x^x^x :(

0

@mpaw:
Opisz dokładnie, jak Chcesz przetwarzac te równania, bo z tego co pamiętam to nie była kompletna ewaluacja tylko upraszczanie. Tak czy siak, nie wiem czemu nie Chcesz, do tokenizacji, skorzystać ze źródła, które Ci już podałem, (https://craftinginterpreters.com/scanning.html) i zrobić to porządnie?

1
AnyKtokolwiek napisał(a):
KamilAdam napisał(a):
  • klasy Expression (dziedziczącej z Component) reprezentującej operację dwuargumentową. Zawiera ona enuma Operation raz prawy i lewy Component

Potem 2^x^x to E(O('^'),L("2"), E(O('^'), L("x"), L("x")) Tak przynajmniej w uproszczeniu bym to widział

A w "nie uproszczeniu" może pojawić się zróżnicowanie "expression" ze względu na priorytet. Zbudowane drzewo już tego nie potrzebuje, ale parsing jeśli ma być ładny, to tak

problemu parsowania nawet tu nie poruszałem. Problem priorytetów nie wpływa na klasy które zaproponowałem, bo priorytety są zaszyte w kształcie drzewa. Jak op chce parsować to ja pojęcia nie mam. Może za pomocą Bisona to wtedy problem priorytetów też nas nie dotyka bo można to ogarnąć na poziomie konfiguracji Bisona

0
vector<Token> rozczlonkuj(string & w) {
    vector<Token> wynik;
    vector<string> liczby, litery;
    string znaki = regex_replace(w, regex("\\s+"), "");
    
    for (int i = 0; i < znaki.size(); i++) {
        if (czyToCyfra(znaki[i]))
            liczby.push_back(string(1, znaki[i]));
        
        else if (czyToLitera(znaki[i])) {
            if (liczby.size()) {
                czyscBuforLiczbIZapiszLiteral(liczby, litery, wynik);
                wynik.push_back(Token(Operacja, "*"));
            }
            
            litery.push_back(string(1, znaki[i]));
        }
        
        else if (czyToOperator(znaki[i])) {
            czyscBuforLiczbIZapiszLiteral(liczby, litery, wynik);
            czyscBuforLiterIZapiszZmienna(liczby, litery, wynik);
            wynik.push_back(Token(Operacja, znaki[i]));
        }
        
        else if (czyToNawiasLewy(znaki[i])) {
            if(litery.size()) {
                wynik.push_back(Token(Funkcja, join(litery)));
                litery.clear();
            }
            
            else if (liczby.size()) {
                czyscBuforLiczbIZapiszLiteral(liczby, litery, wynik);
                wynik.push_back(Token(Operacja, "*"));
            }
            
            wynik.push_back(Token(NawiasO, znaki[i]));
        }
        
        else if (czyToNawiasPrawy(znaki[i])) {
            czyscBuforLiterIZapiszZmienna(liczby, litery, wynik);
            czyscBuforLiczbIZapiszLiteral(liczby, litery, wynik);
            wynik.push_back(Token(NawiasZ, znaki[i]));
        } 
        
        else if (czyToPrzecinek(znaki[i])) {
            czyscBuforLiczbIZapiszLiteral(liczby, litery, wynik);
            czyscBuforLiterIZapiszZmienna(liczby, litery, wynik);
            wynik.push_back(Token(Przecinek, znaki[i]));
        }
    }
    
    czyscBuforLiczbIZapiszLiteral(liczby, litery, wynik);
    czyscBuforLiterIZapiszZmienna(liczby, litery, wynik);
    
    return wynik;
}
vector<Token> naONP(vector<Token> tokeny) {
	vector<Token> dzialania;
	vector<Token> wyjscie;
	
	for (int i = 0; i < tokeny.size(); i++) {
		if (tokeny[i].odczytajTyp() == Operacja) {
			while (dzialania.size() > 0 && dzialania[dzialania.size() - 1].odczytajTyp() == Operacja) {
				if ((tokeny[i].czyAsocjacyjny("lewa")  && cmpPierwszenstwo(tokeny[i], dzialania[dzialania.size() - 1]) <= 0) || 
				    (tokeny[i].czyAsocjacyjny("prawa") && cmpPierwszenstwo(tokeny[i], dzialania[dzialania.size() - 1]) <  0)) {
					wyjscie.push_back(dzialania.back());
					dzialania.pop_back();
					continue;
				}
				break;
			}
			dzialania.push_back(tokeny[i]);
		} 
		else if (tokeny[i].odczytajTyp() == NawiasO) {
			dzialania.push_back(tokeny[i]);
		}
		else if (tokeny[i].odczytajTyp() == NawiasZ) {
			while (dzialania.size() > 0 && dzialania[dzialania.size() - 1].odczytajTyp() != NawiasO) {
				wyjscie.push_back(dzialania.back());
				dzialania.pop_back();
			}
			dzialania.pop_back();
		}
		else {
			wyjscie.push_back(tokeny[i]);
		}
	}
	while (dzialania.size() > 0) {
		wyjscie.push_back(dzialania.back());
		dzialania.pop_back();
	}
    
    return wyjscie;
}
Obiekt obliczZONP(vector<Token> & tokenyONP) {
    vector<Obiekt> wynik;
    
    for (int i = 0; i < tokenyONP.size(); i++) {
        if (tokenyONP[i].odczytajTyp() == Liczba)
            wynik.push_back(Obiekt(tokenyONP[i].odczytajWartosc()));
        
        else if (tokenyONP[i].odczytajTyp() == Zmienna)
            wynik.push_back(Obiekt("1", tokenyONP[i].odczytajWartosc(), "1"));
        
        else if (tokenyONP[i].typ === Typ.Operacja) {
            Obiekt a = wynik.back(); wynik.pop_back();
            Obiekt b = wynik.back(); wynik.pop_back();
            
            if (tokenyONP[i].wartosc === "+")
                ...;
            
            if (tokenyONP[i].wartosc === "-")
               ...;
            
            if (tokenyONP[i].wartosc === "*")
                ...;
            
            if (tokenyONP[i].wartosc === "/")
                ...;
            
            if (tokenyONP[i].wartosc === "^")
                ...;
        }
    }
    
    return wynik[0];
}
0

Jak parsujesz do ONP, to wystarczy operatory otoczyć spacjami, a potem split().

0

Czy możecie podać tytuł książki najlepiej po polsku która omawia tą tematykę?

2

Już mówiłem, Struktura i interpretacja programów komputerowych, znana jako SICP i dostępna też za darmo na stronie MIT.

1 użytkowników online, w tym zalogowanych: 0, gości: 1