اپراتورهای Partition در LINQ

02 اردیبهشت 1398
درسنامه درس 7 از سری آموزش LINQ
LINQ-React

با سلام به کاربران عزیز با یکی دیگر از سری آموزش های LINQ در C# در خدمت شما هستیم. در این جلسه قصد داریم با اپراتورهای Partition آشنا شویم.

Partition Operators چیست؟

کاربرد این سری از اپراتور ها برای تقسیم لیست یا مجموعه به دو قسمت است که در نهایت یک قسمت به عنوان خروجی در نظر گرفته می شود که آن هم پیرو شرایط و قوانینی است که در ادامه بررسی خواهیم کرد.

اپراتورهای Partition در LINQ به چهار بخش:

  • Take
  • TakeWhile
  • Skip
  • SkipWhile

تقسیم بندی می شوند که در جدول زیر کاربرد هر یک بررسی شده است:

کاربرد نام اپراتور
برای بازگرداندن تعدادی مشخص از عناصر یک مجموعه یا لیست استفاده می شود Take
کاملا شبیه به Take است فقط عناصری که در شرطی مشخص صدق کنند را برمی گرداند TakeWhile
برای نادیده گرفتن تعدادی مشخص از عناصر یک مجموعه یا لیست استفاده می شود Skip
کاملا شبیه به Skip است، فقط عناصری که در شرطی مشخص صدق کنند را نادیده می گیرد SkipWhile

متد Take

فرض کنید مجموعه ای متشکل از تعدادی عضو وجود دارد و قصد شما این است که n عضو اول این مجموعه را در اختیار بگیرید، برای این کار از متد Take استفاده می شود. کاربرد آن بسیار ساده بوده و الگوی آن به صورت زیر است:

IEnumerable<string> result = list.Take(n);

توجه کنید در اینجا نوع داده ی result از نوع <IEnumerable<string است. در این الگو فرض شده داده های list از نوع string  هستند، به همین خاطر در <> نوع string نوشته شده است. همان طور که قبلا بیان شد برای راحتی کار می توانید از نوع داده ی var به جای <>IEnumerable استفاده کنید.

برای روشن تر شدن موضوع به مثال زیر توجه کنید:

using System;
using System.Collections.Generic;
using System.Linq;
namespace LINQExamples
{
    class Program
    {
        static void Main(string[] args)
        {
            string[] countries = { "India", "Iran", "Russia", "China", "USA", "Argentina" };
            var result = countries.Take(3);
            foreach (string s in result)
            {
                Console.WriteLine(s);
            }
            Console.ReadLine();
        }
    }
}

در این مثال ساده یک آرایه از جنس string با اعضایی مشخص وجود دارد و می خواهیم سه عضو اول این آرایه را در خروجی چاپ کنیم. این کار توسط فراخوانی متد Take بر روی مجموعه ی مورد نظر قابل انجام است.

بازنویسی کد به روش Query به صورت زیر است:

 var result = (from x in countries
select x).Take(3);

یا

 var result = from x in countries.Take(3)
select x;
خروجی مثال اول Take
خروجی مثال اول Take

به مثال دوم از کاربرد Take توجه کنید:

using System;
using System.Collections.Generic;
using System.Linq;
namespace LINQExamples
{
    class Program
    {
        static void Main(string[] args)
        {
            IEnumerable<Student> result = Student.GetListStu().Take(2);
            foreach (Student s in result)
            {
                Console.WriteLine("{0}  |   {1}",s.Name,s.Average);
            }
            Console.ReadLine();
        }
    }
    public class Student
    {
        public int ID { get; set; }
        public string Name { get; set; }
        public Double Average { get; set; }
        public static List<Student>  GetListStu()
        {
            return new List<Student>
            {
                new Student{ID=100,Name="Jack",Average=15.25 },
                new Student{ID=101,Name="Olivia",Average=10.75 },
                new Student{ID=102,Name="Freddie",Average=18.33 },
                new Student{ID=103,Name="Oscar",Average=16.45 },
                new Student{ID=104,Name="Emily",Average=12.25 },
            };
        }
    }
}

مشاهده می کنید که در این مثال یک List با جنس داده ی Student با اعضایی مشخص در اختیار داریم که با استفاده از متد Take به دو عضو اول آن دسترسی پیدا کرده ایم.

برای باز نویسی کد به روش Query به صورت زیر عمل می کنیم:

IEnumerable<Student> result = (from stu in Student.GetListStu()
select stu).Take(2);
خروجی مثال دوم Take
خروجی مثال دوم Take

متد TakeWhile

این متد تا حدودی مانند متد Take عمل می کند فقط قابلیت پیاده سازی شرط را به همراه دارد. در واقع Take نوع داده ی int را به عنوان پارامتر ورودی می پذیرد اما TakeWhile شرط را به عنوان پارامتر ورودی می گیرد.

به الگوی استفاده از آن توجه کنید:

IEnumerable<string> result = list.TakeWhile(Condition");

به مثال زیر توجه کنید:

using System;
using System.Collections.Generic;
using System.Linq;
namespace LINQExamples
{
    class Program
    {
        static void Main(string[] args)
        {
            string[] countries = { "India","Russia", "Iran", "China", "USA", "Argentina" };
            var result = countries.TakeWhile(x =>x.StartsWith("I"));
            foreach (string s in result)
            {
                Console.WriteLine(s);
            }
            Console.ReadLine();
        }
    }
}

در این مثال قصد داریم عناصری که با I (بزرگ) شروع می شوند را در خروجی نشان دهیم. احتمالا انتظار دارید که خروجی دو کلمه ی Iran و India باشد اما این چنین نخواهد بود. به یاد داشته باشید شرط متد تا زمانی معتبر است که نقض نشود. پس به محض نقض شدن شرط اجرای متد TakeWhile متوقف خواهد شد.

همین مثال را در نظر بگیرید. عضو اول با حرف I شروع می شود پس شرط را ارضا می کند اما عضو دوم با R شروع می شود بنابراین شرط متد نقض شده و فورا اجرای متد متوقف می شود و اهمیت ندارد که عناصر بعدی در شرط صدق می کنند یا نه.

روش Query:

var result = (from x in countries
              select x).TakeWhile(x => x.StartsWith("I"));

مثال دوم این متد را در نظر بگیرید:

using System;
using System.Collections.Generic;
using System.Linq;
namespace LINQExamples
{
    class Program
    {
        static void Main(string[] args)
        {
            int[] num = {2,4,6,7,8,10,12};
            var result = num.TakeWhile(x => x%2 ==0);
            foreach (int s in result)
            {
                Console.WriteLine(s);
            }
            Console.ReadLine();
        }
    }
}

یک آرایه از نوع int در اختیار داریم که همه ی اعضای آن به جز یکی زوج هستند. شرط قرار داده شده در متد TakeWhile برای گرفتن اعداد زوج موجود در آرایه است.

همان طور که در خروجی مشاهده می کنید فقط سه عضو اول که در شرط صدق می کنند به عنوان خروجی در نظر گرفته شده اند و وجود عنصر چهارم (7) باعث می شود شرط نقض شده و اجرای متد متوقف شود پس عناصر زوجی که بعد از آن قرار دارند در خروجی نشان داده نمی شوند.

روش Query:

var result = (from x in num
              select x).TakeWhile(x=>x%2==0);
خروجی متد TakeWhile
خروجی متد TakeWhile

متد Skip

این متد کاملا برعکس متد Take عمل می کند و تعداد n عنصر اول یک مجموعه را نادیده گرفته و بقیه را به عنوان خروجی در نظر می گیرد.

به الگوی استفاده از آن توجه کنید:

IEnumerable<string> result = list.Skip(n);

مثال اول متد Take را برای این متد بازنویسی می کنیم:

using System;
using System.Collections.Generic;
using System.Linq;
namespace LINQExamples
{
    class Program
    {
        static void Main(string[] args)
        {
            string[] countries = { "India", "Iran", "Russia", "China", "USA", "Argentina" };
            var result = countries.Skip(3);
            foreach (string s in result)
            {
                Console.WriteLine(s);
            }
            Console.ReadLine();
        }
    }
}

متد Skip فقط یک int  به عنوان پارامتر ورودی می گیرد که تعیین می کند که چند عضو از مجموعه نادیده گرفته شوند. در این مثال قصد داریم 3 عنصر اول یک آرایه را نادیده بگیریم و بقیه را در خروجی چاپ کنیم برای این منظور عدد 3 را در متد Skip قرار داده ایم.

خروجی مثال Skip
خروجی مثال Skip

همان طور که مشاهده می کنید از عنصر سوم به بعد در خروجی چاپ شده اند.

روش Query:

var result = (from x in countries
              select x).Skip(3);

متد SkipWhile

این متد نیز کاملا برعکس متد TakeWhile عمل می کند. در این متد تا زمانی که شرط برقرار است عناصر مربوطه نادیده گرفته می شوند و باقی عناصر به عنوان خروجی تعیین می گردند.

به الگوی استفاده از آن توجه کنید:

IEnumerable<string> result = list.SkipWhile(Condition");

برای این که تفاوت دو متد را بهتر درک کنید مثال دوم مربوط به متد TakeWhile را برای این متد بازنویسی می کنیم:

using System;
using System.Collections.Generic;
using System.Linq;
namespace LINQExamples
{
    class Program
    {
        static void Main(string[] args)
        {
            int[] num = { 2, 4, 6, 7, 8, 10, 12 };
            var result = num.SkipWhile(x => x % 2 == 0);
            foreach (int s in result)
            {
                Console.WriteLine(s);
            }
            Console.ReadLine();
        }
    }
}

در این مثال فقط جای TakeWhile را با SkipWhile عوض کرده ایم. کد را اجرا و خروجی را مشاهده کنید. شرط نوشته شده در متد باعث می شود عناصری که در شرط صدق می کنند نادیده گرفته شوند. وجود عضو فرد باعث نقض شدن شرط شده و اجرای متد را متوقف می کند.

بنابراین خروجی به شکل زیر خواهد بود:

خروجی مثال SkipWhile
خروجی مثال SkipWhile

حالا ممکن است این سوال پیش بیاید که اصلا این متدها کجا کاربرد دارند و فایده آنها چیست؟

پس به مثال جالب زیر توجه کنید:

using System;
using System.Collections.Generic;
using System.Linq;
namespace LINQExamples
{
    class Program
    {
        static void Main(string[] args)
        {
            IEnumerable<Student> StuList = Student.GetList();
            while (true)
            {
                Console.WriteLine("Please enter Page Number 1~5");
                int PageNum = 0;
                if (int.TryParse(Console.ReadLine(), out PageNum))
                {
                    if (PageNum >= 1 && PageNum <= 5)
                    {
                        int PageSize = 3;
                        IEnumerable<Student> result = StuList.Skip((PageNum - 1) * PageSize).Take(PageSize);
                        foreach (var item in result)
                        {
                            Console.WriteLine(item.ID + "\t" + item.Name + "\t" + item.Age);
                        }
                    }
                    else
                    {
                        Console.WriteLine("Wrong Number");
                    }
                }
                else
                {
                    Console.WriteLine("Wrong Number");
                } 
            }
        } 
    }
    public class Student
    {
        public int ID { get; set; }
        public string Name { get; set; }
        public int Age { get; set; }
        public static List<Student> GetList()
        {
            return new List<Student>()
            {
                new Student(){ ID=1000,Name="Connor",Age=20},
                new Student(){ ID=1001,Name="Jacob",Age=22},
                new Student(){ ID=1002,Name="Harry",Age=25},
                new Student(){ ID=1003,Name="Liam",Age=20},
                new Student(){ ID=1004,Name="Olivia",Age=19},
                new Student(){ ID=1005,Name="Emily",Age=26},
                new Student(){ ID=1006,Name="James",Age=19},
                new Student(){ ID=1007,Name="Emma",Age=20},
                new Student(){ ID=1008,Name="Mia",Age=25},
                new Student(){ ID=1009,Name="Mason",Age=24},
                new Student(){ ID=1010,Name="Lily",Age=24},
                new Student(){ ID=1011,Name="Thomas",Age=22},
                new Student(){ ID=1012,Name="David",Age=21},
                new Student(){ ID=1013,Name="Linda",Age=26},
                
            };
        }
    }
}

در این مثال با استفاده از دو متد Take و Skip عمل صفحه بندی اطلاعات را پیاده سازی کرده ایم. به منظور تکرار کلیه ی عملیات، تمامی کد ها در داخل while با شرطی همواره صحیح قرار گرفته اند.

ابتدا اطلاعات را توسط متد GetList که در کلاس Student تعریف کرده ایم در داخل متغیر StuList قرار داده ایم. در مرحله ی بعد در if اول، عدد بودن کاراکتر وارد شده توسط کاربر را کنترل شده  و در if دوم، شرط وارد شدن اعداد 1 تا 5 را اعمال کرده ایم. اگر عدد وارد شده توسط کاربر بین 1 تا 5 باشد مقدار آن در PageNum ذخیره می شود.

در این مثال می خواهیم در هر صفحه 3 رکورد نمایش داده شود برای این منظور PageSize=3 قرار داده ایم.

اجازه دهید خط کد (StuList.Skip((PageNum - 1) * PageSize).Take(PageSize را با استفاده از عدد گذاری در آن توضیح دهیم. فرض کنید کاربر، شماره ی صفحه را 2 وارد کرده بنابراین PageNum - 1=1 ضرب در PageSize=3 می شود که حاصل آن عدد 3 خواهد شد.

پس 3 عضو اول مجموعه ی StuList  توسط متد Skip نادیده گرفته می شوند و باقی در خروجی نمایش داده خواهند شد اما هدف ما نمایش سه رکورد در هر صفحه است، پس باید از عناصر باقی مانده سه عضو اول آن را در اختیار بگیریم که این کار توسط متد Take انجام خواهد شد و پارامتر ورودی آن نیز PageSize=3 می باشد.

در ادامه هم مانند قبل توسط یک حلقه اطلاعات را در خروجی چاپ می کنیم:

خروجی مثال بالا
خروجی مثال بالا

این بخش از آموزش هم به پایان رسید.

موفق باشید.

تمام فصل‌های سری ترتیبی که روکسو برای مطالعه‌ی دروس سری آموزش LINQ توصیه می‌کند:
نویسنده شوید
دیدگاه‌های شما

در این قسمت، به پرسش‌های تخصصی شما درباره‌ی محتوای مقاله پاسخ داده نمی‌شود. سوالات خود را اینجا بپرسید.