sprintf填充非英文符号

时间:2015-12-11 16:35:49

标签: r printf cyrillic

我遇到了非英语符号的奇怪sprintf()行为。我尝试填充字符串,但我得到了意想不到的结果:

lapply(c("ZZZ", "ZZZZZZ", "ЯЯЯ", "ЯЯЯЯЯЯ"),
       function(x) sprintf("%-20s: %s", x, "VALUE"))
#> [[1]]
#> [1] "ZZZ                 : VALUE"
#> 
#> [[2]]
#> [1] "ZZZZZZ              : VALUE"
#> 
#> [[3]]
#> [1] "ЯЯЯ              : VALUE"
#> 
#> [[4]]
#> [1] "ЯЯЯЯЯЯ        : VALUE"
#> 

任何人都可以解释为什么会发生这种情况以及如何解决这个问题?

会话信息可能很有用:

R version 3.2.2 (2015-08-14)
Platform: x86_64-pc-linux-gnu (64-bit)
Running under: Arch Linux

locale:
[1] LC_CTYPE=ru_RU.UTF-8       LC_NUMERIC=C               LC_TIME=ru_RU.UTF-8        LC_COLLATE=C              
[5] LC_MONETARY=ru_RU.UTF-8    LC_MESSAGES=ru_RU.UTF-8    LC_PAPER=ru_RU.UTF-8       LC_NAME=C                 
[9] LC_ADDRESS=C               LC_TELEPHONE=C             LC_MEASUREMENT=ru_RU.UTF-8 LC_IDENTIFICATION=C       

attached base packages:
[1] stats     graphics  grDevices utils     datasets  methods   base     

loaded via a namespace (and not attached):
[1] shiny_0.12.2      R6_2.1.1          rsconnect_0.4.1.4 htmltools_0.2.6   tools_3.2.2       Rcpp_0.12.2       digest_0.6.8     
[8] xtable_1.8-0      httpuv_1.3.3      mime_0.4 

2 个答案:

答案 0 :(得分:1)

我可以告诉你它为什么会发生,但不能告诉你如何修复它。来自docs for sprintf

  

%s转换的字段宽度和精度被解释为字节,而不是字符,如C标准中所述。

在UTF-8中,字符Я是两个字节(0xD0 0xAF),因此"ЯЯЯ"是6个字节,而"ZZZ"是3个字节,sprintf相应地呈现它们

修改

一种解决方法是使用sprintf的星号功能,它允许您声明字段的宽度(以字节为单位),以及nchar功能,它可以让您计算显示宽度和字符串中的字节数。

因此,例如,nchar("ЯЯЯ", "width")nchar("ЯЯЯ", "bytes")分别返回3和6。如果我们想将其宽度填充为20个显示字符,那么我们必须给sprintf宽度 23 字节:20加上字节数减去显示宽度。

sprintf("%-*s", 23, "ЯЯЯ")
#> [1] "ЯЯЯ                 "

或者:

str <- "ЯЯЯ"
pad.len <- 20 + nchar(str, "bytes") + nchar(str, "width")
sprintf("%-*s", pad.len, str)
#> [1] "ЯЯЯ                 "

这也适用于"ZZZ",因为字节和显示宽度相等,所以它出现在20:

pad <- function(str) {
  pad.len <- 20 + nchar(str, "bytes") - nchar(str, "width")
  return(sprintf("%-*s: %s", pad.len, str, "VALUE"))
}

print(lapply(c("ZZZ", "ZZZZZZ", "ЯЯЯ", "ЯЯЯЯЯЯ"), pad))
#> [[1]]
#> [1] "ZZZ                 : VALUE"
#> 
#> [[2]]
#> [1] "ZZZZZZ              : VALUE"
#> 
#> [[3]]
#> [1] "ЯЯЯ                 : VALUE"
#> 
#> [[4]]
#> [1] "ЯЯЯЯЯЯ              : VALUE"

P.S。这是我写过的第一个R代码,如果您看到任何改进方法,请随时发表评论。

答案 1 :(得分:1)

我在stri_pad_right()包中找到了stringi函数的解决方案:

lapply(c("ZZZ", "ZZZZZZ", "ЯЯЯ", "ЯЯЯЯЯЯ"),
       function(x) paste0(stringi::stri_pad_right(x, 20), ": VALUE"))
#> [[1]]
#> [1] "ZZZ                 : VALUE"
#> 
#> [[2]]
#> [1] "ZZZZZZ              : VALUE"
#> 
#> [[3]]
#> [1] "ЯЯЯ                 : VALUE"
#> 
#> [[4]]
#> [1] "ЯЯЯЯЯЯ              : VALUE"
#> 

更新

基于@Jordan答案的另一种解决方案仅使用基本R函数:

str_pad <- function(str, width = floor(0.9 * getOption("width")),
                    side = c("left", "both", "right")) {
    side <- match.arg(side)
    asc <- iconv(str, "latin1", "ASCII")
    ind <- is.na(asc) | asc != str
    if (any(ind)) 
        width <- width + nchar(str, "bytes") - nchar(str, "width")
    switch(side, left = sprintf("%-*s", width, str),
           right = sprintf("%*s", width, str),
           both = sprintf("%-*s", width, sprintf("%*s", floor(width/2), str)))
}
lapply(c("ZZZ", "ZZZZZZ", "ЯЯЯ", "ЯЯЯЯЯЯ"),
       function(x) paste0(str_pad(x, 20), ": VALUE"))
#> [[1]]
#> [1] "ZZZ                 : VALUE"
#> 
#> [[2]]
#> [1] "ZZZZZZ              : VALUE"
#> 
#> [[3]]
#> [1] "ЯЯЯ                 : VALUE"
#> 
#> [[4]]
#> [1] "ЯЯЯЯЯЯ              : VALUE"
#>