Question About Swift

I'm really stuck on the following challenge:


The printTable(_:) function has a bug – it crashes if any of the data items are longer than the label of their column. Try changing Joe’s age to 1,000 to see this happen. Fix the bug. (For an easier version of this challenge, just make the function not crash. For a harder version, make sure all the rows and columns of the table are still aligned correctly.)


The challenge is referring to the following:


//: Playground - noun: a place where people can play
import Cocoa
protocol TabularDataSource {
    var numberOfRows: Int { get }
    var numberOfColumns: Int { get }

    func label(forColumn column: Int) -> String

    func itemFor(row: Int, column: Int) -> String

}
func printTable(dataSource: TabularDataSource & CustomStringConvertible) { 

    print("Table: \(dataSource.description)")

    
    var firstRow = "|"

    
    var columnWidths = [Int]()


    for i in 0 ..< dataSource.numberOfColumns {
        let columnLabel = dataSource.label(forColumn: i)
        let columnHeader = " \(columnLabel) |"
        firstRow += columnHeader
        columnWidths.append(columnLabel.count)
    }
    print(firstRow)

    for i in 0 ..< dataSource.numberOfRows {
     
        var out = "|"
     
        
        for j in 0 ..< dataSource.numberOfColumns {
            let item = dataSource.itemFor(row: i, column: j)
            let paddingNeeded = columnWidths[j] - item.characters.count //deleting "item.characters.count" makes the function not crash
            let padding = repeatElement(" ",count: paddingNeeded).joined(separator: " ")
            out += " \(padding)\(item) |"
        }
        
        print(out)
    }
} 
struct Person {
    let name: String
    let age: Int
    let yearsOfExperience: Int
}
struct Department: TabularDataSource, CustomStringConvertible {
    let name: String
    var people = [Person]()

    var description: String {
        return "Department (\(name))"
    }

    init(name: String) {
        self.name = name
    }

    mutating func add(_ person: Person) {
        people.append(person)
    }

    var numberOfRows: Int {
        return people.count
    }
    var numberOfColumns: Int {
        return 3
    }
    func label (forColumn column: Int) -> String {
        switch column {
        case 0: return "Employee Name"
        case 1: return "Age"
        case 2: return "Years of Experience"
        default: fatalError("Invalid column!")
        }
    }

    func itemFor(row: Int, column: Int) -> String {
        let person = people[row]
     
        switch column {
        case 0: return person.name
        case 1: return String(person.age)
        case 2: return String(person.yearsOfExperience)
        default: fatalError("Invalid column!")
        }
    }
}
var department = Department(name: "Engineering")

//When you change one of these ages to "1000" the program crashes
department.add(Person(name: "Joe", age: 20, yearsOfExperience: 6))
department.add(Person(name: "Karen", age: 40, yearsOfExperience: 18))
department.add(Person(name: "Fred", age: 50, yearsOfExperience: 20))
printTable(dataSource: department)

//I found that by deleting "item.characters.count" in line 38 makes the function not crash
//However I'm struggling with the harder version of making sure all of the rows and columns are aligned correctly


I've had a hard time with the second part of the challenge and haven't made much progress. I'd appreciate some hints/suggestions to point me in the right direction

I suggest you start by describing what solution you want, that would solve the problem. "If a data item is longer than the width of the column, then I want to … do what? … to make sure the alignment stays correct." If you can describe in words what would solve the problem, you're more than half way to the solution.


For example, you might say, "If a data item is longer than the width of the column, then I want to warp the fabric of space-time to squeeze the data into the space allowed to make sure the alignment stays correct." Then you'd know you had to go into a physics lab and start inventing.


OK, so that's not a practical solution, so what is?

I'm still tweaking it but I was thinking something along these lines...


printTable Function


  • Initialize two variables: one to hold the character count of the column label and the other to hold the character count of each item
  • if the character count of item is greater than the character count in the column label set them equal to each other
  • for the column labels, set paddingNeeded equal to the width of each column minus the character count in columnLabel
  • for the rows, set paddingNeeded equal to width of each column minus the character count of each item
  • or might use a function to compute widths then implement in printTable function


What do you think?

So you're making the columns wider as necessary? That's a bit of work, as you've seen, to compute the widths. What happens if a name has a million characters? So, there is more to think about here.


A simpler approach is to keep the column widths, but then you can't show all the data. So, what can you show instead? You can solve the crashing problem by only showing as much data as fits, but that's going to be misleading to anyone using the table.


Either way, solving the problem takes a bit of analysis and planning.

Yeah and a million characters is a lot lol but I see your point. I'm going to try basing the column widths by the difference in the variable character counts. I solved the crashing problem, it's just an alignment problem now. I'm going to try one of these ways and I'll post the results

Here's what I ended up with, it works now...


import Cocoa
protocol TabularDataSource {
    var numberOfRows: Int { get }
    var numberOfColumns: Int { get }
   
    func label(forColumn column: Int) -> String
   
    func itemFor(row: Int, column: Int) -> String
   
}
func computeWidths(for dataSource: TabularDataSource) -> [Int] {
    var columnWidths = [Int]()
   
    for j in 0 ..< dataSource.numberOfColumns {
        let columnLabel = dataSource.label(forColumn: j)
        columnWidths.append(columnLabel.count)
        for i in 0 ..< dataSource.numberOfRows {
            let item = dataSource.itemFor(row: i, column: j)
            if columnWidths[j] < item.count {
            columnWidths[j] = item.count
        }
    }
}
return columnWidths
}
func printTable(_ dataSource: TabularDataSource & CustomStringConvertible) {
    print("Table: \(dataSource.description)")
    var firstRow = "|"
    var columnWidths = computeWidths(for: dataSource)
   
    for i in 0 ..< dataSource.numberOfColumns {
        let columnLabel = dataSource.label(forColumn: i)
        let paddingNeeded = columnWidths[i] - columnLabel.count
        let padding = repeatElement(" ", count: paddingNeeded).joined()
        let columnHeader = " \(padding)\(columnLabel) |"
        firstRow += columnHeader
    }
print(firstRow)
   
    for i in 0 ..< dataSource.numberOfRows {
       
        var out = "|"
       
        for j in 0 ..< dataSource.numberOfColumns {
        let item = dataSource.itemFor(row: i, column: j)
        var paddingNeeded = columnWidths[j] - item.count
        if paddingNeeded < 0 { paddingNeeded = 0}
        let padding = repeatElement(" ", count: paddingNeeded).joined(separator: "")
        out += " \(padding)\(item) |"
        }
print(out)
    }
}
       
struct Person {
    let name: String
    let age: Int
    let yearsOfExperience: Int
}
struct Department: TabularDataSource, CustomStringConvertible {
    let name: String
    var people = [Person]()
   
    var description: String {
        return "Department (\(name))"
    }
   
    init(name: String) {
        self.name = name
    }
   
    mutating func add(_ person: Person) {
        people.append(person)
    }
   
    var numberOfRows: Int {
        return people.count
    }
    var numberOfColumns: Int {
        return 3
    }
    func label (forColumn column: Int) -> String {
        switch column {
        case 0: return "Employee Name"
        case 1: return "Age"
        case 2: return "Years of Experience"
        default: fatalError("Invalid column!")
        }
    }
   
    func itemFor(row: Int, column: Int) -> String {
        let person = people[row]
       
        switch column {
        case 0: return person.name
        case 1: return String(person.age)
        case 2: return String(person.yearsOfExperience)
        default: fatalError("Invalid column!")
        }
    }
}
var department = Department(name: "Engineering")
department.add(Person(name: "Joe", age: 1000, yearsOfExperience: 6))
department.add(Person(name: "Karen", age: 40, yearsOfExperience: 18))
department.add(Person(name: "Fred", age: 50, yearsOfExperience: 20))
printTable(department)

Your approach looks fine. I hope you feel that you've learned something about Swift, because the problem itself seems rather artificial.

Question About Swift
 
 
Q