Search code examples
rconvertersletters

Numeric to Alphabetic Lettering Function in R


I have written a function which works on the integers from 1 to 702 for converting a number to a letter in a very specific way. Here are some examples of how I would like the lettering function to work:

  • 1 -> A,
  • 2 -> B,
  • 27 -> AA,
  • 29 -> AC,
  • and so on.

We use this function for "numbering" / "lettering" our appendices in reports. I'm looking to make it more general, such that it can handle positive integers of any size. If I could easily convert the original number to base 26, this would be easier, but I do not see an easy way to do that in R.

appendix_lettering <- function(number) {
  if (number %in% 1:26) {
    return(LETTERS[[number]])
  } else if (number %in% 27:702) {
    first_digit <- (floor((number - 1) / 26))
    second_digit <- ((number - 1) %% 26) + 1
    first_letter <- LETTERS[[first_digit]]
    second_letter <- LETTERS[[second_digit]]
    return(paste0(first_letter, second_letter))
  }
}

Does anyone have suggestions for how I can most easily improve this function to handle any positive integers (or at least many more)?


Solution

  • Here are some alternatives:

    1) encode Let b be the base. Here b = 26. Then there are b^k appendices having k letters so for a particular appendix having number x it has n letters if n is the smallest integer for which b + b^2 + ... + b^n >= x. The LHS of this inequality is a geometric series and therefore has a closed form solution. Replacing the LHS with that expression and solving the resulting equation for n gives the formula for n in the code below. Then we subtract all b^k terms from number for which k < n and use the APL-like encode function found here (and elsewhere on the web). encode does the base conversion giving digits, a vector of digits in base base. Finally add 1 to each digit and use that as a lookup into LETTERS.

    app2 <- function(number, base = 26) {
        n <- ceiling(log((1/(1 - base) - 1 - number) * (1 - base), base = base)) - 1
        digits <- encode(number - sum(base^seq(0, n-1)), rep(base, n))
        paste(LETTERS[digits + 1], collapse = "")
    }
    
    sapply(1:29, app2) # test
    

    giving:

    [1] "A"  "B"  "C"  "D"  "E"  "F"  "G"  "H"  "I"  "J"  "K"  "L"  "M"  "N"  "O" 
    [16] "P"  "Q"  "R"  "S"  "T"  "U"  "V"  "W"  "X"  "Y"  "Z"  "AA" "AB" "AC"
    

    Another test to try is:

    sapply(1:60, app2, base = 3)
    

    2) recursive solution Here is an alternative that works recursively. It computes the last letter of the Appendix number and then removes it and recursively computes the portion to its left.

    app2r <- function(number, base = 26, suffix = "") {
       number1 <- number - 1
       last_digit <- number1 %% base
       rest <- number1 %/% base
       suffix <- paste0(LETTERS[last_digit + 1], suffix)
       if (rest > 0) Recall(rest, base, suffix) else suffix
    }
    
    # tests
    identical(sapply(1:29, app2r), sapply(1:29, app2))
    ## [1] TRUE
    identical(sapply(1:60, app2r, base = 3), sapply(1:60, app2, base = 3))
    ## [1] TRUE