Написал программу для сбора иерархии объектов из двух изначальных столбцов, где в каждой строке отображён родительский и дочерний элемент.
Предусмотрена следующая логика: все элементы в столбце дочерних элементов должны быть уникальными. Два родителя недопустимы. Программа находит элементы, у которых нет дочерних элементов, и отталкиваясь от них выстраивает полностью ветку иерархии.
Пример во вложении, код закомментирован.
[vba]
Код
Option Explicit Option Base 1
Sub Get_Hierarchy() '------------------------------------ ' Author: Roman "Rioran" Voronov ' Date: the 3-rd of September, 2015 ' Feedback: voronov_rv@mail.ru '------------------------------------ ' Programm allows user to define relationships between elements in table format ' where source data consists from two columns with parent and child elements '------------------------------------ ' Программа предназначена для преобразования данных из двух столбцов с зависимостями подчинения ' в табличное представление, где каждый столбец отображает уровень иерархии. '------------------------------------ Dim arr_Data ' Двумерный массив с исходными данными Dim arr_Branches ' Одномерный массив с ветками дерева Dim arr_Temp ' Одномерный массив, равный одной ветке дерева Dim arr_Result ' Двумерный массив с выводимыми результатами
Dim i&, j&, k& ' Ситуативные итераторы Dim lng_DataRows& ' Количество строк в исходных данных (arr_Data) Dim lng_Levels& ' Количество уровней подчинений в дереве иерархии Dim lng_ResultRows& ' Количество строк в результирующем массиве Dim b_Check As Byte ' Значение для проверок. 1 = True, 0 = False.
' Очистка предыдущих результатов, если есть With Cells(1, 4) If .Value <> "" Then i = .End(xlToRight).Column j = .End(xlDown).Row Range(Cells(1, 4), Cells(j, i)).Value = "" End If End With
' Проверяем корректность структуры иерархии - у каждого узла может быть только 1 родитель For i = 1 To lng_DataRows For j = i + 1 To lng_DataRows If arr_Data(i, 2) = arr_Data(j, 2) Then MsgBox "Есть элементы с двумя главенствующими - иерархия нарушена. Программа прервана." Exit Sub End If Next j Next i
' Находим уникальные окончания веток дерева иерархии k = 1 For i = 1 To lng_DataRows b_Check = 0 ' Обновляем маркер проверки For j = 1 To UBound(arr_Branches) ' Перебираем уже собранные уникальные значения If arr_Data(i, 2) = arr_Branches(j)(1) Then ' Если одно из них совпадает с текущим b_Check = 1 ' Устанавливаем маркеру значение на пропуск следующего блока Exit For End If Next j If 1 - b_Check Then ' Если у маркера значение ноль For j = 1 To lng_DataRows ' Если у узла есть дочерний узел - он нам не подходит If arr_Data(i, 2) = arr_Data(j, 1) Then b_Check = 1 Exit For End If Next j ' Если проверки пройдены - добавляем конечный узел ветки и его родителя If 1 - b_Check Then ' В конце массива arr_Branches намеренно 2 одинаковых ветки arr_Branches(k) = Array(arr_Data(i, 2), arr_Data(i, 1)) k = k + 1 ReDim Preserve arr_Branches(k) arr_Branches(k) = Array(arr_Data(i, 2), arr_Data(i, 1)) End If End If Next i ReDim Preserve arr_Branches(k - 1) ' Отсекаем излишнюю дублированную ветку
' Находим пути иерархии каждого конечного узла. lng_ResultRows = UBound(arr_Branches) For i = 1 To lng_ResultRows ReDim arr_Temp(2) arr_Temp = arr_Branches(i) b_Check = 1 k = 1 ' Во временный массив собираем ветку целиком Do While b_Check For j = 1 To lng_DataRows If arr_Data(j, 2) = arr_Temp(k) Then k = k + 1 ReDim Preserve arr_Temp(k) arr_Temp(k) = arr_Data(j, 1) Exit For End If Next j ' Если конечный родительский узел найден - запрашиваем выход из цикла If j = lng_DataRows + 1 Then b_Check = 0 Loop arr_Branches(i) = arr_Temp If lng_Levels < UBound(arr_Temp) Then lng_Levels = UBound(arr_Temp) Next i
' Вывод результатов. Каждая ветка прописывается направо от первого столбца arr_Result ReDim arr_Result(lng_ResultRows, lng_Levels) For i = 1 To lng_ResultRows k = 1 ' Для перебора столбцов результата For j = UBound(arr_Branches(i)) To 1 Step -1 arr_Result(i, k) = arr_Branches(i)(j) k = k + 1 Next j Next i ' Вывод заголовка For i = 1 To lng_Levels Cells(1, 3 + i).Value = "Level " & (i - 1) Next i ' Вывод результатов. Cells(2, 4).Resize(lng_ResultRows, lng_Levels).Value = arr_Result End Sub
[/vba]
Всем привет и хорошего настроения!
Написал программу для сбора иерархии объектов из двух изначальных столбцов, где в каждой строке отображён родительский и дочерний элемент.
Предусмотрена следующая логика: все элементы в столбце дочерних элементов должны быть уникальными. Два родителя недопустимы. Программа находит элементы, у которых нет дочерних элементов, и отталкиваясь от них выстраивает полностью ветку иерархии.
Пример во вложении, код закомментирован.
[vba]
Код
Option Explicit Option Base 1
Sub Get_Hierarchy() '------------------------------------ ' Author: Roman "Rioran" Voronov ' Date: the 3-rd of September, 2015 ' Feedback: voronov_rv@mail.ru '------------------------------------ ' Programm allows user to define relationships between elements in table format ' where source data consists from two columns with parent and child elements '------------------------------------ ' Программа предназначена для преобразования данных из двух столбцов с зависимостями подчинения ' в табличное представление, где каждый столбец отображает уровень иерархии. '------------------------------------ Dim arr_Data ' Двумерный массив с исходными данными Dim arr_Branches ' Одномерный массив с ветками дерева Dim arr_Temp ' Одномерный массив, равный одной ветке дерева Dim arr_Result ' Двумерный массив с выводимыми результатами
Dim i&, j&, k& ' Ситуативные итераторы Dim lng_DataRows& ' Количество строк в исходных данных (arr_Data) Dim lng_Levels& ' Количество уровней подчинений в дереве иерархии Dim lng_ResultRows& ' Количество строк в результирующем массиве Dim b_Check As Byte ' Значение для проверок. 1 = True, 0 = False.
' Очистка предыдущих результатов, если есть With Cells(1, 4) If .Value <> "" Then i = .End(xlToRight).Column j = .End(xlDown).Row Range(Cells(1, 4), Cells(j, i)).Value = "" End If End With
' Проверяем корректность структуры иерархии - у каждого узла может быть только 1 родитель For i = 1 To lng_DataRows For j = i + 1 To lng_DataRows If arr_Data(i, 2) = arr_Data(j, 2) Then MsgBox "Есть элементы с двумя главенствующими - иерархия нарушена. Программа прервана." Exit Sub End If Next j Next i
' Находим уникальные окончания веток дерева иерархии k = 1 For i = 1 To lng_DataRows b_Check = 0 ' Обновляем маркер проверки For j = 1 To UBound(arr_Branches) ' Перебираем уже собранные уникальные значения If arr_Data(i, 2) = arr_Branches(j)(1) Then ' Если одно из них совпадает с текущим b_Check = 1 ' Устанавливаем маркеру значение на пропуск следующего блока Exit For End If Next j If 1 - b_Check Then ' Если у маркера значение ноль For j = 1 To lng_DataRows ' Если у узла есть дочерний узел - он нам не подходит If arr_Data(i, 2) = arr_Data(j, 1) Then b_Check = 1 Exit For End If Next j ' Если проверки пройдены - добавляем конечный узел ветки и его родителя If 1 - b_Check Then ' В конце массива arr_Branches намеренно 2 одинаковых ветки arr_Branches(k) = Array(arr_Data(i, 2), arr_Data(i, 1)) k = k + 1 ReDim Preserve arr_Branches(k) arr_Branches(k) = Array(arr_Data(i, 2), arr_Data(i, 1)) End If End If Next i ReDim Preserve arr_Branches(k - 1) ' Отсекаем излишнюю дублированную ветку
' Находим пути иерархии каждого конечного узла. lng_ResultRows = UBound(arr_Branches) For i = 1 To lng_ResultRows ReDim arr_Temp(2) arr_Temp = arr_Branches(i) b_Check = 1 k = 1 ' Во временный массив собираем ветку целиком Do While b_Check For j = 1 To lng_DataRows If arr_Data(j, 2) = arr_Temp(k) Then k = k + 1 ReDim Preserve arr_Temp(k) arr_Temp(k) = arr_Data(j, 1) Exit For End If Next j ' Если конечный родительский узел найден - запрашиваем выход из цикла If j = lng_DataRows + 1 Then b_Check = 0 Loop arr_Branches(i) = arr_Temp If lng_Levels < UBound(arr_Temp) Then lng_Levels = UBound(arr_Temp) Next i
' Вывод результатов. Каждая ветка прописывается направо от первого столбца arr_Result ReDim arr_Result(lng_ResultRows, lng_Levels) For i = 1 To lng_ResultRows k = 1 ' Для перебора столбцов результата For j = UBound(arr_Branches(i)) To 1 Step -1 arr_Result(i, k) = arr_Branches(i)(j) k = k + 1 Next j Next i ' Вывод заголовка For i = 1 To lng_Levels Cells(1, 3 + i).Value = "Level " & (i - 1) Next i ' Вывод результатов. Cells(2, 4).Resize(lng_ResultRows, lng_Levels).Value = arr_Result End Sub
Sub ertert() 'with using reference "MS Scripting Runtime" Dim x, i&, d As New Dictionary Application.ScreenUpdating = False With Range("A2").CurrentRegion x = .Offset(1).Resize(.Rows.Count - 1).Value End With
d.CompareMode = 1 For i = 1 To UBound(x) If d.Exists(x(i, 1)) Then d.Item(x(i, 1)) = d.Item(x(i, 1)) & "~" & x(i, 2) Else d.Item(x(i, 1)) = x(i, 2) End If Next i
Cells(2, 4) = d.Keys(0) rty d, d.Keys(0), 2, 4 Application.ScreenUpdating = True End Sub
[/vba] [vba]
Код
Sub rty(d As Dictionary, k, n&, m&) Dim s, i& s = Split(d.Item(k), "~") For i = 0 To UBound(s) If d.Exists(s(i)) Then m = m + 1: Cells(n, m) = s(i) rty d, s(i), n, m Else Cells(n, m + 1) = s(i): n = n + 1 End If Next i m = m - 1 End Sub
[/vba]
в качестве варианта
[vba]
Код
Sub ertert() 'with using reference "MS Scripting Runtime" Dim x, i&, d As New Dictionary Application.ScreenUpdating = False With Range("A2").CurrentRegion x = .Offset(1).Resize(.Rows.Count - 1).Value End With
d.CompareMode = 1 For i = 1 To UBound(x) If d.Exists(x(i, 1)) Then d.Item(x(i, 1)) = d.Item(x(i, 1)) & "~" & x(i, 2) Else d.Item(x(i, 1)) = x(i, 2) End If Next i
Cells(2, 4) = d.Keys(0) rty d, d.Keys(0), 2, 4 Application.ScreenUpdating = True End Sub
[/vba] [vba]
Код
Sub rty(d As Dictionary, k, n&, m&) Dim s, i& s = Split(d.Item(k), "~") For i = 0 To UBound(s) If d.Exists(s(i)) Then m = m + 1: Cells(n, m) = s(i) rty d, s(i), n, m Else Cells(n, m + 1) = s(i): n = n + 1 End If Next i m = m - 1 End Sub
Интересное решение! Замечу, что для упорядоченных данных твой макрос работает хорошо, если у всех элементов строго по одному родителю. Но если отсортировать первый столбец по алфавиту - на выходе получаю только три строки, пример прикладываю. Мой макрос работает независимо от порядка исходных данных.
nilem, привет!
Интересное решение! Замечу, что для упорядоченных данных твой макрос работает хорошо, если у всех элементов строго по одному родителю. Но если отсортировать первый столбец по алфавиту - на выходе получаю только три строки, пример прикладываю. Мой макрос работает независимо от порядка исходных данных.Rioran
Sub ertert() 'with using reference "MS Scripting Runtime" Dim x, i&, sk$ Application.ScreenUpdating = False With Range("A2").CurrentRegion x = .Offset(1).Resize(.Rows.Count - 1).Value End With d.CompareMode = 1
With New Dictionary .CompareMode = 1 For i = 1 To UBound(x) If d.Exists(x(i, 1)) Then d.Item(x(i, 1)) = d.Item(x(i, 1)) & "~" & x(i, 2) Else d.Item(x(i, 1)) = x(i, 2) End If .Item(x(i, 2)) = 1 Next i For i = 1 To UBound(x) If Not .Exists(x(i, 1)) Then sk = x(i, 1) Next i End With
Cells(2, 4) = sk rty sk, 2, 4 d.RemoveAll
Application.ScreenUpdating = True End Sub
[/vba]
[vba]
Код
Sub rty(ByVal k$, n&, m&) Dim s, i& s = Split(d.Item(k), "~") For i = 0 To UBound(s) If d.Exists(s(i)) Then m = m + 1: Cells(n, m) = s(i) rty s(i), n, m Else Cells(n, m + 1) = s(i): n = n + 1 End If Next i m = m - 1 End Sub
[/vba]
Ла-а-адно еще словарик добавим:
[vba]
Код
Option Explicit Dim d As New Dictionary
Sub ertert() 'with using reference "MS Scripting Runtime" Dim x, i&, sk$ Application.ScreenUpdating = False With Range("A2").CurrentRegion x = .Offset(1).Resize(.Rows.Count - 1).Value End With d.CompareMode = 1
With New Dictionary .CompareMode = 1 For i = 1 To UBound(x) If d.Exists(x(i, 1)) Then d.Item(x(i, 1)) = d.Item(x(i, 1)) & "~" & x(i, 2) Else d.Item(x(i, 1)) = x(i, 2) End If .Item(x(i, 2)) = 1 Next i For i = 1 To UBound(x) If Not .Exists(x(i, 1)) Then sk = x(i, 1) Next i End With
Cells(2, 4) = sk rty sk, 2, 4 d.RemoveAll
Application.ScreenUpdating = True End Sub
[/vba]
[vba]
Код
Sub rty(ByVal k$, n&, m&) Dim s, i& s = Split(d.Item(k), "~") For i = 0 To UBound(s) If d.Exists(s(i)) Then m = m + 1: Cells(n, m) = s(i) rty s(i), n, m Else Cells(n, m + 1) = s(i): n = n + 1 End If Next i m = m - 1 End Sub
Sub GetHierarchy() Dim dicParent As Object: Set dicParent = CreateObject("Scripting.Dictionary") Dim dicChild As Object: Set dicChild = CreateObject("Scripting.Dictionary") Dim i&, k&, r&, c&, n&, s$(), v()
' очищаем предыдущий результат With Cells(1, 4) If Len(.Value) Then .Resize(.End(xlDown).Row - .Row + 1, .End(xlToRight).Column - .Column + 1).ClearContents '^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ End With 'это на случай, если результат нужно выводить не в первуя строку
v = Range("A2:B" & [A1].End(xlDown).Row) k = UBound(v) For i = 1 To k dicParent(v(i, 1)) = Empty dicChild(v(i, 2)) = IIf(dicChild.Exists(v(i, 1)), dicChild(v(i, 1)), v(i, 1)) & vbNullChar & v(i, 2) Next
r = 1 ' "резервируем" одну строку для заголовка n = 2 ' UBound(v, 2) For i = 1 To k If Not dicParent.Exists(v(i, 2)) Then r = r + 1 s = Split(dicChild(v(i, 2)), vbNullChar) c = UBound(s) + 1 If n < c Then n = c: ReDim Preserve v(k, c) For c = 1 To c v(r, c) = s(c - 1) Next End If Next ' формируем заголовок For c = 1 To n v(1, c) = "Level " & c - 1 Next Cells(1, 4).Resize(r, n) = v End Sub
Sub GetHierarchy() Dim dicParent As Object: Set dicParent = CreateObject("Scripting.Dictionary") Dim dicChild As Object: Set dicChild = CreateObject("Scripting.Dictionary") Dim i&, k&, r&, c&, n&, s$(), v()
' очищаем предыдущий результат With Cells(1, 4) If Len(.Value) Then .Resize(.End(xlDown).Row - .Row + 1, .End(xlToRight).Column - .Column + 1).ClearContents '^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ End With 'это на случай, если результат нужно выводить не в первуя строку
v = Range("A2:B" & [A1].End(xlDown).Row) k = UBound(v) For i = 1 To k dicParent(v(i, 1)) = Empty dicChild(v(i, 2)) = IIf(dicChild.Exists(v(i, 1)), dicChild(v(i, 1)), v(i, 1)) & vbNullChar & v(i, 2) Next
r = 1 ' "резервируем" одну строку для заголовка n = 2 ' UBound(v, 2) For i = 1 To k If Not dicParent.Exists(v(i, 2)) Then r = r + 1 s = Split(dicChild(v(i, 2)), vbNullChar) c = UBound(s) + 1 If n < c Then n = c: ReDim Preserve v(k, c) For c = 1 To c v(r, c) = s(c - 1) Next End If Next ' формируем заголовок For c = 1 To n v(1, c) = "Level " & c - 1 Next Cells(1, 4).Resize(r, n) = v End Sub