【问题标题】:Linq OrderBy against specific valuesLinq OrderBy 针对特定值
【发布时间】:2009-04-08 02:44:36
【问题描述】:

在 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

【问题讨论】:

  • 你是什么意思?也许您应该展示一个与常规 OrderBy 的结果不完全相同的示例。
  • 我同意,你的例子很糟糕;)
  • var usersWithClue = 从线索 > 0 的访客中选择人;返回空枚举。

标签: c# linq linq-to-objects


【解决方案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));

如果你 concat preferencesdata,解决方案会变得更好。

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

我不喜欢里面的Concat()ToList()。但目前我没有很好的解决方法。我正在寻找一个很好的技巧来将第一个示例的 -1 变成一个大数字。

【讨论】:

  • 实际上 IndexOf 返回 -1 的情况可以通过简单地将preferences.IndexOf(item) 与 Math.Abs​​(preferences.IndexOf(item)) 包装来解决,并且您的解决方案可以完美运行。
  • 不,使用 Math.Abs​​ 将“不喜欢”的值与第二个偏好混合在一起。尝试使用一些最初没有排序的数据。
  • 您可以在初始化new string[] { "A", "B", "C" }.Reverse(); 中反转首选项,因此它只在对象创建时执行一次。如果您将其设为静态(在这种情况下,没有理由不能这样做),那么它将在加载类时完成,并且只加载一次。请注意,List&lt;T&gt; 隐藏了 Linq Reverse() 扩展及其自己的返回 void - 这就是我选择在这里使用数组的原因,因为它不需要是动态的。
【解决方案2】:

除了@Daniel Brückner answer 和最后定义的问题:

我不喜欢里面的 Concat() 和 ToList()。但目前我没有真正>好的解决方法。我正在寻找一个很好的技巧来将第一个 >example 的 -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 

【讨论】:

  • 聪明的解决方案
  • 可以将它用于 int 数据吗?我现在将列表转换为字符串列表,但它不起作用。
【解决方案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】:

    将所有答案(以及更多)组合到一个通用 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");
    

    【讨论】:

      【解决方案5】:

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

      【讨论】:

      • 正是我需要解决一些更高级的排序条件。谢谢。
      【解决方案6】:

      是的,您必须实现自己的IComparer&lt;string&gt;,然后将其作为 LINQ 的 OrderBy 方法的第二个参数传入。

      可以在此处找到示例: Ordering LINQ results

      【讨论】:

        【解决方案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&lt;T&gt;(....) 使用EqualityComparer&lt;T&gt;.Default 查找目标索引。

        【讨论】:

          【解决方案8】:

          我使用这些。我喜欢 IEnumerable 重载的清洁度,但优先级映射版本在重复调用时应该有更好的性能。

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

          【讨论】:

            猜你喜欢
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 2021-07-30
            相关资源
            最近更新 更多