【问题标题】:Method that recognized if a IEnumerable is sorted识别 IEnumerable 是否已排序的方法
【发布时间】:2018-08-07 06:18:33
【问题描述】:

我有这个扩展方法来检查任何类型的列表是否已排序

public static bool IsSorted<T>(this IEnumerable<T> input)
{
    IEnumerable<T> expectedListASC = input.OrderBy(x => x);
    IEnumerable<T> expectedListDESC = input.OrderByDescending(x => x);
    return expectedListASC.SequenceEqual(input) || expectedListDESC.SequenceEqual(input);
}

但是对于大型列表,这需要时间。有没有更有效的方法来获得相同的结果?

【问题讨论】:

  • 为什么不只枚举列表并检查后面的元素是否总是小于/大于前面的元素。它在时间上是线性的(如果未排序,您可以提前放弃检查) - 对于未排序的列表,它将接近 const time。
  • 多次使用可枚举是 a) 不保证是可能的,b) 不保证是一致的。如果你想要solidly 通用的东西,你的起点不应该是IEnumerable&lt;T&gt;。即使您更改代码以使用例如ToList() 首先,无论您返回什么,您的调用者仍然存在这些问题。如果调用者需要排序的可枚举,只需让他们使用OrderBY 并接受如果可枚举已经排序,它可能是多余的。
  • @FCin - 在我看来,这不是一个很好的重复 - 对于ints 的非泛型数组,代码更容易,而且您不需要查找两种升序/降序。
  • “排序”和“按默认比较排序”不一定相同...另外,请考虑在计算其他任何内容之前先对 IOrderedEnumerable&lt;T&gt; 进行类型检查。

标签: c# list sorting


【解决方案1】:

这是一个通用方法,它应该检测序列是按升序还是降序排列,然后检查集合的其余部分是否遵循。

没有经过全面测试,如果你决定使用它,你应该左右扔数据集并编写单元测试。

public static class CollectionExtensions
{
    public static bool IsOrdered<T>(this IEnumerable<T> collection, IComparer<T> comparer = null)
    {
        comparer = comparer ?? Comparer<T>.Default;

        bool? expectedToIncrease = null;
        using (var enumerator = collection.GetEnumerator())
        {
            bool gotFirst = enumerator.MoveNext();
            if (!gotFirst)
                return true; // empty collection is ordered
            var first = enumerator.Current;
            T second = default(T);

            while (expectedToIncrease is null)
            {
                bool gotSecond = enumerator.MoveNext();
                if (!gotSecond)
                    return true; // only equal elements are ordered
                second = enumerator.Current;

                switch (comparer.Compare(first, second))
                {
                    case int i when i < 0:
                        expectedToIncrease = false;
                        break;

                    case int i when i > 0:
                        expectedToIncrease = true;
                        break;
                }

                if (expectedToIncrease is null)
                    first = second; // prepare for next round
            }

            while (enumerator.MoveNext())
            {
                if (expectedToIncrease.GetValueOrDefault())
                {
                    if (comparer.Compare(second, enumerator.Current) < 0)
                        return false;
                }
                else
                {
                    if (comparer.Compare(second, enumerator.Current) > 0)
                        return false;
                }
            }

            return true;
        }
    }
}

【讨论】:

    【解决方案2】:

    这样的事情应该可以工作:

    public static bool IsSorted<T>(IEnumerable<T> input)
    {
        if (input is IOrderedEnumerable<T>)
        {
            return true;
        }
    
        var comparer = Comparer<T>.Default;
        T previous = default(T);
        bool previousSet = false;
        bool? comparisonOrder = null;
        foreach (var value in input)
        {
            if (!previousSet)
            {
                previous = value;
                previousSet = true;
            }
            else
            {
                int comparisonResult = comparer.Compare(previous, value);
                if (comparisonResult != 0)
                {
                    if (!comparisonOrder.HasValue)
                    {
                        comparisonOrder = comparisonResult > 0;
                    }
                    else if (comparisonResult > 0 != comparisonOrder)
                    {
                        return false;
                    }
                }
                previous = value;
            }
        }
        return true;
    }
    

    它在跟踪前一个项目的同时遍历每个项目,然后使用默认比较器(如.OrderBy() 那样)检查它们是否已排序。为了允许检查任一方向的排序,我存储了第一个非零比较的结果,并将其用作检查点。

    正如 cmets 中已经指出的那样,并非所有 IEnumerables 都是可重复的,并且重复那些可能会很昂贵,具体取决于提供 IEnumerable 的内容的实现。此外,请考虑返回随机数的 IEnumerable 的情况 - 每次迭代它时,它都会给出不同的值(假设种子每次都不相同)。

    对 50,000 个项目(5,000 次迭代)的排序列表的测试表明:

    • Lasse 用了 2137 毫秒来确定它是否已排序。
    • 我的方法用了 2348 毫秒来确定 IEnumerable 是否已排序。
    • MineR 需要 2403 毫秒才能返回结果。

    【讨论】:

    • 升序和降序有区别吗?
    • 确实如此,但现在已修复,所以它应该检查任何一个。不过,Lasse 的效率可能比我的高一点。
    • 对此有一个注释 - comparer.Compare 可能返回任何正数/负数,因此您应该使用 Math.Sign 来获取 1-10
    • @Kobi 谢谢,已修复:)
    • 感谢您的性能测量!现在我想对我的代码进行微优化。 :P
    【解决方案3】:

    我已经包含了以下解决方案,它与其他解决方案的不同之处在于您可以指定一个比较器,它会告诉您集合的排序顺序。

    public static class LinqHelpers
    {
        [Flags]
        public enum SortDirections
        {
            NotSorted = 0,
            Ascending = 1,
            Descending = 2,
        }
        public static SortDirections GetSortDirection<T>(this IEnumerable<T> input, IComparer<T> comparer = null)
        {
            comparer = comparer ?? Comparer<T>.Default;
    
            bool isAsc = true;
            bool isDsc = true;
            bool isFirst = true;
            T last = default(T);
            foreach (var val in input)
            {
                if (isFirst)
                {
                    isFirst = false;
                }
                else
                {
                    int cmp = comparer.Compare(last, val);
                    if (cmp > 0) isAsc = false;
                    if (cmp < 0) isDsc = false;
                }
                if (!isAsc && !isDsc) break;
                last = val;
            }
            int result = 0;
            if (isAsc) result |= (int)SortDirections.Ascending;
            if (isDsc) result |= (int)SortDirections.Descending;
            return (SortDirections)result;
        }
    }
    

    一些极端情况:

    • 如果是 0 个元素,则认为它是双向排序的。
    • 如果是 1 个元素,则认为它是双向排序的。
    • 如果所有元素都相同,则认为它是双向排序的。

    为什么对于大型数据集您的速度很慢?您正在对数据进行排序,即 O(n log n)。这个问题只需要 O(n)。

    【讨论】:

      猜你喜欢
      • 2016-09-23
      • 2022-11-12
      • 1970-01-01
      • 1970-01-01
      • 2011-04-14
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多