本帖最后由 yivifu 于 2014-8-11 13:19 编辑
论坛上有超级高手发布了一个公式,可以抽取字符串中的正数、负数或所有数值分别求和。高手的公式(要输入成数组公式)如下:
正数和: =SUM(TEXT(LEFT(TEXT(MID(A2&"a",COLUMN(2:2),ROW($1:$15)),),ROW($1:$15)-1),"[>];\0;0;!0")*ISERR(-MID(A2,COLUMN(2:2)-1,2))) 负数和: =SUM(-TEXT(LEFT(TEXT(MID(A2&"a",COLUMN(2:2),ROW($1:$15)),),ROW($1:$15)-1),"[<];;\0;!0")*ISERR(-MID(A2,COLUMN(2:2)-1,2))) 全部和: =SUM(TEXT(LEFT(TEXT(MID(A2&"a",COLUMN(2:2),ROW($1:$15)),),ROW($1:$15)-1),"[<>];;0;!0")*ISERR(-MID(A2,COLUMN(2:2)-1,2)))
示例文件中给出了如下字符串进行测试: 五晨152 可是中-1956.36 酃有时地365在0 测试结果全部正确。但是,如果你将测试字符串中的“152”改成“15-2”,正常理解应该是字符串中包含了一个整数15和一个负数-2,但是公式计算正数和和全部和的结果时却出错了。 另外,如果你将“-1956.36”改成“--1956.36”,按excel的运算规则应该是将-1956.36变成了正数,这个公式也不能得出正确结果。 尽管有这些问题,这个公式仍然让人大开眼界,给人许多启示,因此,应该对原作者表示敬意。由于这个公式上没法应用F9进行按步骤的分析,我也没能力修正其中的bug,但是却让我产生了一个念头:编制一个自定义函数用于抽取字符串中的数值。 在excel中,数值可以包含的合法字符是数字、正负号和小数点,我们可以从头开始扫描字符串,遇到合法数值字符时创建一个临时字符串开始记录,记录完成后再扫描余下部分,遇到合法数值字符又创建一个新的临时字符串开始记录,最后将临时字符串数组中的数值转换成数值类型后返回,这就是基本思路。按这个思路完成的自定义函数如下: - '辅助函数,判断字符是否是合法数值字符
- Private Function InCharset(c As String) As Boolean
- If c = "." Or c = "+" Or c = "-" Or IsNumeric(c) Then
- InCharset = True
- Else
- InCharset = False
- End If
- End Function
复制代码- '按excel数值规则抽取字符串中的所有数值,作为双精度数组返回。字符串中无数值时返回仅包含一个元素的双精度数组,该数组只有一个0值。
- Function ExtractNumber(str As String) As Double()
- Dim i%, j%, currC$, preC$, tmp() As String, res() As Double
- j = -1: preC = ""
- For i = 1 To Len(str)
- currC = Mid(str, i, 1)
- If InCharset(currC) Then '当前字符是合法数字字符
- If i > 1 Then preC = Mid(str, i - 1, 1)
- '如果前一字符不是合法数字字符,那么新建临时字符串准备存放新的数字串
- If Not InCharset(preC) Then
- j = j + 1
- ReDim Preserve tmp(j)
- tmp(j) = tmp(j) & currC
- '如果前一字符是正号或负号
- ElseIf preC = "+" Or preC = "-" Then
- '跟随小数点或数字,直接连接到临时字符串中
- If currC = "." Or IsNumeric(currC) Then
- tmp(j) = tmp(j) & currC
- '如果跟随字符是负号,那么将临时字符串中的数字符号变号,如果跟随正号,丢弃,不予处理
- ElseIf currC = "-" Then
- If tmp(j) = "+" Then
- tmp(j) = "-"
- ElseIf tmp(j) = "-" Then
- tmp(j) = "+"
- End If
- End If
- '如果前一字符是小数点或数字
- ElseIf preC = "." Or IsNumeric(preC) Then
- '如果跟随小数点
- If currC = "." Then
- '如果临时字符串中已包含小数点,那么前一数字的解析终止,创建新的临时字符串存放新的数字串
- If InStr(1, tmp(j), currC) Then
- j = j + 1
- ReDim Preserve tmp(j)
- End If
- '将小数点连接到临时字符串中
- tmp(j) = tmp(j) & currC
-
- '如果跟随正负号,那么前一数字的解析终止,创建新的临时字符串存放新的数字串
- ElseIf currC = "-" Or currC = "+" Then
- j = j + 1
- ReDim Preserve tmp(j)
- tmp(j) = tmp(j) & currC
- '如果跟随数字,那么直接连接到临时字符串中
- ElseIf IsNumeric(currC) Then
- tmp(j) = tmp(j) & currC
- End If
- End If
- End If
- Next
- '如果字符串中不包含数字,那么创建一个数组返回
- If j = -1 Then
- ReDim Preserve res(0)
- Else
- i = 0
- For j = LBound(tmp) To UBound(tmp)
- '剔除临时字符串数组中没跟数字的小数点和正负号的字串,并将符合要求的字符串转换为数值后返回
- If IsNumeric(tmp(j)) Then
- ReDim Preserve res(i)
- res(i) = tmp(j) * 1
- i = i + 1
- End If
- Next
- End If
- ExtractNumber = res
- End Function
复制代码
然后用以下公式可以如文首提到的那些牛X公式分别求正数和、负数和和全部和(需输入数组公式,测试字符串放在“G27”单元格中):
正数和: =SUM((ExtractNumber($G$27)>0)*1*ExtractNumber($G$27)) 负数和: =SUM((ExtractNumber($G$27)<0)*1*ExtractNumber($G$27)) 全部和: =SUM((ExtractNumber($G$27))
将其中包含的数值分列显示: =IF(ISERROR(INDEX(ExtractNumber($G$27),,COLUMN(A:A))),"没有了",INDEX(ExtractNumber($G$27),,COLUMN(A:A))) 向右拉,一直到显示“没有了”为止。
这个函数并没有多少实际用途,主要用来练习一下动态数组、返回值为数组的函数、字符串与数字的转换、自定义函数的应用等。另外,字符串的解析过程与编译原理中的词法分析与语法分析过程非常类似,是吗?于是借此锻炼了一下思维。
|