[英]C# Splitting a List<string> Value
我有一个包含值 {"1 120 12"、"1 130 22"、"2 110 21"、"2 100 18"} 等的列表。
List<string> myList = new List<string>();
myList.Add("1 120 12");
myList.Add("1 130 22");
myList.Add("2 110 21");
myList.Add("2 100 18");
我需要根据第一个数字 (ID) 进行计数,并对这个 ID 的结果值求和,即 ID = 1 -> 120+130=150 和 12+22=34 等等...我必须返回一个具有这些值的数组。
我知道我可以获取这些单独的值,将它们添加到一个数组中,然后用它们之间的空白空间将其拆分,例如:
string[] arr2 = arr[i].Split(' ');
并循环通过它们来计算每个值的总和,但是......有没有一种简单的方法可以直接使用 Lists 或 Linq Lambda 表达式来完成它?
您可以像这样在 LINQ 中执行此操作:
var result = myList.Select(x => x.Split(' ').Select(int.Parse))
.GroupBy(x => x.First())
.Select(x => x.Select(y => y.Skip(1).ToArray())
.Aggregate(new [] {0,0}, (y,z) => new int[] {y[0] + z[0], y[1] + z[1]}));
首先将字符串拆分并转换为int,然后将它们按ID分组,然后将ID丢弃,最后将它们相加。
但是我强烈建议不要在LINQ中做,因为这个表达式不好理解。 如果你用循环的经典方式来做,第一眼就很清楚发生了什么。 但是把这个包含循环的代码放到一个单独的方法中,因为这样它不会分散你的注意力,你仍然只调用一个单行,就像在 LINQ 解决方案中一样。
直接做,没有LINQ,也许:
var d = new Dictionary<string, (int A, int B)>();
foreach(var s in myList){
var bits = s.Split();
if(!d.ContainsKey(bits[0]))
d[bits[0]] = (int.Parse(bits[1]), int.Parse(bits[2]));
else {
(int A, int B) x = d[bits[0]];
d[bits[0]] = (x.A + int.Parse(bits[1]), x.B + int.Parse(bits[2]));
}
}
使用 LINQ 解析 int,并切换到使用 TryGetValue,会整理一下:
var d = new Dictionary<int, (int A, int B)>();
foreach(var s in myList){
var bits = s.Split().Select(int.Parse).ToArray();
if(d.TryGetValue(bits[0], out (int A, int B) x))
d[bits[0]] = ((x.A + bits[1], x.B + bits[2]));
else
d[bits[0]] = (bits[1], bits[2]);
}
引入本地 function 以安全地获取字典中的现有数字或 (0,0) 对也可能会减少一点:
var d = new Dictionary<int, (int A, int B)>();
(int A, int B) safeGet(int i) => d.ContainsKey(i) ? d[i]: (0,0);
foreach(var s in myList){
var bits = s.Split().Select(int.Parse).ToArray();
var nums = safeGet(bits[0]);
d[bits[0]] = (bits[1] + nums.A, bits[2] + nums.B);
}
它是否比 linq 版本更具可读性? 嗯......取决于你对 Linq 和元组的经验,我想......
我知道这个问题已经有很多答案了,但我还没有看到一个专注于可读性的答案。
如果将代码拆分为解析阶段和计算阶段,我们可以在不牺牲可读性或可维护性的情况下使用 LINQ,因为每个阶段只做一件事:
List<string> myList = new List<string>();
myList.Add("1 120 12");
myList.Add("1 130 22");
myList.Add("2 110 21");
myList.Add("2 100 18");
var parsed = (from item in myList
let split = item.Split(' ')
select new
{
ID = int.Parse(split[0]),
Foo = int.Parse(split[1]),
Bar = int.Parse(split[2])
});
var summed = (from item in parsed
group item by item.ID into groupedByID
select new
{
ID = groupedByID.Key,
SumOfFoo = groupedByID.Sum(g => g.Foo),
SumOfBar = groupedByID.Sum(g => g.Bar)
}).ToList();
foreach (var s in summed)
{
Console.WriteLine($"ID: {s.ID}, SumOfFoo: {s.SumOfFoo}, SumOfBar: {s.SumOfBar}");
}
如果您愿意,但我认为使用通常的值进行编辑和优化会容易得多。 我发现在 LINQ 内部使用这种逻辑不会长时间保持这种状态。 通常,我们需要添加更多的值、更多的解析等。使它不太适合日常使用。
var query = myList.Select(a => a.Split(' ').Select(int.Parse).ToArray())
.GroupBy(
index => index[0],
amount => new
{
First = amount[1],
Second = amount[2]
},
(index, amount) => new
{
Index = index,
SumFirst = amount.Sum(a => a.First),
SumSecond = amount.Sum(a => a.Second)
}
);
有没有一种简单的方法可以直接使用 Lists 或 Linq Lambda 表达式?
也许,这样做是否明智? 可能不是。 您的代码将难以理解,无法进行单元测试,代码可能无法重用,小改动也很困难。
但是,让我们首先以 LINQ 声明的形式回答您的问题:
const char separatorChar = ' ';
IEnumerable<string> inputText = ...
var result = inputtext.Split(separatorChar)
.Select(text => Int32.Parse(text))
.Select(numbers => new
{
Id = numbers.First()
Sum = numbers.Skip(1).Sum(),
});
不可重用、难以单元测试、难以更改、效率不高,您需要更多的 arguments 吗?
最好有一个程序将一个输入字符串转换为正确的 object,其中包含您的输入字符串真正代表的内容。
唉,您没有告诉我们每个输入字符串是否包含三个 integer 数字,其中一些可能包含无效文本,而有些可能包含多于或少于三个 integer 数字。
你忘了告诉使用你的输入字符串代表什么。 所以我就编一个标识符:
class ProductSize
{
public int ProductId {get; set;} // The first number in the string
public int Width {get; set;} // The 2nd number
public int Height {get; set;} // The 3rd number
}
您需要一个 static 过程并输入一个字符串,以及 output 一个 ProductSize:
public static ProductSize FromText(string productSizeText)
{
// Todo: check input
const char separatorChar = ' ';
var splitNumbers = productSizeText.Split(separatorChar)
.Select(splitText => Int32.Parse(splitText))
.ToList();
return new ProductSize
{
ProductId = splitNumbers[0],
Width = splitNumbers[1],
Height = splitNumbers[2],
};
}
我需要根据第一个数字 (ID) 进行计数,并将此 ID 的结果值相加
创建方法 ParseProductSize 后,这很容易:
IEnumerable<string> textProductSizes = ...
var result = textProductSizes.Select(text => ProductSize.FromText(text))
.Select(productSize => new
{
Id = productSize.Id,
Sum = productSize.Width + productSize.Height,
});
如果您不总是有三个数字,那么您将没有宽度和高度,而是一个属性:
IEnumerable<int> Numbers {get; set;} // TODO: invent proper name
在 ParseProductSize 中:
var splitText = productSizeText.Split(separatorChar);
return new ProductSize
{
ProductId = Int32.Parse(splitText[0]),
Numbers = splitText.Skip(1)
.Select(text => Int32.Parse(text));
我故意将其保留为 IEnumerable,因此如果您不使用所有数字,您将不会无缘无故地解析数字。
LINQ:
var result = textProductSizes.Select(text => ProductSize.FromText(text))
.Select(productSize => new
{
Id = productSize.Id,
Sum = productSize.Numbers.Sum(),
});
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.