【问题标题】:How to convert a list of strings to ints ignoring non integers如何将字符串列表转换为忽略非整数的整数
【发布时间】:2012-05-28 14:00:02
【问题描述】:

我习惯于在不使用 Linq 或 Lambda 语句的情况下编写 C#,但我想提高我的理解。我的代码看起来像这样用 C# 2.0 编写的,带有 foreach 循环:

        List<string> strings = new List<string>();
        strings.Add("1");
        strings.Add("blah");

        List<int> ints1 = new List<int>();
        foreach (string x in strings)
        {
            int r;
            if (int.TryParse(x, out r))
                ints1.Add(r);
        }

它正在执行一项简单的任务 - 从字符串列表中填充整数列表,忽略任何实际上不是整数的内容。 在我有限的经验中,Linq 和 Lambda 语句似乎能够将 foreach 语句缩减为非常简洁易读的方式来做同样的事情。所以我想我会用 C# 3.0 试试这个小家伙。但我能想到的最好的是:

       IEnumerable<int> ints2 = strings.Select(x =>
                                     {
                                         int r;
                                         if (int.TryParse(x, out r))
                                             return r as int?;
                                         return null;
                                     })
                                     .Where<int?>(x => x != null)
                                     .Select(x => x.Value);

呸!我找不到将转换和过滤结合在一个呼叫中的方法,最终得到的结果似乎比原来的要糟糕得多。有没有办法做到这一点,还是我应该坚持我的 foreach?

编辑:总结一下发现,如果您使用的是 .NET 4.0,这里有 2 种使用 LINQ 的方法:

IEnumerable<int> ints1 = strings
.Where(x => new Int32Converter().IsValid(x))
.Select(x => int.Parse(x));//Broken in .NET 3.5 due to bug in Int32Converter


IEnumerable<int> int2 = strings
.Where(x => { int r; return int.TryParse(x, out r); })
.Select(x => int.Parse(x));

第二种方法更快,并且在 .NET 3.5 中没有被破坏。根据接受的答案,可能没有理由不将其放入扩展方法中,而不是 foreach。 有关Int32Converter 错误,请参阅connect issue

【问题讨论】:

    标签: linq lambda c#-3.0


    【解决方案1】:

    你可以用一个简单的 linq 做到这一点

    int num = 0;
    var ints = (from str in strings
                where int.TryParse(str,out num)
                select num).ToList();
    

    【讨论】:

    • 这太棒了,有点可怕。
    • 并行使用时会产生麻烦。我不会这样做。
    【解决方案2】:

    使用 LINQ 仅获取 int 的另一种方法是:

    var integerList =
        strings
            .Where(x => new Int32Converter().IsValid(x))
            .Select(x => int.Parse(x));
    

    [更新]

    我使用 LINQPad 做了一个performance test

    这里我报告代码和结果:

    void Main()
    {
        List<string> strings = new List<string>() { "1", "a", "3", "b" };
    
        Func<IEnumerable<string>, IEnumerable<int>> func1 =
            list => list.Where(x => new Int32Converter().IsValid(x)).Select(x => int.Parse(x));
    
        Func<IEnumerable<string>, IEnumerable<int>> func2 =
            list => { var ret = 0; return list.Where(x => int.TryParse(x, out ret)).Select(x => ret); };
    
        Benchmark
            .For(1000)
                .Execute("Int32Converter", () => func1(strings).Iterate())
                .Execute("int.TryParse", () => func2(strings).Iterate())
            .Report(4).Dump();
    }
    

    int.TryParseInt32Converter 快大约 165 倍。

    [更新 2]

    Int32Converter 类继承的 TypeConverter.IsValid 包含一个在 .NET 4.0 中修复的 bug

    【讨论】:

    • 快多少? foreach 如何比较?并且是否避免了并行使用的麻烦?
    • 快了 165 倍。我没有尝试,但是你可以在CodePlex找到我的sn-p并修改它。
    • 我拿到了 LINQPad 并尝试了 sn-p。但我得到“输入字符串的格式不正确。”来自 int.Parse。如果我将所有字符串都更改为整数,那么速度会提高 166%....
    • 希望你不要介意。我添加了 foreach 和结果。但是为了得到结果,我必须将输入更改为整数,因为如果传入字符串,Int32Converter 会失败。
    • @Colin 我又试了一次,方法 Int32Converter.IsValid 适用于数字,但也适用于通用字符串。
    【解决方案3】:

    我会定义我自己的扩展方法如下:

    public static IEnumerable<int> AsIntegers (this IEnumerable<string> strings) {
       foreach (var s in strings) {
          int r; if (int.TryParse (s, r)) yield return r;
       }
    }
    
    ...
    
    List<int> intList = new List (stringList.AsIntegers ());
    

    var intList = stringList.AsIntegers ().ToList ();
    

    【讨论】:

    • 这是可读且简洁的。但是如果性能可能是一个问题,那么请查看@Matteo 运行的测试
    【解决方案4】:

    就这样:

    from s in strings
    let parsed = s.TryParseInt32()
    where parsed != null
    select parsed.Value;
    
    public static int? TryParseInt32(this string str)
    {
        int i;
        if (!int.TryParse(str, NumberStyles.Integer, CultureInfo.InvariantCulture, out i))
            return null;
        return i;
    }
    

    TryParseInt32 扩展是可重用的。

    这里有一个替代方案:

    from s in strings
    select s.TryParseInt32() into parsed
    where parsed != null
    select parsed.Value;
    

    【讨论】:

      【解决方案5】:

      我有一些这样的辅助方法

      public static class CollectionUtilities {
          public static IEnumerable<int> ParseInt32(this IEnumerable<string> source) {
              return ParseInt32(source, NumberStyles.Integer, null);
          }
      
          public static IEnumerable<int?> TryParseInt32(this IEnumerable<string> source) {
              return TryParseInt32(source, NumberStyles.Integer, null);
          }
      
          public static IEnumerable<int> ParseInt32(this IEnumerable<string> source, IFormatProvider provider) {
              return ParseInt32(source, NumberStyles.Integer, provider);
          }
      
          public static IEnumerable<int?> TryParseInt32(this IEnumerable<string> source, IFormatProvider provider) {
              return TryParseInt32(source, NumberStyles.Integer, provider);
          }
      
          public static IEnumerable<int> ParseInt32(this IEnumerable<string> source, NumberStyles style) {
              return ParseInt32(source, style, null);
          }
      
          public static IEnumerable<int?> TryParseInt32(this IEnumerable<string> source, NumberStyles style) {
              return TryParseInt32(source, style, null);
          }
      
          public static IEnumerable<int> ParseInt32(this IEnumerable<string> source, NumberStyles style, IFormatProvider provider) {
              if (source == null)
                  throw new ArgumentNullException("source");
      
              return ParseInt32Iterator(source, style, provider);
          }
      
          public static IEnumerable<int?> TryParseInt32(this IEnumerable<string> source, NumberStyles style, IFormatProvider provider) {
              if (source == null)
                  throw new ArgumentNullException("source");
      
              return TryParseInt32Iterator(source, style, provider);
          }
      
          private static IEnumerable<int> ParseInt32Iterator(this IEnumerable<string> source, NumberStyles style, IFormatProvider provider) {
              foreach (string element in source) {
                  yield return int.Parse(element, style, provider);
              }
          }
      
          private static IEnumerable<int?> TryParseInt32Iterator(this IEnumerable<string> source, NumberStyles style, IFormatProvider provider) {
              foreach (string element in source) {
                  int value;
                  bool success = int.TryParse(element, style, provider, out value);
                  yield return success ? (int?)value : null;
              }
          }
      }
      

      使用这些帮助程序,您的代码将如下所示:

      var ints = strings.TryParseInt32().Where(x => x != null).Select(x => x.Value).ToList();
      

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 2013-11-12
        • 1970-01-01
        • 2015-01-14
        • 2021-06-18
        • 2022-12-18
        • 1970-01-01
        • 2021-09-10
        • 2010-10-23
        相关资源
        最近更新 更多