简体   繁体   English

如何检查列表是否已排序?

[英]How to check if a list is ordered?

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

If you are using MSTest, you may want to take a look at CollectionAssert.AreEqual .如果您使用的是 MSTest,您可能需要查看CollectionAssert.AreEqual

Enumerable.SequenceEqual may be another useful API to use in an assertion. Enumerable.SequenceEqual可能是在断言中使用的另一个有用的 API。

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. .NET 4.0 方法是使用Enumerable.Zip方法将列表压缩为自身偏移一,这将每个项目与列表中的后续项目配对。 You can then check that the condition holds true for each pair, eg然后,您可以检查每对的条件是否成立,例如

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):如果您使用的是该框架的早期版本,则可以轻松编写自己的 Zip 方法,如下所示(如果适用,则枚举器的参数验证和处理留给读者):

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

Nunit 2.5 introduced CollectionOrderedContraint and a nice syntax for verifying the order of a collection: Nunit 2.5 引入了CollectionOrderedContraint和一个很好的语法来验证集合的顺序:

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

No need to manually order and compare.无需手动订购和比较。

If your unit testing framework has helper methods to assert equality of collections, you should be able do something like this (NUnit flavored):如果你的单元测试框架有辅助方法来断言集合的相等性,你应该能够做这样的事情(NUnit 风格):

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. assert 方法适用于任何IEnumerable ,但是当两个集合都是IList或“某物数组”类型时,断言失败时抛出的错误消息将包含第一个不合适的元素的索引。

The solutions posted involving sorting the list are expensive - determining if a list IS sorted can be done in O(N).发布的涉及对列表排序的解决方案很昂贵 - 确定是否可以在 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 .通过将> 0更改为< 0可以轻松实现相应的IsOrderedDescending

Greg Beech answer , although excellent, can be simplified further by performing the test in the Zip itself. Greg Beech 的回答虽然很好,但可以通过在 Zip 本身中执行测试来进一步简化。 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.这为您节省了一种 lambda 表达式和一种匿名类型。

(In my opinion removing the anonymous type also makes it easier to read.) (在我看来,删除匿名类型也使阅读更容易。)

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! for还没死呢!

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> .其中yourComparer是实现IComparer<YourBusinessObject>YourComparer一个实例。 This ensures that every element is less than the next element in the enumeration.这确保每个元素都小于枚举中的下一个元素。

Linq based answer is:基于 Linq 的答案是:

You can use SequenceEqual method to check if the original and ordered one is same or not.您可以使用SequenceEqual方法来检查原始和有序的是否相同。

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

Don't forget to import System.Linq namespace.不要忘记导入System.Linq命名空间。

Additionally:另外:

I am repeating that this answer is Linq based, you can achieve more efficiency by creating your custom extension method.我重复一遍,这个答案是基于 Linq 的,您可以通过创建自定义扩展方法来提高效率。

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:此外,如果有人仍然想使用 Linq 并检查序列是按升序还是降序排列,那么您可以像这样获得更高的效率:

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
}

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

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.这是我使用 Linq 的方法,我可以比较,它可能不是最好的,但对我有用,并且它独立于测试框架。

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:这适用于任何实现 IComparable 的东西,所以数字字符串和任何继承自 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.希望这会有所帮助,我花了很长时间才解决这个问题。

Checking a sequence can have four different outcomes.检查一个序列可以有四种不同的结果。 Same means that all elements in the sequence are the same (or the sequence is empty): Same的装置,所述序列中的所有元素是相同的(或该序列为空):

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.我直接使用枚举器而不是foreach循环,因为我需要以对的形式检查序列的元素。 It makes the code more complex but is also more efficient.它使代码更复杂,但也更高效。

Something LINQ-y would be to use a separate sorted query... LINQ-y 将使用单独的排序查询......

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.但是,如果您有多个具有相同优先级的项目,那么对于单元测试断言,您可能最好按照 Jason 的建议使用列表索引进行循环。

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.由于项目比较是自定义的,您可以考虑为此创建一个通用方法并传入一个比较函数 - 与对列表进行排序使用比较函数的方式相同。

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

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();
    }
Microsoft.VisualStudio.TestTools.UnitTesting.CollectionAssert.AreEqual(
  mylist.OrderBy((a) => a.SomeProperty).ToList(),
  mylist,
  "Not sorted.");

Here's a more lightweight generic version.这是一个更轻量级的通用版本。 To test for descending order, change the >= 0 comparison to <= 0.要测试降序,请将 >= 0 比较更改为 <= 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[] { }.IsAscendingOrder() 返回true
  • new int[] { 1 }.IsAscendingOrder() returns true new int[] { 1 }.IsAscendingOrder() 返回
  • new int[] { 1,2 }.IsAscendingOrder() returns true new int[] { 1,2 }.IsAscendingOrder() 返回
  • new int[] { 1,2,0 }.IsAscendingOrder() returns false new int[] { 1,2,0 }.IsAscendingOrder() 返回false

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.虽然 AnorZaken 和 Greg Beech 的答案非常好,因为它们不需要使用扩展方法,但有时避免 Zip() 可能会很好,因为以这种方式枚举某些可枚举可能会很昂贵。

A solution can be found in Aggregate()可以在 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.但它似乎在这里为 double 工作。 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);

You can use lambda in extension:您可以在扩展中使用 lambda:

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

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM