gsub 删除不需要的精度

gsub to remove unwanted precision

任何人都可以用 R 中的 gsub 帮助实现以下目标吗?

input string: a=5.00,b=120,c=0.0003,d=0.02,e=5.20, f=1200.0,g=850.02
desired output: a=5,b=120,c=0.0003,d=0.02,e=5.2, f=1200, g=850.02

实际上,如果小数点后的多余0都是0就去掉,如果有实数就不要去掉。

要删除小数点后的尾随 0,试试这个:

编辑 忘记了 5.00

x = c('5.00', '0.500', '120', '0.0003', '0.02', '5.20', '1200', '850.02')
gsub("\.$" "", gsub("(\.(|[1-9]+))0+$", "\1", x))
# [1] "5"    "0.5"    "120"    "0.0003" "0.02"   "5.2"    "1200"   "850.02"

HT @TimBiegeleisen:我将输入误读为字符串向量。对于单个字符串输入,转换为字符串向量,您可以对其调用 gsub,然后将输出折叠回单个字符串:

paste(
    gsub("\.$", "", gsub("(\.(|[1-9]+))0+$", "\1",
    unlist(strsplit(x, ", ")))), 
        collapse=", ")

[1] "a=5, b=0.5, c=120, d=0.0003, e=0.02, f=5.2, g=1200, h=850.02"

gsub 是一个文本处理工具,适用于字符级别。它不知道任何语义解释。

但是,您对操纵这种语义解释特别感兴趣,即文本中编码的数字的精度。

所以使用:解析文本中的数字,并以所需的精度写出它们:

parse_key_value_pairs = function (text) {
    parse_pair = function (pair) {
        pair = strsplit(pair, "\s*=\s*")[[1]]
        list(key = pair[1], value = as.numeric(pair[2]))
    }
    pairs = unlist(strsplit(text, "\s*,\s*"))
    structure(lapply(pairs, parse_pair), class = 'kvp')
}

as.character.kvp = function (x, ...) {
    format_pair = function (pair) {
        sprintf('%s = %g', pair[1], pair[2])
    }
    pairs = vapply(x, format_pair, character(1))
    paste(pairs, collapse = ", ")
}

并按如下方式使用:

text = "a=5.00,b=120,c=0.0003,d=0.02,e=5.20, f=1200.0,g=850.02"
parsed = parse_key_value_pairs(text)
as.character(parsed)

这使用了 R 的几个有趣的特性:

  • 对于文本处理,它仍然使用正则表达式(strsplit内)。
  • 要处理多个值,请使用lapply依次对字符串的部分应用解析函数
  • 要重建键值对,请使用 sprintf 格式化字符串。 sprintf 是一个从 C 改编而来的原始文本格式化工具。但它相当通用,在我们的例子中它工作正常。
  • 解析后的值带有 S3 class name 标记。这就是 R 实现面向对象的方式。
  • 为我们的类型提供标准泛型 as.character 的重载。这意味着任何接受对象并通过 as.character 显示它的现有函数都可以处理我们解析的数据类型。特别是,这适用于 {glue} library:

    > glue::glue("result: {parsed}")
    result: a = 5, b = 120, c = 0.0003, d = 0.02, e = 5.2, f = 1200, g = 850.02
    

我无法单独使用 gsub 使它工作,但我们可以尝试用逗号分割你的输入向量,然后使用 apply 函数和 gsub:

x <- "a=5.00,b=120,c=0.0003,d=0.02,e=5.20, f=1200.0,g=850.02"
input <- sapply(unlist(strsplit(x, ",")), function(x) gsub("(?<=\d)\.$", "", gsub("(\.[1-9]*)0+$", "\1", x), perl=TRUE))
input <- paste(input, collapse=",")
input

[1] "a=5,b=120,c=0.0003,d=0.02,e=5.2, f=1200,g=850.02"

Demo

实际上我打了两次电话给 gsub。如果数字有一个,第一个调用会去掉所有出现在 小数点后 的尾随零。在像 5.00 这样的数字的情况下,第二次调用会删除杂散的小数点,第一次调用将保留为 5. 而不是我们想要的 5

这可能不是最理想的解决方案,但出于教育目的,这是一种使用 条件正则表达式仅 一次 调用 gsub 的方法:

x = 'a=5.00,b=120,c=0.0003,d=0.02,e=5.20, f=1200.0,g=850.02'

gsub('(?!\d+(?:,|$))(\.[0-9]*[1-9])?(?(1)0+\b|\.0+(?=(,|$)))', '\1', x, perl = TRUE)
# [1] "a=5,b=120,c=0.0003,d=0.02,e=5.2, f=1200,g=850.02"

备注:

  1. (?!\d+(?:,|$)) 是一种负向后视,它与逗号或字符串结尾后的数字匹配一次或多次。这有效地从整个正则表达式匹配中排除模式。

  2. (\.[0-9]*[1-9])? 匹配文字点、数字零次或多次以及数字(零除外)。 ? 使此模式可选,并且对于条件如何处理反向引用至关重要。

  3. (?(1)0+\b|\.0+(?=(,|$))) 是条件逻辑 (?(IF)THEN|ELSE)

    • (1)(IF) 部分,它检查捕获组 1 是否匹配。这是指(\.[0-9]*[1-9])

    • 0+\b(THEN) 部分,仅当 (IF)TRUE 时才匹配。在这种情况下,仅当 (\.[0-9]*[1-9]) 匹配时,正则表达式才会尝试匹配单词边界后的零一次或多次

    • \.0+(?=(,|$))(ELSE) 部分,仅当 (IF)FALSE 时才匹配。在这种情况下,仅当 (\.[0-9]*[1-9]) 没有 匹配时,正则表达式才会尝试匹配文字点,在逗号或字符串结尾后出现一次或多次零

  4. 如果我们将 2. 和 3. 放在一起,我们会得到 (\.[0-9]*[1-9])0+\b\.0+(?=(,|$))

  5. \1 作为替换因此将 (\.[0-9]*[1-9])0+\b 变为 (\.[0-9]*[1-9]) 匹配的模式或将 \.0+(?=(,|$)) 变为空白。转化为:

    • 5.205.2为前者

    • 5.0051200.01200 后者