اتطلب مني أعمل تاسك في الشغل، والصراحة التاسك عجبتني عشان فيها شوية تفكير وبزنس حلوين، فقلت أنقلها لكم هنا … يمكن حد يستفيد.

المشكلة

المطلوب هو إعادة ترتيب داتا موجودة في الميموري، باستخدام LINQ، بحيث يكون الترتيب مبني على كذا شرط، وحسب نتيجة الشرط العناصر هاتظهر في مكان معين في الليستة … العادي إننا لما بنعمل Sorting بنستخدم Field/Property يخص العناصر اللي في اللستة، زي ما بنعمل في TSQL باستخدام عمود Column معين في جدول Table … لكن الحالة هنا محتاجه ترتيب بشرط - Conditional Sorting.

مثال

للتوضيح أكتر، خليني أديكم مثال حقيقي … تخيل عندنا صفحة بتعرض كل الاشتراكات Subscription في مجلة مثلاً، والجماعة بتوع المبيعات عاوزين يغيروا ترتيب العناصر في الصفحة بحيث الأهم بالنسبة لهم يظهر الأول … الوضع الافتراضي إن الترتيب بيكون تنازلي حسب تاريخ بداية الاشتراك.

الجماعة بتوع المبيعات عاوزين الترتيب يبقى كالتالي: يظهر في الأول الاشتراكات السنوية yearly subs وبعديها الاشتراكات الشهرية monthly subs وأي نوع اشتراك تاني يظهر بعد كده … وكمان في كل مجموعة من دول محتاجين نرتب داخلياً، يظهر في الأول الاشتراكات اللي جاية من دولة هولندا NL وبعديها الاشتراكات اللي من فرنسا FR وبعديهم بريطانيا UK وبعد كده أي دولة تانية.

نكمل المثال باننا نتخيل إن الموديل Model بتاع الاشتراكات اللي في اللستة اسمه Subscription وفيه الـ fields دي:
ID, StartDate, EndDate, Country, SubType.

تحدي 1

عاوزك توقّف قراية لحد هنا وتاخد لك بريك صغير تفكر فيه في كل الحلول الممكنة باستخدام LINQ اللي بيها تقدر تحقق المطالب اللي فوق على لستة من الاشتراكات

List<Subscription> subscriptions

موجودة ومتعبية في الميموري … المطلوب إنك على الأقل تطلع لك بحلّين، وتفكر بعد كده أنهي حل الأفضل … طبعاً مافيش حل مثالي 100%، بس ع الأقل فكّر في الحل اللي ليه أقل آثار جانبية side effects … وبعد ما تخلص كمّل قراية.

الحل البلدي

بيتهيألي أسهل حل وأول حاجه ممكن تيجي في دماغ الواحد هي إننا نكسّر اللستة دي لمجموعة لستات صغيرة، كل واحدة فيهم فيها العناصر اللي تخص نوع اشتراك معين … يعني هانستخدم الـ Where بتاع الـ LINQ في عملية التكسير دي … ونكمل تكسير لكل سلسلة لسلاسل أصغر حسب الدولة … وفي الآخر هاندمج Union كل السلاسل دي مع بعض … وعشان الكود يبقى مقبول، هانعمل recursive باستخدام كام method هانعملهم لأغراض التكسير اللي قلناها فوق.

طبعاً حل بدائي بس هايشتغل … عيوبه الرئيسية إن صيانة الكود هاتبقى أصعب وبالذات لو اتطلب بعد كده نزوّد شروط الترتيب، سواء رأسياً باننا نضيف طبقة ترتيب تالتة، أو أفقياً باننا نزود اختيارات الدول مثلاً.

الحل الأفضل

وده الحل اللي أنا طبقته، وهو عن طريق عمل لستة أو array ثابتة، ونحط فيها أنوع الاشتراكات اللي هانعمل ترتيب بناءاً عليهم، ولستة تانية شبهها فيها الدول … القايمتين دول هانحط العناصر فيهم حسب الترتيب المطلوب … وبعد كده نعمل ترتيب في لستة الاشتراكات حسب Index ظهور كل قيمة من اللستتين اللي فاتوا … يعني هانستخدم OrderBy عادي جداً.

ما تقلقش، أنا كمان تهت مني … خليني أوريك الحل ده بالكود:

var subscriptions = new List<Subscription>();

// Fill subscriptions from somewhere, db, API call, etc

var sortedTypes = new List<string> { "Monthly", "Yearly" };
var sortedCountries = new List<string> { "UK", "FR", "NL" };

var sortedSubs = subscriptions
                    .OrderByDescending(sub => sortedTypes.IndexOf(sub.SubType))
                    .ThenByDescending(sub => sortedCountries.IndexOf(sub.Country));

وبس كده … الكود شكله أسهل وفهمه أسهل، على الأقل في رأيي، وبالتالي كمان صيانته هاتبقى سهلة … لو احتجنا نزود طبقة ترتيب زيادة، هانبقى بس محتاجين نزود لسته ترتيب زي الاتنين اللي في الأول، ونضيف كمان ThenByDescending زيادة … ولو عاوزين نغير في الترتيب الأصلي أو نضيف عناصر زيادة، كل اللي محتاجينه إننا نغير في اللستتين اللي فوق وكل حاجه هاتشتغل زي ما هي.

مشكلة الحل ده إننا ممكن نحتاج نتعامل مع capital/small cases زي مثلاً الدولة تكون في الموديل uk مش UK … ومشكلة تانية هي الأداء لو كانت لستة الـ Subscriptions ضخمة، ساعتها يبقى محتاجين نغير طريقة تفكيرنا ونتجنب الترتيب في الميموري. أنا اخترت الحل ده لأنه كان أقلّهم مشاكل بالنسبة لي وللتيم اللي معايا والمنتج اللي شغالين عليه.

تحدي 2

ما اعرفش أخدت بالك وللا لأ، بس الكود اللي فوق احنا مرتبين القايمتين اللي فوق تنازلي مش تصاعدي، يعني بادئين بنوع الاشتراك اللي المفروض يظهر تحت مش فوق، وبالمثل في قايمة الدول … وكمان الترتيب اللي استخدمناه بعد كده في اللستة هو ترتيب تنازلي مش تصاعدي OrderByDescending … عاوزك تفكر ليه أنا عملتها كده وتكتب لي حلك في التعليقات، ده الواجب home work بتاعك … وبعد كام يوم إن شاء الله هارد على التعليقات الصحيحة.

أخيراً

الحل اللي فوق ممكن يتطبق على تكنولوجيات تانية مش بس الدوت نت والـ LINQ … وكمان ممكن تطبيق نفس فكرة الترتيب بالـ Index باستخدام شروط تانية زي مثلاً أكبر من وأصغر من … وطبعاً لو جبت حل أحسن من اللي فوق يبقى خير وبركة، يا ريت تتفضل تعرضه وادينا كلنا بنتعلم … ولو شايف مشاكل كبيرة في الحل اللي فوق، برضه أحب اسمع رأيك.

لو عجبك التحدي أو فكرة التحديات البرمجية يا ريت تعرفني … أنا ناوي ان شاء الله كل شوية أكتب تحدي برمجي حقيقي بيقابلني في شغلي، وبابقى عاوز اشارك التجربة يمكن حد يستفيد.

تعديل 17-03-2022: حل تحدي 2

لو بصينا على الفلترة بالدول مثلاً، هانلاقي إن أي دولة خارج الدول المطلوبة هايبقى الـ Index بتاعها -1 … ده معناه إن لو رتبنا ترتيب تصاعدي، الدول اللي بره المجموعة المطلوبة هايظهروا في الأول لأن الـ Index بتاعهم -1، وبعديهم باقي المجموعات اللي بتحقق الشروط بـ Index بقيم 0 ثم 1 ثم 2 … فكان لازم نغير الترتيب يبقى تنازلي عشان القيم اللي بره المستهدف بتاعنا تظهر في آخر النتايج، وبالتالي هايبتدي الترتيب بقيم Index كالتالي: 2 ثم 1 ثم 0 ثم -1 … عشان كده برضه خلينا لستة الدول هي كمان مترتبة عكسي عشان الدولة المطلوبة تظهر الأول هايبقى ليها أكبر Index وبالتالي تظهر في أول النتايج.