How to check if a list is ordered?

C#Unit TestingListHtml Lists

C# Problem Overview


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

C# Solutions


Solution 1 - C#

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

Solution 2 - C#

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

Solution 3 - C#

Nunit 2.5 introduced CollectionOrderedContraint and a nice syntax for verifying the order of a collection:

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

No need to manually order and compare.

Solution 4 - C#

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.

Solution 5 - C#

The solutions posted involving sorting the list are expensive - determining if a list IS sorted can be done in O(N). Here's an extension method which will check:

public static bool IsOrdered<T>(this IList<T> list, IComparer<T> comparer = null)
{
	if (comparer == null)
	{
		comparer = Comparer<T>.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;
}

A corresponding IsOrderedDescending could be implemented easily by changing > 0 to < 0.

Solution 6 - C#

Greg Beech answer, although excellent, can be simplified further by performing the test in the Zip itself. So instead of:

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

You can simply do:

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

Which saves you one lambda expression and one anonymous type.

(In my opinion removing the anonymous type also makes it easier to read.)

Solution 7 - C#

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!

Solution 8 - C#

How about:

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

where yourComparer is an instance of YourComparer which implements IComparer<YourBusinessObject>. This ensures that every element is less than the next element in the enumeration.

Solution 9 - C#

Linq based answer is:

You can use SequenceEqual method to check if the original and ordered one is same or not.

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

Don't forget to import System.Linq namespace.

Additionally:

I am repeating that this answer is Linq based, you can achieve more efficiency by creating your custom extension method.

Also, if somebody still wants to use Linq and check if the sequence both is ordered in ascending or descending order, then you can achieve a little bit more efficiency like that:

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
}

Solution 10 - C#

You could use an extension method like this:

public static System.ComponentModel.ListSortDirection? SortDirection<T>(this IEnumerable<T> items, Comparer<T> comparer = null)
{
    if (items == null) throw new ArgumentNullException("items");
    if (comparer == null) comparer = Comparer<T>.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;
}

It enables to check if the sequence is sorted and also determines the direction:

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

Solution 11 - C#

Here's how I do it with Linq and I comparable, might not be the best but works for me and it's test framework independent.

So the call looks like this:

    myList.IsOrderedBy(a => a.StartDate)

This works for anything that implements IComparable, so numbers strings and anything that inherit from IComparable:

    public static bool IsOrderedBy<T, TProperty>(this List<T> list, Expression<Func<T, TProperty>> propertyExpression) where TProperty : IComparable<TProperty>
    {
        var member = (MemberExpression) propertyExpression.Body;
        var propertyInfo = (PropertyInfo) member.Member;
        IComparable<TProperty> 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;
    }

Hope this helps, took me ages to work this one out.

Solution 12 - C#

Checking a sequence can have four different outcomes. Same means that all elements in the sequence are the same (or the sequence is empty):

enum Sort {
  Unsorted,
  Same,
  SortedAscending,
  SortedDescending
}

Here is a way to check the sorting of a sequence:

Sort GetSort<T>(IEnumerable<T> source, IComparer<T> comparer = null) {
  if (source == null)
    throw new ArgumentNullException(nameof(source));
  if (comparer == null)
    comparer = Comparer<T>.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;
  }
}

I'm using the enumerator directly instead of a foreach loop because I need to examine the elements of the sequence as pairs. It makes the code more complex but is also more efficient.

Solution 13 - C#

Something LINQ-y would be to use a separate sorted query...

var sorted = from item in items
 orderby item.Priority
 select item;

Assert.IsTrue(items.SequenceEquals(sorted));

Type inference means you'd need a

 where T : IHasPriority

However, if you have multiple items of the same priority, then for a unit test assertion you're probably best off just looping with the list index as Jason suggested.

Solution 14 - C#

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.

Solution 15 - C#

You can create an ordered and an unordered version of the list first:

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

Now compare the original list with both:

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

Solution 16 - C#

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

Solution 17 - C#

What about something like this, without sorting the list

    public static bool IsAscendingOrder<T>(this IEnumerable<T> 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();
    }

Solution 18 - C#

Microsoft.VisualStudio.TestTools.UnitTesting.CollectionAssert.AreEqual(
  mylist.OrderBy((a) => a.SomeProperty).ToList(),
  mylist,
  "Not sorted.");

Solution 19 - C#

Here's a more lightweight generic version. To test for descending order, change the >= 0 comparison to <= 0.

public static bool IsAscendingOrder<T>(this IEnumerable<T> seq) where T : IComparable<T>
{
    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;
}

Tests:

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

Solution 20 - C#

While AnorZaken's and Greg Beech's answers are very nice, as they don't require using an extension method, it can be good to avoid Zip() sometimes, as some enumerables can be expensive to enumerate in this way.

A solution can be found in Aggregate()

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

One thing a little ugly about the above solution, is the double less-than comparisons. Floating comparisons like this make me queasy as it is almost like a floating point equality comparison. But it seems to work for double here. Integer values would be fine, also. The floating point comparison can be avoided by using nullable types, but then the code becomes a bit harder to read.

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

Solution 21 - C#

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

Solution 22 - C#

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

Attributions

All content for this solution is sourced from the original question on Stackoverflow.

The content on this page is licensed under the Attribution-ShareAlike 4.0 International (CC BY-SA 4.0) license.

Content TypeOriginal AuthorOriginal Content on Stackoverflow
QuestionNicoleView Question on Stackoverflow
Solution 1 - C#Mark SeemannView Answer on Stackoverflow
Solution 2 - C#Greg BeechView Answer on Stackoverflow
Solution 3 - C#mattkView Answer on Stackoverflow
Solution 4 - C#Jørn Schou-RodeView Answer on Stackoverflow
Solution 5 - C#RichardView Answer on Stackoverflow
Solution 6 - C#AnorZakenView Answer on Stackoverflow
Solution 7 - C#user1228View Answer on Stackoverflow
Solution 8 - C#jasonView Answer on Stackoverflow
Solution 9 - C#Farhad JabiyevView Answer on Stackoverflow
Solution 10 - C#Tim SchmelterView Answer on Stackoverflow
Solution 11 - C#jolySoftView Answer on Stackoverflow
Solution 12 - C#Martin LiversageView Answer on Stackoverflow
Solution 13 - C#TanzelaxView Answer on Stackoverflow
Solution 14 - C#Steven M. CherryView Answer on Stackoverflow
Solution 15 - C#MakePeaceGreatAgainView Answer on Stackoverflow
Solution 16 - C#Darin DimitrovView Answer on Stackoverflow
Solution 17 - C#Ricardo stands with UkraineView Answer on Stackoverflow
Solution 18 - C#Jan HeldalView Answer on Stackoverflow
Solution 19 - C#jmikView Answer on Stackoverflow
Solution 20 - C#CameronView Answer on Stackoverflow
Solution 21 - C#Peter AlmazovView Answer on Stackoverflow
Solution 22 - C#Suknarin ChaikheawView Answer on Stackoverflow