2009-04-08 5 views
32

Y at-il un moyen dans Linq de faire un OrderBy par rapport à un ensemble de valeurs (chaînes dans ce cas) sans connaître l'ordre des valeurs?Linq OrderBy par rapport à des valeurs spécifiques

Tenez compte de ces données:

A 
B 
A 
C 
B 
C 
D 
E 

Et ces variables:

chaîne firstPref, secondPref, thirdPref;

Lorsque les valeurs sont définies comme ceci:

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

Est-il possible de commander les données comme ceci:

A 
A 
B 
B 
C 
C 
D 
E 
+3

Comment voulez-vous dire? Peut-être que vous devriez montrer un exemple qui n'a pas exactement le même résultat qu'un OrderBy normal. – Guffa

+4

Je suis d'accord, votre exemple est terrible;) –

+0

var usersWithClue = sélectionner des personnes parmi les visiteurs où indice> 0; renvoie l'énumération vide. –

Répondre

83

Si vous mettez vos préférences dans une liste, il pourrait devenir plus 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)); 

Cela mettra tous les éléments ne figurant pas dans preferences devant parce que IndexOf() retours -1. Un travail ad hoc autour peut être preferences et ordonner le résultat décroissant. Cela devient assez moche, mais ça marche.

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

La solution devient un peu plus agréable si vous concat preferences et data.

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

Je n'aime pas Concat() et ToList() là-dedans. Mais pour le moment, je n'ai pas vraiment de bonne solution. Je suis à la recherche d'une bonne astuce pour transformer le -1 du premier exemple en un grand nombre.

+2

+1 joli mouvement avec cette deuxième liste et indexOf. – James

+0

En fait, le cas où IndexOf renvoie -1 peut être résolu simplement en enveloppant les préférences. IndexOf (item) avec Math.Abs ​​(preferences.IndexOf (item)) et votre solution fonctionne parfaitement. – James

+1

Non, l'utilisation de Math.Abs ​​mélange les valeurs "non préférées" avec la deuxième préférence. Essayez avec des données qui ne sont pas principalement triées pour commencer. – Guffa

1

Oui, vous devez implémenter votre propre IComparer<string> et passer ensuite en tant que le deuxième argument de la méthode OrderBy de LINQ.

Un exemple peut être trouvé ici: Ordering LINQ results

0

La solution de Danbrucs est plus élégante, mais voici une solution utilisant un IComparer personnalisé. Cela peut être utile si vous avez besoin de conditions plus avancées pour votre ordre de tri.

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

Placez les valeurs préférées dans un dictionnaire. La recherche de clés dans un dictionnaire est une opération O (1) comparée à la recherche de valeurs dans une liste qui est une opération O (n), donc elle évolue beaucoup mieux.

Créez une chaîne de tri pour chaque valeur préférée afin qu'elle soit placée avant les autres valeurs. Pour les autres valeurs, la valeur elle-même sera utilisée comme chaîne de tri afin qu'elle soit effectivement triée. (Utiliser n'importe quelle valeur élevée arbitraire ne les placerait qu'à la fin de la liste non triée).

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

En plus de Brückner answer et @ Daniel problème défini à la fin de celui-ci:

Je n'aime pas concat() et ToList() là-dedans. Mais pour l'instant, je n'ai pas vraiment de bonne solution.Je cherche un bon truc pour transformer le -1 du premier exemple en un grand nombre.

Je pense que la solution est d'utiliser une déclaration lambda au lieu d'une expression 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; 
        }); 

Les données ordonné est:

foo 
bar 
baz 
corge 
qux 
quux 
+0

solution intelligente – scottsanpedro

1

combiné toutes les réponses (et plus) dans une extension LINQ générique de mise en cache de support qui gère tout type de données, peuvent être insensibles à la casse et permet d'être enchaînés avec avant et après la commande:

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

utilisation de l'échantillon en utilisant 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

Pas vraiment efficace pour les grandes listes, mais assez facile à lire:

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

Utilisation:

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

Note: Array.IndexOf<T>(....) utilise EqualityComparer<T>.Default pour trouver l'indice cible.

Questions connexes