2

Like this Java question, but for Swift.

How can I output a table like this to the console, ideally using println?

n       result1      result2      time1      time2    
-----------------------------------------------------  
5       1000.00      20000.0      1000ms     1250ms
5       1000.00      20000.0      1000ms     1250ms
5       1000.00      20000.0      1000ms     1250ms

I tried using println("n\tresult1\tresult2") but the results don't line up properly.

Aaron Brager
  • 65,323
  • 19
  • 161
  • 287

3 Answers3

9

I found a quick and easy way to generate columnar text output in Swift (3.0) using the String method "padding(::)" [In Swift 2.x, the method is named "stringByPaddingToLength(::)"]. It allows you to specify the width of your column, the text you want to use as a pad, and the index of the pad to start with. Works like a charm if you don't mind that it only works with left-aligned text columns. If you want other alignments, you have to buy into the other methods of character counting and other such complexities.

The solution below is contrived to illustrate the utility of the method "padding(::)". Obviously, the best way to leverage this would be to create a function that iterated through a collection to produce the desired table while minimizing code repetition. I did it this way to focus on the task at hand.

Lastly, "println" doesn't seem to exist in Swift 2.x+, so I reverted to "print()".

To illustrate an example using your stated problem:

//Set up the data
let n : Int = 5
let result1 = 1000.0
let result2 = 20000.0
let time1 = "1000ms"
let time2 = "1250ms"

//Establish column widths
let column1PadLength = 8
let columnDefaultPadLength = 12

//Define the header string
let headerString = "n".padding(toLength: column1PadLength, withPad: " ", startingAt: 0) + "result1".padding(toLength: columnDefaultPadLength, withPad: " ", startingAt: 0) + "result2".padding(toLength: columnDefaultPadLength, withPad: " ", startingAt: 0) + "time1".padding(toLength: columnDefaultPadLength, withPad: " ", startingAt: 0) + "time2".padding(toLength: columnDefaultPadLength, withPad: " ", startingAt: 0)

//Define the line separator
let lineString = "".padding(toLength: headerString.characters.count, withPad: "-", startingAt: 0)

//Define the string to display a line of our data
let nString = String(n)
let result1String = String(result1)
let result2String = String(result2)
let dataString = nString.padding(toLength: column1PadLength, withPad: " ", startingAt: 0) + result1String.padding(toLength: columnDefaultPadLength, withPad: " ", startingAt: 0) + result2String.padding(toLength: columnDefaultPadLength, withPad: " ", startingAt: 0) + time1.padding(toLength: columnDefaultPadLength, withPad: " ", startingAt: 0) + time2.padding(toLength: columnDefaultPadLength, withPad: " ", startingAt: 0)

//Print out the data table
print("\(headerString)\n\(lineString)\n\(dataString)")

The output will be printed to your console in a tidy columnar format:

n       result1     result2     time1       time2       
--------------------------------------------------------
5       1000.0      20000.0     1000ms      1250ms  

Changing the variable "columnDefaultPadLength" from 12 to 8 will result in the following output:

n       result1 result2 time1   time2   
----------------------------------------
5       1000.0  20000.0 1000ms  1250ms  

Finally, reducing the padding length to a value less than the data truncates the data instead of generating errors, very handy! Changing the "columnDefaultPadLength" from 8 to 4 results in this output:

n       resuresutimetime
------------------------
5       1000200010001250

Obviously not a desired format, but with the simple adjustment of the padding length, you can quickly tweak the table into a compact yet readable form.

1

You need to determine the maximum length of a string in your data (from both the keys and values) and then pad those strings. You can use a function like what I've provided below to calculate the maximum length and go from there.

func maxLength(data: Dictionary<String,Double>) -> Int {
    var greatestLength = 0
    for (key, value) in data {
        var valueLength = countElements(String(format: "%.2f", value))
        var keyLength   = countElements(key)
        var length      = max(valueLength, keyLength)
        if (length > greatestLength) {
            greatestLength = length
        }
    }
    return greatestLength
}
james_womack
  • 10,028
  • 6
  • 55
  • 74
0

One approach would be to calculate the maximum length of each column and add some spacing.

import Foundation

let rowTitle = ["C1", "Column 2", "Column 3"]
let row1 = ["0", "1", "2"]
let row2 = ["00000", "11", "2222"]

let columns = [rowTitle, row1, row2]

struct ColumnFormatter {
    let columns: [[String]]
    let spacing: Int
    
    func maxColumnLength() -> Dictionary<Int, Int>{
        // row -> maxLength
        var maxColumnLength: Dictionary<Int, Int> = [:]
        for column in columns {
            for (i, e) in column.enumerated(){
                let maxLength = maxColumnLength[i] ?? 0
                let length = e.count
                if length > maxLength {
                    maxColumnLength[i] = length
                }
            }
        }
        return maxColumnLength
    }
    
    func format() -> String {
        var formattedString = ""
        let maxColumnLength = maxColumnLength()
        
        for column in columns {
            for i in 0..<column.count - 1 {
                let length = (maxColumnLength[i] ?? 0) + spacing
                let paddedString = column[i].padding(toLength: length, withPad: " ", startingAt: 0)
                formattedString += paddedString
            }
            formattedString += column.last ?? "" // no need to pad the last column
            formattedString += "\n"
        }
        return formattedString
    }
}

let columnFormatter = ColumnFormatter(columns: columns, spacing: 5)
columnFormatter.maxColumnLength()
print(columnFormatter.format())
C1        Column 2     Column 3
0         1            2
00000     11           2222
BPDev
  • 397
  • 1
  • 9