/// <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);
        }
Exemple #3
0
        /// <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);
        }
Exemple #4
0
        /// <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);
        }
Exemple #5
0
 /// <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);
 }
Exemple #6
0
 /// <summary>
 /// Construct a Builder.
 /// </summary>
 public Builder(RelationTable <TFromId, TToId> table, int capacity = DefaultCapacity)
     : base(table, capacity)
 {
 }