Excel VBA 字母组合/自定义函数组合数组元素/数组元素花样组合

Excel VBA 字母组合/自定义函数组合数组元素/数组元素花样组合

编码文章call10242024-12-19 11:42:2726A+A-

???本文于2023年5月5日首发于本人同名公众号:Excel活学活用,更多文章敬请关注

☆本期内容概要☆

  • 自定义函数-数组元素组合
  • 正则表达式:字母、数字
  • 解决问题的思路

今天无意间上了ExcelHome论坛,看到有个求助的贴子:

老朋友有没有想起什么?对了,我以前分享过一篇文章就是讲组合的:Excel VBA 数组应用/核算项目代码组合/VBA代码优化/AI辅助

我想这可以弄一下嘛。于是下载来附件一看,需求还比较特殊:

不管怎么样,还是来试一试吧,看看能不能帮到楼主。

需求分析及实现过程:

一、所有组合:

这个好办,用我们的自定义函数CombineArray,于是直接用它来组合,先测试一下存到数组里看看,哪知道还是图样图深破了,直接死机,半天不动。无奈强行退出重来,这次把字母区域选少一点,可以正常组合,这才放下心来。

但这样的速度明显不行啊,于是把代码再检查一遍,估计问题出在后半段数组排序过程:

上面这段给删掉,果然很快就组合好了。

二、固定长度组合:

首先想到的是,循环遍历数组,把指定长度的元素给提取出来,存到另一个数组中,于是测试了一下,没有问题:

   arrTem = CombineArray(arrResult, "")
    Erase arrResult
    For i = LBound(arrTem) To UBound(arrTem)
        If Len(arrTem(i)) = xLen Then
            ReDim Preserve arrResult(k)
            arrResult(k) = arrTem(i)
            k = k + 1
        End If
    Next

代码简析:数组arrResult()在前面存有数据,现在把它清空再使用。xLen是定义的一个变量,用来存放输入的组合长度。接着循环arrTem(),把长度等于xLen的元素存入arrResult(),这里采用ReDim Preserve的方法。(这段代码后来是不用了,采用另外的方法了。)

后来一想,这样是不是有点浪费资源?可不可以在组合过程中就直接得出固定长度的组合?于是就请教AI,它给了几段代码,测试不起作用,于是就另想它法了。

最后想到的方案是,在全部组合过程中,检查一下组合的元素长度,如果等于给定长度就存下来,否则就丢弃。最终修改后的自定义组合函数:

Function CombineArr(arr As Variant, Optional delimiter As String = "/", Optional length As Integer = 0) As Variant
    '将一个数组中的所有元素进行组合
    Dim n As Long, i As Long, j As Long, k As Long, count As Long
    Dim result(), temp As String
    n = UBound(arr) - LBound(arr) + 1 ' 计算数组长度
    count = 2 ^ n - 1 ' 计算组合数
    For i = 1 To count ' 遍历所有组合
        temp = ""
        For j = 0 To n - 1
            If i And 2 ^ j Then ' 根据位运算判断元素是否参与组合
                temp = temp & arr(LBound(arr) + j) & delimiter ' 将元素值拼接为字符串
            End If
        Next
        temp = Left(temp, Len(temp) - Len(delimiter))  ' 去掉字符串末尾的分隔符
        If length > 0 Then
            If Len(temp) = length + Len(delimiter) * (length - 1) Then
                ReDim Preserve result(r)
                result(r) = temp
                r = r + 1
            End If
        Else
            ReDim Preserve result(r)
            result(r) = temp
            r = r + 1
        End If
    Next
    CombineArr = result ' 返回结果数组
End Function

代码简析:

这个自定义函数有两个参数:分隔符(delimiter),默认为“/”,在今天的应用中,我们给它的值为空,就没有分隔符了,直接连到一起;组合元素长度(length ),默认为0,表示所有组合,但今天的应用中,它不能小于2。

通过位运算来取得组合元素temp(位运算是一种算法,具体怎么回事有待研究学习)。

判断函数的参数length,如果大于0,则继续判断temp的长度,这里要考虑分隔符的长度。符合长度条件的存入数组。如果length=0,则输出所有组合。

三、开头与结尾不可以是“1,3,5,6,8”,我们可以理解为不能是数字。

这个我们可以再分析,它的意思可以表述为:首尾必须是字母,也就是说,这个元素长度至少为2,至少2个字母,因为是组合,在组合时不考虑顺序,所以,如果包括数字的元素,如果开头、结尾是数字的,我们要把它放到字母中间,这样的元素也是符合条件的。

我们别自己费脑筋了,请教一下AI吧:

有了这段代码做参考,我理出了实现思路:

1、循环遍历数组,使用正则表达式来判断元素是否包括两个及以上字母;

2、再利用正则表达达判断元素的开头、结尾字符是否为数字;

3、如果是数字的,就从开头或结尾开始循环,找到第一个非数字的字符,将它与开头或结尾的数字互换位置。这个过程有点小复杂,我把字符串元素拆分成单个字符存到数组,再循环数组来调换位置,再连接成字符串。这里又定义了两个函数:

(1)字符串拆分成单个字符,存入数组

Function strSplit(str As Variant) As Variant
    Dim arr()
    For i = 1 To Len(str)
        ReDim Preserve arr(i - 1)
        arr(i - 1) = Mid(str, i, 1)
    Next
    strSplit = arr
End Function

代码简析:从1开始循环字符串长度,依次截取字符,存入数组。

(2)调整字符位置的函数,连带舍弃仅有1个、0个字母的元素。

Function AdjustElements(arr As Variant) As Variant
    Dim arrTem()
    Dim regEx As Object
    Dim NewElem As String
    Dim arrResult()
    Dim strA As String, strB As String, strT As String
    Set regEx = CreateObject("VBScript.RegExp")
    With regEx
        .Pattern = "[a-zA-Z].*[a-zA-Z]"
        .Global = True
    End With
    For i = LBound(arr) To UBound(arr)
        regEx.Pattern = "[a-zA-Z].*[a-zA-Z]"
        If regEx.test(arr(i)) Then
            strA = Left(arr(i), 1): strB = Right(arr(i), 1)
            regEx.Pattern = "[0-9]"
            If regEx.test(strA) Then
                arrTem = strSplit(arr(i))
                For j = LBound(arrTem) To UBound(arrTem)
                    If Not regEx.test(arrTem(j)) Then
                        strT = arrTem(j)
                        arrTem(j) = strA
                        arrTem(LBound(arrTem)) = strT
                        Exit For
                    End If
                Next
                If regEx.test(strB) Then
                    For j = UBound(arrTem) To LBound(arrTem) Step -1
                        If Not regEx.test(arrTem(j)) Then
                            strT = arrTem(j)
                            arrTem(j) = strB
                            arrTem(UBound(arrTem)) = strT
                            Exit For
                        End If
                    Next
                End If
                NewElem = ""
                For j = LBound(arrTem) To UBound(arrTem)
                    NewElem = NewElem & arrTem(j)
                Next
                ReDim Preserve arrResult(k)
                arrResult(k) = NewElem
                k = k + 1
            ElseIf regEx.test(strB) Then
                arrTem = strSplit(arr(i))
                For j = UBound(arrTem) To LBound(arrTem) Step -1
                    If Not regEx.test(arrTem(j)) Then
                        strT = arrTem(j)
                        arrTem(j) = strB
                        arrTem(UBound(arrTem)) = strT
                        Exit For
                    End If
                Next
                NewElem = ""
                For j = LBound(arrTem) To UBound(arrTem)
                    NewElem = NewElem & arrTem(j)
                Next
                ReDim Preserve arrResult(k)
                arrResult(k) = NewElem
                k = k + 1
            Else
                ReDim Preserve arrResult(k)
                arrResult(k) = arr(i)
                k = k + 1
            End If
        End If
    Next
    AdjustElements = arrResult
End Function

代码简析:在前面的实现思路就基本阐述清楚了,好象也没什么可说的。有一个地方可以提一下,就是再次连接字符串的时候:

 NewElem = ""
 For j = LBound(arrTem) To UBound(arrTem)
     NewElem = NewElem & arrTem(j)
  Next

可以用另一种方法,代码简洁一点:

NewElem = Replace(Join(arrTem), " ", "")

原来我是用Join方法连接的,但看到中间有空格,也没多想,就换了循环数组的方法,后来想到这种方法,原代码就懒得改了。

四、最后完成代码执行

(一)新建一个过程CombineL()组合表格中的元素

Sub CombineL()
    Dim arr(), arrResult(), arrTem()
    arr = Sheet1.Range("c9:c25")
    arrResult = FlattenArray(arr)
    arrTem = CombineArr(arrResult, "", xLen)
    arrResult = AdjustElements(arrTem)
    If xLen = 3 Then
        Sheet1.Range("E9").Resize(UBound(arrResult) + 1, 1) = Application.WorksheetFunction.Transpose(arrResult)
    ElseIf xLen = 5 Then
        Sheet1.Range("F9").Resize(UBound(arrResult) + 1, 1) = Application.WorksheetFunction.Transpose(arrResult)
    Else
        Sheet1.Range("G9").Resize(UBound(arrResult) + 1, 1) = Application.WorksheetFunction.Transpose(arrResult)
     End If
End Sub

代码简析:

1、读取待组合区域的字符,存入arr(),转为一维数组

2、通过自定义函数CombineArr组合数组元素,存入arrTem()

3、通过自定义函数AdjustElements再次处理数组元素,只有1个或0个字母的元素将被舍弃,开头结尾都调整成字母。结果存入arrResult数组

4、根据输入的组合元素长度,存到表格的相应单元格。

(二)在表格界面增加一个命令按钮CmdCombine(组合),输入代码:

Private Sub CmdCombine_Click()
    xLen = Val(InputBox("请输入组合长度:", "组合长度", 3))
    If xLen < 2 Then
        MsgBox "组合元素长度必须大于等于2!"
        Exit Sub
    End If
    Call CombineL
End Sub

(三)点击组合按钮,输入组合元素长度,结果就出来啦:

好,今天就分享到这,由于时间仓促,代码可能存在错误,欢迎批评指正!请大家点赞、留言、分享,谢谢大家,我们下期再会。


☆猜你喜欢☆

Excel VBA 这样酷炫的日期控件,你不想要吗?

Excel 公式函数/数据透视表/固定资产折旧计提表!

Excel VBA 自定义函数/数组字段定位/数组字段排序

Excel 功能/公式函数/VBA/多种姿势处理重复值

Excel VBA 最简单的收发存登记系统

Excel 公式函数/查找函数之LOOKUP

Excel VBA 文件批量改名

Excel 公式函数/数据验证/动态下拉列表

Excel VBA 输入逐步提示/TextBox+ListBox

Excel 基础功能【数据验证】,你会怎么用?


????本文于2023年5月5日首发于本人同名公众号:Excel活学活用,更多文章敬请关注

点击这里复制本文地址 以上内容由文彬编程网整理呈现,请务必在转载分享时注明本文地址!如对内容有疑问,请联系我们,谢谢!
qrcode

文彬编程网 © All Rights Reserved.  蜀ICP备2024111239号-4