使用 OrderByDescending 每一步返回 IEnumerable<T> 列表集合中的最大值
Posted
技术标签:
【中文标题】使用 OrderByDescending 每一步返回 IEnumerable<T> 列表集合中的最大值【英文标题】:Return the highest value in the IEnumerable<T> list collection each step using OrderByDescending 【发布时间】:2021-01-21 07:03:35 【问题描述】:这是我第一次在网站上提问,所以我可能做得不对。我需要在我的 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;
我得到的结果有重复,我不能拥有它们。我如何在没有任何副本或临时集合甚至不使用 distinct 的情况下摆脱它们?
【问题讨论】:
为什么你不能使用Distinct
或临时集合?
您有一个 IEnumerable,并且您想删除重复项。看起来很不同。所以让我们看看区如何做到这一点referencesource。如果有一个不需要任何资源的解决方案,它就会在那里
Util.GenRange
长什么样子?如果您以某种方式生成/获取具有相同最大值的两个可枚举项,您如何期望该方法知道在输出中忽略(?)其中一个而无需以某种方式“记住”已经输出的内容? 您必须保证您将永远生成/获取多个具有相同最大值的可枚举项。 - 顺便提一句。这听起来像是一个相当奇怪的规定,与 XY 问题接壤......
但是当套装被订购时,你不需要一个完整的Set
..
@Corak 公平地说,这是一个好的约束,因为它迫使你以一种有效的方式实现它。
【参考方案1】:
一种避免与另一个解决方案的多重枚举问题的方法(即另一个解决方案在可枚举对象上迭代不止一次):
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
)会显着加快,因为它避免了不必要的计数等。
【讨论】:
【参考方案2】:自己编写这个算法有很多不同的选择。
但是,在可能的情况下重用代码总是更好,唯一的问题是您还没有找到一种 LINQ 方法,它只在一次通道中执行不同的排序。
但是有SortedSet<T>
,它基本上是一个排序的distinct 列表,它声称O(n log n)
用于插入,并且可以采用IEnumerable
。它不会抛出重复,它只是忽略它们:
static IEnumerable<T> PullMax<T>(this IEnumerable<T> source, IComparer<T> comparer)
return new SortedSet<T>(source, comparer);
要使其按降序排列,请使用 -
翻转 lambda 中的比较结果:
list.PullMax((a, b) => -a.CompareTo(b))
【讨论】:
【参考方案3】:你写道:
我需要在我的 IEnumerable 列表中查找并返回最高值,但不能显示任何重复项。
从您的代码中,我了解到您不仅想要输入序列的最高值,还想要按降序排列的值,没有任何重复。
为了像 LINQ 一样使用,我将其创建为扩展方法。如果你不熟悉扩展方法,请阅读extension methods demystified
扩展方法只是语法糖。它使它看起来像你调用的方法是类的方法一样寻找读者。
而不是调用静态类中的方法:
var customers = FetchCustomers();
var maxCustomers = ExtraCustomerFunctions.PullMax(customers);
你可以写:
var customers = FetchCustomers();
var maxCustomers = customers.PullMax();
为此,您唯一要做的就是在静态类中创建一个静态方法,并在第一个参数前使用单词this
。
回到你的问题
要求
从实现IComparable<T>
的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。这样,对于不可比较的项目,该方法将很容易使用。
客户没有可比性,但您可以在生日或邮政编码进行比较。 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>(this IEnumerable<T> source,
Func<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,
);
【讨论】:
【参考方案4】:完全没有任何临时变量或独特的没有机会。但最便宜的解决方法是使用一个临时变量来保存最后一个返回值,并检查该值是否相同。
也许这个方法应该可以解决问题
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;
【讨论】:
我可以使用临时变量,但我需要做什么? 添加了一种可能的方法。未经测试 好的,改变方法来解决空问题。如果可用,则产生第一项,并对新的 tmp 变量进行以下检查 它只给了我列表的开头和结尾,所以最大值:15 和最大值:3 是的,我改变了它。它现在显示正确的顺序,但是在显示正确的输出之前以最大值开始:3以上是关于使用 OrderByDescending 每一步返回 IEnumerable<T> 列表集合中的最大值的主要内容,如果未能解决你的问题,请参考以下文章
为啥添加两个 .OrderBy(或 .OrderByDescending)语句会以相反的顺序应用排序?
linq ,语句是 query = query.OrderByDescending(NewsTj => NewsTj.Id); 为啥不按倒序进行排序呢?
EF OrderBy(string propertyname), OrderByDescending(string propertyname) 按属性排序,扩展方法