2009-04-08 124 views
32

Linq是否有辦法在不知道值的順序的情況下針對一組值(字符串)執行OrderBy?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 
+3

你是什麼意思?也許你應該展示一個沒有像常規OrderBy一樣的結果的例子。 – Guffa 2009-04-08 02:51:25

+4

我同意,你的例子是可怕的;) – 2009-04-08 02:53:32

+0

var usersWithClue =從訪客線索> 0;返回空枚舉。 – 2009-04-08 03:17:09

回答

83

如果你把你的喜好到一個列表,它可能會變得更加容易。

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變成一個大數字。

1

是的,你必須實現自己的IComparer<string>,然後通過它在作爲LINQ的OrderBy方法的第二個參數。

一個例子可以在這裏找到: Ordering LINQ results

0

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

將首選值放在字典中。在字典中查找鍵是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 
); 
7

除了@Daniel布魯克納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 
1

合併所有的答案(和更多)爲通用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 - 卸載():

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

沒有真正有效的大名單,但很容易閱讀:

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找到目標指數。