Entwicklung einer einfachen Matrizenklasse



  • Guter Hinweis. Muss ich nur mal schauen, wie sich das mit den Tastenbelegungen in Spielen macht. Gab afaik da mal das Problem, wenn man die CapsLock-Taste ganz ausgestellt hat.

    Aber Tastatur-Layout wechseln ist ja kein Problem.


  • Mod

    @wob sagte in Entwicklung einer einfachen Matrizenklasse:

    @zeropage sagte in Entwicklung einer einfachen Matrizenklasse:

    Ja, die eckigen Klammern mach ich immer altgr8 - altgr9 - altgr8 - altgr9

    Tipp: die ansonsten nutzlose Feststelltaste (caps lock) kann man wunderbar zur Tastaturlayoutumstellung verwenden. ü und + sind [ und ], keine Shifts nötig mit US-Layout. Mit deutschem Tastaturlayout kann man nicht programmieren 🙂

    Umschalten ist doof, da muss man ja dauernd umdenken. Tipp: US Layout und dann Umlaute auf rechtes Alt + Vokal legen. Gewöhnt man sich in einer Stunde dran und man vermisst nichts. Die rechte Alt-Taste ist gut für Tastenkombinationen erreichbar (Caps nicht so sehr...) und wird ansonsten für nichts spezielles gebraucht, was nicht auch die linke Alt leisten würde. Auch passt die Beschriftung der Tasten stets zum Effekt, da man sich leicht die Assoziation von Vokal zu Umlaut merken kann.

    @zeropage sagte in Entwicklung einer einfachen Matrizenklasse:

    Guter Hinweis. Muss ich nur mal schauen, wie sich das mit den Tastenbelegungen in Spielen macht. Gab afaik da mal das Problem, wenn man die CapsLock-Taste ganz ausgestellt hat.

    Bei mir ist seit Jahren CapsLock ein drittes Shift und ich hatte noch nie Probleme. Spiele scannen die Tasten, nicht die Tastenbelegung. Zumindest alle vernünftigen Spiele, die auf so etwas wie DirectX aufsetzen. Mag sein, dass du irgendwelche Ausnahmen von Anno 1996 finden kannst.



  • @SeppJ, @wob Könntet Ihr bitte kurz anreißen wie Ihr das macht?

    @SeppJ sagte in [Entwicklung einer einfachen Matrizenklasse]

    Umlaute auf rechtes Alt + Vokal legen.

    @wob sagte in [Entwicklung einer einfachen Matrizenklasse]

    Tipp: die ansonsten nutzlose Feststelltaste (caps lock) kann man wunderbar zur Tastaturlayoutumstellung verwenden.



  • @Caligulaminus sagte in Entwicklung einer einfachen Matrizenklasse:

    @SeppJ, @wob Könntet Ihr bitte kurz anreißen wie Ihr das macht?

    Bei mir in KDE -> Systemeinstellungen -> Eingabegeräte -> Tastatur
    Dort:

    • Reiter Belegungen: DE und EurKEY (US based with european letters) einstellen
    • Reiter Erweitert: "Switching to another Layout" -> "Caps Lock to first layout; Shift+Caps Lock to last layout"

    Und ich schreibe auch öfter Texte / Berichte, dann will ich unbedingt deutsches Layout haben. Allein schon wegen z/y. Die liegen für deutsche Wörter einfacher auf deutscher Belegung besser (ich habe eine deutsche Tastatur). Zum Programmieren im Editorfenster schnell Shift+Caps und schon schreibe ich mit US-Layout. Dort gibt's zur Not mit AltGr+u ein ü, aber dann bin ich mit Caps, u schneller.

    Aber im Endeffekt musst du selbst wissen, ob es dich nicht durcheinander bringt, wenn du mal die eine, mal die andere Belegung hast. Ich finde es relativ einfach und natürlich, englische Texte mit US-Belegung zu schreiben und deutsche Texte mit de-Belegung.



  • @wob sagte in Entwicklung einer einfachen Matrizenklasse:

    @Caligulaminus sagte in Entwicklung einer einfachen Matrizenklasse:

    @SeppJ, @wob Könntet Ihr bitte kurz anreißen wie Ihr das macht?

    Bei mir in KDE ...

    Ah, danke. Leider bin ich hier unter Windows unterwegs.

    Aber im Endeffekt musst du selbst wissen, ob es dich nicht durcheinander bringt, wenn du mal die eine, mal die andere Belegung hast. Ich finde es relativ einfach und natürlich, englische Texte mit US-Belegung zu schreiben und deutsche Texte mit de-Belegung.

    Genau so mache ich es derzeit. Einziges Problem sind halt die Umlaute. Da behelfe ich mir aktuell mit ac'tivAid. Ich dachte halt, daß es schöner wäre, das ganze mit Bordmitteln zu erschlagen...



  • Hier die erste größere Zusammenfassung.
    Ich hatte hier noch unter anderem folgenden Code zur Verfügung gestellt bekommen:

    template <typename T>
    Matrix<T>::Matrix(std::initializer_list<std::initializer_list<T>> init)
    : Matrix{ 
        init.size(),
        std::max(
            init,
            [](const auto& a, const auto& b) { //Lambda-Funktion?
                return a.size() < b.size();
            }
        ).size()
      }
    {
        std::size_t i = 0;
        for (const auto& row : init)
        {
            if (i >= rows())
                break;
            std::size_t j = 0;
            for (const auto& value : row)
            {
                if (j >= columns())
                    break;
                (*this)(i, j) = value;
                ++j;
            }
            ++i;
        } 
    }
    

    Was genau macht eigentlich die dortige Lambda-Funktion(?)?
    Den habe ich noch bei mir eingefügt:

    template<typename T>
    class Mat2D_T
    {
    public:
    	using valType = T;
    	using size_t = std::size_t;
    
    	class Row_T
    	{
    		valType* row_data;
    		size_t cols_ = 0;
    
    	public:
    		Row_T(valType* row_data, const size_t columns)
    			: row_data{ row_data }, cols_{ columns } {}
    
    		valType& operator[](const size_t column)
    		{
    			if (column >= cols_)
    				throw std::out_of_range("operator[](): column: " + std::to_string(column));
    
    			return row_data[column];
    		}
    	};
    
    	Mat2D_T() = default;
    	Mat2D_T(const size_t rows, const size_t columns, const valType& v = {})
    		: rows_{ rows }, columns_{ columns }, elements_(rows * columns, v) {}
    
    	Mat2D_T(const std::initializer_list<std::initializer_list<valType>>& elements)
    		: Mat2D_T{ elements.size(), std::max(elements, [](const auto& row, const auto& column) { return row.size() < column.size(); }).size() } 
    	{
    		size_t r = 0;
    		for (const auto& row : elements)
    		{
    			if (r >= rows()) break;
    			size_t c = 0;
    			for (const auto& v : row)
    			{
    				if (c >= columns()) break;
    				(*this)(r, c) = v;
    				++c;
    			}
    			++r;
    		}
    	}
    
    	size_t rows() const { return rows_; }
    	size_t columns() const { return columns_; }
    
    	valType element(const size_t row, const size_t column) const
    	{
    		const size_t idx = toLinearIndex(row, column);
    		return elements_[idx];
    	}
    
    	void setElement(const size_t row, const size_t column, const valType& v)
    	{
    		const size_t idx = toLinearIndex(row, column);
    		elements_[idx] = v;
    	}
    
    	valType operator()(const size_t row, const size_t column) const
    	{
    		const size_t idx = toLinearIndex(row, column);
    		return elements_[idx];
    	}
    
    	valType& operator()(const size_t row, const size_t column)
    	{
    		const size_t idx = toLinearIndex(row, column);
    		return elements_[idx];
    	}
    
    	Row_T operator[](const size_t row)
    	{
    		if (row >= rows())
    			throw std::out_of_range("operator[](): row: " + std::to_string(row));
    
    		return { &elements_[row * columns_], columns_ };
    	}
    	
    	Mat2D_T operator*(const Mat2D_T& mT) const
    	{
    		if (columns() != mT.rows())
    			throw std::exception("operator*()");
    
    		Mat2D_T nmT(rows(), mT.columns());
    		valType v = {};
    		for (size_t c = 0; c < mT.columns(); ++c)
    		{
    			for (size_t r = 0; r < rows(); ++r)
    			{
    				v = element(r, 0) * mT.element(0, c);
    				for (size_t l = 1; l < columns(); ++l)
    				{
    					v += element(r, l) * mT.element(l, c);
    				}
    				nmT[r][c] = v;
    			}
    			v = {};
    		}
    		return nmT;
    	}
    
    private:
    	size_t rows_ = 0;
    	size_t columns_ = 0;
    	std::vector<valType> elements_;
    
    	size_t toLinearIndex(const size_t row, const size_t column) const
    	{
    		if (row >= rows())
    			throw std::out_of_range("toLinearIndex(): row: " + std::to_string(row));
    		if (column >= columns())
    			throw std::out_of_range("toLinearIndex(): column: " + std::to_string(column));
    
    		return columns() * row + column;
    	}
    };
    


  • std::max() liefert das größte Element aus init und das Lambda ist der Komperator der die Elemente vergleicht. Verwendet wird das Dingsti aus init das die größte .length() hat.



  • @zeropage sagte in Entwicklung einer einfachen Matrizenklasse:

    Was genau macht eigentlich die dortige Lambda-Funktion(?)?

    Ich glaube der Code stammt von mir, daher bin ich mal so frei:

    template <typename T>
    Matrix<T>::Matrix(std::initializer_list<std::initializer_list<T>> init)
    : Matrix{ 
        init.size(),
        std::max(
            init,
            [](const auto& a, const auto& b) { //Lambda-Funktion?
                return a.size() < b.size();
            }
        ).size()
      }
    {
    ...
    

    Die Lambda-Funktion vergleicht zwei std::initializer_list<T> nach Länge (Anzahl der Elemente). Siehe Doku zu std::max (Variante 4 wird hier verwendet).

    Der std::max(...)-Ausdruck liefert diejenige, innere std::initializer_list<T> zurück, welche die meisten Elemente hat (die Listen können unterschiedlich lang sein). Mit deren Größe wird die Anzahl der Spalten insitialisiert (Delegating Constructor-Aufruf Matrix{ rows, columns }). Die Matrix soll schliesslich immer rechteckig sein.

    Bei Initialisierung mit geschachtelten Initializer Lists wie z.B.

    Matrix m{
        { 1, 2 },
        { 3, 4, 5 },
        { 6 }
    };
    

    wird hier zunächst eine wegen des Matrix(rows, columns)-Konstruktors Value-Initialisierte (effektiv genullte, mithilfe des Konstruktors von std::vector<T>) 3x3-Matrix erzeugt, und die angegebenen Elemente dann mit dem weiteren Code des Konstruktors in die Matrix geschrieben:

    {
        std::size_t i = 0;
        for (const auto& row : init)
        {
            if (i >= rows())
                break;
            std::size_t j = 0;
            for (const auto& value : row)
            {
                if (j >= columns())
                    break;
                (*this)(i, j) = value;
                ++j;
            }
            ++i;
        } 
    }
    

    Die resultierende Matrix bei meiner Beispiel-Initialisierung oben wäre also:

    1 2 0
    3 4 5
    6 0 0
    

    Dieses Verhalten ist analog zur aggregate initialization, wo es auch so ist, dass wenn zu wenige Elemente in der Liste angegeben werden, die restlichen Elemente ebenfalls Value-Initialisiert werden (principle of least surprise).


  • Mod

    @Caligulaminus sagte in Entwicklung einer einfachen Matrizenklasse:

    @SeppJ, @wob Könntet Ihr bitte kurz anreißen wie Ihr das macht?

    @SeppJ sagte in [Entwicklung einer einfachen Matrizenklasse]

    Umlaute auf rechtes Alt + Vokal legen.

    @wob sagte in [Entwicklung einer einfachen Matrizenklasse]

    Tipp: die ansonsten nutzlose Feststelltaste (caps lock) kann man wunderbar zur Tastaturlayoutumstellung verwenden.

    Wie das in Linux geht, hat wob schon erklärt. Allgemein gibt es dort sehr gute und einfache Möglichkeiten. Unter Windows benutze ich auf meinem persönlichen Rechner ein custom Tastaturlayout. Da gibt's einen Editor von Microsoft für, das man dann als ganz normales Tastenlayout installieren kann. Wenn ich auf Rechnern unterwegs bin, wo ich kein Admin bin, dann nutze ich Autohotkey, weil das auch auf Userebene funktioniert. Das funktioniert zu 99% so gut wie ein richtiges Tastaturlayout, aber im Schnitt einmal im Monat verschluckt es sich mal kurz.



  • @Swordfish
    @Finnegan

    Toll. Danke, die Funktion verstehe ich jetzt, hätte dafür wieder ellenlangen Code geschrieben. Nur an die Syntax muss ich mich noch gewöhnen.



  • @SeppJ
    Danke fürs Feedback. Dann werde ich mich mal nach diesem Editor umschauen...



  • Damals mit Windows habe ich https://www.microsoft.com/en-us/download/details.aspx?id=22339 verwendet. Entweder meint @SeppJ den - oder es gibt inzwischen irgendwas Neueres.


  • Mod

    @wob sagte in Entwicklung einer einfachen Matrizenklasse:

    Damals mit Windows habe ich https://www.microsoft.com/en-us/download/details.aspx?id=22339 verwendet. Entweder meint @SeppJ den - oder es gibt inzwischen irgendwas Neueres.

    Ja, klingt richtig. ist lange her. Das hab' ich noch unter Windows 7 gemacht und seither bloß das neue Layout auf neue Rechner migriert.



  • Hi, ich möchte gerne hier irgendwie weitermachen. Was mir noch fehlt, ist ein Konstruktor, der einen std::vectorannimmt. Hatte gehofft, die ìnitializer_list nimmt einen an, aber das ist wohl nicht so einfach.

    Deshalb habe ich noch folgenden:

    Mat2D_T(const std::vector<std::vector<valType>>& elements)
    		: Mat2D_T{ elements.size(), elements.front().size() }
    	{
    		for (size_t r = 0; r < rows(); ++r)
    		{
    			for (size_t c = 0; c < columns(); ++c)
    			{
    				if (c >= elements[r].size()) break;
    				const auto v = elements[r][c];
    				setElement(r, c, v); //EDIT: geht natürlich auch (*this)(r, c) = v; ;-)
    			}
    		}
    	}
    

    Ich nehme den ersten Vector als Spaltenanzahl. Um unsinnige Eingaben abzufangen, müsste das doch genügen?



  • @zeropage sagte in Entwicklung einer einfachen Matrizenklasse:

    Um unsinnige Eingaben abzufangen, müsste das doch genügen?

    Eine Matrix als vector von vectoren ist an sich schon eine unsinnige Eingabe. ^^



  • Schon deshalb blöd, weil ich eigentlich zB dahin will:

    	std::vector<std::vector<float>> pArr = { {0., 0. }, {15., 0.}, {15., 9. }, {7.5, 13.}, {0., 9. } };
    		std::vector<Mat2D_T<float>> mVec;
    
    
    		for (const auto& pVec : pArr)
    		{
    			Mat2D_T<float> mt{ pVec };
    			mVec.push_back(mt);
    		}
    


  • @zeropage Ich kapiers nicht. Du willst Zeilenvektorenmatrizen aus std::vector<sonstwas> basteln und dann eine Matrix daraus?



  • Vergiss es. Bin betrunken. Punkte sollen eine Matrix sein. Und dann gibt es Matrizen, die ich mit Punkten multiplizieren kann. Habe vorgegriffen. Natürlich erst Matrizen erstellen, dann Punkte, und dann nochmal multiplizieren.

    Also vergiss alles. Erst Matrizen, dann Punkte.





  • Wieder nüchtern.
    std::vectorhat sich erledigt, die initializer_listreicht. Aber noch etwas zu print-Methoden. Ich habe zwar eine Ausgabe,

    template <typename T>
    std::ostream& operator<<(std::ostream& stream, const Mat2D_T<T>& mT)
    {
    	stream << typeid(T).name() << " " << mT.rows() << "x" << mT.columns();
    	stream << " { ";
    	for (std::size_t r = 0; r < mT.rows(); ++r)
    	{
    		if (r > 0) stream << ", ";
    		stream << "{ ";
    		for (std::size_t c = 0; c < mT.columns(); ++c)
    		{
    			if (c > 0) stream << ", ";
    			stream << mT(r, c);
    		}
    		stream << " }";
    	}
    	stream << " }\n";
    	return stream;
    }
    

    möchte aber gerne mal eine formatierte. Da ich mir vorstellen kann, das die nicht in eine Template-Klasse gehören, habe ich sie in ein namespacegepackt.
    Ich zeig das lieber jetzt, bevor es irgendwann auftaucht und dann extra korrigiert werden muss.

    namespace Mat2D
    {
    	void print(const Mat2D_T<int>& mT, const int cwidth = 4, std::ostream& stream = std::cout);
    	void printElement(const Mat2D_T<int>& mT, const size_t row, const size_t column, const int cwidth, std::ostream& stream = std::cout);
    	void print(const Mat2D_T<float>& mT, const int cwidth = 4, std::ostream& stream = std::cout);
    	void printElement(const Mat2D_T<float>& mT, const size_t row, const size_t column, const int cwidth, std::ostream& stream = std::cout);
    	void print(const Mat2D_T<double>& mT, const int cwidth = 4, std::ostream& stream = std::cout);
    	void printElement(const Mat2D_T<double>& mT, const size_t row, const size_t column, const int cwidth, std::ostream& stream = std::cout);
    }
    

    Die verschiedene Ausgabe von int und fließkomma ist nur wegen setprecision().

    void Mat2D::printElement(const Mat2D_T<int>& mT, const size_t row, const size_t column, const int cwidth, std::ostream& stream)
    {
    	stream << "(" << row << "," << column << "):";
    	stream << std::setw(cwidth) << mT.element(row, column);
    }
    
    void Mat2D::printElement(const Mat2D_T<float>& mT, const size_t row, const size_t column, const int cwidth, std::ostream& stream)
    {
    	const int cp = 4;
    	const int cpwidth = cwidth + cp;
    	stream << "(" << row << "," << column << "): ";
    	stream << std::setw(cpwidth) << std::fixed << std::setprecision(cp) << mT.element(row, column);
    }
    

Anmelden zum Antworten