the problem is a derivation from the following questions :
https://www.mrexcel.com/board/threads/make-a-formula-to-design-a-hierarchy-from-a-parent-child-relation.1133448/
http://www.vbaexpress.com/forum/showthread.php?67327-VBA-to-make-formula-for-parent-child
...
https://www.mrexcel.com/board/threads/build-parent-child-hierarchy.966430/
generating a tree like structure using parent-child ID's with VBA
...
Let's take the first question's example and work around that :
Parent | Child |
---|---|
Pizza | Margerita |
Sandwich | HotDog |
Root | Pizza |
Margerita | Cheese |
Pizza | Sea |
Sea | Tuna |
Burger | Bun |
HotDog | Sausage |
Sea | Shrimp |
Root | Sandwich |
Sandwich | Burger |
Burger | Steack |
Burger | Tomato |
Burger | Cucumber |
HotDog | Bun |
Root | Offer |
Offer | Pizza |
Offer | Sandwich |
Margerita | Tomato |
Margerita | Oliva |
Sea | Tomato |
Sea | Cheese |
The original question was to turn this parent/child table into a proper hierarchy :
Level 1 | Level 2 | Level 3 | Level 4 |
---|---|---|---|
Root | Pizza | Margerita | Tomato |
Oliva | |||
Cheese | |||
Sea | Tuna | ||
Shrimp | |||
Tomato | |||
Cheese | |||
Sandwich | Burger | Steack | |
Bun | |||
Tomato | |||
Cucumber | |||
HotDog | Sausage | ||
Bun | |||
Offer | Pizza | ||
Sandwich |
Notice the 'Offer' entry and the two 'Pizza' and 'Sandwich' both related to 'Root' and 'Offer'.
However I'd like to turn each line into a flat hierarchy (the "longer" the better) :
Parent | Child | Level 1 | Level 2 | Level 3 | Level 4 | Level 5 |
---|---|---|---|---|---|---|
Pizza | Margerita | Root | Offer | Pizza | Margerita | |
Sandwich | HotDog | Root | Offer | Sandwich | HotDog | |
Root | Pizza | Root | Pizza | |||
Margerita | Cheese | Root | Offer | Pizza | Margerita | Cheese |
Pizza | Sea | Root | Offer | Pizza | Sea | |
Sea | Tuna | Root | Offer | Pizza | Sea | Tuna |
Burger | Bun | Root | Offer | Sandwich | Burger | Bun |
HotDog | Sausage | Root | Offer | Sandwich | HotDog | Sausage |
Sea | Shrimp | Root | Offer | Pizza | Sea | Shrimp |
Root | Sandwich | Root | Sandwich | |||
Sandwich | Burger | Root | Offer | Sandwich | Burger | |
Burger | Steack | Root | Offer | Sandwich | Burger | Steack |
Burger | Tomato | Root | Offer | Sandwich | Burger | Tomato |
Burger | Cucumber | Root | Offer | Sandwich | Burger | Cucumber |
HotDog | Bun | Root | Offer | Sandwich | HotDog | Bun |
Root | Offer | Root | Offer | |||
Offer | Pizza | Root | Offer | Pizza | ||
Offer | Sandwich | Root | Offer | Sandwich | ||
Margerita | Tomato | Root | Offer | Pizza | Margerita | Tomato |
Margerita | Oliva | Root | Offer | Pizza | Margerita | Oliva |
Sea | Tomato | Root | Offer | Pizza | Sea | Tomato |
Sea | Cheese | Root | Offer | Pizza | Sea | Cheese |
Please note that the original parent/child table has not to be modified/sorted. Each "child" path is to be flattened/expanded into a list of "folders".
The purpose is then to feed this flattened hierarchy into a Power Pivot Table.
I've made some trial and error based on the code extracted from the related questions, but the result is not satisfying :
Parent | Child | Level 1 | Level 2 | Level 3 | Level 4 | Level 5 |
---|---|---|---|---|---|---|
Pizza | Margerita | Root | Pizza | Margerita | 'Offer' ? | |
Sandwich | HotDog | Root | Sandwich | HotDog | 'Offer' ? | |
Root | Pizza | Root | Pizza | OK | ||
Margerita | Cheese | Root | Pizza | Margerita | Cheese | 'Offer' ? |
Pizza | Sea | Root | Pizza | Sea | 'Offer' ? | |
Sea | Tuna | Root | Pizza | Sea | Tuna | 'Offer' ? |
Burger | Bun | Root | Sandwich | Burger | Bun | 'Offer' ? |
HotDog | Sausage | Root | Sandwich | HotDog | Sausage | 'Offer' ? |
Sea | Shrimp | Root | Pizza | Sea | Shrimp | 'Offer' ? |
Root | Sandwich | Root | Sandwich | OK | ||
Sandwich | Burger | Root | Sandwich | Burger | 'Offer' ? | |
Burger | Steack | Root | Sandwich | Burger | Steack | 'Offer' ? |
Burger | Tomato | Root | Sandwich | Burger | Tomato | 'Offer' ? |
Burger | Cucumber | Root | Sandwich | Burger | Cucumber | 'Offer' ? |
HotDog | Bun | Root | Sandwich | HotDog | Bun | 'Offer' ? |
Root | Offer | Root | Offer | OK | ||
Offer | Pizza | Root | Offer | Pizza | OK | |
Offer | Sandwich | Root | Offer | Sandwich | OK | |
Margerita | Tomato | Root | Pizza | Margerita | Tomato | 'Offer' ? |
Margerita | Oliva | Root | Pizza | Margerita | Oliva | 'Offer' ? |
Sea | Tomato | Root | Pizza | Sea | Tomato | 'Offer' ? |
Sea | Cheese | Root | Pizza | Sea | Cheese | 'Offer' ? |
And using the example above, I'm stuck on the 'Pizza' and 'Sandwich' related to either 'Root' or 'Offer', despite the later being the obvious answer.
As a matter of fact, the "depth" of each line's hierarchy should be known to reserve enough "levels".
And now the code
' Option Explicit
' https://www.appsloveworld.com/vba/100/7/filtering-2d-arrays-in-excel-vba
' https://stackoverflow.com/a/43047962/3069988
Function Filter2DArray(ByVal i_aSrc, ByVal i_nCol As Long, ByVal i_sFind As String, ByVal i_bTitle As Boolean)
Dim l_aOut As Variant
Dim l_aKeys As Variant
Dim l_oDic As Variant
'Dim l_oDic As Scripting.Dictionary
Dim l_nRow As Long
Dim l_ncol As Long
Dim l_bChk As Boolean
Dim l_nVal As Double
On Error Resume Next
' https://excelmacromastery.com/vba-dictionary/
Set l_oDic = CreateObject("Scripting.Dictionary")
l_oDic.CompareMode = vbBinaryCompare ' case sensitive
'l_oDic.CompareMode = vbTextCompare ' case insensitive
i_nCol = i_nCol + LBound(i_aSrc, 2) - 1
l_bChk = (InStr("><=", Left(i_sFind, 1)) > 0)
' Scan all rows, add row number when string found
For l_nRow = LBound(i_aSrc, 1) - i_bTitle To UBound(i_aSrc, 1)
If l_bChk Then
l_nVal = CDbl(i_aSrc(l_nRow, i_nCol))
If Evaluate(l_nVal & i_sFind) Then
l_oDic.Add l_nRow, ""
End If
Else
'If UCase(i_aSrc(l_nRow, i_nCol)) Like UCase("*" & i_sFind & "*") Then
If UCase(i_aSrc(l_nRow, i_nCol)) Like UCase(i_sFind) Then
l_oDic.Add l_nRow, ""
End If
End If
Next
' If row numbers present, string found
If l_oDic.Count > 0 Then
' Get row numbers
l_aKeys = l_oDic.Keys
' Create array of found rows and all columns
ReDim l_aOut(LBound(i_aSrc, 1) To UBound(l_aKeys) + LBound(i_aSrc, 1) - i_bTitle, LBound(i_aSrc, 2) To UBound(i_aSrc, 2))
' All found rows
For l_nRow = LBound(i_aSrc, 1) - i_bTitle To UBound(l_aKeys) + LBound(i_aSrc, 1) - i_bTitle
' All source columns
For l_ncol = LBound(i_aSrc, 2) To UBound(i_aSrc, 2)
' Copy row
l_aOut(l_nRow, l_ncol) = i_aSrc(l_aKeys(l_nRow - LBound(i_aSrc, 1) + i_bTitle), l_ncol)
Next
Next
If i_bTitle Then
' All source columns
For l_ncol = LBound(i_aSrc, 2) To UBound(i_aSrc, 2)
' Copy title row
l_aOut(LBound(i_aSrc, 1), l_ncol) = i_aSrc(LBound(i_aSrc, 1), l_ncol)
Next
End If
End If
' Return result
Filter2DArray = l_aOut
End Function
Sub FlattenParentChild()
Dim l_oHiera As Variant
Dim l_aHiera As Variant
Dim l_aChild As Variant
Dim l_aCurr() As Variant
Dim l_aTemp() As Variant
Dim l_iRow, l_iCol As Long
Dim l_nRow, l_nCol As Long
Dim l_nMin, l_nMax As Long
Dim l_nOut As Long
Dim l_sTemp As String
' https://excelmacromastery.com/vba-arraylist/
Set l_aChild = CreateObject("System.Collections.ArrayList")
' Extract stacked Parent/Child table (position specific)
Set l_oHiera = ActiveSheet.Range("A1:B" & [A65000].End(xlUp).Row)
' Get position
l_nRow = l_oHiera.Row - 1
l_nCol = l_oHiera.Column - 1
' Show range
l_oHiera.Select
' Get Parent/Child pairs
l_aHiera = l_oHiera.Value
' Out at column
l_nOut = l_nCol + 4
' Get list of Childs
l_aChild = Application.Transpose(Application.Index(l_aHiera, 0, 2))
' Application.Index(array, 0, col)
' Application.Index(array, row, 0)
' Stop updating screen (may increase speed by alot)
'Application.ScreenUpdating = False
' Scan all parent/child pairs
For l_iRow = LBound(l_aHiera) To UBound(l_aHiera)
' Copy current Parent/Child
l_aTemp = Application.Index(l_aHiera, l_iRow, 0)
' Step depth, max 5
For l_nMin = 1 To 5
' Place current Parent/Child (position specific)
ActiveSheet.Range( _
Cells(l_nRow + l_iRow, l_nOut), _
Cells(l_nRow + l_iRow, l_nOut + 1)) = l_aTemp
' Get Parent
l_sTemp = l_aTemp(1)
' Check that there is a Child
If Not IsError(Application.Match(l_sTemp, l_aChild, 0)) Then
' Get rows where Parent is Child
l_aCurr = Filter2DArray(l_aHiera, 2, l_sTemp, False)
l_nMax = UBound(l_aCurr)
If 0 < l_nMax Then
' Move flattened hierarchy to the right
ActiveSheet.Range( _
Cells(l_nRow + l_iRow, l_nOut + 1), _
Cells(l_nRow + l_iRow, l_nOut + 1 + l_nMin)) = _
ActiveSheet.Range( _
Cells(l_nRow + l_iRow, l_nOut), _
Cells(l_nRow + l_iRow, l_nOut + l_nMin)).Value
While 0 < l_nMax
' Extract row
l_aTemp = Application.Index(l_aCurr, l_nMax, 0)
' Place new Parent/Child (position specific)
ActiveSheet.Range( _
Cells(l_nRow + l_iRow, l_nOut), _
Cells(l_nRow + l_iRow, l_nOut + 1)) = l_aTemp
' RECURSIVE LOOP ???
' Get new Parent
l_sTemp = l_aTemp(1)
' Check that there is no Child (hence Root)
If IsError(Application.Match(l_sTemp, l_aChild, 0)) Then
Exit For
End If
' Next loop
l_nMax = l_nMax - 1
Wend
End If
Else
Exit For
End If
Next
Next
Application.ScreenUpdating = True
End Sub
Regards.