| | 1 | | namespace MoreStructures.Graphs.Visitor; |
| | 2 | |
|
| | 3 | | /// <summary> |
| | 4 | | /// A <see cref="IVisitStrategy"/> implementation which uses an <see cref="HashSet{T}"/> of <see cref="int"/> to store |
| | 5 | | /// already visited vertices, while visiting the graph, and performs the visit recursively. |
| | 6 | | /// </summary> |
| | 7 | | public class FullyRecursiveHashSetBasedGraphVisit : DirectionableVisit |
| | 8 | | { |
| | 9 | | /// <inheritdoc path="//*[not(self::summary)]"/> |
| | 10 | | /// <summary> |
| | 11 | | /// <inheritdoc cref="FullyRecursiveHashSetBasedGraphVisit"/> |
| | 12 | | /// </summary> |
| 747 | 13 | | public FullyRecursiveHashSetBasedGraphVisit(bool directedGraph) : base(directedGraph) |
| 747 | 14 | | { |
| 747 | 15 | | } |
| | 16 | |
|
| | 17 | | /// <inheritdoc/> |
| | 18 | | protected override IEnumerable<(int, int)> DepthFirstSearchAndConnectedComponentsOfGraph(IGraph graph) |
| 180 | 19 | | { |
| 180 | 20 | | var alreadyVisited = new HashSet<int>(); // Populated by RExplore |
| 180 | 21 | | var numberOfVertices = graph.GetNumberOfVertices(); |
| | 22 | |
|
| 180 | 23 | | var currentConnectedComponent = 0; |
| 1908 | 24 | | for (var vertex = 0; vertex < numberOfVertices; vertex++) |
| 774 | 25 | | { |
| | 26 | | // Here RaiseAlreadyVisitedVertex should not be triggered, since the vertex is not been checked due to an |
| | 27 | | // incoming edge from another vertex, but rather to explore vertices in connected components which haven't |
| | 28 | | // been explored yet. |
| 774 | 29 | | if (alreadyVisited.Contains(vertex)) |
| 399 | 30 | | continue; |
| | 31 | |
|
| 2673 | 32 | | foreach (var outputItem in RDepthFirstSearchFromVertex( |
| 375 | 33 | | graph, alreadyVisited, vertex, currentConnectedComponent, null)) |
| 774 | 34 | | yield return (outputItem, currentConnectedComponent); |
| | 35 | |
|
| 375 | 36 | | currentConnectedComponent++; |
| 375 | 37 | | } |
| 180 | 38 | | } |
| | 39 | |
|
| | 40 | | /// <inheritdoc path="//*[not(self::remarks)]"/> |
| | 41 | | /// <remarks> |
| | 42 | | /// <para id="advantages"> |
| | 43 | | /// ADVANTAGES AND DISADVANTAGES |
| | 44 | | /// <br/> |
| | 45 | | /// Implemented fully recursively, so limited by stack depth and usable with graphs of a "reasonable" size. |
| | 46 | | /// </para> |
| | 47 | | /// <para id="algorithm"> |
| | 48 | | /// ALGORITHM |
| | 49 | | /// <br/> |
| | 50 | | /// - An <see cref="HashSet{T}"/> of the ids of already visited vertices is instantiated to an empty set. |
| | 51 | | /// <br/> |
| | 52 | | /// - Vertices are iterated over, from the first (id = 0), to the last (id = v - 1, where v is the total number |
| | 53 | | /// of vertices). |
| | 54 | | /// <br/> |
| | 55 | | /// - The total number of vertices is retrieved via <see cref="IGraph.GetNumberOfVertices"/>. |
| | 56 | | /// <br/> |
| | 57 | | /// - If the vertex i has not already been visited (i.e. it appears in the <see cref="HashSet{T}"/>), it is |
| | 58 | | /// visited, with the same algorithm it would be visited by |
| | 59 | | /// <see cref="DepthFirstSearchFromVertex(IGraph, int)"/>. |
| | 60 | | /// <br/> |
| | 61 | | /// - The order of visit is returned as a sequence of integers. |
| | 62 | | /// <br/> |
| | 63 | | /// - The set of already visited vertices is updated, adding the visited vertex, every time a visit is made, |
| | 64 | | /// and it is shared by all visits of all vertices, whether they are connected to each other or not. |
| | 65 | | /// <br/> |
| | 66 | | /// - When the sequence of recursive visits terminates, all vertices of the graph will have been visited. |
| | 67 | | /// </para> |
| | 68 | | /// <para id="complexity"> |
| | 69 | | /// COMPLEXITY |
| | 70 | | /// <br/> |
| | 71 | | /// - Instantiating and adding items to the set of already visited vertices are constant-time operations. |
| | 72 | | /// <br/> |
| | 73 | | /// - The set of already visited vertices ensures that each vertex of the graph is visited at most once. |
| | 74 | | /// <br/> |
| | 75 | | /// - To reach all vertices, the algorithm goes through all edges of the graph. |
| | 76 | | /// <br/> |
| | 77 | | /// - Because each vertex is visited at most once throughout the entire execution, edges are visited at most |
| | 78 | | /// once, when edge direction is taken into account during the visit, and twice, when it is not. |
| | 79 | | /// <br/> |
| | 80 | | /// - The complexity of retrieving the total number of vertices depends on |
| | 81 | | /// <see cref="IGraph.GetNumberOfVertices"/>. While being specific to the <see cref="IGraph"/> |
| | 82 | | /// implementation being used, all implementation provide O(1) runtime for such operation. |
| | 83 | | /// <br/> |
| | 84 | | /// - Checking whether a neighbor has already been visited is a O(1) operation, because the set used is an |
| | 85 | | /// <see cref="HashSet{T}"/>. |
| | 86 | | /// <br/> |
| | 87 | | /// - Therefore, Time Complexity is O(v * Ta + e) and Space Complexity is O(v * Sa + e), where |
| | 88 | | /// v is the number of vertices, e is the number of edges and Ta and Sa are the time and space cost of |
| | 89 | | /// retrieving the neighborhood of a given vertex. |
| | 90 | | /// </para> |
| | 91 | | /// <para id="complexity-and-events"> |
| | 92 | | /// COMPLEXITY AND EVENTS |
| | 93 | | /// <br/> |
| | 94 | | /// - Event handlers are externally defined and have been considered O(1) in this analysis. |
| | 95 | | /// <br/> |
| | 96 | | /// - To include them in the analysis, it should be taken into account that |
| | 97 | | /// <see cref="IVisitStrategy.VisitingVertex"/> and <see cref="IVisitStrategy.VisitedVertex"/> happen once |
| | 98 | | /// per visited vertex, whereas <see cref="IVisitStrategy.AlreadyVisitedVertex"/> can happen globally as many |
| | 99 | | /// times as the number of edges. |
| | 100 | | /// </para> |
| | 101 | | /// </remarks> |
| 105 | 102 | | public override IEnumerable<int> DepthFirstSearchOfGraph(IGraph graph) => base.DepthFirstSearchOfGraph(graph); |
| | 103 | |
|
| | 104 | | /// <inheritdoc path="//*[not(self::remarks)]"/> |
| | 105 | | /// <remarks> |
| | 106 | | /// <para id="advantages"> |
| | 107 | | /// ADVANTAGES AND DISADVANTAGES |
| | 108 | | /// <br/> |
| | 109 | | /// Implemented fully recursively, so limited by stack depth and usable with graphs of a "reasonable" size. |
| | 110 | | /// </para> |
| | 111 | | /// <para id="algorithm"> |
| | 112 | | /// ALGORITHM |
| | 113 | | /// <br/> |
| | 114 | | /// - The algorithm is a simple variation of <see cref="DepthFirstSearchOfGraph(IGraph)"/>. |
| | 115 | | /// <br/> |
| | 116 | | /// - All vertices from 0 to <see cref="IGraph.GetNumberOfVertices"/> - 1 are explored. |
| | 117 | | /// <br/> |
| | 118 | | /// - An <see cref="HashSet{T}"/> of already visited vertices is shared across all visits, to ensure that a |
| | 119 | | /// vertex is not visited twice. |
| | 120 | | /// <br/> |
| | 121 | | /// - A current value of the connected component is instantiated at 0 and incremented every time a new |
| | 122 | | /// connected component is explored, i.e. every time the vertex i, of the top-level iteration, has not been |
| | 123 | | /// visited yet, meaning that none of the connected components explored so far contains it. |
| | 124 | | /// <br/> |
| | 125 | | /// - The resulting mapping between vertex id and connected component value is returned as result. |
| | 126 | | /// </para> |
| | 127 | | /// <para id="complexity"> |
| | 128 | | /// COMPLEXITY |
| | 129 | | /// <br/> |
| | 130 | | /// - The algorithm closely resembles <see cref="DepthFirstSearchOfGraph(IGraph)"/>, with the added complexity |
| | 131 | | /// of instantiating and populating a <see cref="IDictionary{TKey, TValue}"/> of the mapping between vertices |
| | 132 | | /// and connected component labels. |
| | 133 | | /// <br/> |
| | 134 | | /// - Because the <see cref="IDictionary{TKey, TValue}"/> implementation used is a |
| | 135 | | /// <see cref="Dictionary{TKey, TValue}"/>, which is hash-based, such additional operations are performed in |
| | 136 | | /// constant time. |
| | 137 | | /// <br/> |
| | 138 | | /// - Therefore the complexity of this method is the same as <see cref="DepthFirstSearchOfGraph(IGraph)"/>: |
| | 139 | | /// Time Complexity is O(v * Ta + e) and Space Complexity is O(v * Sa + e), where v is the number of |
| | 140 | | /// vertices, e is the number of edges and Ta and Sa are the time and space cost of retrieving the |
| | 141 | | /// neighborhood of a given vertex. |
| | 142 | | /// </para> |
| | 143 | | /// <inheritdoc cref="DepthFirstSearchOfGraph" path="/remarks/para[@id='complexity-and-events']"/> |
| | 144 | | /// </remarks> |
| 27 | 145 | | public override IDictionary<int, int> ConnectedComponents(IGraph graph) => base.ConnectedComponents(graph); |
| | 146 | |
|
| | 147 | | /// <inheritdoc path="//*[not(self::remarks)]"/> |
| | 148 | | /// <remarks> |
| | 149 | | /// <para id="advantages"> |
| | 150 | | /// ADVANTAGES AND DISADVANTAGES |
| | 151 | | /// <br/> |
| | 152 | | /// Implemented fully recursively, so limited by stack depth and usable with graphs of a "reasonable" size. |
| | 153 | | /// </para> |
| | 154 | | /// <para id="algorithm"> |
| | 155 | | /// ALGORITHM |
| | 156 | | /// <br/> |
| | 157 | | /// - A <see cref="HashSet{T}"/> of the ids of already visited vertices is instantiated to an empty set. |
| | 158 | | /// <br/> |
| | 159 | | /// - The set of already visited vertices is updated, adding the visited vertex, every time a visit is made. |
| | 160 | | /// <br/> |
| | 161 | | /// - The visit starts from the specified start vertex and looks for all the neighboring edges of such vertex |
| | 162 | | /// (taking into account the direction of the edge or not, depending on the specified parameters). |
| | 163 | | /// <br/> |
| | 164 | | /// - Only neighboring edges connecting the start vertex to a vertex not already visited are taken into |
| | 165 | | /// account. |
| | 166 | | /// <br/> |
| | 167 | | /// - However, to reach all relevant vertices, the algorithm may go through all edges of the graph. |
| | 168 | | /// <br/> |
| | 169 | | /// - All other vertices are skipped, since those vertices, their neighborhood, neighbors of their neighborhood |
| | 170 | | /// etc. have already been visited in previous steps of the recursive visit. |
| | 171 | | /// <br/> |
| | 172 | | /// - When the recursive visit terminates, all vertices directly or indirectly connected to the start vertex S |
| | 173 | | /// (i.e. all vertices V for which there is a path of edges e1, e2, ..., en, connecting S to V) will |
| | 174 | | /// have been visited. |
| | 175 | | /// <br/> |
| | 176 | | /// - Vertices which are not connected to S (i.e. for which there is no path), are not included in the |
| | 177 | | /// resulting sequence of vertices. |
| | 178 | | /// </para> |
| | 179 | | /// <para id="complexity"> |
| | 180 | | /// COMPLEXITY |
| | 181 | | /// <br/> |
| | 182 | | /// - Instantiating and adding items to the set of already visited vertices are constant-time operations. |
| | 183 | | /// <br/> |
| | 184 | | /// - The set of already visited vertices ensures that each vertex of the graph is visited at most once. |
| | 185 | | /// <br/> |
| | 186 | | /// - The complexity of retrieving the neighborhood of a vertex depends on the implementation of |
| | 187 | | /// <see cref="IGraph.GetAdjacentVerticesAndEdges(int, bool)"/>, thus on the specific <see cref="IGraph"/> |
| | 188 | | /// implementation being used. |
| | 189 | | /// <br/> |
| | 190 | | /// - Checking whether a neighbor has already been visited is a O(1) operation, because the set used is an |
| | 191 | | /// <see cref="HashSet{T}"/>. |
| | 192 | | /// <br/> |
| | 193 | | /// - Therefore, Time Complexity is O(v * Ta + e) and Space Complexity is O(v * Sa + e), where v is the number |
| | 194 | | /// of vertices, e is the number of edges and Ta and Sa are the time and space cost of retrieving the |
| | 195 | | /// neighborhood of a given vertex. |
| | 196 | | /// </para> |
| | 197 | | /// <inheritdoc cref="DepthFirstSearchOfGraph" path="/remarks/para[@id='complexity-and-events']"/> |
| | 198 | | /// </remarks> |
| | 199 | | public override IEnumerable<int> DepthFirstSearchFromVertex(IGraph graph, int vertex) |
| 296 | 200 | | { |
| 296 | 201 | | var alreadyVisited = new HashSet<int>(); // Populated by RExplore |
| 296 | 202 | | var lazyExploration = RDepthFirstSearchFromVertex( |
| 296 | 203 | | graph, alreadyVisited, vertex, 0, null); // Single connected component "0" |
| 296 | 204 | | MoreLinq.MoreEnumerable.Consume(lazyExploration); |
| 296 | 205 | | return alreadyVisited; |
| 296 | 206 | | } |
| | 207 | |
|
| | 208 | | /// <inheritdoc path="//*[not(self::remarks)]"/> |
| | 209 | | /// <remarks> |
| | 210 | | /// <para id="advantages"> |
| | 211 | | /// ADVANTAGES AND DISADVANTAGES |
| | 212 | | /// <br/> |
| | 213 | | /// Implemented fully recursively, so limited by stack depth and usable with graphs of a "reasonable" size. |
| | 214 | | /// </para> |
| | 215 | | /// <para id="algorithm"> |
| | 216 | | /// ALGORITHM |
| | 217 | | /// <br/> |
| | 218 | | /// - The algorithm closely resembles <see cref="DepthFirstSearchFromVertex(IGraph, int)"/> with one |
| | 219 | | /// foundamental difference: <b>the recursive calls to neighbors of each vertex generate |
| | 220 | | /// <see cref="IEnumerable{T}"/> which are iterated lazily and in parallel, one element of each |
| | 221 | | /// <see cref="IEnumerator{T}"/> at a time</b>, up until all enumerators are done. |
| | 222 | | /// <br/> |
| | 223 | | /// - Let's take as an example a graph in which neighbors of vertex 0 are 1 and 2, neighbors of vertex 1 are 3 |
| | 224 | | /// and 4 and neighbors of vertex 2 are 5 and 4. |
| | 225 | | /// <br/> |
| | 226 | | /// - The visit of vertex 0 will first yield the vertex 0 itself. |
| | 227 | | /// <br/> |
| | 228 | | /// - It then yields the 1st element of the enumerable of BFS from vertex 1, which is the vertex 1 itself. |
| | 229 | | /// <br/> |
| | 230 | | /// - After that, it yields the 1st element of the enumerable of BFS from vertex 2, which is the vertex 2 |
| | 231 | | /// itself. |
| | 232 | | /// <br/> |
| | 233 | | /// - After that, since there are no other neighbors of 0, moves to the 2nd elements of each of the |
| | 234 | | /// enumerators. |
| | 235 | | /// <br/> |
| | 236 | | /// - It yields the 2nd element of the enumerable of BFS from vertex 1, which is the vertex 3. |
| | 237 | | /// <br/> |
| | 238 | | /// - It then yields the 2nd element of the enumerable of BFS from vertex 2, which is the vertex 5. |
| | 239 | | /// <br/> |
| | 240 | | /// - Etc, until all enumerators are done (i.e. <see cref="System.Collections.IEnumerator.MoveNext"/> is |
| | 241 | | /// <see langword="false"/>. |
| | 242 | | /// </para> |
| | 243 | | /// <para id="complexity"> |
| | 244 | | /// COMPLEXITY |
| | 245 | | /// <br/> |
| | 246 | | /// - Same consideration about complexity as in <see cref="DepthFirstSearchFromVertex(IGraph, int)"/> apply. |
| | 247 | | /// What is different is just the order of visit, not edges or vertices visited. |
| | 248 | | /// <br/> |
| | 249 | | /// - Therefore, Time Complexity is O(v * Ta + e) and Space Complexity is O(v * Sa + e), where v is the number |
| | 250 | | /// of vertices, e is the number of edges and Ta and Sa are the time and space cost of retrieving the |
| | 251 | | /// neighborhood of a given vertex. |
| | 252 | | /// </para> |
| | 253 | | /// <inheritdoc cref="DepthFirstSearchOfGraph" path="/remarks/para[@id='complexity-and-events']"/> |
| | 254 | | /// </remarks> |
| | 255 | | public override IEnumerable<int> BreadthFirstSearchFromVertex(IGraph graph, int vertex) |
| 198 | 256 | | { |
| 198 | 257 | | return BreadthFirstSearchFromVertices(graph, new[] { vertex }); |
| 198 | 258 | | } |
| | 259 | |
|
| | 260 | | /// <inheritdoc path="//*[not(self::remarks)]"/> |
| | 261 | | /// <remarks> |
| | 262 | | /// The algorithm is very similar to <see cref="BreadthFirstSearchFromVertex(IGraph, int)"/>, with the only |
| | 263 | | /// difference that all vertices in the <paramref name="vertices"/> sequence are visited, in parallel, instead |
| | 264 | | /// of a single one. |
| | 265 | | /// <br/> |
| | 266 | | /// Because the vertices may or may not belong to different connected components, this algorithm returns "-1" |
| | 267 | | /// as connected component for all visited vertices. |
| | 268 | | /// <br/> |
| | 269 | | /// Time Complexity is O(v * Ta + e) and Space Complexity is O(v + e + Sa), exactly as for |
| | 270 | | /// <see cref="BreadthFirstSearchFromVertex(IGraph, int)"/>, since in the worst case the entire graph has to be |
| | 271 | | /// explored. |
| | 272 | | /// </remarks> |
| | 273 | | public override IEnumerable<int> BreadthFirstSearchFromVertices(IGraph graph, IEnumerable<int> vertices) |
| 414 | 274 | | { |
| 414 | 275 | | var alreadyVisited = new HashSet<int>(); |
| 414 | 276 | | var verticesEnumerators = vertices |
| 414 | 277 | | .Select(vertex => |
| 426 | 278 | | RBreadthFirstSearchFromVertex(graph, vertex, alreadyVisited, -1, null, 0).GetEnumerator()) |
| 414 | 279 | | .ToList(); |
| | 280 | |
|
| 3516 | 281 | | foreach (var (descendantVertex, _) in EnumerateInParallel(verticesEnumerators, 0)) |
| 1137 | 282 | | yield return descendantVertex; |
| 414 | 283 | | } |
| | 284 | |
|
| | 285 | | private IEnumerable<int> RDepthFirstSearchFromVertex( |
| | 286 | | IGraph graph, HashSet<int> alreadyVisited, int vertex, int connectedComponent, int? previousVertex) |
| 1427 | 287 | | { |
| 1427 | 288 | | RaiseVisitingVertex(new(vertex, connectedComponent, previousVertex)); |
| | 289 | |
|
| 1427 | 290 | | alreadyVisited.Add(vertex); |
| 1427 | 291 | | yield return vertex; |
| | 292 | |
|
| 1427 | 293 | | var neighbors = graph |
| 1427 | 294 | | .GetAdjacentVerticesAndEdges(vertex, DirectedGraph) |
| 3050 | 295 | | .OrderBy(neighbor => neighbor.Vertex); |
| | 296 | |
|
| 7527 | 297 | | foreach (var (unexploredVertex, _, _) in neighbors) |
| 1623 | 298 | | { |
| 1623 | 299 | | if (alreadyVisited.Contains(unexploredVertex)) |
| 867 | 300 | | { |
| 867 | 301 | | RaiseAlreadyVisitedVertex(new(unexploredVertex, connectedComponent, vertex)); |
| 867 | 302 | | continue; |
| | 303 | | } |
| | 304 | |
|
| 4492 | 305 | | foreach (var outputItem in RDepthFirstSearchFromVertex( |
| 756 | 306 | | graph, alreadyVisited, unexploredVertex, connectedComponent, vertex)) |
| 1112 | 307 | | yield return outputItem; |
| 756 | 308 | | } |
| | 309 | |
|
| 1427 | 310 | | RaiseVisitedVertex(new(vertex, connectedComponent, previousVertex)); |
| 1427 | 311 | | } |
| | 312 | |
|
| | 313 | | private IEnumerable<(int value, int level)> RBreadthFirstSearchFromVertex( |
| | 314 | | IGraph graph, int vertex, HashSet<int> alreadyVisited, int connectedComponent, int? previousVertex, int level) |
| 2402 | 315 | | { |
| 2402 | 316 | | if (alreadyVisited.Contains(vertex)) |
| 1265 | 317 | | yield break; |
| | 318 | |
|
| 1137 | 319 | | RaiseVisitingVertex(new(vertex, connectedComponent, previousVertex)); |
| | 320 | |
|
| 1137 | 321 | | alreadyVisited.Add(vertex); |
| 1137 | 322 | | yield return (vertex, level); |
| | 323 | |
|
| 1137 | 324 | | var neighborsEnumerators = graph |
| 1137 | 325 | | .GetAdjacentVerticesAndEdges(vertex, DirectedGraph) |
| 1976 | 326 | | .OrderBy(neighbor => neighbor.Vertex) |
| 1976 | 327 | | .Select(neighbor => RBreadthFirstSearchFromVertex( |
| 1976 | 328 | | graph, neighbor.Vertex, alreadyVisited, connectedComponent, vertex, level + 1).GetEnumerator()) |
| 1137 | 329 | | .ToList(); |
| | 330 | |
|
| 5283 | 331 | | foreach (var descendantVertex in EnumerateInParallel(neighborsEnumerators, level + 1)) |
| 936 | 332 | | yield return descendantVertex; |
| | 333 | |
|
| 1137 | 334 | | RaiseVisitedVertex(new(vertex, connectedComponent, previousVertex)); |
| 1137 | 335 | | } |
| | 336 | |
|
| | 337 | | private static IEnumerable<(int value, int level)> EnumerateInParallel( |
| | 338 | | IList<IEnumerator<(int value, int level)>> enumerators, int level) |
| 1551 | 339 | | { |
| 1551 | 340 | | var parallelEnumeration = new ParallelEnumeration(enumerators); |
| | 341 | |
|
| 1551 | 342 | | var currentLevel = level; |
| 3036 | 343 | | while (parallelEnumeration.MoveNextCount > 0) |
| 1485 | 344 | | { |
| 1485 | 345 | | var skippedBecauseOfHigherLevel = 0; |
| 7224 | 346 | | for (var i = 0; i < enumerators.Count; i++) |
| 2127 | 347 | | { |
| 4200 | 348 | | while (parallelEnumeration.CurrentValueDefinedAndAtLevel(i, currentLevel)) |
| 2073 | 349 | | { |
| 2073 | 350 | | yield return enumerators[i].Current; |
| 2073 | 351 | | parallelEnumeration.MoveNext(i); |
| 2073 | 352 | | } |
| | 353 | |
|
| 2127 | 354 | | if (parallelEnumeration.MoveNextValues[i]) |
| 636 | 355 | | skippedBecauseOfHigherLevel++; |
| 2127 | 356 | | } |
| | 357 | |
|
| 1485 | 358 | | if (skippedBecauseOfHigherLevel == parallelEnumeration.MoveNextCount) |
| 1485 | 359 | | currentLevel++; |
| 1485 | 360 | | } |
| 1551 | 361 | | } |
| | 362 | |
|
| | 363 | | private sealed class ParallelEnumeration |
| | 364 | | { |
| 4782 | 365 | | public IList<IEnumerator<(int value, int level)>> Enumerators { get; } |
| 10473 | 366 | | public bool[] MoveNextValues { get; } |
| 8346 | 367 | | public int MoveNextCount { get; private set; } |
| | 368 | |
|
| 1551 | 369 | | public ParallelEnumeration(IList<IEnumerator<(int value, int level)>> enumerators) |
| 1551 | 370 | | { |
| 1551 | 371 | | var moveNextValues = new bool[enumerators.Count]; |
| 1551 | 372 | | var moveNextCount = 0; |
| | 373 | |
|
| 7906 | 374 | | for (var i = 0; i < enumerators.Count; i++) |
| 2402 | 375 | | { |
| 2402 | 376 | | moveNextValues[i] = enumerators[i].MoveNext(); |
| 2402 | 377 | | if (moveNextValues[i]) |
| 1137 | 378 | | moveNextCount++; |
| 2402 | 379 | | } |
| | 380 | |
|
| 1551 | 381 | | Enumerators = enumerators; |
| 1551 | 382 | | MoveNextValues = moveNextValues; |
| 1551 | 383 | | MoveNextCount = moveNextCount; |
| 1551 | 384 | | } |
| | 385 | |
|
| | 386 | | public bool CurrentValueDefinedAndAtLevel(int enumeratorIndex, int level) |
| 4200 | 387 | | { |
| 4200 | 388 | | return MoveNextValues[enumeratorIndex] && Enumerators[enumeratorIndex].Current.level == level; |
| 4200 | 389 | | } |
| | 390 | |
|
| | 391 | | public void MoveNext(int enumeratorIndex) |
| 2073 | 392 | | { |
| 2073 | 393 | | MoveNextValues[enumeratorIndex] = Enumerators[enumeratorIndex].MoveNext(); |
| | 394 | |
|
| 2073 | 395 | | if (!MoveNextValues[enumeratorIndex]) |
| 1137 | 396 | | MoveNextCount--; |
| 2073 | 397 | | } |
| | 398 | | } |
| | 399 | | } |