#C

Array Slicing در #C

در این مقاله قصد داریم در مورد slicing Array در #C صحبت کنیم. ما قصد داریم راه های مختلف نحوه slicing Array (برش آرایه ) در #C را با چند مثال بررسی کنیم.

معرفی slicing Array در #C 

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

#C اخیراً بسیار تکامل یافته است، به طوری که ما اکنون چندین راه برای slicing یک Array داریم. ما قصد داریم همه آنها را در این مقاله بررسی کنیم، اما ابتدا بیایید منطق پشت slicing (برش) را توضیح دهیم.

به طور کلی، دو روش منطقی برای slicing یک Array وجود دارد. یکی از راه‌ها ایجاد یک Array جدید و اضافه کردن عناصر slicing (برش داده شده)‌ به آن است. دیگری ایجاد یک پوشش در اطراف Array است که می تواند نشانگرها را به عناصر خاصی در داخل Array نگه دارد، بدون ایجاد یک Array جدید .

حالا بیایید وارد کد شویم تا ببینیم چگونه می توان Array ها را در #C در عمل slicing (برش) داد.

slicing Array با استفاده از LINQ

فرض کنید یک Array از ده پست داریم و فقط می‌خواهیم پنج تای آن‌ها را به کاربر برگردانیم، با این هدف که بقیه آن‌ها را در صورت تقاضا برگردانیم. این به عنوان صفحه بندی شناخته می شود و با استفاده از دو روش ()LINQ Skip و ()Take. بنابراین بیایید LINQ را وارد کنیم و این دو روش را مصرف کنیم:

var posts = new string[] {"post1", "post2", "post3", "post4", "post5", "post6", "post7", "post8",
    "post9", "post10" };
var slicedPosts = posts.Skip(0).Take(5);
foreach (var post in slicedPosts)
    Console.WriteLine(post); // Outputs the first 5 posts

تعداد عناصری را که می‌خواهیم از آن بگذریم به ()Skip منتقل می‌کنیم. معلوم می شود که این شاخص شروع است، زیرا اگر از شاخص 0 شروع کنیم، از 0 عنصر می گذریم. و تعداد عناصری را که می خواهیم به متد ()Take ارسال می کنیم. این عملیات که یک IEnumerable جدید برمی گرداند را می توانیم بر شماریم.

با استفاده از روش ()Copy برای slicing آرایه

بیایید همان سناریو را در نظر بگیریم، اما این بار با استفاده از ()Copy به نتیجه خواهیم رسید :

var posts = new string[] {"post1", "post2", "post3", "post4", "post5", "post6", "post7", "post8", 
    "post9", "post10" };
var slicedPosts = new string[5];
Array.Copy(posts, 0, slicedPosts, 0, 5);
foreach (var post in slicedPosts)
    Console.WriteLine(post); // Outputs the first 5 posts

در اینجا، یک Array مقصد را مقداردهی اولیه می کنیم که قطعه Array ما خواهد بود. و سپس متد ()Copy را که یک Array مبدا، شاخص شروع، Array مقصد، شاخص مقصد شروع (صفر چون در حال کپی کردن در یک Array جدید) و تعدادی از عناصری که می‌خواهیم slicing (برش) دهیم، فراخوانی می‌کنیم.

همانطور که می بینیم، این روش مانند LINQ کار می کند. یک Array جدید حاوی عناصر slicing (برش) داده شده را برمی گرداند.

حال بیایید یک رویکرد جدید برای این عملیات را بررسی کنیم.

slicing Array با استفاده از <ArraySegment<T

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

var data = new Tuple<int, bool>[] { 
    new(20, true), 
    new(50, true), 
    new(35, false), 
    new(55, true), 
    new(16, false) 
};

ما می خواهیم این پنج رکورد را به یک مجموعه آموزشی از 3 رکورد و یک مجموعه آزمایشی از 2 رکورد تقسیم کنیم.

 ما قبلاً چند راه برای انجام این کار را یاد گرفتیم، اما اجازه دهید راه جدیدی را با استفاده از <ArraySegment<T را توضیح دهیم:  

var trainingData = new ArraySegment<Tuple<int, bool>>(data, 0, 3);
var testingData = new ArraySegment<Tuple<int, bool>>(data, 3, 2);
Console.WriteLine("Training Data:");
foreach (var record in trainingData)
    Console.WriteLine(record);
Console.WriteLine();
Console.WriteLine("Testing Data:");
foreach (var record in testingData)
    Console.WriteLine(record);

یک نمونه جدید از ArraySegment را مقداردهی اولیه می کنیم ، Array ای که می خواهیم slicing (برش) دهیم، شاخص شروع و تعداد عناصر را ارسال می کنیم. این یک پوشش در اطراف Array ایجاد می کند که عناصر را بین این موقعیت های مشخص شده محدود می کند. سپس می‌توانیم این بخش را تکرار کرده و مقادیر آن را بخوانیم. 

همین امر را می توان با استفاده از ()ArraySegment<T>.Slice روش زیر بدست آورد:

...
var arraySegment = new ArraySegment<Tuple<int, bool>>(data);
var trainingData = arraySegment.Slice(0, 3);
var testingData = arraySegment.Slice(3, 2);
...

ما یک ArraySegment ایجاد می کنیم که کل Array را در بر می گیرد، سپس متد ()Slice را فراخوانی می کنیم که با مرزهای مشخص شده ArraySegment دیگری را ایجاد می کنیم.

با این حال، باید مراقب باشیم، زیرا این یک Array جدید نیست، در واقع یک متغیر نوع مقدار است که نشانگرهای موقعیت عناصر را در Array (آرایه )نگه می‌دارد، بنابراین هر تغییری در مقادیر از طریق ArraySegment در Array (آرایه )اصلی منعکس می‌شود. :

var trainingData = new ArraySegment<Tuple<int, bool>>(data, 0, 3);
Console.WriteLine("Training Data:");
for (int i = 0; i < trainingData.Count; i++)
{
    trainingData[i] = new(40, false);
    Console.WriteLine(trainingData[i]);
}
Console.WriteLine();
Console.WriteLine("Original Data:");
foreach (var record in data)
    Console.WriteLine(record);

خواهیم دید که 3 عنصر اول نه تنها در ArraySegment بلکه در Array (آرایه )اصلی نیز تغییر کرده است:

Training Data:
(40, False)
(40, False)
(40, False)
Original Data:
(40, False)
(40, False)
(40, False)
(55, True)
(16, False)

استفاده از <ReadOnlySpan<T یا <Span<T برای slicing  Array

slicing ArraySegment کارآمدترین راه برای مدیریت کردن حافظه تا کنون بوده است، اما ثبات داده های ما را تضمین نمی کند. اینجاست که <ReadOnlySpan<T به کمک می آید. پوششی را فراهم می کند که فقط خواندن از Array امکان پذیر باشد:

var trainingData = new ReadOnlySpan<Tuple<int, bool>>(data, 0, 3);
var testingData = new ReadOnlySpan<Tuple<int, bool>>(data, 3, 2);
Console.WriteLine("Training Data:");
foreach (var record in trainingData)
    Console.WriteLine(record);
Console.WriteLine();
Console.WriteLine("Testing Data:");
foreach (var record in testingData)
    Console.WriteLine(record);

متوجه شدیم که ReadOnlySpan بسیار شبیه به ArraySegment است، شاخص شروع و تعداد عناصر را می گیرد و می‌توانیم آن را برش دهیم، اما اگر بخواهیم هر عنصری را از طریق یک ReadOnlySpan نمونه تغییر دهیم، trainingData در مثال ما، یک خطا زمان کامپایل دریافت می‌کنیم.

ما مثال را برای <Span<T تکرار نمی کنیم اما به همان روشی که از <ReadOnlySpan<T استفاده می کنیم برای <Span<T نیز می توانیم استفاده کنیم.

برای این سناریو، باید از ReadOnlySpan استفاده کنیم زیرا نمی‌خواهیم داده‌های ما تغییر کند. با این حال، ممکن است الگوریتم‌های دیگری داشته باشیم که داده‌های ما را به‌ طور منظم به‌ روزرسانی می‌کنند، در این مورد، باید از <Span<T استفاده کنیم.

Range Operator (x..y) در #C 8.0+

با شروع #C 8.0 ما یک عملگر جدید داریم که slicing (برش) را از نظر نحوی بسیار ساده کرده است. این عملگر محدوده x..y است. به ما این امکان را می دهد که عناصر را بین شاخص 'x' و 'y' برش دهیم.

var array = new int[] {1, 2, 3, 4, 5 };
var slice1 = array[2..]; // From index 2 to the end
var slice2 = array[..2]; // From the start to index 1
var slice3 = array[1..3]; // From the index 1 to index 2
var slice4 = array[..]; // The whole array
var slice5 = array[3..1]; // Throws ArgumentOutOfRangeException

همانطور که می بینیم، می توانیم از هر دو، یا بدون عملوند استفاده کنیم. همچنین می‌توانیم ببینیم که این مرزها در جهت عقب کار نمی‌کنند، بنابراین slice5یک ArgumentOutOfRangeException.

نکته قابل ذکر این است که اگر از عملگر range برای کار با Array ها استفاده کنیم، Array های جدیدی را اختصاص می دهد. اما زمانی که از آن در <Span<T استفاده می کنیم اینطور نیست.

نتیجه :

در این مقاله به معرفی slicing  Array در #C پرداختیم. ما تعدادی سناریو را با استفاده از رویکردهای مختلف پیاده سازی کرده ایم و یاد گرفته ایم که چگونه Array ها را به طور موثر slicing (برش) دهیم.

ممنون که تا انتهای مقاله با ما همراه بودید امیدواریم این مقاله برای شما مفید بوده باشد.