• LINQ基础(二)


      本文主要介绍LINQ查询操作符
      LINQ查询为最常用的操作符定义了一个声明语法。还有许多查询操作符可用于Enumerable类。

      下面的例子需要用到LINQ基础(一)(http://www.cnblogs.com/afei-24/p/6841361.html)的一些代码

    1.筛选
      LINQ查询使用where子句添加条件表达式来筛选,where子句可以合并多个表达式。

    var racers = from r in Formula1.GetChampions() 
                        where r.Wins>15 && 
                            (r.Country == "Brazil" || r.Country =="Austria")
                        select r;
                        
            foreach(var r in racers)
            {
                Console.WriteLine("{0:A}", r);
            }

      上述LINQ表达式映射为C# LINQ查询的扩展方法:
      var racers = Formula1.GetChampions().Where(r =>r.Wins>15 &&
        (r.Country == "Brazil" || r.Country =="Austria")).Select(r => r);
      注意,并不是所以查询都可以使用LINQ查询语法,也不是所有的扩展方法都映射到LINQ查询。高级查询需要使用扩展方法。

    2.用索引筛选
      不能使用LINQ查询的一个例子是Where()方法的重载。在WHere()方法的重载中,可以传递第二个参数————索引。索引是筛选器返回的每个结果的计数器。可以在表达式中使用这个索引,执行基于索引的计算:

    var racers = Formula1.GetChampions().
                Where((r, index) => r.LastName.StartsWith("A") && index % 2 != 0);
            foreach (var r in racers)
            {
                Console.WriteLine("{0:A}", r);
            }

    3.类型筛选
      为了进行基于类型的筛选,可以使用OfType()扩展方法。

    object[] data = { "one", 2, 3, "four", "five", 6 };
              var query = data.OfType<string>();
              foreach (var s in query)
              {
                Console.WriteLine(s);
              }

      输出:
        one
        four
        five
      从集合仅返回字符串。

    4.复合的from子句
      如果需要根据对象的成员进行筛选,而该成员本身是一个系列,就可以使用复合from子句。例如,LINQ基础(一)(http://www.cnblogs.com/afei-24/p/6841361.html)中的Racer类定义了一个属性Cars,Cars是一个字符串数组。

      筛选驾驶Ferrari的所以冠军:  

      var ferrariDrivers = from r in Formula1.GetChampions()
                               from c in r.Cars
                               where c == "Ferrari"
                               orderby r.LastName
                               select r.FirstName + " " + r.LastName;
    
          foreach (var racer in ferrariDrivers)
          {
            Console.WriteLine(racer);
          }

      第一个from子句访问Formula1.GetChampions()方法返回的Racer对象,第二个from子句访问Racer类的Cars属性,以返回所以sting类型的赛车。

      C#编译器把复合的from子句和LINQ查询转换为SelectMany()扩展方法。SelectMany()扩展方法可以迭代序列中的序列。
      SelectMany()的重载版本:
      public static IEnumerable<TResult> SelectMany<TSource, TCollection, TResult>(this IEnumerable<TSource> source,
        Func<TSource, IEnumerable<TCollection>> collectionSelector,
          Func<TSource, TCollection, TResult> resultSelector);
      第一个参数是隐式参数,它从Formula1.GetChampions()方法接受Racer对象序列。第二个参数是collectionSelector委托,其中定义了内部序列,是序列的序列,本例子为Cars。第三个参数也是一个委托,为每个Racer对象的Cars属性的每个元素调用这个委托。
      这里Cars是一个字符串数组,会将每个Racer和每个字符串作为参数,调用这个委托。  

    var ferrariDrivers = Formula1.GetChampions().SelectMany(
                c => c.Cars, (r, s) => new { Racer=r,Car =s}).Where(
                s =>s.Car == "Ferrari").OrderBy(
                r => r.Racer.LastName).Select(r => r.Racer.FirstName + " " + r.Racer.LastName);
    
            foreach (var racer in ferrariDrivers)
            {
                Console.WriteLine(racer);
            }

    5.排序
      要对序列排序,可以使用前面使用过的orderby.也可以使用orderrby descending子句(降序)。 

    var racers = (from r in Formula1.GetChampions()
                          orderby r.Country  descending
                          select r);
    
            foreach (var racer in racers)
            {
                Console.WriteLine("{0}: {1}, {2}", racer.Country, racer.LastName, racer.FirstName);
            }

      orderby子句解析为OrderBy()方法,orderby r.Country descending解析为OrderByDescending()方法:
      var racers = Formula1.GetChampions().OrderByDescending(r => r.Country).Select(r=>r);
      OrderBy()和OrderByDescending()方法返回IOrderEnumerable<TSource>。这个接口派生自IEnumerable<TSource>接口,但包含一个额外的方法CreateOrderEnumerable<TSource>()方法。这个方法用于进一步给序列排序,可以在最后一个参数指定升序还是降序:
      

    // 摘要:
            //     根据某个键对 System.Linq.IOrderedEnumerable<TElement> 的元素执行后续排序。
            //
            // 参数:
            //   keySelector:
            //     用于提取每个元素的键的 System.Func<T,TResult>。
            //
            //   comparer:
            //     用于比较键在返回序列中的位置的 System.Collections.Generic.IComparer<T>。
            //
            //   descending:
            //     如果为 true,则对元素进行降序排序;如果为 false,则对元素进行升序排序。
            //
            // 类型参数:
            //   TKey:
            //     keySelector 生成的键的类型。
            //
            // 返回结果:
            //     一个 System.Linq.IOrderedEnumerable<TElement>,其元素按键排序。
            IOrderedEnumerable<TElement> CreateOrderedEnumerable<TKey>(Func<TElement, TKey> keySelector, IComparer<TKey> comparer, bool descending);


      例子:

    // Create an array of strings to sort.
                  string[] fruits = { "apricot", "orange", "banana", "mango", "apple", "grape", "strawberry" };
                  // First sort the strings by their length.
                  IOrderedEnumerable<string> sortedFruits2 =
                      fruits.OrderBy(fruit => fruit.Length);
                  // Secondarily sort the strings alphabetically, using the default comparer.
                  IOrderedEnumerable<string> sortedFruits3 =
                      sortedFruits2.CreateOrderedEnumerable<string>(
                          fruit => fruit,
                          Comparer<string>.Default, false);

      使用ThenBy和ThenByDescending()方法进行进一步排序,可以添加任意多个:
      var racers = Formula1.GetChampions().OrderByDescending(r => r.Country).ThenByDescending(
        r => r.LastName).ThenByDescending(r => r.FirstName).Select(r => r);

    6.分组
      要根据一个关键字值对查询结果分组,可以使用group子句。

    // group r by r.Country into g 根据Country属性组合所有的赛车手,并定义为一个新的集合g,用于访问分组的结果信息。
            //select子句创建一个带Country和Count属性的匿名类型。Country = g.Key Key是r.Country
            var countries = from r in Formula1.GetChampions()
                          group r by r.Country into g
                          orderby g.Count() descending, g.Key
                          where g.Count() >= 2
                          select new
                          {
                            Country = g.Key,
                            Count = g.Count()
                          };
            foreach (var item in countries)
              {
                Console.WriteLine("{0, -10} {1}", item.Country, item.Count);
              }

      输出:
      

      使用扩展方法执行相同的操作,把group r by r.Country 子句解析为GroupBy()方法。在GroupBy()方法的声明中,它返回实现了IGrouping<TKey, TSource>接口的枚举对象。IGrouping<TKey, TSource>接口定义了Key属性,所以在调用了这个方法后,可以访问分组的关键字:
      public static IEnumerable<IGrouping<TKey, TSource>> GroupBy<TSource, TKey>(this IEnumerable<TSource> source,
        Func<TSource, TKey> keySelector);
      使用GroupBy方法:

     var countries = Formula1.GetChampions().GroupBy(r => r.Country).OrderByDescending(
                g => g.Count()).ThenBy(g => g.Key).Where(g => g.Count() >= 2).Select(
                g=>new
                          {
                              Country = g.Key,
                              Count = g.Count()
                          });


    7.对嵌套的对象分组
      如果得到的分组的对象需要包含嵌套的序列,就可以改变select子句创建匿名类型。
      

    //返回的对象不仅需要包含国家名和赛车手这两个属性,还应包含赛车手集合。
            //使用from r1 in g orderby r1.LastName select r1.FirstName + " " + r1.LastName 内部子句
            var countries = from r in Formula1.GetChampions()
                          group r by r.Country into g
                          orderby g.Count() descending, g.Key
                          where g.Count() >= 2
                          select new
                          {
                            Country = g.Key,
                            Count = g.Count(),
                            Racers = from r1 in g
                                     orderby r1.LastName
                                     select r1.FirstName + " " + r1.LastName
                          };
          foreach (var item in countries)
          {
            Console.WriteLine("{0, -10} {1}", item.Country, item.Count);
            foreach (var name in item.Racers)
            {
              Console.Write("{0}; ", name);
            }
            Console.WriteLine();
          }


    8.内连接
      使用join子句可以根据特定的条件合并两个数据源,但之前要获得两个连接的列表。
      使用了LINQ基础(一)(http://www.cnblogs.com/afei-24/p/6841361.html)的代码  

    //GetChampions获得冠军赛车手
            var racers = from r in Formula1.GetChampions()
                       from y in r.Years
                       select new
                       {
                         Year = y,
                         Name = r.FirstName + " " + r.LastName
                       };
            //GetContructorChampions获取冠军车队
              var teams = from t in Formula1.GetContructorChampions()
                          from y in t.Years
                          select new
                          {
                            Year = y,
                            Name = t.Name
                          };
            //得到每一年获得冠军的赛车手和车队
            //通过join t in teams on r.Year equals t.Year into rt 子句连接两个数据源
              var racersAndTeams =
                (from r in racers
                 join t in teams on r.Year equals t.Year into rt
                 from t in rt.DefaultIfEmpty()
                 orderby r.Year
                 select new
                 {
                   Year = r.Year,
                   Champion = r.Name,
                   Constructor = t == null ? "no constructor championship" : t.Name
                 });
    
              Console.WriteLine("Year  Champion		   Constructor Title");
              foreach (var item in racersAndTeams)
              {
                Console.WriteLine("{0}: {1,-20} {2}",
                   item.Year, item.Champion, item.Constructor);
              }

    9.左连接
      使用内连接返回匹配r.Year equals t.Year的结果。左连接返回左边数据源的全部元素,即使在右边的数据源中没有匹配的元素。
      

    var racers = from r in Formula1.GetChampions()
                       from y in r.Years
                       select new
                       {
                         Year = y,
                         Name = r.FirstName + " " + r.LastName
                       };
    
          var teams = from t in Formula1.GetContructorChampions()
                      from y in t.Years
                      select new
                      {
                        Year = y,
                        Name = t.Name
                      };
            //左连接用join和DefaultIfEmpty方法定义。
            //如果查询到左侧数据源没有和右边数据源Year相同的结果,使用DefaultIfEmpty方法定义右侧的默认值(为空)
          var racersAndTeams =
            (from r in racers
             join t in teams on r.Year equals t.Year into rt
             from t in rt.DefaultIfEmpty()
             orderby r.Year
             select new
             {
               Year = r.Year,
               Champion = r.Name,
               Constructor = t == null ? "no constructor championship" : t.Name
             });
    
          Console.WriteLine("Year  Champion		   Constructor Title");
          foreach (var item in racersAndTeams)
          {
            Console.WriteLine("{0}: {1,-20} {2}",
               item.Year, item.Champion, item.Constructor);
          }


    10.组连接
      组连接类似内连接,内连接通过某一项连接两个数据源(如 r.Year equals t.Year),组连接使用一组项连接,例如下面的例子,
      通过 new
        {
          FirstName = r.FirstName,
          LastName = r.LastName
        }
        equals
        new
        {
          FirstName = r2.FirstName,
          LastName = r2.LastName
        }
      连接两个数据源
      

    var racers = Formula1.GetChampionships()
            .SelectMany(cs => new List<RacerInfo>()
            {
             new RacerInfo {
               Year = cs.Year,
               Position = 1,
               FirstName = cs.First.FirstName(),
               LastName = cs.First.LastName()        
             },
             new RacerInfo {
               Year = cs.Year,
               Position = 2,
               FirstName = cs.Second.FirstName(),
               LastName = cs.Second.LastName()        
             },
             new RacerInfo {
               Year = cs.Year,
               Position = 3,
               FirstName = cs.Third.FirstName(),
               LastName = cs.Third.LastName()        
             }
           });
    
          var q = (from r in Formula1.GetChampions()
                   join r2 in racers on
                   new
                   {
                     FirstName = r.FirstName,
                     LastName = r.LastName
                   }
                   equals
                   new
                   {
                     FirstName = r2.FirstName,
                     LastName = r2.LastName
                   }
                   into yearResults
                   select new
                   {
                     FirstName = r.FirstName,
                     LastName = r.LastName,
                     Wins = r.Wins,
                     Starts = r.Starts,
                     Results = yearResults
                   });
    
          foreach (var r in q)
          {
            Console.WriteLine("{0} {1}", r.FirstName, r.LastName);
            foreach (var results in r.Results)
            {
              Console.WriteLine("{0} {1}", results.Year, results.Position);
            }
          }

    11.集合操作
      扩展方法Distinct(),Union(),Intersect()(获取交集),Except()都是集合操作。  

    //获取同时驾驶Ferrari和驾驶McLaren获得过冠军的赛车手
            static void SetOperations()
            {
                //定义一个委托,用来查询驾驶Ferrari获得过冠军的赛车手和驾驶McLaren获得过冠军的赛车手
              Func<string, IEnumerable<Racer>> racersByCar =
                  car => from r in Formula1.GetChampions()
                         from c in r.Cars
                         where c == car
                         orderby r.LastName
                         select r;
    
              Console.WriteLine("World champion with Ferrari and McLaren");
              //使用Intersect方法获取两个数据源的交集
              foreach (var racer in racersByCar("Ferrari").Intersect(racersByCar("McLaren")))
              {
                Console.WriteLine(racer);
              }
            }

    12.合并
      Zip()方法是.NET 4.0新增的,允许用一个为此函数把两个相关的序列合并为一个。
      对于合并,第一个集合中的第一项与第二个集合的第一项合并,第一个集合中的第二项与第二个集合的第二项合并,以此类推。如果两个序列的项数不同,Zip()方法就会在达到较小集合的末尾时停止。

      第一个集合中的元素有一个Name属性,第二个集合中的元素有LastName和Starts属性。
      在racerNames集合上使用Zip()方法,需要把第二个集合racerNamesAndStarts作为第一个参数。第二个参数是一个委托,它通过参数first接受第一个集合的元素,通过参数second接受第二个集合的元素。其实现代码返回一个字符串:  

    var racerNames = from r in Formula1.GetChampions()
                           where r.Country == "Italy"
                           orderby r.Wins descending
                           select new
                           {
                             Name = r.FirstName + " " + r.LastName
                           };
    
              var racerNamesAndStarts = from r in Formula1.GetChampions()
                                        where r.Country == "Italy"
                                        orderby r.Wins descending
                                        select new
                                        {
                                          LastName = r.LastName,
                                          Starts = r.Starts
                                        };
    
    
              var racers = racerNames.Zip(racerNamesAndStarts, (first, second) => first.Name + ", starts: " + second.Starts);
              foreach (var r in racers)
              {
                  Console.WriteLine(r);
              }


    13.分区
      扩展方法Take()和Skip()等的分区操作可用于分页。
      例如,在第一页只显示5个赛车手,下一页显示接下来的5个赛车手...
      Skip(page * pageSize)方法调到指定索引出,忽略前面的数据。Take(pageSize)方法显示pageSize条数据
      

     int pageSize = 5;
    
              int numberPages = (int)Math.Ceiling(Formula1.GetChampions().Count() /
                    (double)pageSize);
    
              for (int page = 0; page < numberPages; page++)
              {
                Console.WriteLine("Page {0}", page);
    
                var racers =
                   (from r in Formula1.GetChampions()
                    orderby r.LastName, r.FirstName
                    select r.FirstName + " " + r.LastName).
                   Skip(page * pageSize).Take(pageSize);
    
                foreach (var name in racers)
                {
                  Console.WriteLine(name);
                }
                Console.WriteLine();
              }

      TakeWhile()和SkipWhile()方法,传递一个委托,满足这个条件的数据就提取或跳转:
      public static IEnumerable<TSource> SkipWhile<TSource>(this IEnumerable<TSource> source, Func<TSource, bool> predicate);

    14.聚合操作符
      聚合操作符(如Count(),Sum(),Min(),Max(),Average(),Aggregate())不返回一个序列,而是返回一个值。
      例如,使用Count方法应用于Racer的Years属性,筛选获得冠军次数超过3次的赛车手。因为多次使用r.Years.Count(),所以使用let子句定义了一个变量。  

      

    var query = from r in Formula1.GetChampions()
                      let numberYears = r.Years.Count()
                      where numberYears >= 3
                      orderby numberYears descending, r.LastName
                      select new
                      {
                        Name = r.FirstName + " " + r.LastName,
                        TimesChampion = numberYears
                      };
    
              foreach (var r in query)
              {
                Console.WriteLine("{0} {1}", r.Name, r.TimesChampion);
              }

      Aggregate()方法传递一个委托,将数据源中的每个元素作为委托的参数,并使用指定的函数累加。

    15.转换操作符
      LINQ基础(一)(http://www.cnblogs.com/afei-24/p/6841361.html)提到,查询会推迟到迭代数据项时才执行,使用转换操作符会立即执行查询,把查询结果放在数组,列表和字典中。  

        

    //转换为数组
            var names = new List<string> { "Nino", "Alberto", "Juan", "Mike", "Phil" };
    
            var namesWithJ = (from n in names
                             where n.StartsWith("J")
                             orderby n
                             select n).ToList();



      转换为Lookup<TKey,TElement>
      

    //把Car赛车属性作为键,每个键关联多个车手Racer
                var racers = (from r in Formula1.GetChampions()
                          from c in r.Cars
                          select new
                          {
                              Car = c,
                              Racer = r
                              }).ToLookup(cr => cr.Car, cr => cr.Racer);
                foreach (var v in racers)
                {
                    Console.Write(v.Key+"........");
                    foreach (var k in racers[v.Key])
                    {
                        Console.WriteLine(k);
                    }
                }


      ToLookup(cr => cr.Car, cr => cr.Racer)方法的一个重载版本传递一个键和一个元素选择器

      如果需要在非类型化的集合上使用LINQ查询,可以使用Cast()方法,定义强类型化的查询:  

    var list = new System.Collections.ArrayList(Formula1.GetChampions() as System.Collections.ICollection);
    
              var query = from r in list.Cast<Racer>()
                          where r.Country == "USA"
                          orderby r.Wins descending
                          select r;
              foreach (var racer in query)
              {
                Console.WriteLine("{0:A}", racer);
              }

      Cast<Racer>()将 System.Collections.IEnumerable 的元素强制转换为指定的类型。

    16.生成操作符
      生成操作符Range(),Empty(),Repeat()方法不是扩展方法,而是返回序列的正常静态方法。在LING to Object中,这些方法可用于Enumerable类。
      Range()方法用来填充一个范围的数字。第一个参数作为起始值,第二个参数作为要填充的项数:
        var values = Enumerable.Range(1,20);
      结果为1至20的集合。


      可以把该结果与其它扩展方法合并:
        var values = Enumerable.Range(1,20).Select(n=> n*3);

      Empty()方法返回一个不返回值的迭代器,它用于需要一个集合的参数,其中可以给参数传递空集合。
      Repeat()方法返回指定个数的重复值的集合迭代器。

  • 相关阅读:
    欧拉定理 (证明+在求逆元上的应用)
    【转】弱校的ACM奋斗史
    SDUT 2412 (单调队列 + dp)
    做SRM感想。。。
    平面图中最小割向最短路的转化
    HDU 4533
    黑书上的DP 30题
    POJ【数论/组合/博弈论】题目列表
    HDU 4534 郑厂长系列故事——新闻净化
    SRM 571
  • 原文地址:https://www.cnblogs.com/afei-24/p/6845551.html
Copyright © 2020-2023  润新知