【问题标题】:Need R code with GREP to extract numbers from string containing special characters需要使用 GREP 的 R 代码从包含特殊字符的字符串中提取数字
【发布时间】:2021-10-04 12:22:46
【问题描述】:

我需要 R 代码来提取字符串中的所有数字,然后对它们求和。

这是一个示例字符串: s <- c("123(11)56(10)89")

  1. 大多数字符串至少有九个数字,但在某些情况下最后一个值是字母“x”。
  2. 括号中为两位数。
  3. 并非每个字符串都有括号中的数字。那些可能在括号中包含多个两位数的数字,并且它们在字符串中的位置可能会有所不同。
  4. 示例字符串包含九个数字:1,2,3,11,5,6,10,8,9。
  5. 在结果字符串中,我需要能够单独访问每个数字:例如,字符串中的第 5 个数字是什么?我还需要能够对字符串的所有数字求和,对于示例字符串,它是 55。

在 R 中,我尝试使用 str_replace 函数,但无法使其工作。

我熟悉“dplyr”。

我查看了处理字符串问题的 stackoverflow 帖子,但找不到与我的情况相符的帖子。如果我错过了一个,请告诉我如何查看它。

【问题讨论】:

  • 搅拌应该有引号。你有"c(123(11)56(10)89)"这样的字符串吗
  • 甚至"123(11)56(10)89"?您能否粘贴运行dput(s) 得到的输出

标签: r string


【解决方案1】:

我想你实际上有一个像这样的 char 标量:

s <- "123(11)56(10)89"

您可以使用stringr。首先使用|折叠的特定模式提取所有单个数字\\d或括号\\(\\d{2}\\)包围的两位数字,因为R将尽可能匹配最长的字符串。 然后你可以用'[()]' 删除()。最后用as.numeric()转成数字:

library(stringr)

output <- s %>% str_extract_all('\\(\\d{2}\\)|\\d', simplify = TRUE) %>%
        str_remove_all('[()]')%>%
        as.numeric()

output
[1]  1  2  3 11  5  6 10  8  9

如果您有这样的字符 s &lt;- "c(123(11)56(10)89)",您可以通过管道进行一些初步转换并使用相同的逻辑:

s %>% str_remove_all('^c\\(|\\)$')%>%
        str_extract_all('\\(\\d{2}\\)|\\d', simplify = TRUE) %>%
        str_remove_all('[()]')%>%
        as.numeric()

之后您可以进行所需的操作:

sum(output)
[1] 55

output[5]
[1] 5

【讨论】:

    【解决方案2】:

    我们可以使用SKIP/FAIL 操作和gsub 来插入一个分隔符,即在除(..) 内的块之外的所有数字上插入一个类似于, 的东西,在第二个gsub 中删除()并使用scan读取为整数值

    na.omit(scan(text = gsub("[()]", ",",
        gsub("\\([^)]+\\)(*SKIP)(*FAIL)|(\\d)", "\\1,", x, perl = TRUE)), 
              what = numeric(), sep=","))
    

    -输出

    [1]  1  2  3 11  5  6 10  8  9
    

    或者另一种选择是

    as.integer(setdiff(strsplit(gsub("\\(\\d+\\)(*SKIP)(*FAIL)|(\\d)", "\\1(", x, perl = TRUE), "[()]")[[1]], ""))
    [1]  1  2  3 11  5  6 10  8  9
    

    数据

    x <- "123(11)56(10)89"
    

    【讨论】:

      【解决方案3】:

      所以这对所有专业人士来说一定很痛苦,但这是我的尝试,请告诉我:

      1. s中提取所有数字
      2. 然后使用上述模式创建一个命名向量。

      我无法通过正则表达式获取此模式,因此我一一使用gsub

      请告诉!

      s <- as.numeric(gsub("[^0-9.-]", "", s))
      
      my_vector <- c(digit_1 = substr(s, 1,1),
                     digit_2 = substr(s, 2,2),
                     digit_3 = substr(s, 3,3),
                     digit_4 = substr(s, 4,5),
                     digit_5 = substr(s, 6,6),
                     digit_6 = substr(s, 7,7),
                     digit_7 = substr(s, 8,9),
                     digit_8 = substr(s, 10,10),
                     digit_9 = substr(s, 11,11)
                     )
      my_vector
      

      输出:

      digit_1 digit_2 digit_3 digit_4 digit_5 digit_6 digit_7 digit_8 digit_9 
          "1"     "2"     "3"    "11"     "5"     "6"    "10"     "8"     "9" 
      

      【讨论】:

      • 这种方法的问题是它可能只适用于示例字符串,但根据 OP 规范,它不能推广到其他字符串
      • 感谢您的反馈。
      【解决方案4】:

      如果一个数字有时超过两位,您可以使用如下函数:

      get_num <- function(x) {
        y <- unlist(strsplit(x,""))
        out <- numeric()
        nexting <- FALSE
        for(i in 1:length(y)) {
          
          if(!nexting) keep <- character()
          if(y[i] == "(") {
            nexting <- TRUE
            keep <- character()
            next
          } 
          if(y[i] == ")") {
            nexting <- FALSE
          } else {
            keep <- paste(keep,y[i], collapse = "")
            keep <- gsub(" ","", keep)
          }
          if(!nexting) {
            if(is.na(as.numeric(keep))) next
            out <- c(out,as.numeric(keep))
          } 
        }
        return(out)
      }
      

      然后你从字符串中得到数字向量,并可以从那里做你想做的事。这也会删除任何非数字或(/) 的不需要的字符。

      x <- "123(11)56(10)89"
      get_num(x)
      [1]  1  2  3 11  5  6 10  8  9
      z <- "11(384)2299(298)29"
      get_num(z)
      [1]   1   1 384   2   2   9   9 298   2   9
      a <- "23p94872(39827)20x"
      get_num(a)
      [1]     2     3     9     4     8     7     2 39827     2     0
      

      这有点hacky,但它可以完成工作。

      【讨论】:

        【解决方案5】:

        这是一个单一的stringr 解决方案:

        str_extract_all(x, "(?<!\\()\\d(?!\\))|(?<=\\()(\\d+)(?=\\))", "\\1")
        [,1] [,2] [,3] [,4] [,5] [,6] [,7] [,8] [,9]
        "1"  "2"  "3"  "11" "5"  "6"  "10" "8"  "9"
        

        这通过使用lookarounds(一个用于单个数字,一个用于双数字,两个由|分隔作为替代)并通过引用它们之间的数字来工作使用 \\1 的反向引用

        作为数字数据:

        as.numeric(unlist(str_extract_all(x, "(?<!\\()\\d(?!\\))|(?<=\\()(\\d+)(?=\\))", "\\1")))
        [1]  1  2  3 11  5  6 10  8  9
        

        数据:

        x <- "123(11)56(10)89"
        

        【讨论】:

        • OP 声明 "示例字符串包含九个数字:1,2,3,11,5,6,10,8,9",但这会返回 c(NA, 123, 11, 56, 10, 89) .
        【解决方案6】:

        也许有点蛮力,但是...

        s <- c("123(11)56(10)89")
        
        int1 <- lapply(regmatches(s, gregexpr("\\([0-9]+\\)", s)),
                       function(z) as.integer(gsub(z, pattern = "\\D", replacement = "")))
        int2 <- lapply(strsplit(sapply(regmatches(s, gregexpr("[^(0-9][0-9]+\\D?|\\D?[0-9]+[^)0-9]", s)),
                       function(z) paste(gsub("\\D", "", z), collapse = "")), ""), as.integer)
        
        int1
        # [[1]]
        # [1] 11 10
        int2
        # [[1]]
        # [1] 1 2 3 5 6 8 9
        

        然后你可以总结它们

        mapply(sum, int1, int2)
        # [1] 55
        

        我将它们保留为 lists 的原因是,它可以应用于字符串向量和索引中保留的总和。


        编辑

        这可以通过在基础 R 中使用 GuedesBF 的正则表达式来简化为单个表达式。不要接受我基于此的答案(该答案完成了大部分工作),但在基础 R 中这同样有效:

        lapply(regmatches(s, gregexpr("\\(\\d{2}\\)|\\d", s)),
               function(z) as.integer(gsub("\\D", "", z)))
        # [[1]]
        # [1]  1  2  3 11  5  6 10  8  9
        

        如果你想要的只是求和而不看到这个向量,那么

        sapply(regmatches(txt, gregexpr("\\(\\d{2}\\)|\\d", txt)),
               function(z) sum(as.integer(gsub("\\D", "", z))))
        # [1] 55
        

        确实,GuedesBF 得到了正确的正则表达式,你应该接受这个答案。此答案提供的唯一优势是:

        1. Base R,以防这对您很重要。

        2. 这个答案独立地处理每个字符串,所以如果length(s) 大于 1,这个答案仍然可以使用 GuedesBF 的正则表达式。

          s3 <- rep(s, 3)
          sapply(regmatches(s3, gregexpr("\\(\\d{2}\\)|\\d", s3)),
                 function(z) sum(as.integer(gsub("\\D", "", z))))
          # [1] 55 55 55
          

          ...但这可以通过lapplypurrr::map 或类似的东西轻松解决。

        【讨论】:

          猜你喜欢
          • 2020-08-26
          • 2020-12-25
          • 1970-01-01
          • 2017-09-02
          • 1970-01-01
          • 2019-12-02
          • 2020-07-12
          • 2016-08-24
          • 2015-02-15
          相关资源
          最近更新 更多