Tengo una List<TimeAndCode> de objetos. Cada objeto contiene un Code y un valor de TimeSpan .
public struct TimeAndCode { public TimeSpan Time { get; set; } public string Code { get; set; } } Los códigos que aparecen están predefinidos como "CO" , "GO" y "BT" .
Cuando la propiedad Time alcanza el mismo valor, el orden de la lista debe tener un formato determinado. por ejemplo (representación JSON para legibilidad)
[ {"Time" : "08:00:00", "Code" : "CO" }, {"Time" : "09:00:00", "Code" : "GO" }, {"Time" : "09:30:00", "Code" : "CO" }, {"Time" : "09:30:00", "Code" : "GO" }, {"Time" : "09:30:00", "Code" : "CO" }, {"Time" : "09:30:00", "Code" : "GO" }, {"Time" : "09:30:00", "Code" : "BT" }, {"Time" : "12:30:00", "Code" : "CO" }, {"Time" : "12:30:00", "Code" : "GO" }, {"Time" : "13:30:00", "Code" : "CO" }, {"Time" : "13:30:00", "Code" : "GO" }, {"Time" : "13:30:00", "Code" : "BT" }, {"Time" : "13:30:00", "Code" : "CO" }, {"Time" : "13:30:00", "Code" : "GO" } ] Entonces, el patrón es CO -> GO o GO -> BT o CO -> GO -> BT -> CO -> GO .
Puede haber una buena solución anterior if else , pero estoy buscando una solución LINQ agradable y fácil de usar. (por ejemplo list.OrderBy( x => x.Time ).RearrangeBy( x => x.Code, pattern ); )
EDITAR
El pedido personalizado solo debe realizarse cuando los códigos se encuentran en la misma franja horaria.
El patrón más fácil es:
CO -> GO -> CO -> GO -> CO -> GOPero también existe la posibilidad de que un BT pueda estar presente:
CO -> GO -> BT -> CO -> GO -> BT -> CO -> GO -> BT -> COPero también existe la posibilidad de que solo haya un BT:
CO -> GO -> BT -> GO -> GO -> CO -> GO -> CO -> GO CO -> GO -> CO -> GO -> BT -> CO -> GO -> CO -> GO CO -> GO -> CO -> GO -> CO -> GO -> BT -> CO -> GO CO -> GO -> BT -> CO -> GO -> CO -> GO -> BT -> CO -> GO Entonces, el patrón principal es CO -> GO -> BT .
EDITAR 2 Resuelto
Creé un método de extensión. Puede ser que este código sea útil para alguien. (margen de mejora)
public static IEnumerable<TSource> RearrangeByPattern<TSource, TKey>(this IEnumerable<TSource> list, Func<TSource, TKey> keySelector, IEnumerable<TKey> pattern) { var groups = list.GroupBy(keySelector).OrderBy(x => pattern.IndexOf(x.Key)); var maxOccurences = groups.Select(x => x.Count()).Max(); var result = new List<TSource>(); for (var i = 0; i < maxOccurences; i++) { foreach (var group in groups) { if (group.Count() > i) { result.Add(group.ElementAt(i)); } } } return result; }Ahora puedo usarlo así:
var list = new List<TimeAndCode>(); ... // add values var ordered = list.OrderBy(x => x.Date).RearrangeByPattern( x => x.Code, new string[] { "CO", "GO", "BT" });Aquí hay una sugerencia donde TimeAndCode.Code es una enum en lugar de una string .
Siempre ordenará las entradas con intervalos de tiempo idénticos en un patrón repetido de CO , GO , BT ; lo que significa que si, por ejemplo, cinco entradas con intervalos de tiempo idénticos tienen la siguiente selección de Code : 2 x CO , 1 x BT , 2 x GO , siempre las ordenará como CO , GO , BT , CO , GO (a diferencia de CO , GO , CO , GO , BT ).
Logro esto generando una propiedad OrderBy basada en el intervalo de tiempo, un índice (generado dentro de un grupo anidado) y el valor del Code numérico para cada entrada.
Usando los siguientes tipos:
public struct TimeAndCode { public TimeSpan Time { get; set; } public Code Code { get; set; } } public enum Code { Undefined, CO, GO, BT }podemos escribir la siguiente expresión:
List<TimeAndCode> result = list .GroupBy(entry => entry.Code) .SelectMany(gr => gr .GroupBy(entry => entry.Time) .SelectMany(gr => gr.Select((entry, index) => ( OrderBy: entry.Time.ToString() + index + (int)entry.Code, TimeAndCode: entry)))) .OrderBy(entry => entry.OrderBy) .Select(entry => entry.TimeAndCode) .ToList(); donde list es List<TimeAndCode> .
Usando la entrada de ejemplo de la siguiente manera:
List<TimeAndCode> list = new List<TimeAndCode> { new TimeAndCode { Time = new TimeSpan(09, 00, 00), Code = Code.GO }, new TimeAndCode { Time = new TimeSpan(13, 30, 00), Code = Code.BT }, new TimeAndCode { Time = new TimeSpan(09, 30, 00), Code = Code.GO }, new TimeAndCode { Time = new TimeSpan(09, 30, 00), Code = Code.GO }, new TimeAndCode { Time = new TimeSpan(13, 30, 00), Code = Code.GO }, new TimeAndCode { Time = new TimeSpan(08, 00, 00), Code = Code.CO }, new TimeAndCode { Time = new TimeSpan(09, 30, 00), Code = Code.BT }, new TimeAndCode { Time = new TimeSpan(12, 30, 00), Code = Code.CO }, new TimeAndCode { Time = new TimeSpan(09, 30, 00), Code = Code.CO }, new TimeAndCode { Time = new TimeSpan(12, 30, 00), Code = Code.GO }, new TimeAndCode { Time = new TimeSpan(13, 30, 00), Code = Code.CO }, new TimeAndCode { Time = new TimeSpan(13, 30, 00), Code = Code.CO }, new TimeAndCode { Time = new TimeSpan(09, 30, 00), Code = Code.CO }, new TimeAndCode { Time = new TimeSpan(13, 30, 00), Code = Code.GO }, };, después de aplicar la expresión Linq, podemos imprimir el resultado
foreach (var entry in result) { Console.WriteLine("Time: " + entry.Time + " Code: " + entry.Code); }y obtener el siguiente resultado:
Time: 08:00:00 Code: CO Time: 09:00:00 Code: GO Time: 09:30:00 Code: CO Time: 09:30:00 Code: GO Time: 09:30:00 Code: BT Time: 09:30:00 Code: CO Time: 09:30:00 Code: GO Time: 12:30:00 Code: CO Time: 12:30:00 Code: GO Time: 13:30:00 Code: CO Time: 13:30:00 Code: GO Time: 13:30:00 Code: BT Time: 13:30:00 Code: CO Time: 13:30:00 Code: GOEs una variación de Astrid:
var r = list.GroupBy(tc => tc) .SelectMany(g => g.Select((tc, i) => (tc, i))) .OrderBy(t => (t.tc.Time, ti, t.tc.Code)) .Select(t => t.tc);Con la siguiente configuración precursora:
public record TimeAndCode(TimeSpan Time, Code Code); public enum Code { CO, GO, BT } ... var list = new List<TimeAndCode> { new (TimeSpan.FromHours(8), Code.CO), new (TimeSpan.FromHours(9), Code.GO), new (TimeSpan.FromHours(9.5), Code.CO), new (TimeSpan.FromHours(9.5), Code.GO), new (TimeSpan.FromHours(9.5), Code.GO), new (TimeSpan.FromHours(9.5), Code.CO), new (TimeSpan.FromHours(9.5), Code.BT), new (TimeSpan.FromHours(12.5), Code.CO), new (TimeSpan.FromHours(12.5), Code.GO), new (TimeSpan.FromHours(13.5), Code.CO), new (TimeSpan.FromHours(13.5), Code.BT), new (TimeSpan.FromHours(13.5), Code.GO), new (TimeSpan.FromHours(13.5), Code.CO), new (TimeSpan.FromHours(13.5), Code.GO), };TimeAndCode es un registro, lo que significa que adquiere algunas propiedades útiles para ordenar y comparar. Se puede agrupar simplemente porque es automáticamente igual a otro TimeAndCode con los mismos datos
La agrupación por tiempo y código da como resultado una lista de listas; los dos 9:30 CO s van en una lista. Pasar esto a Select((tc,i) significa que i es 0 para el primero y 1 para el segundo, etc. Todo lo que hacemos con esto es promocionarlo a una tupla de tc e i porque los necesitaremos más tarde , y seleccionamos Muchos para deshacer el GroupBy que nos permitía contar el mismo Tiempo/Código
Ordenamos por otra tupla; las tuplas se ordenan por sus valores en orden, así que para ordenar por 3 cosas a, b y c, podemos ordenar por una tupla de (a, b, c)
Y todo lo que queda por hacer al final es seleccionar el tc back para hacer una lista de TimeAndCode, ordenada algo como lo que desea
Al igual que Astrid, tampoco pone el BT al final de las 9:30, sino que va en el medio, como lo hace el de las 13:30. Pero no has proporcionado una explicación de por qué el BT de las 9:30 está al final. ..