【问题标题】:Linq: Sum() non-integer valuesLinq:Sum() 非整数值
【发布时间】:2014-02-17 12:47:01
【问题描述】:

这是我之前的问题的延续: Linq (GroupBy and Sum) over List<List<string>>

我有一个这样的查询:

var content = new List<List<string>>
        {
            new List<string>{ "book", "code", "columnToSum" },
            new List<string>{ "abc", "1", "10" },
            new List<string>{ "abc", "1", "5" },
            new List<string>{ "cde", "1", "6" },
        };

var headers = content.First();
var result = content.Skip(1)
    .GroupBy(s => new { Code = s[headers.IndexOf("code")], Book = s[headers.IndexOf("book")]})
    .Select(g => new
             {
                 Book = g.Key.Book,
                 Code = g.Key.Code,
                 Total = g.Select(s => int.Parse(s[headers.IndexOf("columnToSum")])).Sum()
             });

这很好,但我只是想知道如何处理 columnToSum 为空的情况?因此,例如,这给了我错误“输入字符串的格式不正确”,因为 int.Parse 失败

var content = new List<List<string>>
        {
            new List<string>{ "book", "code", "columnToSum" },
            new List<string>{ "abc", "1", "10" },
            new List<string>{ "abc", "1", "" },
            new List<string>{ "cde", "1", "6" },
        };

如何优雅地处理这种情况?

【问题讨论】:

  • 如果可能的话,您应该将您的List&lt;string&gt;s 转换为适当的数据结构,例如具有 string Nameint Codeint? ColumnToSum 属性的 Book 类。

标签: c# .net linq


【解决方案1】:

为什么不在字符串前面加一个零呢?

s => int.Parse("0" + s[headers.IndexOf("columnToSum")])

当然,这是一个大技巧。但是,如果您真正担心的唯一例外情况是空字符串,它将快速且(相当)易读地解决您的问题。

我想知道你从哪里得到这些空字符串。如果它是你可以控制的东西,比如 SQL 查询,为什么不改变你的查询以给出“0”而没有价值呢? (只要在代码中的其他地方没有以不同的方式使用空列。)

【讨论】:

  • "你为什么不在字符串前面加一个零" 因为这并不能真正回答问题 - "Sum() 非-整数值”"" 可能只是为了问题的目的,即使不是,"" 所代表的任何东西都很有可能在未来发生变化,例如"N/A"。不过,我同意您问题的第二部分,真正的解决方法是确保您不会在看似数字的列中得到非数字值。跨度>
  • @James 它确实回答了问题如果您真正担心的唯一例外情况是空字符串。这也是问题中描述问题的方式,而不是标题。
  • 是的,如果这不是唯一的例外情况怎么办? OP 没有具体说明唯一的非数字值是您 假设 的空字符串,因为给出的示例就是这种情况。然而,据我们所知,这正是 - 一个例子。您的回答解决了问题,但不一定回答了问题。
【解决方案2】:

一个选项,使用string.All(Char.IsDigit)作为预检查:

Total = g.Select(s => !string.IsNullOrEmpty(s[headers.IndexOf("columnToSum")]) && 
                      s[headers.IndexOf("columnToSum")].All(Char.IsDigit) ?
                      int.Parse(s[headers.IndexOf("columnToSum")]) : 0).Sum())

另一个是使用int.TryParse:

int val = 0;
// ...
Total = g.Select(s => int.TryParse(s[headers.IndexOf("columnToSum")], out val) ?            
                      int.Parse(s[headers.IndexOf("columnToSum")]) : 0).Sum())

【讨论】:

    【解决方案3】:

    该代码假定空字符串为 0:

    Total = g.Where(s => !String.IsNullOrEmpty(s)).Select(s => int.Parse(s[headers.IndexOf("columnToSum")])).Sum()
    

    【讨论】:

      【解决方案4】:

      不幸的是,这看起来不太好......

      g.Select(s => !s[headers.IndexOf("columnToSum")].Any(Char.IsDigit) ? 
          0 : Int32.Parse(s[headers.IndexOf("columnToSum")])).Sum()
      

      但是,您可以将其封装在一个不错的扩展方法中

      public static class StrExt
      {
          public static int IntOrDefault(this string str, int defaultValue = 0)
          {
              return String.IsNullOrEmpty(str) || !str.Any(Char.IsDigit) ? defaultValue : Int32.Parse(str);
          }
      }
      ...
      g.Select(s => s[headers.IndexOf("columnToSum")].IntOrDefault()).Sum();
      

      如果str 不是数字,扩展方法可以让您灵活设置所需的任何默认值 - 如果省略参数,则默认为0

      【讨论】:

        【解决方案5】:

        在这里使用列表是有问题的,我会将其解析为适当的数据结构(如 Book 类),我认为这会稍微清理代码。如果您正在解析 CSV 文件,请查看 FileHelpers,它是处理这些类型任务的绝佳库,它可以为您解析成数据结构。

        话虽如此,如果您仍想继续使用此范例,我认为您可以通过创建两种自定义方法使代码相当干净:一种用于处理标头(我会使用的少数几个地方之一)动态类型来摆脱代码中丑陋的字符串)和一种用于解析整数。然后你会得到这样的东西:

        var headers = GetHeaders(content.First());
        var result = from entry in content.Skip(1)
                        group entry by new {Code = entry[headers.code], Book = entry[headers.book] } into grp
                        select new {
                            Book = grp.Key.Book,
                            Code = grp.Key.Code,
                            Total = grp.Sum(x => ParseInt(x[headers.columnToSum]))
                        };
        
        public dynamic GetHeaders(List<string> headersList){
            IDictionary<string, object> headers = new ExpandoObject();
            for (int i = 0; i < headersList.Count; i++)
                headers[headersList[i]] = i;        
        
            return headers;
        }
        
        public int ParseInt(string s){
            int i;
            if (int.TryParse(s, out i)) 
                return i; 
        
            return 0;   
        }
        

        【讨论】:

          【解决方案6】:

          您可以在 lambda 表达式中使用多行并在末尾返回一个值。 所以,而不是

          Total = g.Select(s => int.Parse(s[headers.IndexOf("columnToSum")])).Sum()
          

          我会写

          Total = g.Select(s => {
              int tempInt = 0;
              int.TryParse(s[headers.IndexOf("columnToSum")], out tempInt);
              return tempInt;                        
          }).Sum()
          

          【讨论】:

            【解决方案7】:
            t = new List<List<string>>
                    {
                        new List<string>{ "book", "code", "columnToSum" },
                        new List<string>{ "abc", "1", "10" },
                        new List<string>{ "abc", "1", "5" },
                        new List<string>{ "cde", "1", "6" },
                    };
            
            var headers = content.First();
            var result = content.Skip(1)
                .GroupBy(s => new { Code = s[headers.IndexOf("code")], Book = s[headers.IndexOf("book")]})
                .Select(g => new
                         {
                             Book = g.Key.Book,
                             Code = g.Key.Code,
                             Total = g.Select(s => int.Parse(s[headers.IndexOf("columnToSum")]!=""?s[headers.IndexOf("columnToSum")]:0)).Sum()
                         });
            

            【讨论】:

            • 如果columnToSumABC 呢?这必须适用于 非整数值 而不仅仅是空字符串。
            猜你喜欢
            • 2017-12-07
            • 1970-01-01
            • 2010-11-16
            • 2013-12-28
            • 1970-01-01
            • 1970-01-01
            • 2018-10-15
            • 2015-10-08
            • 2021-10-29
            相关资源
            最近更新 更多