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 milk

Both 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 120

With 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  120

Names 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 kg

Below 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 kg

Start programming for free

By signing up, you agree to the Terms of Service and Privacy Policy.

Or sign up with:

4/9