Format println output in a table - swift

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.

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.

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
}

Related

How to truncate a comma-separated value string with remainder count

I'm trying to achieve string truncate with "& more..." when string is truncated. I have this in picture:
Exact code minus text, in image:
func formatString() -> String {
let combinedLength = 30
// This array will never be empty
let strings = ["Update my profile", "Delete me", "Approve these letters"]
// In most cases, during a loop (no order of strings)
//let strings = ["Update", "Delete", "Another long word"]
let rangeNum = strings.count > 1 ? 2 : 1
let firstN = strings[0..<rangeNum]
// A sum of first 2 or 1
let actualLength = firstN.compactMap { $0.count }.reduce(0, +)
switch actualLength {
case let x where x <= combinedLength:
// It's safe to display all
return strings.map{String($0)}.joined(separator: ", ")
default:
if rangeNum == 2 {
if actualLength <= combinedLength {
return strings.first! + ", " + strings[1] + ", & \(strings.count - 2) more..."
}
return strings.first! + ", & \(strings.count - 1) more..."
}
// There has to be at least one item in the array.
return strings.first!
}
}
While truncateMode looks like a match, it's missing the , & n more... where n is the remainder.
My code may not be perfect but was wondering how to refactor. I feel there's a bug in there somewhere. I've not taken into consideration for larger screens: iPad where I would want to display more comma-separated values, I only look for the max 2 then display "& n more" depending on the size of the array.
Is there a hidden modifier for this? I'm using XCode 13.4.1, targeting both iPhone and iPad.
Edit:
The title is incorrect. I want to convert an array of strings into a comma-separated value string that's truncated using the function I have.

The compiler is unable to type-check this expression

I want to divide difference data into 60 and print it as double numbers. When I print it as a string, it does not appear to be a fraction of the number. I get this problem when I print the number "n" . What should I do?
My mistake: the compiler is unable to type-check this expression in reasonable time; try breaking up the expression into distinct sub-expressions
if let date = formatter.date(from: receivedTimeString) {
let receivedTimeHoursMinutes = Calendar.current.component(.hour, from: date) * 60
let receivedTimeMinutes = Calendar.current.component(.minute, from: date)
let totalreceivedTimeMinutes = receivedTimeHoursMinutes + receivedTimeMinutes
let todayHoursMinutes = Calendar.current.component(.hour, from: Date()) * 60
let todayMinutes = Calendar.current.component(.minute, from: Date())
let todayTimeMinutes = todayHoursMinutes + todayMinutes
let difference = todayTimeMinutes - totalreceivedTimeMinutes
let str = String(difference)
switch true {
case difference > 60:
let deger = String(difference / 60)
guard let n = NumberFormatter().number(from: deger) else { return }
print("deger", deger)
self.labelTimerFarkSonuc.text = (n) + (" ") + ("Saattir") + (" ") + (durum)
case difference == 0:
self.labelTimerFarkSonuc.text = (n) + (" ") + ("Dakikadır") + (" ") + (durum)
case difference < 60:
self.labelTimerFarkSonuc.text = (n) + (" ") + ("Dakikadır") + (" ") + (durum)
default:
self.labelTimerFarkSonuc.text = (n) + (" ") + ("Dakikadır") + (" ") + (durum)
}
If i had understood your question correctly,
If you want to have result of following code as decimal fraction,
let deger = String(difference / 60) // Dividing by INT will not give fractions.
Change it to following.
let deger = String(difference / 60.0)

Swift: Byte array to decimal value

In my project, I communicate with a bluetooth device, the bluetooth device must send me a timestamp second, I received in byte:
[2,6,239]
When I convert converted to a string:
let payloadString = payload.map {
String(format: "%02x", $0)
}
Output:
["02", "06","ef"]
When I converted from the website 0206ef = 132847 seconds
How can I directly convert my aray [2,6,239] in second (= 132847 seconds)?
And if it's complicated then translate my array ["02", "06,"ef"] in second (= 132847 seconds)
The payload contains the bytes of the binary representation of the value.
You convert it back to the value by shifting each byte into its corresponding position:
let payload: [UInt8] = [2, 6, 239]
let value = Int(payload[0]) << 16 + Int(payload[1]) << 8 + Int(payload[2])
print(value) // 132847
The important point is to convert the bytes to integers before shifting, otherwise an overflow error would occur. Alternatively,
with multiplication:
let value = (Int(payload[0]) * 256 + Int(payload[1])) * 256 + Int(payload[2])
or
let value = payload.reduce(0) { $0 * 256 + Int($1) }
The last approach works with an arbitrary number of bytes – as long as
the result fits into an Int. For 4...8 bytes you better choose UInt64
to avoid overflow errors:
let value = payload.reduce(0) { $0 * 256 + UInt64($1) }
payloadString string can be reduced to hexStr and then converted to decimal
var payload = [2,6,239];
let payloadString = payload.map {
String(format: "%02x", $0)
}
//let hexStr = payloadString.reduce(""){$0 + $1}
let hexStr = payloadString.joined()
if let value = UInt64(hexStr, radix: 16) {
print(value)//132847
}

Add and format leading zero's in a string [duplicate]

This question already has an answer here:
Including zeros in front of an integer [duplicate]
(1 answer)
Closed 6 years ago.
I want to show a number in my app, and to keep it pretty, display leading zeros to make the layout even.
For example
000 500 / 500 000
001 000 / 500 000
100 350 / 500 000
I get the first number from an Int, and I want to format it into a string.
Is there a neat way to assure that a number is always six digits, and also get the range of the leading zeros to format them differently?
NSNumberFormatter has everything that you needs:
let formatter = NumberFormatter()
formatter.minimumIntegerDigits = 6
formatter.numberStyle = .decimal
print(formatter.string(from: 500)!)
print(formatter.string(from: 1000)!)
print(formatter.string(from: 100_350)!)
How about this?
String extension:
extension String {
func substring(startIndex: Int, length: Int) -> String {
let start = self.startIndex.advancedBy(startIndex)
let end = self.startIndex.advancedBy(startIndex + length)
return self[start..<end]
}
}
usage:
var a = 500
var s = "000000\(a)"
print(s.substring(s.characters.count - 6, length: 6))
a = 500000
s = "000000\(a)"
print(s.substring(s.characters.count - 6, length: 6))
a = 50
s = "000000\(a)"
print(s.substring(s.characters.count - 6, length: 6))

Swift formatting a string to a certain length

I have a series of string that I need to print on 1 line.
var qty: Int = 1
var name: "Book"
var price: 13.50
I need each to have blank space appended to them so they are a certain length or have characters removed if they are too long. For the qty id like it to be a length of 3, name 30 and price 8
Format should be
1 Book 13.50
There is a much better, Swift-like solution:
String(qty).stringByPaddingToLength(3, withString: " ", startingAtIndex: 0)
name.stringByPaddingToLength(30, withString: " ", startingAtIndex: 0)
String(price).stringByPaddingToLength(8, withString: " ", startingAtIndex: 0)
According to Apple documentation:
Returns a new string formed from the receiver by either removing characters from the end, or by appending as many occurrences as necessary of a given pad string.
So that's what you need.
The usage can be something as this:
let output = String(qty).stringByPaddingToLength(3, withString: " ", startingAtIndex: 0) + name.stringByPaddingToLength(30, withString: " ", startingAtIndex: 0) + String(price).stringByPaddingToLength(8, withString: " ", startingAtIndex: 0)
UPDATE:
In order to pad to the right:
let p = String(price)
"".stringByPaddingToLength(8 - p.characters.count, withString: " ", startingAtIndex: 0) + p