17

半順序関係を持つアイテムのリストがあります。e、リストは半順序集合と見なすことができます。この質問と同じ方法でこのリストを並べ替えたいと思います。そこで正しく答えられているように、これはトポロジカルソートとして知られています。

問題を解決するためのかなり単純な既知のアルゴリズムがあります。LINQのような実装が必要です。

すでにOrderBy拡張法を使ってみましたが、トポロジカルソートはできないと思います。問題は、IComparer<TKey>インターフェースが半順序を表すことができないことです。これは、Compareメソッドが基本的に3種類の値を返すことができるために発生します。つまり、ゼロ正の3種類の値を返します。つまり、それぞれ、等しいより小さいより大きいという意味 です。有効な解決策は、関係のないものを返す方法があった場合にのみ可能です。

私の偏った見方からすると、私が探している答えは、次のIPartialOrderComparer<T>ようなインターフェースと拡張メソッドによって構成されている可能性があります。

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

これはどのように実装されますか?IPartialOrderComparer<T>インターフェイスはどのようになりますか?別のアプローチをお勧めしますか?見たいです。半順序を表すためのより良い方法があるかもしれませんが、私にはわかりません。

4

6 に答える 6

16

同じIComparerインターフェイスを使用することをお勧めしますが、0を関連していないと解釈するように拡張メソッドを記述します。半順序では、要素aとbが等しい場合、それらの順序は重要ではありません。同様に、それらが無関係である場合、それらが定義された関係を持つ要素に関してのみ順序付けする必要があります。

偶数と奇数の整数の半順序を行う例を次に示します。

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());
        }
    }
}

結果:4、8、3、5、7、10

于 2009-12-30T23:26:38.327 に答える
8

これは、tehMickの回答の最適化および再生されたバージョンです。

私が行った変更の1つは、論理リストを生成するために残された値の実際のリストを置き換えることでした。このために、同じサイズの2つのサイズの配列があります。1つにはすべての値があり、もう1つには各値が生成されたかどうかを示すフラグが含まれています。このようにして、実際のサイズを変更するコストを回避できList<Key>ます。

もう1つの変更は、反復の開始時にすべてのキーを1回だけ読み取ることです。今は思い出せない理由で(たぶんそれは私の本能だったのかもしれません)、keySelector関数を何度も呼び出すという考えは好きではありません。

最後の仕上げは、パラメーターの検証と、暗黙のキー比較機能を使用する追加のオーバーロードでした。コードが十分に読めることを願っています。見てみな。

public static IEnumerable<TSource> PartialOrderBy<TSource, TKey>(
    this IEnumerable<TSource> source,
    Func<TSource, TKey> keySelector)
{
    return PartialOrderBy(source, keySelector, null);
}

public static IEnumerable<TSource> PartialOrderBy<TSource, TKey>(
    this IEnumerable<TSource> source,
    Func<TSource, TKey> keySelector,
    IComparer<TKey> comparer)
{
    if (source == null) throw new ArgumentNullException("source");
    if (keySelector == null) throw new ArgumentNullException("keySelector");
    if (comparer == null) comparer = (IComparer<TKey>)Comparer<TKey>.Default;

    return PartialOrderByIterator(source, keySelector, comparer);
}

private static IEnumerable<TSource> PartialOrderByIterator<TSource, TKey>(
    IEnumerable<TSource> source,
    Func<TSource, TKey> keySelector,
    IComparer<TKey> comparer)
{
    var values = source.ToArray();
    var keys = values.Select(keySelector).ToArray();
    int count = values.Length;
    var notYieldedIndexes = System.Linq.Enumerable.Range(0, count).ToArray();
    int valuesToGo = count;

    while (valuesToGo > 0)
    {
        //Start with first value not yielded yet
        int minIndex = notYieldedIndexes.First( i => i >= 0);

        //Find minimum value amongst the values not yielded yet
        for (int i=0; i<count; i++)
        if (notYieldedIndexes[i] >= 0)
        if (comparer.Compare(keys[i], keys[minIndex]) < 0) {
            minIndex = i;
        }

        //Yield minimum value and mark it as yielded
        yield return values[minIndex];
        notYieldedIndexes[minIndex] = -1;
        valuesToGo--;
    }
}
于 2010-01-04T15:21:38.937 に答える
2

まあ、私はそれを処理するこの方法が最善の方法であるかどうかはわかりませんが、私は間違っている可能性があります。

トポロジカルソートを処理する一般的な方法は、グラフを使用することです。反復ごとに、インバウンド接続を持たないすべてのノードを削除し、同時にそれらのノードからアウトバウンドするすべての接続を削除します。削除されたノードは、反復の出力です。これ以上ノードを削除できなくなるまで繰り返します。

ただし、最初にこれらの接続を取得するには、メソッドで次のことが必要になります。

  1. 「その前にこれ」と言うことができるが、「これら2つの情報はない」と言うことができる方法(あなたの比較者)
  2. すべての組み合わせを繰り返し、比較者が順序を返すすべての組み合わせの接続を作成します。

言い換えれば、メソッドはおそらく次のように定義されます。

public Int32? Compare(TKey a, TKey b) { ... }

そして、null2つのキーに対する決定的な答えがない場合に戻ります。

私が考えている問題は、「すべての組み合わせを繰り返す」部分です。おそらくこれを処理するためのより良い方法がありますが、私はそれを見ていません。

于 2009-12-30T22:52:14.660 に答える
1

Lasse V. Karlsenの答えは正しい方向に進んでいると思いますが、Compareメソッド(またはから拡張されていない別のインターフェイスIComparable<T>)を非表示にするのは好きではありません。

代わりに、私はむしろこのようなものを見たいです:

public interface IPartialOrderComparer<T> : IComparer<T>
{
    int? InstancesAreComparable(T x, T y);
}

IComparer<T>このようにして、を必要とする他の場所で使用できる実装がまだありますIComparer<T>

ただし、次の方法でTのインスタンスの相互関係と戻り値の関係を示す必要もあります(と同様IComparable<T>)。

  • null-インスタンスは互いに比較できません。
  • 0-インスタンスは互いに比較可能です。
  • 0-xは同等のキーですが、yはそうではありません。

  • <0-yは同等のキーですが、xはそうではありません。

もちろん、これの実装を期待するものに渡すときに半順序を取得することはありませんIComparable<T>(そして、Lasse V. Karlsenの答えもこれを解決しないことに注意してください)。必要なものはで表すことができないためです。 Tの2つのインスタンスを取り、intを返す単純なCompareメソッド。

ソリューションを完了するには、(すでに示したように)新しいインスタンスパラメーターを受け入れるカスタムOrderBy(およびThenBy、OrderByDescending、ThenByDescending)拡張メソッドを提供する必要があります。実装は次のようになります。

public static IOrderedEnumerable<TSource> OrderBy<TSource, TKey>(      
    this IEnumerable<TSource> source,      
    Func<TSource, TKey> keySelector,      
    IPartialOrderComparer<TKey> comparer)
{
    return Enumerable.OrderBy(source, keySelector,
        new PartialOrderComparer(comparer);
}

internal class PartialOrderComparer<T> : IComparer<T>
{
    internal PartialOrderComparer(IPartialOrderComparer<T> 
        partialOrderComparer)
    {
        this.partialOrderComparer = partialOrderComparer;
    }

    private readonly IPartialOrderComparer<T> partialOrderComparer;

    public int Compare(T x, T y)
    {
        // See if the items are comparable.
        int? comparable = partialOrderComparable.
            InstancesAreComparable(x, y);

        // If they are not comparable (null), then return
        // 0, they are equal and it doesn't matter
        // what order they are returned in.
        // Remember that this only to determine the
        // values in relation to each other, so it's
        // ok to say they are equal.
        if (comparable == null) return 0;

        // If the value is 0, they are comparable, return
        // the result of that.
        if (comparable.Value == 0) return partialOrderComparer.Compare(x, y);

        // One or the other is uncomparable.
        // Return the negative of the value.
        // If comparable is negative, then y is comparable
        // and x is not.  Therefore, x should be greater than y (think
        // of it in terms of coming later in the list after
        // the ordered elements).
        return -comparable.Value;            
    }
}
于 2009-12-31T00:20:45.040 に答える
1

半順序関係を定義するためのインターフェース:

interface IPartialComparer<T> {
    int? Compare(T x, T y);
}

Compare-1if x < y0if x = y1if y < xnullifxyが比較できない場合に返されます。

私たちの目標は、列挙を尊重する半順序で要素の順序を返すことです。つまり、 ifとthenに匹敵するe_1, e_2, e_3, ..., e_nような、部分的な順序で要素のシーケンスを探します。これは、深さ優先探索を使用して行います。i <= je_ie_je_i <= e_j

深さ優先探索を使用してトポロジカルソートを実装するクラス:

class TopologicalSorter {
    class DepthFirstSearch<TElement, TKey> {
        readonly IEnumerable<TElement> _elements;
        readonly Func<TElement, TKey> _selector;
        readonly IPartialComparer<TKey> _comparer;
        HashSet<TElement> _visited;
        Dictionary<TElement, TKey> _keys;
        List<TElement> _sorted;

        public DepthFirstSearch(
            IEnumerable<TElement> elements,
            Func<TElement, TKey> selector,
            IPartialComparer<TKey> comparer
        ) {
            _elements = elements;
            _selector = selector;
            _comparer = comparer;
            var referenceComparer = new ReferenceEqualityComparer<TElement>();
            _visited = new HashSet<TElement>(referenceComparer);
            _keys = elements.ToDictionary(
                e => e,
                e => _selector(e), 
                referenceComparer
            );
            _sorted = new List<TElement>();
        }

        public IEnumerable<TElement> VisitAll() {
            foreach (var element in _elements) {
                Visit(element);
            }
            return _sorted;
        }

        void Visit(TElement element) {
            if (!_visited.Contains(element)) {
                _visited.Add(element);
                var predecessors = _elements.Where(
                    e => _comparer.Compare(_keys[e], _keys[element]) < 0
                );
                foreach (var e in predecessors) {
                    Visit(e);
                }
                _sorted.Add(element);
            }
        }
    }

    public IEnumerable<TElement> ToplogicalSort<TElement, TKey>(
        IEnumerable<TElement> elements,
        Func<TElement, TKey> selector, IPartialComparer<TKey> comparer
    ) {
        var search = new DepthFirstSearch<TElement, TKey>(
            elements,
            selector,
            comparer
        );
        return search.VisitAll();
    }
}

深さ優先探索の実行中にノードを訪問済みとしてマークするために必要なヘルパークラス:

class ReferenceEqualityComparer<T> : IEqualityComparer<T> {
    public bool Equals(T x, T y) {
        return Object.ReferenceEquals(x, y);
    }

    public int GetHashCode(T obj) {
        return obj.GetHashCode();
    }
}

これがアルゴリズムの最良の実装であるとは主張しませんが、正しい実装であると信じています。また、ご要望通りの返品はいたしませんでしたIOrderedEnumerableが、この時点で簡単に返品できます。

アルゴリズムは、要素を深さ優先探索しeて線形順序付け(アルゴリズムで表される_sorted)に要素を追加することで機能します(すべての先行要素がeすでに順序付けに追加されている場合)。したがって、各要素eについて、まだアクセスしていない場合は、その前の要素にアクセスしてから、を追加しeます。したがって、これはアルゴリズムの中核です。

public void Visit(TElement element) {
    // if we haven't already visited the element
    if (!_visited.Contains(element)) {
        // mark it as visited
        _visited.Add(element);
        var predecessors = _elements.Where(
            e => _comparer.Compare(_keys[e], _keys[element]) < 0
        );
        // visit its predecessors
        foreach (var e in predecessors) {
            Visit(e);
        }
        // add it to the ordering
        // at this point we are certain that
        // its predecessors are already in the ordering
        _sorted.Add(element);
    }
}

例として、 ifがのサブセットで{1, 2, 3}ある場合のサブセットで定義された半順序を考えてみます。私はこれを次のように実装します:X < YXY

public class SetComparer : IPartialComparer<HashSet<int>> {
    public int? Compare(HashSet<int> x, HashSet<int> y) {
        bool xSubsety = x.All(i => y.Contains(i));
        bool ySubsetx = y.All(i => x.Contains(i));
        if (xSubsety) {
            if (ySubsetx) {
                return 0;
            }
            return -1;
        }
        if (ySubsetx) {
            return 1;
        }
        return null;
    }
}

次に、setsのサブセットのリストとして定義されます{1, 2, 3}

List<HashSet<int>> sets = new List<HashSet<int>>() {
    new HashSet<int>(new List<int>() {}),
    new HashSet<int>(new List<int>() { 1, 2, 3 }),
    new HashSet<int>(new List<int>() { 2 }),
    new HashSet<int>(new List<int>() { 2, 3}),
    new HashSet<int>(new List<int>() { 3 }),
    new HashSet<int>(new List<int>() { 1, 3 }),
    new HashSet<int>(new List<int>() { 1, 2 }),
    new HashSet<int>(new List<int>() { 1 })
};
TopologicalSorter s = new TopologicalSorter();
var sorted = s.ToplogicalSort(sets, set => set, new SetComparer());

これにより、次の順序になります。

{}, {2}, {3}, {2, 3}, {1}, {1, 3}, {1, 2}, {1, 2, 3}

これは半順序を尊重します。

とても楽しかったです。ありがとう。

于 2009-12-31T01:41:09.803 に答える
0

Lasse V. Karlsenが言ったように、関係がないことを示すためにnull値を使用することを好むので、Eric Mickelsenの回答から始めて、皆さんに感謝します。

public static IEnumerable<TSource> PartialOrderBy<TSource>(
        this IEnumerable<TSource> source,            
        IPartialEqualityComparer<TSource> comparer)
    {
        if (source == null) throw new ArgumentNullException("source");
        if (comparer == null) throw new ArgumentNullException("comparer");


        var set = new HashSet<TSource>(source);
        while (!set.IsEmpty())
        {
            TSource minimum = set.First();                

            foreach (TSource s in set)
            {                    
                var comparison = comparer.Compare(s, minimum);
                if (!comparison.HasValue) continue;
                if (comparison.Value <= 0)
                {
                    minimum = s;                        
                }
            }
            yield return minimum;
            set.Remove(minimum);
        }
    }

public static IEnumerable<TSource> PartialOrderBy<TSource>(
       this IEnumerable<TSource> source,
       Func<TSource, TSource, int?> comparer)
    {
        return PartialOrderBy(source, new PartialEqualityComparer<TSource>(comparer));
    }

それから私は比較者のために次のインターフェースを持っています

public interface IPartialEqualityComparer<T>
{
    int? Compare(T x, T y);
}

そしてこのヘルパークラス

internal class PartialEqualityComparer<TSource> : IPartialEqualityComparer<TSource>
{
    private Func<TSource, TSource, int?> comparer;

    public PartialEqualityComparer(Func<TSource, TSource, int?> comparer)
    {
        this.comparer = comparer;
    }

    public int? Compare(TSource x, TSource y)
    {
        return comparer(x,y);
    }
}

これにより、使用法を少し美しくすることができるので、私のテストは次のようになります

 var data = new int[] { 8,7,6,5,4,3,2,1,0 };
 var partiallyOrdered = data.PartialOrderBy((x, y) =>
     {
        if (x % 2 == 0 && y % 2 != 0) return null;
        return x.CompareTo(y);
     });
于 2016-07-12T09:31:36.240 に答える