{"id":"l-js-3-3","courseId":"javascript-fundamentals","lang":"ar","kind":"lesson","summary":"تلخّص قائمة في قيمة واحدة باستخدام reduce، وتبني مجاميع لكل فئة، وترتّب الأرقام والنصوص والكائنات ترتيبًا صحيحًا بدالة مقارنة، دون الوقوع في فخّي الترتيب الافتراضي والتعديل في المكان.","html":"<p>سؤالان على كل تطبيق ميزانية أن يجيب عنهما: “كم أنفقت على الطعام؟” و&quot;ما أكبر مصاريفي؟&quot;. الأول يحوّل القائمة إلى رقم واحد لكل فئة. والثاني يضع القائمة في ترتيب مفيد. لدى JavaScript دالة لكل منهما، ولكل منهما فخّ واحد شهير.</p>\n<h2 id=\"sec-1\" tabindex=\"-1\">reduce: قيم كثيرة تصبح قيمة واحدة</h2>\n<p>أنت تعرف نمط المُراكِم: ابدأ بقيمة، وحدّثها مع كل عنصر، واستخدمها في النهاية. و<code dir=\"ltr\">reduce</code> هي هذا النمط على شكل دالة:</p>\n<div class=\"code-block\" dir=\"ltr\" data-lang=\"js\" data-run=\"js\" data-index=\"0\"><div class=\"code-head\"><span class=\"code-title\">JavaScript</span><span class=\"code-actions\"><button type=\"button\" class=\"code-btn code-run\" data-action=\"run\" data-lang=\"js\" data-index=\"0\" aria-label=\"شغّل هذا المثال\"><svg class=\"icon \" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\" aria-hidden=\"true\" focusable=\"false\"><path d=\"M5 5a2 2 0 0 1 3.008-1.728l11.997 6.998a2 2 0 0 1 .003 3.458l-12 7A2 2 0 0 1 5 19z\"/></svg><span>تشغيل</span></button><button type=\"button\" class=\"code-btn\" data-action=\"copy\" aria-label=\"نسخ الكود\"><svg class=\"icon \" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\" aria-hidden=\"true\" focusable=\"false\"><rect width=\"14\" height=\"14\" x=\"8\" y=\"8\" rx=\"2\" ry=\"2\"/><path d=\"M4 16c-1.1 0-2-.9-2-2V4c0-1.1.9-2 2-2h10c1.1 0 2 .9 2 2\"/></svg><span>نسخ</span></button></span></div><pre class=\"shiki\" tabindex=\"0\"><code><span class=\"line\"><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">const</span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\"> expenses</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\"> =</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> [</span></span>\n<span class=\"line\"><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">  { label: </span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\">\"Coffee\"</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">, amount: </span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\">450</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">, category: </span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\">\"food\"</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> },</span></span>\n<span class=\"line\"><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">  { label: </span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\">\"Train\"</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">, amount: </span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\">1220</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">, category: </span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\">\"travel\"</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> },</span></span>\n<span class=\"line\"><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">  { label: </span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\">\"Lunch\"</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">, amount: </span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\">1350</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">, category: </span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\">\"food\"</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> },</span></span>\n<span class=\"line\"><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">];</span></span>\n<span class=\"line\"></span>\n<span class=\"line\"><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">const</span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\"> total</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\"> =</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> expenses.</span><span style=\"color:#8250DF;--shiki-dark:#D2A8FF\">reduce</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">((</span><span style=\"color:#953800;--shiki-dark:#FFA657\">sum</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">, </span><span style=\"color:#953800;--shiki-dark:#FFA657\">e</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">) </span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">=></span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> sum </span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">+</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> e.amount, </span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\">0</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">);</span></span>\n<span class=\"line\"><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">console.</span><span style=\"color:#8250DF;--shiki-dark:#D2A8FF\">log</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">(total);</span></span></code></pre><div class=\"run-output\" data-run-output=\"0\" hidden></div></div>\n<p>تأخذ <code dir=\"ltr\">reduce</code> وسيطين: callback و<strong>قيمة ابتدائية</strong> (هنا <code dir=\"ltr\">0</code>). وتستدعي الـ callback مرة لكل عنصر، مع <strong>المُراكِم</strong> (accumulator) الحالي (<code dir=\"ltr\">sum</code>) والعنصر (<code dir=\"ltr\">e</code>). وكل ما يُرجعه الـ callback يصبح المُراكِم في الاستدعاء التالي. وبعد العنصر الأخير، يكون المُراكِم النهائي هو النتيجة.</p>\n<figure class=\"diagram\"><svg class=\"diagram-svg\" focusable=\"false\" viewBox=\"0 0 680 190\" role=\"img\" aria-labelledby=\"l-js-3-3-t1\"><defs><marker id=\"arrow-l-js-3-3-1\" viewBox=\"0 0 10 10\" refX=\"9\" refY=\"5\" markerWidth=\"7\" markerHeight=\"7\" orient=\"auto-start-reverse\"><path d=\"M0 0 L10 5 L0 10 z\" class=\"d-arrowhead\"/></marker></defs>\n  <title id=\"l-js-3-3-t1\">تبدأ reduce بالقيمة الابتدائية 0. الاستدعاء الأول يُرجع 0 زائد 450، أي 450؛ والثاني يُرجع 450 زائد 1220، أي 1670؛ والثالث يُرجع 1670 زائد 1350، أي 3020، وهي النتيجة النهائية.</title>\n  <rect class=\"d-box-accent\" x=\"10\" y=\"70\" width=\"80\" height=\"50\" rx=\"10\"/>\n  <text class=\"d-code\" x=\"50\" y=\"100\" text-anchor=\"middle\">0</text>\n  <text class=\"d-label-muted\" x=\"50\" y=\"146\" text-anchor=\"middle\">ابتدائية</text>\n  <rect class=\"d-box\" x=\"130\" y=\"60\" width=\"140\" height=\"70\" rx=\"10\"/>\n  <text class=\"d-code\" x=\"200\" y=\"90\" text-anchor=\"middle\">0 + 450</text>\n  <text class=\"d-label-muted\" x=\"200\" y=\"116\" text-anchor=\"middle\">Coffee</text>\n  <rect class=\"d-box\" x=\"310\" y=\"60\" width=\"150\" height=\"70\" rx=\"10\"/>\n  <text class=\"d-code\" x=\"385\" y=\"90\" text-anchor=\"middle\">450 + 1220</text>\n  <text class=\"d-label-muted\" x=\"385\" y=\"116\" text-anchor=\"middle\">Train</text>\n  <rect class=\"d-box\" x=\"500\" y=\"60\" width=\"150\" height=\"70\" rx=\"10\"/>\n  <text class=\"d-code\" x=\"575\" y=\"90\" text-anchor=\"middle\">1670 + 1350</text>\n  <text class=\"d-label-muted\" x=\"575\" y=\"116\" text-anchor=\"middle\">Lunch</text>\n  <path class=\"d-arrow\" d=\"M90 95 L126 95\" marker-end=\"url(#arrow-l-js-3-3-1)\"/>\n  <path class=\"d-arrow\" d=\"M270 95 L306 95\" marker-end=\"url(#arrow-l-js-3-3-1)\"/>\n  <path class=\"d-arrow\" d=\"M460 95 L496 95\" marker-end=\"url(#arrow-l-js-3-3-1)\"/>\n  <rect class=\"d-box-success\" x=\"530\" y=\"150\" width=\"90\" height=\"34\" rx=\"8\"/>\n  <text class=\"d-code\" x=\"575\" y=\"172\" text-anchor=\"middle\">3020</text>\n  <path class=\"d-arrow\" d=\"M575 130 L575 146\" marker-end=\"url(#arrow-l-js-3-3-1)\"/>\n  <text class=\"d-label-muted\" x=\"200\" y=\"40\" text-anchor=\"middle\">تُرجع 450</text>\n  <text class=\"d-label-muted\" x=\"385\" y=\"40\" text-anchor=\"middle\">تُرجع 1670</text>\n</svg><figcaption>reduce تمرّر المُراكِم من استدعاء إلى الذي يليه</figcaption></figure>\n<p>مرّر القيمة الابتدائية دائمًا. فدونها تستخدم <code dir=\"ltr\">reduce</code> العنصر الأول مُراكِمًا ابتدائيًا، وهو هنا كائن مصروف كامل لا رقم، ومع القائمة الفارغة ترمي <code dir=\"ltr\">TypeError: Reduce of empty array with no initial value</code>.</p>\n<h3 id=\"sec-2\" tabindex=\"-1\">التجميع: reduce إلى كائن</h3>\n<p>يمكن أن يكون المُراكِم أي شيء، بما في ذلك كائن. وهكذا تبني مجاميع لكل فئة:</p>\n<div class=\"code-block\" dir=\"ltr\" data-lang=\"js\" data-run=\"js\" data-index=\"1\"><div class=\"code-head\"><span class=\"code-title\">JavaScript</span><span class=\"code-actions\"><button type=\"button\" class=\"code-btn code-run\" data-action=\"run\" data-lang=\"js\" data-index=\"1\" aria-label=\"شغّل هذا المثال\"><svg class=\"icon \" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\" aria-hidden=\"true\" focusable=\"false\"><path d=\"M5 5a2 2 0 0 1 3.008-1.728l11.997 6.998a2 2 0 0 1 .003 3.458l-12 7A2 2 0 0 1 5 19z\"/></svg><span>تشغيل</span></button><button type=\"button\" class=\"code-btn\" data-action=\"copy\" aria-label=\"نسخ الكود\"><svg class=\"icon \" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\" aria-hidden=\"true\" focusable=\"false\"><rect width=\"14\" height=\"14\" x=\"8\" y=\"8\" rx=\"2\" ry=\"2\"/><path d=\"M4 16c-1.1 0-2-.9-2-2V4c0-1.1.9-2 2-2h10c1.1 0 2 .9 2 2\"/></svg><span>نسخ</span></button></span></div><pre class=\"shiki\" tabindex=\"0\"><code><span class=\"line\"><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">const</span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\"> expenses</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\"> =</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> [</span></span>\n<span class=\"line\"><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">  { label: </span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\">\"Coffee\"</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">, amount: </span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\">450</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">, category: </span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\">\"food\"</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> },</span></span>\n<span class=\"line\"><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">  { label: </span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\">\"Train\"</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">, amount: </span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\">1220</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">, category: </span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\">\"travel\"</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> },</span></span>\n<span class=\"line\"><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">  { label: </span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\">\"Lunch\"</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">, amount: </span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\">1350</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">, category: </span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\">\"food\"</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> },</span></span>\n<span class=\"line\"><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">];</span></span>\n<span class=\"line\"></span>\n<span class=\"line\"><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">const</span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\"> byCategory</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\"> =</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> expenses.</span><span style=\"color:#8250DF;--shiki-dark:#D2A8FF\">reduce</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">((</span><span style=\"color:#953800;--shiki-dark:#FFA657\">totals</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">, </span><span style=\"color:#953800;--shiki-dark:#FFA657\">e</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">) </span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">=></span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> {</span></span>\n<span class=\"line\"><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">  totals[e.category] </span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">=</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> (totals[e.category] </span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">??</span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\"> 0</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">) </span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">+</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> e.amount;</span></span>\n<span class=\"line\"><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">  return</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> totals;</span></span>\n<span class=\"line\"><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">}, {});</span></span>\n<span class=\"line\"></span>\n<span class=\"line\"><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">console.</span><span style=\"color:#8250DF;--shiki-dark:#D2A8FF\">log</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">(byCategory);</span></span></code></pre><div class=\"run-output\" data-run-output=\"1\" hidden></div></div>\n<p>مع كل مصروف، اقرأ المجموع الجاري لفئته (أو 0 إن كانت هذه أول مرة تظهر فيها الفئة؛ وللعامل <code dir=\"ltr\">??</code> شرح خاص في الدرس التالي)، وأضف المبلغ، وخزّنه من جديد بالأقواس المربّعة، لأن اسم الخاصية في متغيّر. ثم أرجع الكائن نفسه كي يستلمه الاستدعاء التالي.</p>\n<aside class=\"callout callout-mistake\" role=\"note\"><p class=\"callout-title\"><svg class=\"icon \" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\" aria-hidden=\"true\" focusable=\"false\"><path d=\"m21.73 18-8-14a2 2 0 0 0-3.48 0l-8 14A2 2 0 0 0 4 21h16a2 2 0 0 0 1.73-3\"/><path d=\"M12 9v4\"/><path d=\"M12 17h.01\"/></svg><span><span class=\"callout-kind\">خطأ شائع</span><span class=\"callout-sep\" aria-hidden=\"true\"> · </span>نسيان إرجاع المُراكِم</span></p>\n<p>مع الأقواس المعقوفة في الـ callback، يجب أن يكون السطر الأخير <code dir=\"ltr\">return totals;</code>. انسَه فيستلم الاستدعاء التالي <code dir=\"ltr\">undefined</code> مُراكِمًا، ويرمي الـ callback الخطأ <code dir=\"ltr\">TypeError: Cannot read properties of undefined</code> بمجرّد أن يقرأ <code dir=\"ltr\">totals[e.category]</code>. حين تسيء <code dir=\"ltr\">reduce</code> التصرّف، افحص الـ return أولًا.</p>\n</aside>\n<p>وحين تريد تجميع المصاريف نفسها بدلًا من مجموع لكل مجموعة، فهناك اختصار مدمج منذ عام 2024: <code dir=\"ltr\">Object.groupBy(expenses, (e) =&gt; e.category)</code> تُرجع كائنًا خصائصه هي الفئات، وقيمه مصفوفات المصاريف المطابقة. إنها مدعومة في كل المتصفّحات الحالية وفي Node.js، وتُقرأ أفضل من مقابلها المكتوب بـ <code dir=\"ltr\">reduce</code>. أما المجاميع فما زالت تحتاج إلى <code dir=\"ltr\">reduce</code> (أو حلقة تمرّ على المجموعات).</p>\n<p><code dir=\"ltr\">reduce</code> قوية، وهذا سبب لاستخدامها باعتدال. إن كانت <code dir=\"ltr\">map</code> أو <code dir=\"ltr\">filter</code> أو حلقة <code dir=\"ltr\">for...of</code> قصيرة تقول الشيء نفسه بوضوح أكبر، ففضّلها. المجاميع والتجميع هي المواضع التي تُقرأ فيها <code dir=\"ltr\">reduce</code> أفضل ما يكون.</p>\n<h2 id=\"sec-3\" tabindex=\"-1\">sort: وضع الأشياء في ترتيبها</h2>\n<p>يبدو الترتيب سهلًا، لكنه يحتوي على أشهر فخّ في JavaScript:</p>\n<div class=\"code-block\" dir=\"ltr\" data-lang=\"js\" data-run=\"js\" data-index=\"2\"><div class=\"code-head\"><span class=\"code-title\">JavaScript</span><span class=\"code-actions\"><button type=\"button\" class=\"code-btn code-run\" data-action=\"run\" data-lang=\"js\" data-index=\"2\" aria-label=\"شغّل هذا المثال\"><svg class=\"icon \" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\" aria-hidden=\"true\" focusable=\"false\"><path d=\"M5 5a2 2 0 0 1 3.008-1.728l11.997 6.998a2 2 0 0 1 .003 3.458l-12 7A2 2 0 0 1 5 19z\"/></svg><span>تشغيل</span></button><button type=\"button\" class=\"code-btn\" data-action=\"copy\" aria-label=\"نسخ الكود\"><svg class=\"icon \" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\" aria-hidden=\"true\" focusable=\"false\"><rect width=\"14\" height=\"14\" x=\"8\" y=\"8\" rx=\"2\" ry=\"2\"/><path d=\"M4 16c-1.1 0-2-.9-2-2V4c0-1.1.9-2 2-2h10c1.1 0 2 .9 2 2\"/></svg><span>نسخ</span></button></span></div><pre class=\"shiki\" tabindex=\"0\"><code><span class=\"line\"><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">const</span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\"> amounts</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\"> =</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> [</span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\">450</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">, </span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\">1220</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">, </span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\">99</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">];</span></span>\n<span class=\"line\"><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">amounts.</span><span style=\"color:#8250DF;--shiki-dark:#D2A8FF\">sort</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">();</span></span>\n<span class=\"line\"><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">console.</span><span style=\"color:#8250DF;--shiki-dark:#D2A8FF\">log</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">(amounts);</span></span></code></pre><div class=\"run-output\" data-run-output=\"2\" hidden></div></div>\n<p>دون تعليمات، تحوّل <code dir=\"ltr\">sort</code> كل عنصر إلى نص وترتّبها كنصوص، مثل الكلمات في القاموس. وبصفتها نصوصًا، تأتي “1220” قبل “450” لأن الحرف “1” يأتي قبل “4”. هذا جيد للكلمات وخاطئ للأرقام.</p>\n<p>والحل <strong>دالة مقارنة</strong> (comparator): دالة تستلم عنصرين، <code dir=\"ltr\">a</code> و<code dir=\"ltr\">b</code>، وتُرجع رقمًا يقول أيّهما يأتي أولًا.</p>\n<div class=\"table-wrap\" role=\"region\" aria-label=\"جدول قابل للتمرير\" tabindex=\"0\"><table><thead>\n<tr>\n<th>ما تُرجعه دالة المقارنة</th>\n<th>النتيجة</th>\n</tr>\n</thead>\n<tbody>\n<tr>\n<td>رقم سالب</td>\n<td><code dir=\"ltr\">a</code> تأتي قبل <code dir=\"ltr\">b</code></td>\n</tr>\n<tr>\n<td>رقم موجب</td>\n<td><code dir=\"ltr\">b</code> تأتي قبل <code dir=\"ltr\">a</code></td>\n</tr>\n<tr>\n<td><code dir=\"ltr\">0</code></td>\n<td>يبقى ترتيبهما الحالي</td>\n</tr>\n</tbody>\n</table></div><p>ومع الأرقام، الطرح يُنتج هذا بالضبط:</p>\n<div class=\"code-block\" dir=\"ltr\" data-lang=\"js\" data-run=\"js\" data-index=\"3\"><div class=\"code-head\"><span class=\"code-title\">JavaScript</span><span class=\"code-actions\"><button type=\"button\" class=\"code-btn code-run\" data-action=\"run\" data-lang=\"js\" data-index=\"3\" aria-label=\"شغّل هذا المثال\"><svg class=\"icon \" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\" aria-hidden=\"true\" focusable=\"false\"><path d=\"M5 5a2 2 0 0 1 3.008-1.728l11.997 6.998a2 2 0 0 1 .003 3.458l-12 7A2 2 0 0 1 5 19z\"/></svg><span>تشغيل</span></button><button type=\"button\" class=\"code-btn\" data-action=\"copy\" aria-label=\"نسخ الكود\"><svg class=\"icon \" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\" aria-hidden=\"true\" focusable=\"false\"><rect width=\"14\" height=\"14\" x=\"8\" y=\"8\" rx=\"2\" ry=\"2\"/><path d=\"M4 16c-1.1 0-2-.9-2-2V4c0-1.1.9-2 2-2h10c1.1 0 2 .9 2 2\"/></svg><span>نسخ</span></button></span></div><pre class=\"shiki\" tabindex=\"0\"><code><span class=\"line\"><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">const</span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\"> amounts</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\"> =</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> [</span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\">450</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">, </span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\">1220</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">, </span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\">99</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">];</span></span>\n<span class=\"line\"><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">console.</span><span style=\"color:#8250DF;--shiki-dark:#D2A8FF\">log</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">([</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">...</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">amounts].</span><span style=\"color:#8250DF;--shiki-dark:#D2A8FF\">sort</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">((</span><span style=\"color:#953800;--shiki-dark:#FFA657\">a</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">, </span><span style=\"color:#953800;--shiki-dark:#FFA657\">b</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">) </span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">=></span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> a </span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">-</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> b));  </span><span style=\"color:#6E7781;--shiki-dark:#8B949E\">// ascending</span></span>\n<span class=\"line\"><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">console.</span><span style=\"color:#8250DF;--shiki-dark:#D2A8FF\">log</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">([</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">...</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">amounts].</span><span style=\"color:#8250DF;--shiki-dark:#D2A8FF\">sort</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">((</span><span style=\"color:#953800;--shiki-dark:#FFA657\">a</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">, </span><span style=\"color:#953800;--shiki-dark:#FFA657\">b</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">) </span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">=></span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> b </span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">-</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> a));  </span><span style=\"color:#6E7781;--shiki-dark:#8B949E\">// descending</span></span></code></pre><div class=\"run-output\" data-run-output=\"3\" hidden></div></div>\n<p>حين تكون <code dir=\"ltr\">a</code> تساوي 450 و<code dir=\"ltr\">b</code> تساوي 99، يكون <code dir=\"ltr\">a - b</code> موجبًا، فتنتقل 99 إلى الأمام. واقلبها إلى <code dir=\"ltr\">b - a</code> ليأتي الأكبر أولًا.</p>\n<p>النصوص تحتاج إلى مقارنة خاصة بها، لأن <code dir=\"ltr\">&lt;</code> تقارن رموز الحروف وتضع كل حرف كبير قبل كل حرف صغير. أما <code dir=\"ltr\">localeCompare</code> فتقارن كما يفعل قاموس لغة حقيقية:</p>\n<div class=\"code-block\" dir=\"ltr\" data-lang=\"js\" data-run=\"js\" data-index=\"4\"><div class=\"code-head\"><span class=\"code-title\">JavaScript</span><span class=\"code-actions\"><button type=\"button\" class=\"code-btn code-run\" data-action=\"run\" data-lang=\"js\" data-index=\"4\" aria-label=\"شغّل هذا المثال\"><svg class=\"icon \" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\" aria-hidden=\"true\" focusable=\"false\"><path d=\"M5 5a2 2 0 0 1 3.008-1.728l11.997 6.998a2 2 0 0 1 .003 3.458l-12 7A2 2 0 0 1 5 19z\"/></svg><span>تشغيل</span></button><button type=\"button\" class=\"code-btn\" data-action=\"copy\" aria-label=\"نسخ الكود\"><svg class=\"icon \" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\" aria-hidden=\"true\" focusable=\"false\"><rect width=\"14\" height=\"14\" x=\"8\" y=\"8\" rx=\"2\" ry=\"2\"/><path d=\"M4 16c-1.1 0-2-.9-2-2V4c0-1.1.9-2 2-2h10c1.1 0 2 .9 2 2\"/></svg><span>نسخ</span></button></span></div><pre class=\"shiki\" tabindex=\"0\"><code><span class=\"line\"><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">const</span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\"> labels</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\"> =</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> [</span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\">\"lunch\"</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">, </span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\">\"Train\"</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">, </span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\">\"coffee\"</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">, </span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\">\"Éclair\"</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">];</span></span>\n<span class=\"line\"><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">console.</span><span style=\"color:#8250DF;--shiki-dark:#D2A8FF\">log</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">(labels.</span><span style=\"color:#8250DF;--shiki-dark:#D2A8FF\">toSorted</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">((</span><span style=\"color:#953800;--shiki-dark:#FFA657\">a</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">, </span><span style=\"color:#953800;--shiki-dark:#FFA657\">b</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">) </span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">=></span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> a.</span><span style=\"color:#8250DF;--shiki-dark:#D2A8FF\">localeCompare</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">(b)));</span></span></code></pre><div class=\"run-output\" data-run-output=\"4\" hidden></div></div>\n<p>والكائنات تُرتَّب حسب إحدى خصائصها، بالأنماط نفسها: <code dir=\"ltr\">(a, b) =&gt; b.amount - a.amount</code> ليأتي الأغلى أولًا، و<code dir=\"ltr\">(a, b) =&gt; a.label.localeCompare(b.label)</code> للترتيب الأبجدي.</p>\n<h3 id=\"sec-4\" tabindex=\"-1\">sort تغيّر الأصل</h3>\n<p>تعيد <code dir=\"ltr\">sort</code> ترتيب المصفوفة <strong>في مكانها</strong> وتُرجع المصفوفة نفسها. فإن كان كود آخر يقرأ القائمة، ربما ليعرض المصاريف بترتيب إضافتها، فقد صارت مرتّبة عنده أيضًا. ولهذا ترتّب الأمثلة أعلاه نسخة، <code dir=\"ltr\">[...amounts]</code>. أما الدالة الأحدث <code dir=\"ltr\">toSorted</code> (جزء من JavaScript منذ عام 2023، ومدعومة في كل المتصفّحات الحالية) فتُرجع نسخة مرتّبة مباشرة، فلا تحتاج إلى تذكّر خطوة النسخ:</p>\n<div class=\"code-block\" dir=\"ltr\" data-lang=\"js\" data-run=\"js\" data-index=\"5\"><div class=\"code-head\"><span class=\"code-title\">JavaScript</span><span class=\"code-actions\"><button type=\"button\" class=\"code-btn code-run\" data-action=\"run\" data-lang=\"js\" data-index=\"5\" aria-label=\"شغّل هذا المثال\"><svg class=\"icon \" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\" aria-hidden=\"true\" focusable=\"false\"><path d=\"M5 5a2 2 0 0 1 3.008-1.728l11.997 6.998a2 2 0 0 1 .003 3.458l-12 7A2 2 0 0 1 5 19z\"/></svg><span>تشغيل</span></button><button type=\"button\" class=\"code-btn\" data-action=\"copy\" aria-label=\"نسخ الكود\"><svg class=\"icon \" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\" aria-hidden=\"true\" focusable=\"false\"><rect width=\"14\" height=\"14\" x=\"8\" y=\"8\" rx=\"2\" ry=\"2\"/><path d=\"M4 16c-1.1 0-2-.9-2-2V4c0-1.1.9-2 2-2h10c1.1 0 2 .9 2 2\"/></svg><span>نسخ</span></button></span></div><pre class=\"shiki\" tabindex=\"0\"><code><span class=\"line\"><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">const</span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\"> expenses</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\"> =</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> [</span></span>\n<span class=\"line\"><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">  { label: </span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\">\"Coffee\"</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">, amount: </span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\">450</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> },</span></span>\n<span class=\"line\"><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">  { label: </span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\">\"Train\"</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">, amount: </span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\">1220</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> },</span></span>\n<span class=\"line\"><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">  { label: </span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\">\"Lunch\"</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">, amount: </span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\">1350</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> },</span></span>\n<span class=\"line\"><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">];</span></span>\n<span class=\"line\"><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">const</span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\"> biggestFirst</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\"> =</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> expenses.</span><span style=\"color:#8250DF;--shiki-dark:#D2A8FF\">toSorted</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">((</span><span style=\"color:#953800;--shiki-dark:#FFA657\">a</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">, </span><span style=\"color:#953800;--shiki-dark:#FFA657\">b</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">) </span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">=></span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> b.amount </span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">-</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> a.amount);</span></span>\n<span class=\"line\"><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">console.</span><span style=\"color:#8250DF;--shiki-dark:#D2A8FF\">log</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">(biggestFirst.</span><span style=\"color:#8250DF;--shiki-dark:#D2A8FF\">map</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">((</span><span style=\"color:#953800;--shiki-dark:#FFA657\">e</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">) </span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">=></span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> e.label), expenses.</span><span style=\"color:#8250DF;--shiki-dark:#D2A8FF\">map</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">((</span><span style=\"color:#953800;--shiki-dark:#FFA657\">e</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">) </span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">=></span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> e.label));</span></span></code></pre><div class=\"run-output\" data-run-output=\"5\" hidden></div></div>\n<aside class=\"callout callout-tip\" role=\"note\"><p class=\"callout-title\"><svg class=\"icon \" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\" aria-hidden=\"true\" focusable=\"false\"><path d=\"M15 14c.2-1 .7-1.7 1.5-2.5 1-.9 1.5-2.2 1.5-3.5A6 6 0 0 0 6 8c0 1 .2 2.2 1.5 3.5.7.7 1.3 1.5 1.5 2.5\"/><path d=\"M9 18h6\"/><path d=\"M10 22h4\"/></svg><span><span class=\"callout-kind\">نصيحة</span><span class=\"callout-sep\" aria-hidden=\"true\"> · </span>اجعل الخيار الافتراضي عدم التعديل في المكان</span></p>\n<p>في كود هذه الدورة، وفي معظم المشاريع الحديثة، الخيار الافتراضي هو إنتاج مصفوفات جديدة: <code dir=\"ltr\">map</code> و<code dir=\"ltr\">filter</code> و<code dir=\"ltr\">toSorted</code> والنشر. ولا تلجأ إلى الدوال التي تعدّل في المكان إلا حين تملك المصفوفة ولا يقرؤها أحد غيرك. هذا يُزيل فئة كاملة من أخطاء “من غيّر بياناتي؟”.</p>\n</aside>\n<p>في الدرس التالي تتعامل مع بيانات غير موجودة أصلًا: خصائص مفقودة، وقيم فارغة، ومجموعتان، Map وSet، صُمّمتا للبحث السريع ولضمان عدم التكرار.</p>\n","toc":[{"id":"sec-1","text":"reduce: قيم كثيرة تصبح قيمة واحدة","level":2},{"id":"sec-2","text":"التجميع: reduce إلى كائن","level":3},{"id":"sec-3","text":"sort: وضع الأشياء في ترتيبها","level":2},{"id":"sec-4","text":"sort تغيّر الأصل","level":3}],"takeaways":["<code dir=\"ltr\">reduce</code> تمرّ على القائمة حاملةً مُراكِمًا (accumulator)؛ والـ callback يُرجع المُراكِم التالي، والمُراكِم الأخير هو النتيجة.","مرّر دائمًا قيمة ابتدائية إلى <code dir=\"ltr\">reduce</code>، مثل <code dir=\"ltr\">0</code> للمجاميع أو <code dir=\"ltr\">{}</code> للتجميع؛ فدونها ترمي القائمة الفارغة خطأ.","دون دالة مقارنة، تقارن <code dir=\"ltr\">sort</code> العناصر على أنها نصوص، فتُرتَّب <code dir=\"ltr\">[450, 1220, 99]</code> لتصبح <code dir=\"ltr\">[1220, 450, 99]</code>.","دالة المقارنة تُرجع رقمًا سالبًا لتضع <code dir=\"ltr\">a</code> أولًا، وموجبًا لتضع <code dir=\"ltr\">b</code> أولًا؛ و<code dir=\"ltr\">(a, b) =&gt; a - b</code> ترتّب الأرقام تصاعديًا.","<code dir=\"ltr\">sort</code> تعدّل المصفوفة في مكانها؛ استخدم <code dir=\"ltr\">toSorted</code> (أو رتّب نسخة) حين يجب أن يبقى الأصل كما هو."],"further":[{"title":"Array.prototype.reduce() (MDN)","url":"https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/reduce"},{"title":"Array.prototype.sort() (MDN)","url":"https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/sort"},{"title":"Array.prototype.toSorted() (MDN)","url":"https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/toSorted"}],"quiz":[{"q":"<p>ماذا تُرجع <code dir=\"ltr\">[450, 1220, 99].sort()</code>؟</p>","options":[{"text":"<code dir=\"ltr\">[99, 450, 1220]</code>","why":"هذا يحتاج إلى دالة مقارنة رقمية. الترتيب الافتراضي يحوّل كل رقم إلى نص ويقارن النصوص."},{"text":"<code dir=\"ltr\">[1220, 450, 99]</code>","why":"صحيح. بصفتها نصوصًا، تأتي “1220” قبل “450” لأن “1” تأتي قبل “4”، وتأتي “450” قبل “99” لأن “4” تأتي قبل “9”."},{"text":"<code dir=\"ltr\">[1220, 99, 450]</code>","why":"النصوص تُقارن حرفًا حرفًا من اليسار، لذلك تُرتَّب “450” قبل “99”."}],"answer":1},{"q":"<p>أيّ دالة مقارنة ترتّب المصاريف من الأغلى إلى الأرخص؟</p>","options":[{"text":"<code dir=\"ltr\">(a, b) =&gt; a.amount - b.amount</code>","why":"هذه تُرجع رقمًا سالبًا حين تكون <code dir=\"ltr\">a</code> أرخص، فتضع الأرخص أولًا. وهذا ترتيب تصاعدي."},{"text":"<code dir=\"ltr\">(a, b) =&gt; a.amount &gt; b.amount</code>","why":"هذه تُرجع قيمة منطقية لا رقمًا. ودوال المقارنة يجب أن تُرجع قيمة سالبة أو صفرًا أو موجبة؛ والقيم المنطقية تعطي نتائج غير موثوقة."},{"text":"<code dir=\"ltr\">(a, b) =&gt; b.amount - a.amount</code>","why":"صحيح. حين تكون <code dir=\"ltr\">b</code> أغلى، يكون الناتج موجبًا، فتوضع <code dir=\"ltr\">b</code> قبل <code dir=\"ltr\">a</code>. فتأتي المبالغ الأكبر أولًا."}],"answer":2},{"q":"<p>استُدعيت <code dir=\"ltr\">[].reduce((sum, e) =&gt; sum + e.amount)</code> على قائمة فارغة. ماذا يحدث؟</p>","options":[{"text":"ترمي TypeError، لأنه لا توجد قيمة ابتدائية ولا عنصر أول تبدأ منه.","why":"صحيح. تمرير قيمة ابتدائية، <code dir=\"ltr\">reduce(fn, 0)</code>، يجعلها تُرجع 0 للقائمة الفارغة بدلًا من ذلك."},{"text":"تُرجع 0.","why":"فقط إن مرّرت 0 قيمةً ابتدائية. دونها لا تجد <code dir=\"ltr\">reduce</code> ما تبدأ به المُراكِم."},{"text":"تُرجع <code dir=\"ltr\">undefined</code>.","why":"<code dir=\"ltr\">reduce</code> لا تُرجع <code dir=\"ltr\">undefined</code> بهدوء هنا؛ بل ترمي “Reduce of empty array with no initial value”."}],"answer":0},{"q":"<p>تعرض المصاريف مرتّبةً حسب المبلغ في لوحة، وبترتيب إضافتها في لوحة أخرى. واللوحتان تقرآن المصفوفة نفسها <code dir=\"ltr\">expenses</code>. ما الذي يفسد مع <code dir=\"ltr\">expenses.sort(byAmount)</code>؟</p>","options":[{"text":"لا شيء، لأن <code dir=\"ltr\">sort</code> تُرجع نسخة مرتّبة.","why":"<code dir=\"ltr\">sort</code> تُرجع المصفوفة نفسها التي رتّبتها. إنها تعيد ترتيب الأصل في مكانه."},{"text":"تصبح لوحة “بترتيب الإضافة” مرتّبةً هي الأخرى، لأن <code dir=\"ltr\">sort</code> عدّلت المصفوفة المشتركة.","why":"صحيح. استخدم <code dir=\"ltr\">expenses.toSorted(byAmount)</code> أو <code dir=\"ltr\">[...expenses].sort(byAmount)</code> لترتيب نسخة."},{"text":"ترمي خطأ، لأن المصفوفة مُصرَّح بها بـ <code dir=\"ltr\">const</code>.","why":"<code dir=\"ltr\">const</code> لا تمنع تغيير محتويات المصفوفة، والترتيب لا يفعل إلا إعادة ترتيب المحتويات."}],"answer":1}],"exercise":{"kind":"web","mode":"js","starter":{"js":"const expenses = [\n  { label: \"coffee\", amount: 450, category: \"food\" },\n  { label: \"Train\", amount: 1220, category: \"travel\" },\n  { label: \"Lunch\", amount: 1350, category: \"food\" },\n  { label: \"Museum\", amount: 99, category: \"fun\" },\n];\n\n// Sum of all amounts (0 for an empty list)\nfunction totalCents(expenses) {\n  return 0;\n}\n\n// An object like { food: 1800, travel: 1220, fun: 99 }\nfunction totalsByCategory(expenses) {\n  return {};\n}\n\n// A NEW array, most expensive first; do not reorder the original\nfunction biggestFirst(expenses) {\n  return expenses.sort();\n}\n\n// A NEW array sorted by label, A to Z, ignoring upper/lower case\nfunction alphabetical(expenses) {\n  return expenses;\n}\n\nconsole.log(totalCents(expenses), totalsByCategory(expenses));\n"},"solution":{"js":"const expenses = [\n  { label: \"coffee\", amount: 450, category: \"food\" },\n  { label: \"Train\", amount: 1220, category: \"travel\" },\n  { label: \"Lunch\", amount: 1350, category: \"food\" },\n  { label: \"Museum\", amount: 99, category: \"fun\" },\n];\n\nfunction totalCents(expenses) {\n  return expenses.reduce((sum, e) => sum + e.amount, 0);\n}\n\nfunction totalsByCategory(expenses) {\n  return expenses.reduce((totals, e) => {\n    totals[e.category] = (totals[e.category] ?? 0) + e.amount;\n    return totals;\n  }, {});\n}\n\nfunction biggestFirst(expenses) {\n  return expenses.toSorted((a, b) => b.amount - a.amount);\n}\n\nfunction alphabetical(expenses) {\n  return expenses.toSorted((a, b) => a.label.localeCompare(b.label));\n}\n\nconsole.log(totalCents(expenses), totalsByCategory(expenses));\n"},"checks":[{"id":"total","test":"return totalCents(expenses) === 3119 && totalCents([]) === 0;\n"},{"id":"by-category","test":"const t = totalsByCategory(expenses);\nreturn t.food === 1800 && t.travel === 1220 && t.fun === 99 && Object.keys(t).length === 3;\n"},{"id":"biggest","test":"const out = biggestFirst(expenses);\nreturn Array.isArray(out) && out.map((e) => e.amount).join() === '1350,1220,450,99';\n"},{"id":"alpha","test":"const out = alphabetical(expenses);\nreturn Array.isArray(out) && out.map((e) => e.label).join() === 'coffee,Lunch,Museum,Train';\n"},{"id":"no-mutation","test":"const list = [{ label: 'b', amount: 1 }, { label: 'a', amount: 2 }];\nconst a = biggestFirst(list);\nconst b = alphabetical(list);\nreturn list[0].label === 'b' && a !== list && b !== list;\n"}],"title":"المجاميع والترتيب","prompt":"<p>أكمل أربع دوال لشاشة الملخّص في Pocket:</p>\n<ul>\n<li><code dir=\"ltr\">totalCents(expenses)</code> تُرجع مجموع كل قيم <code dir=\"ltr\">amount</code> (و<code dir=\"ltr\">0</code> للقائمة الفارغة). استخدم <code dir=\"ltr\">reduce</code>.</li>\n<li><code dir=\"ltr\">totalsByCategory(expenses)</code> تُرجع كائنًا فيه خاصية لكل فئة تحمل مجموع تلك الفئة، مثل <code dir=\"ltr\">{ food: 1800, travel: 1220, fun: 99 }</code>.</li>\n<li><code dir=\"ltr\">biggestFirst(expenses)</code> تُرجع مصفوفة <strong>جديدة</strong> مرتّبة حسب <code dir=\"ltr\">amount</code>، الأكبر أولًا.</li>\n<li><code dir=\"ltr\">alphabetical(expenses)</code> تُرجع مصفوفة <strong>جديدة</strong> مرتّبة حسب <code dir=\"ltr\">label</code> من A إلى Z، وتعامل “coffee” و&quot;Coffee&quot; على أنهما متماثلتان.</li>\n</ul>\n<p>ولا يجوز لأيّ من دالتي الترتيب أن تعيد ترتيب المصفوفة التي تستلمها.</p>\n","hints":["<code dir=\"ltr\">reduce((sum, e) =&gt; sum + e.amount, 0)</code>: الـ <code dir=\"ltr\">0</code> في النهاية هي القيمة الابتدائية، وهي ما يجعل القائمة الفارغة تعمل.","للفئات، ابدأ من <code dir=\"ltr\">{}</code> وفي الـ callback اكتب <code dir=\"ltr\">totals[e.category] = (totals[e.category] ?? 0) + e.amount; return totals;</code>.","<code dir=\"ltr\">toSorted</code> تُرجع نسخة مرتّبة. الأكبر أولًا هو <code dir=\"ltr\">(a, b) =&gt; b.amount - a.amount</code>؛ وحسب التسمية هو <code dir=\"ltr\">(a, b) =&gt; a.label.localeCompare(b.label)</code>."],"explanation":"<p>المجموعان كلاهما نمط المُراكِم مكتوبًا بـ <code dir=\"ltr\">reduce</code>. والقيمة الابتدائية تحدّد شكل النتيجة: <code dir=\"ltr\">0</code> لرقم، و<code dir=\"ltr\">{}</code> لكائن مجاميع. وفي callback التجميع، تُنشئ الأقواس المربّعة خاصية كل فئة في أول مرة تظهر فيها، وإرجاع <code dir=\"ltr\">totals</code> يمرّر الكائن نفسه إلى الاستدعاء التالي. أما <code dir=\"ltr\">expenses.sort()</code> في كود البداية ففشلت مرتين: رتّبت الكائنات كنصوص (كل كائن يصبح النص “[object Object]”)، وأعادت ترتيب مصفوفة المُستدعي. و<code dir=\"ltr\">toSorted</code> مع دالة مقارنة تُصلح الأمرين. وتضع <code dir=\"ltr\">localeCompare</code> “coffee” قبل “Lunch”، بينما كانت <code dir=\"ltr\">&lt;</code> ستضع كل تسمية تبدأ بحرف كبير أولًا.</p>\n","checkLabels":{"total":"<code dir=\"ltr\">totalCents</code> تجمع المبالغ وتُرجع 0 للقائمة الفارغة","by-category":"<code dir=\"ltr\">totalsByCategory</code> تحسب مجموع كل فئة","biggest":"<code dir=\"ltr\">biggestFirst</code> ترتّب حسب المبلغ، الأكبر أولًا","alpha":"<code dir=\"ltr\">alphabetical</code> ترتّب حسب التسمية، متجاهلةً حالة الأحرف","no-mutation":"تحتفظ المصفوفة الأصلية بترتيبها"}},"words":652,"readMinutes":4}