repeating vector of letters

前端 未结 6 1302
野的像风
野的像风 2020-12-17 17:59

Is there a function to create a repeating list of letters in R?

something like

letters[1:30]
[1] \"a\" \"b\" \"c\" \"d\" \"e\" \"f\" \"g\" \"h\" \"i         


        
6条回答
  •  轮回少年
    2020-12-17 18:34

    Working solution

    A function to produce Excel-style column names, i.e.

    # A, B, ..., Z, AA, AB, ..., AZ, BA, BB, ..., ..., ZZ, AAA, ...
    
    letterwrap <- function(n, depth = 1) {
        args <- lapply(1:depth, FUN = function(x) return(LETTERS))
        x <- do.call(expand.grid, args = list(args, stringsAsFactors = F))
        x <- x[, rev(names(x)), drop = F]
        x <- do.call(paste0, x)
        if (n <= length(x)) return(x[1:n])
        return(c(x, letterwrap(n - length(x), depth = depth + 1)))
    }
    
    letterwrap(26^2 + 52) # through AAZ
    

    Botched attempt

    Initially I thought this would best be done cleverly by converting to base 26, but that doesn't work. The issue is that Excel column names aren't base 26, which took me a long time to realize. The catch is 0: if you try to map a letter (like A) to 0, you've got a problem when you want to distinguish between A and AA and AAA...

    Another way to illustrate the problem is in "digits". In base 10, there are 10 single-digit numbers (0-9), then 90 double-digit numbers (10:99), 900 three-digit numbers... generalizing to 10^d - 10^(d - 1) numbers with d digits for d > 1. However, in Excel column names there are 26 single-letter names, 26^2 double-letter names, 26^3 triple-letter names, with no subtraction.

    I'll leave this code as a warning to others:

    ## Converts a number to base 26, returns a vector for each "digit"
    b26 <- function(n) {
        stopifnot(n >= 0)
        if (n <= 1) return(n)
        n26 <- rep(NA, ceiling(log(n, base = 26)))
        for (i in seq_along(n26)) {
            n26[i] <- (n %% 26)
            n <- n %/% 26
        }
        return(rev(n26))
    }
    
    ## Returns the name of nth value in the sequence
    ## A, B, C, ..., Z, AA, AB, AC, ..., AZ, BA, ...
    letterwrap1 <- function(n, lower = FALSE) {
        let <- if (lower) letters else LETTERS
        base26 <- b26(n)
        base26[base26 == 0] <- 26
        paste(let[base26], collapse = "")
    }
    
    ## Vectorized version of letterwrap
    letter_col_names <- Vectorize(letterwrap, vectorize.args="n")
    
    > letter_col_names(1:4)
    [1] "A" "B" "C" "D"
    
    > letter_col_names(25:30)
    [1] "Y"  "Z"  "AA" "AB" "AC" "AD"
    
    # Looks pretty good
    # Until we get here:
    > letter_col_names(50:54)
    [1] "AX" "AY" "BZ" "BA" "BB"
    

提交回复
热议问题