从化学式

时间:2017-09-07 08:10:24

标签: excel vba excel-vba excel-formula chemistry

道歉,如果已经提出并回答了这个问题,但我找不到满意的答案。

我有一个化学公式列表,按顺序包括:C,H,N和O.我想在每个字母之后提取数字。问题是并非所有公式都包含N.但是,所有公式都包含C,H和O.并且数字可以是单数,双数或(仅在H的情况下)三位数。

因此数据如下所示:

  • C20H37N1O5
  • C10H12O3
  • C20H19N3O4
  • C23H40O3
  • C9H13N1O3
  • C14H26O4
  • C58H100N2O9

我希望列表中的每个元素编号都在不同的列中。所以在第一个例子中它将是:

20 37 1 5

我一直在尝试:

=IFERROR(MID(LEFT(A2,FIND("H",A2)-1),FIND("C",A2)+1,LEN(A2)),"") 

分离出C#。然而,在此之后我被卡住了,因为H#侧面是O或N.

是否有excel公式或VBA可以做到这一点?

6 个答案:

答案 0 :(得分:9)

使用正则表达式

regular expressions(正则表达式)这是一项很好的任务。由于VBA不支持开箱即用的正则表达式,因此我们需要首先引用Windows库。

  1. 工具下添加对正则表达式的引用,然后引用 enter image description here

  2. 并选择 Microsoft VBScript Regular Expression 5.5 enter image description here

  3. 将此功能添加到模块

    Option Explicit 
    
    Public Function ChemRegex(ChemFormula As String, Element As String) As Long
        Dim strPattern As String
        strPattern = "([CNHO])([0-9]*)" 
                     'this pattern is limited to the elements C, N, H and O only.
        Dim regEx As New RegExp
    
        Dim Matches As MatchCollection, m As Match
    
        If strPattern <> "" Then
            With regEx
                .Global = True
                .MultiLine = True
                .IgnoreCase = False
                .Pattern = strPattern
            End With
    
            Set Matches = regEx.Execute(ChemFormula)
            For Each m In Matches
                If m.SubMatches(0) = Element Then
                    ChemRegex = IIf(Not m.SubMatches(1) = vbNullString, m.SubMatches(1), 1) 
                                'this IIF ensures that in CH4O the C and O are count as 1
                    Exit For
                End If
            Next m
        End If
    End Function
    
  4. 在单元格公式中使用这样的函数

    E.g。在单元格B2中:=ChemRegex($A2,B$1)并将其复制到其他单元格 enter image description here

  5. 同时识别出多次出现的元素的化学公式,例如CH3OHCH2COOH

    请注意,上面的代码不能计算元素出现多次的CH3OH之类的内容。然后只有第一个H3计数,最后一个被省略。

    如果您还需要以CH3OHCH2COOH等格式识别公式(并总结元素的出现次数),那么您需要更改代码以识别这些......

    If m.SubMatches(0) = Element Then
        ChemRegex = ChemRegex + IIf(Not m.SubMatches(1) = vbNullString, m.SubMatches(1), 1)
        'Exit For needs to be removed.
    End If
    

    enter image description here

    同时识别包含NaOHCaCl2

    等2个字母元素的化学公式

    除了上面对多次出现的元素的更改外,请使用以下模式:

    strPattern = "([A-Z][a-z]?)([0-9]*)"   'https://regex101.com/r/nNv8W6/2
    

    enter image description here

    1. 请注意,它们必须位于正确的大写/小写字母大小写中。 CaCl2有效,但不是cacl2CACL2
    2. 请注意,这并不能证明这些字母组合是否是元素周期表的现有元素。所以这也将承认例如。 Xx2Zz5Q作为虚构元素Xx = 2Zz = 5Q = 1

      要仅接受元素周期表中存在的组合,请使用以下模式:

      strPattern = "([A][cglmrstu]|[B][aehikr]?|[C][adeflmnorsu]?|[D][bsy]|[E][rsu]|[F][elmr]?|[G][ade]|[H][efgos]?|[I][nr]?|[K][r]?|[L][airuv]|[M][cdgnot]|[N][abdehiop]?|[O][gs]?|[P][abdmortu]?|[R][abefghnu]|[S][bcegimnr]?|[T][abcehilms]|[U]|[V]|[W]|[X][e]|[Y][b]?|[Z][nr])([0-9]*)"
      'https://regex101.com/r/Hlzta2/3
      'This pattern includes all 118 elements up to today. 
      'If new elements are found/generated by scientist they need to be added to the pattern.
      

答案 1 :(得分:4)

这似乎工作正常:

enter image description here

B2中的公式如下。向上和向下拖动

=IFERROR(IFERROR(--(MID($A2,SEARCH(B$1,$A2)+1,3)),IFERROR(--(MID($A2,SEARCH(B$1,$A2)+1,2)),--MID($A2,SEARCH(B$1,$A2)+1,1))),0)

或更短的数组公式,必须使用 ctrl + shift + 输入

=MAX(IFERROR(--MID($A2,SEARCH(B$1,$A2)+1,ROW($A$1:$A$3)),0))

如果你想保持VBA超级简单,那么这样的东西也可以:

Public Function ElementCount(str As String, element As String) As Long
    Dim i As Integer
    Dim s As String

    For i = 1 To 3
        s = Mid(str, InStr(str, element) + 1, i)
        On Error Resume Next
        ElementCount = CLng(s)
        On Error GoTo 0
    Next i
End Function

像这样使用它:

=ElementCount(A1,"C")

答案 2 :(得分:2)

我是在VBA中使用正则表达式完成的。你也许可以像Vityata那样通过循环遍历字符串来做到这一点,但我怀疑这会更快更容易阅读。

Option Explicit

Function find_associated_number(chemical_formula As Range, element As String) As Variant
  Dim regex As Object: Set regex = CreateObject("VBScript.RegExp")
  Dim pattern As String
  Dim matches As Object

  If Len(element) > 1 Or chemical_formula.CountLarge <> 1 Then
    find_associated_number = CVErr(xlErrName)
  Else
    pattern = element + "(\d+)\D"
    With regex
      .pattern = pattern
      .ignorecase = True
      If .test(chemical_formula) Then
        Set matches = .Execute(chemical_formula)
        find_associated_number = matches(0).submatches(0)
      Else
        find_associated_number = CVErr(xlErrNA)
      End If
    End With
  End If
End Function

然后就像正常一样使用表格中的公式:

enter image description here

C列含有碳原子数,D列含有氮原子数。只需通过复制公式并更改其搜索的元素来扩展它。

答案 3 :(得分:1)

使用VBA这是一项简单的任务 - 您必须遍历字符并检查值是否为数字。 使用Excel,该解决方案包含一些冗余。但这是可行的。例如,

如果您应用以下公式,

C20H37NO5 将返回 20375

=IF(ISNUMBER(1*MID(A1,1,1)),MID(A1,1,1),"")&
IF(ISNUMBER(1*MID(A1,2,1)),MID(A1,2,1),"")&
IF(ISNUMBER(1*MID(A1,3,1)),MID(A1,3,1),"")&
IF(ISNUMBER(1*MID(A1,4,1)),MID(A1,4,1),"")&
IF(ISNUMBER(1*MID(A1,5,1)),MID(A1,5,1),"")&
IF(ISNUMBER(1*MID(A1,6,1)),MID(A1,6,1),"")&
IF(ISNUMBER(1*MID(A1,7,1)),MID(A1,7,1),"")&
IF(ISNUMBER(1*MID(A1,8,1)),MID(A1,8,1),"")&
IF(ISNUMBER(1*MID(A1,9,1)),MID(A1,9,1),"")

目前,它会检查前9个字符是否为数字。如果要包含9个以上,那么只需在公式中添加几行。

公式中有一个小技巧 - 1*。如果可能,它会将文本字符转换为数字。因此,5作为文本,乘以1成为数字字符。

答案 4 :(得分:1)

使用split和like方法。

Sub test()
    Dim vDB As Variant, vR() As Variant
    Dim s As String
    Dim vSplit As Variant
    Dim i As Long, n As Long, j As Integer

    vDB = Range("a2", Range("a" & Rows.Count).End(xlUp))

    n = UBound(vDB, 1)
    ReDim vR(1 To n, 1 To 4)
    For i = 1 To n
        s = vDB(i, 1)
        For j = 1 To Len(s)
            If Mid(s, j, 1) Like "[A-Z]" Then
                s = Replace(s, Mid(s, j, 1), " ")
            End If
        Next j
        vSplit = Split(s, " ")
        For j = 1 To UBound(vSplit)

            vR(i, j) = vSplit(j)
        Next j
    Next i
    Range("b2").Resize(n, 4) = vR
End Sub

答案 5 :(得分:1)

如果您希望vba解决方案提取所有数字,我首选的解决方案是使用正则表达式。以下代码将从字符串中提取所有数字

"another-foo"