The fmt
package does not have means to jump up one row in the terminal. There are control characters, but they are usually terminal-dependant, so we should avoid them if we can.
We can achieve the "table" layout by printing the elements by rows. If we have 4 rows and 6 columns, that means the first row will contain the 1. element, then the 7. element, then the 13. etc. The second row will contain the 2. element, then the 8. element, then the 14. element etc.
In general, we will have len(inputs) / cols
rows, rounded up. This is how we can express that with integer arithmetic:
rows := (len(input) + cols - 1) / cols
And the elements in a row will have indices:
i := col*rows + row // col goes from 0 to cols
So basically we may use a for
loop, iterating over the rows, and print the elements of the row. We may use fmt.Printf()
with a format string that contains the index and the element, using the same width for all elements (left aligned), something like this: "%d.%-11s"
(the 11 is the width for the elements, adjust that if you have longer input texts).
The index however does not always have the same width. E.g. the index 3
has a single digit, while 13
has 2 digits. So we may use a "padding" to make each element and index occupy the same index. This padding may simply be spaces, and as many as needed to make all indices occupy the same space. E.g. if we assume we have less than 100 elements, then we may use 1 space for numbers less than 10, and no spaces for numbers 10..99.
Without further ado, here's our simple table()
printing function, that's generic enough to take the number of columns, and takes care of the rest:
func table(input []string, cols int) {
rows := (len(input) + cols - 1) / cols
for row := 0; row < rows; row++ {
for col := 0; col < cols; col++ {
i := col*rows + row
if i >= len(input) {
break // This means the last column is not "full"
}
padding := ""
if i < 9 {
padding = " "
}
fmt.Printf("%d.%-11s%s", i+1, input[i], padding)
}
fmt.Println()
}
}
Let's test it:
input := []string{
"one", "two", "three", "four", "five", "six",
"seven", "eight", "nine", "ten", "eleven", "twelve",
"thirteen", "fourteen", "fifteen", "sixteen", "seventeen", "eighteen",
"nineteen", "twenty", "twenty-one", "twenty-two", "twenty-three", "twenty-four",
}
table(input, 6)
For the above, we get this output:
1.one 5.five 9.nine 13.thirteen 17.seventeen 21.twenty-one
2.two 6.six 10.ten 14.fourteen 18.eighteen 22.twenty-two
3.three 7.seven 11.eleven 15.fifteen 19.nineteen 23.twenty-three
4.four 8.eight 12.twelve 16.sixteen 20.twenty 24.twenty-four
Now let's test with 5 columns where the last column will not be "complete":
table(input, 5)
This time output will be:
1.one 6.six 11.eleven 16.sixteen 21.twenty-one
2.two 7.seven 12.twelve 17.seventeen 22.twenty-two
3.three 8.eight 13.thirteen 18.eighteen 23.twenty-three
4.four 9.nine 14.fourteen 19.nineteen 24.twenty-four
5.five 10.ten 15.fifteen 20.twenty
Try these on the Go Playground.
Note #1:
The above solution contains the "max width" of elements "wired in". If you can't make such assumption, you may iterate over the elements first to get the max width of all elements, and use that in the format string.
This is how it can be done:
maxWidth := 0
for _, s := range input {
if len(s) > maxWidth {
maxWidth = len(s)
}
}
format := fmt.Sprintf("%%d.%%-%ds%%s", maxWidth)
Then this format
is to be used when printing elements:
fmt.Printf(format, i+1, input[i], padding)
Try this improved version on the Go Playground.
Note #2:
Also note that the above algorithm might use less columns than what you pass. It does so in the spirit of "minimizing" columns.
For example if input length is 24 and you pass cols=10
, that means at least 3 rows is required to present the 24 elements (2 rows could only display 20 elements at most in 10 columns). But if 3 rows are used / utilized, then 24 elements can be presented in as few as 8 columns, because 3*8 = 24.