【问题标题】:C# Splitting a List<string> ValueC# 拆分列表<string> 值
【发布时间】:2021-07-26 10:58:09
【问题描述】:

我有一个包含值 {"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?我认为你的情况在 Linq 中会更难阅读。我认为应该可以,但是您仍然需要一个临时变量来存储结果值。但是,它会更难阅读并且可能会更慢。不过需要更多测试。
  • 你应该明确字符串的格式是否一致。如果是,您可以编写一个遍历列表值的算法。我认为使用 linq Lamda 没有更经济的方法来做到这一点

标签: c# arrays list linq


【解决方案1】:

你可以在 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 解决方案中一样。

【讨论】:

  • 我认为 Where 应该是 GroupBy 所以它不仅仅是 ID 1
  • @CaiusJard 再次阅读问题后,我认为您是对的。我编辑了问题。
  • 大声笑。我喜欢你写的“我编辑了问题”的那段话——我认为你的意思是“回答”,但把它当作问题真的让我笑了
  • 没错!当然我编辑了答案:-)
【解决方案2】:

直接做,不用 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]);
 
}

引入一个局部函数来安全地获取字典中的现有数字或 (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 和元组的经验,我想...

【讨论】:

    【解决方案3】:

    我知道这个问题已经有很多答案了,但我还没有看到一个专注于可读性的答案。

    如果您将代码分成解析阶段计算阶段,我们可以在不牺牲可读性或可维护性的情况下使用 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}");
    }
    

    fiddle

    【讨论】:

      【解决方案4】:

      如果您愿意,但我认为使用通常的值进行编辑和优化会容易得多。我发现在 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) 
                      }
                      );
      

      fiddle

      【讨论】:

        【解决方案5】:

        有没有一种简单的方法可以直接使用 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(),
             }); 
        

        不可重用、难以单元测试、难以更改、效率不高,是否需要更多参数?

        最好有一个过程,将一个输入字符串转换为包含输入字符串真正表示的内容的适当对象。

        唉,你没有告诉我们每个输入字符串是否包含三个整数,其中一些可能包含无效文本,而一些可能包含多于或少于三个整数。

        你忘了告诉使用你的输入字符串代表什么。 所以我就编一个标识符:

        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
        }
        

        你需要一个静态过程,输入一个字符串,输出一个 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(),
             });
        

        【讨论】:

          猜你喜欢
          • 2017-08-22
          • 2019-01-14
          • 2016-01-17
          • 1970-01-01
          • 2017-11-15
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 2019-05-10
          相关资源
          最近更新 更多