Table style print for 2D Array, Dictionary and Tuples

I suggest table-style printing for 2D arrays, dictionaries, and tuples. Inspired from Javascript console.table

The stdlib print doesn't support table-style output. So It is hard to check the data in the 2D array.

Here is my suggestion. I made a sample project called Table.

1D Array with Header

print(
    table: ["Good", "Very Good", "Happy", "Cool!"], 
    header: ["Wed", "Thu", "Fri", "Sat"]
)

Output:

+----+---------+-----+-----+
|Wed |Thu      |Fri  |Sat  |
+----+---------+-----+-----+
|Good|Very Good|Happy|Cool!|
+----+---------+-----+-----+

2D Array but the length of data is not equal.

print(
    table: [["1", "b2"], 
    ["Hellow", "Great!"], 
    ["sdjfklsjdfklsadf", "dsf", "1"]]
)

OUtput:

+----------------+------+-+
|1               |b2    | |
+----------------+------+-+
|Hellow          |Great!| |
+----------------+------+-+
|sdjfklsjdfklsadf|dsf   |1|
+----------------+------+-+

Dictionary

print(
    table: [
    "1": 1, 
    2: "Hellow?", 
    1.2: 0, 
    "I'm Table": [1, 2, 3, 2, 1]
    ], 
    header: ["key", "value"]
)

Output

+---------+---------------+
|key      |value          |
+---------+---------------+
|2        |Hellow?        |
+---------+---------------+
|I'm Table|[1, 2, 3, 2, 1]|
+---------+---------------+
|1.2      |0              |
+---------+---------------+
|1        |1              |
+---------+---------------+

Implementation

  1. Define the print function.
public enum TableSpacing {
    case fillProportionally
    case fillEqually
}
/// - Parameters:
///   - table: Zero or more items to print.
///   - header: A string to print header on table.
///   - terminator: A string to print end of function.
///   - distribution: A spacing for item
@discardableResult public func print(
    table data: Any,
    header: [String]? = nil,
    distribution: TableSpacing = .fillProportionally,
    terminator: String = ""
) -> String
  1. Check the table type
let mirrorObj = Mirror(reflecting: data)

if mirrorObj.subjectType == [String].self {
}
else if mirrorObj.subjectType == [Int].self {
}
else if else if mirrorObj.subjectType == [AnyHashable: Any].self {
}
...
  1. Helper functions
  • 3.1 Horizontal Line
    Use for drawing the horizontal line on Table
@discardableResult private func horizontal(
    numberOfItems: Int,
    keyWidth: Int,
    valueWidth: Int,
    distribution: TableSpacing
) -> String
  • 3.2 Table Infomation
    Calculating the spaces between the data
private func tableInfo(data: [AnyHashable: Any]) -> (
    numberOfItem: Int,
    maxKeyWidth: Int,
    maxValueWidth: Int,
    widthInfo: [String: Int]
    )

private func tableInfo<Item: LosslessStringConvertible>(data: [Item]) -> (
    numberOfItem: Int,
    maxWidth: Int,
    widthInfo: [Int: Int]
    )

private func tableInfo<Item: LosslessStringConvertible>(data: [[Item]]) -> (
    numberOfItem: Int,
    maxWidth: Int,
    widthInfo: [Int: Int]
    )

Unit Test

func test_1DArray_Of_String_with_header() {
        let output = print(
            table: ["Good", "Very Good", "Happy", "Cool!"],
            header: ["Wed", "Thu", "Fri", "Sat"]
        )
        let expected = """
            +----+---------+-----+-----+
            |Wed |Thu      |Fri  |Sat  |
            +----+---------+-----+-----+
            |Good|Very Good|Happy|Cool!|
            +----+---------+-----+-----+
            """
        XCTAssertEqual(output, expected)
    }
    
    func test_1DArray_Of_Int() {
        let output = print(table: [2, 94231, 241245125125])
        let expected = """
            +-+-----+------------+
            |2|94231|241245125125|
            +-+-----+------------+
            """
        XCTAssertEqual(output, expected)
    }
    
    func test_1DArray_Of_Double() {
        let output = print(table: [2.0, 931, 214.24124])
        let expected = """
            +---+-----+---------+
            |2.0|931.0|214.24124|
            +---+-----+---------+
            """
        XCTAssertEqual(output, expected)
    }
    
    func test_2DArray_Of_String() {
        let output = print(
            table: [["1", "HELLOW"], ["2", "WOLLEH"]],
            header: ["Index", "Words"]
        )
        let expected = """
            +-----+------+
            |Index|Words |
            +-----+------+
            |1    |HELLOW|
            +-----+------+
            |2    |WOLLEH|
            +-----+------+
            """
        XCTAssertEqual(output, expected)
    }
    
    func test_2DArray_Of_String_With_Different_Columns() {
        let output = print(
            table: [["1", "b2"], ["Hellow", "Great!"], ["sdjfklsjdfklsadf", "dsf", "1"]],
            header: ["1", "2", "3"]
        )
        let expected = """
            +----------------+------+-+
            |1               |2     |3|
            +----------------+------+-+
            |1               |b2    | |
            +----------------+------+-+
            |Hellow          |Great!| |
            +----------------+------+-+
            |sdjfklsjdfklsadf|dsf   |1|
            +----------------+------+-+
            """
        XCTAssertEqual(output, expected)
    }
    
    func test_2DArray_Of_Int_With_Different_Columns() {
        let output = print(table: [[1, 2, 3], [4, 5, 6], [7, 8, 9, 10]])
        let expected = """
            +-+-+-+--+
            |1|2|3|  |
            +-+-+-+--+
            |4|5|6|  |
            +-+-+-+--+
            |7|8|9|10|
            +-+-+-+--+
            """
        XCTAssertEqual(output, expected)
    }
    
    func test_fillEqually_for_1DArray_Of_String_with_header() {
        let output = print(
            table: ["Good", "Very Good", "Happy", "Cool!"],
            header: ["Wed", "Thu", "Fri", "Sat"],
            distribution: .fillEqually
        )
        let expected = """
            +---------+---------+---------+---------+
            |Wed      |Thu      |Fri      |Sat      |
            +---------+---------+---------+---------+
            |Good     |Very Good|Happy    |Cool!    |
            +---------+---------+---------+---------+
            """
        XCTAssertEqual(output, expected)
    }
    
    func test_fillEqually_for_1DArray_Of_Int() {
        let output = print(
            table: [2, 94231, 241245125125],
            distribution: .fillEqually
        )
        let expected = """
            +------------+------------+------------+
            |2           |94231       |241245125125|
            +------------+------------+------------+
            """
        XCTAssertEqual(output, expected)
    }
    
    func test_fillEqually_for_1DArray_Of_Double() {
        let output = print(
            table: [2.0, 931, 214.24124],
            distribution: .fillEqually
        )
        let expected = """
            +---------+---------+---------+
            |2.0      |931.0    |214.24124|
            +---------+---------+---------+
            """
        XCTAssertEqual(output, expected)
    }
    
    func test_fillEqually_for_2DArray_Of_String() {
        let output = print(
            table: [["1", "HELLOW"], ["2", "WOLLEH"]],
            header: ["Index", "Words"],
            distribution: .fillEqually
        )
        let expected = """
            +------+------+
            |Index |Words |
            +------+------+
            |1     |HELLOW|
            +------+------+
            |2     |WOLLEH|
            +------+------+
            """
        XCTAssertEqual(output, expected)
    }
    
    func test_fillEqually_for_2DArray_Of_String_With_Different_Columns() {
        let output = print(
            table: [["1", "b2"], ["Hellow", "Great!"], ["sdjfklsjdfklsadf", "dsf", "1"]],
            header: ["1", "2", "3"],
            distribution: .fillEqually
        )
        let expected = """
            +----------------+----------------+----------------+
            |1               |2               |3               |
            +----------------+----------------+----------------+
            |1               |b2              |                |
            +----------------+----------------+----------------+
            |Hellow          |Great!          |                |
            +----------------+----------------+----------------+
            |sdjfklsjdfklsadf|dsf             |1               |
            +----------------+----------------+----------------+
            """
        XCTAssertEqual(output, expected)
    }
    
    func test_fillEqually_for_2DArray_Of_Int_With_Different_Columns() {
        let output = print(
            table: [[1, 2, 3], [4, 5, 6], [7, 8, 9, 10]],
            distribution: .fillEqually
        )
        let expected = """
            +--+--+--+--+
            |1 |2 |3 |  |
            +--+--+--+--+
            |4 |5 |6 |  |
            +--+--+--+--+
            |7 |8 |9 |10|
            +--+--+--+--+
            """
        XCTAssertEqual(output, expected)
    }
5 Likes

I like it. You may also want to participate in this related topic.

1 Like

Text really isn’t suited for faking layout:

+------+------------+
|Hebrew|Arabic      |
+------+------------+
|שלום  |السلام عليكم|
+------+------------+

:scream:

But if you are a stickler for old‐fashioned hacks, then the actual box‐drawing characters will look nicer with any text that does obey their bounds.

6 Likes

Thank you :sweat_smile:

I'll fix it.

Thank you :smiley: