使用 OrderByDescending 在每一步返回 IEnumerable<T> 列表集合中的最高值

问题描述

这是我第一次在网站上提问,所以我可能做得不对。我需要在 IEnumerable 列表中查找并返回最高值,但不能显示任何重复项。我也不能使用任何副本或临时集合,或使用不同的。它必须首先通过获取列表并执行 OrderByDescending 来完成。

这是我目前所拥有的:

public static IEnumerable<T> PullMax<T> (IEnumerable<T> sourcelist) where T : IComparable
{
    IEnumerable<T> list = sourcelist.OrderByDescending(item => item);

    foreach (T item in list)
    {
        yield return item;
    }
}


这是我从这段代码中得到的结果:


foreach (int item in Util.PullMax(Util.GenRange(iMin,iMax).Concat(Util.GenRange(iMin + 5,iMax + 5))))
{
    Console.Write($"\nMaximum Value: {item}");
}    

Maximum Value: 15
Maximum Value: 14
Maximum Value: 13
Maximum Value: 12
Maximum Value: 11
Maximum Value: 10
Maximum Value: 10
Maximum Value: 9
Maximum Value: 9
Maximum Value: 8
Maximum Value: 8
Maximum Value: 7
Maximum Value: 6
Maximum Value: 5
Maximum Value: 4
Maximum Value: 3

这应该是预期的结果:

Maximum Value: 15
Maximum Value: 14
Maximum Value: 13
Maximum Value: 12
Maximum Value: 11
Maximum Value: 10
Maximum Value: 9
Maximum Value: 8
Maximum Value: 7
Maximum Value: 6
Maximum Value: 5
Maximum Value: 4
Maximum Value: 3

我被要求展示 Util.GenRange()

public static IEnumerable<int> GenRange(int iMin,int iMax)
        {
            while (true)
            {
                for (int i = iMin; i < iMax; i++)
                {
                    yield return i;
                }
                yield break;
            }
        }

我得到的结果有重复,我不能有它们。如何在没有任何副本或临时集合甚至使用不同的情况下摆脱它们?

解决方法

避免另一个解决方案的多重枚举问题的方法(即另一个解决方案在可枚举项上迭代不止一次):

public static IEnumerable<T> PullMax<T> (IEnumerable<T> sourcelist) where T : IComparable
{
    IEnumerable<T> list = sourcelist.OrderByDescending(item => item);
 
    T previousValue = default(T);
    bool firstIteration = true;
 
    foreach (T item in list)
    {
        if (firstIteration)
            firstIteration = false;
        else if (item.CompareTo(previousValue) == 0)
            continue;
        
        previousValue = item;
        yield return item;
    }
}

基本上它总是返回第一个元素 - 然后对于第一个元素以外的元素,只有当它们与前一个元素不匹配时才返回它们。对于某些类别的数据(例如,其中 sourcelist 是 IQueryable),这将显着更快,因为它避免了不必要的计数等。

,

自己编写这个算法有很多不同的选择。

然而,在可能的情况下重用代码总是更好的,唯一的问题是你还没有找到一种只在一次传递中进行不同排序的 LINQ 方法。

但是有 SortedSet<T>,它基本上是一个排序的 distinct 列表,它声称 docker-compose.yml 用于插入,并且可以采用 O(n log n)。它不会抛出重复项,它只是忽略它们:

IEnumerable

要使其按降序排列,请使用 static IEnumerable<T> PullMax<T>(this IEnumerable<T> source,IComparer<T> comparer) { return new SortedSet<T>(source,comparer); } 翻转 lambda 中的比较结果:

-
,

你写道:

我需要在 IEnumerable 列表中查找并返回最高值,但不能显示任何重复项。

从您的代码中,我认为您不仅需要输入序列的最高值,而且还希望值按降序排列,没有任何重复。

为了使用法类似于 LINQ,我将其创建为扩展方法。如果您不熟悉扩展方法,请阅读 extension methods demystified

扩展方法只是语法糖。它让读者看起来好像你调用的方法是类的方法。

而不是在静态类中调用方法:

var customers = FetchCustomers();
var maxCustomers = ExtraCustomerFunctions.PullMax(customers);

你可以写:

var customers = FetchCustomers();
var maxCustomers = customers.PullMax();

为此,您唯一需要做的就是在静态类中创建一个静态方法并在第一个参数前使用单词 this

回到你的问题

要求
从实现 T 的类 IComparable<T> 的对象的输入序列中,返回一个序列,其中第一个元素是最大的项目,然后是最大的项目,等等。丢弃重复值。

public class MyExtensionMethods
{
    public static IEnumerable<T> PullMax<T> (this IEnumerable<T> source)
        where T : IComparable<T>
    {
        IEnumerable<T> orderedSource = source.OrderByDescending(item => item);

        using (IEnumerator<T> enumerator = orderedSource.GetEnumerator())
        {
            if (enumerator.MoveNext())
            {
                // The sequence is not empty.
                // return the first item,and remember that you returned this value
                T lastReturnedItem = enumerator.Current;
                yield return lastReturnedItem;

                // enumerate the rest,skip the items with a value that you just returned
                while (enumerator.MoveNext())
                {
                    // Current is either as large as the last returned one,or it is smaller
                    if (enumerator.Curren.Compare(lastReturnedItem) < 0)
                    {
                        // Current is smaller; return it,and remember that you returned it
                        lastReturnedItem = enumerator.Current;
                        yield return lastReturnedItem;
                     }
                }
            }
        }
    }
}

这看起来代码很多,但大部分都是注释。

用法:

int[] numbers = FetchNumbers();
var pulledMaxNumbers = numbers.PullMax();

还有改进的空间!

如果您打算重用此方法,请考虑使其更通用,更像 LINQ。这样就可以很容易地对没有可比性的项目使用该方法。

客户没有可比性,但您可以在 BirthDay 或 PostCode 进行比较。只需稍加改动,PullMax 客户就可以:

// PullMax customers,oldest Customers first:
IEnumerable<Customer> customers = this.FetchCustomers();
var result = customers.PullMax(customer => customer.BirthDay);

为此,创建一个带有属性选择器和通用 IComparer 的方法。 像这样:

static IEnumerable<T> PullMax<T>(this IEnumerable<T> source)
{
    return source.PullMax(item => item);
}

static IEnumerable<T> PullMax<T,TKey>(this IEnumerable<T> source,Func<T,TKey> propertySelector)
{
    return source.PullMax(propertySelector,null);
}

static IEnumerable<T> PullMax<T,TKey> keySelector,IComparer<TKey> comparer)
{
    // TODO: check source and keySelector not null
    if (comparer == null) comparer = Comparer<TKey>.Default;

    // rest of code is similar to code above,difference: check on keySelector
    IEnumerable<T> orderedSource = source.OrderByDescending(keySelector);
    using (IEnumerator<T> enumerator = orderedSource.GetEnumerator())
    {
        if (enumerator.MoveNext())
        {
            T currentValue = enumerator.Current;
            yield return currentValue
            TKey lastKeyValue = keySelector(currentValue);

            while (enumerator.MoveNext())
            {
                currentValue = enumerator.Current;
                TKey keyValue = keySelector(currentValue);
                if (comparer.Compare(keyValue,lastKeyValue) < 0)
                {
                     yield return currentValue;
                     lastKeyValue = keyValue;
                }

您甚至可以将 PullMax 放入 LINQ 串联中:

var result = Customers.Where(customer => customer.City == "New York")
    .PullMax(customer => customer.PostCode)
    .Select(customer => new
    {
        Id = customer.Id,Name = customer.Name,});
,

完全没有任何临时变量或不同的完全没有机会。但是最便宜的解决方法是使用一个临时变量来保存最后一个返回值并检查该值是否相同。

也许这个方法应该可以解决问题

 public static IEnumerable<T> PullMax<T>(IEnumerable<T> sourcelist) where T : IComparable
        {
            IEnumerable<T> list = sourcelist.OrderByDescending(item => item);
            if (sourcelist.LongCount() > 0) yield return list.ElementAt(0);
            T tmp = sourcelist.ElementAt(0);
            foreach (T item in list)
            {
                if (item.CompareTo(tmp) < 1) continue;
                tmp = item;
                yield return item;
            }
        }