Nice programing

목록이 주문되었는지 확인하는 방법은 무엇입니까?

nicepro 2020. 12. 31. 23:29
반응형

목록이 주문되었는지 확인하는 방법은 무엇입니까?


일부 단위 테스트를 수행하고 있으며 목록이 포함 된 개체의 속성에 따라 정렬되는지 테스트 할 방법이 있는지 알고 싶습니다.

지금은 이런 식으로하고 있지만 마음에 들지 않고 더 나은 방법을 원합니다. 누군가 제발 도와 줄 수 있습니까?

// (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 버전).

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

assert 메서드는 모든 IEnumerable에서 작동 하지만 두 컬렉션이 모두 유형 IList이거나 "뭔가의 배열"인 경우, assert 실패시 발생하는 오류 메시지에는 첫 번째 외부 요소의 색인이 포함됩니다.


Nunit 2.5는 CollectionOrderedContraint컬렉션 의 순서를 확인하는 멋진 구문을 도입 했습니다.

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

수동으로 주문하고 비교할 필요가 없습니다.


목록 정렬과 관련하여 게시 된 솔루션은 비용이 많이 듭니다. 목록이 정렬되었는지 여부를 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.


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하는 인스턴스는 어디에 있습니까 ? 이렇게하면 모든 요소가 열거 형의 다음 요소보다 작습니다.YourComparerIComparer<YourBusinessObject>


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

하나의 람다 식과 하나의 익명 유형을 저장합니다.

(제 생각에는 익명 유형을 제거하면 읽기가 더 쉬워집니다.)


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
}

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

이것이 도움이되기를 바랍니다.


다음과 같은 확장 방법을 사용할 수 있습니다.

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

시퀀스 확인은 네 가지 다른 결과를 가질 수 있습니다. 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 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 ()가 true를 반환 합니다.
  • new int[] { 1,2 }.IsAscendingOrder() returns true
  • new int[] { 1,2,0 }.IsAscendingOrder() returns 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.

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

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

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

ReferenceURL : https://stackoverflow.com/questions/1940214/how-to-check-if-a-list-is-ordered

반응형