/// <summary> /// Recursively traverse this self-relationship, collecting all IDs that pass the isResult check. /// </summary> /// <remarks> /// Note that this extension method only applies to relations from a given ID type to itself. /// That is how the relation can be traversed multiple times. /// /// This can be used, for instance, to collect all (and only) the process pips that are dependencies of a /// given pip: just traverse the PipDependencies relation looking for PipType.ProcessPips only. /// /// The traversal stops once there are no more IDs to traverse, because the traversal reached a /// Halt result for all nodes, and/or because the traversal reached the end of the graph. If there /// is a cycle in the graph, the algorithm will fail after MaximumTraverseLimit iterations. /// </remarks> /// <typeparam name="TId">The ID type of the self-relationship.</typeparam> /// <param name="relation">The relation.</param> /// <param name="initial">The initial value to start the iteration.</param> /// <param name="filter">Returns whether to accept or reject the value, and whether to continue or halt the traversal.</param> /// <returns>The collection of all IDs that pass the isResult check, transitively from the initial ID.</returns> public static IEnumerable <TId> Traverse <TId>( this RelationTable <TId, TId> relation, TId initial, Func <TId, TraversalFilterResult> filter) where TId : unmanaged, Id <TId> { return(relation.Traverse(new TId[] { initial }, filter)); }
/// <summary> /// Recursively traverse this self-relationship, collecting all IDs that pass the isResult check. /// </summary> /// <remarks> /// Note that this extension method only applies to relations from a given ID type to itself. /// That is how the relation can be traversed multiple times. /// /// This can be used, for instance, to collect all (and only) the process pips that are dependencies of a /// given pip: just traverse the PipDependencies relation looking for PipType.ProcessPips only. /// /// The traversal stops once there are no more IDs to traverse, because the traversal reached a /// Halt result for all nodes, and/or because the traversal reached the end of the graph. If there /// is a cycle in the graph, the algorithm will fail after MaximumTraverseLimit iterations. /// </remarks> /// <typeparam name="TId">The ID type of the self-relationship.</typeparam> /// <param name="relation">The relation.</param> /// <param name="initialValues">The initial values to start the iteration.</param> /// <param name="filter">Returns whether to accept or reject the value, and whether to continue or halt the traversal.</param> /// <returns>The collection of all IDs that pass the isResult check, transitively from the initial ID.</returns> public static IEnumerable <TId> Traverse <TId>( this RelationTable <TId, TId> relation, IEnumerable <TId> initialValues, Func <TId, TraversalFilterResult> filter) where TId : unmanaged, Id <TId> { var prospects = new HashSet <TId>(initialValues, default(TId).Comparer); var results = new HashSet <TId>(default(TId).Comparer); var nextProspects = new HashSet <TId>(default(TId).Comparer); var traverseCount = 0; while (prospects.Count > 0) { nextProspects.Clear(); foreach (var next in prospects.SelectMany(p => relation.Enumerate(p))) { TraversalFilterResult result = filter(next); if (result.IsAccept()) { results.Add(next); } if (result.IsContinue()) { nextProspects.Add(next); } } // swap the collections HashSet <TId> temp = prospects; prospects = nextProspects; nextProspects = temp; if (++traverseCount > MaximumTraverseLimit) { throw new Exception($"Exceeded maximum relation traversal depth of {MaximumTraverseLimit}, probably due to cycle in data"); } } return(results); }
/// <summary> /// Construct a RelationTable from a one-to-one SingleValueTable. /// </summary> /// <remarks> /// The only real point of doing this is to be able to invert the resulting relation. /// </remarks> public static RelationTable <TFromId, TToId> FromSingleValueTable( SingleValueTable <TFromId, TToId> baseTable, ITable <TToId> relatedTable) { RelationTable <TFromId, TToId> result = new RelationTable <TFromId, TToId>(baseTable, relatedTable); TToId[] buffer = new TToId[1]; TToId[] empty = new TToId[0]; foreach (TFromId id in baseTable.Ids) { if (!s_toComparer.Equals(baseTable[id], default)) { buffer[0] = baseTable[id]; result.Add(buffer); } else { result.Add(empty); } } return(result); }
/// <summary> /// Create a new RelationTable that is the inverse of this relation. /// </summary> /// <remarks> /// Very useful for calculating, for example, pip dependents based on pip dependencies, or /// per-file producers based on per-pip files produced. /// </remarks> public RelationTable <TToId, TFromId> Invert() { RelationTable <TToId, TFromId> result = new RelationTable <TToId, TFromId>(RelatedTable, BaseTableOpt); // We will use result.Values to accumulate the counts as usual. result.SingleValues.Fill(RelatedTable.Count, 0); // And we will use result.m_offsets to store the offsets as usual. result.Offsets.Fill(RelatedTable.Count, 0); int sum = 0; foreach (TFromId id in BaseTableOpt.Ids) { foreach (TToId relatedId in this[id]) { result.SingleValues[relatedId.FromId() - 1]++; sum++; } } // Now we can calculate m_offsets. result.CalculateOffsets(); // And we know the necessary size of m_relations. result.MultiValues.Capacity = sum; result.MultiValues.Fill(sum, default); // Allocate an array of positions to track how many relations we have filled in. SpannableList <int> positions = new SpannableList <int>(RelatedTable.Count + 1); positions.Fill(RelatedTable.Count + 1, 0); // And accumulate all the inverse relations. foreach (TFromId id in BaseTableOpt.Ids) { foreach (TToId relatedId in this[id]) { int relatedIdInt = relatedId.FromId() - 1; int idInt = id.FromId() - 1; int offset = result.Offsets[relatedIdInt]; int position = positions[relatedIdInt]; int relationIndex = result.Offsets[relatedIdInt] + positions[relatedIdInt]; result.MultiValues[relationIndex] = id; positions[relatedIdInt]++; if (positions[relatedIdInt] > result.SingleValues[relatedIdInt]) { // this is a logic bug, should never happen throw new Exception( $"RelationTable.Inverse: logic exception: positions[{relatedIdInt}] = {positions[relatedIdInt]}, result.SingleValues[{relatedIdInt}] = {result.SingleValues[relatedIdInt]}"); } else if (positions[relatedIdInt] == result.SingleValues[relatedIdInt]) { // all the relations for this ID are known. now, we have to sort them. Span <TFromId> finalSpan = result.MultiValues.AsSpan().Slice(result.Offsets[relatedIdInt], result.SingleValues[relatedIdInt]); SpanUtilities.Sort(finalSpan, (id1, id2) => id1.FromId().CompareTo(id2.FromId())); } } } // TODO: error check that there are no zero entries in m_relations return(result); }
/// <summary> /// Construct a Builder. /// </summary> public Builder(RelationTable <TFromId, TToId> table, int capacity = DefaultCapacity) { Table = table ?? throw new ArgumentException("Table argument must not be null"); m_list = new SpannableList <(TFromId, TToId)>(capacity); }
/// <summary> /// Construct a Builder. /// </summary> public Builder(RelationTable <TFromId, TToId> table, int capacity = DefaultCapacity) : base(table, capacity) { }