wigging
(Gavin Wiggins)
1
Below is a matrix struct for working with 2D numerical data.
struct Matrix<T> {
let rows: Int
let columns: Int
var data: Array<T>
subscript(row: Int, column: Int) -> T {
get { return self.data[(row * self.columns) + column] }
set { self.data[(row * self.columns) + column] = newValue }
}
}
extension Matrix: CustomStringConvertible {
var description: String {
var desc = ""
desc += "\(self.rows)x\(self.columns) \(type(of: self))\n"
for i in 0..<self.rows {
for j in 0..<self.columns {
desc += "\(self[i, j]) "
}
desc += "\n"
}
return desc
}
}
Here I print out a matrix of double-precision values:
let values: [Double] = [1, 2, 3, 4,
5, 6, 7, 8,
9, 10, 11, 12]
let mat = Matrix(rows: 3, columns: 4, data: values)
print(mat)
// 3x4 Matrix<Double>
// 1.0 2.0 3.0 4.0
// 5.0 6.0 7.0 8.0
// 9.0 10.0 11.0 12.0
I would like the print output to be aligned by the decimal place like the following:
3x4 Matrix<Double>
1.0 2.0 3.0 4.0
5.0 6.0 7.0 8.0
9.0 10.0 11.0 12.0
How can I align the numbers in each column by the decimal?
Also, if I have a matrix of integers, I would like to align the print output as shown below. So instead of aligning by a decimal place, the numbers are left padded.
3x4 Matrix<Int>
1 2 3 4
5 6 7 8
9 10 11 12
vanvoorden
(Rick van Voorden)
2
The Ordo One Benchmark package uses a fork of the TextTable repo to print results. Maybe try there for some ideas to get started?
1 Like
wigging
(Gavin Wiggins)
3
I tried the following which gets me closer to what I want to achieve. But there's still some left padding in the first column and large decimal numbers aren't handled well.
extension Matrix: CustomStringConvertible where T: Comparable {
var description: String {
let maxValue = self.data.max()!
let width = "\(maxValue)".components(separatedBy: ".")[0].count
var desc = ""
desc += "\(self.rows)x\(self.columns) \(type(of: self))\n"
for i in 0..<self.rows {
for j in 0..<self.columns {
let newWidth = "\(self[i, j])".components(separatedBy: ".")[0].count
let pad = String(repeating: " ", count: width + 2 - newWidth)
let d = pad + "\(self[i, j])"
if j == 0 {
desc += d.dropFirst(width)
} else {
desc += d
}
}
desc += "\n"
}
return desc
}
}
3x4 Matrix<Int>
1 2 3 4
5 6 7 8
9 10 11 12
3x4 Matrix<Double>
1.0 2.0 3.0 4.0
5.0 6.0 7.0 8.0
9.0 10.0 11.0 12.0
wigging
(Gavin Wiggins)
4
Using a protocol I'm able to format each number based on its type. But I would like to get rid of the left padding in the first column.
protocol NumberString {
var numberDescription: String { get }
}
extension Int: NumberString {
var numberDescription: String {
let d = String(format: "%4d", self)
return d
}
}
extension Double: NumberString {
var numberDescription: String {
let d = String(format: "%8.2f", self)
return d
}
}
extension Matrix: CustomStringConvertible where T: NumberString {
var description: String {
var desc = ""
desc += "\(self.rows)x\(self.columns) \(type(of: self))\n"
for i in 0..<self.rows {
for j in 0..<self.columns {
let d = self[i, j].numberDescription
desc += d
}
desc += "\n"
}
return desc
}
}
3x4 Matrix<Int>
1 2 3 4
5 6 7 8
9 10 11 12
3x4 Matrix<Double>
1.00 2.00 3.00 4.00
5.00 6.00 7.00 8.00
9.00 10.00 11.00 12.00
bzamayo
(Benjamin Mayo)
5
In the numberDescription implementation of Int, the %4d string format will always include minimum spacing, so the width of the string is always at least 4 characters.
If you want custom alignment, I would suggest using %d so that numberDescription values only include the digits with no whitespace padding. (You don't even need String(format:), then, you could just do "\\(self)".)
Then before you print the columns, you can loop all values, work out the maximum space of the column, and prepend (maxColumnWidth - itemDescription.count) spaces for every other row in the column, which will give you right alignment. For the first column, to get left alignment as desired, do the same but append spaces instead of prepending.
wigging
(Gavin Wiggins)
6
I would like each column to be the same size except for the first column. So instead of this:
3x4 Matrix<Int>
1 2 3 4
50 6 7 8
0 10 11 12
I want it to be printed as:
3x4 Matrix<Int>
1 2 3 4
50 6 7 8
0 10 11 12
Basically, the width of the first column should be the width of the largest number string. So in this example, for the first column I want something like String(format: "%2d", self) and for the other columns it would be String(format: "%4d", self).
bzamayo
(Benjamin Mayo)
7
Sure, but what I'm saying is you don't want to involve the internals of numberDescription at all with the spacing. numberDescription produces the formatted number, so Int(1).numberDescription yields simply "1". Add the spacing as a separate step when you make the Matrix description.
Collect all the numberDescriptions of a column in a temporary array, get their maximum width, and then you know how many whitespace characters to add before or after each item's description, to achieve the alignment you want for the column it is in.
bzamayo
(Benjamin Mayo)
8
Here's a working code snippet to illustrate what I mean:
func descriptionWithAlignedColumns(_ matrix: [[Int]]) -> String {
let formattedMatrix = matrix.map { row in
row.map {
String($0) // this would be your $0.numberDescription call
}
}
let numberOfRows = matrix.count
let numberOfColumns = matrix[0].count
func maxWidth(forColumn col: Int) -> Int {
var width: Int = 0
for i in 0..<numberOfRows {
width = max(width, formattedMatrix[i][col].count)
}
return width
}
var formatted = ""
for y in 0..<numberOfRows {
for x in 0..<numberOfColumns {
let maxColumnWidth = maxWidth(forColumn: x)
let itemDescription = formattedMatrix[y][x]
let itemDescriptionWidth = itemDescription.count
let whitespacePadding = String(repeating: " ", count: maxColumnWidth - itemDescriptionWidth)
let itemDescriptionWithPadding: String
if x == 0 {
itemDescriptionWithPadding = itemDescription + whitespacePadding
} else {
itemDescriptionWithPadding = whitespacePadding + itemDescription
}
formatted += itemDescriptionWithPadding
formatted += " "
}
formatted += "\n"
}
return formatted
}
let numbers = [
[42555555, 13, 150, 2400],
[2, 530, 1500, 400],
[6, 3, 100, 925],
[8, 9, 24, 100],
]
let s = descriptionWithAlignedColumns(numbers)
print(s)
This will print out the table of numbers with the first column left-aligned and all subsequent columns right-aligned.
I wrote this very quickly in like two minutes, but hopefully you can see how to adapt this to your Matrix type as appropriate. Note there are also some obvious performance optimisations you might want to do, which may matter if you have a bigger table of data in reality, like caching the max width calculation for each column.
wigging
(Gavin Wiggins)
9
This would give:
42555555 13 150 2400
2 530 1500 400
6 3 100 925
8 9 24 100
But what I'm trying to accomplish would be like this:
42555555 13 150 2400
2 530 1500 400
6 3 100 925
8 9 24 100
All the columns should be right-aligned and the width of the first column should be determined from the largest number in that column. I got close to such an effect in my example that used the Comparable protocol but there was still a small left pad on the first column.
bzamayo
(Benjamin Mayo)
10
If the second formatting is what you want, then, remove the if x == 0 branch and everything will be right aligned.
wigging
(Gavin Wiggins)
11
Yep, it works when I remove the x == 0 check. And if make the function generic with descriptionWithAlignedColumns<T>(_ matrix: [[T]]) then it works with Float and Double values too. Thank you very much for helping me with this.
wigging
(Gavin Wiggins)
12
@bzamayo Here's what I ended up using for the Matrix struct.
protocol NumberString {
var numberDescription: String { get }
}
extension Int: NumberString {
var numberDescription: String {
"\(self)"
}
}
extension Double: NumberString {
var numberDescription: String {
String(format: "%.2f", self)
}
}
extension Matrix: CustomStringConvertible where T: NumberString, T: Comparable {
var description: String {
var desc = ""
// Matrix size and type
desc += "\(self.rows)x\(self.columns) \(type(of: self))\n"
// Max column width for each matrix column
var colVals = [T]()
var maxColWidth = [Int]()
for j in 0..<self.columns {
for i in 0..<self.rows {
colVals.append(self[i, j])
}
maxColWidth.append(colVals.max()!.numberDescription.count)
colVals = []
}
// Matrix values as strings with padding
for i in 0..<self.rows {
for j in 0..<self.columns {
let colWidth = maxColWidth[j]
let valWidth = self[i, j].numberDescription.count
let pad = String(repeating: " ", count: colWidth - valWidth)
let valDesc = pad + self[i, j].numberDescription + " "
desc += valDesc
}
desc += "\n"
}
return desc
}
}
1 Like
ibex10
13
Good work!
To finish it off, pretty print more than one vector side by side. 
[42555555 13 150 2400] [0 0 0] [ 13 150 2400]
[ 2 530 1500 400] [1 0 0] [530 1500 400]
[ 6 3 100 925] [0 1 0] [ 3 100 925]
[ 8 9 24 100] [0 0 1] [ 9 24 100]