如何检查列表是否被sorting?
我正在做一些unit testing,我想知道是否有任何方法来testing列表是否由它包含的对象的属性sorting。
现在我正在这样做,但我不喜欢它,我想要一个更好的方法。 有人可以帮我吗?
// (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; }
如果您正在使用MSTest,则可能需要查看CollectionAssert.AreEqual 。
Enumerable.SequenceEqual可能是在断言中使用的另一个有用的API。
在这两种情况下,您都应该按照预期的顺序准备一份列表,列出预期的列表,然后将该列表与结果进行比较。
这是一个例子:
var studyFeeds = Feeds.GetStudyFeeds(2120, DateTime.Today.AddDays(-200), 20); var expectedList = studyFeeds.OrderByDescending(x => x.Date); Assert.IsTrue(expectedList.SequenceEqual(studyFeeds));
如果你的unit testing框架有辅助方法来声明集合的平等,你应该可以做这样的事情(NUnit风格):
var sorted = studyFeeds.OrderBy(s => s.Date); CollectionAssert.AreEqual(sorted.ToList(), studyFeeds.ToList());
assert方法适用于任何IEnumerable
,但是当两个集合都是IList
types或“某些数组”时,断言失败时抛出的错误消息将包含第一个out-of-place元素的索引。
.NET 4.0的方法是使用Enumerable.Zip
方法压缩列表,使其自身偏移1,这将使每个项目与列表中的后续项目配对。 然后,您可以检查条件是否适用于每一对,例如
var ordered = studyFeeds.Zip(studyFeeds.Skip(1), (a, b) => new { a, b }) .All(p => paDate < pbDate);
如果你使用的是早期版本的框架,你可以编写你自己的Zip方法,而不会有太多的麻烦,如下所示(如果适用的话,validation和处理枚举数据给读者):
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引入了CollectionOrderedContraint和一个很好的语法来validation集合的顺序:
Assert.That(collection, Is.Ordered.By("PropertyName"));
无需手动订购和比较。
if(studyFeeds.Length < 2) return; for(int i = 1; i < studyFeeds.Length;i++) Assert.IsTrue(studyFeeds[i-1].Date > studyFeeds[i].Date);
for
还没死呢
怎么样:
var list = items.ToList(); for(int i = 1; i < list.Count; i++) { Assert.IsTrue(yourComparer.Compare(list[i - 1], list[i]) <= 0); }
其中yourComparer
是实现IComparer<YourBusinessObject>
的YourComparer
实例。 这确保每个元素都小于枚举中的下一个元素。
所发布的涉及对列表进行sorting的解决scheme是昂贵的 – 确定列表是否被sorting可以在O(N)中完成。 这是一个扩展方法,将检查:
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; }
相应的IsOrderedDescending
可以很容易地通过改变> 0
到< 0
来实现。
下面是我如何与Linq和我做比较,可能不是最好的,但对我来说,它是独立的testing框架。
所以这个调用看起来像这样:
myList.IsOrderedBy(a => a.StartDate)
这适用于任何实现IComparable,所以数字string和从IComparableinheritance的任何东西:
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; }
希望这会有所帮助,花了我很多时间来解决这个问题。
格雷格·比奇(Greg Beech)的回答虽然很好,但可以通过在Zip本身进行testing来进一步简化。 所以,而不是:
var ordered = studyFeeds.Zip(studyFeeds.Skip(1), (a, b) => new { a, b }) .All(p => paDate < pbDate);
你可以简单地做:
var ordered = !studyFeeds.Zip(studyFeeds.Skip(1), (a, b) => a.Date < b.Date) .Contains(false);
这可以节省一个lambdaexpression式和一个匿名types。
(在我看来,删除匿名types也使阅读更容易。)
基于Linq的答案是:
您可以使用SequenceEqual
方法来检查原始的和有序的是否相同。
var isOrderedAscending = lJobsList.SequenceEqual(lJobsList.OrderBy(x => x)); var isOrderedDescending = lJobsList.SequenceEqual(lJobsList.OrderByDescending(x => x));
不要忘记导入System.Linq
命名空间。
另外:
我重复说这个答案是基于Linq的,你可以通过创build自定义的扩展方法来获得更高的效率。
另外,如果有人仍然想使用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 }
你可以使用这样的扩展方法:
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; }
它能够检查序列是否被sorting并确定方向:
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
检查一个序列可能有四个不同的结果。 Same
意味着序列中的所有元素都是相同的(或者序列是空的):
enum Sort { Unsorted, Same, SortedAscending, SortedDescending }
这是一种检查序列sorting的方法:
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; } }
我直接使用枚举数而不是foreach
循环,因为我需要检查序列的元素是成对的。 它使代码更复杂,但也更有效率。
LINQ-y的东西是使用单独的sorting查询…
var sorted = from item in items orderby item.Priority select item; Assert.IsTrue(items.SequenceEquals(sorted));
types推理意味着你需要一个
where T : IHasPriority
但是,如果你有多个相同优先级的项目,那么对于一个unit testing断言,你可能最好只是像Jasonbuild议的那样循环使用列表索引。
无论如何,你将不得不走在名单,并确保项目是你想要的顺序。 由于项目比较是自定义的,因此可以考虑为此创build一个通用方法,并传入一个比较函数 – 与sorting列表使用比较函数的方式相同。
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); }
那么这样的事情,没有整理清单
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.");
这是一个更轻量级的通用版本。 要testing降序,请将> = 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; }
testing:
- new int [] {} .IsAscendingOrder()返回true
- new int [] {1} .IsAscendingOrder()返回true
- new int [] {1,2} .IsAscendingOrder()返回true
- new int [] {1,2,0} .IsAscendingOrder()返回false
AnorZaken和Greg Beech的回答非常好,因为他们不需要使用扩展方法,有时避免使用Zip()会很好,因为某些枚举types可能会以这种方式枚举。
一个解决scheme可以在Aggregate()中find
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);
有一点关于上述解决scheme有点难看,是双重比较。 像这样的浮动比较使我感到不安,因为它几乎就像一个浮点平等比较。 但这似乎在这里工作了一倍。 整数值也可以。 通过使用可为空的types可以避免浮点比较,但是代码变得难以阅读。
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);
您可以先创build列表的有序和无序版本:
var asc = jobs.OrderBy(x => x); var desc = jobs.OrderByDescending(x => x);
现在比较原来的列表和两个:
if (jobs.SequenceEqual(asc) || jobs.SequenceEquals(desc)) // ...