۲۵ ژوئیه ۲۰۲۲

آرایه‌ها

شیءها به شما اجازه می‌دهند که مجموعه‌ای کلیددار از مقدارها را ذخیره کنید. این چیز خوبی است.

اما بسیار پیش می‌آید که ما به یک مجموعه‌ی مرتب نیاز داشته باشیم، که دارای یک المان اول، دوم، سوم و غیره باشیم. برای مثال، ما نیاز داریم که یک لیست از چیزی را ذخیره کنیم: کاربران، کالاها، المان‌های HTML و غیره.

اینکه اینجا از یک شیء استفاده کنیم خوب نیست، چون هیچ روشی برای کنترل کردن ترتیب المان‌ها فراهم نمی‌کند. ما نمی‌توانیم یک ویژگی جدید را «بین» ویژگی‌های جدید اضافه کنیم. شیءها برای چنین موردی ساخته نشده‌اند.

یک ساختار داده خاص به نام Array وجود دارد که برای ذخیره مجموعه‌های مرتب استفاده می‌شود.

تعریف کردن

برای ساخت یک آرایه خالی دو سینتکس وجود دارد:

let arr = new Array();
let arr = [];

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

let fruits = ["Apple", "Orange", "Plum"];

المان‌های آرایه عددگذاری شده‌اند که از صفر شروع می‌شود.

ما می‌توانیم یک المان را با استفاده از عددش درون براکت‌ها دریافت کنیم:

let fruits = ["Apple", "Orange", "Plum"];

alert( fruits[0] ); // Apple
alert( fruits[1] ); // Orange
alert( fruits[2] ); // Plum

می‌توانیم یک المان را جایگزین کنیم:

fruits[2] = 'Pear'; // now ["Apple", "Orange", "Pear"]

…یا یک المان جدید را به آرایه اضافه کنیم:

fruits[3] = 'Lemon'; // now ["Apple", "Orange", "Pear", "Lemon"]

تعداد کل المان‌های درون آرایه در length آن است:

let fruits = ["Apple", "Orange", "Plum"];

alert( fruits.length ); // 3

همچنین ما می‌توانیم از alert برای نشان دادن کل آرایه استفاده کنیم.

let fruits = ["Apple", "Orange", "Plum"];

alert( fruits ); // Apple,Orange,Plum

یک آرایه می‌تواند المان‌هایی از هر نوع را ذخیره کند.

برای مثال:

// ترکیبی از مقدارها
let arr = [ 'Apple', { name: 'John' }, true, function() { alert('hello'); } ];

// آن name دریافت شیء در ایندکس 1 و سپس نمایش
alert( arr[1].name ); // John

// دریافت تابع در ایندکس 3 و اجرا کردن آن
arr[3](); // hello
کامای دنباله‌دار

یک آرایه، درست مانند یک شیء، می‌تواند با یک کاما پایان یاید:

let fruits = [
  "Apple",
  "Orange",
  "Plum",
];

سبک «کامای دنباله‌دار» اضافه/حذف کردن المان را آسان‌تر می‌کند، چون همه خطوط مشابه می‌شوند.

دریافت آخرین المان‌ها با «at»

به تازگی اضافه شده است
این قسمت به تازگی به زبان اضافه شده است. مرورگرهای قدیمی ممکن است به پلیفیل نیاز داشته باشند.

فرض کنیم ما آخرین المان یک ارایه را می‌خواهیم.

بعضی از زبان‌های برنامه‌نویسی به ما اجازه می‌دهند که برای چنین اهدافی از ایندکس‌های منفی استفاده کنیم، مانند friuts[-1].

اگرچه این کار در جاوااسکریپت کار نمی‌کند. نتیجه undefined خواهد بود چون در براکت‌ها با ایندکس به صورت لفظی رفتار می‌شود.

ما می‌توانیم به صورت واضح ایندکس المان آخر را محاسبه کنیم و به آن دسترسی پیدا کنیم: fruits[fruits.length - 1].

let fruits = ["Apple", "Orange", "Plum"];

alert( fruits[fruits.length-1] ); // Plum

پ کمی زحمت دارد نه؟ ما باید اسم متغیر را دوبار بنویسیم.

خوشبختانه سینتکسی کوتاه‌تر وجود دارد: fruits.at(-1):

let fruits = ["Apple", "Orange", "Plum"];

// fruits[fruits.length-1] درست مانند
alert( fruits.at(-1) ); // Plum

به عبارتی دیگر، arr.at(i):

  • اگر i >= 0 باشد درست مانند arr[i] است.
  • برای مقدارهای منفی از i، از انتهای آرایه به عقب قدم برمی‌دارد.

متدهای pop/push، shift/unshift

یک صف یکی از متداول‌ترین استفاده‌ها از یک آرایه است. در علوم کامپیوتر، آرایه به معنای یک مجموعه مرتب‌شده از المان‌ها است که دو عملیات را پشتیبانی می‌کند:

  • push یک المان را به آخر اضافه می‌کند.
  • shift یک المان را از آغاز برمی‌دارد، صف را پیش می‌برد، پس المان دوم به المان اول تبدیل می‌شود.

آرایه‌ها هر دو عملیات را پشیبانی می‌کنند.

خیلی پیش می‌آید که در عمل به آن نیاز داشته باشیم. برای مثال، یک صف از پیام‌ها که باید روی صفحه نمایش داده شوند.

آرایه‌ها یک مورد استفاده دیگر هم دارند که یک ساختار داده به نام پشته است.

پشته دو عملیات را پشتیبانی می‌کند:

  • push یک المان را به آخر اضافه می‌کند.
  • pop یک المان را از آخر برمی‌دارد.

پس المان‌های جدید یا اضافه می‌شوند یا همیشه از «آخر» برداشته می‌شوند.

یک پشته معمولا به عنوان یک بسته‌ای از کارت‌ها فرض می‎شود: کارت‌های جدید به بالا اضافه می‌شوند یا از بالا برداشته می‌شوند:

برای پشته‌ها، آخرین چیزی که اضافه شده باشد اول دریافت می‌شود، همچنین به آن LIFO (Last-In-First-Out) هم گفته می‌شود. برای صف‌ها، ما FIFO (First-In-First-Out) را داریم.

آرایه‌ها در جاوااسکریپت می‌توانند هم به عنوان یک صف و هم به عنوان یک پشته کار کنند. آنها به شما اجازه می‌دهند که المان‌ها را به/از آغاز یا پایان اضافه/حذف کنید.

در علوم کامپیوتر ساختار داده‌ای که همچنین چیزی را ممکن می‌کند، صف دو طرفه نامیده می‌شود.

متدهایی که با انتهای آرایه کار می‌کنند:

pop

آخرین المان از آرایه را خارج می‌کند و آن را برمی‌گرداند:

let fruits = ["Apple", "Orange", "Pear"];

alert( fruits.pop() ); // می‌کند alert را حذف می‌کند و آن را "Pear"

alert( fruits ); // Apple, Orange

هر دوی fruits.pop() و fruits.at(-1) المان آخر آرایه را برمی‌گردانند اما fruits.pop() با حذف آن المان آرایه را تغییر می‌دهد.

push

المان را به انتهای آرایه اضافه می‌کند:

let fruits = ["Apple", "Orange"];

fruits.push("Pear");

alert( fruits ); // Apple, Orange, Pear

صدا زدن friuts.push(...) برابر است با fruits[fruits.length] = ....

متدهایی که با آغاز آرایه کار می‌کنند:

shift

اولین المان آرایه را خارج می‌کند و آن را برمی‌گرداند:

let fruits = ["Apple", "Orange", "Pear"];

alert( fruits.shift() ); // می‌کند alert را حذف می‌کند و آن را "Apple"

alert( fruits ); // Orange, Pear
unshift

المان را به آغاز آرایه اضافه می‌کند:

let fruits = ["Orange", "Pear"];

fruits.unshift('Apple');

alert( fruits ); // Apple, Orange, Pear

متدهای push و unshift می‌توانند چند المان را یک جا اضافه کنند:

let fruits = ["Apple"];

fruits.push("Orange", "Peach");
fruits.unshift("Pineapple", "Lemon");

// ["Pineapple", "Lemon", "Apple", "Orange", "Peach"]
alert( fruits );

اجزای داخلی

یک آرایه نوع خاصی از یک شیء است. براکت‌ها که برای دسترسی به یک ویژگی arr[0] استفاده می‌شوند در واقع از سینتکس شیء آمده‌اند. اساسا شبیه به obj[key] است، که در آن arr شیء است، درحالی که اعداد به عنوان کلیدها استفاده می‌شوند.

آنها شیءها را با فراهم کردن متدهای خاصی برای کارکردن با مجموعه‌های مرتب شده‌ی داده و ویژگی length گسترده می‌کنند. اما در ریشه و ذات هنوز یک شیء هستند.

به یاد داشته باشید، فقط 8 نوع داده ساده در جاوااسکریپت وجود دارد (برای اطلاعات بیشتر فصل انواع داده را ببینید). آرایه یک شیء است و به همین دلیل مانند یک شیء عمل می‌کند.

برای مثال، آرایه توسط مرجع کپی می‌شود:

let fruits = ["موز"]

let arr = fruits; کپی شدن توسط مرجع (دو متغیر به آرایه مشابهی رجوع می‌کنند)

alert( arr === fruits ); // true

arr.push("گلابی"); // تغییر دادن آرایه با استفاده از مرجع

alert( fruits ); // حال دارای 2 المان است - موز، گلابی

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

اما اگر ما از کار کردن با آرایه به عنوان یک «مجموعه مرتب شده» دست بکشیم و شروع به کار کردن به عنوان یک شیء معمولی کنیم، بهینه‌سازی‌ها متوقف می‌شوند.

برای مثال، به طور فنی می‌توانیم همچین کاری کنیم:

let fruits = []; // یک آرایه بسازیم

fruits[99999] = 5; // مقداردهی به یک ویژگی با ایندکسی بسیار بیشتر از طول آرایه

fruits.age = 25; // ساخت یک ویژگی با یک اسم دلخواه

این کار قابل انجام است، چون آرایه‌ها در ذات خود شیء هستند. ما می‌توانیم هر ویژگی‌ای را به آنها اضافه کنیم.

اما موتور خواهد دید که ما با آرایه به عنوان یک شیء معمولی کار می‌کنیم. بهینه‌سازی‌های مخصوص آرایه برای چنین موارد استفاده‌ای مناسب نیستند و غیر فعال خواهند شد و فواید آنها هم از بین خواهند رفت.

راه‌های استفاده نامناسب با یک آرایه:

  • اضافه کردن یک ویژگی غیر عددی مانند arr.test = 5.
  • ایجاد فضای خالی، مانند: اضافه کردن arr[0] و سپس arr[1000] (اضافه نکردن چیزی بین آنها).
  • پر کردن آرایه با ترتیب برعکس، مثل arr[1000]، arr[999] و غیره.

لطفا به آرایه‌ها به عنوان یک ساختار خاص برای کارکردن با داده مرتب شده نگاه کنید. آنها متدهای خاصی را برای این موضوع فراهم می‌کنند. آرایه‌ها با حساسیت به داخل موتورهای جاوااسکریپت برای کارکردن با داده مرتب شده‌ی متوالی راه یافته‌اند، لطفا از آنها در همین راه استفاده کنید. اگر به کلیدهای دلخواه نیاز دارید، به احتمال زیاد شما در واقع به یک شیء معمولی {} احتیاج دارید.

عملکرد

متدهای push/pop سربع اجرا می‌شوند، در حالی که shift/unshift کند هستند.

چرا کارکردن با انتهای آرایه از آغاز آن سریع‌تر است؟ بیایید ببینیم در طی اجراشدن چه اتفاقی می‌افتد:

fruits.shift(); // یک المان را از آغاز از بین ببر

اینکه المان با عدد 0 را بگیریم و ازبین ببریم کافی نیست. بقیه المان‌ها هم نیاز دارند که دوباره شماره گذاری شوند.

عملیات shift باید 3 کار انجام دهد:

  1. المان دارای ایندکس 0 را ازبین ببرد.
  2. تمام المان‌ها را به سمت چپ حرکت دهد، آنها را از ایندکس 1 به 0، از 2 به 1 و غیره دوباره شماره گذاری کند.
  3. ویژگی length را بروز کند.

هر چقدر المان‌های بیشتری داخل آرایه باشند، زمان بیشتری برای حرکت آنها نیاز است و عملیات درون حافظه هم بیشتر می‌شود.

روند مشابهی برای unshift اتفاق می‌افتد: برای اضافه کردن یک المان به آغاز آرایه، ما باید اول المان‌های موجود را به سمت راست حرکت دهیم و ایندکس آنها را افزایش دهیم.

درباره push/pop چطور؟ آنها نیازی به حرکت دادن چیزی ندارند. برای استخراج یک المان از انتهای آرایه، متد pop ایندکس را پاک می‌کند و length را کوتاه می‌کند.

اقدامات برای عملیات pop:

fruits.pop(); // یک المان را از انتها ازبین ببر

متد pop نیازی به حرکت دادن چیزی ندارد، چون المان‌های دیگر ایندکس‌های خود را نگه می‌دارند. به همین دلیل این متد بسیار بسیار سریع است.

روند مشابهی هم برای متد push اتفاق می‌افتد.

حلقه‌ها

یکی از قدیمی‌ترین راه‌ها برای چرخش بین المان‌های آرایه استفاده از حلقه for برای ایندکس‌ها است:

let arr = ["Apple", "Orange", "Pear"];

for (let i = 0; i < arr.length; i++) {
  alert( arr[i] );
}

اما برای آرایه‌ها شکل دیگری از حلقه وجود دارد، for..of:

let fruits = ["Apple", "Orange", "Plum"];

// حلقه‌زدن بین المان‌ها آرایه
for (let fruit of fruits) {
  alert( fruit );
}

حلقه for..of به عدد المان کنونی دسترسی نمی‌دهد، فقط مقدار آن، اما در بیشتر موارد همین کافی است. و کوتاه‌تر هم است.

از لحاظ فنی، به دلیل اینکه آرایه‌ها شیء هستند، استفاده از for..in هم ممکن است:

let arr = ["Apple", "Orange", "Pear"];

for (let key in arr) {
  alert( arr[key] ); // Apple, Orange, Pear
}

اما در واقع این ایده مناسب نیست. مشکلاتی ممکن است همراه با آن رخ دهد:

  1. حلقه for..in بین تمام ویژگی‌ها حلقه می‌زند، نه فقط ویژگی‌های عددی.

    شیءهایی «آرایه مانند» در مرورگر و در دیگر محیط‌ها وجود دارند، که مانند آرایه به نظر می‌رسند. یعنی اینکه آنها دارای length و ویژگی‌های ایندکسی هستند، اما ممکن است ویژگی‌ها و متدهای غیر عددی دیگری هم داشته باشند، که ما معمولا نیازی به آنها نداریم. حلقه for..in آنها را لیست می‌کند. پس اگر ما نیاز به کارکردن با شیءهای آرایه مانند داشته باشیم، ویژگی‌های اضافی ممکن است تبدیل به مشکل شوند.

  2. حلقه for..in برای شیءهای معمولی بهینه‌سازی شده است، نه آرایه‌ها، و به همین دلیل 10 تا 100 برابر کندتر است. قطعا هنوز خیلی سریع است. پر سرعت بودن ممکن است فقط در تنگناها مهم باشد. اما با این حال باید از تفاوت آنها مطلع باشیم.

به طور کلی ما نباید از for..in برای آرایه‌ها استفاده کنیم.

سخنی درباره “length”

ویژگی length زمانی که ما تغییری در آرایه ایجاد می‌کنیم، به صورت خودکار بروز می‌شود. اگر بخواهیم دقیق باشیم، در واقع این ویژگی برابر با تعداد مقدارها در آرایه نیست، بلکه برابر با بزرگ‌ترین ایندکس عددی به علاوه یک است.

برای مثال، یک المان با ایندکس بزرگ مسبب ایجاد یک length بزرگ می‌شود:

let fruits = [];
fruits[123] = "Apple";

alert( fruits.length ); // 124

توجه داشته باشید که ما معمولا از آرایه‌ها به این صورت استفاده نمی‌کنیم.

یک چیز جالب دیگر درباره ویژگی length این است که قابل نوشتن است.

اگر آن را به طور دستی افزایش دهیم، چیز جالبی اتفاق نمی‌افتد. اما اگر آن را کم کنیم، آرایه بریده می‌شود. این فرایند قابل بازگشت نیست، برای مثال:

let arr = [1, 2, 3, 4, 5];

arr.length = 2; // تا 2 المان بریده شد
alert( arr ); // [1, 2]

arr.length = 5; // را برگرداندیم length مقدار
alert( arr[3] ); // undefined :مقدارها برنمی‌گردند

بنابراین، ساده‌ترین راه برای خالی کردن آرایه arr.length = 0 است.

سازنده new Array()

یک سینتکس دیگر برای ساخت آرایه وجود دارد:

let arr = new Array("Apple", "Pear", "etc");

این سینتکس به ندرت استفاده می‌شود چون استفاده از براکت‌ها کوتاه‌تر است. همچنین یک خاصیت فریبنده همراه آن وجود دارد.

اگر new Array همراه با یک آرگومان که عدد است صدا زده شود، سپس یک آرایه بدون المان، اما با طول داده شده ساخته می‌شود.

بیایید ببینیم چگونه یک شخص به طور ناخواسته شرایط را برای خود بدتر می‌کند:

let arr = new Array(2); آیا یک آرایه با 2 المان ساخته می‌شود؟

alert( arr[0] ); // undefined !المانی وجود ندارد

alert( arr.length ); // length 2

برای اینکه از چنین سوپرایزهایی جلوگیری کنیم، باید از براکت‌ها استفاده کنیم، مگر اینکه واقعا بدانیم در حال انجام چه کاری هستیم.

آرایه‌های چند بعدی

آرایه‌ها می‌توانند المان‌هایی داشته باشند که خودشان هم آرایه هستند. ما می‌توانیم از آن برای آرایه‌های چند بعدی استفاده کنیم، برای مثال ذخیره کردن ماتریس‌ها:

let matrix = [
  [1, 2, 3],
  [4, 5, 6],
  [7, 8, 9]
];

alert( matrix[1][1] ); // 5 ،المان مرکزی

متد toString

آرایه‌ها پیاده‌سازی خود را از متد toString دارند که یک لیستی از المان‌ها که توسط کاما جدا شده‌اند را برمی‌گرداند.

برای مثال:

let arr = [1, 2, 3];

alert( arr ); // 1,2,3
alert( String(arr) === '1,2,3' ); // true

بیایید این را هم امتحان کنیم:

alert( [] + 1 ); // "1"
alert( [1] + 1 ); // "11"
alert( [1,2] + 1 ); // "1,21"

آرایه‌ها نه Symbol.toPrimitive دارند و نه یک valueOf مناسب، آنها فقط تبدیل toString را پیاده‌سازی می‌کنند، پس اینجا [] به یک رشته خالی تبدیل می‌شود، [1] به "1" تبدیل می‌شود و [1,2] به "1,2" تبدیل می‌شود.

زمانی که عملگر مثبت دوگانه "+" چیزی را به یک رشته اضافه می‌کند، آن را هم به یک رشته تبدیل می‌کند، پس مرحله بعد اینگونه به نظر می‌رسد:

alert( "" + 1 ); // "1"
alert( "1" + 1 ); // "11"
alert( "1,2" + 1 ); // "1,21"

آرایه‌ها را با استفاده از == مقایسه نکنید

آرایه‌ها در جاوااسکریپت، بر خلاف زبان‌های برنامه‌نویسی دیگر، نباید با عملگر == مقایسه شوند.

این عملگر نحوه برخورد خاصی برای آرایه‌ها ندارد و با آنها مانند شیءها رفتار می‌کند.

بیایید قوانین را یادآوری کنیم:

  • دو شیء با == فقط زمانی برابر هستند که مرجع آنها به یک شیء باشد.
  • اگر یکی از آرگومان‌های == شیء باشد و دیگری یک مقدار اصلی (primitive) باشد، سپس شیء به مقدار اصلی تبدیل می‌شود، همانطور که در فصل تبدیل شیء به مقدار اصلی توضیح داده شد.
  • …به استثنای null و undefined که با == برابر هستند اما با چیز دیگری برابر نیستند.

مقایسه سخت‌گیرانه === حتی ساده‌تر است چون نوع مقدارها را تبدیل نمی‌کند.

پس اگر ما آرایه‌ها را با == مقایسه کنیم، آنها هیچ وقت برابر نیستند، مگر اینکه دو متغیر را که به یک آرایه رجوع می‌کنند را مقایسه کنیم.

برای مثال:

alert( [] == [] ); // false
alert( [0] == [0] ); // false

این آرایه‌ها به طور فنی شیءهای متفاوت هستند. پس آنها برابر نیستند. عملگر == المان به المان مقایسه نمی‌کند.

مقایسه با مقدارهای اصلی هم ظاهرا می‌تواند نتایج عجیبی بدهد:

alert( 0 == [] ); // true

alert('0' == [] ); // false

اینجا در هر دو مورد، ما یک مقدار اصلی را با یک شیء آرایه‌ای مقایسه می‌کنیم. پس آرایه [] برای انجام مقایسه به مقدار اصلی و سپس به یک رشته خالی '' تبدیل می‌شود.

سپس فرایند مقایسه با مقدارهای اصلی پیش می‌رود، همانطور که در فصل تبدیل نوع داده توضیح داده شد:

// بعد از اینکه [] به '' تبدیل شد
alert( 0 == '' ); // true ،چون '' به عدد 0 تبدیل شد

alert('0' == '' ); // false ،هیچ تبدیلی رخ نداد، رشته‌ها متفاوت هستند

پس، چگونه آرایه‌ها را مقایسه کنیم؟

کاری ندارد: از عملگر == استفاده نکنید. به جای آن، آنها را در یک حلقه یا با استفاده از متدهای حلقه‌زدن که در فصل بعد توضیح داده شده‌اند، المان به المان مقایسه کنید.

خلاصه

آرایه یک نوع خاصی از شیء است که برای ذخیره و مدیریت داده‌های مرتب مناسب است.

  • نحوه تعریف کردن:
// براکت‌ها (معمولا)
let arr = [item1, item2...];

// new Array (به ندرت)
let arr = new Array(item1, item2...);

صدا زدن new Array(number) یک آرایه با طول داده شده می‌سازد، اما بدون المان.

  • ویژگی length طول آرایه است، یا اگر بخواهیم دقیق باشیم، برابر با آخرین ایندکس به علاوه یک است. این ویژگی به طور خودکار توسط متدهای آرایه تنظیم می‌شود.
  • اگر ما به طور دستی length را کوتاه کنیم، آرایه بریده می‌شود.

دریافت المان‌ها:

  • می‌توانیم المان را با استفاده از ایندکس آن دریافت کنیم، مانند arr[0]
  • همچنین می‌توانیم از متد at(i) که ایندکس‌های منفی را هم مجاز می‌داند استفاده کنیم. برای مقادیر منفی i، این متد از انتهای آرایه به سمت عقب قدم برمی‌دارد. اگر i >= 0 باشد، این متد مانند arr[i] کار می‌کند.

ما می‌توانیم از یک آرایه با عملیات‌های زیر به عنوان یک صف دو طرفه استفاده کنیم:

  • push(...items) اضافه می‌کند items را به انتهای آرایه.
  • pop() المان را از آخر حذف می‌کند و آن را برمی‌گرداند.
  • shift() – المان را از آغاز حذف می‌کند و آن را برمی‌گرداند.
  • unshift(...items) اضافه می‌کند items را به آغاز آرایه.

برای حلقه‌زدن در المان‌های آرایه:

  • for (let i=0; i<arr.length; i++) – سریع کار می‌کند و با مرورگرهای قدیمی سازگار است،
  • for (let item of arr) – سینتکسی مدرن که فقط برای المان‌ها استفاده می‌شود،
  • for (let i in arr) – هیچ وقت از این استفاده نکنید.

برای مقایسه آرایه‌ها، از عملگر == (همینطور >، < و بقیه) استفاده نکنید، چون آنها با آرایه‌ها به طور خاص رفتار نمی‌کنند. با آرایه‌ها به عنوان شیء کار می‌کنند و این چیزی نیست که ما معمولا می‌خواهیم.

به جای آن، می‌توانیم از حلقه for..of برای مقایسه المان به المان آرایه‌ها استفاده کنیم.

ما آرایه‌ها را ادامه می‌دهیم و در فصل بعدی متدهای آرایه متدهای بیشتری برای اضافه کردن، حذف کردن، استخراج سازی المان‌ها و مرتب کردن آرایه‌ها یاد می‌گیریم

تمارین

اهمیت: 3

این کد چه چیزی نشان خواهد داد؟

let fruits = ["Apples", "Pear", "Orange"];

// یک مقدار جدید را به «کپی» اضافه کردیم
let shoppingCart = fruits;
shoppingCart.push("Banana");

// چه چیزی وجود دارد؟ fruits در آرایه
alert( fruits.length ); // ?

نتیجه 4 است:

let fruits = ["Apples", "Pear", "Orange"];

let shoppingCart = fruits;

shoppingCart.push("Banana");

alert( fruits.length ); // 4

به این دلیل که آرایه‌ها شیء هستند. پس هر دوی shoppingCart و fruits به یک آرایه رجوع می‌کنند.

اهمیت: 5

بیایید 5 عملیات آرایه را امتحان کنیم.

  1. یک آرایه styles با المان‌های “Jazz” و “Blues” ایجاد کنید.
  2. “Rock-n-Roll” را به انتهای آن اضافه کنید.
  3. مقدار وسطی را با “Classics” جایگزین کنید. کد شما برای پیدا کردن مقدار وسط باید برای هر آرایه‌ای با طول فرد کار کند.
  4. مقدار اول آرایه را بیرون بیاورید و آن را نمایش دهید.
  5. دو مقدار Rap و Reggae را به اول آرایه اضافه کنید.

آرایه در حین فرایند اینگونه خواهد بود:

Jazz, Blues
Jazz, Blues, Rock-n-Roll
Jazz, Classics, Rock-n-Roll
Classics, Rock-n-Roll
Rap, Reggae, Classics, Rock-n-Roll
let styles = ["Jazz", "Blues"];
styles.push("Rock-n-Roll");
styles[Math.floor((styles.length - 1) / 2)] = "Classics";
alert( styles.shift() );
styles.unshift("Rap", "Reggae");
اهمیت: 5

نتیجه چه خواهد بود؟ چرا؟

let arr = ["a", "b"];

arr.push(function() {
  alert( this );
});

arr[2](); // ?

صدازدن arr[2]() از لحاظ سینتکس همان obj[method]() قدیم است، ما arr را در نقش obj داریم و 2 در نقش method.

پس صدازدن تابع arr[2] مانند متد یک شیء است. به طور طبیعی، این تابع this را درحالی که به شیء arr رجوع می‌کند می‌گیرد و آرایه را نمایش می‌دهد:

let arr = ["a", "b"];

arr.push(function() {
  alert( this );
})

arr[2](); // a,b,function(){...}

آرایه 3 مقدار دارد: در ابتدا 2 مقدار داشت سپس تابع اضافه شد.

اهمیت: 4

تابع sumInput() را بنویسید که:

  • از کاربر با استفاده از prompt درخواست مقدار می‌کند و مقدارها را در آرایه ذخیره می‌کند.
  • زمانی که کاربر یک مقدار غیر عددی یا رشته خالی وارد کند یا “Cancel” را فشار دهد، درخواست کردن را متوقف می‌کند.
  • جمع المان‌های آرایه یا محاسبه و آن را برگردانید.

پی‌نوشت: صفر 0 یک مقدار معتبر است، لطفا درخواست ورودی را با صفر متوقف نکنید.

[دمو]

لطفا جزئیات کوچک اما مهم راه حل را در نظر داشته باشید. ما value را درست بعد از prompt به عدد تبدیل نمی‌کنیم، چون بعد از value = +value ما نمی‌توانیم بین یک رشته خالی (نشان‌دهنده توقف) و صفر (مقداری معتبر) فرقی قائل شویم. در عوض آن را بعدا انجام می‌دهیم.

function sumInput() {

  let numbers = [];

  while (true) {

    let value = prompt("لطفا یک عدد وارد کنید.", 0);

    // باید لغو کنیم؟
    if (value === "" || value === null || !isFinite(value)) break;

    numbers.push(+value);
  }

  let sum = 0;
  for (let number of numbers) {
    sum += number;
  }
  return sum;
}

alert( sumInput() );
اهمیت: 2

ورودی یک آرایه از اعداد است، برای مثال arr = [1, -2, 3, 4, -9, 6].

کاری که باید انجام شود: زیرآرایه متوالی از arr را پیدا کنید که بیشترین مقدار جمع المان‌ها را دارد.

تابع getMaxSubSum(arr) را بنویسید که مقدار جمع را برگرداند.

برای مثال:

getMaxSubSum([-1, 2, 3, -9]) == 5 (جمع المان‌های برجسته)
getMaxSubSum([2, -1, 2, 3, -9]) == 6
getMaxSubSum([-1, 2, 3, -9, 11]) == 11
getMaxSubSum([-2, -1, 1, 2]) == 3
getMaxSubSum([100, -9, 2, -3, 5]) == 100
getMaxSubSum([1, 2, 3]) == 6 (همه را انتخاب می‌کنیم)

اگر تمام المان‌ها منفی باشند، به این معنی است که هیچ کدام را انتخاب نمی‌کنیم (زیرآرایه خالی است)، پس جمع برابر با صفر است:

getMaxSubSum([-1, -2, -3]) = 0

لطفا سعی کنید یک راه حل سریع پیدا کنید: O(n2) یا حتی O(n) اگر می‌توانید.

باز کردن یک sandbox همراه با تست‌ها.

راه حل کُند

ما می‌توانیم تمام جمع‌های ممکن را حساب کنیم.

ساده‌ترین راه حل این است که تمام المان‌ها را دریافت کنیم و از آن المان به بعد، حاصل جمع تمامی زیرآرایه‌ها را حساب کنیم.

برای مثال، برای [11 ,9- ,3 ,2 ,1-]:

// -1 شروع از:
-1
-1 + 2
-1 + 2 + 3
-1 + 2 + 3 + (-9)
-1 + 2 + 3 + (-9) + 11

// 2 شروع از:
2
2 + 3
2 + 3 + (-9)
2 + 3 + (-9) + 11

// 3 شروع از:
3
3 + (-9)
3 + (-9) + 11

// -9 شروع از:
-9
-9 + 11

// -11 شروع از:
-11

در واقع کد یک حلقه تو در تو است: حلقه بیرونی در المان‌های آرایه حلقه می‌زند، و حلقه داخلی تمام جمع‌ها را حساب می‌کند که از المان کنونی شروع می‌شوند.

function getMaxSubSum(arr) {
  let maxSum = 0; // اگر هیچ المانی نگیریم، صفر برگردانده می‌شود

  for (let i = 0; i < arr.length; i++) {
    let sumFixedStart = 0;
    for (let j = i; j < arr.length; j++) {
      sumFixedStart += arr[j];
      maxSum = Math.max(maxSum, sumFixedStart);
    }
  }

  return maxSum;
}

alert( getMaxSubSum([-1, 2, 3, -9]) ); // 5
alert( getMaxSubSum([-1, 2, 3, -9, 11]) ); // 11
alert( getMaxSubSum([-2, -1, 1, 2]) ); // 3
alert( getMaxSubSum([1, 2, 3]) ); // 6
alert( getMaxSubSum([100, -9, 2, -3, 5]) ); // 100

این راه حل یک پیچیدگی زمانی O(n2) دارد، به عبارتی دیگر، اگر ما اندازه آرایه را دو برابر کنیم، الگوریتم 4 برابر بیشتر زمان می‌برد.

برای آرایه‌های بزرگ (1000، 10000 یا المان‌های بیشتر) چنین الگوریتمی می‌تواند باعث سستی جدی شود.

راه حل سریع

بیایید در آرایه پیش برویم و حاصل جمع کنونی المان‌ها را در متغیر s نگه داریم. اگر s در جایی منفی شود، سپس s=0 را مقداردهی می‌کنیم. بیشترین مقدار چنین sهایی جواب خواهد بود.

اگر توضیحات خیلی مبهم است، لطفا به کد نگاه کنید، به اندازه کافی کوتاه است:

function getMaxSubSum(arr) {
  let maxSum = 0;
  let partialSum = 0;

  for (let item of arr) { // برای هر المان آرایه
    partialSum += item; // اضافه کن partialSum آن را به
    maxSum = Math.max(maxSum, partialSum); // بیشترین مقدار را یه یاد بسپر
    if (partialSum < 0) partialSum = 0; // اگر منفی بود برابر با منفی شود
  }

  return maxSum;
}

alert( getMaxSubSum([-1, 2, 3, -9]) ); // 5
alert( getMaxSubSum([-1, 2, 3, -9, 11]) ); // 11
alert( getMaxSubSum([-2, -1, 1, 2]) ); // 3
alert( getMaxSubSum([100, -9, 2, -3, 5]) ); // 100
alert( getMaxSubSum([1, 2, 3]) ); // 6
alert( getMaxSubSum([-1, -2, -3]) ); // 0

الگوریتم دقیقا به 1 آرایه نیاز دارد، پس پیچیدگی زمان O(n) است.

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

function getMaxSubSum(arr) {
  let maxSum = 0;
  let partialSum = 0;

  for (let item of arr) {
    partialSum += item;
    maxSum = Math.max(maxSum, partialSum);
    if (partialSum < 0) partialSum = 0;
  }
  return maxSum;
}

باز کردن راه‌حل همراه با تست‌ها درون یک sandbox.

نقشه آموزش

نظرات

قبل از نظر دادن این را بخوانید…
  • اگر پیشنهادی برای بهبود ترجمه دارید - لطفا یک ایشوی گیت‌هاب یا یک پول‌ریکوئست به جای کامنت‌گذاشتن باز کنید.
  • اگر چیزی را در مقاله متوجه نمی‌شوید – به دقت توضیح دهید.
  • برای قراردادن یک خط از کد، از تگ <code> استفاده کنید، برای چندین خط – کد را درون تگ <pre> قرار دهید، برای بیش از ده خط کد – از یک جعبهٔ شنی استفاده کنید. (plnkr، jsbin، codepen…)