Formatting and Columns
You know string.format from the lesson on variables: it puts values into a text according to its format specifiers, %d for a whole number, %.2f for a decimal number with two decimal places, and %s for a text. format is a string function like the others, so you can also call it as a method on the text with the specifiers:
local name = "Bella"
local liters = 18.5
print(string.format("%s gave %.1f liters of milk", name, liters))
print(("%s gave %.1f liters of milk"):format(name, liters))This prints:
Bella gave 18.5 liters of milk
Bella gave 18.5 liters of milkBoth lines do the same; the method needs the parentheses around the text, as you know. %s takes any value, not just a text: ("%s"):format(true) is "true". And because % starts a specifier, a percent sign is written as %%: ("%d%%"):format(75) is "75%".
A number between the % and the letter sets the width: how many characters the value takes at least. A shorter value is filled up with spaces on the left, so it's aligned to the right. With a minus, the spaces go on the right, and the value is aligned to the left. The brackets in this example show where each value starts and ends:
print(("[%5d]"):format(42))
print(("[%-5d]"):format(42))
print(("[%8s]"):format("hen"))
print(("[%-8s]"):format("hen"))
print(("[%7.2f]"):format(3.14159))This prints:
[ 42]
[42 ]
[ hen]
[hen ]
[ 3.14]In %7.2f, 7 is the whole width, decimal point included, and 2 is the number of decimal places. Widths are what you need for a table that lines up. Without them, each line is only as long as its values, and the numbers end up all over the place:
print(("%s %d"):format("eggs", 14))
print(("%s %d"):format("milk", 9))
print(("%s %d"):format("strawberries", 120))This prints:
eggs 14
milk 9
strawberries 120With widths, the columns line up:
print(("%-12s %4d"):format("eggs", 14))
print(("%-12s %4d"):format("milk", 9))
print(("%-12s %4d"):format("strawberries", 120))This prints:
eggs 14
milk 9
strawberries 120Names go to the left, so %-12s; numbers go to the right, so that the ones and the tens are under each other, %4d. Choose the width by the longest value in the column: a longer value isn't cut off, it just breaks the alignment.
A typical mistake is a specifier without a value:
print(("%s and %s"):format("eggs"))main.lua:1: bad argument #2 to 'format' (no value)The message says that one of the specifiers got no value. Count the specifiers and the values: there must be as many values as specifiers (%% doesn't count, it needs no value). With the colon, Lua doesn't count the text before it as an argument, so the message says #2; string.format("%s and %s", "eggs") says #3, as in the lesson on variables.
One more thing about widths: they count bytes, and a letter with an accent takes two. A line with purée in it would be shifted by one place. That's why the names in this exercise have no accents; the next exercise shows how to deal with them.
Instructions
Change the print in the loop so that it uses format: the name aligned to the left in a column 12 characters wide, a space, the weight aligned to the right in a column 6 characters wide with 2 decimal places, then a space and kg. The output will look like this:
carrots 12.50 kg
potatoes 30.00 kg
strawberries 4.25 kgBelow the loop, print a line with the total: the text Total in the column of the names, and the sum of the weights in the column of the weights. Add up the weights in the loop. The line will look like this:
Total 46.75 kgStart programming for free
4/9