Here's my implementation. Note that you need to load SRFI 1, which provides unfold-right
:
(define letters "ABCDEFGHIJKLMNOPQRSTUVWXYZ")
(define (number->letters num)
(unfold-right negative?
(lambda (i) (string-ref letters (remainder i 26)))
(lambda (i) (- (quotient i 26) 1))
num))
(define (number->tag num)
(list->string (cons #\M (number->letters num))))
Examples:
> (number->tag 0)
"MA"
> (number->tag 18277)
"MZZZ"
> (number->tag 18278)
"MAAAA"
The OP asked for an explanation of what the code does. So, with the understanding that the OP already understands the algorithm (since they linked to it already), what's basically left is the unfold operation.
Fold and unfold are a bit lengthy to explain and I don't want to derail this post by explaining them, but it's possible to "expand" the unfold into the equivalent loop (using the same variable names as the SRFI 1 reference implementation of unfold-right
) to express what's going on:
(define (number->letters num)
(let lp ((seed num) (ans '()))
(if (negative? seed)
ans
(lp (- (quotient seed 26) 1)
(cons (string-ref letters (remainder seed 26)) ans)))))
Basically, it builds a list, from right to left, using (string-ref letters (remainder seed 26))
each iteration (where seed
is num
in the initial iteration). seed
's value is then updated to (- (quotient seed 26) 1)
for the next iteration. The list stops when (negative? seed)
is true.
You might then ask why one would use an unfold instead of the loop. Basically, in functional programming, when a "concept" can be expressed in higher-level terms (e.g., for-each, map, filter, fold, or unfold), using those terms helps other programmers understand what the code does. It's a bit like "design patterns" (as commonly used in object-oriented programming), within a functional programming context. :-)