.netlinqsortingtopological-sortpartial-ordering

Topological Sorting using LINQ


I have a list of items that have a partial order relation, i. e, the list can be considered a partially ordered set. I want to sort this list in the same way as in this question. As correctly answered there, this is known as topological sorting.

There's a reasonably simple known algorithm to solve the problem. I want a LINQ-like implementation of it.

I already tried to use OrderBy extension method, but I'm quite sure it's not able to make topological sorting. The problem is that the IComparer<TKey> interface is not able to represent a partial order. This happens because the Compare method can return basically 3 kinds of values: zero, negative, and positive, meaning are-equal, is-less-than, and is-greater-then, respectively. A working solution would only be possible if there were a way to return are-unrelated.

From my biased point of view, the answer I'm looking for might be composed by an IPartialOrderComparer<T> interface and an extension method like this:

public static IOrderedEnumerable<TSource> OrderBy<TSource, TKey>(
    this IEnumerable<TSource> source,
    Func<TSource, TKey> keySelector,
    IPartialOrderComparer<TKey> comparer
);

How would this be implemented? How does the IPartialOrderComparer<T> interface would look like? Would you recommend a different approach? I'm eager to see it. Maybe there's a nicer way to represent the partial order, I don't know.


Solution

  • I would suggest using the same IComparer interface, but writing the extension method so as to interpret 0 as not related. In a partial ordering, if elements a and b are equal their order doesn't matter, like-wise if they are unrelated - you only have to order them with respect to elements with which they have defined relationships.

    Here's an example that does a partial ordering of even and odd integers:

    namespace PartialOrdering
    {
        public static class Enumerable
        {
            public static IEnumerable<TSource> PartialOrderBy<TSource, TKey>(this IEnumerable<TSource> source, Func<TSource, TKey> keySelector, IComparer<TKey> comparer)
            {
                List<TSource> list = new List<TSource>(source);
                while (list.Count > 0)
                {
                    TSource minimum = default(TSource);
                    TKey minimumKey = default(TKey);
                    foreach (TSource s in list)
                    {
                        TKey k = keySelector(s);
                        minimum = s;
                        minimumKey = k;
                        break;
                    }
                    foreach (TSource s in list)
                    {
                        TKey k = keySelector(s);
                        if (comparer.Compare(k, minimumKey) < 0)
                        {
                            minimum = s;
                            minimumKey = k;
                        }
                    }
                    yield return minimum;
                    list.Remove(minimum);
                }
                yield break;
            }
    
        }
        public class EvenOddPartialOrdering : IComparer<int>
        {
            public int Compare(int a, int b)
            {
                if (a % 2 != b % 2)
                    return 0;
                else if (a < b)
                    return -1;
                else if (a > b)
                    return 1;
                else return 0; //equal
            }
        }
        class Program
        {
            static void Main(string[] args)
            {
                IEnumerable<Int32> integers = new List<int> { 8, 4, 5, 7, 10, 3 };
                integers = integers.PartialOrderBy<Int32, Int32>(new Func<Int32, Int32>(delegate(int i) { return i; }), new EvenOddPartialOrdering());
            }
        }
    }
    

    Result: 4, 8, 3, 5, 7, 10