@@ -163,3 +163,237 @@ func connectionCount(in groupId: UUID, connections: [DatabaseConnection], groups
163163 } . count
164164 return directCount + descendantCount
165165}
166+
167+ // MARK: - Indexed Tree (O(G+C))
168+
169+ struct GroupTreeIndices {
170+ var connectionCountByGroup : [ UUID : Int ] = [ : ]
171+ var depthByGroup : [ UUID : Int ] = [ : ]
172+ var maxDescendantDepthByGroup : [ UUID : Int ] = [ : ]
173+ }
174+
175+ private struct GroupTreeIndex {
176+ let validGroupIds : Set < UUID >
177+ let childrenByParentId : [ UUID ? : [ ConnectionGroup ] ]
178+ let connectionsByGroupId : [ UUID : [ DatabaseConnection ] ]
179+ }
180+
181+ private func sortGroups( _ groups: [ ConnectionGroup ] ) -> [ ConnectionGroup ] {
182+ groups. sorted {
183+ $0. sortOrder != $1. sortOrder
184+ ? $0. sortOrder < $1. sortOrder
185+ : $0. name. localizedStandardCompare ( $1. name) == . orderedAscending
186+ }
187+ }
188+
189+ private func sortConnections( _ connections: [ DatabaseConnection ] ) -> [ DatabaseConnection ] {
190+ connections. sorted {
191+ $0. sortOrder != $1. sortOrder
192+ ? $0. sortOrder < $1. sortOrder
193+ : $0. name. localizedStandardCompare ( $1. name) == . orderedAscending
194+ }
195+ }
196+
197+ private func buildGroupTreeIndex( groups: [ ConnectionGroup ] , connections: [ DatabaseConnection ] ) -> GroupTreeIndex {
198+ let validGroupIds = Set ( groups. map ( \. id) )
199+
200+ var childrenByParentId : [ UUID ? : [ ConnectionGroup ] ] = [ : ]
201+ for group in groups {
202+ let parentKey = group. parentId. flatMap { validGroupIds. contains ( $0) ? $0 : nil }
203+ childrenByParentId [ parentKey, default: [ ] ] . append ( group)
204+ }
205+ for key in childrenByParentId. keys {
206+ if let levelGroups = childrenByParentId [ key] {
207+ childrenByParentId [ key] = sortGroups ( levelGroups)
208+ }
209+ }
210+
211+ var connectionsByGroupId : [ UUID : [ DatabaseConnection ] ] = [ : ]
212+ for connection in connections {
213+ guard let groupId = connection. groupId, validGroupIds. contains ( groupId) else { continue }
214+ connectionsByGroupId [ groupId, default: [ ] ] . append ( connection)
215+ }
216+ for groupId in connectionsByGroupId. keys {
217+ if let groupConnections = connectionsByGroupId [ groupId] {
218+ connectionsByGroupId [ groupId] = sortConnections ( groupConnections)
219+ }
220+ }
221+
222+ return GroupTreeIndex (
223+ validGroupIds: validGroupIds,
224+ childrenByParentId: childrenByParentId,
225+ connectionsByGroupId: connectionsByGroupId
226+ )
227+ }
228+
229+ func buildGroupTreeIndexed(
230+ groups: [ ConnectionGroup ] ,
231+ connections: [ DatabaseConnection ] ,
232+ maxDepth: Int = 3
233+ ) -> [ ConnectionGroupTreeNode ] {
234+ let index = buildGroupTreeIndex ( groups: groups, connections: connections)
235+ return buildGroupTreeIndexedLevel (
236+ parentId: nil ,
237+ currentDepth: 0 ,
238+ maxDepth: maxDepth,
239+ index: index,
240+ connections: connections
241+ )
242+ }
243+
244+ private func buildGroupTreeIndexedLevel(
245+ parentId: UUID ? ,
246+ currentDepth: Int ,
247+ maxDepth: Int ,
248+ index: GroupTreeIndex ,
249+ connections: [ DatabaseConnection ]
250+ ) -> [ ConnectionGroupTreeNode ] {
251+ var items : [ ConnectionGroupTreeNode ] = [ ]
252+ let levelGroups = index. childrenByParentId [ parentId] ?? [ ]
253+
254+ for group in levelGroups {
255+ var children : [ ConnectionGroupTreeNode ] = [ ]
256+ if currentDepth < maxDepth {
257+ children = buildGroupTreeIndexedLevel (
258+ parentId: group. id,
259+ currentDepth: currentDepth + 1 ,
260+ maxDepth: maxDepth,
261+ index: index,
262+ connections: connections
263+ )
264+ }
265+ for conn in index. connectionsByGroupId [ group. id] ?? [ ] {
266+ children. append ( . connection( conn) )
267+ }
268+ items. append ( . group( group, children: children) )
269+ }
270+
271+ if parentId == nil {
272+ let ungrouped = sortConnections ( connections. filter { conn in
273+ guard let groupId = conn. groupId else { return true }
274+ return !index. validGroupIds. contains ( groupId)
275+ } )
276+ for conn in ungrouped {
277+ items. append ( . connection( conn) )
278+ }
279+ }
280+
281+ return items
282+ }
283+
284+ func buildGroupTreeWithIndices(
285+ groups: [ ConnectionGroup ] ,
286+ connections: [ DatabaseConnection ] ,
287+ maxDepth: Int = 3
288+ ) -> ( tree: [ ConnectionGroupTreeNode ] , indices: GroupTreeIndices ) {
289+ let index = buildGroupTreeIndex ( groups: groups, connections: connections)
290+ let tree = buildGroupTreeIndexedLevel (
291+ parentId: nil ,
292+ currentDepth: 0 ,
293+ maxDepth: maxDepth,
294+ index: index,
295+ connections: connections
296+ )
297+ return ( tree, computeGroupTreeIndices ( from: index, groups: groups, connections: connections) )
298+ }
299+
300+ func computeGroupTreeIndices( groups: [ ConnectionGroup ] , connections: [ DatabaseConnection ] ) -> GroupTreeIndices {
301+ computeGroupTreeIndices (
302+ from: buildGroupTreeIndex ( groups: groups, connections: connections) ,
303+ groups: groups,
304+ connections: connections
305+ )
306+ }
307+
308+ private func computeGroupTreeIndices(
309+ from index: GroupTreeIndex ,
310+ groups: [ ConnectionGroup ] ,
311+ connections: [ DatabaseConnection ]
312+ ) -> GroupTreeIndices {
313+ var result = GroupTreeIndices ( )
314+
315+ var depthByGroup : [ UUID : Int ] = [ : ]
316+ var visitedDepth : Set < UUID > = [ ]
317+ var queue : [ ( UUID , Int ) ] = [ ]
318+ let roots = index. childrenByParentId [ nil ] ?? [ ]
319+ for root in roots where !visitedDepth. contains ( root. id) {
320+ visitedDepth. insert ( root. id)
321+ depthByGroup [ root. id] = 1
322+ queue. append ( ( root. id, 1 ) )
323+ }
324+ var queueIndex = 0
325+ while queueIndex < queue. count {
326+ let ( currentId, currentDepth) = queue [ queueIndex]
327+ queueIndex += 1
328+ let children = index. childrenByParentId [ currentId] ?? [ ]
329+ for child in children where !visitedDepth. contains ( child. id) {
330+ visitedDepth. insert ( child. id)
331+ depthByGroup [ child. id] = currentDepth + 1
332+ queue. append ( ( child. id, currentDepth + 1 ) )
333+ }
334+ }
335+
336+ var maxDepthByGroup : [ UUID : Int ] = [ : ]
337+ var connectionCountByGroup : [ UUID : Int ] = [ : ]
338+ for root in roots {
339+ _ = aggregateSubtree (
340+ groupId: root. id,
341+ visited: [ ] ,
342+ index: index,
343+ maxDepthByGroup: & maxDepthByGroup,
344+ connectionCountByGroup: & connectionCountByGroup
345+ )
346+ }
347+
348+ for group in groups {
349+ result. depthByGroup [ group. id] = depthByGroup [ group. id]
350+ ?? depthOf ( groupId: group. id, groups: groups)
351+ result. maxDescendantDepthByGroup [ group. id] = maxDepthByGroup [ group. id]
352+ ?? maxDescendantDepth ( groupId: group. id, groups: groups)
353+ result. connectionCountByGroup [ group. id] = connectionCountByGroup [ group. id]
354+ ?? connectionCount ( in: group. id, connections: connections, groups: groups)
355+ }
356+
357+ return result
358+ }
359+
360+ private struct SubtreeAggregate {
361+ var maxDescendantDepth : Int
362+ var connectionCount : Int
363+ }
364+
365+ private func aggregateSubtree(
366+ groupId: UUID ,
367+ visited: Set < UUID > ,
368+ index: GroupTreeIndex ,
369+ maxDepthByGroup: inout [ UUID : Int ] ,
370+ connectionCountByGroup: inout [ UUID : Int ]
371+ ) -> SubtreeAggregate {
372+ var nextVisited = visited
373+ nextVisited. insert ( groupId)
374+
375+ let children = index. childrenByParentId [ groupId] ?? [ ]
376+ var maxChildDescendantDepth = 0
377+ var subtreeCount = index. connectionsByGroupId [ groupId] ? . count ?? 0
378+
379+ for child in children where !visited. contains ( child. id) {
380+ let childAggregate = aggregateSubtree (
381+ groupId: child. id,
382+ visited: nextVisited,
383+ index: index,
384+ maxDepthByGroup: & maxDepthByGroup,
385+ connectionCountByGroup: & connectionCountByGroup
386+ )
387+ maxChildDescendantDepth = max ( maxChildDescendantDepth, childAggregate. maxDescendantDepth)
388+ subtreeCount += childAggregate. connectionCount
389+ }
390+
391+ let maxDescendantDepthValue = children. isEmpty ? 0 : 1 + maxChildDescendantDepth
392+ let result = SubtreeAggregate (
393+ maxDescendantDepth: maxDescendantDepthValue,
394+ connectionCount: subtreeCount
395+ )
396+ maxDepthByGroup [ groupId] = result. maxDescendantDepth
397+ connectionCountByGroup [ groupId] = result. connectionCount
398+ return result
399+ }
0 commit comments