تعليق توضيحي للترجمة: الخريطة (رسم الخرائط) والتقليل (التقليل، التبسيط) هما مفهومان أساسيان للغاية في الرياضيات. لقد ظهرا في العديد من لغات البرمجة الوظيفية لفترة طويلة ولم تقم Google بطرحهما حتى عام 2003 بعد تطبيق الحوسبة المتوازية في الأنظمة الموزعة، بدأ اسم هذه المجموعة يلمع في عالم الكمبيوتر (قد لا يعتقد هؤلاء المعجبون الوظيفيون ذلك). في هذه المقالة، سنرى الظهور الأول للخريطة وتقليل المجموعات بعد دعم Java 8 للبرمجة الوظيفية (هذه مجرد مقدمة أولية، وستكون هناك مواضيع خاصة بها لاحقًا).
تقليل مجموعة
لقد قدمنا حتى الآن العديد من التقنيات الجديدة لتشغيل المجموعات: العثور على العناصر المطابقة، والعثور على العناصر الفردية، وتحويلات المجموعة. تشترك هذه العمليات في شيء واحد، وهي أنها تعمل جميعها على عنصر واحد في المجموعة. ليست هناك حاجة لمقارنة العناصر أو إجراء عمليات على عنصرين. سنتناول في هذا القسم كيفية مقارنة العناصر والحفاظ ديناميكيًا على نتيجة العملية أثناء اجتياز المجموعة.
لنبدأ بأمثلة بسيطة ثم نواصل طريقنا. في المثال الأول، نقوم أولاً بالتكرار عبر مجموعة الأصدقاء وحساب إجمالي عدد الأحرف في جميع الأسماء.
انسخ رمز الكود كما يلي:
System.out.println("إجمالي عدد الأحرف في جميع الأسماء:" + friends.stream()
.mapToInt(الاسم -> الاسم.الطول())
.مجموع())؛
لحساب العدد الإجمالي للأحرف نحتاج إلى معرفة طول كل اسم. يمكن تحقيق ذلك بسهولة من خلال طريقة MapToInt(). بعد أن قمنا بتحويل الأسماء إلى أطوال مقابلة، نحتاج فقط إلى جمعها معًا في النهاية. لدينا طريقة sum() مدمجة لإنجاز هذا. هنا هو الناتج النهائي:
انسخ رمز الكود كما يلي:
إجمالي عدد الأحرف في جميع الأسماء: 26
استخدمنا متغيرًا لعملية الخريطة، وطريقة MapToInt () (مثل MapToInt وmapToDouble وما إلى ذلك، والتي ستنشئ أنواعًا معينة من التدفقات، مثل IntStream وDoubleStream)، ثم قمنا بحساب إجمالي عدد الأحرف بناءً على طول عاد.
بالإضافة إلى استخدام طريقة المجموع، هناك العديد من الطرق المشابهة التي يمكن استخدامها، مثل max() للعثور على الحد الأقصى للطول، min() للعثور على الحد الأدنى للطول،sorted() لفرز الأطوال، Average() لـ أوجد متوسط الطول، وما إلى ذلك، انتظر.
جانب آخر جذاب في المثال أعلاه هو وضع MapReduce الشائع بشكل متزايد. تقوم طريقة Map () بإجراء التعيين، وطريقة sum () هي عملية تقليل شائعة الاستخدام. في الواقع، يستخدم تنفيذ طريقة sum() في JDK طريقة تقليل(). دعونا نلقي نظرة على بعض الأشكال الأكثر استخدامًا لعمليات التخفيض.
على سبيل المثال، نكرر جميع الأسماء ونطبع الاسم الذي يحتوي على الاسم الأطول. إذا كان هناك العديد من الأسماء الطويلة، فإننا نطبع الاسم الذي تم العثور عليه أولاً. إحدى الطرق هي أن نحسب الحد الأقصى للطول ثم نختار العنصر الأول الذي يطابق هذا الطول. ومع ذلك، فإن القيام بذلك يتطلب اجتياز القائمة مرتين - وهو أمر غير فعال للغاية. هذا هو المكان الذي تلعب فيه عملية التخفيض.
يمكننا استخدام عملية التصغير لمقارنة أطوال عنصرين، ثم إرجاع العنصر الأطول، ومقارنته أيضًا بالعناصر المتبقية. مثل الوظائف ذات الترتيب الأعلى الأخرى التي رأيناها من قبل، فإن طريقة التخفيض () تجتاز المجموعة بأكملها أيضًا. من بين أمور أخرى، فإنه يسجل نتيجة الحساب التي يتم إرجاعها بواسطة تعبير لامدا. إذا كان هناك مثال يمكن أن يساعدنا على فهم هذا بشكل أفضل، فلنلقِ نظرة على جزء من التعليمات البرمجية أولاً.
انسخ رمز الكود كما يلي:
نهائي اختياري<String> aLongName = friends.stream()
.تقليل ((الاسم1، الاسم2) ->
name1.length() >= name2.length() ? name1 : name2);
aLongName.ifPresent(name ->
System.out.println(String.format("الاسم الأطول: %s"، الاسم)));
يتلقى تعبير lambda الذي تم تمريره إلى طريقة التخفيض () معلمتين، name1 وname2، ويقوم بمقارنة أطوالهما وإرجاع أطول واحد. ليس لدى طريقة التخفيض () أي فكرة عما سنفعله. تم تجريد هذا المنطق من تعبير لامدا الذي نمرره - وهذا تطبيق خفيف الوزن لنمط الإستراتيجية.
يمكن تكييف تعبير lambda هذا مع طريقة التطبيق للواجهة الوظيفية لـ BinaryOperator في JDK. هذا هو بالضبط نوع الوسيطة التي تقبلها طريقة التخفيض. لنقم بتشغيل طريقة التصغير هذه ونرى ما إذا كان بإمكانها تحديد الاسم الأول من أطول اسمين بشكل صحيح.
انسخ رمز الكود كما يلي:
الاسم الأطول: بريان
عندما تعبر طريقة الاختزال () المجموعة، فإنها تستدعي أولاً تعبير لامدا على العنصرين الأولين من المجموعة، ويستمر استخدام النتيجة التي أرجعها الاستدعاء في المكالمة التالية. في الاستدعاء الثاني، ترتبط قيمة name1 بنتيجة الاستدعاء السابق، وقيمة name2 هي العنصر الثالث في المجموعة. يتم استدعاء العناصر المتبقية أيضًا بهذا الترتيب. نتيجة استدعاء تعبير لامدا الأخير هي النتيجة التي يتم إرجاعها بواسطة طريقة التخفيض () بأكملها.
ترجع طريقة التخفيض () قيمة اختيارية لأن المجموعة التي تم تمريرها إليها قد تكون فارغة. في هذه الحالة، لن يكون هناك اسم أطول. إذا كانت القائمة تحتوي على عنصر واحد فقط، فإن طريقة التخفيض ترجع هذا العنصر مباشرة ولا تستدعي تعبير لامدا.
من هذا المثال يمكننا أن نستنتج أن نتيجة التخفيض يمكن أن تكون عنصرًا واحدًا فقط في المجموعة على الأكثر. إذا أردنا إرجاع قيمة افتراضية أو أساسية، فيمكننا استخدام متغير من طريقة التخفيض () التي تقبل معلمة إضافية. على سبيل المثال، إذا كان الاسم الأقصر هو Steve، فيمكننا تمريره إلى طريقة تقليل () مثل هذا:
انسخ رمز الكود كما يلي:
السلسلة النهائية steveOrLonger = friends.stream()
.reduce("ستيف"، (name1، name2) ->
name1.length() >= name2.length() ? name1 : name2);
إذا كان هناك اسم أطول منه، فسيتم تحديد هذا الاسم، وإلا فسيتم إرجاع القيمة الأساسية Steve. هذا الإصدار من طريقة التخفيض () لا يُرجع كائنًا اختياريًا، لأنه إذا كانت المجموعة فارغة، فسيتم إرجاع قيمة افتراضية بغض النظر عن الحالة التي لا توجد فيها قيمة إرجاع.
قبل أن ننهي هذا الفصل، دعونا نلقي نظرة على عملية أساسية للغاية ولكنها ليست سهلة في عمليات المجموعة: دمج العناصر.
دمج العناصر
لقد تعلمنا كيفية العثور على العناصر، واجتياز، وتحويل المجموعات. ومع ذلك، هناك عملية شائعة أخرى - ربط عناصر المجموعة - بدون وظيفة join () المضافة حديثًا، فإن الكود المختصر والأنيق المذكور سابقًا سيكون عبثًا. هذه الطريقة البسيطة عملية للغاية لدرجة أنها أصبحت واحدة من الوظائف الأكثر استخدامًا في JDK. دعونا نرى كيفية استخدامه لطباعة العناصر في القائمة، مفصولة بفواصل.
مازلنا نستخدم قائمة الأصدقاء هذه. إذا كنت تستخدم الطريقة القديمة في مكتبة JDK، فماذا عليك أن تفعل إذا كنت تريد طباعة جميع الأسماء مفصولة بفواصل؟
يتعين علينا تكرار القائمة وطباعة العناصر واحدًا تلو الآخر. تم تحسين حلقة for في Java 5 مقارنة بالحلقة السابقة، لذلك دعونا نستخدمها.
انسخ رمز الكود كما يلي:
لـ (اسم السلسلة: الأصدقاء) {
System.out.print(name + ", ");
}
System.out.println();
الكود بسيط للغاية، دعونا نرى ما هو الناتج.
انسخ رمز الكود كما يلي:
بريان، نيت، نيل، راجو، سارة، سكوت،
اللعنة، هناك تلك الفاصلة المزعجة في النهاية (هل يمكننا إلقاء اللوم على سكوت في النهاية؟). كيف يمكنني إخبار Java بعدم وضع فاصلة هنا؟ لسوء الحظ، يتم تنفيذ الحلقة خطوة بخطوة، وليس من السهل القيام بشيء خاص في النهاية. لحل هذه المشكلة، يمكننا استخدام طريقة الحلقة الأصلية.
انسخ رمز الكود كما يلي:
for(int i = 0; i < friends.size() - 1; i++) {
System.out.print(friends.get(i) + ", ");
}
إذا (الأصدقاء.الحجم () > 0)
System.out.println(friends.get(friends.size() - 1));
دعونا نرى ما إذا كان إخراج هذا الإصدار على ما يرام.
انسخ رمز الكود كما يلي:
بريان، نيت، نيل، راجو، سارة، سكوت
النتيجة لا تزال جيدة، ولكن هذا الرمز ليس الاغراء. أنقذونا يا جافا.
ليس علينا أن نتحمل هذا الألم بعد الآن. تساعدنا فئة StringJoiner في Java 8 على حل هذه المشكلات، ليس هذا فحسب، بل تضيف فئة String أيضًا طريقة ربط حتى نتمكن من استبدال العناصر المذكورة أعلاه بسطر واحد من التعليمات البرمجية.
انسخ رمز الكود كما يلي:
System.out.println(String.join("، "، الأصدقاء));
تعال وألق نظرة، النتائج مرضية مثل الكود.
انسخ رمز الكود كما يلي:
بريان، نيت، نيل، راجو، سارة، سكوت
النتيجة لا تزال جيدة، ولكن هذا الرمز ليس الاغراء. أنقذونا يا جافا.
ليس علينا أن نتحمل هذا الألم بعد الآن. تساعدنا فئة StringJoiner في Java 8 على حل هذه المشكلات، ليس هذا فحسب، بل تضيف فئة String أيضًا طريقة ربط حتى نتمكن من استبدال العناصر المذكورة أعلاه بسطر واحد من التعليمات البرمجية.
انسخ رمز الكود كما يلي:
System.out.println(String.join("، "، الأصدقاء));
تعال وألق نظرة، النتائج مرضية مثل الكود.
انسخ رمز الكود كما يلي:
بريان، نيت، نيل، راجو، سارة، سكوت
في التنفيذ الأساسي، تستدعي طريقة String.join() فئة StringJoiner لربط القيمة التي تم تمريرها كمعلمة ثانية (وهي معلمة متغيرة الطول) في سلسلة طويلة، باستخدام المعلمة الأولى كمحدد. وبطبيعة الحال، هذه الطريقة هي أكثر من مجرد ربط الفواصل. على سبيل المثال، يمكننا تمرير مجموعة من المسارات وتوضيح مسار الفصل بسهولة، وذلك بفضل هذه الأساليب والفئات المضافة حديثًا.
نحن نعرف بالفعل كيفية ربط عناصر القائمة، قبل ربط القوائم، يمكننا أيضًا تحويل العناصر. بالطبع، نعرف أيضًا كيفية استخدام طريقة الخريطة لتحويل القوائم. بعد ذلك، يمكننا أيضًا استخدام طريقة التصفية () لتصفية العناصر التي نريدها. الخطوة الأخيرة لربط عناصر القائمة، باستخدام الفواصل أو بعض المحددات الأخرى، هي مجرد عملية تصغير بسيطة.
يمكننا استخدام طريقة التخفيض () لتسلسل العناصر في سلسلة، ولكن هذا يتطلب بعض العمل من جانبنا. لدى JDK طريقة تجميع () مريحة للغاية، وهي أيضًا نوع مختلف من طريقة التخفيض (). يمكننا استخدامها لدمج العناصر في القيمة المطلوبة.
تقوم طريقة التجميع () بتنفيذ عملية التخفيض، ولكنها تقوم بتفويض العملية المحددة إلى أداة التجميع للتنفيذ. يمكننا دمج العناصر المحولة في ArrayList. بالاستمرار في المثال السابق، يمكننا سلسلة العناصر المحولة في سلسلة مفصولة بفواصل.
انسخ رمز الكود كما يلي:
System.out.println(
friends.stream()
.map(سلسلة::toUpperCase)
.collect(الانضمام("، ""));
لقد قمنا بتسمية طريقة التجميع () في القائمة المحولة، وتمريرها إلى المجمع الذي تم إرجاعه بواسطة طريقة الانضمام () يعد الانضمام طريقة ثابتة في فئة أداة المجمعات. المُجمع يشبه جهاز الاستقبال، فهو يستقبل الكائنات التي تم تمريرها عن طريق التجميع ويخزنها بالتنسيق الذي تريده: ArrayList، String، وما إلى ذلك. سنستكشف هذه الطريقة بشكل أكبر في طريقة التجميع وفئة Collectors في الصفحة 52.
هذا هو اسم الإخراج، والآن أصبح مكتوبًا بأحرف كبيرة ومفصولاً بفواصل.
انسخ رمز الكود كما يلي:
بريان، نيت، نيل، راجو، سارة، سكوت
تلخيص
المجموعات شائعة جدًا في البرمجة باستخدام تعبيرات لامدا، أصبحت عمليات التجميع في Java أبسط وأسهل. يمكن استبدال جميع التعليمات البرمجية القديمة القديمة لعمليات التجميع بهذا النهج الجديد الأنيق والموجز. يجعل المكرر الداخلي اجتياز المجموعة وتحويلها أكثر ملاءمة، بعيدًا عن مشكلة التباين، ويصبح العثور على عناصر المجموعة أمرًا سهلاً للغاية. يمكنك كتابة تعليمات برمجية أقل بكثير باستخدام هذه الأساليب الجديدة. وهذا يجعل صيانة التعليمات البرمجية أسهل، وأكثر تركيزًا على منطق الأعمال، وعمليات أقل أساسية في البرمجة.
في الفصل التالي، سنرى كيف تعمل تعبيرات لامدا على تبسيط عملية أساسية أخرى في تطوير البرنامج: معالجة السلسلة ومقارنة الكائنات.