Native C# support for checking if an IEnumerable is sorted?

戏子无情 提交于 2019-11-29 11:19:41

You can check if collection is IOrderedEnumerable but that will work only if ordering is the last operation which was applied to sequence. So, basically you need to check all sequence manually.

Also keep in mind, that if sequence is IOrderedEnumerable you really can't say which condition was used to sort sequence.


Here is generic method which you can use to check if sequence is sorted in ascending order by field you want to check:

public static bool IsOrdered<T, TKey>(
    this IEnumerable<T> source, Func<T, TKey> keySelector)
{
    if (source == null)
        throw new ArgumentNullException("source");

    var comparer = Comparer<TKey>.Default;
    using (var iterator = source.GetEnumerator())
    {
        if (!iterator.MoveNext())
            return true;

        TKey current = keySelector(iterator.Current);

        while (iterator.MoveNext())
        {
            TKey next = keySelector(iterator.Current);
            if (comparer.Compare(current, next) > 0)
                return false;

            current = next;
        }
    }

    return true;
}

Usage:

string[] source = { "a", "ab", "c" };
bool isOrdered = source.IsOrdered(s => s.Length);

You can create similar IsOrderedDescending method - just change checking comparison result to comparer.Compare(current, next) < 0.

There is no such built-in support.

Obviously if your IEnumerable<T> also implements IOrderedEnumerable<T> then you don't need to do an additional check, otherwise you'd have to implement an extension method like you did.

You might want to add a direction parameter or change its name to IsSortedAscending<T>, by the way. Also, there might be different properties in your T to sort on, so it would have to be obvious to you in some way what "sorted" means.

There is a short and simple version using Zip, although your IEnumerable does get enumerated twice.

var source = Enumerable.Range(1,100000);

bool isSorted = source.Zip(source.Skip(1),(a,b)=>b>=a).All(x=>x);

I often find usages for an extension method I've created called SelectPairs(), and also in this case:

/// <summary>
/// Projects two consecutive pair of items into tuples.
/// {1,2,3,4} -> {(1,2), (2,3), (3,4))
/// </summary>
public static IEnumerable<Tuple<T, T>> SelectPairs<T>(this IEnumerable<T> source)
{
    return SelectPairs(source, (t1, t2) => new Tuple<T, T>(t1, t2));
}

/// <summary>
/// Projects two consecutive pair of items into a new form.
/// {1,2,3,4} -> {pairCreator(1,2), pairCreator(2,3), pairCreator(3,4))
/// </summary>
public static IEnumerable<TResult> SelectPairs<T, TResult>(
    this IEnumerable<T> source, Func<T, T, TResult> pairCreator)
{
    T lastItem = default(T);
    bool isFirst = true;
    foreach (T currentItem in source)
    {
        if (!isFirst)
        {
            yield return pairCreator(lastItem, currentItem);
        }

        isFirst = false;
        lastItem = currentItem;
    }
}

Use it like this:

bool isOrdered = myCollection
    .SelectPairs()
    .All(t => t.Item1.MyProperty < t.Item2.MyProperty);

This statement can of course be placed in another extension method:

public static bool IsOrdered<T>(
    this IEnumerable<T> source, Func<T, T, int> comparer)
{
    return source.SelectPairs().All(t => comparer(t.Item1, t.Item2) > 0);
}

bool isOrdered = myCollection
    .IsOrdered((o1, o2) => o2.MyProperty - o1.MyProperty);

Here's an implementation that uses a predicate to select the value to order by.

public static bool IsOrdered<TKey, TValue>(this IEnumerable<TKey> list, Func<TKey, TValue> predicate) where TValue : IComparable
{
    if (!list.Any()) return true;
    var previous = predicate(list.First());

    foreach(var entry in list.Skip(1))
    {
        var current = predicate(entry);
        if (previous.CompareTo(current) > 0)
            return false;
        previous = current;
    }
    return true;
}
易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!