I have a Matrix
structure as shown below for working with two-dimensional numerical data. The underlying numeric values of the matrix are stored as a flat array.
struct Matrix<T> {
let rows: Int
let columns: Int
var values: [T]
subscript(row: Int, column: Int) -> T {
get { return self.values[(row * self.columns) + column] }
set { self.values[(row * self.columns) + column] = newValue }
}
}
I extend the Matrix
as shown here to print the values as a grid of numbers. If the matrix is only one row, then it is printed as a row vector; otherwise, the the max integer and fraction width (length) for each column is determined based on the values in the column. The integer and fraction lengths are determined by the NumberString
protocol for Int, Float, and Double values. The integer and fraction lengths are used to pad the numbers so they are properly aligned in each column. Parentheses are added to each row for a prettier mathmatical display of the matrix.
extension Matrix: CustomStringConvertible where T: NumberString {
var description: String {
var desc = ""
// Matrix is only one row so print as a row vector
if self.rows == 1 {
desc += "( "
desc += self.values.map { "\($0)" }.joined(separator: " ")
desc += " )"
return desc
}
// Max integer width and fraction width for each matrix column
var maxIntWidth = Array(repeating: 0, count: self.columns)
var maxFracWidth = Array(repeating: 0, count: self.columns)
for i in 0..<self.rows {
for j in 0..<self.columns {
maxIntWidth[j] = max(maxIntWidth[j], self[i, j].integerLength)
maxFracWidth[j] = max(maxFracWidth[j], self[i, j].fractionLength)
}
}
// Matrix values as strings with padding
for i in 0..<self.rows {
switch i {
case 0:
desc += "⎛ "
case self.rows - 1:
desc += "⎝ "
default:
desc += "⎜ "
}
for j in 0..<self.columns {
let leftPad = String(repeating: " ", count: maxIntWidth[j] - self[i, j].integerLength)
let rightPad = String(repeating: " ", count: maxFracWidth[j] - self[i, j].fractionLength)
let valDesc = leftPad + self[i, j].numberDescription + rightPad
if j != self.columns - 1 {
desc += valDesc + " "
} else {
desc += valDesc
}
}
switch i {
case 0:
desc += " ⎞\n"
case self.rows - 1:
desc += " ⎠"
default:
desc += " ⎟\n"
}
}
return desc
}
}
import Foundation
protocol NumberString {
var numberDescription: String { get }
var integerLength: Int { get }
var fractionLength: Int { get }
}
extension Int: NumberString {
var numberDescription: String {
String(self)
}
var integerLength: Int {
String(self).count
}
var fractionLength: Int {
0
}
}
extension Float: NumberString {
var numberDescription: String {
self.formatted(.number.precision(.fractionLength(1...4)))
}
var integerLength: Int {
self.numberDescription.split(separator: ".")[0].count
}
var fractionLength: Int {
self.numberDescription.split(separator: ".")[1].count
}
}
extension Double: NumberString {
var numberDescription: String {
self.formatted(.number.precision(.fractionLength(1...6)))
}
var integerLength: Int {
self.numberDescription.split(separator: ".")[0].count
}
var fractionLength: Int {
self.numberDescription.split(separator: ".")[1].count
}
}
Here is an example of printing a matrix of integers:
func runExample3() {
let array2D = [2, 1, 892,
4, 5, 3]
let matrix = Matrix(rows: 2, columns: 3, values: array2D)
print("\nExample 3")
print(matrix)
}
runExample3()
Example 3
⎛ 2 1 892 ⎞
⎝ 4 5 3 ⎠
Here is an example of printing a matrix of doubles:
func runExample5() {
let array2D: [Float] = [2.5, 1, 8.235,
0.409, 23.5, 3,
19, 0.02, 1,
201, 9, 1902.3]
let matrix = Matrix(rows: 4, columns: 3, values: array2D)
print("\nExample 5")
print(matrix)
}
runExample5()
Example 5
⎛ 2.5 1.0 8.235 ⎞
⎜ 0.409 23.5 3.0 ⎟
⎜ 19.0 0.02 1.0 ⎟
⎝ 201.0 9.0 1,902.3 ⎠
This works fine but the current method does not provide a way for the user to define the number formatting. The formatting is coded into the NumberString
protocol for each value type. Ideally, I would like to provide a format style to the matrix which is then used for printing. Something like this where the values are printed using scientific notation:
let matrix = Matrix(rows: 4, columns: 3, values: array2D)
matrix.formatted(.number.notation(.scientific))
print(matrix)
Any suggestions on how this could be accomplished?