簡體   English   中英

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

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

我正在做一些單元測試,我想知道是否有任何方法可以測試列表是否按其包含的對象的屬性排序。

現在我正在這樣做,但我不喜歡它,我想要一個更好的方法。 有人可以幫我嗎?

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

.NET 4.0 方法是使用Enumerable.Zip方法將列表壓縮為自身偏移一,這將每個項目與列表中的后續項目配對。 然后,您可以檢查每對的條件是否成立,例如

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

如果您使用的是該框架的早期版本,則可以輕松編寫自己的 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 引入了CollectionOrderedContraint和一個很好的語法來驗證集合的順序:

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

無需手動訂購和比較。

如果你的單元測試框架有輔助方法來斷言集合的相等性,你應該能夠做這樣的事情(NUnit 風格):

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

assert 方法適用於任何IEnumerable ,但是當兩個集合都是IList或“某物數組”類型時,斷言失敗時拋出的錯誤消息將包含第一個不合適的元素的索引。

發布的涉及對列表排序的解決方案很昂貴 - 確定是否可以在 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;
}

通過將> 0更改為< 0可以輕松實現相應的IsOrderedDescending

Greg Beech 的回答雖然很好,但可以通過在 Zip 本身中執行測試來進一步簡化。 所以而不是:

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

你可以簡單地做:

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

這為您節省了一種 lambda 表達式和一種匿名類型。

(在我看來,刪除匿名類型也使閱讀更容易。)

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一個實例。 這確保每個元素都小於枚舉中的下一個元素。

基於 Linq 的答案是:

您可以使用SequenceEqual方法來檢查原始和有序的是否相同。

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

不要忘記導入System.Linq命名空間。

另外:

我重復一遍,這個答案是基於 Linq 的,您可以通過創建自定義擴展方法來提高效率。

此外,如果有人仍然想使用 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;
}

它可以檢查序列是否已排序並確定方向:

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

這是我使用 Linq 的方法,我可以比較,它可能不是最好的,但對我有用,並且它獨立於測試框架。

所以調用看起來像這樣:

    myList.IsOrderedBy(a => a.StartDate)

這適用於任何實現 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;
    }

希望這會有所幫助,我花了很長時間才解決這個問題。

檢查一個序列可以有四種不同的結果。 Same的裝置,所述序列中的所有元素是相同的(或該序列為空):

enum Sort {
  Unsorted,
  Same,
  SortedAscending,
  SortedDescending
}

這是一種檢查序列排序的方法:

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 將使用單獨的排序查詢......

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

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

類型推斷意味着你需要一個

 where T : IHasPriority

但是,如果您有多個具有相同優先級的項目,那么對於單元測試斷言,您可能最好按照 Jason 的建議使用列表索引進行循環。

以一種或另一種方式,您將不得不遍歷列表並確保項目按您想要的順序排列。 由於項目比較是自定義的,您可以考慮為此創建一個通用方法並傳入一個比較函數 - 與對列表進行排序使用比較函數的方式相同。

您可以先創建列表的有序和無序版本:

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

現在將原始列表與兩者進行比較:

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

這樣的事情怎么樣,不排序列表

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

這是一個更輕量級的通用版本。 要測試降序,請將 >= 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;
}

測試:

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

雖然 AnorZaken 和 Greg Beech 的答案非常好,因為它們不需要使用擴展方法,但有時避免 Zip() 可能會很好,因為以這種方式枚舉某些可枚舉可能會很昂貴。

可以在 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);

上述解決方案有點丑陋的一件事是雙重小於比較。 像這樣的浮點比較讓我感到不安,因為它幾乎就像一個浮點相等比較。 但它似乎在這里為 double 工作。 整數值也可以。 可以通過使用可空類型來避免浮點比較,但是代碼變得有點難以閱讀。

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

您可以在擴展中使用 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;
}

使用:

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