Last month I made the Table library. I inspired by javascript console.table.

 

What is the Table?

The Table is a helper function to print the tabulation data bypassing the Any data! [e.g., 1d array, 2d array, and dictionary]. I’m sure if you practice coding interviews, it helps you a lot. You don’t need to struggle for checking results using a build-in print function! 

 

Examples

The Table can print the tabulation data. It also supports the iPad playground.

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

//Result
+----+---------+-----+-----+
|Wed |Thu      |Fri  |Sat  |
+----+---------+-----+-----+
|Good|Very Good|Happy|Cool!|
+----+---------+-----+-----+
print(
    table: [
        "1": 1, 
        2: "Hellow?", 
        1.2: 0, 
        "I'm Table": [1, 2, 3, 2, 1]], 
    header: [
        "key", "value"
    ]
)

//Result
+---------+---------------+
|key      |value          |
+---------+---------------+
|2        |Hellow?        |
+---------+---------------+
|I'm Table|[1, 2, 3, 2, 1]|
+---------+---------------+
|1.2      |0              |
+---------+---------------+
|1        |1              |
+---------+---------------+
print(table: [
    [1, 2, 3], 
    [4, 5, 6], 
    [7, 8, 9, 10]
])

//Result
+-+-+-+--+
|1|2|3|  |
+-+-+-+--+
|4|5|6|  |
+-+-+-+--+
|7|8|9|10|
+-+-+-+--+

iPad.PNG

 

Inside the Table Library

The declaration of function is similar to standard Swift print function.

//Standard print
func print(
    _ items: Any..., 
    separator: String = " ", 
    terminator: String = "\n"
)

//Table print
@discardableResult func print(
    table data: Any,
    header: [String]? = nil,
    distribution: TableSpacing = .fillProportionally,
    terminator: String = ""
) -> String

The difference is that it is not a variadic function. It take the one data type and then print the tabulation of given data.

 

How to check the given data type?

It use the Mirror to check the given data type. It is useful to check the type of any data.

A representation of the substructure and display style of an instance of any type.

https://developer.apple.com/documentation/swift/mirror
let mirrorObj = Mirror(reflecting: data)

//Check one dimensional array
if mirrorObj == [Any].self {
}
//Check two dimensional array
else if mirrorObj == [[Any]].self {
}
//Check Dictionary
else if mirrorObj == [AnyHashable: Any].self {
}

 

Check the Item Width

To display the tabulation of data, It needed item width in data elements. The table cell’s width is set by the longest item width.

private func tableInfo<Item: LosslessStringConvertible>(
data: [Item]) -> (
    numberOfItem: Int,
    maxWidth: Int,
    widthInfo: [Int: Int]
    ) {
    let stringData = data.map { String($0) }
    let maxWidth = stringData.sorted { 
        $0.count > $1.count 
    }.first!.count
    var maxWidthDict: [Int: Int] = [:]
    for (index, item) in stringData.enumerated() {
        maxWidthDict[index] = item.count
    }
    return (
        numberOfItem: stringData.count, 
        maxWidth: maxWidth, 
        widthInfo: maxWidthDict
    )
}

The tableInfo function return the informations of data. I use the LosslessStringConvertible protocols to get the item width by checking the characters of string.

For example, the integer value 1050 can be represented in its entirety as the string “1050”.

https://developer.apple.com/documentation/swift/losslessstringconvertible

This function didn’t considering the Unicode block so far. If you set the CJK(Chinese, Japanese, and Korean) characters then table layout will be broken. I’m going to solve it by using Unocode-Box-Drawing next time.

 

Unit Test

Apple tests the print function using TextOutputStream.

//Declaration of print function by Apple
func print<Target>(
    _ items: Any..., 
    separator: String = " ", 
    terminator: String = "\n", 
    to output: inout Target
) where Target : TextOutputStream

The TextOutputStream is a protocol. The String type already conforms to TextOutputStream. So If you pass the reference of String at to in print function, The output of print will be written into String.

//https://github.com/apple/swift/blob/master/test/stdlib/Print.swift
PrintTests.test("StdoutUTF8") {
  expectPrinted("µ", "\u{00B5}")
}

PrintTests.test("Varargs") {
  var s0 = ""
  print("", 1, 2, 3, 4, "", separator: "|", to: &s0)
  expectEqual("|1|2|3|4|\n", s0)

  var s1 = ""
  print(1, 2, 3, separator: "\n", terminator: "===", to: &s1)
  expectEqual("1\n2\n3===", s1)

  var s2 = ""
  print(4, 5, 6, separator: "\n", to: &s2)
  expectEqual("4\n5\n6\n", s2)

  var s3 = ""
  print("", 1, 2, 3, 4, "", separator: "|", to: &s3)
  expectEqual("|1|2|3|4|\n", s3)
}

I wrote the unit tests code by checking the result of function.

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_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)
}

Swift Over Coffee S2E4: Erica vs the World

Paul Hudson(HackingWithSwift.com) introduces the Table library on the Swift Over Coffee PodCast Episode S2E4.

In this episode: WWDC goes WFH, Swift gets some inspiration from JavaScript, and we review your awesome Breathe app submissions.

 

What’s the next step?

I’m going to support more types!

  • tuple

  • decodable / encodable

  • custom data type

  • emoji / unicode

Quote of the week

"People ask me what I do in the winter when there's no baseball. I'll tell you what I do. I stare out the window and wait for spring."

~ Rogers Hornsby