c# - query - Linq orderby, comienza con un número específico, luego regresa al más bajo
linq c# tutorial español (11)
Método de extensión para cambiar una secuencia para comenzar en un elemento determinado. Esto también solo pasará por la secuencia original una vez, lo que puede o no ser importante. Esto también asume que la secuencia ya está ordenada de la manera que lo desea, a excepción del desplazamiento.
public static IEnumerable<T> Shift<T>(this IEnumerable<T> subject, T shouldBeFirst)
{
return subject.Shift(shouldBeFirst, EqualityComparer<T>.Default);
}
public static IEnumerable<T> Shift<T>(this IEnumerable<T> subject, T shouldBeFirst, IEqualityComparer<T> comparer)
{
var found = false;
var queue = new Queue<T>();
foreach (var item in subject)
{
if(!found)
found = comparer.Equals(item, shouldBeFirst);
if(found)
yield return item;
else
queue.Enqueue(item);
}
while(queue.Count > 0)
yield return queue.Dequeue();
}
Uso
var list = new List<int>() { 1, 2, 3, 4, 5, 6 };
foreach (var i in list.Shift(4))
Console.WriteLine(i);
Huellas dactilares
4
5
6
1
2
3
Tengo un conjunto de datos que me gustaría reordenar comenzando con un número específico y luego, cuando se alcanza el número más alto, volver al más bajo y luego continuar aumentando.
Por ejemplo, para la secuencia (1,2,3,4,5,6), si 4 era el número específico, el orden se convertiría en (4,5,6,1,2,3).
¿Es eso posible con linq & c #?
Para el caso general, el siguiente es un IComparer
personalizado que debe hacerlo para cualquier clase.
public class StartWithComparer<T> : IComparer<T>
{
private T startWith;
private IComparer<T> baseComparer = Comparer<T>.Default;
public StartWithComparer(T startWith, IComparer<T> baseComparer = null)
{
this.startWith = startWith;
if (baseComparer != null) this.baseComparer = baseComparer;
}
public int Compare(T x, T y)
{
int xToS = baseComparer.Compare(x, startWith);
int yToS = baseComparer.Compare(y, startWith);
if (xToS >= 0 && yToS < 0)
return -1;
else if (xToS < 0 && yToS >= 0)
return 1;
else
return baseComparer.Compare(x, y);
}
}
Llamado por
new[] { 1, 2, 3, 4, 5, 6 }.OrderBy(i => i, new StartWithComparer<int>(4))
Podrías implementar un IComparer personalizado.
Algo como lo siguiente (nota código no está probado!):
List<int> list = new List<int>() { 1, 2, 3, 4, 5, 6 };
list.OrderBy(n => n, new IntComparer(4));
public class IntComparer : IComparer<int>
{
int start;
public IntComparer (int start)
{
this.start = start;
}
// Compares by Height, Length, and Width.
public int Compare(int x, int y)
{
if (x >= start && y < start)
// X is greater than Y
return 1;
else if (x < start && y >= start)
// Y is greater than X
return -1;
else if (x == y)
return 0;
else
return x > y ? 1 : -1;
}
}
Podrías usar (o abusar, lo admito) una simple resta para lograr esto:
var seq = Enumerable.Range(0, 10);
int n = 4;
int m = seq.Max() + 1; // or a magic number like 1000, thanks RB.
var ordered = seq.OrderBy(x => x >= n ? x - m : x);
foreach(int i in ordered)
Console.WriteLine(i);
Además, si los números aumentan de tamaño, tenga en cuenta los desbordamientos de enteros . Para casos simples puede estar bien sin embargo.
Aquí hay una mejor solución (inspirada en otras respuestas):
var seq = Enumerable.Range(0, 10);
int n = 4;
var ordered = seq.Where(x => x >= n).OrderBy(x => x)
.Concat(seq.Where(x => x < n).OrderBy(x => x));
foreach(int i in ordered)
Console.WriteLine(i);
Ordena cada secuencia. Antes de concatenarlos. T_12 preguntó en un comentario si están ordenados ascendentemente. Si lo son, vaya con la solución de LB en lugar de la mía, ya que OrderBy
sopla el esfuerzo al menos a O(n log n)
lugar de O(n)
(lineal).
Propondré una solución herética aquí porque no está usando los operadores estándar de LINQ:
IEnumerable<int> GetSequence(IList<int> input, int index) {
for (var i = index; i < input.Count; i++) yield return input[i];
for (var i = 0; i < index; i++) yield return input[i];
}
Creo que esto muestra la intención con bastante claridad.
No creo que las extrañas contorsiones que tiene que realizar con los operadores de consulta LINQ estándar (combinaciones de Omitir, Tomar, Concat) sean legibles o mantenibles. Creo que sería un abuso usarlos en este caso solo por el simple hecho de hacerlo. Los bucles están bien.
Si sus datos son una List<T>
esto funciona:
var sequence = new[] { 1, 2, 3, 4, 5, 6 }.ToList();
List<int> result;
int start = 4;
int index = sequence.IndexOf(start);
if (index == 0)
result = sequence;
else if (index > -1)
{
result = sequence.GetRange(index, sequence.Count - index);
var secondPart = sequence.GetRange(0, sequence.Count - index);
result.AddRange(secondPart);
}
Eso no es realmente ordenar, sino crear una nueva lista.
OrderBy()
es bastante poderoso por sí mismo, y para expandir su alcance existe ThenBy()
, por lo que, en mi opinión, la forma más limpia de hacerlo es la siguiente:
var list = new[] {1, 2, 3, 4, 5, 6};
var pivot = 4;
var order = list.OrderBy(x => x == pivot ? 0 : 1).ThenBy(y => y < pivot ? 1: 0);
List<int> list = new List<int>()
{
1,2,3,4,5,6
};
int number = 4;
int max = list.Max();
var result = list.OrderBy(i => i >= number ? i : max + i);
List<int> list = new List<int>() { 1, 2, 3, 4, 5, 6 };
item = 4;
var index = input.IndexOf(item);
var firstList = input.Take(index);
return input.Except(firstList)
.Concat(firstList)
.ToList();
List<int> list = new List<int>() { 1, 2, 3, 4, 5, 6 };
int num = 4;
var newList = list.SkipWhile(x=>x!=num)
.Concat(list.TakeWhile(x=>x!=num))
.ToList();
int specific = 4;
var numbers = Enumerable.Range(1, 9);
var result = numbers.OrderBy(n => Tuple.Create(n < speficic, n)).ToList();
Utilizo un pequeño truco aquí, usando Tuple<bool, int>
como comparador, ya que false < true
. Una alternativa es:
var result = numbers.OrderBy(n => n < speficic).ThenBy(n => n).ToList();
EDITAR después de un punto de referencia, encontré la segunda solución .OrderBy .ThenBy
es mucho más rápido que la solución Tuple
. Creo que es porque la FCL usa Comparer<T>.Default
como el comparador, que cuesta tiempo construirlo.