web-dev-qa-db-fra.com

Comment vérifier si une liste est commandée?

Je fais des tests unitaires et je veux savoir s'il existe un moyen de tester si une liste est ordonnée par une propriété des objets qu'elle contient.

En ce moment je le fais de cette façon mais je n'aime pas ça, je veux une meilleure façon. quelqu'un peut m'aider s'il vous plait?

// (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;
}
55
Nicole

Si vous utilisez MSTest, vous voudrez peut-être jeter un œil à CollectionAssert.AreEqual .

Enumerable.SequenceEqual peut être une autre API utile à utiliser dans une assertion.

Dans les deux cas, vous devez préparer une liste qui contient la liste attendue dans l'ordre attendu, puis comparer cette liste au résultat.

Voici un exemple:

var studyFeeds = Feeds.GetStudyFeeds(2120, DateTime.Today.AddDays(-200), 20);   
var expectedList = studyFeeds.OrderByDescending(x => x.Date);
Assert.IsTrue(expectedList.SequenceEqual(studyFeeds));
59
Mark Seemann

Une façon .NET 4.0 serait d'utiliser le Enumerable.Zip méthode pour compresser la liste avec elle-même décalée d'une unité, qui associe chaque élément à l'élément suivant de la liste. Vous pouvez ensuite vérifier que la condition est vraie pour chaque paire, par ex.

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

Si vous utilisez une version antérieure du framework, vous pouvez écrire votre propre méthode Zip sans trop de problèmes, quelque chose comme ceci (la validation des arguments et l'élimination des énumérateurs le cas échéant sont laissées au lecteur):

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);
}
37
Greg Beech

Si votre framework de tests unitaires a des méthodes d'aide pour affirmer l'égalité des collections, vous devriez pouvoir faire quelque chose comme ça (version NUnit):

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

La méthode assert fonctionne avec n'importe quel IEnumerable, mais lorsque les deux collections sont de type IList ou "tableau de quelque chose", le message d'erreur renvoyé lorsque l'assertion échoue contiendra l'index de la première sortie. élément de lieu.

26
Jørn Schou-Rode

Nunit 2.5 a introduit CollectionOrderedContraint et une belle syntaxe pour vérifier l'ordre d'une collection:

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

Pas besoin de commander et de comparer manuellement.

23
mattk

Les solutions publiées impliquant le tri de la liste sont coûteuses - déterminer si une liste IS triée peut être effectuée en O (N). Voici une méthode d'extension qui vérifiera:

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

Un IsOrderedDescending correspondant pourrait être facilement implémenté en changeant > 0 à < 0.

11
Richard
if(studyFeeds.Length < 2)
  return;

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

for n'est pas encore mort!

9
Will

Greg Beech réponse , bien qu'excellent, peut encore être simplifié en effectuant le test dans le Zip lui-même. Donc au lieu de:

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

Vous pouvez simplement faire:

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

Ce qui vous permet d'économiser une expression lambda et un type anonyme.

(À mon avis, la suppression du type anonyme facilite également la lecture.)

7
AnorZaken

Que diriez-vous:

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

yourComparer est une instance de YourComparer qui implémente IComparer<YourBusinessObject> . Cela garantit que chaque élément est inférieur à l'élément suivant dans l'énumération.

7
jason

La réponse basée sur Linq est:

Vous pouvez utiliser la méthode SequenceEqual pour vérifier si l'original et celui commandé sont identiques ou non.

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

N'oubliez pas d'importer System.Linq espace de noms.

De plus:

Je répète que cette réponse est basée sur Linq, vous pouvez obtenir plus d'efficacité en créant votre méthode d'extension personnalisée.

De plus, si quelqu'un veut toujours utiliser Linq et vérifier si la séquence est ordonnée dans l'ordre croissant ou décroissant, vous pouvez obtenir un peu plus d'efficacité comme ça:

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
}
6
Farhad Jabiyev

Vous pouvez utiliser une méthode d'extension comme celle-ci:

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

Il permet de vérifier si la séquence est triée et détermine également la 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
4
Tim Schmelter

Voici comment je le fais avec Linq et moi comparable, ce n'est peut-être pas le meilleur mais ça fonctionne pour moi et c'est indépendant du framework de test.

Donc, l'appel ressemble à ceci:

    myList.IsOrderedBy(a => a.StartDate)

Cela fonctionne pour tout ce qui implémente IComparable, donc les chaînes de nombres et tout ce qui hérite d'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;
    }

J'espère que cela aide, cela m'a pris beaucoup de temps pour travailler celui-ci.

4
jolySoft

La vérification d'une séquence peut avoir quatre résultats différents. Same signifie que tous les éléments de la séquence sont identiques (ou que la séquence est vide):

enum Sort {
  Unsorted,
  Same,
  SortedAscending,
  SortedDescending
}

Voici un moyen de vérifier le tri d'une séquence:

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

J'utilise l'énumérateur directement au lieu d'une boucle foreach car j'ai besoin d'examiner les éléments de la séquence sous forme de paires. Il rend le code plus complexe mais est également plus efficace.

2
Martin Liversage

D'une manière ou d'une autre, vous devrez parcourir la liste et vous assurer que les éléments sont dans l'ordre que vous souhaitez. Comme la comparaison d'éléments est personnalisée, vous pouvez envisager de créer une méthode générique pour cela et de passer une fonction de comparaison - de la même manière que le tri de la liste utilise des fonctions de comparaison.

1
Steven M. Cherry

Quelque chose LINQ-y serait d'utiliser une requête triée distincte ...

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

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

L'inférence de type signifie que vous auriez besoin d'un

 where T : IHasPriority

Cependant, si vous avez plusieurs éléments de la même priorité, alors pour une affirmation de test unitaire, vous feriez probablement mieux de simplement boucler avec l'index de liste comme Jason l'a suggéré.

1
Tanzelax

Vous pouvez utiliser lambda en 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;
}

Utilisation:

  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);
    }
  });
0
Peter Almazov
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);
}
0
Darin Dimitrov

Vous pouvez d'abord créer une version ordonnée et non ordonnée de la liste:

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

Comparez maintenant la liste d'origine avec les deux:

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

Qu'en est-il de quelque chose comme ça, sans trier la liste

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

Bien que les réponses d'AnorZaken et de Greg Beech soient très agréables, car elles ne nécessitent pas l'utilisation d'une méthode d'extension, il peut être bon d'éviter parfois Zip (), car certains énumérables peuvent être coûteux à énumérer de cette manière.

Une solution peut être trouvée dans 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);

Une chose un peu moche à propos de la solution ci-dessus, c'est le double de moins que les comparaisons. Des comparaisons flottantes comme celle-ci me rendent mal à l'aise car c'est presque comme une comparaison d'égalité à virgule flottante. Mais cela semble fonctionner pour le double ici. Les valeurs entières seraient également très bien. La comparaison en virgule flottante peut être évitée en utilisant des types nullables, mais le code devient alors un peu plus difficile à lire.

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

Voici une version générique plus légère. Pour tester l'ordre décroissant, remplacez la comparaison> = 0 par <= 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 () renvoie true
  • new int [] {1} .IsAscendingOrder () renvoie true
  • new int [] {1,2} .IsAscendingOrder () renvoie true
  • new int [] {1,2,0} .IsAscendingOrder () renvoie false
0
jmik