How to check if a list is ordered?

烂漫一生 提交于 2019-12-11 13:50:00

问题


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

回答1:


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



回答2:


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



回答3:


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.




回答4:


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.




回答5:


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.




回答6:


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!




回答7:


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.




回答8:


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




回答9:


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
}



回答10:


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.




回答11:


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



回答12:


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.




回答13:


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.




回答14:


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.




回答15:


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



回答16:


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



回答17:


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



回答18:


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



回答19:


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



回答20:


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)) // ...



回答21:


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();
  for (int i = 1; i < list.Count; i++) {
    if (compareTo(list[i - 1], list[i]) > 0) {
      return false;
    }
  }
  return true;
}

Use:

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

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


来源:https://stackoverflow.com/questions/34720649/how-to-check-if-contents-of-a-liststring-is-alphabetical

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!