How to check if a list is ordered?

Viewed 69716

I am doing some unit tests and I want to know if there's any way to test if a list is ordered by a property of the objects it contains.

Right now I am doing it this way but I don't like it, I want a better way. Can somebody help me please?

// (fill the list)
List<StudyFeedItem> 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;
}
22 Answers

If you are using MSTest, you may want to take a look at CollectionAssert.AreEqual.

Enumerable.SequenceEqual may be another useful API to use in an assertion.

In both cases you should prepare a list that holds the expected list in the expected order, and then compare that list to the result.

Here's an example:

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

A .NET 4.0 way would be to use the Enumerable.Zip method to zip the list with itself offset by one, which pairs each item with the subsequent item in the list. You can then check that the condition holds true for each pair, e.g.

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

If you're on an earlier version of the framework you can write your own Zip method without too much trouble, something like the following (argument validation and disposal of the enumerators if applicable is left to the reader):

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

If your unit testing framework has helper methods to assert equality of collections, you should be able do something like this (NUnit flavored):

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

The assert method works with any IEnumerable, but when both collections are of type IList or "array of something", the error message thrown when the assert fails will contain the index of the first out-of-place element.

if(studyFeeds.Length < 2)
  return;

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

for isn't dead just quite yet!

One way or another you're going to have to walk the list and ensure that the items are in the order you want. Since the item comparison is custom, you could look into creating a generic method for this and passing in a comparison function - the same way that sorting the list uses comparison functions.

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

You can use lambda in extension:

public static bool IsAscending<T>(this IEnumerable<T> self, Func<T, T, int> compareTo) {
  var list = self as IList<T> ?? self.ToList();
  if (list.Count < 2) {
    return true;
  }
  T a = list[0];
  for (int i = 1; i < list.Count; i++) {
    T b = list[i];
    if (compareTo(a, b) > 0) {
      return false;
    }
    a = b;
  }
  return true;
}

Using:

bool result1 = Enumerable.Range(2, 10).IsAscending((a, b) => a.CompareTo(b));

more:

var lst = new List<(int, string)> { (1, "b"), (2, "a"), (3, "s1"), (3, "s") };
bool result2 = lst.IsAscending((a, b) => {
  var cmp = a.Item1.CompareTo(b.Item1);
  if (cmp != 0) {
    return cmp;
  } else {
    return a.Item2.CompareTo(b.Item2);
  }
});
var expectedList = resultA.ToArray();
var actualList = resultB.ToArray();
var i = 0;
foreach (var item in expectedList)
{
     Assert.True(expectedList[i].id == actualList[i].id);
     i++;
}
Related