ExcelHome技术论坛

 找回密码
 免费注册

QQ登录

只需一步,快速开始

快捷登录

搜索
EH技术汇-专业的职场技能充电站 妙哉!函数段子手趣味讲函数 Excel服务器-会Excel,做管理系统 效率神器,一键搞定繁琐工作
HR薪酬管理数字化实战 Excel 2021函数公式学习大典 Excel数据透视表实战秘技 打造核心竞争力的职场宝典
让更多数据处理,一键完成 数据工作者的案头书 免费直播课集锦 ExcelHome出品 - VBA代码宝免费下载
用ChatGPT与VBA一键搞定Excel WPS表格从入门到精通 Excel VBA经典代码实践指南
查看: 5678|回复: 25

[分享] 死磕 Dictionary! 关于字典排序的 最终方案! 。。。

[复制链接]

TA的精华主题

TA的得分主题

发表于 2020-7-17 10:40 | 显示全部楼层 |阅读模式
[广告] VBA代码宝 - VBA编程加强工具 · VBA代码随查随用  · 内置多项VBA编程加强工具       ★ 免费下载 ★      ★使用手册
本帖最后由 山中老人 于 2020-7-19 09:44 编辑

最近一直在死磕Dictionary,力求将既往遇到的所有【字典】相关的问题都解决了!
实际排序中遇到的问题。

1、排序算法:
字典的排序,比教科书上的数据排序简单一些,由于Dictionary字典的特性,不需考虑移除项目后数据结构的变化。
所以,我没有使用经典的排序法,使用了最土、最易于理解的【拔尖法】,教科书上类似的称为【选择法】,但更简单。
【拔尖法】每次从教室里挑出最好(最大、最小)的学生,让他先去食堂。不停循环,直到整个教室被清空,在食堂里就得到了我们要的顺序。
分组排序: 在一些时候我们要排序的对象有很多重复值,为了更快排序,我们可以把它们作为一个组,来排序。
各位网友对排序还有更好的想法,敬请支出!

2020-7-19 更新: 上面都是我的胡言乱语!
经过测试,词典用来排序是很坑爹的,跳出这个框用还是用数组排序速度更快! 所以后面我更新的排序的代码,使用经典的【快速排序法】,体验飞一般的感觉!足以支持50万行数据排序,  如果更多数据,只好调用数据库来进行排序了!

2、异常值处理 :
由于 Dictionary使用的数据类型是Variant,支持任意数据类型,这用起来很方便,但排序时就很头痛。
一些值 如: Null、Empty、Nothing、数组、空数组
不能直接用VB 的大于(>)、小于(<) 来计算,我不得不学习数据库排序的方法,使用替代值来实现。
下面是替代方法:
Private Function ReValue(Var As Variant) As Long '替代值
    On Error GoTo Err1
    ReValue = 0 '其它值
    If VBA.IsNull(Var) Then 'Null
        ReValue = -5
    ElseIf VBA.IsEmpty(Var) Then 'Empty
        ReValue = -10
    ElseIf VBA.IsArray(Var) Then '数组
        ReValue = -9 '未初始化的数组
        ReValue = UBound(Var) - LBound(Var) + 1 '以数组 第一维 元素个数替代
    ElseIf VBA.IsObject(Var) Then
        If Var Is Nothing Then
            ReValue = -1
        End If
    End If
Err1:
End Function

主键异常值:
Dictionary不允许用数组来做主键,如果分组排序就必须让它成为主键,我也只好替代了!
Private Function CastKeyValue(Var As Variant, Optional ValueIfErr As Variant = Empty) As Variant '强制转换成主键允许的值。
    'ValueIfErr=替代值。(Var不能作为主键时替代)
    On Error GoTo Err1
    Dim d As New Scripting.Dictionary: d.Add Var, 0 '尝试作为主键添加
    Call Me.PassVal(CastKeyValue, Var)
    Exit Function
Err1:
    CastKeyValue = ValueIfErr
End Function

3、更快的遍历字典各项(各行)数据 :
Dictionary的Keys和Items方法,用起来很方便,但是我实际研究了一下,它们其实是将字典内部的数据,复制成一个数组出来。
所以是Function而不是Property。无法使用 Dictionary.Items(i)="Adc",来赋值回去。
为了避免每次使用Keys和Items时都新建一个数组出来(数据量少其实也很快),我在遍历的时候都先将数组固定,再来遍历。
Dim Keys() as Variant:  Keys = IDic.Keys() '
For i=0 To IDic.Count - 1
     Key=Keys(i)


前面已经有两个相关的帖子:

【类模块】的超级属性,无限扩展,值和对象 通用! 类的继承封装演示

类的继承、封装 演示。 Dictionary 词典 的增强!

类的继承、封装 演示。 Dictionary 词典增强,第二弹! 2维字典
















补充内容 (2020-7-20 20:01):
跌进算法这个坑,【快速排序法】堆栈溢出问题,只好弄了个深度计数,太深了就切换到【插入排序法】!

新附件在 10楼!

DevelopDictionarydd2-17.rar

67.21 KB, 下载次数: 62

DevelopDictionarydd2-19.rar

77.45 KB, 下载次数: 36

评分

3

查看全部评分

TA的精华主题

TA的得分主题

 楼主| 发表于 2020-7-17 10:51 | 显示全部楼层
本帖最后由 山中老人 于 2020-7-19 09:57 编辑

考虑到很多问题,完整的排序代码弄得很复杂,大家看看吧!排序过程中以行号(Index)为对象进行,排序完成后,才通过行号变化来改变词典顺序。

其中 Dic_ 就是要排序的字典
       Me.PassVal 传递值,避免VB中对象与非对象加不加Set的尴尬!

代码改了,跳出Dictionary的框,用经典的【快速排序法】!.........

'--排序--
Public Function Sort(Optional ByVal ColumnIndex As Variant = 0, Optional ByVal DESC As Boolean = False) As Boolean
    'ColumnIndex 指定排序依据列(列名/列号)0=主键
    'DESC=True 从大到小排序;DESC=False 从小到大排序
    On Error GoTo Err1
    Dim SubName As String, SubTxt As String
    SubName = "Sort"
    SubTxt = "排序" '


    '参数处理
    ReDim SortCArr_(0) '清空数组
    If Me.Count <= 1 Then Exit Function
    '确定排序【依据列】
    Dim Ci As Long: Ci = Me.ColumnIndex(ColumnIndex) '列定位
    If Ci < 0 Then Exit Function
    SortCArr_ = Column_(Ci) '排序【依据列】数据数组
    Dim i As Long, MaxI As Long, Index As Long
    '【依据列】索引数组
    MaxI = Dic_.Count - 1
    Dim IndexArr() As Long, IndexLen As Long: ReDim IndexArr(0 To MaxI)
    Dim ErrDic As New Scripting.Dictionary, ErrRet As Long '异常值词典(作为最小值 单独处理)
    Dim ErrIndexArr() As Long
    IndexLen = -1
    For Index = 0 To MaxI
        ErrRet = ReValue(SortCArr_(Index))
        If ErrRet < 0 Then '特殊处理
            ErrDic.Add Index, ErrRet
        Else '一般处理
            IndexLen = IndexLen + 1
            IndexArr(IndexLen) = Index
        End If
    Next
    If IndexLen >= 0 Then '一般处理 排序
        ReDim Preserve IndexArr(0 To IndexLen) '调整 索引数组
        Call qSort(IndexArr, 0, IndexLen) '【索引】快速排序
    End If
    If ErrDic.Count > 0 Then '特殊排序
        ErrIndexArr = ErrSort(ErrDic)
    End If
   
    '转移字典内容
    Dim Keys() As Variant, Key As Variant
    Keys = Dic_.Keys 'Key数组
    SortCArr_ = Dic_.Items 'Item数组
    Dic_.RemoveAll '清空字典
    '重新装填字典
    If DESC Then '降序
        If IndexLen > 0 Then '一般处理
            For i = UBound(IndexArr) To 0 Step -1
                Index = IndexArr(i)
                Dic_.Add Keys(Index), SortCArr_(Index)
            Next
        End If
        If ErrDic.Count > 0 Then '添加特殊值
            For i = UBound(ErrIndexArr) To 0 Step -1
                Index = ErrIndexArr(i)
                Dic_.Add Keys(Index), SortCArr_(Index)
            Next
        End If
    Else '升序
        If ErrDic.Count > 0 Then '添加特殊值
            For i = 0 To UBound(ErrIndexArr)
                Index = ErrIndexArr(i)
                Dic_.Add Keys(Index), SortCArr_(Index)
            Next
        End If
        If IndexLen > 0 Then '一般处理
            For i = 0 To UBound(IndexArr)
                Index = IndexArr(i)
                Dic_.Add Keys(Index), SortCArr_(Index)
            Next
        End If
    End If
    ReDim SortCArr_(0) '清空数组
    Sort = True
    Exit Function
Err1:
    Debug.Print Me.Name & "[" & Me.ClassTypeName & "]." & SubName & "  [" & SubTxt & "]错误!" & Chr(13) & Err.Description
    ReDim SortCArr_(0) '清空数组
End Function


Private Function ErrSort(Dic As Scripting.Dictionary) As Long() '特殊排序,返回Index顺序数组
    On Error GoTo Err1
    Dim Arrt() As Variant, IArr() As Long, i As Long
    Arrt = SortCArr_ '保存
    SortCArr_ = Dic.Items '排序比较值
    ReDim IArr(0 To Dic.Count - 1)
    For i = 0 To Dic.Count - 1 '内部位置
        IArr(i) = i
    Next
    Call qSort(IArr, LBound(IArr), UBound(IArr))
    SortCArr_ = Arrt '恢复
    For i = 0 To Dic.Count - 1 '转换成全局Index
        IArr(i) = Dic.Keys(IArr(i))
    Next
    ErrSort = IArr
    Exit Function
Err1:
    Debug.Print Me.Name & "[" & Me.ClassTypeName & "].[ErrSort]错误!" & Chr(13) & Err.Description
End Function


Private Function qSort(Arr() As Long, ByVal p As Long, ByVal r As Long) '数组快速排序 升序
    Dim q As Long, i As Long
    If p >= r Then Exit Function
    q = p
    For i = p To r - 1
        If SortCArr_(Arr(i)) < SortCArr_(Arr(r)) Then
            swap Arr(i), Arr(q)
            q = q + 1
        End If
    Next i
    swap Arr(q), Arr(r)
    qSort Arr, p, q - 1
    qSort Arr, q + 1, r
    Exit Function
End Function


Private Sub swap(a As Long, b As Long) '交换值
    Dim t As Long
    t = a: a = b: b = t
End Sub

TA的精华主题

TA的得分主题

发表于 2020-7-17 15:21 | 显示全部楼层
[广告] VBA代码宝 - VBA编程加强工具 · VBA代码随查随用  · 内置多项VBA编程加强工具       ★ 免费下载 ★      ★使用手册
向“老同志”学习!

TA的精华主题

TA的得分主题

发表于 2020-7-17 16:36 | 显示全部楼层
那個才是最後的版本?
先前已經下載了你的developdictionary

TA的精华主题

TA的得分主题

发表于 2020-7-17 16:57 | 显示全部楼层

TA的精华主题

TA的得分主题

 楼主| 发表于 2020-7-17 17:25 | 显示全部楼层
本帖最后由 山中老人 于 2020-7-17 17:28 编辑
xd3210 发表于 2020-7-17 15:21
向“老同志”学习!

不好意思回错了!

TA的精华主题

TA的得分主题

 楼主| 发表于 2020-7-17 17:29 | 显示全部楼层
chis3 发表于 2020-7-17 16:36
那個才是最後的版本?
先前已經下載了你的developdictionary


最后的当然是最新的版本。
目前这个帖子是最新的。 除非你对事件有要求(用developdictionary)
,否则还是用 这个(DevelopDictionarydd2)

TA的精华主题

TA的得分主题

发表于 2020-7-18 12:31 | 显示全部楼层
山中老人 发表于 2020-7-17 17:29
最后的当然是最新的版本。
目前这个帖子是最新的。 除非你对事件有要求(用developdictionary)
,否 ...

先前的sort不行嗎?
為什麼寫了這個複雜的

TA的精华主题

TA的得分主题

 楼主| 发表于 2020-7-18 13:50 | 显示全部楼层
本帖最后由 山中老人 于 2020-7-18 21:01 编辑
chis3 发表于 2020-7-18 12:31
先前的sort不行嗎?
為什麼寫了這個複雜的

如果纯文本、数字的值,几行数据,没有问题。如果碰到更复杂、更多数据就没有办法了!

总有一些你意料之外的数据出现,例如你要排序的数据中冒出几个Nothing或Null, 你就歇菜了!
所有只好(异常值处理 )把每种可能都设计出来。

这次改进,将实际可用的排序最大行从500提升到2000行。后面更改算法,使用更复杂的分级排序,能将排序能力提升到20000行。


TA的精华主题

TA的得分主题

 楼主| 发表于 2020-7-19 09:52 | 显示全部楼层
[广告] VBA代码宝 - VBA编程加强工具 · VBA代码随查随用  · 内置多项VBA编程加强工具       ★ 免费下载 ★      ★使用手册
本帖最后由 山中老人 于 2020-7-20 20:04 编辑
chis3 发表于 2020-7-18 12:31
先前的sort不行嗎?
為什麼寫了這個複雜的

呵呵!  有点入魔的赶脚!


老在字典这个圈子里转,换用经典的【快速排序法】,排序速度立马提升十倍。
跌进算法这个坑,【快速排序法】堆栈溢出问题,只好弄了个深度计数,太深了就切换到【插入排序法】!
新附件!





DevelopDictionarydd2-20.rar

82.49 KB, 下载次数: 61

您需要登录后才可以回帖 登录 | 免费注册

本版积分规则

手机版|关于我们|联系我们|ExcelHome

GMT+8, 2024-11-16 11:33 , Processed in 0.041876 second(s), 11 queries , Gzip On, MemCache On.

Powered by Discuz! X3.4

© 1999-2023 Wooffice Inc.

沪公网安备 31011702000001号 沪ICP备11019229号-2

本论坛言论纯属发表者个人意见,任何违反国家相关法律的言论,本站将协助国家相关部门追究发言者责任!     本站特聘法律顾问:李志群律师

快速回复 返回顶部 返回列表