ExcelHome技术论坛

 找回密码
 免费注册

QQ登录

只需一步,快速开始

快捷登录

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

[分享] BOM父子结构转树形结构和树形结构转父子结构。

  [复制链接]

TA的精华主题

TA的得分主题

发表于 2018-10-29 20:03 | 显示全部楼层 |阅读模式
很久以前我做工程师的时候,有这个BOM父子结构转树形结构的需求,当时候我在excelhome找到一个代码,这个代码是哪个大师的我现在真的忘记了,好像是Lee1982,如果错了,请这位大师求你的原谅,在这里我也要感谢你的分享。这个代码我用了一阵子,就开始研究它。但是感觉太复杂了,看不懂。就自己到w3school学习了xml的一些技术,修改了一下。原来的代码如下,我自己做了点注释。(不贴了,附件里面看吧,系统字数限制没法贴)

复制代码
我修改后的代码如下 ,这个是父子转树形
  1. Sub 父子结构() '本来是父子结构转换为树形结构
  2.     Dim Arr, strA$
  3.     Dim L1 As IXMLDOMNodeList, Tt As New MSXML2.DOMDocument60
  4.     Arr = Sheets("FaS").Range("a1").CurrentRegion
  5.     Set Rt = Tt.createElement("A")    'Tt 为最顶部,增加一个Rt的子项,Rt的名字为root
  6.     Rt.setAttribute "LEVEL", 0:    Rt.setAttribute "DES", "A" 'Rt中增加四个属性
  7.     Rt.setAttribute "QTY", 1:    Rt.setAttribute "TQTY", 1
  8.     Tt.appendChild Rt: Set Rt = Nothing 'Rt死了以后,Tt中的数据还存在,所以这个应该是传地址
  9.     For i = 2 To UBound(Arr)                                   '这段循环的意义在于,讲原始数据表格的输入到Rt中,按照程序的意思,
  10.         strA = "//*[@DES='" & Arr(i, 1) & "']"        'stra为xpath语言,这句话是寻找描述为arr(i,1)的数据
  11.         Set L1 = Tt.selectNodes(strA)     'L1为一个列表,找到属性值为父亲的节点,然后创建一个RT,
  12.         Debug.Print L1.Length
  13.         For Each Lx In L1
  14.             Set Rt = Tt.createElement(Arr(i, 2))
  15.             Rt.setAttribute "LEVEL", Lx.Attributes(0).NodeValue + 1  'rt为子节点,然后将子节点按安装到L1中,L1虽然为列表,但是他里面只有一个数据.
  16.             Rt.setAttribute "DES", Arr(i, 2)
  17.             Rt.setAttribute "QTY", Arr(i, 3)
  18.             Rt.setAttribute "TQTY", Arr(i, 3) * Lx.Attributes(3).NodeValue '这一列增加了计算总数量的计算公式
  19.             Lx.appendChild Rt  'L1直接增加子几点,然后一直循环
  20.             Set Rt = Nothing
  21.         Next
  22.         
  23.     Next i
  24.     '上面的程序完成以后,其实再XML中,一颗大树已经建立好了,
  25.     Call 父子转树形(Tt)

  26. End Sub

  27. Private Function 父子转树形(Tt As MSXML2.DOMDocument60) '
  28.     Dim Brr, Ac, Abc
  29.     Set Abc = Tt.selectNodes("//*[@*]")                                         '这句话是吧所有带有属性的节点全部找出来了,,按照常规的BOM格式输出出来。
  30.     ReDim Brr(1 To Abc.Length, 1 To 4)                                          'Document 按照平时的BOM的需求全部排列出来
  31.     nn = 1
  32.     For Each Ac In Abc                                                                       '这个XML中selectnodes的好处就是 选择以后,就直接按照BOM的结构选择出来了.
  33.         Brr(nn, 1) = Ac.Attributes(0).NodeValue                               '第一列为 层级别 第二列为名字,第三列为数量
  34.         Brr(nn, 2) = Ac.Attributes(1).NodeValue                                '也就是深度优先的一种历遍方式
  35.         Brr(nn, 3) = Ac.Attributes(2).NodeValue
  36.         Brr(nn, 4) = Ac.Attributes(3).NodeValue
  37.         nn = nn + 1
  38.     Next
  39.     Range("G2").Resize(UBound(Brr), UBound(Brr, 2)) = Brr
  40. End Function

复制代码
这个是树形转父子
  1. Sub 树形结构()   '程序将树形结构转变为父子结构
  2.     Dim Arr, strA$
  3.     Dim LastNode As IXMLDOMElement, Tt As New MSXML2.DOMDocument60
  4.     Dim Rt As IXMLDOMElement, St As IXMLDOMElement '变量定义,其实VBA中 XML的技术主要有,Element,Document,和Nodelist几种对象,
  5.     Dim Nlist As IXMLDOMNodeList
  6.     Arr = Sheets("FaS").Range("G1").CurrentRegion
  7.     Set Rt = Tt.createElement("A")    'Tt 为最顶部,增加一个Rt的子项,Rt的名字为root
  8.     Rt.setAttribute "LEVEL", 0          '建立几个需要的属性
  9.     Rt.setAttribute "QTY", 1
  10.     Rt.setAttribute "TQTY", 1
  11.     Tt.appendChild Rt: Set Rt = Nothing  '建立完了以后清除内存
  12.     For i = 3 To UBound(Arr)
  13.         Set St = Tt.createElement(Arr(i, 2))   '开始循环 ,建立第一个 名字直接为名称
  14.         St.setAttribute "LEVEL", Arr(i, 1)         '建立属性值
  15.         St.setAttribute "QTY", Arr(i, 3)               ''
  16.         St.setAttribute "TQTY", Arr(i, 4)   ''
  17.         strA = "//*[@LEVEL=" & Arr(i, 1) - 1 & "]" ''在已经有的大树中选择属性值LEVEL的值为当前LEVEL-1的节点.
  18.         Set Nlist = Tt.selectNodes(strA)
  19.         Set LastNode = Nlist(Nlist.Length - 1)   '这一句最关键,需要选择最后一个选择出来的节点.
  20.         LastNode.appendChild St  '将孩子插入
  21.         Set LastNode = Nothing  '内存清除
  22.         Set St = Nothing   '内存清除
  23.     Next i
  24.     Call 树形转父子(Tt)
  25. End Sub

  26. Private Function 树形转父子(Tt As MSXML2.DOMDocument60) 'Tt As MSXML2.DOMDocument
  27.     Dim Ac As IXMLDOMElement, Abc As IXMLDOMNodeList
  28.     Set Abc = Tt.selectNodes("//*[@*]")   '将所有有属性的节点选择出来
  29.     ReDim Brr(1 To Abc.Length - 1, 1 To 3)
  30.     nn = 1
  31.     For Each Ac In Abc
  32.         If Ac.childNodes.Length > 0 Then  '如果节点有孩子,将此节点和孩子一起打印出来
  33.             For Each b In Ac.childNodes
  34.                 Brr(nn, 1) = Ac.BaseName
  35.                 Brr(nn, 2) = b.BaseName
  36.                 Brr(nn, 3) = b.Attributes(1).NodeValue
  37.                 nn = nn + 1
  38.             Next
  39.         End If
  40.     Next
  41.     Range("N2").Resize(UBound(Brr), UBound(Brr, 2)) = Brr
  42. End Function


  43. '总结:
  44. '1. 感觉其实使用XML技术,主要是向建立大树,然后利用xml 中SelectNode方法可以直接选出前序遍历的顺序.
复制代码

这个代码有个问题,就是太复杂了,xml技术很简单,但是xpath我觉的挺难的,网上的代码拿到vba中总是不好用,前段时间没事研究递归算法,发现使用递归来做这个转换是最简单的。
父子转树形的代码如下
  1. Private Arr, Level&, K&, Max&, Xrr
  2. Public Sub 父子转树递归() '本来是父子结构转换为树形结构
  3.     Dim strA$
  4.     Arr = Sheets("FaS2").Range("a1").CurrentRegion
  5.     Max = UBound(Arr)
  6.     ReDim Xrr(1 To 100, 1 To 3)
  7.     strA = "A" '父级别
  8.     Level = 0: K = 1
  9.     Call 父子递归(strA)
  10.     Sheets("FaS2").Range("G3").Resize(UBound(Xrr, 1), 3) = Xrr
  11. End Sub
  12. Private Function 父子递归(str$)
  13.     Dim i&, strB$
  14.     Level = Level + 1
  15.     For i = 2 To Max
  16.         If Arr(i, 1) = str Then
  17.             Xrr(K, 1) = Level: Xrr(K, 2) = Arr(i, 2): Xrr(K, 3) = Arr(i, 3)
  18.             K = K + 1: strB = Arr(i, 2)
  19.             Call 父子递归(strB)
  20.             Level = Level - 1
  21.         End If
  22.     Next i
  23. End Function
复制代码
树形转父子的代码如下(这个没用递归,很简单的代码)

  1. Public Sub 树转父子递归() '本来是父子结构转换为树形结构
  2.     Dim dicA As New Dictionary
  3.     Dim dicB As New Dictionary '用来去重
  4.     Dim Arr, K&, Max&, Xrr(1 To 100, 1 To 3)
  5.     Arr = Sheets("FaS2").Range("G1").CurrentRegion
  6.     dicA(0) = "A"
  7.     For i = 3 To UBound(Arr)
  8.         dicA(Arr(i, 1)) = Arr(i, 2)
  9.         s = dicA(Arr(i, 1) - 1) & Arr(i, 2)
  10.         dicB(s) = Array(dicA(Arr(i, 1) - 1), Arr(i, 2), Arr(i, 3))
  11.     Next i
  12.     it = dicB.Items
  13.     a = Application.Transpose(it)
  14.     b = Application.Transpose(a)
  15.     Range("N2").Resize(UBound(b), UBound(b, 2)) = b
  16. End Sub
复制代码
我这个人写东西不行,让我讲还能讲清楚,我现在发这个东西,就是因为前段时间有人问这个事情。帖子我找不到了,特意发出和大家分享。如果递归程序大家不理解,我觉的我也没啥好办法,我是看了网易公开课里面的斯坦福大学的 抽象编程里面的 7-11集,里面讲的非常清楚。我之前一直对递归了解的不是很清楚,看了这个视频感觉讲的非常非常的清楚。
最后附上附件




BOM.rar

42.78 KB, 下载次数: 721

评分

3

查看全部评分

TA的精华主题

TA的得分主题

 楼主| 发表于 2018-10-29 21:40 | 显示全部楼层
[广告] VBA代码宝 - VBA编程加强工具 · VBA代码随查随用  · 内置多项VBA编程加强工具       ★ 免费下载 ★      ★使用手册
自己回复,顶 啊 好贴啊

TA的精华主题

TA的得分主题

发表于 2018-10-30 13:47 | 显示全部楼层

TA的精华主题

TA的得分主题

发表于 2018-10-30 15:27 | 显示全部楼层

TA的精华主题

TA的得分主题

发表于 2019-2-10 11:34 来自手机 | 显示全部楼层

TA的精华主题

TA的得分主题

发表于 2019-2-12 15:30 | 显示全部楼层

TA的精华主题

TA的得分主题

发表于 2019-5-31 15:31 | 显示全部楼层
[广告] VBA代码宝 - VBA编程加强工具 · VBA代码随查随用  · 内置多项VBA编程加强工具       ★ 免费下载 ★      ★使用手册
你这是针对一个BOM的吧,要是多个BOM怎么转换成树形

TA的精华主题

TA的得分主题

发表于 2019-5-31 20:55 | 显示全部楼层

TA的精华主题

TA的得分主题

发表于 2019-6-1 02:51 | 显示全部楼层
[广告] VBA代码宝 - VBA编程加强工具 · VBA代码随查随用  · 内置多项VBA编程加强工具       ★ 免费下载 ★      ★使用手册
raniie1982 发表于 2019-5-31 15:31
你这是针对一个BOM的吧,要是多个BOM怎么转换成树形

多个父子BOM转换成树形BOM,请看这个帖子:

BOM表展开成树型以及物料的子系查询的相关计算
http://club.excelhome.net/thread-1443505-1-1.html
(出处: ExcelHome技术论坛)

TA的精华主题

TA的得分主题

发表于 2019-6-1 09:36 | 显示全部楼层
您需要登录后才可以回帖 登录 | 免费注册

本版积分规则

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

GMT+8, 2024-11-19 09:34 , Processed in 0.052582 second(s), 11 queries , Gzip On, MemCache On.

Powered by Discuz! X3.4

© 1999-2023 Wooffice Inc.

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

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

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