5

I need to write something to convert number into string with agda. I found someone asked the way to transfer string into agda before.

Agda: parse a string with numbers

I thinked about use it backwards,

row-to-stringh : (m : ℕ) → string
row-to-stringh 0 = "0"
row-to-stringh 1 = "1"
row-to-stringh 2 = "2"
row-to-stringh 3 = "3"
row-to-stringh 4 = "4"
row-to-stringh 5 = "5"
row-to-stringh 6 = "6"
row-to-stringh 7 = "7"
row-to-stringh 8 = "8"
row-to-stringh 9 = "9"
row-to-stringh _ = ""

but it not good enough. when the number is greater than 9, it will just convert it into "", instead of "(that number)". Can someone help me with this?

Community
  • 1
  • 1
Bobai
  • 51
  • 2

1 Answers1

6

If you don't want to implement this function yourself, there's a show function in the standard library.

If you want to write it yourself: the usual way of converting a number into a string is to extract the digits by repeatedly dividing with a remainder. For example (remainders are written in parens):

7214 / 10 = 721 (4)
721  / 10 = 72  (1)
72   / 10 = 7   (2)
7    / 10 = 0   (7)

You then just collect the remainders into list, reverse it and convert the digits to chars. It might be tempting to try this in Agda as well, however, you'll run into problems with termination checker.

Firstly, you'll have to convince it that divMod (that is, division with remainder - modulus) terminates. You can just hardcode the divisor into the function and convincing the termination checker becomes easy.

The hard part is showing that repeatedly dividing the number by 10 actually terminates. This will most likely involve some rather complex tricks (such as well founded recursion).


If you want to know how it's done this way, take a look at the implementation linked above. Anyways, there's a bit less efficient but much simpler way of doing this.

Let's represent digits by a list of natural numbers.

Digits = List ℕ

We would like to write a function addOne, that (as the name suggests) adds one to a number represented by a list of digits, that is:

addOne : Digits → Digits

For this, we'll use the primitive pen & paper method: add one to the least significant digit; if the result is less than 10, we are done; if it isn't, write 0 and carry the 1 to the next digit. So, here's our carry:

data Carry : Set where
  +0 : Carry
  +1 : Carry

And here's the function that performs the addition - the second Carry argument can be thought of as a carry from the addition of previous two digits.

ripple-carry : Digits → Carry → Digits
ripple-carry ns       +0 = ?
ripple-carry []       +1 = ?
ripple-carry (n ∷ ns) +1 with suc n ≤? 9
... | yes _ = ?
... | no  _ = ?

The actual implementation is an exercise - use the description given above. Just note that we store digits in reverse order (this allows for more efficient and easier implementation). For example, 123 is represented by 3 ∷ 2 ∷ 1 ∷ [] and 0 by [].

We can recover the addOne function:

addOne : Digits → Digits
addOne n = ripple-carry n +1

The rest is just plumbing.

toDigits : ℕ → Digits
toDigits zero    = []
toDigits (suc n) = addOne (toDigits n)

show : ℕ → String
show 0 = "0"
show n = (fromList ∘ map convert ∘ reverse ∘ toDigits) n
  where
  convert : ℕ → Char
  convert 0 = '0'
  convert 1 = '1'
  convert 2 = '2'
  convert 3 = '3'
  convert 4 = '4'
  convert 5 = '5'
  convert 6 = '6'
  convert 7 = '7'
  convert 8 = '8'
  convert 9 = '9'
  convert _ = ' ' -- Never happens.

Used modules:

open import Data.Char
open import Data.List
open import Data.Nat
open import Data.String
open import Function
open import Relation.Nullary

I did some testing and it turns out that this method is actually fairly effective (especially when compared to the function from standard library).

The algorithm presented above needs to access O(n) digits (addOne needs to access only one digit in 90% of cases, two digits in 9%, three in 0.9%, etc) for a given number n. Unless we have some faster primitive operations (such as _+_ using Haskell's Integer behind the scenes), this is about the fastest we can get - we are working with unary numbers after all.

Standard library uses repeated division mentioned above, which is also (unless my math is wrong) O(n). However, this does not count handling of proofs, which adds enormous overhead, slowing it down to halt. Let's do a comparison:

open import Data.Nat
open import Data.Nat.Show
open import Function
open import IO

main = (run ∘ putStrLn ∘ show) n

And here are times for the compiled code (using C-c C-x C-c in Emacs). show from standard library:

n       time
———————————————
1000     410 ms
2000    2690 ms
3000    8640 ms

If we use show as defined above, we get:

n         time
———————————————
100000    26 ms
200000    41 ms
300000    65 ms
Vitus
  • 11,822
  • 7
  • 37
  • 64
  • The show documentation is now here: [http://agda.github.io/agda-stdlib/Data.Nat.Show.html#1](http://agda.github.io/agda-stdlib/Data.Nat.Show.html#1) – Kevin Tindall Jul 08 '16 at 23:57