我遇到了非英语符号的奇怪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
答案 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"
#>