-
Notifications
You must be signed in to change notification settings - Fork 0
/
sort.ps1
210 lines (138 loc) · 7.13 KB
/
sort.ps1
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
function Get-ClonedObject {
param($DeepCopyObject)
$memStream = new-object IO.MemoryStream
$formatter = new-object Runtime.Serialization.Formatters.Binary.BinaryFormatter
$formatter.Serialize($memStream,$DeepCopyObject)
$memStream.Position=0
$formatter.Deserialize($memStream)
}
function Get-TopologicalSort {
param(
[PSObject] $objectsToSort,
[string]$sortProperty,
[string]$dependenciesProperty
)
# Handle cases where there are fewer than two objects
if ($objectsToSort.Count -lt 2) {
if ($objectsToSort.Count -eq 1) {
# Set Sort property to 0 for the single item
$objectsToSort[0] | Add-Member -MemberType NoteProperty -Name 'Sort' -Value 0 -Force
}
# Return the array (either with one item or empty) as-is
return $objectsToSort
}
if ( $localDebug ) { Write-Host "Sorting objects based on inter-dependencies ..." }
# Initialize list for original unsorted key-value list
$originalObjectList = @{}
# Get desired keys and dependency values to sort by
forEach ($object in $objectsToSort) {
# Assuming each object has a unique key that we want to use for the hashtable
$key = $object.$sortProperty
$value = $object.$dependenciesProperty
# Add key-value pair to the hashtable
$originalObjectList[$key] = $value
}
# Make sure we can use HashSet
Add-Type -AssemblyName System.Core
# Clone it so as to not alter original
$currentObjectList = [hashtable] (Get-ClonedObject $originalObjectList)
# Initialize list for sorted object identifiers
$topologicallySortedObjects = New-Object System.Collections.ArrayList
# Initialize a queue list for checked objects
$objectsWithNoUnCheckedDependencies = New-Object System.Collections.Queue
# Initialize a list for object list and dependencies converted to HashSets
$fasterObjectList = @{}
# Keep track of all nodes in case they put it in as an edge destination but not source
$allObjects = New-Object -TypeName System.Collections.Generic.HashSet[object] -ArgumentList (,[object[]] $currentObjectList.Keys)
#
forEach( $currentObject in $currentObjectList.Keys ) {
# Get dependencies of the current object
$currentObjectDependencies = [array] $currentObjectList[$currentObject]
# Check if there are any dependencies
if( $currentObjectDependencies.Length -eq 0 ) {
# Add to end of list of objects with no dependencies
$objectsWithNoUnCheckedDependencies.Enqueue($currentObject)
}
# Adds dependency as an object if it does not exist
forEach ( $currentObjectDependency in $currentObjectDependencies ) {
# Check if dependency is listed as an object
if( !$allObjects.Contains( $currentObjectDependency ) ) {
# Add dependency to list of objects
[void] $allObjects.Add($currentObjectDependency)
}
}
# Create a HashSet from current dependencies
$currentObjectDependencies = New-Object -TypeName System.Collections.Generic.HashSet[object] -ArgumentList (,[object[]] $currentObjectDependencies )
# Add current object and dependencies to a new list for faster operation
[void] $fasterObjectList.Add($currentObject, $currentObjectDependencies)
}
# Reconcile by adding dependencies with no parent object as objects with no children in the object list
forEach ( $currentObject in $allObjects ) {
# Check if object is in the current object list
if( !$currentObjectList.ContainsKey($currentObject) ) {
# Add as new object in the list
[void] $currentObjectList.Add($currentObject, (New-Object -TypeName System.Collections.Generic.HashSet[object]))
# Add also to list of objects with no dependencies
$objectsWithNoUnCheckedDependencies.Enqueue($currentObject)
}
}
# Get the complete "faster" object list
$currentObjectList = $fasterObjectList
# Cross-reference all project dependencies to find any cyclic dependencies
forEach ( $referenceObject in $currentObjectList.Keys ) {
# Check each object against every other object except itself
forEach ( $checkedObject in $currentObjectList.Keys | Where-Object { $_ -ne $referenceObject } ) {
# Check if object if dependent on reference object
if ( $currentObjectList[$checkedObject].Contains($referenceObject) ) {
# Check for a reciprocal dependency and exit if found
if ( $currentObjectList[$referenceObject].Contains($checkedObject) ) {
# Set an error message and exit the loop
$errorMessage = "Error: Cyclic Dependency ( {0} <-> {1} )" -f
$referenceObject,
$checkedObject
Write-Error $errorMessage
return $False
}
}
}
}
# Do a topological sort by using Kahn's algorithm
while( $objectsWithNoUnCheckedDependencies.Count -gt 0 ) {
# Get first object with no un-processed dependencies
$processedObject = $objectsWithNoUnCheckedDependencies.Dequeue()
# Remove object from current object list
[void] $currentObjectList.Remove($processedObject)
# Add to end of sorted object list
[void] $topologicallySortedObjects.Add($processedObject)
# Check if the processed object is a dependency for any other object
forEach( $currentUnProcessedObject in $currentObjectList.Keys ) {
# Get the dependencies of the currently checked unprocessed object
$currentUnProcessedObjectDependencies = $currentObjectList[$currentUnProcessedObject]
# Check if any dependency is the processed object
if( $currentUnProcessedObjectDependencies.Contains($processedObject) ) {
# Remove the dependency reference to the processed object
[void] $currentUnProcessedObjectDependencies.Remove($processedObject)
# Check if the current un-processed object has any more dependencies
if( $currentUnProcessedObjectDependencies.Count -eq 0 ) {
# Add the object to the list of objects with no un-checked dependencies
[void] $objectsWithNoUnCheckedDependencies.Enqueue($currentUnProcessedObject)
}
}
}
}
# Sort provided list of objects according to the topological sorted list
$sortedObjects = $objectsToSort | Sort-Object { $topologicallySortedObjects.IndexOf($_.$sortProperty) }
$sortedObjects = $sortedObjects | ForEach-Object {
if ($_.PSObject.Properties.Match('Sort').Count -gt 0) {
# If the Sort property exists, update it
$_.Sort = $sortedObjects.IndexOf($_)
} else {
# Otherwise, add Sort as a new property
$_ | Add-Member -MemberType NoteProperty -Name 'Sort' -Value $sortedObjects.IndexOf($_)
}
$_
}
# Return sorted objects
return $sortedObjects
}
$localDebug = $global:debug