sub(): 处理反向引用

3
我有一个分子式 "C5Cl2NO2S",想在 R 中计算其分子量。我认为最简单的方法是使用正则表达式将公式分解成其元素组件,并将它们传递给另一个执行计算的函数。然而,我遇到了问题,当我传递我的正则表达式的回溯引用时,它们不会被求值,而是作为 "\\1"、"\\2" 被传递。
以下是我的尝试:
masses <- list(
  C  = 12,
  H  = 1.01,
  Cl = 34.97,
  N  = 14.00,
  O  = 15.99,
  P  = 30.97,
  S  = 31.97
)

elementMass <- function( element, count ) {
  if( count == "" ) {
    count <- "1"
  }
  return( as.character( masses[[ element ]] * as.numeric( count ) ) )
}


sumFormula2Mass <- function( x ){
  y <- 0.0
  for( e in x ) {
    if( e != "" ) {
      y <- y + as.numeric( sub( "^(C|H|Cl|N|O|P|S)([0-9]*)$", elementMass("\\1", "\\2"), e ) )
    }
  }
  return( y )
}

sub(
  "^(C[0-9]*)?(H[0-9]*)?(Cl[0-9]*)?(N[0-9]*)?(O[0-9]*)?(P[0-9]*)?(S[0-9]*)?$",
  sumFormula2Mass( c("\\1", "\\2", "\\3", "\\4", "\\5", "\\6", "\\7") ),
  "C5Cl2NO2S"
)

有什么想法可以改进这个?非常感谢。
4个回答

5
在下面的假设中,我们假定问题的公式形式为一个字符串,由若干个组件组成,每个组件是一个大写字母,后面可以选择性地跟着小写字母和数字。我们使用 gsubfn 包中的 gsubfn 函数。它类似于 gsub 函数,不同之处在于替换字符串可以是各种其他对象。这里是一个 proto 对象。proto 对象是一个环境,在这里用于包含一个属性 sum 和两个方法 pre 和 fun。在开始时,pre 会自动运行,并具有初始化 sum 的效果。然后每次正则表达式匹配时,proto 对象和两个引用字符串会传递给 fun 函数,并且 fun 函数会处理它们。最终,p$sum 包含了结果。变量 masses 在问题中已经定义。
library(gsubfn)
p <- proto(pre = function(this) this$sum <- 0,
    fun = function(this, name, count) {
        count <- as.numeric(count)
        if (is.na(count)) count <- 1
        this$sum <- this$sum + masses[[name]] * count
        ""
    })
gsubfn("([[:upper:]][[:lower:]]*)(\\d*)", p, "C5Cl2NO2S")
p$sum # 207.89

3
我认为在 `sub()` 中不会像这样使用反向引用。您似乎把它们当作返回值对待,而它们是输入参数。
这里有一个不同的解决方案。它采用非常不同的方法,即将字符串分成单独的部分,然后引用这些部分。但是,它有一些限制。首先,它假设无法处理带括号的化学式。其次,它假定原子以合理的方式编写(即氯被写为 Cl - 大写 C 和小写 l)。可能还有其他很多限制,但这应该让您了解此解决方案可能的外观。
sumFormula2Mass2 <- function(x,masses){
  summedMasses <- NULL
  for(e in x){
    ## split up the string
    split.e <- unlist(strsplit(e,''))
    ## join letters from individual elements (since subequent letters should be lower case)
    ilower <- grep('[a-z]',split.e)
    if(length(ilower) > 0){
      for(i in 1:length(ilower)){
        j <- ilower[i]
        split.e <- c(if(j > 2) split.e[1:(j-2)],
                     paste(split.e[(j-1):j],collapse=''),
                     if(j < length(split.e)) split.e[(j+1):length(split.e)])
        ilower <- ilower - 1
      }
    }
    ## join numbers together (in case there are more than 10 atoms)
    inum <- grep('[0-9]',split.e)
    if(length(inum) > 1){
      for(i in 1:(length(inum)-1)){ 
        if(inum[i + 1] == inum[i] + 1){
          j <- inum[i]
          split.e <- c(split.e[1:(j-1)],
                       paste(split.e[j:(j+1)],collapse=''),
                       if(j+2 <= length(split.e)) split.e[(j+2):length(split.e)])
          inum <- inum - 1
        }
      }
    }
    ## add up the mass
    sumMass = 0
    for(i in 1:length(split.e)){
      if(length(grep('[1-9]',split.e[i])) > 0){
        next 
      } else if(split.e[i] %in% names(masses)){
        nMolecules <- 1
        if(i != length(split.e) && length(grep('[1-9]',split.e[i+1])) > 0)
          nMolecules <- as.numeric(split.e[i+1])
        sumMass <- sumMass + nMolecules * masses[[split.e[i]]]
      } else {
        warning(sprintf('Could not match element %s',split.e[i]))
        next
      }
    }
    summedMasses <- c(summedMasses,sumMass)
  }
  return(summedMasses)
}

这里是您的化合物以及一些虚构化合物的结果(我不是一名化学家):
> sumFormula2Mass2(c("C5Cl2NO2S","C5Cl2NO2S4","C5Cl10NO2S4"),masses)
[1] 207.89 303.80 583.56

我也在想做和你一样的事情 +1 - Luciano Selzer
感谢您的解决方案。对于我的情况,您的限制完全可以接受。基本上,您把它带到了点上:我需要一个函数,它将匹配组作为返回值给我。最终,我通过 unlist( strsplit( sub( ..., replacement="\\1#\\2\\#3\\4# ... ", ... ), split="#" ) ) 找到了我的解决方案。虽然不是很优雅,但很有效 :-) - Beasterfield

0

谢谢提供的链接,但不幸的是它们都与我的问题无关。分子式只是我今天正在研究的主题,但我的问题是在R中处理正则表达式反向引用。 - Beasterfield
抱歉,我犯了错误。我会将问题保留在这里,因为其他人可能会在寻找类似问题时偶然发现它 :) - JD Long

0

请看一下

RSiteSearch ("molecular weight")

我猜第二或第三个结果可能是你要找的(第一个是关于蛋白质的)。

(抱歉,没有看到你在其他答案上的评论 - 不过,如果有人真的在寻找分子量计算的话,我还是留下这个回答)。


这正是我今天问题的完美解决方案! - Beasterfield

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接