#ifndef _mitrax_mitrax_matrix_hpp_INCLUDED_
#define _mitrax_mitrax_matrix_hpp_INCLUDED_
/// \file matrix.hpp
///
/// \brief Matrixklasse
///
/// In dieser Datei steht die Definition der <code>matrix</code>-Klasse, welche die Kernkomponente
/// von mitrax bildet. Eine Überladung der <code>swap()</code>-Funktion für
/// <code>matrix</code>-Objekte ist ebenfalls hier definiert. Weiterhin existiert in dieser Datei
/// die von mitrax genutzter Trait-Klasse <code>identity</code>, welche für einen gegeben Datentyp
/// die neutralen Elemente bezüglich der Addition und Multiplikation liefert.
#include <vector>
#include "proxy.hpp"
#include "dimension.hpp"
namespace mitrax{
namespace detail{
/// \brief Positionsberechnung eines Elements in einem 1D-Feld anhand der 2D-Position
///
/// Diese Klasse sollte nicht direkt vom Nutzer verwendet werden.
///
/// siehe matrix::position_adapter
template < typename SizeType >
class position_adapter{
public:
/// \brief Nutzt <code>columns</code> als Spaltenanzahl
position_adapter(SizeType columns):columns_(columns){}
/// \brief Nutzt <code>dim.columns()</code> als Spaltenanzahl
position_adapter(dimension< SizeType > const& dim):columns_(dim.columns()){}
/// \brief Gibt <code>row * Spaltenanzahl + column</code> zurück
SizeType const operator()(SizeType const& row, SizeType const& column){
return row * columns_ + column;
}
private:
/// \brief Gespeicherte Spaltenanzahl
SizeType columns_;
};
}
/// \brief Matrizen mit Elementzugriff und Größenanpassung
///
/// Die <code>matrix</code>-Templateklasse ist das Herzstück von mitrax. Sie besitzt im
/// Wesentlichen eine Dimension (Zeilen mal Spalten) und die entsprechend angeordneten Element.
///
/// Die Erstellung einer Matrix kann mittels Standardkonstruktor mit einer Dimension von Null
/// erfolgen. Weiterhin existieren Konstruktoren für die Erstellung mit einer angegebenen Dimension
/// und einem Füllelement (Defaultwert T()) oder einem Argument vom Typ Referenz auf Container,
/// welches die korrekte Anzahl von Elementen enthalten muss. Das Argument wird nach dem Aufruf
/// keine Elemente mehr enthalten!
///
/// Compilergenerierte Methoden:
/// - Kopierkonstruktor
/// - T muss kopierkonstruierbar sein
/// - Container muss kopierkonstruierbar sein
/// - size_type muss kopierkonstruierbar sein
/// - Kopierzuweisung
/// - T muss kopierzuweisbar sein
/// - Container muss kopierzuweisbar sein
/// - size_type muss kopierzuweisbar sein
/// - Desturktor
/// - T muss destruierbar sein
/// - Container muss destruierbar sein
/// - size_type muss destruierbar sein
///
/// \section proxy_interface Proxy-Interface
///
/// Der wahlfreie Zugriff auf einzelne Elemente erfolgt, unter Zuhilfenahme von Proxyklassen, durch
/// zweimalige Anwendung des Indexoperators (<code>operator[]()</code>). Der Zugriff kann auch über
/// Methoden <code>row()</code> und <code>column()</code> erfolgen. Entsprechend ist auch der
/// Zugriff auf einzelne Zeilen und Spalten möglich. Der Zugriff über Zeilenproxys ist tendenziell
/// schneller.
///
/// Die Proxyklassen können kopiert und aneinander zugewiesen werden, so dass unter bestimmten
/// Umständen, die Vertauschung zweier Proxys stellvertretend für die Vertauschung zweier
/// kompletter Zeilen oder Spalten genutzt werden kann. Zu diesem Zweck kann beispielsweise ein
/// Feld mit allen Zeilenproxys einer Matrix erstellt werden. Der Zugriff auf die Elemente ist
/// dann, durch zweimalige Anwendung des Indexoperators, auch über dieses Feld möglich.
///
/// Um Zuweisungen an ein temporäres Objekt zu verhindern, sind die von matrix zurückgegebenen
/// Proxyobjekte immer konstant. Die Daten, auf welche die Proxys verweisen, haben mit dieser
/// Konstantheit natürlich nichts zu tun.
///
/// \section iterator_interface Iterator-Interface
///
/// Für die elementweise Bearbeitung der Elemente wird ein Iterator-Interface angeboten. Neben den
/// Methoden <code>begin()</code> und <code>end()</code>, welche in einer konstanten und einer
/// nicht-konstante Version existieren, gibt es auch noch die Methoden <code>cbegin()</code> und
/// <code>cend()</code>, welche immer <code>const_iterator</code>-Objekte zurückgeben.
///
/// Auch die Proxyklassen verfügen über ein Iteratorinterface dieser Art. Wobei zu beachten ist,
/// das die Methoden <code>begin()</code> und <code>end()</code> für die Proxys für nicht-konstante
/// Matrizen (<code>row_proxy</code> und <code>column_proxy</code>) immer auch Iteratoren für
/// nicht-konstante Matrizen zurückgeben, unabhängig davon, ob das Proxyobjekt selbst konstant ist.
template < typename T, typename Container = std::vector< T > >
class matrix{
public:
// types:
/// \brief Datentyp der Elemente
typedef T value_type;
/// \brief Referenz auf ein Element
typedef T& reference;
/// \brief Referenz auf ein konstantes Element
typedef T const& const_reference;
/// \brief Typ des Containers
typedef Container container_type;
/// \brief Typ für eine Anzahl Zeilen oder Spalten
typedef typename Container::size_type size_type;
/// \brief Typ der Dimension
typedef mitrax::dimension< size_type > dimension_type;
/// \brief Iteratortyp der auf den <code>value_type</code> verweist
typedef typename Container::iterator iterator;
/// \brief Iteratortyp der auf den konstanten <code>value_type</code> verweist
typedef typename Container::const_iterator const_iterator;
/// \brief Vorzeichenbehaftete Ganzzahl
typedef typename Container::difference_type difference_type;
/// \brief Proxy für eine Zeile
typedef mitrax::row_proxy< matrix > row_proxy;
/// \brief Proxy für eine konstante Zeile
typedef mitrax::row_const_proxy< matrix > row_const_proxy;
/// \brief Proxy für eine Spalte
typedef mitrax::column_proxy< matrix > column_proxy;
/// \brief Proxy für eine konstante Spalte
typedef mitrax::column_const_proxy< matrix > column_const_proxy;
/// \brief Positionsberechnung eines Elements in einem 1D-Feld anhand der 2D-Position
///
/// Es handelt sich bei diesem Typ um einen Funktor, welcher anhand einer Zeilenlänge und
/// zweier 2D-Koordinaten die Speicherposition eines Elements, in einem eindimensionalen
/// Container berechnet. Er bekommt bei seiner Initialisierung die Anzahl der Spalten
/// (Zeilenlänge) und kann anschließend mit den zwei Koordinaten (x, y) aufgerufen werden.
///
/// Zur Initialisierung kann neben einer Zeilenlänge auch ein <code>dimension</code>-Objekt
/// verwendet werden.
typedef detail::position_adapter< size_type > position_adapter;
// construct/copy/destroy:
/// \brief Erstellt eine Matrix mit Null Zeilen und Spalten
matrix();
/// \brief Erstellt eine Matrix der Dimension <code>size</code> und füllt alle Elemente mit
/// <code>fill</code>
matrix(dimension_type const& size, const_reference fill = value_type());
/// \brief Erstellt eine Matrix mit <code>rows</code> Zeilen und <code>columns</code> Spalten
/// und füllt alle Elemente mit <code>fill</code>
matrix(size_type const& rows, size_type const& columns, const_reference fill = value_type());
/// \brief Erstellt eine Matrix der Dimension <code>size</code> und verwendet <code>data</code>
/// als Elemente
///
/// In <code>data</code> müssen <code>size.rows() * size.columns()</code> viele Einträge
/// enthalten sein. Der Konstruktor tauscht <code>data</code> gegen einen leeren Container aus.
/// Die Berechnung der zweidimensionalen Position erfolgt mit
/// <code>row * columns + column</code>. Dies gewährleistet eine sehr effiziente
/// Initialisierung.
///
/// \throw error::data_size falls <code>data.size() != size.rows() * size.columns()</code>
matrix(dimension_type const& size, container_type& data);
/// \brief Erstellt eine Matrix mit rows Zeilen und columns Spalten und verwendet data als
/// Elemente
///
/// \throw error::data_size falls <code>data.size() != size.rows() * size.columns()</code>
matrix(size_type const& rows, size_type const& columns, container_type& data);
// iterators:
/// \brief Gibt einen Schreib-/Leseiterator auf das erste Element der Matrix zurück
///
/// Siehe \ref iterator_interface "Iterator-Interface"
iterator const begin();
/// \brief Gibt einen Leseiterator auf das erste Element der Matrix zurück
///
/// Siehe \ref iterator_interface "Iterator-Interface"
const_iterator const begin()const;
/// \brief Gibt einen Schreib-/Leseiterator hinter das letzte Element der Matrix zurück
///
/// Siehe \ref iterator_interface "Iterator-Interface"
iterator const end();
/// \brief Gibt einen Leseiterator hinter das letzte Element der Matrix zurück
///
/// Siehe \ref iterator_interface "Iterator-Interface"
const_iterator const end()const;
/// \brief Gibt einen Leseiterator auf das erste Element der Matrix zurück
///
/// Siehe \ref iterator_interface "Iterator-Interface"
const_iterator const cbegin()const;
/// \brief Gibt einen Leseiterator hinter das letzte Element der Matrix zurück
///
/// Siehe \ref iterator_interface "Iterator-Interface"
const_iterator const cend()const;
// capacity:
/// \brief Gibt die Dimension der Matrix zurück
///
/// Die Dimension einer Matrix beinhaltet die Anzahl der Zeilen und Spalten.
///
/// Siehe auch:
/// - \link rows \endlink
/// - \link columns \endlink
dimension_type const dimension()const;
/// \brief Gibt die Anzahl der Zeilen zurück
///
/// Siehe auch:
/// - \link rows \endlink
/// - \link dimension \endlink
size_type const rows()const;
/// \brief Gibt die Anzahl der Spalten zurück
///
/// Siehe auch:
/// - \link columns \endlink
/// - \link dimension \endlink
size_type const columns()const;
/// \brief Setzt die Dimension der Matrix neu
///
/// Diese Methode setzt die Dimension der Matrix neu. Ist <code>preserve</code> gesetzt,
/// werden Elemente die bei der alten und neuen Dimension existieren, erhalten. Alle übrigen
/// Elemente werden auf <code>fill</code> gesetzt. Ist <code>preserve</code> nicht gesetzt,
/// werden alle Elemente auf <code>fill</code> gesetzt.
void resize(
dimension_type const& size,
bool preserve = true,
const_reference fill = value_type()
);
/// \brief Setzt die Dimension der Matrix neu
///
/// Siehe \link resize \endlink
void resize(
size_type const& rows,
size_type const& columns,
bool preserve = true,
const_reference fill = value_type()
);
/// \brief Reinitialisiert die Matrix mit gegebener Dimension und gegebenen Daten
///
/// In <code>data</code> müssen <code>size.rows() * size.columns()</code> viele Einträge
/// enthalten sein. Der Konstruktor tauscht <code>data</code> gegen den aktuellen Container
/// mit den Elementdaten aus. Die Berechnung der zweidimensionalen Position erfolgt mit
/// <code>row * columns + column</code>. Dies gewährleistet eine sehr effiziente
/// Initialisierung.
///
/// \throw error::data_size falls <code>data.size() != size.rows() * size.columns()</code>
void reinit(dimension_type const& size, container_type& data);
/// \brief Reinitiallisiert die Matrix mit gegebener Dimension und gegebenen Daten
///
/// \throw error::data_size falls <code>data.size() != size.rows() * size.columns()</code>
void reinit(size_type const& rows, size_type const& columns, container_type& data);
// element access:
/// \brief Gibt einen Schreib-/Leseproxy für die Zeile <code>row</code> zurück
row_proxy const operator[](size_type const& row);
/// \brief Gibt einen Leseproxy für die Zeile <code>row</code> zurück
row_const_proxy const operator[](size_type const& row)const;
/// \brief Gibt einen Schreib-/Leseproxy für die Zeile <code>number</code> zurück
row_proxy const row(size_type const& number);
/// \brief Gibt einen Leseproxy für die Zeile <code>number</code> zurück
row_const_proxy const row(size_type const& number)const;
/// \brief Gibt einen Schreib-/Leseproxy für die Spalte <code>number</code> zurück
column_proxy const column(size_type const& number);
/// \brief Gibt einen Leseproxy für die Spalte <code>number</code> zurück
column_const_proxy const column(size_type const& number)const;
// modifiers:
/// \brief Löscht eine oder mehrere Zeile
///
/// Durch das Löschen von <code>count</code> vielen Zeilen verringert sich die Anzahl
/// der Zeilen um <code>count</code>.
void erase_row(size_type const& number, size_type const& count = size_type(1));
/// \brief Löscht eine oder mehrere Spalte
///
/// Durch das Löschen von <code>count</code> vielen Spalten verringert sich die Anzahl
/// der Spalten um <code>count</code>.
void erase_column(size_type const& number, size_type const& count = size_type(1));
/// \brief Tauscht Dimension und Werte der aktuellen Matrix mit jenen aus <code>m</code>
///
/// Die Vertauschung erfolgt durch den Aufruf von <code>swap()</code> für die beiden
/// Containerobjekte und die Dimensionobjekte. Bei ersteren existiert oft eine überladene
/// oder spezialisierte Version, die sehr Effizient ist. Die argumentabhängige Namenssuche
/// für eine solche <code>swap()</code>-Funktion wird unterstützt.
void swap(matrix& m);
private:
/// \brief Speichert die Anzahl der Zeilen und Spalten
dimension_type dimension_;
/// \brief Speichert die Elemente der Matrix
///
/// Der Zugriff im 2D-Raum erfolgt mittels <code>row * columns + column</code>
container_type data_;
/// \brief Helfer um Codeverdopplung zu vermeiden
///
/// Prüft ob die Anzahl der Elemente in <code>data</code> zu <code>size</code> passt. Ist dies
/// nicht der Fall, wird eine Ausnahme vom Typ <code>error::data_size</code> geworfen.
/// Andernfalls wird <code>swap(data_, data)</code> aufgerufen. Die Dimension wird \em nicht
/// zugewiesen.
///
/// \throw error::data_size
void raw_data_swap(dimension_type const& size, container_type& data);
};
/// \brief Vertauscht zwei Matrizen
///
/// Ruft <code>lhs.swap(rhs)</code> auf.
template < typename T, typename Container >
void swap(matrix< T, Container >& lhs, matrix< T, Container >& rhs);
/// \brief Neutrale Elemente für einen numerischen Datentyp <code>T</code>
///
/// Dies ist eine Trait-Klasse. Sie stellt durch die statischen Funktionen <code>additive()</code>
/// und <code>multiplicative()</code> die neutralen Elemente bezüglich der Addition und der
/// Multiplikation mit einem Objekt eines numerischen Datentyps <code>T</code> bereit.
///
/// In der Standardimplementierung wird angenommen, das ein standardkonstruiertes
/// <code>T</code>-Objekt bezüglich der Addition neutral ist. Addiert man zu diesem den
/// <code>int</code>-Literal <code>1</code> sollte sich das neutrale Element bezüglich der
/// Multiplikation ergeben. Trifft dies auf einen Datentyp den Sie verwenden möchten nicht zu, so
/// können Sie eine entsprechende Spezialisierung des Templates <code>identity</code> vornehmen.
///
/// Standardmäßig gibt es eine Spezialisierung für alle Klassentemplates, die eine Parametertyp
/// <code>U</code> übernehmen. Für derartige Klassen wird wiederum angenommen, das ein
/// standardkonstruiertes Objekt bezüglich der Addition neutral ist. Für die Multiplikation wird
/// diesmal angenommen, dass sich das neutrale Element durch die Addition des standardkonstruierten
/// Objekts mit dem multiplikativen Neutrum von <code>U<code> ergibt.
///
/// Klassen, die mehrere neutrale Elemente pro Operation besitzen, wie etwa die
/// <code>matrix</code>-Klasse, werden derzeit nicht unterstützt.
template < typename T >
struct identity{
/// \brief Additives Neutrum
static T const& additive() { static T const t=T(); return t; };
/// \brief Multiplikatives Neutrum
static T const& multiplicative(){ static T const t(T()+1); return t; };
};
/// \brief Neutrale Elemente für einen numerischen Datentyp <code>Numeric< T ></code>
///
/// Speziallisierung von <code>identity< T ></code>.
template < typename T, template < typename U > class Numeric >
struct identity< Numeric< T > >{
/// \brief Additives Neutrum
static Numeric< T > const& additive(){
static Numeric< T > const t=T(); return t;
};
/// \brief Multiplikatives Neutrum
static Numeric< T > const& multiplicative(){
static Numeric< T > const t(Numeric< T >() + identity< T >::multiplicative());
return t;
};
};
//+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
// Definitionen
//+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
template < typename T, typename Container >
inline
matrix< T, Container >::matrix()
{}
template < typename T, typename Container >
inline
matrix< T, Container >::matrix(
matrix< T, Container >::dimension_type const& size,
const_reference fill
):
dimension_(size),
data_(elements(dimension_), fill)
{}
template < typename T, typename Container >
inline
matrix< T, Container >::matrix(
size_type const& rows,
size_type const& columns,
const_reference fill
):
dimension_(dimension_type(rows, columns)),
data_(elements(dimension_), fill)
{}
template < typename T, typename Container >
inline
matrix< T, Container >::matrix(
dimension_type const& size,
container_type& data
):
dimension_(size)
{
raw_data_swap(dimension_, data);
}
template < typename T, typename Container >
inline
matrix< T, Container >::matrix(
size_type const& rows,
size_type const& columns,
container_type& data
):
dimension_(dimension_type(rows, columns))
{
raw_data_swap(dimension_, data);
}
template < typename T, typename Container >
inline
typename matrix< T, Container >::dimension_type const
matrix< T, Container >::dimension()const{
return dimension_;
}
template < typename T, typename Container >
inline
typename matrix< T, Container >::size_type const
matrix< T, Container >::rows()const{
return dimension_.rows();
}
template < typename T, typename Container >
inline
typename matrix< T, Container >::size_type const
matrix< T, Container >::columns()const{
return dimension_.columns();
}
template < typename T, typename Container >
inline
typename matrix< T, Container >::iterator const
matrix< T, Container >::begin(){
return data_.begin();
}
template < typename T, typename Container >
inline
typename matrix< T, Container >::const_iterator const
matrix< T, Container >::begin()const{
return data_.begin();
}
template < typename T, typename Container >
inline
typename matrix< T, Container >::iterator const
matrix< T, Container >::end(){
return data_.end();
}
template < typename T, typename Container >
inline
typename matrix< T, Container >::const_iterator const
matrix< T, Container >::end()const{
return data_.end();
}
template < typename T, typename Container >
inline
typename matrix< T, Container >::const_iterator const
matrix< T, Container >::cbegin()const{
return begin();
}
template < typename T, typename Container >
inline
typename matrix< T, Container >::const_iterator const
matrix< T, Container >::cend()const{
return end();
}
template < typename T, typename Container >
inline
typename matrix< T, Container >::row_proxy const
matrix< T, Container >::operator[](
size_type const& number
){
return row(number);
}
template < typename T, typename Container >
inline
typename matrix< T, Container >::row_const_proxy const
matrix< T, Container >::operator[](
size_type const& number
)const{
return row(number);
}
template < typename T, typename Container >
inline
typename matrix< T, Container >::row_proxy const
matrix< T, Container >::row(
size_type const& number
){
return row_proxy(*this, number);
}
template < typename T, typename Container >
inline
typename matrix< T, Container >::row_const_proxy const
matrix< T, Container >::row(
size_type const& number
)const{
return row_const_proxy(*this, number);
}
template < typename T, typename Container >
inline
typename matrix< T, Container >::column_proxy const
matrix< T, Container >::column(
size_type const& number
){
return column_proxy(*this, number);
}
template < typename T, typename Container >
inline
typename matrix< T, Container >::column_const_proxy const
matrix< T, Container >::column(
size_type const& number
)const{
return column_const_proxy(*this, number);
}
template < typename T, typename Container >
inline
void matrix< T, Container >::raw_data_swap(dimension_type const& size, container_type& data){
if(elements(size) != data.size()){
throw error::data_size("mitrax::matrix<>::raw_data_swap", size, data);
}
using std::swap;
swap(data_, data);
}
template < typename T, typename Container >
inline
void
matrix< T, Container >::resize(
matrix< T, Container >::dimension_type const& size,
bool preserve,
const_reference fill
){
container_type init(elements(size), fill); // Neuer Container
if(preserve){
// Länge der pro Zeile zu kopierenden Elemente ermitteln
size_type min_columns = std::min(size.columns(), dimension_.columns());
// Differenz von kurzen und langen Zeilen
size_type dif_columns = std::max(size.columns(), dimension_.columns()) - min_columns;
// Iteratoren beider Container
iterator data_iter = data_.begin();
iterator init_iter = init.begin();
// Iterator mit den längeren Zeilen ermitteln
iterator& bigger = size.columns() < dimension_.columns() ? data_iter : init_iter;
// Zeilenweise durchlaufen
for(size_type i = size_type(); i < size.rows() && i < dimension_.rows(); ++i){
// Iteratoren hinter das Ende des zu kopierenden Bereiches setzen
iterator begin_data_iter = data_iter;
iterator begin_init_iter = init_iter;
data_iter += min_columns;
init_iter += min_columns;
// Kopie durchführen
std::copy(begin_data_iter, data_iter, begin_init_iter);
// Iterator mit den längeren Zeilen auf den Anfang der nächsten Zeile setzen
bigger += dif_columns;
}
}
using std::swap;
swap(data_, init);
dimension_ = size;
}
template < typename T, typename Container >
inline
void
matrix< T, Container >::resize(
size_type const& rows,
size_type const& columns,
bool preserve,
const_reference fill
){
resize(dimension_type(rows, columns), preserve, fill);
}
template < typename T, typename Container >
inline
void
matrix< T, Container >::reinit(
dimension_type const& size,
container_type& data
){
raw_data_swap(size, data);
dimension_ = size;
}
template < typename T, typename Container >
inline
void
matrix< T, Container >::reinit(
size_type const& rows,
size_type const& columns,
container_type& data
){
reinit(dimension_type(rows, columns), data);
}
template < typename T, typename Container >
inline
void
matrix< T, Container >::erase_row(
size_type const& number,
size_type const& count
){
if(rows() < number + count){
throw error::row_access("mitrax::matrix<>::erase_row", dimension_, rows());
}
// Zeilen löschen
iterator iter = data_.begin();
iter += number * columns();
data_.erase(iter, iter + count * columns());
// Dimension anpassen
dimension_.resize(rows() - count, columns());
}
template < typename T, typename Container >
inline
void
matrix< T, Container >::erase_column(
size_type const& number,
size_type const& count
){
if(columns() < number + count){
throw error::column_access("mitrax::matrix<>::erase_column", dimension_, columns());
}
// Zeilen löschen
for(size_type i = size_type(); i < rows(); ++i){
iterator iter = data_.begin();
iter += number + (columns() - count) * i;
data_.erase(iter, iter + count);
}
// Dimension anpassen
dimension_.resize(rows(), columns() - count);
}
template < typename T, typename Container >
inline
void
matrix< T, Container >::swap(
matrix< T, Container >& m
){
using std::swap;
swap(data_, m.data_);
swap(dimension_, m.dimension_);
}
template < typename T, typename Container >
inline
void
swap(
matrix< T, Container >& lhs,
matrix< T, Container >& rhs
){
lhs.swap(rhs);
}
}
#endif