```(This isn't a library I plan on maintaining, but ANN felt like the
right prefix for offering up code for mass enjoyment.)

At work yesterday I needed to do some algebra on some matrix math to
reverse-derive a value. (To be specific, I needed to figure out how
to draw the three euler rotation values out of a ZXY rotation
matrix.) After spending 2 minutes writing out equations and fearing I
was making a mistake, I turned to Excel and some stupid text
manipulation to produce my formulae. After 10 minutes of messing
around, I got the answer wrong anyhow.

I decided to write a little Ruby library to do the task for me. I
wrote a matrix class which knows how to do matrix math on strings,
producing formulae from them. After the initial pass produced a lot
of values like "0*sinX" and "0 + 0 + 1*(sinX)" I made it a bit
smarter, so that 0s and 1s properly simplify the equations during
calculation.

It doesn't produce perfectly-reduced equations by any means, but it
did the trick. In the end, I got my result :)

Sample output follows, followed by the code itself. Enjoy!

M1:
---------------------------------------------------------------------
0 | 1 | 2
3 | 4 | 5

M2:
---------------------------------------------------------------------
x |  y |  z
3q | 4r | 5s

M1 scaled by 2:
---------------------------------------------------------------------
0 | 2 |  4
6 | 8 | 10

M2 scaled by 2:
---------------------------------------------------------------------
x * 2 |  y * 2 |  z * 2
3q * 2 | 4r * 2 | 5s * 2

M1 + M2:
---------------------------------------------------------------------
x   |  1 + y |  2 + z
3 + 3q | 4 + 4r | 5 + 5s

X:
------------------------------------------------------------------------
-----
1 |   0   |   0
0 |  cosX | sinX
0 | -sinX | cosX

Y:
------------------------------------------------------------------------
-----
cosY | 0 | -sinY
0  | 1 |   0
sinY | 0 |  cosY

Z:
------------------------------------------------------------------------
-----
cosZ | sinZ | 0
-sinZ | cosZ | 0
0   |   0  | 1

X - Y:
------------------------------------------------------------------------
-----
1 - cosY |     0    |     sinY
0    | cosX - 1 |     sinX
-sinY  |   -sinX  | cosX - cosY

X * Y * Z:
------------------------------------------------------------------------
-----
cosY * cosZ               |               cosY *
sinZ               |    -sinY
((sinX * sinY) * cosZ) + (cosX * -sinZ) |  ((sinX * sinY) * sinZ) +
(cosX * cosZ) | sinX * cosY
((cosX * sinY) * cosZ) + (-sinX * -sinZ) | ((cosX * sinY) * sinZ) + (-
sinX * cosZ) | cosX * cosY

Z * X * Y:
------------------------------------------------------------------------
-----
(cosZ * cosY) + ((sinZ * sinX) * sinY) | sinZ * cosX |  (cosZ * -
sinY) + ((sinZ * sinX) * cosY)
(-sinZ * cosY) + ((cosZ * sinX) * sinY) | cosZ * cosX | (-sinZ * -
sinY) + ((cosZ * sinX) * cosY)
cosX * sinY               |    -sinX
|                cosX * cosY

stringmatrix.rb
------------------------------------------------------------------------
-----
# A two-dimensional matrix of arbitrary size, with common matrix
# math methods. When entries in the matrix are strings, the math
# methods produce strings representing the equation.
#
# Strings and numbers mix nicely so that multiplying by 0 wipes
# out the string properly, and multiplying by 1 or adding 0
# leaves the original unchanged.
#
# For example:
#  x = StringMatrix.parse <<END
#  1 0 0
#  0 cosX sinX
#  0 -sinX cosX
#  END
#
#  y = StringMatrix.parse <<END
#  cosY | 0 | -sinY
#  0    | 1 | 0
#  sinY | 0 | cosY
#  END
#
#  puts x, ' ', y, ' ', x - y, ' ', x * y
#
#  #=> 1 |   0   |   0
#  #=> 0 |  cosX | sinX
#  #=> 0 | -sinX | cosX
#  #=>
#  #=> cosY | 0 | -sinY
#  #=>   0  | 1 |   0
#  #=> sinY | 0 |  cosY
#  #=>
#  #=> 1 - cosY |     0    |     sinY
#  #=>     0    | cosX - 1 |     sinX
#  #=>   -sinY  |   -sinX  | cosX - cosY
#  #=>
#  #=>     cosY    |   0   |    -sinY
#  #=> sinX * sinY |  cosX | sinX * cosY
#  #=> cosX * sinY | -sinX | cosX * cosY
class StringMatrix
if v1==0
v2
elsif v2==0
v1
elsif Numeric===v1 && Numeric===v2
v1+v2
else
self.operator_join( v1, v2, '+' )
end
end

# Subtract two values intelligently
def self.subtract( v1, v2 )
if v2==0
v1
elsif v1==0
if v2 =~ /^-\((.+)\)\$/ || v2 =~ /^-(.+)\$/
\$1
elsif v2 =~ /\s/
"-(#{v2})"
else
"-#{v2}"
end
elsif Numeric===v1 && Numeric===v2
v1-v2
else
self.operator_join( v1, v2, '-' )
end
end

# Multiply two values intelligently
def self.multiply( v1, v2 )
if v1==1
v2
elsif v2==1
v1
elsif v1==0 || v2==0
0
elsif Numeric===v1 && Numeric===v2
v1*v2
else
self.operator_join( v1, v2, '*' )
end
end

# Join two values semi-intelligently
def self.operator_join( v1, v2, op_str )
out = ( Numeric === v1 || v1 =~ /^\S+\$/ ) ? "#{v1}" : "(#{v1})"
out << " #{op_str} "
out << ( ( Numeric === v2 || v2 =~ /^\S+\$/ ) ? "#{v2}" : "(#
{v2})" )
end

# Creates a new matrix of the specified _width_ and _height_,
optionally
# specifying a _default_value_ to fill each cell.
def initialize( width, height, default_value='' )
@width = width
@height = height
@values = Array.new( width ){ Array.new(height)
{ default_value } }
end

# Reads the value from column _x_, row _y_.
#
# StringMatrices are 1-based, not zero-based.
# (The first item in the matrix is 1,1 and the last is
_width_,_height_)
def []( x, y )
if !y
@values[ x-1 ].dup
elsif !x
a = []
1.upto(@width){ |x|
a << @values[ x-1 ][ y-1 ]
}
a
else
@values[ x-1 ][ y-1 ]
end
end

# Sets the value in column _x_, row _y_ to _val_.
#
# StringMatrices are 1-based, not zero-based.
# (The first item in the matrix is 1,1 and the last is
_width_,_height_)
def []=( x, y, val )
@values[ x-1 ][ y-1 ] = val
end

# Adds the supplied _right_matrix_ from the current matrix
# and returns the result. (The original matrix is not modified.)
def +( right_matrix )
raise "Size mismatch" if width != right_matrix.width ||
height != right_matrix.height
out = self.class.new( width, height )
1.upto( @height ){ |y|
1.upto( @width ){ |x|
out[ x, y ] = self.class.add( self[ x, y ],
right_matrix[ x, y ] )
}
}
out
end

# Subtracts the supplied _right_matrix_ from the current matrix
# and returns the result. (The original matrix is not modified.)
def -( right_matrix )
raise "Size mismatch" if width != right_matrix.width ||
height != right_matrix.height
out = self.class.new( width, height )
1.upto( @height ){ |y|
1.upto( @width ){ |x|
out[ x, y ] = self.class.subtract( self[ x, y ],
right_matrix[ x, y ] )
}
}
out
end

# Performs matrix multiplication between the two matrices.
# (The original matrix is not modified.)
def *( right_matrix )
raise "Size mismatch" if width != right_matrix.height ||
height != right_matrix.width
out = self.class.new( width, height )
1.upto( @height ){ |y|
1.upto( @width ){ |x|
row = self[ nil, y ]
col = right_matrix[ x, nil ]
val = row.zip( col ).inject( 0 ){ |sum, pair|
self.class.add( sum, self.class.multiply( *pair ) )
}
out[ x, y ] = val
}
}
out
end

# Scales the matrix, multiplying each value by _scale_value_ and
returning
# the resulting matrix.
# (The original matrix is not modified.)
def scale( scale_value )
out = self.class.new( width, height )
1.upto( @height ){ |y|
1.upto( @width ){ |x|
out[ x, y ] = self.class.multiply( self[ x, y ],
scale_value )
}
}
out
end

# Parses a multi-line string for use as a StringMatrix
#
# Lines in the string may be delimited by tabs, vertical bars,
or commas.
# The most common item is used as the separator; if none of the
above are
# present in the string, spaces are used.
def self.parse( raw_str )
best_count = 1
split_char = [ "\t", '|', ',' ].inject( ' ' ){ |split_char,
char|
count = raw_str.scan( char ).length
if count > best_count
best_count = count
char
else
split_char
end
}

values = []
y = 0
raw_str.each_line{ |line|
line.split( split_char ).each_with_index{ |val, x|
val.strip!
( values[ x ] ||= [] )[ y ] = case val
when /^\d+\$/ then val.to_i
when /^\d+\.\d+\$/ then val.to_f
else val
end
}
y += 1
}

width = values.length
height = y
out = self.new( width, height, '' )
1.upto( height ){ |y|
1.upto( width ){ |x|
out[ x, y ] = values[ x-1 ][ y-1 ]
}
}
out
end

out = ''
column_widths = @values.collect{ |col|
no_padding ? 0 : col.inject(0){ |max_len,val|
len = val.to_s.length
max_len > len ? max_len : len
}
}
1.upto(@height){ |y|
1.upto(@width){ |x|
out << self[ x, y ].to_s.centered_in( column_widths
[ x-1 ] )
out << " | " unless x == @width
}
out << "\n" unless y == @height
}
out
end

end

class String
# Returns a copy of the string, centered (by padding both sides
with spaces)
# within the specified width.
#
# If width is smaller than the length of the string, the string
itself is returned.
def centered_in( width )
out = self.dup
remains = width - out.length
if remains > 0
back = remains / 2
front = remains - back
out = " "*front + out + " "*back
end
out
end
end

if \$0 == __FILE__
m1 = StringMatrix.parse( "0,1,2\n3,4,5" )
m2 = StringMatrix.parse( "x,y,z\n3q,4r,5s" )
puts <<-ENDOUTPUT
M1:
---------------------------------------------------------------------
#{ m1 }

M2:
---------------------------------------------------------------------
#{ m2 }

M1 scaled by 2:
---------------------------------------------------------------------
#{ m1.scale( 2 ) }

M2 scaled by 2:
---------------------------------------------------------------------
#{ m2.scale( 2 ) }

M1 + M2:
---------------------------------------------------------------------
#{ m1 + m2 }

ENDOUTPUT

x = StringMatrix.parse <<-ENDMATRIX
1 0 0
0 cosX sinX
0 -sinX cosX
ENDMATRIX

y = StringMatrix.parse <<-ENDMATRIX
cosY | 0 | -sinY
0    | 1 | 0
sinY | 0 | cosY
ENDMATRIX

z = StringMatrix.parse <<-ENDMATRIX
cosZ    sinZ    0
-sinZ    cosZ    0
0    0    1
ENDMATRIX

puts <<-ENDOUT
X:
------------------------------------------------------------------------
-----
#{x}

Y:
------------------------------------------------------------------------
-----
#{y}

Z:
------------------------------------------------------------------------
-----
#{z}

X - Y:
------------------------------------------------------------------------
-----
#{x-y}

X * Y * Z:
------------------------------------------------------------------------
-----
#{x*y*z}
ENDOUT

end

```