یافتن مقادیر کمینه و بیشینه آرایه در جاوا اسکریپت – از صفر تا صد


جاوا اسکریپت چندین روش برای یافتن مقادیر کمینه و بیشینه در یک لیست ارائه کرده است که شامل تابعهای ریاضیاتی داخلی و مرتبسازی عددی آرایه میشود. در این مقاله به مقایسه عملکرد 5 روش مختلف با استفاده از jsPerf میپردازیم. گاهی اوقات لازم میشود که کمترین و بزرگترین مقادیر در یک آرایه مفروض از اعداد در جاوا اسکریپت محاسبه شوند و این کار باید به سرعت انجام شود. در این نوشته روشهای مختلف یافتن مقادیر کمینه و بیشینه آرایه در جاوا اسکریپت را بررسی خواهیم کرد.
چند روش داخلی برای یافتن مقادیر کمینه و بیشینه در یک آرایه در جاوا اسکریپت وجود دارند که شامل استفاده از تابعهای Math با عملگر اسپرد (...) و مرتبسازی عددی آرایه با استفاده از ()sort. میشود. در این مقاله طرز کار هر متد را توضیح میدهیم و سپس عملکرد 5 متد مختلف را برای یافتن مقادیر کمینه و بیشینه آرایهای از اعداد بررسی میکنیم.
استفاده از تابعهای ریاضیاتی
تابعهای ریاضیاتی داخلی جاوا اسکریپت به نام ()Math.min و ()Math.max دقیقاً آن چه را که ما میخواهیم انجام میدهند، یعنی کوچکترین یا بزرگترین عدد لیستی از اعداد را که به صورت آرگومان به آنها ارسال شده است بازگشت میدهند.
از آنجا که هر دو تابع ()Math.min و ()Math.max برای آرگومان مقدار عددی دریافت میکنند و نه یک آرایه، به عنوان نخستین گزینه برای دریافت کوچکترین و بزرگترین عدد یک آرایه مناسب به نظر نمیرسند:
1Math.min(1,3,5) // 1
2Math.min([1,3,5]) // NaN
در نگاه نخست به نظر میرسد که نیاز به رویکرد متفاوتی داریم. خوشبختانه با استفاده از یکی از قابلیتهای ES6 یعنی spread syntax (…) میتوانیم آرایهها را به راحتی با این تابعها سازگار کنیم. این موضوع در بخش بعدی بیشتر توضیح داده شده است.
Math.min(…array) و Math.max(…array)
عملگر اسپرد موجب میشود که مقادیر موجود در یک آرایه به آرگومانهای تابع بسط یابند. عملگر اسپرد (...) در جاوا اسکریپت میتواند یک آرایه از اعداد را به لیستی از آرگومانها بسط دهد و این حالت مانند ()Math.min و ()Math.max است:
1// Using the spread operator
2Math.min(...[1,3,5]) // 1
3Math.max(...[1,3,5]) // 5
با استفاده از این سه نقطه جادویی به راحتی میتوانیم هر تابعی را که مانند تابعهای ریاضیاتی داخلی جاوا اسکریپت آرگومانهای عددی میپذیرد، با استفاده از آرایه فراخوانی کنیم.
بدون استفاده از عملگر اسپرد
اگر میخواهید در برنامه خود از مرورگرهای قدیمیتر مانند اینترنت اکسپلورر نیز پشتیبانی کنید و از ابزاری مانند Babel برای کامپایل کردن کد جاوا اسکریپت به ساختارهای قدیمی با افزونهای مانند babel-plugin-transform-spread استفاده کنید، در این صورت عملگر اسپرد برای شما کار نمیکند. نمودار پشتیبانی مرورگرها برای عملکرد اسپرد در حال حاضر به صورت زیر است:
فراخوانی تابع ()Function.prototype.apply همان تأثیر ساختار اسپرد را دارد:
1// Using Function.prototype.apply() instead of the spread operator
2[1,3,5].min() // undefined
3Math.min([1,3,5]) // NaN
4Math.min.apply(null, [1,3,5]) // 1
5Math.max.apply(null, [1,3,5]) // 5
توجه داشته باشید که آرگومان اول ()apply. یک هدف this است که در این کد اهمیتی ندارد و از این رو مقدار null را به عنوان آرگومان اول ارسال میکنیم.
مرتبسازی عددی لیست
جاوا اسکریپت به صورت پیشفرض روی لیستها، مرتبسازی الفبایی انجام میدهد که در آن اعداد به رشته تبدیل میشوند و سپس به صورت الفبایی مرتب میشوند. ذخیرهسازی یک آرایه از اعداد به صورت عددی در جاوا اسکریپت از طریق ارسال عملگر مقایسه به تابع ()Array.prototype.sort انجام مییابد. برای نمونه کد زیر اعداد را به ترتیب نزولی از کوچک به بزرگ مرتبسازی میکند:
1.sort((a,b) => a-b)
زمانی که این کار انجام شد، عدد نخست min و عدد آخر max خواهد بود:
1const array = [37,-5,-15,-37,5,15]
2
3array.sort() // Default is lexicographical sort
4console.log(array.join(", ")) // -15, -37, -5, 15, 37, 5
5
6array.sort((a,b) => a-b) // Sort numerically, ascending
7console.log(array.join(", ")) // -37, -15, -5, 5, 15, 37
8
9const min = array[0]
10const max = array[array.length-1]
11console.log(`Minimum: ${min}, Maximum: ${max}`) // Minimum: -37, Maximum: 37
استفاده از حلقه for یا ()reduce.
هم حلقه for و هم متد ()Array.prototype.reduce میتوانند برای یافتن مقادیر کمینه و بیشینه در یک آرایه مورد استفاده قرار گیرند:
1const array = [37,-5,-15,-37,5,15]
2
3const forLoopMinMax = () => {
4 let min = array[0], max = array[0]
5
6 for (let i = 1; i < array.length; i++) {
7 let value = array[i]
8 min = (value < min) ? value : min
9 max = (value > max) ? value : max
10 }
11
12 return [min, max]
13}
14
15const [forLoopMin, forLoopMax] = forLoopMinMax()
16console.log(`Minimum: ${forLoopMin}, Maximum: ${forLoopMax}`) // Minimum: -37, Maximum: 37
17
18const minUsingReduce = () => array.reduce((min, currentValue) => Math.min(min, currentValue), array[0])
19const maxUsingReduce = () => array.reduce((max, currentValue) => Math.max(max, currentValue), array[0])
20console.log(`Minimum: ${minUsingReduce()}, Maximum: ${maxUsingReduce()}`) // Minimum: -37, Maximum: 37
در ادامه عملکرد 5 متد معرفی شده فوق را مورد بررسی قرار میدهیم.
تست عملکرد
ما برخی تستها را به کمک jsPerf (+) اجرا کردیم. jsPerf یک ابزار رایگان برای تست عملکرد کد جاوا اسکریپت در مرورگر است. هر نمونه کد مقدار کمینه و بیشینه یک آرایه از چهل عدد تصادفی را پیدا میکند. نتایج به صورت زیر هستند:
به دلایلی عملگر اسپرد نصف سرعت متدهای دیگر را دارد. از آنجا که استفاده از ()apply. با سرعت بالایی کار میکند، به نظر میرسد که استفاده از عملگر اسپرد روی 40 آرگومان واقعاً موجب کاهش سرعت میشود.
چنین به نظر میرسد که این مشکل ناشی از خود تابعهای ()Math.min یا ()Math.max نیست، چون حلقه for که از این تابعها استفاده نمیکند نیز اساس عملکرد یکسانی را در تست این 40 عدد تصادفی نشان میدهد.
بررسی مجدد با 4000 آیتم
ما تست خود را بهروزرسانی کردیم و این بار از 4000 آیتم آرایه برای بررسی تغییرات عملکردی یافتن مقادیر کمینه و بیشینه استفاده کردیم. در واقع این بار استفاده از عملگر اسپرد موجب کاهش سرعت 92 درصدی شد.
این امر نشان میدهد که افت عملکرد مرتبط با عملگر اسپرد به نحوی است که با افزایش تعداد اعداد بیشتر میشود.
هنگامی که از آرایه بسیار بزرگی استفاده میکنیم، حلقه for سریعترین راهحل است و ()apply در وهله دوم قرار دارد. ()sort و ()reduce در مکانهای سوم قرار میگیرند و اسپرد آخر میشود. لازم به ذکر است که استفاده از Babel برای کامپایل کد میتواند مقداری از عملکرد را برای آرایههای بسیار بزرگ بازیابی کند، زیرا عملگر اسپرد را به ساختار ()apply ترجمه میکند.
بررسی مجدد با تنها 3 آیتم
شما نباید بر مبنای نتایج فوق عملگر Spread را به کل فراموش کنید، چون وقتی از تنها سه آیتم در آرایه استفاده میکنیم تفاوت عملکردی چندانی مشاهده نمیشود.
بنابراین برای کاربردهای روزمره عملگر اسپرد کارکرد مناسبی دارد.
با این حال، زمانی که با آرایهای شامل تنها 3 آیتم سر و کار داریم، حلقه تکرار for سه درصد کندتر از عملگر اسپرد است.
سخن پایانی
چندین روش مختلف برای یافتن کوچکترین و بزرگترین مقادیر در یک آرایه جاوا اسکریپت وجود دارد و عملکرد هر روش بر اساس تعداد مقادیر موجود در آرایه متفاوت است. سادهترین روش استفاده از ساختار Math.min(...array) و Math.min(...array) است. در این روش از عملگر اسپرد ES6 برای بسط دادن آرایه به آرگومانهایی که تابعهای ریاضیاتی داخلی جاوا اسکریپت میپذیرند استفاده میشود. با این حال زمانی که با آرایهای به بزرگی 40 آیتم یا بیشتر سر و کار داریم، استفاده از عملگر اسپرد موجب افت عملکردی شدیدی در قیاس با دیگر روشهای یافتن کمینه و بیشینه میشود.
با آرایههای بزرگ چه بکنیم؟
استفاده از Math.min.apply(null,array) و Math.max.apply(null,array) روی آرایههای بزرگ موجب میشود که بخشی از عملکرد از دست رفته ناشی از عملگر اسپرد بازگردد و امکان ادامه استفاده از تابعهای ریاضیاتی داخلی جاوا اسکریپت فراهم آید. این در واقع همان کاری است که Babel با babel-plugin-transform-spread انجام میدهد، لذا کامپایل کردن کد جاوا اسکریپت به آرایههایی محدود به 40 آیتم کمک میکند. در نهایت در مورد آرایههای بسیار بزرگ مانند آرایههای دارای 4000 آیتم، حلقه for سریعترین روش است و گرچه زشت به نظر میرسد، اما کار میکند.
اگر این مطلب برای شما مفید بوده است، آموزشهای زیر نیز به شما پیشنهاد میشوند:
- مجموعه آموزشهای برنامهنویسی جاوا اسکریپت
- مجموعه آموزشهای برنامهنویسی
- آموزش جاوا اسکریپت (JavaScript)
- آموزش جاوا اسکریپت — مجموعه مقالات جامع وبلاگ فرادرس
- جاوا اسکریپت چیست؟ — به زبان ساده
==