[英]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.