Disregard my previous post about not using a protocol. I see how beneficial it is now. So using the protocol, I made some improvements to the number formatting by using a format style and I also added some properties to get integer and fraction lengths. See the updated protocol code below.
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 {
String(self).split(separator: ".")[0].count
}
var fractionLength: Int {
String(self).split(separator: ".")[1].count
}
}
extension Double: NumberString {
var numberDescription: String {
self.formatted(.number.precision(.fractionLength(1...6)))
}
var integerLength: Int {
String(self).split(separator: ".")[0].count
}
var fractionLength: Int {
String(self).split(separator: ".")[1].count
}
}
And here is the revised code for the matrix struct.
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 }
}
}
extension Matrix: CustomStringConvertible where T: NumberString {
var description: String {
// 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
var desc = ""
for i in 0..<self.rows {
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
}
}
if i != self.rows - 1 {
desc += "\n"
}
}
return desc
}
}
These changes allow the printed output to be aligned by the decimal point for single and double precision values while handling integers appropriately too. Here are some examples of using this code:
let array2D = [2.5, 1, 8.235, 0.45, 23.5, 3]
let matrix = Matrix(rows: 2, columns: 3, values: array2D)
print(matrix)
2.5 1.0 8.235
0.45 23.5 3.0
let array2D = [2, 1, 892, 4, 5, 3]
let matrix = Matrix(rows: 2, columns: 3, values: array2D)
print(matrix)
2 1 892
4 5 3
let array2D: [Float] = [2.5, 1, 8.235, 0.4, 23.5, 3, 19, 0.02, 1]
let matrix = Matrix(rows: 3, columns: 3, values: array2D)
print(matrix)
2.5 1.0 8.235
0.4 23.5 3.0
19.0 0.02 1.0
My only complaint is having to loop through the values twice. Once to get the maximum integer and fraction widths for each column, then again to construct the string description for the entire matrix. If there is a way to iterate through the values once then please let me know.