Linq OrderBy against specific values

后端 未结 8 1723
悲&欢浪女
悲&欢浪女 2020-12-04 17:41

Is there a way in Linq to do an OrderBy against a set of values (strings in this case) without knowing the order of the values?

Consider this data:

A         


        
相关标签:
8条回答
  • 2020-12-04 18:25

    I use these. I like the IEnumerable overload for cleanliness, but the priority map version should have better performance on repeated calls.

        public static IEnumerable<T> OrderByStaticList<T>(this IEnumerable<T> items, IReadOnlyDictionary<T, double> priorityMap)
        {
            return items.OrderBy(x => priorityMap.GetValueOrDefault(x, double.MaxValue));
        }
    
        public static IEnumerable<T> OrderByStaticList<T>(this IEnumerable<T> items, IEnumerable<T> preferenceOrder)
        {
            int priority = 0;
            var priorityMap = preferenceOrder.ToDictionary(x => x, x => (double) priority++);
            return OrderByStaticList(items, priorityMap);
        }
    
    
    
    
        [TestMethod]
        public void PriorityMap_DeterminesSort()
        {
            var map = new Dictionary<char, double>()
            {
                {'A', 1},
                {'B', 7},
                {'C', 3},
                {'D', 42},
                {'E', -1},
            };
            Assert.AreEqual("EACBD", new string("ABCDE".OrderByStaticList(map).ToArray()));
        }
    
        [TestMethod]
        public void PriorityMapMissingItem_SortsLast()
        {
            var map = new Dictionary<char, double>()
            {
                {'A', 1},
                {'B', 7},
                {'D', 42},
                {'E', -1},
            };
            Assert.AreEqual("EABDC", new string("ABCDE".OrderByStaticList(map).ToArray()));
        }
    
        [TestMethod]
        public void OrderedList_DeterminesSort()
        {
            Assert.AreEqual("EACBD", new string("ABCDE".OrderByStaticList("EACBD").ToArray()));
        }
    
        [TestMethod]
        public void OrderedListMissingItem_SortsLast()
        {
            Assert.AreEqual("EABDC", new string("ABCDE".OrderByStaticList("EABD").ToArray()));
        }
    
    0 讨论(0)
  • 2020-12-04 18:34

    Combined all answers (and more) into a generic LINQ extension supporting caching which handles any data type, can be case-insensitive and allows to be chained with pre- and post-ordering:

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

    Sample usage using 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");
    
    0 讨论(0)
提交回复
热议问题