¿Cómo verificar si se ordena una lista?

Estoy haciendo algunas pruebas unitarias y quiero saber si hay alguna manera de probar si una lista está ordenada por una propiedad de los objetos que contiene.

En este momento lo estoy haciendo de esta manera, pero no me gusta, quiero una mejor manera. ¿Puede alguien ayudarme por favor?

// (fill the list) List studyFeeds = Feeds.GetStudyFeeds(2120, DateTime.Today.AddDays(-200), 20); StudyFeedItem previous = studyFeeds.First(); foreach (StudyFeedItem item in studyFeeds) { if (item != previous) { Assert.IsTrue(previous.Date > item.Date); } previous = item; } 

Si está utilizando MSTest, es posible que desee echar un vistazo a CollectionAssert.AreEqual .

Enumerable.SequenceEqual puede ser otra API útil para usar en una aserción.

En ambos casos, debe preparar una lista que contenga la lista esperada en el orden esperado y luego comparar esa lista con el resultado.

Aquí hay un ejemplo:

 var studyFeeds = Feeds.GetStudyFeeds(2120, DateTime.Today.AddDays(-200), 20); var expectedList = studyFeeds.OrderByDescending(x => x.Date); Assert.IsTrue(expectedList.SequenceEqual(studyFeeds)); 

Si el marco de prueba de su unidad tiene métodos de ayuda para afirmar la igualdad de las colecciones, debería poder hacer algo como esto (NUnit con sabor):

 var sorted = studyFeeds.OrderBy(s => s.Date); CollectionAssert.AreEqual(sorted.ToList(), studyFeeds.ToList()); 

El método assert funciona con cualquier IEnumerable , pero cuando ambas colecciones son de tipo IList o “array of something”, el mensaje de error arrojado cuando la afirmación falla contendrá el índice del primer elemento fuera de lugar.

Una forma de .NET 4.0 sería usar el método Enumerable.Zip para comprimir la lista con uno mismo, que empareja cada elemento con el siguiente elemento de la lista. Luego puede verificar que la condición sea verdadera para cada par, por ejemplo

 var ordered = studyFeeds.Zip(studyFeeds.Skip(1), (a, b) => new { a, b }) .All(p => paDate < pbDate); 

Si está en una versión anterior del marco, puede escribir su propio método Zip sin demasiados problemas, algo como lo siguiente (la validación de argumentos y la eliminación de los enumeradores, si corresponde, se deja al lector):

 public static IEnumerable Zip( this IEnumerable first, IEnumerable second, Func selector) { var e1 = first.GetEnumerator(); var e2 = second.GetEnumerator(); while (e1.MoveNext() & e2.MoveNext()) // one & is important yield return selector(e1.Current, e2.Current); } 

Nunit 2.5 introdujo CollectionOrderedContraint y una buena syntax para verificar el orden de una colección:

 Assert.That(collection, Is.Ordered.By("PropertyName")); 

No es necesario ordenar y comparar manualmente.

 if(studyFeeds.Length < 2) return; for(int i = 1; i < studyFeeds.Length;i++) Assert.IsTrue(studyFeeds[i-1].Date > studyFeeds[i].Date); 

for no está muerto!

Las soluciones publicadas que implican ordenar la lista son costosas; determinar si una lista ES ordenada se puede hacer en O (N). Aquí hay un método de extensión que comprobará:

 public static bool IsOrdered(this IList list, IComparer comparer = null) { if (comparer == null) { comparer = Comparer.Default; } if (list.Count > 1) { for (int i = 1; i < list.Count; i++) { if (comparer.Compare(list[i - 1], list[i]) > 0) { return false; } } } return true; } 

Un IsOrderedDescending correspondiente podría implementarse fácilmente al cambiar > 0 a < 0 .

Qué tal si:

 var list = items.ToList(); for(int i = 1; i < list.Count; i++) { Assert.IsTrue(yourComparer.Compare(list[i - 1], list[i]) <= 0); } 

donde yourComparer es una instancia de YourComparer que implementa IComparer . Esto asegura que cada elemento sea menor que el siguiente elemento en la enumeración.

La respuesta de Greg Beech, aunque excelente, se puede simplificar aún más realizando la prueba en el propio Zip. Entonces, en lugar de:

 var ordered = studyFeeds.Zip(studyFeeds.Skip(1), (a, b) => new { a, b }) .All(p => paDate < pbDate); 

Simplemente puede hacer:

 var ordered = !studyFeeds.Zip(studyFeeds.Skip(1), (a, b) => a.Date < b.Date) .Contains(false); 

Lo que le ahorra una expresión lambda y un tipo anónimo.

(En mi opinión, eliminar el tipo anónimo también lo hace más fácil de leer).

Así es como lo hago con Linq y yo, puede que no sea el mejor pero funciona para mí y es independiente del marco de prueba.

Entonces la llamada se ve así:

  myList.IsOrderedBy(a => a.StartDate) 

Esto funciona para cualquier cosa que implemente IComparable, así que numera las cadenas y cualquier cosa que herede de IComparable:

  public static bool IsOrderedBy(this List list, Expression> propertyExpression) where TProperty : IComparable { var member = (MemberExpression) propertyExpression.Body; var propertyInfo = (PropertyInfo) member.Member; IComparable previousValue = null; for (int i = 0; i < list.Count(); i++) { var currentValue = (TProperty)propertyInfo.GetValue(list[i], null); if (previousValue == null) { previousValue = currentValue; continue; } if(previousValue.CompareTo(currentValue) > 0) return false; previousValue = currentValue; } return true; } 

Espero que esto ayude, me tomó siglos trabajar en esto.

La respuesta basada en Linq es:

Puede usar el método SequenceEqual para verificar si el original y el pedido son iguales o no.

 var isOrderedAscending = lJobsList.SequenceEqual(lJobsList.OrderBy(x => x)); var isOrderedDescending = lJobsList.SequenceEqual(lJobsList.OrderByDescending(x => x)); 

No olvides importar el espacio de nombres System.Linq .

Adicionalmente:

Estoy repitiendo que esta respuesta está basada en Linq, puede lograr más eficiencia al crear su método de extensión personalizado.

Además, si alguien todavía quiere usar Linq y verifica si la secuencia está ordenada en orden ascendente o descendente, entonces puede lograr un poco más de eficiencia así:

 var orderedSequence = lJobsList.OrderBy(x => x) .ToList(); var reversedOrderSequence = orderedSequence.AsEnumerable() .Reverse(); if (lJobsList.SequenceEqual(orderedSequence)) { // Ordered in ascending } else (lJobsList.SequenceEqual(reversedOrderSequence)) { // Ordered in descending } 

Puede usar un método de extensión como este:

 public static System.ComponentModel.ListSortDirection? SortDirection(this IEnumerable items, Comparer comparer = null) { if (items == null) throw new ArgumentNullException("items"); if (comparer == null) comparer = Comparer.Default; bool ascendingOrder = true; bool descendingOrder = true; using (var e = items.GetEnumerator()) { if (e.MoveNext()) { T last = e.Current; // first item while (e.MoveNext()) { int diff = comparer.Compare(last, e.Current); if (diff > 0) ascendingOrder = false; else if (diff < 0) descendingOrder = false; if (!ascendingOrder && !descendingOrder) break; last = e.Current; } } } if (ascendingOrder) return System.ComponentModel.ListSortDirection.Ascending; else if (descendingOrder) return System.ComponentModel.ListSortDirection.Descending; else return null; } 

Permite verificar si la secuencia está ordenada y también determina la dirección:

 var items = new[] { 3, 2, 1, 1, 0 }; var sort = items.SortDirection(); Console.WriteLine("Is sorted? {0}, Direction: {1}", sort.HasValue, sort); //Is sorted? True, Direction: Descending 

Verificar una secuencia puede tener cuatro resultados diferentes. Same significa que todos los elementos en la secuencia son los mismos (o la secuencia está vacía):

 enum Sort { Unsorted, Same, SortedAscending, SortedDescending } 

Aquí hay una manera de verificar la clasificación de una secuencia:

 Sort GetSort(IEnumerable source, IComparer comparer = null) { if (source == null) throw new ArgumentNullException(nameof(source)); if (comparer == null) comparer = Comparer.Default; using (var enumerator = source.GetEnumerator()) { if (!enumerator.MoveNext()) return Sort.Same; Sort? result = null; var previousItem = enumerator.Current; while (enumerator.MoveNext()) { var nextItem = enumerator.Current; var comparison = comparer.Compare(previousItem, nextItem); if (comparison < 0) { if (result == Sort.SortedDescending) return Sort.Unsorted; result = Sort.SortedAscending; } else if (comparison > 0) { if (result == Sort.SortedAscending) return Sort.Unsorted; result = Sort.SortedDescending; } } return result ?? Sort.Same; } } 

Estoy usando el enumerador directamente en lugar de un bucle foreach porque necesito examinar los elementos de la secuencia como pares. Hace que el código sea más complejo pero también más eficiente.

Algo LINQ-y sería usar una consulta separada separada …

 var sorted = from item in items orderby item.Priority select item; Assert.IsTrue(items.SequenceEquals(sorted)); 

Tipo de inferencia significa que necesitaría un

  where T : IHasPriority 

Sin embargo, si tiene varios elementos de la misma prioridad, entonces, para una afirmación de prueba unitaria, probablemente sea mejor que solo bifurque con el índice de la lista como sugirió Jason.

De una manera u otra, tendrá que recorrer la lista y asegurarse de que los artículos estén en el orden que desea. Dado que la comparación de elementos es personalizada, puede buscar crear un método genérico para esto y pasar una función de comparación, de la misma manera que la clasificación de la lista usa funciones de comparación.

 var studyFeeds = Feeds.GetStudyFeeds(2120, DateTime.Today.AddDays(-200), 20); var orderedFeeds = studyFeeds.OrderBy(f => f.Date); for (int i = 0; i < studyFeeds.Count; i++) { Assert.AreEqual(orderedFeeds[i].Date, studyFeeds[i].Date); } 

¿Qué tal algo así, sin ordenar la lista?

  public static bool IsAscendingOrder(this IEnumerable seq) where T : IComparable { var seqArray = seq as T[] ?? seq.ToArray(); return !seqArray.Where((e, i) => i < seqArray.Count() - 1 && e.CompareTo(seqArray.ElementAt(i + 1)) >= 0).Any(); } 
 Microsoft.VisualStudio.TestTools.UnitTesting.CollectionAssert.AreEqual( mylist.OrderBy((a) => a.SomeProperty).ToList(), mylist, "Not sorted."); 

Aquí hay una versión genérica más ligera. Para probar el orden descendente, cambie la comparación> = 0 a < = 0.

 public static bool IsAscendingOrder(this IEnumerable seq) where T : IComparable { var predecessor = default(T); var hasPredecessor = false; foreach(var x in seq) { if (hasPredecessor && predecessor.CompareTo(x) >= 0) return false; predecessor = x; hasPredecessor = true; } return true; } 

Pruebas:

  • new int [] {} .IsAscendingOrder () devuelve true
  • new int [] {1} .IsAscendingOrder () devuelve true
  • new int [] {1,2} .IsAscendingOrder () devuelve true
  • new int [] {1,2,0} .IsAscendingOrder () devuelve falso

Si bien las respuestas de AnorZaken y Greg Beech son muy buenas, ya que no requieren el uso de un método de extensión, puede ser bueno evitar Zip () a veces, ya que algunos enumerables pueden ser caros de enumerar de esta manera.

Una solución se puede encontrar en Agregado ()

 double[] score1 = new double[] { 12.2, 13.3, 5, 17.2, 2.2, 4.5 }; double[] score2 = new double[] { 2.2, 4.5, 5, 12.2, 13.3, 17.2 }; bool isordered1 = score1.Aggregate(double.MinValue,(accum,elem)=>elem>=accum?elem:double.MaxValue) < double.MaxValue; bool isordered2 = score2.Aggregate(double.MinValue,(accum,elem)=>elem>=accum?elem:double.MaxValue) < double.MaxValue; Console.WriteLine ("isordered1 {0}",isordered1); Console.WriteLine ("isordered2 {0}",isordered2); 

Una cosa un poco desagradable de la solución anterior es el doble de comparaciones. Las comparaciones flotantes como esta me dan náuseas, ya que es casi como una comparación de igualdad de coma flotante. Pero parece funcionar para el doble aquí. Los valores enteros estarían bien, también. La comparación de punto flotante se puede evitar mediante el uso de tipos que aceptan valores nulos, pero luego el código se vuelve un poco más difícil de leer.

 double[] score3 = new double[] { 12.2, 13.3, 5, 17.2, 2.2, 4.5 }; double[] score4 = new double[] { 2.2, 4.5, 5, 12.2, 13.3, 17.2 }; bool isordered3 = score3.Aggregate((double?)double.MinValue,(accum,elem)=>(elem>(accum??(double?)double.MaxValue).Value)?(double?)elem:(double?)null) !=null; bool isordered4 = score4.Aggregate((double?)double.MinValue,(accum,elem)=>(elem>(accum??(double?)double.MaxValue).Value)?(double?)elem:(double?)null) !=null; Console.WriteLine ("isordered3 {0}",isordered3); Console.WriteLine ("isordered4 {0}",isordered4); 

Primero puede crear una versión ordenada y no ordenada de la lista:

 var asc = jobs.OrderBy(x => x); var desc = jobs.OrderByDescending(x => x); 

Ahora compare la lista original con ambos:

 if (jobs.SequenceEqual(asc) || jobs.SequenceEquals(desc)) // ...