2009-04-08 4 views
32

Czy w Linq istnieje sposób na wykonanie OrderBy przeciwko zestawowi wartości (w tym przypadku łańcuchów) bez znajomości kolejności wartości?Linq OrderBy pod konkretne wartości

Rozważmy te dane:

A 
B 
A 
C 
B 
C 
D 
E 

a te zmienne:

ciąg firstPref, secondPref, thirdPref;

Gdy wartości są ustawione tak:

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

Czy można zamówić dane tak:

A 
A 
B 
B 
C 
C 
D 
E 
+3

Jak masz na myśli? Być może powinieneś pokazać przykład, który nie ma dokładnie takiego samego wyniku jak zwykły OrderBy. – Guffa

+4

Zgadzam się, twój przykład jest okropny;) –

+0

var usersWithClue = wybierz osoby od odwiedzających, gdzie wskazówka> 0; zwraca puste wyliczenie. –

Odpowiedz

83

Jeśli umieścisz swoje preferencje na listę, to może stać się łatwiejsze.

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

To będzie umieścić wszystkie elementy nie występujące w preferences przed ponieważ IndexOf() powraca -1. Praca ad hoc może być odwrócona preferences i kolejność malejąca. To staje się dość brzydkie, ale działa.

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

Rozwiązaniem staje się nieco ładniejszy jeśli Concat preferences i data.

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

Nie lubię Concat() i ToList() tam. Ale na razie nie mam na to naprawdę dobrego wyjścia. Poszukuję miłej sztuczki, aby zmienić pierwszy przykład na dużą liczbę.

+2

+1 ładny ruch z tą drugą listą i indexOf. – James

+0

W rzeczywistości przypadek, w którym IndexOf zwraca -1, można rozwiązać, po prostu zawijając preferencje.IndexOf (element) z Math.Abs ​​(preferences.IndexOf (element)) i twoje rozwiązanie działa idealnie. – James

+1

Nie, użycie Math.Abs ​​łączy wartości "niezrealizowane" z drugą preferencją. Spróbuj z niektórymi danymi, które nie są w większości sortowane na początek. – Guffa

1

Tak, trzeba zaimplementować własną IComparer<string> a następnie przekazać go w jak drugi argument metody OrderBy LINQ.

Przykład można znaleźć tutaj: Ordering LINQ results

0

Rozwiązanie Danbrucs jest bardziej eleganckie, ale tutaj jest rozwiązanie z wykorzystaniem niestandardowego programu IComparer. Może to być przydatne, jeśli potrzebujesz bardziej zaawansowanych warunków dla porządku sortowania.

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

Umieść preferowane wartości w słowniku. Wyszukiwanie kluczy w słowniku jest operacją O (1) w porównaniu do znajdowania wartości na liście, która jest operacją O (n), więc skaluje się znacznie lepiej.

Utwórz ciąg sortowania dla każdej preferowanej wartości, aby umieścić je przed innymi wartościami. W przypadku pozostałych wartości sama wartość będzie używana jako ciąg sortujący, tak aby były rzeczywiście posortowane. (Użycie dowolnej arbitralnej wartości wysokiej spowoduje umieszczenie ich tylko na końcu listy nieposortowanej).

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

Oprócz @Daniel Brückner answer i problemu zdefiniowanego na jej końcu:

nie jak Concat() i ToList() w środku. Ale w tej chwili nie potrafię tego obejść.Szukam miłej sztuczki, aby zmienić -1 pierwszego> przykładu na dużą liczbę.

Myślę, że rozwiązaniem jest użycie instrukcji lambda zamiast wyrażenia 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; 
        }); 

Uporządkowana danych:

foo 
bar 
baz 
corge 
qux 
quux 
+0

sprytne rozwiązanie – scottsanpedro

1

połączeniu wszystkie odpowiedzi (i więcej) do rodzajowy rozszerzenia LINQ buforowania nośnej, która obsługuje dowolny typ danych, może być wielkość liter i pozwala być przykuty z pre- i post-Kolejność:

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

wykorzystanie próbki za pomocą 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

Nie bardzo skuteczny dla dużych list, ale dość łatwe do odczytania:

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

Zastosowanie:

var orderedData = data.OrderBy(x => x, new FixedOrderComparer<string>("A", "B", "C")); 

Uwaga: Array.IndexOf<T>(....) wykorzystuje EqualityComparer<T>.Default znaleźć indeks docelowej.