2009-04-08 17 views
32

C'è un modo in Linq di eseguire un ordine con un insieme di valori (stringhe in questo caso) senza conoscere l'ordine dei valori?Ordine Linq in base a valori specifici

Considerate questi dati:

A 
B 
A 
C 
B 
C 
D 
E 

E queste variabili:

stringa firstPref, secondPref, thirdPref;

Quando i valori sono impostati in questo modo:

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

E 'possibile ordinare i dati in questo modo:

A 
A 
B 
B 
C 
C 
D 
E 
+3

Come intendete? Forse dovresti mostrare un esempio che non ha lo stesso risultato di un normale OrderBy. – Guffa

+4

Sono d'accordo, il tuo esempio è terribile;) –

+0

var usersWithClue = seleziona le persone dai visitatori dove indizio> 0; restituisce l'enumerazione vuota. –

risposta

83

Se mettete le vostre preferenze in una lista, potrebbe diventare più facile.

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

Questo metterà tutti gli elementi non appaiono nelle preferences davanti perchè IndexOf() rendimenti -1. Una soluzione ad hoc potrebbe essere invertire preferences e ordinare il risultato decrescente. Questo diventa abbastanza brutto, ma funziona.

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

La soluzione diventa un po 'più bello se si concat preferences e data.

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

non mi piace Concat() e ToList() in là. Ma per il momento non ho davvero un buon modo per aggirare questo. Sto cercando un bel trucco per trasformare lo -1 del primo esempio in un grande numero.

+2

+1 bella mossa con quella seconda lista e indexOf. – James

+0

In realtà il caso in cui IndexOf restituisce -1 può essere risolto semplicemente spostando le preferenze.IndexOf (elemento) con Math.Abs ​​(preferences.IndexOf (item)) e la soluzione funziona perfettamente. – James

+1

No, usando Math.Abs ​​mischia i valori "non selezionati" con la seconda preferenza. Prova con alcuni dati che non sono per lo più ordinati per cominciare. – Guffa

1

Sì, è necessario implementare il proprio IComparer<string> e poi passarlo come il secondo argomento del metodo OrderBy di LINQ.

Un esempio può essere trovato qui: Ordering LINQ results

0

La soluzione Danbrucs è più elegante, ma questa è una soluzione che utilizza un IComparer personalizzato. Questo potrebbe essere utile se hai bisogno di condizioni più avanzate per il tuo ordinamento.

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

Inserire i valori preferiti in un dizionario. La ricerca di chiavi in ​​un dizionario è un'operazione di tipo O (1) rispetto alla ricerca di valori in una lista che è un'operazione di tipo O (n), quindi è molto migliore.

Creare una stringa di ordinamento per ciascun valore preferito in modo che siano posizionati prima degli altri valori. Per gli altri valori, il valore stesso verrà utilizzato come stringa di ordinamento in modo che vengano effettivamente ordinati. (L'utilizzo di qualsiasi valore alto arbitrario li posizionerebbe solo alla fine della lista non ordinati).

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

Oltre a @ Daniel Brückner answer e problema definito alla fine di esso:

non amo Concat() e ToList() in là. Ma per il momento non ho davvero un buon senso.Sto cercando un bel trucco per trasformare il -1 del primo> esempio in un grande numero.

Penso che la soluzione sia usare una dichiarazione lambda anziché un'espressione 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; 
        }); 

I dati ordinato è:

foo 
bar 
baz 
corge 
qux 
quux 
+0

soluzione intelligente – scottsanpedro

1

combinata tutte le risposte (e altri) in una cache di supporto generico estensione LINQ che gestisce qualsiasi tipo di dati, può essere insensibile minuscole e permette di essere concatenato con pre e post-ordinazione:

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

utilizzo di esempio utilizzando 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
Non

realmente efficace per grandi liste, ma abbastanza facile da leggere:

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

Usage:

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

Nota: Array.IndexOf<T>(....) utilizza EqualityComparer<T>.Default per trovare l'indice di destinazione.

Problemi correlati