Подтвердить что ты не робот

Linq OrderBy против определенных значений

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

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

A
B
A
C
B
C
D
E

И эти переменные:

string firstPref, secondPref, thirdPref;

Когда значения заданы так:

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

Можно ли упорядочить данные так:

A
A
B
B
C
C
D
E
4b9b3361

Ответ 1

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

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 первого примера в большое число.

Ответ 2

В дополнение к @Daniel Brückner answer и заданной в конце проблеме:

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

Я думаю, что решение заключается в использовании оператора lambda вместо выражения lambda.

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 

Ответ 3

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

Ответ 4

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

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

Ответ 5

Объединение всех ответов (и более) в общее расширение 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");

Ответ 6

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

Ответ 7

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

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 для поиска целевого индекса.