将增长和 return 作为行计算到单独的列表

Calculate growth and return as rows to a separate List

我有一个 iList,其中包含多个观察单位的年度值(例如 BBV 值、价格、税收、其他)。通常该值包含小数,但在某个范围内的某些年份,任何给定的观察单位都包含 null 或 0。

对于其中许多数据单元,我需要将它们的年增长率绘制成百分比图,我想在 YearlyGrowthTable 中 return。 return计算平均年增长率,或者在数据被 returned 后在主代码中计算的方法,将是一个奖励。

虽然我计划为每个数据单元复制这个 class,但我觉得有一种方法可以对此进行编码,这样我就可以通过单个 class 传递每个数据单元,如果我只是聪明了一点。

以下是我为 BVV 价值所做的笨拙尝试。我包括构建源 YearlyValuesTable table 而不是提供实际的 table 来帮助您帮助我,但实际上年度值将从数据库读取中传递。对不起,如果这样的话会很长

using System;
using System.Collections.Generic;
using System.Linq;

public class YearlyValuesTable
{
    public string Code { get; set; }
    public int Year { get; set; }
    public string Type { get; set; }
    public decimal? Value { get; set; }
    public decimal? Growth { get; set; }
    public bool Active { get; set; }
    
    public override string ToString() => $"Code: {Code}   |   Year: {Year}   |   Type: {Type}   |   Value: {Value}   |   Active: {Active} ";
}

//new YearlyGrowthTable { Code = yv.code, Year = year, Type = "BBVVal", Value = growth },
public class YearlyGrowth
{
    public string Code { get; set; }
    public int? Year { get; set; }
    public string Type { get; set; }
    public decimal? Value { get; set; }
    
    public override string ToString() => $"Code: {Code}   |   Year: {Year}   |   Type: {Type}   |   Value: {Value} ";
}   

public class Program
{
    public static void CalculateYearlyBBVGrowth()
    {
        List<YearlyValuesTable> yv = new()
        {
            new YearlyValuesTable { Code = "ABC", Year = 2010, Type = "Other", Value = 71.20m, Active = true },
            new YearlyValuesTable { Code = "ABC", Year = 2010, Type = "BBVVal", Value = 3445m, Active = true },
            new YearlyValuesTable { Code = "ABC", Year = 2011, Type = "BBVVal", Value = 17667m, Active = true },
            new YearlyValuesTable { Code = "ABC", Year = 2012, Type = "BBVVal", Value = -2884m, Active = true },
            new YearlyValuesTable { Code = "ABC", Year = 2013, Type = "BBVVal", Value = 6577m, Active = true },
            new YearlyValuesTable { Code = "ABC", Year = 2014, Type = "BBVVal", Value = 3963m, Active = true },
            new YearlyValuesTable { Code = "ABC", Year = 2015, Type = "BBVVal", Value = 7183m, Active = true },
            new YearlyValuesTable { Code = "ABC", Year = 2016, Type = "BBVVal", Value = -4561m, Active = true },
            new YearlyValuesTable { Code = "ABC", Year = 2017, Type = "BBVVal", Value = -807m, Active = true },
            new YearlyValuesTable { Code = "ABC", Year = 2018, Type = "BBVVal", Value = 1109m, Active = true },
            new YearlyValuesTable { Code = "ABC", Year = 2019, Type = "BBVVal", Value = null, Active = true },
            new YearlyValuesTable { Code = "ABC", Year = 2020, Type = "BBVVal", Value = 38860m, Active = true },
            new YearlyValuesTable { Code = "ABC", Year = 2021, Type = "BBVVal", Value = 41312m, Active = true },
            new YearlyValuesTable { Code = "ABC", Year = 2022, Type = "BBVVal", Value = null, Active = true }
        };
            string code = yv.Where(x => x.Year > 1).Select(x => x.Code).FirstOrDefault();
            int? oldestYear = yv.Where(x => x.Type == "BBVVal" && x.Active).OrderBy(x => x.Year).Select(x => x.Year).FirstOrDefault();      // may not need this
            decimal? oldestBBV = yv.Where(x => x.Type == "BBVVal" && x.Active).OrderBy(x => x.Year).Select(x => x.Value).FirstOrDefault();
            //decimal BBVValue = oldestBBV.HasValue ? oldestBBV.Value : 0.0m;   //doesn't seem to be necessary

            //Work out growth between BBV value year on year 
            //Can this line be in the control code and pass any individual data unit of observation  (BBV, Tax, Price) to this class rather than repeat the whole class for each data unit?    
            //CalculateYearlyGrowth instead of CalculateYearlyBBVGrowth, CalculateYearlyTaxGrowth, etc?
            IList<decimal?> yearlyBBVValues = yv.Where(x => x.Type == "BBVVal" && x.Active).OrderBy(x => x.Year).Select(x => x.Value).ToList();
            IList<decimal> BBVGrowth = new List<decimal>(); 
            int? year = oldestYear;
            decimal previousBBVValue = 0.00m;
            var growth = 0.00m;
            foreach (var BBV in yearlyBBVValues)
            {
                if (previousBBVValue == 0.00m)
                {
                    growth = 0.00m;
                }
                else
                {
                    growth = BBV.HasValue ? (BBV.Value - previousBBVValue)/Math.Abs(previousBBVValue) * 100 : 0.00m;
                    growth = Math.Truncate(100 * growth) / 100;
                }   
                if (year > oldestYear)
                {
                    BBVGrowth.Add(growth);
                    // I suspect I'll need a new IList for yearly growth so I can display as a graph later as Year x growth for each data Type
                    IEnumerable<YearlyGrowth> AnnualGrowth = new YearlyGrowth()
                    {
                        Code = code, 
                        Year = year, 
                        Type = "BBVVal", 
                        Value = growth
                    };

                    List<YearlyGrowth> YearlyGrowthTable = AnnualGrowth.ToList();
                    //new YearlyGrowthTable = { Code = yv.code, Year = year, Type = "BBVVal", Value = growth },
                }
                previousBBVValue = BBV.HasValue ? BBV.Value : 0.00m;
                Console.WriteLine(year.ToString() + " | " + BBV.ToString() + " | " + growth.ToString() );       // View for testing only
                year++;                 
            }
            
            decimal averageBBVGrowth = BBVGrowth.Average() / 100;
            //Console.WriteLine(">>> Average Growth: " + averageBBVGrowth.ToString("P"));       // View for testing only
        
        foreach (var entry in BBVGrowth)
        {
            Console.WriteLine("BBV growth: " + entry);      // View for testing only
        }

        foreach (var entry in YearlyGrowthTable)
        {
            Console.WriteLine("- " + entry);        // View for testing only
        }
        
        // Return the percentage growth percentage as +/-#.## 
        return YearlyGrowthTable();
    }
}

这是否满足您的需求?

List<YearlyValuesTable> yv = new()
{
    new YearlyValuesTable { Code = "ABC", Year = 2010, Type = "Other", Value = 71.20m, Active = true },
    new YearlyValuesTable { Code = "ABC", Year = 2010, Type = "BBVVal", Value = 3445m, Active = true },
    new YearlyValuesTable { Code = "ABC", Year = 2011, Type = "BBVVal", Value = 17667m, Active = true },
    new YearlyValuesTable { Code = "ABC", Year = 2012, Type = "BBVVal", Value = -2884m, Active = true },
    new YearlyValuesTable { Code = "ABC", Year = 2013, Type = "BBVVal", Value = 6577m, Active = true },
    new YearlyValuesTable { Code = "ABC", Year = 2014, Type = "BBVVal", Value = 3963m, Active = true },
    new YearlyValuesTable { Code = "ABC", Year = 2015, Type = "BBVVal", Value = 7183m, Active = true },
    new YearlyValuesTable { Code = "ABC", Year = 2016, Type = "BBVVal", Value = -4561m, Active = true },
    new YearlyValuesTable { Code = "ABC", Year = 2017, Type = "BBVVal", Value = -807m, Active = true },
    new YearlyValuesTable { Code = "ABC", Year = 2018, Type = "BBVVal", Value = 1109m, Active = true },
    new YearlyValuesTable { Code = "ABC", Year = 2019, Type = "BBVVal", Value = null, Active = true },
    new YearlyValuesTable { Code = "ABC", Year = 2020, Type = "BBVVal", Value = 38860m, Active = true },
    new YearlyValuesTable { Code = "ABC", Year = 2021, Type = "BBVVal", Value = 41312m, Active = true },
    new YearlyValuesTable { Code = "ABC", Year = 2022, Type = "BBVVal", Value = null, Active = true }
};

IEnumerable<YearlyGrowth> ComputeGrowth(string code, string type)
{
    var valids =
        yv
            .Where(x => x.Code == code)
            .Where(x => x.Type == type)
            .Where(x => x.Active)
            .Where(x => x.Value.HasValue && x.Value.Value != 0m)
            .OrderBy(x => x.Year)
            .ToArray();
    var lookup = valids.ToLookup(x => x.Year, x => x.Value.Value);
    int first = valids.First().Year;
    int last = valids.Last().Year;
    int count = last - first + 1;
    foreach (var year in Enumerable.Range(first, count).Skip(1))
    {
        yield return new YearlyGrowth()
        {
            Code = code,
            Type = type,
            Year = year,
            Value =
                (lookup[year - 1].Any() && lookup[year].Any())
                ? lookup[year].Average() / lookup[year - 1].Average()
                // .Average() here only to remove multiple values if any
                // Otherwise a .First() would work.
                : null
        };
    }
}

foreach (var yg in ComputeGrowth("ABC", "BBVVal"))
{
    Console.WriteLine($"Year {yg.Year} had {(yg.Value.HasValue ? yg.Value.Value.ToString("0.0%") : "null")} growth");
}

我得到的输出是这样的:

Year 2011 had 512.8% growth
Year 2012 had -16.3% growth
Year 2013 had -228.1% growth
Year 2014 had 60.3% growth
Year 2015 had 181.3% growth
Year 2016 had -63.5% growth
Year 2017 had 17.7% growth
Year 2018 had -137.4% growth
Year 2019 had null growth
Year 2020 had null growth
Year 2021 had 106.3% growth

在进行计算时,请提几点建议 - 不要将值存储为百分比,并且在计算过程中不要截断或舍入。在显示最终结果时,只将格式设置为百分比并截断。