C# 无需自定义类使用LINQ GroupBy处理CSV调整客户消费数据
CSV消费金额按用户限额调整实现方案
需求说明
原始数据格式
| Name | Spending | Category |
|---|---|---|
| John | 100 | Grocery |
| John | 50 | Dining |
| Michael | 55 | Carpooling |
| Steven | 15 | Grocery |
| Steven | 50 | Dining |
预期输出格式
| Name | Spending | Category |
|---|---|---|
| John | 40 | Grocery |
| John | 20 | Dining |
| Michael | 55 | Carpooling |
| Steven | 13.846 | Grocery |
| Steven | 46.154 | Dining |
处理规则
- 按姓名(Name)分组统计单个用户的总消费金额
- 若用户总消费金额超过60,总金额上限设为60,Carpooling等除外类别的消费不受限额影响,金额直接保留
- 普通类别消费的调整后金额,按原始金额占该用户普通类别总消费的比例重新分配
- 调整后的数据保留原始行顺序,替换原Spending列后写入新CSV
现有代码问题
原有代码核心问题是把每行的三个属性拆分到三个独立的List,无法关联同一行的属性进行分组计算,同时缺少类别排除、比例分配、结果写回的逻辑。
修正后完整代码
using System; using System.Collections.Generic; using System.IO; using System.Linq; namespace Query_csv { // 定义实体存储每行原始数据和原始索引,保证输出顺序不变 public class CustomerSpendingEntry { public string Name { get; set; } public double OriginalAmount { get; set; } public double RevisedAmount { get; set; } public string Category { get; set; } public int OriginalIndex { get; set; } } class Program { static void Main(string[] args) { string inputPath = @"Customer Spending.csv"; string outputPath = @"Adjusted Customer Spending.csv"; const double MAX_TOTAL_SPENDING = 60; // 除外类别集合,可随时新增其他不需要限额的类别 var excludedCategories = new HashSet<string> { "Carpooling" }; // 1. 读取并解析CSV var rawLines = File.ReadAllLines(inputPath); string header = rawLines[0]; var entries = new List<CustomerSpendingEntry>(); for (int i = 1; i < rawLines.Length; i++) { var parts = rawLines[i].Split(','); entries.Add(new CustomerSpendingEntry { Name = parts[0].Trim(), OriginalAmount = Convert.ToDouble(parts[1].Trim()), Category = parts[2].Trim(), OriginalIndex = i }); } // 2. 按用户名分组处理 var userGroups = entries.GroupBy(e => e.Name); foreach (var group in userGroups) { // 拆分除外类别和普通类别记录 var excludedEntries = group.Where(e => excludedCategories.Contains(e.Category)).ToList(); var normalEntries = group.Where(e => !excludedCategories.Contains(e.Category)).ToList(); double excludedTotal = excludedEntries.Sum(e => e.OriginalAmount); double normalTotal = normalEntries.Sum(e => e.OriginalAmount); double totalSpending = excludedTotal + normalTotal; // 除外类别金额直接保留原值 foreach (var entry in excludedEntries) { entry.RevisedAmount = entry.OriginalAmount; } // 总金额未超过限额,普通类别也保留原值 if (totalSpending <= MAX_TOTAL_SPENDING) { foreach (var entry in normalEntries) { entry.RevisedAmount = entry.OriginalAmount; } continue; } // 总金额超限额,计算普通类别可分配总金额 double availableNormalTotal = MAX_TOTAL_SPENDING - excludedTotal; if (availableNormalTotal <= 0 || normalTotal == 0) { foreach (var entry in normalEntries) { entry.RevisedAmount = 0; } continue; } // 按比例分配金额 foreach (var entry in normalEntries) { double ratio = entry.OriginalAmount / normalTotal; entry.RevisedAmount = Math.Round(ratio * availableNormalTotal, 3); } } // 3. 按原始索引排序,构建输出CSV var sortedEntries = entries.OrderBy(e => e.OriginalIndex).ToList(); var outputLines = new List<string> { header }; foreach (var entry in sortedEntries) { outputLines.Add($"{entry.Name},{entry.RevisedAmount},{entry.Category}"); } File.WriteAllLines(outputPath, outputLines); Console.WriteLine("处理完成,结果已保存到 " + outputPath); Console.ReadKey(); } } }
代码说明
- 自定义实体类存储每行全量信息和原始索引,避免输出行顺序错乱
- 除外类别用HashSet存储,后续新增其他免限额类别直接加元素即可
- 增加了除零、可分配金额为负的边界场景处理
- 输出金额默认保留3位小数,可自行修改
Math.Round的第二个参数调整精度
内容的提问来源于stack exchange,提问作者exd-AS
相关产品推荐
相关产品推荐

