2009-04-08 6 views
32

¿Existe alguna manera en Linq de realizar un pedido por un conjunto de valores (cadenas en este caso) sin conocer el orden de los valores?Orden de LinqPor valores específicos

Considere estos datos:

A 
B 
A 
C 
B 
C 
D 
E 

Y estas variables:

cadena firstPref, secondPref, thirdPref;

Cuando los valores se establecen de este modo:

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

¿Es posible ordenar los datos de este modo:

A 
A 
B 
B 
C 
C 
D 
E 
+3

¿Qué quieres decir? Tal vez debería mostrar un ejemplo que no tenga exactamente el mismo resultado que un OrderBy normal. – Guffa

+4

Acepto, su ejemplo es terrible;) –

+0

var usersWithClue = seleccionar personas de los visitantes donde pista> 0; devuelve enumeración vacía. –

Respuesta

83

Si coloca sus preferencias en una lista, que podría ser más fácil.

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

Esto pondrá todos los elementos que no aparecen en preferences en frente porque IndexOf() vuelve -1. Un trabajo ad hoc podría estar invirtiendo preferences y ordenar el resultado descendente. Esto se vuelve bastante feo, pero funciona.

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

la solución se vuelve un poco más agradable si ConCat preferences y data.

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

no me gusta Concat() y ToList() allí. Pero por el momento no tengo una buena manera de evitar eso. Estoy buscando un buen truco para convertir el -1 del primer ejemplo en un gran número.

+2

+1 buen movimiento con esa segunda lista e indexOf. – James

+0

En realidad, el caso en el que IndexOf devuelve -1 puede resolverse simplemente ajustando preferencias. Indexef (elemento) con Math.Abs ​​(preferences.IndexOf (item)) y su solución funciona perfectamente. – James

+1

No, el uso de Math.Abs ​​mezcla los valores "no preferidos" con la segunda preferencia. Intente con algunos datos que no están principalmente ordenados para comenzar. – Guffa

1

Sí, debe implementar su propio IComparer<string> y luego pasarlo en tan el segundo argumento del método OrderBy de LINQ.

Un ejemplo se puede encontrar aquí: Ordering LINQ results

0

La solución Danbrucs es más elegante, pero aquí hay una solución que usa un IComparer personalizado. Esto podría ser útil si necesita condiciones más avanzadas para su ordenamiento.

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

Coloque los valores preferidos en un diccionario. Buscar claves en un diccionario es una operación O (1) en comparación con encontrar valores en una lista que es una operación O (n), por lo que escala mucho mejor.

Crea una cadena de clasificación para cada valor preferido para que se coloquen antes que los otros valores. Para los otros valores, el valor en sí mismo se usará como una cadena de clasificación para que estén realmente ordenados. (El uso de cualquier valor alto arbitrario solo los ubicaría al final de la lista sin clasificar).

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

Además de @ Daniel Brückner answer y problema definido en el extremo de la misma:

no me gusta Concat() y ToList() en allí. Pero por el momento no tengo una buena> manera de evitarlo.Estoy buscando un buen truco para convertir el -1 del primer> ejemplo en un gran número.

Creo que la solución es usar una declaración lambda en lugar de una expresión 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; 
        }); 

Los datos ordenado es:

foo 
bar 
baz 
corge 
qux 
quux 
+0

solución inteligente – scottsanpedro

1

combinado todas las respuestas (y más) en un caché de apoyo extensión LINQ genérico que se ocupa de cualquier tipo de datos, puede ser sensible a las mayúsculas y permite que estar encadenado con antes y después de pedido:

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

uso de muestra usando 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
No

muy eficiente para grandes listas, pero bastante fácil de leer:

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

Uso:

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

Nota: Array.IndexOf<T>(....) utiliza EqualityComparer<T>.Default para encontrar el índice de destino.

Cuestiones relacionadas