簡體   English   中英

C# 拆分列表<string>價值</string>

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

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM