繁体   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