web-dev-qa-db-ja.com

特定の値に対するLinq OrderBy

値の順序を知らずに一連の値(この場合は文字列)に対してOrderByを実行する方法はLinqにありますか?

このデータを検討してください:

A
B
A
C
B
C
D
E

そしてこれらの変数:

文字列firstPref、secondPref、thirdPref;

値が次のように設定されている場合:

firstPref = 'A';
secondPref = 'B';
thirdPref = 'C';

次のようにデータを注文することは可能ですか?

A
A
B
B
C
C
D
E
41
Jonathan Parker

設定をリストに入れると、簡単になる場合があります。

_List<String> data = new List<String> { "A","B","A","C","B","C","D","E" };
List<String> preferences = new List<String> { "A","B","C" };

IEnumerable<String> orderedData = data.OrderBy(
   item => preferences.IndexOf(item));
_

IndexOf()は_-1_を返すため、これによりpreferencesに表示されないすべてのアイテムが前面に表示されます。その場しのぎの回避策は、preferencesを逆にして、結果を降順に並べることです。これはかなり醜くなりますが、うまくいきます。

_IEnumerable<String> orderedData = data.OrderByDescending(
   item => Enumerable.Reverse(preferences).ToList().IndexOf(item));
_

preferencesdataを連結すると、ソリューションは少し良くなります。

_IEnumerable<String> orderedData = data.OrderBy(
   item => preferences.Concat(data).ToList().IndexOf(item));
_

そこにConcat()ToList()が好きではありません。しかし今のところ、それを回避する方法は本当にありません。最初の例の_-1_を大きな数に変換するための素敵なトリックを探しています。

108

@DanielBrückner answer に加えて、その最後に定義された問題:

そこにあるConcat()とToList()は好きではありません。しかし今のところ、それを回避する良い方法はありません。 >最初の例の-1を大きな数に変換するための素敵なトリックを探しています。

解決策は、式ラムダの代わりに文ラムダを使用することだと思います。

var data = new List<string> { "corge", "baz", "foo", "bar", "qux", "quux" };
var fixedOrder = new List<string> { "foo", "bar", "baz" };
data.OrderBy(d => {
                    var index = fixedOrder.IndexOf(d);
                    return index == -1 ? int.MaxValue : index; 
                  });

順序付けられたデータは次のとおりです。

foo 
bar 
baz 
corge 
qux 
quux 
19
alexqc

推奨値を辞書に入れます。辞書でキーを検索することは、O(1)操作であるリスト内の値を見つけることと比較して、O(n)操作です。ずっといい。

優先値ごとにソート文字列を作成して、他の値の前に配置されるようにします。その他の値については、値自体がソート文字列として使用され、実際にソートされます。 (任意の高い値を使用すると、ソートされていないリストの最後にのみ配置されます)。

List<string> data = new List<string> {
    "E", "B", "D", "A", "C", "B", "A", "C"
};
var preferences = new Dictionary<string, string> {
    { "A", " 01" },
    { "B", " 02" },
    { "C", " 03" }
};

string key;
IEnumerable<String> orderedData = data.OrderBy(
    item => preferences.TryGetValue(item, out key) ? key : item
);
5
Guffa

すべての回答(およびその他)を組み合わせて、任意のデータ型を処理するキャッシングをサポートする汎用LINQ拡張機能に組み込み、大文字と小文字を区別せず、前後の順序でチェーンすることができます。

public static class SortBySample
{
    public static BySampleSorter<TKey> Create<TKey>(IEnumerable<TKey> fixedOrder, IEqualityComparer<TKey> comparer = null)
    {
        return new BySampleSorter<TKey>(fixedOrder, comparer);
    }

    public static BySampleSorter<TKey> Create<TKey>(IEqualityComparer<TKey> comparer, params TKey[] fixedOrder)
    {
        return new BySampleSorter<TKey>(fixedOrder, comparer);
    }

    public static IOrderedEnumerable<TSource> OrderBySample<TSource, TKey>(this IEnumerable<TSource> source, Func<TSource, TKey> keySelector, BySampleSorter<TKey> sample)
    {
        return sample.OrderBySample(source, keySelector);
    }

    public static IOrderedEnumerable<TSource> ThenBySample<TSource, TKey>(this IOrderedEnumerable<TSource> source, Func<TSource, TKey> keySelector, BySampleSorter<TKey> sample)
    {
        return sample.ThenBySample(source, keySelector);
    }
}

public class BySampleSorter<TKey>
{
    private readonly Dictionary<TKey, int> dict;

    public BySampleSorter(IEnumerable<TKey> fixedOrder, IEqualityComparer<TKey> comparer = null)
    {
        this.dict = fixedOrder
            .Select((key, index) => new KeyValuePair<TKey, int>(key, index))
            .ToDictionary(kv => kv.Key, kv => kv.Value, comparer ?? EqualityComparer<TKey>.Default);
    }

    public BySampleSorter(IEqualityComparer<TKey> comparer, params TKey[] fixedOrder)
        : this(fixedOrder, comparer)
    {
    }

    public IOrderedEnumerable<TSource> OrderBySample<TSource>(IEnumerable<TSource> source, Func<TSource, TKey> keySelector)
    {
        return source.OrderBy(item => this.GetOrderKey(keySelector(item)));
    }

    public IOrderedEnumerable<TSource> ThenBySample<TSource>(IOrderedEnumerable<TSource> source, Func<TSource, TKey> keySelector)
    {
        return source.CreateOrderedEnumerable(item => this.GetOrderKey(keySelector(item)), Comparer<int>.Default, false);
    }

    private int GetOrderKey(TKey key)
    {
        int index;
        return dict.TryGetValue(key, out index) ? index : int.MaxValue;
    }
}

LINQPad-Dump()を使用した使用例:

var sample = SortBySample.Create(StringComparer.OrdinalIgnoreCase, "one", "two", "three", "four");
var unsorted = new[] {"seven", "six", "five", "four", "THREE", "tWo", "One", "zero"};
unsorted
    .OrderBySample(x => x, sample)
    .ThenBy(x => x)
    .Dump("sorted by sample then by content");
unsorted
    .OrderBy(x => x.Length)
    .ThenBySample(x => x, sample)
    .Dump("sorted by length then by sample");
2
springy76

はい、自分で実装する必要がありますIComparer<string>し、それをLINQのOrderByメソッドの2番目の引数として渡します。

例はここにあります: LINQ結果の順序付け

1
Ben Hoffstein

大きなリストにはあまり効果的ではありませんが、かなり読みやすいです:

_public class FixedOrderComparer<T> : IComparer<T>
{
    private readonly T[] fixedOrderItems;

    public FixedOrderComparer(params T[] fixedOrderItems)
    {
        this.fixedOrderItems = fixedOrderItems;
    }

    public int Compare(T x, T y)
    {
        var xIndex = Array.IndexOf(fixedOrderItems, x);
        var yIndex = Array.IndexOf(fixedOrderItems, y);
        xIndex = xIndex == -1 ? int.MaxValue : xIndex;
        yIndex = yIndex == -1 ? int.MaxValue : yIndex;
        return xIndex.CompareTo(yIndex);
    }
}
_

使用法:

_var orderedData = data.OrderBy(x => x, new FixedOrderComparer<string>("A", "B", "C"));
_

注:Array.IndexOf<T>(....)は、_EqualityComparer<T>.Default_を使用してターゲットインデックスを検索します。

0
Mike Rowley

Danbrucsソリューションはよりエレガントですが、ここではカスタムIComparerを使用したソリューションを示します。これは、並べ替え順序にさらに高度な条件が必要な場合に役立ちます。

    string[] svals = new string[] {"A", "B", "A", "C", "B", "C", "D", "E"};
    List<string> list = svals.OrderBy(a => a, new CustomComparer()).ToList();

    private class CustomComparer : IComparer<string>
    {
        private string firstPref = "A";
        private string secondPref = "B";
        private string thirdPref = "C";
        public int Compare(string x, string y)
        {
            // first pref 
            if (y == firstPref && x == firstPref)
                return 0;
            else if (x == firstPref && y != firstPref)
                return -1;
            else if (y == firstPref && x != firstPref)
                return 1;
            // second pref
            else if (y == secondPref && x == secondPref)
                return 0;
            else if (x == secondPref && y != secondPref)
                return -1;
            else if (y == secondPref && x != secondPref)
                return 1;
            // third pref
            else if (y == thirdPref && x == thirdPref)
                return 0;
            else if (x == thirdPref && y != thirdPref)
                return -1;
            else
                return string.Compare(x, y);
        }
    }
0
James