【问题标题】:List<T>.Any(); How to get index of matched item?列表<T>.Any();如何获取匹配项的索引?
【发布时间】:2013-08-23 07:57:49
【问题描述】:

m我将 Listview 项与通用列表项与 List.Any 方法进行比较,如下所示:

foreach (ListViewItem itemRow in lstviewAddsheets.Items)
 {
     if (InvalidSheets.Any(x => x != null && x.FilePath == itemRow.Tag.ToString()))
          {
           //Math found
          }
 }

请告诉我,如何获取与 itemRow.Tag.ToString() 匹配的 InvalidSheets 列表索引。

【问题讨论】:

    标签: c# list generics any


    【解决方案1】:

    由于关于使用List.FindIndex()而不是Linq查找索引的速度似乎存在一些争议,因此我编写了一个测试程序。

    这假设您只关心查找列表中第一个匹配项的索引。它不处理多个匹配项。

    另请注意,此测试是最坏的情况,因为匹配项位于列表的最后。

    我的 x86 版本构建结果(在 Windows 8 x64,四核处理器上运行):

    Calling Via FindIndex() 100 times took 00:00:00.9326057
    Calling Via Linq 100 times took 00:00:04.0014677
    Calling Via FindIndex() 100 times took 00:00:00.8994282
    Calling Via Linq 100 times took 00:00:03.9179414
    Calling Via FindIndex() 100 times took 00:00:00.8971618
    Calling Via Linq 100 times took 00:00:03.9134804
    Calling Via FindIndex() 100 times took 00:00:00.8963758
    

    表明List.FindIndex() 比使用 Linq 快大约四倍。

    这是测试代码:

    using System;
    using System.Collections.Generic;
    using System.Diagnostics;
    using System.Linq;
    
    namespace Demo
    {
        class Test
        {
            public string FilePath;
        }
    
        class Program
        {
            private void run()
            {
                int count = 1000000;
    
                List<Test> list = new List<Test>(count);
    
                for (int i = 0; i < count; ++i)
                    list.Add(new Test{ FilePath = i.ToString()});
    
                string target = (count-1).ToString();
    
                for (int trial = 0; trial < 4; ++trial)
                {
                    Action viaFindIndex =
                    (
                        () =>
                        {
                            int index = list.FindIndex(x => (x != null) && (x.FilePath == target));
                        }
                    );
    
                    Action viaLinq =
                    (
                        () =>
                        {
                            int index = list.Select((x, i) => new { Item = x, Index = i })
                            .First(x => (x != null) && (x.Item.FilePath == target))
                            .Index;
                        }
                    );
    
                    viaFindIndex.TimeThis("Via FindIndex()", 100);
                    viaLinq.TimeThis("Via Linq", 100);
                }
            }
    
            private static void Main()
            {
                new Program().run();
            }
        }
    
        static class DemoUtil
        {
            public static void TimeThis(this Action action, string title, int count = 1)
            {
                var sw = Stopwatch.StartNew();
    
                for (int i = 0; i < count; ++i)
                    action();
    
                Console.WriteLine("Calling {0} {1} times took {2}", title, count, sw.Elapsed);
            }
        }
    }
    

    因此,鉴于List.FindIndex() 比使用 Linq 更快且更易于阅读,我认为没有理由使用 Linq 来解决这个特定问题。

    int index = list.FindIndex(x => (x != null) && (x.FilePath == target));
    

    int index = list.Select((x, i) => new { Item = x, Index = i })
                .First(x => (x != null) && (x.Item.FilePath == target))
                .Index;
    

    第一个版本在 IMO 的所有方面都获胜。

    【讨论】:

    • 我认为原因之一是匿名类型。在您的测试用例中,您需要创建 1000000-1 个实例,而 FindIndex 不需要它。但是,为测试 +1。
    • 是的,这似乎是一个可能的候选人。
    • list.FindIndex 工作完美、快速、简单和干净的代码谢谢。
    【解决方案2】:

    你可以这样做

     int index =   InvalidSheets.FindIndex(x => x != null && x.FilePath == itemRow.Tag.ToString());
    

    如果你想直接获取对象然后这样做

     var matchedObject = InvalidSheets.FirstOrDefault(x => x != null && x.FilePath == itemRow.Tag.ToString());
    

    【讨论】:

      【解决方案3】:

      获取索引的方法如下:

      var index = InvalidSheets.Select((x, i) => new {Item = x, Index = i})
                               .First(x => x.Item != null && x.Item.FilePath == itemRow.Tag.ToString())
                               .Index;
      

      但是,您可能希望像这样使用 FirstOrDefault 重构它:

      foreach (ListViewItem itemRow in lstviewAddsheets.Items)
      {
          var sheet = InvalidSheets.Select((x, i) => new {Item = x, Index = i})
                                   .FirstOrDefault(x => x.Item != null && x.Item.FilePath == itemRow.Tag.ToString());
          if (sheet != null)
          {
             var index = sheet.Index;
          }
      }
      

      【讨论】:

      • 非常好的和非常快的代码。感谢您带我走上正确的道路。
      【解决方案4】:

      试试这个:

      InvalidSheets.IndexOf(InvalidSheets.First(x => x != null && x.FilePath == itemRow.Tag.ToString()))
      

      它将获得与谓词匹配的第一个无效工作表的索引

      【讨论】:

      • +1 是的。如果你有List&lt;&gt;,那么List.IndexOf() 比使用 Linq 更有效。
      • @MatthewWatson: IndexOf 采用 T 而不是 lambda。 T 还必须覆盖 Equals。在大多数情况下,它也不是“更有效”,但接近于微优化。
      • @TimSchmelter 我的错误:我的意思是评论 FindIndex() 方法。
      • @MatthewWatson:如果两者都使用相同的集合,我无法相信会有这样的差异。 List.FindIndexfor (int i = 0; i &lt; this.Count; i++) if (match(this._items[i])) return i; return -1;Enumerable.FirstOrDefaultforeach (TSource current in source) if (predicate(current)) return current; return default(TSource); 的(简化)源。可能您的测量结果并不理想。
      • @TimSchmelter 我写了一个时序测试,结果表明(假设你只需要找到一个索引)使用 Linq 比使用 List.FindIndex() 慢 8 倍以上。我意识到这对小列表没有什么影响,但这几乎不是“微优化”。此外,使用List.FindIndex() 比使用 Linq 更短且更易读。因此,在您只想获取索引的情况下,我认为没有理由使用 Linq。
      【解决方案5】:

      你可以用重载来投影索引,因此你需要选择一个匿名类型:

      var invalids = InvalidSheets.Select((s, i) => { Sheet=s, Index=i })
          .Where(x => x.Sheet != null && x.Sheet.FilePath == itemRow.Tag.ToString()));
      bool anyInvalid = invalids.Any(); // is any invalid
      IEnumerable<int> indices = invalids.Select(x => x.Index);// if you need all indices
      

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 2021-06-27
        • 2018-10-26
        • 1970-01-01
        • 2015-11-10
        • 1970-01-01
        • 2021-01-07
        • 1970-01-01
        相关资源
        最近更新 更多