将整数序列1、2、3、...转换为相应的字符串序列A、B、C、...

8

有什么快速可扩展的方法可以将整数1到N转换为相应长度的字符串序列"A","B",..."Z","AA","AB"等?

或者,我希望有一种方法可以将整数向量映射到字符向量,使得字符向量的每个元素具有相同的字符数。例如:1、2、...27 => "AA","AB",...,"AZ","BA"

示例输入:

num_vec <- seq(1, 1000)
char_vec <- ???

更新

我的临时措施,但是效果最好:

library(data.table)
myfunc <- function(n){
  if(n <= 26){
    dt <- CJ(LETTERS)[, Result := paste0(V1)]
  } else if(n <= 26^2){
    dt <- CJ(LETTERS, LETTERS)[, Result := paste0(V1, V2)]
  } else if(n <= 26^3){
    dt <- CJ(LETTERS, LETTERS, LETTERS)[, Result := paste0(V1, V2, V3)]
  } else if(n <= 26^4){
    dt <- CJ(LETTERS, LETTERS, LETTERS, LETTERS)[, Result := paste0(V1, V2, V3, V4)]
  } else if(n <= 26^5){
    dt <- CJ(LETTERS, LETTERS, LETTERS, LETTERS, LETTERS)[, Result := paste0(V1, V2, V3, V4, V5)]
  } else if(n <= 26^6){
    dt <- CJ(LETTERS, LETTERS, LETTERS, LETTERS, LETTERS, LETTERS)[, Result := paste0(V1, V2, V3, V4, V5, V6)]
  } else{
    stop("n too large")
  }

  return(dt$Result[1:n])
}

myfunc(10)

1
不幸的是,重新开放问题比关闭问题困难5倍。或者是3倍吗? - rawr
2
这个答案似乎恰好符合您的需求 https://dev59.com/MWEi5IYBdhLWcg3wFooL#21689613。不过效率方面我不太确定。 - David Arenburg
1
你可以将这个加入到问题中。如果有人觉得它可以改进,那么问题可以重新打开。 - Jaap
1
你的函数可以稍作简化:sz = ceiling(log(n)/log(26)); do.call(CJ, replicate(sz, LETTERS, simplify = F))[, Reduce(paste0, .SD)][1:n] - eddi
2
请参阅此帖子 - alexis_laz
显示剩余9条评论
3个回答

7

已经有几个不错的解决方案在评论中发布了。目前只有@Gregor在这里发布的解决方案是Ben首选的解决方案。

然而,@eddi、@DavidArenburg和@G.Grothendieck发布的方法也可以适应得到首选的结果:

# adaptation of @eddi's method:
library(data.table)
n  <- 29
sz  <- ceiling(log(n)/log(26))
do.call(CJ, replicate(sz, c("", LETTERS), simplify = F))[-1, unique(Reduce(paste0, .SD))][1:n]

# adaptation of @DavidArenburg's method:
n <- 29
list(LETTERS, c(LETTERS, do.call(CJ, replicate((n - 1) %/% 26 + 1, LETTERS, simplify = FALSE))[, do.call(paste0, .SD)][1:(n-26)])[[(n>26)+1]]

# adaptation of @G.Grothendieck's method:
n  <- 29
sz  <- ceiling(log(n)/log(26))
g <- expand.grid(c('',LETTERS), rep(LETTERS, (sz-1)))
g <- g[order(g$Var1),]
do.call(paste0, g)[1:n]

所有这三种情况都会导致以下结果:
 [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"

4

这似乎是一个非常适合使用 Rcpp 的候选方案。以下是一个非常简单的方法:

// [[Rcpp::export]]
StringVector combVec(CharacterVector x, CharacterVector y) {
    int nx = x.size();
    int ny = y.size();
    CharacterVector z(nx*ny);
    int k = 0;
    for (int i = 0; i < nx; i++) {
        for (int j = 0; j < ny; j++) {
            z[k] = x[i];
            z[k] += y[j];
            k++;
        }
    }
    return z;  
}

NumChar <- function(n) {
    t <- trunc(log(n, 26))
    ch <- LETTERS
    for (i in t:1L) {ch <- combVec(ch, LETTERS)}
    ch[1:n]
}

结果与答案提供者的答案完全一致。
library(data.table)
Rcpp::sourceCpp('combVec.cpp')

identical(myfunc(100000), NumChar(100000))
[1] TRUE 

head(NumChar(100000))
[1] "AAAA" "AAAB" "AAAC" "AAAD" "AAAE" "AAAF"
tail(NumChar(100000))
[1] "FRXY" "FRXZ" "FRYA" "FRYB" "FRYC" "FRYD"

更新的基准测试,包括@eddi的优秀Rcpp实现:

library(microbenchmark)

microbenchmark(myfunc(10000), funEddi(10000), NumChar(10000), excelCols(10000, LETTERS))
Unit: microseconds
                     expr       min        lq       mean     median        uq       max neval  cld
            myfunc(10000)  6632.125  7255.454  8441.7770  7912.4780  9283.660 14184.971   100   c 
           funEddi(10000) 12012.673 12869.928 15296.3838 13870.7050 16425.907 80443.142   100    d
           NumChar(10000)  2592.555  2883.394  3326.9292  3167.4995  3574.300  6051.273   100  b  
excelCols(10000, LETTERS)   636.165   656.820   782.7679   716.9225   811.148  1386.673   100 a 

microbenchmark(myfunc(100000), funEddi(100000), NumChar(100000), excelCols(100000, LETTERS), times = 10)
Unit: milliseconds
                     expr        min         lq       mean    median        uq       max neval  cld
            myfunc(1e+05) 203.992591 210.049303 255.049395 220.74955 262.52141 397.03521    10   c 
           funEddi(1e+05) 523.934475 530.646483 563.853995 552.83903 577.88915 688.84714    10    d
           NumChar(1e+05)  82.216802  83.546577  97.615537  93.63809 112.14316 115.84911    10  b  
excelCols(1e+05, LETTERS)   7.480882   8.377266   9.562554   8.93254  11.10519  14.11631    10 a   

正如 @DirkEddelbuettel 所说的那样 “Rcpp不是什么魔术小马…”。 这些效率上的差异只是表明,虽然 Rcpp 或者任何其他的软件包都很棒,但是它们不能修复糟糕的代码。感谢@eddi发布一个合适的Rcpp实现。


1
这很不错,但我天真地希望至少从Rcpp解决方案中获得10倍的改进... - eddi
@eddi,我同意,我期待更大的改进,但是,data.table的强大从未让我感到惊讶。此外,data.table解决方案的优雅程度已经超乎寻常。真是太赞了! - Joseph Wood
@eddi 我还有很多关于 C++Rcpp 的知识需要学习。我不应该说“以下是非常简单的方法”,而应该说“以下是一个超级新手的hack”。 - Joseph Wood
明白了 :) 我添加了另一个 Rcpp 解决方案,速度更快一些。 - eddi
@eddi,稍微快一点是低估了。你的Rcpp实现比我的快了多达10倍...非常棒!! - Joseph Wood

1

这里有一个快速的Rcpp解决方案,比本地R解决方案快几个数量级:

cppFunction('CharacterVector excelCols(int n, CharacterVector x) {
  CharacterVector res(n);
  int sz = x.size();
  std::string base;
  int baseN[100] = {0}; // being lazy about size here - you will never grow larger than this
  for (int i = 0; i < n; ++i) {
    bool incr = false;
    for (int j = base.size() - 1; j >= 0 && !incr; --j) {
      if (baseN[j] == sz) {
        baseN[j] = 1;
        base[j] = as<std::string>(x[0])[0];
      } else {
        baseN[j] += 1;
        base[j] = as<std::string>(x[baseN[j] - 1])[0];
        incr = true;
      }
    }
    if (!incr) {
      baseN[base.size()] = 1;
      base += x[0];
    }
    res[i] = base;
  }
  return res;
}')

excelCols(100, LETTERS)

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接