Как проверить, упорядочен ли список? - программирование
Подтвердить что ты не робот

Как проверить, упорядочен ли список?

Я выполняю некоторые модульные тесты, и я хочу знать, есть ли способ проверить, упорядочен ли список по свойству объектов, которые он содержит.

Сейчас я делаю это так, но мне это не нравится, я хочу лучше. Может кто-нибудь мне помочь?

// (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;
}
4b9b3361

Ответ 1

Если вы используете 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));

Ответ 2

Способ .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);
}

Ответ 3

Если ваша платформа тестирования модулей имеет вспомогательные методы для подтверждения равенства коллекций, вы должны сделать что-то вроде этого (NUnit flavored):

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

Метод assert работает с любым IEnumerable, но когда обе коллекции имеют тип IList или "массив чего-то", сообщение об ошибке, которое генерируется при неудачном утверждении, будет содержать индекс первого вне места элемент.

Ответ 4

Nunit 2.5 представил CollectionOrderedContraint и хороший синтаксис для проверки порядка коллекции:

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

Не нужно вручную заказывать и сравнивать.

Ответ 5

Решения, связанные с сортировкой списка, дороги - определение того, может ли отсортированный список сортироваться в 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.

Ответ 6

if(studyFeeds.Length < 2)
  return;

for(int i = 1; i < studyFeeds.Length;i++)  
 Assert.IsTrue(studyFeeds[i-1].Date > studyFeeds[i].Date);

for еще не мертв!

Ответ 7

Как насчет:

var list = items.ToList();
for(int i = 1; i < list.Count; i++) {
    Assert.IsTrue(yourComparer.Compare(list[i - 1], list[i]) <= 0);
} 

где yourComparer - это экземпляр yourComparer, который реализует IComparer<YourBusinessObject>. Это гарантирует, что каждый элемент меньше, чем следующий элемент в перечислении.

Ответ 8

Greg Beech answer, хотя и отлично, можно упростить, выполнив тест в самом 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);

Что экономит вам одно лямбда-выражение и один анонимный тип.

(На мой взгляд, удаление анонимного типа также облегчает чтение.)

Ответ 9

Ответ на основе 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
}

Ответ 10

Вот как я это делаю с 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;
    }

Надеюсь, что это поможет, мне потребовались годы для работы над этим.

Ответ 11

Вы можете использовать метод расширения следующим образом:

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

Ответ 12

Проверка последовательности может иметь четыре разных результата. 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, потому что мне нужно проверить элементы последовательности как пары. Это делает код более сложным, но также более эффективным.

Ответ 13

Что-то LINQ-y будет использовать отдельный отсортированный запрос...

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

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

Вывод типа означает, что вам понадобится

 where T : IHasPriority

Однако, если у вас есть несколько элементов с одним и тем же приоритетом, то для утверждения unit test вы, вероятно, лучше всего просто зацикливаете с индексом списка, как предложил Джейсон.

Ответ 14

Так или иначе вам придется ходить по списку и убедиться, что элементы находятся в том порядке, в котором вы хотите. Поскольку сравнение элементов является обычным, вы можете изучить создание общего метода для этого и передачу в функции сравнения - так же, как сортировка списка использует функции сравнения.

Ответ 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

Что-то вроде этого, не сортируя список

    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

Здесь представлена ​​более легкая родовая версия. Чтобы проверить порядок убывания, измените сравнение >= 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() возвращает true
  • new int [] {1,2,0}.IsAscendingOrder() возвращает false

Ответ 19

В то время как ответы 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[] 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

Сначала вы можете создать упорядоченную и неупорядоченную версию списка:

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

Теперь сравните исходный список с обоими:

if (jobs.SequenceEqual(asc) || jobs.SequenceEquals(desc)) // ...

Ответ 21

Вы можете использовать лямбду в расширении:

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

Использование:

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