Matrixoperationen (überarbeitete Fassung 21.1.2011)

Matrizen werden in der Programmierung als zweidimensionale Arrays dargestellt, dies ist in VBA auch nicht anders. Ein Problem mit den Arrays von VBA ist, dass man die Indexuntergrenzen frei wählen kann. Stellt man sich als Programmierer darauf ein, dass die zu verarbeitenden Arrays beliebige Indexuntergrenzen haben sollen, so werden die Matrixoperationen zum Teil sehr kompliziert.

 

Wir wollen daher vereinbaren, dass alle Indizes bei 1 beginnen. In Verbindung mit Matrizen ist dies auch in der Mathematik so üblich. Diese Vereinbarung soll sowohl für die Argumente gelten, die in die Matrixfunktionen eingehen, als auch für die Resultate dieser Funktionen.

 

Ein weiteres Problem in Excel-VBA ist, dass exakter Umgang mit Datentypen im Prinzip möglich ist, aber nicht in Verbindung mit sogenannten benutzerdefinierten Funktionen, also solchen, die von Tabellenblättern aus genutzt werden können. Wir betrachten deshalb jeweils zwei Versionen, eine exakte und eine benutzerdefinierte Funktion. Wir beginnen mit einer sehr einfachen Funktion:

 

 

Multiplikation einer Matrix mit einem Skalar

 

Public Function MSk(ByVal s As Double, ByRef a() As Double) As Double()

    Dim m() As Double

    ReDim m(1 To UBound(a, 1), 1 To UBound(a, 2))

    Dim i As Integer, j As Integer

    For i = 1 To UBound(m, 1)

        For j = 1 To UBound(m, 2)

            m(i, j) = s * a(i, j)

        Next j

    Next i

    MSk = m

End Function

 

Beachten Sie den Gebrauch der Arrayvariablen m. Hätte man für die multiplizierten Elemente gleich das Parameter-Array a genommen, so wäre die Funktion kürzer ausgefallen. Der Preis für diese Verkürzung wäre aber ein unerwünschter Nebeneffekt gewesen: Da Array-Parameter als Referenz (ByRef) übergeben werden, hätte sich das ursprüngliche Array bei der aufrufenden Stelle mit der Multiplikation ebenfalls verändert.

 

Als benutzerdefinierte Funktion ist diese im Hinblick auf die Datentypen exakte Version nicht verwendbar. Nachfolgend eine Version, die als benutzerdefinierte Funktion durchgeht. Wie nicht anders zu erwarten, macht sie ausgiebig vom Daten(un)typ Variant Gebrauch:

 

Public Function MSkC(ByVal s As Double, ByVal r As Range) As Variant

    Dim a As Variant

    a = r.Value

    Dim i As Integer, j As Integer

    For i = 1 To UBound(a, 1)

        For j = 1 To UBound(a, 2)

            a(i, j) = a(i, j) * s

        Next j

    Next i

    MSkC = a

End Function

 

Dies taugt nun zwar als benutzerdefinierte Funktion, aber innerhalb von Programmen ist es kaum zu verwenden. Soll man nun alle beiden Versionen bereit halten, oder gibt es eine dritte Möglichkeit?

 

Ein in den meisten Fällen tragbarer Kompromiss ist, die exakte Version als Grundversion zu nehmen und für die benutzerdefinierte Funktion eine Hülle (Wrapper) herum zu legen. In dieser Hülle muss man allerdings Ranges in Arrays verwandeln und (implizit) Arrays in Variant-Daten. Die Umwandlung der Ranges lagert man am besten in eine eigene Funktion RangeToDblArray aus, die man dann für alle derartigen Hüllenfunktionen verwenden kann:

 

Public Function MSkCW(ByVal s As Double, ByVal rng As Range) As Variant

    MSkCW = MSk(s, RangeToDblArray(rng))

End Function

 

Public Function RangeToDblArray(ByVal r As Range) As Double()

    Dim d() As Double

    ReDim d(1 To r.Cells.Rows.Count, 1 To r.Cells.Columns.Count)

    Dim i As Integer, j As Integer

    For i = 1 To UBound(d, 1)

        For j = 1 To UBound(d, 2)

            d(i, j) = CDbl(r.Cells(i, j).Value)

        Next j

    Next i

    RangeToDblArray = d

End Function

 

Einen kleinen Wermutstropfen gibt es allerdings: die Effizienz der Hüllenfunktion ist schlechter als die der „reinrassigen“ benutzerdefinierten Funktion, denn für die Umwandlung des Range in ein Array muss eine Doppelschleife durchlaufen werden (Eine pauschale Zuweisung des Range an eine Arrayvariable führt leider nicht zum gewünschten Erfolg). Bei nicht zu großen Matrizen sollte dies jedoch kein Problem darstellen.

 

Nun noch eine etwas aufwendigere Funktion, die Multiplikation zweier Matrizen:

 

 

Multiplikation zweier Matrizen

 

Wir beginnen wieder mit der exakten Grundversion und betrachten die nicht exakte benutzerdefinierte Version später.

 

Bei der Matrixmultiplikation ergibt sich ein Problem, das sich bei anderen Matrixoperationen in ähnlicher Form stellt. Die Operation ist an bestimmte Voraussetzungen geknüpft. Bei der Multiplikation A x B ist es die Bedingung, dass A und B verkettet sein müssen, dass also die Spaltenzahl von A gleich der Zeilenzahl von B sein muss.

 

Wie kann man nun bei Verletzung der Bedingungen der aufrufenden Stelle mitteilen, dass ein Verstoß gegen die Voraussetzungen vorliegt? Eine gängige Methode ist, dass die Funktion in einem solchen Fall Werte liefert, die normalerweise ausgeschlossen sind. In der folgenden Lösung wird der Fehler über die Indexgrenzen des gelieferten Arrays signalisiert: sind die beiden Matrizen nicht verkettbar, so wird ein Array geliefert, bei dem sowohl die Indexuntergrenze als auch die Indexobergrenze -1 ist.

 

Public Function MProd(ByRef a() As Double, ByRef b() As Double) As Double()

    Dim p() As Double

    If UBound(a, 2) <> UBound(b, 1) Then       ‘Prüfung, ob Matrizen verkettet sind

        ReDim p(-1 To -1)

        MProd = p

        Exit Function

    End If

    ReDim p(1 To UBound(a, 1), 1 To UBound(b, 2))

    Dim i As Integer, j As Integer, k As Integer

    For i = 1 To UBound(a, 1)

        For j = 1 To UBound(b, 2)

            p(i, j) = 0

            For k = 1 To UBound(b, 1)

                p(i, j) = p(i, j) + a(i, k) * b(k, j)

            Next k

        Next j

    Next i

    MProd = p

End Function

 

Auch bei der Multiplikation ist es möglich, eine benutzerdefinierte Version mit Hilfe einer kleinen Hüllenfunktion herzustellen. Wie beim ersten Beispiel bedienen wir uns dabei der Hilfsfunktion RangeToDblArray (s. oben).

 

Public Function MProdCW(ByVal a As Range, ByVal b As Range) As Variant

    MProdCW = MProd(RangeToDblArray(a), RangeToDblArray(b))

End Function