带有非英文符号的 sprintf 填充
sprintf padding with non English symbols
我遇到了非英语符号的奇怪 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
我可以告诉你为什么会这样,但不能告诉你如何解决。来自 docs for sprintf
:
Field widths and precisions of %s
conversions are interpreted as bytes, not characters, as described in the C standard.
在 UTF-8 中,字符 Я
是两个字节 (0xD0 0xAF),因此 "ЯЯЯ"
是 6 个字节,而 "ZZZ"
是 3 个字节,并且 sprintf
呈现他们相应地。
编辑
一种解决方法是使用 sprintf
的星号功能,它可以让您声明字段的宽度(以字节为单位),以及 nchar
函数,它可以让您计算显示宽度和字符串中的字节数。
因此,例如,nchar("ЯЯЯ", "width")
和 nchar("ЯЯЯ", "bytes")
return 分别为 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 代码,所以如果您发现任何改进方法,请随时发表评论。
我从 stringi
包中找到了带有 stri_pad_right()
函数的解决方案:
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"
#>
我遇到了非英语符号的奇怪 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
我可以告诉你为什么会这样,但不能告诉你如何解决。来自 docs for sprintf
:
Field widths and precisions of
%s
conversions are interpreted as bytes, not characters, as described in the C standard.
在 UTF-8 中,字符 Я
是两个字节 (0xD0 0xAF),因此 "ЯЯЯ"
是 6 个字节,而 "ZZZ"
是 3 个字节,并且 sprintf
呈现他们相应地。
编辑
一种解决方法是使用 sprintf
的星号功能,它可以让您声明字段的宽度(以字节为单位),以及 nchar
函数,它可以让您计算显示宽度和字符串中的字节数。
因此,例如,nchar("ЯЯЯ", "width")
和 nchar("ЯЯЯ", "bytes")
return 分别为 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 代码,所以如果您发现任何改进方法,请随时发表评论。
我从 stringi
包中找到了带有 stri_pad_right()
函数的解决方案:
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"
#>