首页 > 解决方案 > 如何使用 LINQ 按值范围对项目进行分组

问题描述

我在使用 linq 对对象进行分组时遇到问题,想知道是否有人可以告诉我我在这里做错了什么。此外,选择功能是我从此处(链接)获取的扩展,因此我可以比较前一个值和当前值,如果值在一个范围之间,那么我将值设置为当前值。

// Range of values, the first item in the group data, i 
var ranges = new List<double> { 7.0, 6.0, 5.0, 4.0, 3.0, 2.0, 1.0 };

        // Simple class i created
        public class CurrencyGroupItemData
        {
            public string Code { get; set; }

            public double TotalStrength { get; set; }
        }

        var lstCurrencyGroups = new List<CurrencyGroupItemData>();

        lstCurrencyGroups.Add(new CurrencyGroupItemData
        { Code = "USD", TotalStrength = 5.0 });
        lstCurrencyGroups.Add(new CurrencyGroupItemData
        { Code = "CHF",TotalStrength = 2.14285714285714 });
        lstCurrencyGroups.Add(new CurrencyGroupItemData
        { Code = "EUR",TotalStrength = 3.85714285714286 });
        lstCurrencyGroups.Add(new CurrencyGroupItemData
        { Code = "GBP",TotalStrength = 3.42857142857143 });
        lstCurrencyGroups.Add(new CurrencyGroupItemData
        { Code = "JPY",TotalStrength = 5.71428571428571 });
        lstCurrencyGroups.Add(new CurrencyGroupItemData
        { Code = "CAD",TotalStrength = 6.85714285714286 });
        lstCurrencyGroups.Add(new CurrencyGroupItemData
        { Code = "AUD",TotalStrength = 4.28571428571429 });
        lstCurrencyGroups.Add(new CurrencyGroupItemData
        { Code = "NZD",TotalStrength = 4.71428571428571 });

// compare The total strength of each object, if it's value is between any of ranges above then group item by the range value. for example if the object totalstrength value is 5.7 that value is between 6.0 and 5.0, it's range value would the minimum of the 2 which 5.0, i would group that item by 5.0. 
var jjjj01 = lstCurrencyGroups.GroupBy(x => ranges.SelectWithPrev((double r1, double r2, bool isfirst)
            => (isfirst && x.TotalStrength >= r1) ? r1 : (x.TotalStrength <= r1 && x.TotalStrength <= r2) ? r2 : 0.0).ToArray())
            .Select(g => new { Rank = g.Key, Count = g.Count() })
            .ToList();

// the extension i grabbed from the link above
public static IEnumerable<TResult> SelectWithPrev<TSource, TResult>
(this IEnumerable<TSource> source, Func<TSource, TSource, bool, TResult> projection)
{
    using (var iterator = source.GetEnumerator())
    {
        var isfirst = true;
        var previous = default(TSource);
        while (iterator.MoveNext())
        {
            yield return projection(iterator.Current, previous, isfirst);
            isfirst = false;
            previous = iterator.Current;
        }
    }
}

标签: c#linq

解决方案


使用我自己的扩展来配对范围:

public static IEnumerable<TResult> ScanByPairs<T, TResult>(this IEnumerable<T> src, Func<T, T, TResult> combineFn) {
    using (var srce = src.GetEnumerator()) {
        if (srce.MoveNext()) {
            var prev = srce.Current;

            while (srce.MoveNext()) {
                yield return combineFn(prev, srce.Current);
                prev = srce.Current;
            }
        }
    }
}

答案很简单。将范围转换为范围对:

var rangesWithKey = ranges.ScanByPairs((p,c) => (max: p, min: c)).ToList();

然后按包含TotalStrength.

var ans = lstCurrencyGroups.GroupBy(cg => rangesWithKey.FirstOrDefault(rk => rk.min <= cg.TotalStrength && cg.TotalStrength <= rk.max).min)
                           .Select(cgg => new { Rank = cgg.Key, Count = cgg.Count() })
                           .OrderBy(cgg => cgg.Rank);

推荐阅读