道歉,如果已经提出并回答了这个问题,但我找不到满意的答案。
我有一个化学公式列表,按顺序包括:C,H,N和O.我想在每个字母之后提取数字。问题是并非所有公式都包含N.但是,所有公式都包含C,H和O.并且数字可以是单数,双数或(仅在H的情况下)三位数。
因此数据如下所示:
我希望列表中的每个元素编号都在不同的列中。所以在第一个例子中它将是:
20 37 1 5
我一直在尝试:
=IFERROR(MID(LEFT(A2,FIND("H",A2)-1),FIND("C",A2)+1,LEN(A2)),"")
分离出C#。然而,在此之后我被卡住了,因为H#侧面是O或N.
是否有excel公式或VBA可以做到这一点?
答案 0 :(得分:9)
regular expressions(正则表达式)这是一项很好的任务。由于VBA不支持开箱即用的正则表达式,因此我们需要首先引用Windows库。
将此功能添加到模块
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
在单元格公式中使用这样的函数
CH3OH
或CH2COOH
请注意,上面的代码不能计算元素出现多次的CH3OH
之类的内容。然后只有第一个H3
计数,最后一个被省略。
如果您还需要以CH3OH
或CH2COOH
等格式识别公式(并总结元素的出现次数),那么您需要更改代码以识别这些......
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
NaOH
或CaCl2
除了上面对多次出现的元素的更改外,请使用以下模式:
strPattern = "([A-Z][a-z]?)([0-9]*)" 'https://regex101.com/r/nNv8W6/2
CaCl2
有效,但不是cacl2
或CACL2
。请注意,这并不能证明这些字母组合是否是元素周期表的现有元素。所以这也将承认例如。 Xx2Zz5Q
作为虚构元素Xx = 2
,Zz = 5
和Q = 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)
这似乎工作正常:
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
然后就像正常一样使用表格中的公式:
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"