۱۲ دسامبر ۲۰۲۲

متدهای آرایه

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

اضافه/حذف کردن عضوها

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

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

اینجا چند متد دیگر داریم.

متد splice

چطور یک المان را از آرایه حذف کنیم؟

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

let arr = ["I", "go", "home"];

delete arr[1]; // "go" حذف

alert( arr[1] ); // undefined

// now arr = ["I",  , "home"];
alert( arr.length ); // 3

المان حذف شد، اما آرایه هنوز هم 3 عضو دارد که می‌توانیم آن را با arr.length == 3 ببینیم.

این چیز طبیعی است چون delete obj.key یک مقدار را با استفاده از key حذف می‌کند. به طور کلی کارش همین است. برای شیءها مناسب است. اما برای آرایه‌ها ما معمولا می‌خواهیم که بقیه المان‌ها پخش شوند و فضای آزاد شده را اشغال کنند. توقع داریم که الان آرایه‌ای کوتاه‌تر داشته باشیم.

بنابراین متدهای خاص باید استفاده شوند.

متد arr.splice یک شمشیر ارتشی سوئیسی برای آرایه‌ها است. می‌تواند هر کاری کند: اضافه کند، حذف کند و المان‌ها را جایگزین کند.

سینتکس آن اینگونه است:

arr.splice(start[, deleteCount, elem1, ..., elemN])

این متد arr را از ایندکس start تغییر می‌دهد: به تعداد deleteCount المان حذف می‌کند و سپس elem1, ..., elemN را در مکان خودشان اضافه می‌کند. آرایه‌ای از المان‌های حذف شده را برمی‌گرداند.

این متد را با مثال به راحتی متوجه می‌شوید.

بیایید با حذف کردن شروع کنیم:

let arr = ["I", "study", "JavaScript"];

arr.splice(1, 1); // از ایندکس 1 به تعداد 1 المان حذف کن

alert( arr ); // ["I", "JavaScript"]

راحت است، نه؟ از ایندکس 1 به تعداد 1 المان حذف کرد.

در مثال بعد ما 3 المان را حذف و آنها را با دو المان جایگزین می‌کنیم:

let arr = ["I", "study", "JavaScript", "right", "now"];

// سه المان ابتدایی را حذف کن و آنها را با المان‌های دیگر جایگزین کن
arr.splice(0, 3, "Let's", "dance");

alert( arr ) // now ["Let's", "dance", "right", "now"]

اینجا می‌بینیم که splice آرایه‌ای از المان‌های حذف شده را برمی‌گرداند:

let arr = ["I", "study", "JavaScript", "right", "now"];

// دو المان اول را حذف کن
let removed = arr.splice(0, 2);

alert( removed ); // "I", "study" <-- array of removed elements

متد splice همچنین قادر به اضافه کردن المان بدون هیچ حذفیاتی است. برای این کار باید deleteCount را 0 بگذاریم:

let arr = ["I", "study", "JavaScript"];

// از ایندکس 2
// به تعداد 0 حذف کن
// را اضافه کن "language" و "complex" سپس
arr.splice(2, 0, "complex", "language");

alert( arr ); // "I", "study", "complex", "language", "JavaScript"
ایندکس‌های منفی مجاز هستند

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

let arr = [1, 2, 5];

// از ایندکس 1- (یک قدم قبل از انتها)
// به تعداد 0 المان حذف کن،
// سپس 3 و 4 را اضافه کن
arr.splice(-1, 0, 3, 4);

alert( arr ); // 1,2,3,4,5

متد slice

متد arr.slice از متد arr.splice که از لحاظ ظاهری شبیه به آن است بسیار ساده‌تر است.

سینتکس اینگونه است:

arr.slice([start], [end])

این متد یک آرایه جدید که تمام المان‌ها را از ایندکس start تا end (شامل خود end نمی‌شود) کپی می‌کند، برمی‌گرداند. start و end هر دو می‌توانند منفی باشند، که در این صورت موقعیت از انتهای آرایه حساب می‌شود.

این متد شبیه متد رشته str.slice است، اما به جای زیر رشته، زیر آرایه ایجاد می‌کند.

برای مثال:

let arr = ["t", "e", "s", "t"];

alert( arr.slice(1, 3) ); // e,s (کپی کردن از 1 تا 3)

alert( arr.slice(-2) ); // s,t (کپی کردن از 2- تا انتها)

همچنین می‌توانیم آن را بدون آرگومان هم صدا بزنیم: arr.slice() که یک کپی از arr می‌سازد. معمولا از این روش برای ایجاد یک کپی با هدف اینکه تغییرات آینده روی آرایه اصلی تاثیری نگذارد استفاده می‌کنند.

متد concat

متد arr.concat یک آرایه جدید می‌سازد که حاوی مقدارهای آرایه‌های دیگر و المان‌های اضافی است.

سینتکس آن اینگونه است:

arr.concat(arg1, arg2...)

این متد به هر تعدادی آرگومان می‌پذیرد – چه آرایه باشند چه مقدار.

نتیجه آن یک آرایه جدید حاوی المان‌های arr، سپس arg1، arg2 و غیره.

اگر آرگومان argN یک آرایه باشد، سپس تمام المان‌های آن کپی می‌شود. در غیر این صورت، خود آرگومان کپی می‌شود.

برای مثال:

let arr = [1, 2];

// و [3,4] arr :ساخت یک آرایه از
alert( arr.concat([3, 4]) ); // 1,2,3,4

// و [3,4] و [5,6] arr :ساخت یک آرایه از
alert( arr.concat([3, 4], [5, 6]) ); // 1,2,3,4,5,6

// و [3,4]، سپس اضافه کردن مقدارهای 5 و 6 arr :ساخت یک آرایه از
alert( arr.concat([3, 4], 5, 6) ); // 1,2,3,4,5,6

به طور معمول، این متد فقط المان‌ها را از آرایه‌ها کپی می‌کند. بقیه شیءها، حتی اگر شبیه آرایه باشند، به طور کلی اضافه می‌شوند:

let arr = [1, 2];

let arrayLike = {
  0: "something",
  length: 1
};

alert( arr.concat(arrayLike) ); // 1,2,[object Object]

…اما اگر یک شیء شبیه به آرایه یک ویژگی Symbol.isConcatSpreadable داشته باشد، سپس concat با آن به عنوان یک آرایه رفتار می‌کند: در عوض المان‌های آن اضافه می‌شوند:

let arr = [1, 2];

let arrayLike = {
  0: "something",
  1: "else",
  [Symbol.isConcatSpreadable]: true,
  length: 2
};

alert( arr.concat(arrayLike) ); // 1,2,something,else

حلقه زدن: forEach

متد arr.forEach به ما این امکان را می‌دهد که یک تابع را روی تمام المان‌های آرایه اجرا کنیم.

سینتکس اینگونه است:

arr.forEach(function(item, index, array) {
  // ... با المان کاری انجام دهید
});

برای مثال، این کد هر المان آرایه را نشان می‌دهد:

// را صدا بزن alert برای هر المان
["Bilbo", "Gandalf", "Nazgul"].forEach(alert);

و این کد درباره موقعیت آنها در آرایه مورد نظر جزئیات بیشتری دارد:

["Bilbo", "Gandalf", "Nazgul"].forEach((item, index, array) => {
  alert(`${item} is at index ${index} in ${array}`);
});

نتیجه تابع (اگر چیزی برگرداند) نادیده گرفته و دور ریخته می‌شود.

جستجو در آرایه

حال بیایید متدهایی را بخوانیم که در آرایه جستجو می‌کنند.

متدهای indexOf/lastIndexOf and includes

متدهای arr.indexOf و arr.includes سینتکس مشابه دارند و اساسا همان کار همتایان خود در رشته‌ها را انجام می‌دهند، اما به جای کاراکترها با المان‌ها کار دارند:

  • arr.indexOf(item, from) – با شروع از ایندکس from به دنبال item می‌گردد و ایندکسی که المان در آن پیدا شد را برمی‌گرداند، در غیر این صورت 1-.
  • arr.includes(item, from) – با شروع از ایندکس from به دنبال item می‌گردد، اگر پیدا کند true را برمی‌گرداند.

معمولا این متدها تنها با یک آرگومان استفاده می‌شوند: المانی (item) که جستجو برای آن انجام می‌شود. به طور پیش‌فرض، جستجو از ابتدا انجام می‌شود.

برای مثال:

let arr = [1, 0, false];

alert( arr.indexOf(0) ); // 1
alert( arr.indexOf(false) ); // 2
alert( arr.indexOf(null) ); // -1

alert( arr.includes(1) ); // true

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

اگر ما بخواهیم بررسی کنیم که item درون آرایه وجود دارد یا نه و به دنبال ایندکس دقیق نیستیم، پس arr.includes ترجیح داده می‌شود.

متد arr.lastIndexOf مانند indexOf است اما از راست به چپ جستجو می‌کند.

let fruits = ['Apple', 'Orange', 'Apple']

alert( arr.indexOf('Apple') ); // 0 (Apple اولین)
alert( arr.lastIndexOf('Apple') ); // 2 (Apple آخرین)

````smart header="متد `includes` مقدار `NaN` را به درستی مدیریت می‌کند"
یک تفاوت بسیار کوچک `includes` این است که این متد به درستی `NaN` را کنترل می‌کند، درست برعکس `indexOf`:

```js run
const arr = [NaN];
alert( arr.indexOf(NaN) ); // -1 (اشتباه است، باید 0 باشد)
alert( arr.includes(NaN) );// true (درست است)

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

### متدهای find و findIndex/findLastIndex

تصور کنید که یک آرایه‌ای از شیءها داریم. چگونه باید یک شیء با شرطی مشخص را پیدا کنیم؟

اینجاست که متد [arr.find(fn)](mdn:js/Array/find) بدرد می‌خورد.

سینتکس آن اینگونه است:
```js
let result = arr.find(function(item, index, array) {
  // برگردانده شود، المان برگردانده می‌شود و حلقه‌ی تکرار متوقف می‌شود true اگر مقدار
  // برگردانده می‌شود undefined مقدار falsy برای سناریوهای
});
```

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

- `item` المان است.
- `index` ایندکس آن است.
- `array` خود آرایه است.

اگر `true` برگرداند، جستجو متوقف می‎شود، `item` برگردانده می‌شود. اگر چیزی پیدا نشود، `undefined` برگردانده می‌شود.

برای مثال، ما یک آرایه‌ای از کاربران داریم، که هر کدام دارای `id` و `name` هستند. بیایید کاربری که `id == 1` داشته باشد را پیدا کنیم:

```js run
let users = [
  {id: 1, name: "John"},
  {id: 2, name: "Pete"},
  {id: 3, name: "Mary"}
];

let user = users.find(item => item.id == 1);

alert(user.name); // John
```

در واقعیت، آرایه‌هایی از شیءها چیز متداولی است، پس متد `find` بسیار مفید است.

توجه داشته باشید که در مثال بالا ما تابع `item => item.id == 1` را همراه با یک آرگومان برای `find` در نظر گرفتیم. این چیز معمولی است، بقیه آرگومان‌های این تابع به ندرت استفاده می‌شوند.

متد [arr.findIndex](mdn:js/Array/findIndex) سینتکس یکسانی دارد اما به جای خود المان ایندکسی که المان در آن پیدا شد را برمی‌گرداند. اگر چیزی پیدا نشد مقدار `1-` برگردانده می‌شود.

متد [arr.findLastIndex](mdn:js/Array/findLastIndex) مانند `findIndex` است اما مانند `lastIndexOf` از راست به چپ جستجو می‌کند.

اینجا یک مثال داریم:

```js run
let users = [
  {id: 1, name: "John"},
  {id: 2, name: "Pete"},
  {id: 3, name: "Mary"},
  {id: 4, name: "John"}
];

// را پیدا کن John ایندکس اولین
alert(users.findIndex(user => user.name == 'John')); // 0

// را پیدا کن John ایندکس آخرین
alert(users.findLastIndex(user => user.name == 'John')); // 3
```

### متد filter

متد `find` برای یک (اولین) المان که باعث شود تابع `true` برگرداند، جستجو می‌کند.

اگر ممکن باشد تعداد بیشتری موجود باشند، می‌توانیم از [arr.filter(fn)](mdn:js/Array/filter) استفاده کنیم.

سینتکس آن مشابه `find` است اما `filter` یک آرایه از المان‌های منطبق را برمی‌گرداند:

```js
let results = arr.filter(function(item, index, array) {
  // باشد المان به نتیجه‌ها اضافه می‌شود و حلقه تکرار ادامه پیدا می‌کند true اگر
  // اگر چیزی پیدا نشود یک آرایه خالی برمی‌گرداند
});
```

برای مثال:

```js run
let users = [
  {id: 1, name: "John"},
  {id: 2, name: "Pete"},
  {id: 3, name: "Mary"}
];

// آرایه شامل دو کاربر اول را برمی‌گرداند
let someUsers = users.filter(item => item.id < 3);

alert(someUsers.length); // 2
```

## تغییر شکل دادن آرایه

بیایید به سراغ متدهایی برویم که یک آرایه را تغییر شکل و نظم دوباره می‌دهند.

### متد map

متد [arr.map](mdn:js/Array/map) یکی از پرکاربردترین و متدوال‌ترین متدهاست.

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

سینتکس آن اینگونه است:

```js
let result = arr.map(function(item, index, array) {
  // به جای المان، مقدار جدید را برمی‌گرداند
});
```

برای مثال، ما هر المان را به طول آن تغییر می‌دهیم:

```js run
let lengths = ["Bilbo", "Gandalf", "Nazgul"].map(item => item.length);
alert(lengths); // 5,7,6
```

### متد sort(fn)

صدازدن [arr.sort()](mdn:js/Array/sort) آرایه را *در محل* با تغییر دادن ترتیب المان‌ها، مرتب می‌کند.

همچنین این متد آرایه مرتب شده را برمی‌گرداند، اما همانطور که خود `arr` تغییر داده می‌شود، مقدار برگردانده شده معمولا نادیده گرفته می‌شود.

برای مثال:

```js run
let arr = [ 1, 2, 15 ];

// را دوباره ترتیب بندی می‌کند arr این متد
arr.sort();

alert( arr );  // 1, 15, 2
```

چیز عجیبی را در نتیجه متوجه شدید؟

ترتیب المان‌ها `1, 15, 2` شد. این اشتباه است. اما چرا؟

**المان‌ها به صورت پیشفرض به عنوان رشته مرتب می‌شوند.**

به طور کلی، تمام المان‌ها برای انجام مقایسه به رشته تبدیل می‌شوند. برای رشته‌ها، ترتیب‌بندی لفت‌نامه‌ای اعمال می‌شود و در این صورت `"2" > "15"` است.

برای استفاده از ترتیب‌بندی خودمان، ما نیاز داریم که یک تابع را به عنوان آرگومان `arr.sort()` قرار دهیم.

تابع باید دو مقدار دلخواه را مقایسه کند و چیزی را برگرداند:
```js
function compare(a, b) {
  if (a > b) return 1; // اگر مقدار اول بزرگتر از دومی باشد
  if (a == b) return 0; // اگر مقدارها برابر باشند
  if (a < b) return -1; // اگر مقدار اول کمتر از دومی باشد
}
```

برای مثال، برای مرتب کردن به عنوان اعداد:

```js run
function compareNumeric(a, b) {
  if (a > b) return 1;
  if (a == b) return 0;
  if (a < b) return -1;
}

let arr = [ 1, 2, 15 ];

arr.sort(compareNumeric);

alert(arr);  // 1, 2, 15
```

حالا همانطور که انتظار می‌رفت کار می‌کند.

بیایید کمی عقب بمانیم و ببینیم چه چیزی در حال اتفاق افتادن است. `arr` می‌تواند آرایه‌ای از هر چیزی باشد نه؟ ممکن است شامل اعداد یا رشته‌ها یا شیءها یا هرچیز دیگری باشد. ما دسته‌ای از *چیزها* داریم. برای مرتب کردن آن، ما به یک *تابع مرتب‌کننده* که می‌داند چگونه المان‌های دسته را مقایسه کند، نیاز داریم. ترتیب رشته‌ای پیش‌فرض است.

متد `arr.sort(fn)` یک الگوریتم مرتب‌سازی کلی را پیاده‌سازی می‌کند. ما نیازی نداریم که بدانیم درون آن چه اتفاقی می‌افتد (اکثر اوقات از یک [مرتب‌سازی سریع](https://fa.wikipedia.org/wiki/مرتب%E2%80%8Cسازی_سریع) یا [Timsort](https://fa.wikipedia.org/wiki/مرتب%E2%80%8Cسازی_تیم) بهینه‌شده استفاده می‌شود). این متد آرایه را طی می‌کند، المان‌های آن را با استفاده از تابع فراهم شده مقایسه می‌کند و آنها را مرتب می‌کند، تمام آن چیزی که ما نیاز داریم این است که یک `fn` فراهم کنیم که مقایسه را انجام دهد.

راستی، اگر ما بخواهیم بدانیم که کدام المان‌ها مقایسه می‌شوند -- چیزی ما را از alert کردن آنها متوقف نمی‌کند:

```js run
[1, -2, 15, 2, 0, 8].sort(function(a, b) {
  alert( a + " <> " + b );
  return a - b;
});
```

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

````smart header="یک تابع مقایسه می‌تواند هر عددی برگرداند"
در واقع یک تابع مقایسه فقط نیاز دارد که یک عدد مثبت را برای اینکه بگوید «بزرگتر» است برگرداند و یک عدد منفی را برای گفتن «کمتر» است.

این ویژگی سبب می‌شود که تابع‌های کوتاه‌تری نوشته شود:

```js run
let arr = [ 1, 2, 15 ];

arr.sort(function(a, b) { return a - b; });

alert(arr);  // 1, 2, 15
```
توابع پیکانی بهترین‌اند

تابع‌های پیکانی را به یاد دارید؟ ما می‌توانیم از آنها برای مرتب‌سازی تمیزتر استفاده کنیم:

arr.sort( (a, b) => a - b );

این کد دقیقا مانند نسخه طولانی‌تر بالایی کار می‌کند.

برای رشته‌ها از localeCompare استفاده کنید

الگوریتم مقایسه رشته‌ها را به یاد دارید؟ این الگوریتم به صورت پیش‌فرض حروف را با کدهای آنها مقایسه می‌کند.

برای بساری از حروف الفبا، بهتر است از متد str.localeCompare برای مرتب‌کردن صحیح حروف استفاده شود، مانند Ö.

برای مثال، بیایید چند کشور را به زبان آلمانی مرتب کنیم:

let countries = ['Österreich', 'Andorra', 'Vietnam'];

alert( countries.sort( (a, b) => a > b ? 1 : -1) ); // Andorra, Vietnam, Österreich (اشتباه است)

alert( countries.sort( (a, b) => a.localeCompare(b) ) ); // Andorra,Österreich,Vietnam (درست است!)

متد reverse

متد arr.reverse ترتیب المان‌ها را arr برعکس می‌کند.

برای مثال:

let arr = [1, 2, 3, 4, 5];
arr.reverse();

alert( arr ); // 5,4,3,2,1

همچنین این متد ارایه arr را بعد از برعکس شدن برمی‌گرداند.

متدهای split and join

یک موقعیت در زندگی واقعی را می‌گوییم. ما در حال نوشتن یک برنامه پیام‌رسان هستیم و شخص لیستی از دریافت کنندگان که با کاما جدا شده‌اند را وارد می‌کند: John, Pete, Mary. اما یک آرایه‌ای از اسم‌ها بسیار راحت‌تر از یک رشته خواهد بود. چگونه آن را دریافت کنیم؟

متد str.split(delim) دقیقا همین کار را انجام می‌دهد. این متد رشته را با استفاده از جداکننده‌ی داده شده delim به یک آرایه تقسیم می‌کند.

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

let names = 'Bilbo, Gandalf, Nazgul';

let arr = names.split(', ');

for (let name of arr) {
  alert( `A message to ${name}.` ); // A message to Bilbo  (و بقیه اسم‌ها)
}

متد split یک آرگومان اختیاری دوم هم دارد – یک محدودیت برای طول آرایه. اگر این آرگومان اضافه شود، سپس المان‌های دیگر نادیده گرفته می‌شوند. گرچه در عمل به ندرت استفاده می‌شود:

let arr = 'Bilbo, Gandalf, Nazgul, Saruman'.split(', ', 2);

alert(arr); // Bilbo, Gandalf
جداکردن به حروف

صدا زدن split(s) با یک s خالی رشته را به آرایه‌ای از حروف جدا می‌کند:

let str = "test";

alert( str.split('') ); // t,e,s,t

صدا زدن arr.join(glue) عمل برعکس split را انجام می‌هد. این متد یک رشته از arr می‌سازد که توسط glue المان‌ها متصل شده‌اند.

برای مثال:

let arr = ['Bilbo', 'Gandalf', 'Nazgul'];

let str = arr.join(';'); // آرایه را با استفاده از ; به یک رشته تبدیل کنید

alert( str ); // Bilbo;Gandalf;Nazgul

متد reduce/reduceRight

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

زمانی که ما نیاز داشته باشیم در المان‌ها حلقه بزنیم و داده را برای هر المان برگردانیم، می‌توانیم از map استفاده کنیم.

متدهای arr.reduce و arr.reduceRight همچنین به این دسته تعلق دارند، اما کمی پیچیده‌تر هستند. آنها برای محاسبه یک مقدار بر اساس آرایه، استفاده می‌شوند.

سینتکس اینگونه است:

let value = arr.reduce(function(accumulator, item, index, array) {
  // ...
}, [initial]);

تابع روی تمام المان‌های آرایه اعمال می‌شود و نتیجه خود را به فراخوانی بعدی «منتقل می‌کند».

آرگومان‌ها:

  • accumulator – نتیجه قبلی فراخوانی تابع است، دفعه اول با initial برابر است (اگر initial وجود داشته باشد).
  • item – المان کنونی آرایه است.
  • index – موقعیت آن است.
  • array – آرایه است.

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

بنابراین، اولین آرگومان اساسا همان حافظه‌ای است که نتیجه ترکیب شده تمام فراخوانی‌های قبلی را ذخیره کرده است. و در پایان تبدیل به نتیجه reduce می‌شود.

بنظر پیچیده می‌آید؟

راحت‌ترین راه برای فهمیدن این قضیه، توسط مثال است.

اینجا ما حاصل جمع یک آرایه را در یک خط می‌گیریم:

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

let result = arr.reduce((sum, current) => sum + current, 0);

alert(result); // 15

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

بیایید جزئیات چیزی که در حال انجام است را ببینیم.

  1. در اجرای اول، sum برابر با مقدار initial است (آخرین آرگومان reduce)، که برابر با 0 است، و current اولین المان آرایه است، که برابر با 1 است. پس نتیجه تابع 1 است.
  2. در اجرای دوم، sum = 1، که ما المان دوم آرایه (2) را به آن اضافه و برمی‌گردانیم.
  3. در اجرای سوم، sum = 3 و ما یک المان دیگر به آن اضافه می‌کنیم و…

گردش محاسبه:

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

sum current نتیجه
فراخوانی اول 0 1 1
فراخوانی دوم 1 2 3
فراخوانی سوم 3 3 6
فراخوانی چهارم 6 4 10
فراخوانی پنجم 10 5 15

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

ما همچنین می‌توانیم مقدار اولیه را حذف کنیم:

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

// حذف شد (بدون 0) reduce مقدار اولیه از
let result = arr.reduce((sum, current) => sum + current);

alert( result ); // 15

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

جدول محاسبات مانند بالا است، منتها ردیف اول را ندارد.

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

یک مثال اینجا داریم:

let arr = [];

// Error: Reduce of empty array with no initial value
// .آن را برای آرایه خالی برمی‌گرداند reduce ،اگر مقدار اولیه وجود داشت
arr.reduce((sum, current) => sum + current);

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

متد arr.reduceRight کار یکسان را انجام می‌هد، اما از راست به چپ.

##متد Array.isArray

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

بنابراین typeof برای تشخیص یک شیء ساده از آرایه کمکی نمی‌کند:

alert(typeof {}); // object
alert(typeof []); // object (یکسان)

…اما آرایه‌ها به دلیل اینکه اغلب اوقات استفاده می‌شوند، یک متد خاص برای این کار دارند: Array.isArray(value). این متد اگر value یک آرایه باشد true برمی‌گرداند و در غیر این صورت false.

alert(Array.isArray({})); // false

alert(Array.isArray([])); // true

اکثر متدها از “thisArg” پشتیبانی می‌کنند

تقریبا تمام متدهای آرایه که تابعی را صدا می‌زنند – مانند find، filter، map، همچنین یک استثنا از sort، پارامتر اختیاری اضافی thisArg را قبول می‌کنند.

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

سینتکس کامل این متدها در زیر آمده است:

arr.find(func, thisArg);
arr.filter(func, thisArg);
arr.map(func, thisArg);
// ...
// آرگومان اختیاری آخر است thisArg

مقدار thisArg برای func برابر با this خواهد بود.

برای مثال، اینجا ما از متد شیء army به عنوان یک فیلتر استفاده می‌کنیم، و thisArg محتوا را رد و بدل می‌کند:

let army = {
  minAge: 18,
  maxAge: 27,
  canJoin(user) {
    return user.age >= this.minAge && user.age < this.maxAge;
  }
};

let users = [
  {age: 16},
  {age: 20},
  {age: 23},
  {age: 30}
];

// برمی‌گرداند را پیدا کن true به ازای آنها army.canJoin هایی کهuser
let soldiers = users.filter(army.canJoin, army);

alert(soldiers.length); // 2
alert(soldiers[0].age); // 20
alert(soldiers[1].age); // 23

اگر در مثال بالا ما از users.filter(army.canJoin) استفاده می‌کردیم، سپس army.canJoin به عنوان یک تابع جداگانه صدا زده می‌شد که this=undefined، بنابراین درجا به یک ارور برمی‌خوردیم.

صدازدن users.filter(army.canJoin, army) می‌تواند با users.filter(user => army.canJoin(user)) جایگزین شود، که هردو یکسان هستند. نوع دوم بیشتر استفاده می‌شود، چون برای اکثر مردم مقداری قابل فهم‌تر است.

خلاصه

برگ تقلبی از متدهای آرایه:

  • برای اضافه/حذف کردن المان‌ها:

    • push(...items) – المان‌ها را به آخر اضافه می‌کند،
    • pop() – یک المان را از آخر حذف می‌کند،
    • shift() – یک المان را از آغاز حذف می‌کند،
    • unshift(...items) – المان‌هایی را به آغاز اضافه می‌کند.
    • splice(pos, deleteCount, ...items) – در ایندکس pos به تعداد deleteCount المان حذف و items را اضافه می‌کند.
    • slice(start, end) – با ساختن یک آرایه جدید، المان‌ها را از ایندکس start تا end (شامل نمی‌شود) در آن کپی می‌کند.
    • concat(...items) – یک آرایه جدید را برمی‌گرداند: تمام عضوهای آرایه کنونی را کپی می‌کند و items را به آن اضافه می‌کند. اگر هر کدام از items آرایه باشد، سپس المان‌های آن اضافه می‌شوند.
  • برای جستجو در بین المان‌ها:

    • indexOf/lastIndexOf(item, pos) – با شروع از موقعیت pos به دنبال item می‌گردد، ایندکس آن را برمی‌گرداند و در صورتی که پیدا نشود 1- را برمی‌گرداند.
    • includes(value) – اگر آرایه دارای value باشد، مقدار true را برمی‌گرداند در غیر این صورت false.
    • find/filter(func) – المان‌ها را از طریق تابع فیلتر می‌کند، اولین/تمام مقدارهایی که سبب می‌شوند تابع true برگرداند را برمی‌گرداند.
    • findIndex مانند find است اما به جای مقدار ایندکس را برمی‌گرداند.
  • برای حلقه زدن در یک آرایه:

    • forEach(func) – برای تمام المان‌ها تابع func را صدا می‌زند، چیزی را برنمی‌گرداند.
  • برای تغییر شکل یک آرایه:

    • map(func) – از نتایج صدازدن func برای هر المان، یک آرایه جدید می‌سازد.
    • sort(func) – آرایه را در محل مرتب می‌کند، سپس آن را برمی‌گرداند.
    • reverse() – آرایه را در محل برعکس می‌کند، سپس آن را برمی‌گرداند.
    • split/join – یک رشته را به آرایه تبدیل می‌کند و برعکس.
    • reduce/reduceRight(func, initial) – با صدا زدن func برای هر المان و رد و بدل کردن یک نتیجه واسطه بین هر فراخوانی، یک مقدار مفرد را در آرایه محاسبه می‌کند.
  • علاوه بر این:

    • Array.isArray(arr) بررسی می‌کند که arr یک آرایه باشد و اگر بود مقدار true را برمی‌گرداند در غیر این صورت false.

لطفا در نظر داشته باشید که متدهای sort، reverse و splice خود آرایه را تغییر می‌دهند.

متدهای ذکر شده بیشترین استفاده را دارند، آنها 99% موارد استفاده را پوشش می‌دهند. اما چند متد دیگر هم هست:

  • arr.some(fn)/arr.every(fn) آرایه را بررسی می‌کنند.

    تابع fn رو تمام المان‌های آرایه صدا زده می‌شود درست شبیه map. اگر تمام نتایج true بود، مقدار true را برمی‌گرداند، در غیر این صورت false.

    این متدها تقریبا شبیه عملگرهای || و && رفتار می‌کنند: اگر fn مقدار truthy را برگرداند، arr.some() درجا true را برمی‌گرداند و حلقه زدن روی بقیه المان‌ها را متوقف می‌کند؛ اگر fn یک مقدار falsy برگرداند، arr.every() فورا false را برمی‌گرداند و حلقه زدن در بقیه المان‌ها را متوقف می‌کند.

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

    function arraysEqual(arr1, arr2) {
      return arr1.length === arr2.length && arr1.every((value, index) => value === arr2[index]);
    }
    
    alert( arraysEqual([1, 2], [1, 2])); // true
  • arr.fill(value, start, end) – آرایه را با مقدار تکرار شونده value از ایندکس start تا end پر می‌کند.

  • arr.copyWithin(target, start, end) – المان‌های خود را از موقعیت start تا موقعیت end در خودش و در موقعیت target کپی می‌کند (جایگزین المان موجود می‌شود).

  • arr.flat(depth)/arr.flatMap(fn) آرایه‌ای یک دست را از آرایه‌ای چند بعدی می‌سازند.

برای دیدن لیست کامل، از راهنما استفاده کنید.

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

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

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

تمارین

اهمیت: 5

تابع camelize(str) را بنویسید که کلمه‌های جدا شده توسط خط تیره مانند “my-short-string” را به عبارت camel-cased مانند “myShortString” تبدیل می‌کند.

یعنی اینکه تمام خط تیره‌ها را حذف کند و هر کلمه بعد از خط تیره با حرف بزرگ شروع شود.

مثال‌ها:

camelize("background-color") == 'backgroundColor';
camelize("list-style-image") == 'listStyleImage';
camelize("-webkit-transition") == 'WebkitTransition';

پی‌نوشت راهنمایی: از split برای تبدیل رشته به آرایه استفاده کنید، آن را تغییر شکل دهید و با join آنها را پیوند بزنید.

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

function camelize(str) {
  return str
    .split('-') // my-long-word -> ['my', 'long', 'word']
    .map(
      (word, index) => index == 0 ? word : word[0].toUpperCase() + word.slice(1)
    ) // ['my', 'long', 'word'] -> ['my', 'Long', 'Word']
    .join(''); // ['my', 'Long', 'Word'] -> myLongWord
}

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

اهمیت: 4

یک تابع filterRange(arr, a, b) بنویسید که یک آرایه arr دریافت می‌کند، به دنبال المان‌های بزرگتر یا مساوی a و کوچکتر یا مساوی b می‌گردد و نتیجه را به شکل آرایه برمی‌گرداند.

تابع نباید آرایه داده شده را تغییر دهد. باید آرایه جدید را برگرداند.

برای مثال:

let arr = [5, 3, 8, 1];

let filtered = filterRange(arr, 1, 4);

alert( filtered ); // 3,1 (مقدارهای مورد نظر)

alert( arr ); // 5,3,8,1 (تغییر داده نشده)

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

function filterRange(arr, a, b) {
  // برای خوانایی بهتر به دور عبارت، پرانتز اضافه کردیم
  return arr.filter(item => (a <= item && item <= b));
}

let arr = [5, 3, 8, 1];

let filtered = filterRange(arr, 1, 4);

alert( filtered ); // 3,1 (مقدارهای مورد نظر)

alert( arr ); // 5,3,8,1 (تغییر داده نشده)
function filterRange(arr, a, b) {
  // added brackets around the expression for better readability
  return arr.filter(item => (a <= item && item <= b));
}

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

اهمیت: 4

تابع filterRangeInPlace(arr, a, b) را بنویسید که یک آرایه arr دریافت می‌کند و تمام مقدارها به جز آنهایی که بین a و b هستند را حذف می‌کند. آزمایش به این صورت است: a ≤ arr[i] ≤ b.

تابع باید فقط آرایه را تغییر دهد. نباید چیزی را برگرداند.

برای مثال:

let arr = [5, 3, 8, 1];

filterRangeInPlace(arr, 1, 4); // تمام اعداد به جز 1 تا 4 را حذف کردیم

alert( arr ); // [3, 1]

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

function filterRangeInPlace(arr, a, b) {

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

    // اگر بیرون از بازه بود آن را حذف کن
    if (val < a || val > b) {
      arr.splice(i, 1);
      i--;
    }
  }

}

let arr = [5, 3, 8, 1];

filterRangeInPlace(arr, 1, 4); // تمام اعداد به جز 1 تا 4 را حذف کرد

alert( arr ); // [3, 1]
function filterRangeInPlace(arr, a, b) {

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

    // remove if outside of the interval
    if (val < a || val > b) {
      arr.splice(i, 1);
      i--;
    }
  }

}

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

اهمیت: 4
let arr = [5, 2, 1, -10, 8];

// ... کد شما برای مرتب کردن آرایه با ترتیب نزولی

alert( arr ); // 8, 5, 2, 1, -10
let arr = [5, 2, 1, -10, 8];

arr.sort((a, b) => b - a);

alert( arr );
اهمیت: 5

ما آرایه‌ای از رشته‌ها arr داریم. ما می‌خواهیم یک کپی مرتب شده از آن را داشته باشیم اما arr را تغییر ندهیم.

یک تابع copySorted(arr) بسازید که چنین کپی‌ای را برگرداند.

let arr = ["HTML", "JavaScript", "CSS"];

let sorted = copySorted(arr);

alert( sorted ); // CSS, HTML, JavaScript
alert( arr ); // HTML, JavaScript, CSS (بدون تغییر)

برای ایجاد یک کپی و اجرای مرتب‌سازی روی آن، می‌توانیم از slice() استفاده کنیم:

function copySorted(arr) {
  return arr.slice().sort();
}

let arr = ["HTML", "JavaScript", "CSS"];

let sorted = copySorted(arr);

alert( sorted );
alert( arr );
اهمیت: 5

یک تابع سازنده Calculator بسازید که شیءهای ماشین حساب «قابل توسعه» می‌سازد.

این تکلیف از دو بخش تشکیل شده است.

  1. اول، متد calculate(str) را بسازید که یک رشته مانند "1 + 2" را در شکل «عدد عملگر عدد» دریافت می‌کند و نتیجه را برمی‌گرداند. این متد باید جمع + و منها - را متوجه شود.

    مثالی از کاربرد آن:

    let calc = new Calculator;
    
    alert( calc.calculate("3 + 7") ); // 10
  2. سپس متد addMethod(name, func) را اضافه کنید که به ماشین حساب یک عملیات جدید را آموزش می‌دهد. این متد اسم عملگر name و تابع دو آرگومانی func(a,b) که عملیات را پیاده‌سازی می‌کند را دریافت می‌کند.

    برای مثال، بیایید عمل ضرب *، تقسیم / و به توان رساندن ** را اضافه کنیم:

    let powerCalc = new Calculator;
    powerCalc.addMethod("*", (a, b) => a * b);
    powerCalc.addMethod("/", (a, b) => a / b);
    powerCalc.addMethod("**", (a, b) => a ** b);
    
    let result = powerCalc.calculate("2 ** 3");
    alert( result ); // 8
  • پرانتز یا عبارات پیچیده در این تکلیف وجود ندارند.
  • اعداد و عملگر دقیقا به یک فاصله خالی محدود می‌شوند.
  • اگر دوست داشته باشید می‌توانید مدیریت ارور را هم اضافه کنید.

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

  • لطفا در نظر داشته باشید که متدها چگونه ذخیره شده‌اند. آنها به سادگی به ویژگی this.methods اضافه شده‌اند.
  • تمام آزمایش‌ها و تبدیلات عددی در متد calculate انجام می‌شوند. در آینده ممکن است برای پشتیبانی از عبارات پیچیده‌تر توسعه بیابد.
function Calculator() {

  this.methods = {
    "-": (a, b) => a - b,
    "+": (a, b) => a + b
  };

  this.calculate = function(str) {

    let split = str.split(' '),
      a = +split[0],
      op = split[1],
      b = +split[2];

    if (!this.methods[op] || isNaN(a) || isNaN(b)) {
      return NaN;
    }

    return this.methods[op](a, b);
  };

  this.addMethod = function(name, func) {
    this.methods[name] = func;
  };
}

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

اهمیت: 5

شما یک آرایه از شیءهای user دارید که هر کدام دارای user.name هستند. کدی بنویسید که آن را به آرایه‌ای از اسم‌ها تبدیل می‌کند.

برای مثال:

let john = { name: "John", age: 25 };
let pete = { name: "Pete", age: 30 };
let mary = { name: "Mary", age: 28 };

let users = [ john, pete, mary ];

let names = /* ... کد شما */

alert( names ); // John, Pete, Mary
let john = { name: "John", age: 25 };
let pete = { name: "Pete", age: 30 };
let mary = { name: "Mary", age: 28 };

let users = [ john, pete, mary ];

let names = users.map(item => item.name);

alert( names ); // John, Pete, Mary
اهمیت: 5

شما آرایه‌ای از شیءهای user دارید که هر کدام دارای name، surname و id هستند.

کدی برای ساختن یک آرایه دیگر از آن بنویسید که شامل شیءهای دارای id و fullName است، که fullName از name و suname ایجاد می‌شود.

برای مثال:

let john = { name: "John", surname: "Smith", id: 1 };
let pete = { name: "Pete", surname: "Hunt", id: 2 };
let mary = { name: "Mary", surname: "Key", id: 3 };

let users = [ john, pete, mary ];

let usersMapped = /* ... کد شما ... */

/*
usersMapped = [
  { fullName: "John Smith", id: 1 },
  { fullName: "Pete Hunt", id: 2 },
  { fullName: "Mary Key", id: 3 }
]
*/

alert( usersMapped[0].id ) // 1
alert( usersMapped[0].fullName ) // John Smith

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

let john = { name: "John", surname: "Smith", id: 1 };
let pete = { name: "Pete", surname: "Hunt", id: 2 };
let mary = { name: "Mary", surname: "Key", id: 3 };

let users = [ john, pete, mary ];

let usersMapped = users.map(user => ({
  fullName: `${user.name} ${user.surname}`,
  id: user.id
}));

/*
usersMapped = [
  { fullName: "John Smith", id: 1 },
  { fullName: "Pete Hunt", id: 2 },
  { fullName: "Mary Key", id: 3 }
]
*/

alert( usersMapped[0].id ); // 1
alert( usersMapped[0].fullName ); // John Smith

لطفا در نظر داشته باشید که در تابع‌های کمانی ما باید از پرانتزهای اضافی استفاده کنیم.

نمی‌توانیم اینگونه بنویسیم:

let usersMapped = users.map(user => {
  fullName: `${user.name} ${user.surname}`,
  id: user.id
});

همانطور که به یاد داریم، دو نوع تابع کمانی وجود دارد: بدون بدنه value => expr و همراه با بدنه value => {...}.

اینجا جاوااسکریپت با } به عنوان آغاز بدنه تابع رفتار می‌کند نه آغاز شیء. راه حل در پیچیدن آنها درون یک «پرانتر» است:

let usersMapped = users.map(user => ({
  fullName: `${user.name} ${user.surname}`,
  id: user.id
}));

حالا درست است.

اهمیت: 5

تابع sortByName(users) را بنویسید که آرایه‌ای از شیءهای شامل age دریافت می‌کند و آنها را بر اساس age مرتب می‌کند.

برای مثال:

let john = { name: "John", age: 25 };
let pete = { name: "Pete", age: 30 };
let mary = { name: "Mary", age: 28 };

let arr = [ john, pete, mary ];

sortByName(arr);

// now: [john, mary, pete]
alert(arr[0].name); // Mary
alert(arr[2].name); // Pete
function sortByAge(arr) {
  arr.sort((a, b) => a.age - b.age);
}

let john = { name: "John", age: 25 };
let pete = { name: "Pete", age: 30 };
let mary = { name: "Mary", age: 28 };

let arr = [ john, pete, mary ];

sortByName(arr);

// [john, mary, pete] :حالا آرایه مرتب شده به این شکل است
alert(arr[0].name); // John
alert(arr[2].name); // Pete
اهمیت: 3

تابع shuffle(array) را بنویسید که المان‌های آرایه را بُر می‌زند (به طور تصادفی ترتیب‌بندی می‌کند).

هر بار فراخوانی shuffle ممکن است به ترتیب متفاوتی از المان‌ها منجر شود. برای مثال:

let arr = [1, 2, 3];

shuffle(arr);
// arr = [3, 2, 1]

shuffle(arr);
// arr = [2, 1, 3]

shuffle(arr);
// arr = [3, 1, 2]
// ...

ترتیب تمام المان‌ها باید احتمال برابر داشته باشند. برای مثال، [1,2,3] می‌تواند به شکل [1,2,3] یا [1,3,2] یا [3,1,2] و… مرتب شود که احتمال هر مورد برابر است.

راه حل ساده می‌تواند اینگونه باشد:

function shuffle(array) {
  array.sort(() => Math.random() - 0.5);
}

let arr = [1, 2, 3];
shuffle(arr);
alert(arr);

این کد تا حدی کار می‌کند، چون Math.random() - 0.5 یک عدد تصادفی است که ممکن است مثبت یا منفی باشد، پس تابع مرتب‌کننده المان‌ها را به صورت تصادفی مرتب می‌کند.

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

برای مثال، کد زیر را در نظر بگیرید. این کد shuffle را 1000000 بار اجرا می‌کند و تعداد وقوع تمام نتایج ممکن را می‌شمارد:

function shuffle(array) {
  array.sort(() => Math.random() - 0.5);
}

// شمارش تعداد وقوع تمام جایجایی‌های ممکن
let count = {
  '123': 0,
  '132': 0,
  '213': 0,
  '231': 0,
  '321': 0,
  '312': 0
};

for (let i = 0; i < 1000000; i++) {
  let array = [1, 2, 3];
  shuffle(array);
  count[array.join('')]++;
}

// نمایش شمارش تمام جابجایی‌های ممکن
for (let key in count) {
  alert(`${key}: ${count[key]}`);
}

نتیجه یک مثال (با وابستگی به موتور جاوااسکریپت):

123: 250706
132: 124425
213: 249618
231: 124880
312: 125148
321: 125223

می‌توانیم جانب‌داری را به طور واضح ببینیم: 123 و 213 نسبت به بقیه بیشتر رخ می‌دهند.

نتیجه کد ممکن است بین موتورهای جاوااسکریپت متفاوت باشد، اما می‌توانیم به سادگی ببینیم که این شیوه قابل اطمینان نیست.

چرا درست کار نمی‌کند؟ به طور کلی، sort یک «جعبه سیاه» است: ما یک آرایه و تابع مقایسه را درون آن می‌اندازیم و توقع داریم که آرایه مرتب شود. اما به دلیل منطق تصادفی مقایسه، جعبه سیاه دیوانه می‌شود و اینکه چگونه دیوانه می‌شود بستگی به پیاده‌سازی دارد که بین موتورها متفاوت است.

راه‌های خوب دیگری هم برای انجام تکلیف وجود دارد. برای مثال، یک الگوریتم عالی به اسم بُرزدن Fisher-Yates وجود دارد. ایده اینگونه است که آرایه را برعکس بررسی کنیم و هر المان را با یک المان تصادفی قبل خود جابجا کنیم:

function shuffle(array) {
  for (let i = array.length - 1; i > 0; i--) {
    let j = Math.floor(Math.random() * (i + 1)); // i ایندکس تصادفی بین 0 تا

    // را جابجا کن array[j] و array[i] المان‌های
    // ما از سینتکس «مقداردهی تخریب‌ساختار» برای انجام آن استفاده می‌کنیم
    // شما درباره سینتکس آن در فصل‌های آینده بیشتر یاد می‌گیرید
    // به طور یکسان می‌توان نوشت:
    // let t = array[i]; array[i] = array[j]; array[j] = t
    [array[i], array[j]] = [array[j], array[i]];
  }
}

بیایید مانند قبل آن را آزمایش کنیم:

function shuffle(array) {
  for (let i = array.length - 1; i > 0; i--) {
    let j = Math.floor(Math.random() * (i + 1));
    [array[i], array[j]] = [array[j], array[i]];
  }
}

// شمارش تعداد وقوع تمام جایجایی‌های ممکن
let count = {
  '123': 0,
  '132': 0,
  '213': 0,
  '231': 0,
  '321': 0,
  '312': 0
};

for (let i = 0; i < 1000000; i++) {
  let array = [1, 2, 3];
  shuffle(array);
  count[array.join('')]++;
}

// نمایش شمارش تمام جابجایی‌های ممکن
for (let key in count) {
  alert(`${key}: ${count[key]}`);
}

خروجی نمونه:

123: 166693
132: 166647
213: 166628
231: 167517
312: 166199
321: 166316

الان بنظر خوب می‌آید: تمام جایجایی‌ها با احتمال یکسان رخ می‌دهند.

همچنین، الگوریتم Fisher-Yates که از نظر عملکرد بهینه می‌باشد، بسیار بهتر است، و هیچ مرتب‌سازی‌ای بر روی آن وجود ندارد.

اهمیت: 4

تابع getAverageAge(users) بنویسید که آرایه‌ای از اشیای دارای ویژگی age می‌گیرد و میانگین سن را برمی‌گرداند.

فرمول میانگین به این صورت است: (age1 + age2 + ... + ageN) / N.

برای مثال:

let john = { name: "John", age: 25 };
let pete = { name: "Pete", age: 30 };
let mary = { name: "Mary", age: 29 };

let arr = [ john, pete, mary ];

alert( getAverageAge(arr) ); // (25 + 30 + 29) / 3 = 28
function getAverageAge(users) {
  return users.reduce((prev, user) => prev + user.age, 0) / users.length;
}

let john = { name: "John", age: 25 };
let pete = { name: "Pete", age: 30 };
let mary = { name: "Mary", age: 29 };

let arr = [ john, pete, mary ];

alert( getAverageAge(arr) ); // 28
اهمیت: 4

فرض کنیم که arr یک آرایه است.

تابع unique(arr) را بنویسید که آرایه‌ای شامل المان‌های یکتای arr را برمی‌گرداند.

برای مثال:

function unique(arr) {
  /* کد شما */
}

let strings = ["Hare", "Krishna", "Hare", "Krishna",
  "Krishna", "Krishna", "Hare", "Hare", ":-O"
];

alert( unique(strings) ); // Hare, Krishna, :-O

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

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

  • برای هر المان بررسی می‌کنیم که آیا آرایه حاصل دارای آن المان هست یا نه.
  • اگر بود، المان را نادیده می‌گیریم، در غیر این صورت آن را به نتایج اضافه می‌کنیم.
function unique(arr) {
  let result = [];

  for (let str of arr) {
    if (!result.includes(str)) {
      result.push(str);
    }
  }

  return result;
}

let strings = ["Hare", "Krishna", "Hare", "Krishna",
  "Krishna", "Krishna", "Hare", "Hare", ":-O"
];

alert( unique(strings) ); // Hare, Krishna, :-O

این کد کار می‌کند، اما احتمالا یک اشکال عملکردی دارد.

متد result.includes(str) درون آرایه result را بررسی می‌کند و هر المان را با str مقایسه می‌کند تا المان مورد نظر را پیدا کند.

ینابراین اگر 100 المان درون result وجود داشته باشد و هیچ کدام با str برابر نباشد، سپس تمام result را بررسی می‌کند و دقیقا 100 مقایسه انجام می‌دهد. و اگر result بزرگ باشد، مثلا 10000، سپس به تعداد 10000 مقایسه وجود خواهد داشت.

این به خودی خود مشکل محسوب نمی‌شود، چون موتورهای جاوااسکریپت بسیار سریع هستند، پس بررسی یک آرایه با 10000 المان چند میکروثانیه طول می‌کشد.

اما ما در حلقه for چنین آزمایشی را برای هر المان درون arr انجام می‌دهیم.

پس اگر arr.length برابر با 10000 باشد ما چیزی مثل 10000*10000 = 100 میلیون مقایسه خواهیم داشت. این مقدار بسیار زیاد است.

بنابراین این راه حل تنها برای آرایه‌های کوچک مناسب است.

بعدا در فصل ساختارهای Map و Set ما یاد می‌گیریم که چگونه آن را بهینه کنیم.

function unique(arr) {
  let result = [];

  for (let str of arr) {
    if (!result.includes(str)) {
      result.push(str);
    }
  }

  return result;
}

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

اهمیت: 4

بیایید فرض کنیم که ما یک آرایه از کاربران به شکل {id:..., name:..., age:... } دریافت کرده‌ایم.

یک تابع groupById(arr) بسازید که یک شیء از آن ایجاد می‌کند، که id به عنوان کلید و المان‌های آرایه به عنوان مقدار موجود هستند.

برای مثال:

let users = [
  {id: 'john', name: "John Smith", age: 20},
  {id: 'ann', name: "Ann Smith", age: 24},
  {id: 'pete', name: "Pete Peterson", age: 31},
];

let usersById = groupById(users);

/*
// بعد از فراخوانی ما باید این را داشته باشیم:

usersById = {
  john: {id: 'john', name: "John Smith", age: 20},
  ann: {id: 'ann', name: "Ann Smith", age: 24},
  pete: {id: 'pete', name: "Pete Peterson", age: 31},
}
*/

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

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

لطفا از متد .reduce در راه حل استفاده کنید.

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

function groupById(array) {
  return array.reduce((obj, value) => {
    obj[value.id] = value;
    return obj;
  }, {})
}

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

نقشه آموزش