Linq OrderBy против определенных значений - PullRequest
38 голосов
/ 08 апреля 2009

Есть ли способ в Linq сделать OrderBy для набора значений (в данном случае строк), не зная порядка значений?

Рассмотрим эти данные:

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

Ответы [ 7 ]

98 голосов
/ 08 апреля 2009

Если вы поместите ваши предпочтения в список, это может стать проще.

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

Это поместит все элементы, которые не отображаются в preferences, потому что IndexOf() возвращает -1. Временное решение может обернуться preferences и упорядочить результат по убыванию. Это становится ужасно, но работает.

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

Решение становится немного лучше, если вы согласны preferences и data.

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

Мне не нравятся Concat() и ToList() там. Но на данный момент у меня нет действительно хорошего способа обойти это. Я ищу хороший трюк, чтобы превратить -1 первого примера в большое число.

16 голосов
/ 18 мая 2016

В дополнение к @Daniel Brü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 
4 голосов
/ 08 апреля 2009

Поместите предпочтительные значения в словарь. Поиск ключей в словаре - это операция 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
);
1 голос
/ 12 июля 2016

Объединяет все ответы (и более) в общее расширение 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");
1 голос
/ 08 апреля 2009

Да, вы должны реализовать свой собственный IComparer<string> и затем передать его в качестве второго аргумента метода LINQ OrderBy.

Пример можно найти здесь: Заказ результатов LINQ

0 голосов
/ 04 октября 2017

Не очень эффективно для больших списков, но довольно легко читается:

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 голосов
/ 08 апреля 2009

Решение 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);
        }
    }
...