JavaScriptのArrayについて

# 初めに
今回は配列に関連するメソッドと、配列に**empty slot**を見つけるメソッドをまとめてみました。

https://ja.javascript.info/array-methods

https://jsprimer.net/basic/array/

# Methods
## Add/Remove
### Remove: Array.prototype.pop() & Array.prototype.shift()
`pop()`末尾から取り出す。
`shift()`先頭から取り出す。
（いずれも元の配列を変更する。）
```jsx
let fruits = ['Apple', 'Orange', 'Lemon', 'Banana', 'Watermelon'];
fruits.pop()
console.log(fruits); // [ 'Apple', 'Orange', 'Lemon', 'Banana' ]
console.log(fruits.pop()); // Banana
console.log(fruits); // [ 'Apple', 'Orange', 'Lemon' ]
```

### Add: Array.prototype.push() & Array.prototype.unshift()
`push()`：末尾に入れる。
`unshift()`：先頭に入れる。
（いずれも元の配列を変更する。）
```jsx
let fruits = ['Apple', 'Orange', 'Lemon', 'Banana', 'Watermelon'];
fruits.shift()
console.log(fruits); // [ 'Orange', 'Lemon', 'Banana', 'Watermelon' ]
console.log(fruits.shift()) // Orange
console.log(fruits); // [ 'Lemon', 'Banana', 'Watermelon' ]
```

## Delete/Replace/Copy
### Array.prototype.splice()
`splice()`：要素を削除や置き換え、または挿入する。
（元の配列を変更する。）
```jsx
// arr.splice(start[, deleteCount, insertItem1, ..., insetItemN])
let fruits = ['Apple', 'Orange', 'Lemon', 'Banana', 'Watermelon'];

// delete
console.log(fruits.splice(0, 1)); // [ 'Apple' ]
console.log(fruits); // [ 'Orange', 'Lemon', 'Banana', 'Watermelon' ]

// replace
console.log(fruits.splice(0, 2, 'Pear')); // [ 'Orange', 'Lemon' ]
console.log(fruits); // [ 'Pear', 'Banana', 'Watermelon' ]

// insert
// fruits.splice(0, 0, 'Apple', 'Orange');
// console.log(fruits) // [ 'Apple', 'Orange', 'Pear', 'Banana', 'Watermelon' ]
let arr = ['Apple', 'Orange'];
fruits.splice(0, 0, ...arr);
console.log(fruits); // [ 'Apple', 'Orange', 'Pear', 'Banana', 'Watermelon' ]
```
負のインデックスは末尾から逆行する意味で、
`-1`：最後の要素から逆行。（＝`arr.length -1`）
`arr.length`：最後の要素の後から。
```jsx
// negative index
console.log(fruits.splice(-1, 1)) // [ 'Watermelon' ]
console.log(fruits) // [ 'Apple', 'Orange', 'Lemon', 'Banana' ]

// fruits.splice(-1, 0, 'Watermelon')
// console.log(fruits) // [ 'Apple', 'Orange', 'Lemon', 'Watermelon', 'Banana' ]
fruits.splice(fruits.length, 0, 'Watermelon');
console.log(fruits) // [ 'Apple', 'Orange', 'Lemon', 'Banana', 'Watermelon' ]
```

### Array.prototype.slice()
`slice()`：要素を浅いコピーして新しい配列を返す。
```jsx
// arr.slice(start[[, end]])
let newArr = [
  { list: ['one Egg', 'two cups Water'] },
  ['We need flour to bread!'],
  'Apple'
];

// shallow copy
let spreadArr = [...newArr];
let sliceArr = newArr.slice();

spreadArr[0].list = ['two Egg', 'two cups Milk'];
sliceArr[1][0] = ['Maybe rice flour is better.'];
spreadArr[2] = ['Apple', 'Orange'];
spreadArr[2][1] = ['No Orange!'];

console.log(newArr);
// [
//   { list: [ 'two Egg', 'two cups Milk' ] },
//   [ [ 'Maybe rice flour is better.' ] ],
//   'Apple'
// ]
console.log(spreadArr);
// [
//   { list: ['two Egg', 'two cups Milk'] },
//   [['Maybe rice flour is better.']],
//   ['Apple', ['No Orange!']]
// ]
console.log(sliceArr);
// [
//   { list: [ 'two Egg', 'two cups Milk' ] },
//   [ [ 'Maybe rice flour is better.' ] ],
//   'Apple'
// ]
```
`newArr[0]`：`object type`
`newArr[1]`：`object type`
`newArr[2]`：`primitive type`
JavaScriptのオブジェクトは`pass by sharing`（参照先の共有）なので、元の配列の要素がオブジェクトタイプであれば、浅いコピーした配列たちでは`property value`へのリアサイン（`spreadArr[0].list`）や、ネストされた配列へのリアサイン（`sliceArr[1][0]`）などの操作が元の配列を変える。
それに対し、浅いコピーした配列`spreadArr[2]`でいくら値をリアサインしてもプリミティブタイプの`newArr[2]`は変えられません。そして`newArr[2]`と参照先を共有している配列`sliceArr`も変えられません。

この場合は、値が変えられたのは配列`spreadArr`だけです。そしてもしここで配列`spreadArr`と参照を共有する配列`copySpreadArr`で、オブジェクトタイプの`copySpreadArr[2][1]`にリアサインしたら、`spreadArr[2][1]`も変えられる。
```jsx
let copySpreadArr = spreadArr.slice();
copySpreadArr[2][1] = 'cinnamon';

console.log(spreadArr)
// [
//   { list: [ 'two Egg', 'two cups Milk' ] },
//   [ [ 'Maybe rice flour is better.' ] ],
//   [ 'Apple', 'cinnamon' ]
// ]
```
配列のコピー操作では、`Spread syntax`、`Array.prototype.slice()`、`Array.prototype.concat()`、`Array.from()`は浅いコピーです。

https://developer.mozilla.org/ja/docs/Glossary/Shallow_copy

プリミティブタイプの値は`immutable`（不可変）、状態を変えることができないため、浅いコピーとは深いコピーとは関係ないです。プリミティブタイプのコピーはいつも新しいメモリー位置に記録されるので`real copy`と呼ばれる。

https://javascript.plainenglish.io/shallow-copy-and-deep-copy-in-javascript-a0a04104ab5c

`slice()`の第1引数は指定したい場合は`0`です。
第2引数は常に目標配列の`length`です。
```jsx
// copy all or apart of array
let newArr = fruits.slice(); // no specific index means copy all
console.log(newArr); // [ 'Apple', 'Orange', 'Lemon', 'Banana', 'Watermelon' ]
newArr = fruits.slice(2); // start from index 2
console.log(newArr); // [ 'Lemon', 'Banana', 'Watermelon' ]
newArr = fruits.slice(2, 4); // start from index 2, end at index 4 (not included)
console.log(newArr); // [ 'Lemon', 'Banana' ]

// negative index
newArr = fruits.slice(-1)
console.log(newArr); // [ 'Watermelon' ]
newArr = fruits.slice(-1, fruits.length)
console.log(newArr); // [ 'Watermelon' ]
newArr = fruits.slice(-2)
console.log(newArr); // [ 'Banana', 'Watermelon' ]

newArr = fruits.slice(2, -2)
console.log(newArr); // [ 'Lemon' ]
```

## Merge
### Array.prototype.concat()
`concat()`：引数を浅いコピーして結合する。
（新しい配列を返す。）
配列はどんなデータ型でも格納できる。`concat()`の引数が配列ではなくても浅いコピーして結合する。
```jsx
// oldArr.concat(arg1, arg2...)
let fruits = ['Apple', 'Orange', 'Banana'];
let vegetables = ['Cabbage'];
let obj = {
  a: '0'
}

let newArr = fruits.concat(vegetables, obj, 'abc');
console.log(newArr);
// [ 'Apple', 'Orange', 'Banana', 'Cabbage', { a: '0' }, 'abc' ]
```
`concat()`は要素を浅いコピーするが配列の平坦化はできません。
```jsx
// return one-dimensional array
let fruits = ['Apple', ['Orange', ['Banana']]];
let vegetables = ['Carrot', 'Cabbage'];

let newArr = fruits.concat(vegetables);
console.log(newArr);
// ['Apple', ['Orange', ['Banana']], 'Carrot', 'Cabbage']
```
浅いコピーなのでオブジェクトタイプの値に操作すると元の配列も変えられる。
```jsx
let fruits = ['Apple', ['Orange', ['Banana']]];
let vegetables = [{ root: 'Carrot' }, { leaf: 'Cabbage' }];

let newArr = fruits.concat(vegetables);
console.log(newArr);
// [
//   'Apple',
//   ['Orange', ['Banana']],
//   { root: 'Carrot' },
//   { leaf: 'Cabbage' }
// ]

newArr[1][1] = ['Lemon'];
console.log(fruits);
// [ 'Apple', [ 'Orange', [ 'Lemon' ] ] ]

newArr[2].root = 'Radish';
console.log(vegetables);
// [ { root: 'Radish' }, { leaf: 'Cabbage' } ]
```
### [Symbol.isConcatSpreadable]
```jsx
// Symbol.isConcatSpreadable
let arr = [1, 2];
let arrayLike = {
  0: 'Apple',
  1: 'Orange',
  [Symbol.isConcatSpreadable]: true,
  length: 2
};
console.log(arr.concat(arrayLike));
// [ 1, 2, 'Apple', 'Orange' ]
//
arrayLike = {
  0: 'Apple',
  1: 'Orange',
  [Symbol.isConcatSpreadable]: false,
  length: 2
};
console.log(arr.concat(arrayLike));
// [
//   1,
//   2,
//   {
//     '0': 'Apple',
//     '1': 'Orange',
//     length: 2,
//     [Symbol(Symbol.isConcatSpreadable)]: false
//   }
// ]
```

## Iterate
### `thisArg`
配列のイテレーターメソッドは、ほとんど最後の引数に`thisArg`が設置されています。（例えば`sort()`には`thisArg`ありません。）
```jsx
let config = {
  minAge: 18,
  maxAge: 30,
  match(user) {
    return (user.age >= this.minAge) && (user.age < this.maxAge);
  }
};

let veterans = {
  minAge: 30,
  maxAge: 40
}

let novice = {
  minAge: 10,
  maxAge: 18
}

let participant = [
  { name: 'Ada', age: 32 },
  { name: 'Leon', age: 25 },
  { name: 'Sherry', age: 16 },
  { name: 'Claire', age: 24 },
]

let candidates = participant.filter(config.match, config);
console.log(candidates);
// [ { name: 'Leon', age: 25 }, { name: 'Claire', age: 24 } ]
candidates = participant.filter(config.match, veterans);
console.log(candidates);
// [ { name: 'Ada', age: 32 } ]
candidates = participant.filter(config.match, novice);
console.log(candidates);
// [ { name: 'Sherry', age: 16 } ]
// note: if we use 'this' in 'config.match', we should set reference to 'thisArg'
```
上の例では、`thisArg`で`filter()`のコールバック`config.match`に`this`の参照先を設置しないとエラーになります。
`thisArg`は一部の人（私も）にとって直観的に読みくいのであまり使われていませんが、実際`thisArg`の設置を通してほかのオブジェクトに参照して分岐処理ができます。
下は整理したあとのコードです。
```jsx
let config = {
  candidates: { minAge: 18, maxAge: 30 },
  veterans: { minAge: 30, maxAge: 40 },
  novice: { minAge: 10, maxAge: 18 },
  match(user) {
    return (user.age >= this.minAge) && (user.age < this.maxAge);
  }
};
let participant = [
  { name: 'Ada', age: 32 },
  { name: 'Leon', age: 25 },
  { name: 'Sherry', age: 16 },
  { name: 'Claire', age: 24 },
];
console.log(participant.filter(config.match, config.candidates))
// [ { name: 'Leon', age: 25 }, { name: 'Claire', age: 24 } ]
console.log(participant.filter(config.match, config.veterans))
// [ { name: 'Ada', age: 32 } ]
console.log(participant.filter(config.match, config.novice))
// [ { name: 'Sherry', age: 16 } ]
```

下のようにコールバックではなく関数内部での呼び出しでは`config.match`の`this`の参照がエラーにならないのです。
```jsx
// or call it simply
let candidates = participant.filter((user) => config.match(user));
console.log(candidates);
// [ { name: 'Leon', age: 25 }, { name: 'Claire', age: 24 } ]
```

### Array.prototype.forEach()
`forEach`：全要素を走査する。
（元の配列を変更しない、いつも`undefined`を返す。）
（**empty slot**をスキップする。）
```jsx
// arr.forEach(function(item, index, array), thisArg)
let fruits = ['Apple', 'Orange', 'Lemon'];
fruits.forEach((item, index, array) => {
  console.log(`${item} is at index ${index} in ${array}`);
});
// Apple is at index 0 in Apple,Orange,Lemon
// Orange is at index 1 in Apple,Orange,Lemon
// Lemon is at index 2 in Apple,Orange,Lemon
```
`forEach`はコールバックで全要素を走査する。自体は何も返さないので常に`undefined`（`return`の値を指定しない場合のデフォルト）です。
```jsx
// forEach always return undefined
console.log(fruits.forEach((item, index) => {
  return item === 'Orange' ? index : 'undefined';
}))
// undefined
// note: if it can't return values of callback function, we shouldn't use it in Promise chain
// note: forEach always iterate all the elements, that is why it always return undefined

let arr = [];
console.log(fruits.forEach((item, index) => {
  item === 'Orange' ? arr.push(index) : 'undefined';
}))
console.log(arr); // [ 1 ]
```
これはメリットでもデメリットでもあります。
メリットは全要素を一度イテレータする。
デメリットは`return`は指定しないので早期終了ができないし、コールバックからの返り値も受け取らない。この同期関数の特徴が`Promise`や非同期関数の使用に不向きです。プロミスチェインでは値を`return`しなければ意味をもたらさない、`await`非同期関数からの結果も待ちません。
```jsx
let arr = [1, 2, 3];
let sum = 0;

// Promise
const sumFn = async (a, b) => a + b;

arr.forEach(async (item) => {
  sum = await sumFn(sum, item);
});
console.log(sum); // 0
```
プロミス`sumFn`では`arr`の要素と今の`sum`の和を返すの待つ。しかし`forEach`は要素の走査に集中するため`await sumFn(sum, item)`の返り値がスキップされ、`sum`は`0`のままでした。

もし元の配列が走査中に変更されたら、`forEach()`の`index`も変えられてしまう。
```jsx
let fruits = ['Apple', 'Orange', 'Lemon'];
fruits.forEach((fruit) => {
  console.log(fruit);
  if (fruit === 'Apple') {
    fruits.shift();
  }
});
// Apple
// Lemon

console.log(fruits)
// [ 'Orange', 'Lemon' ]
```
`'Apple'`が削除され、残りの`'Orange'`と`'Lemon'`の`index`も変えられてしまったので次の要素は`index 1`つまり`'Lemon'`からです。

### Return Boolean: Array.prototype.filter()
`filter()`：設置した条件に`true`を答えた要素だけ取り出す。
（新しい配列を返す。）
（**empty slot**をスキップする。）
```jsx
// arr.filter(function(item, index, array), thisArg)
let users = [
  { id: 1, name: 'Jon', score: 90 },
  { id: 2, name: 'Robin', score: 75 },
  { id: 3, name: 'Sansa', score: 80 },
  { id: 4, name: 'Arya', score: 100 },
  { id: 5, name: 'Bran', score: 120 },
  { id: 6, name: '', score: 1000 }
];

let over100 = users.filter((user) => user.score >= 100);
console.log(over100)
// [
//   { id: 4, name: 'Arya', score: 100 },
//   { id: 5, name: 'Bran', score: 120 },
//   { id: 6, name: '', score: 1000 }
// ]

// filter() returns elements that condition is true
let nameOfOver100 = users.filter((user) => {
  if (user.score >= 100) return user.name
});
console.log(nameOfOver100)
// [
//   { id: 4, name: 'Arya', score: 100 },
//   { id: 5, name: 'Bran', score: 120 }
// ]
```
`filter()`は濾過器です。多重の条件をコールバックに設置して`true`を返す要素だけが新しい配列に現れる。

検索にもよく使われています。
```jsx
function findNameHasA(arr, query) {
  return arr.filter((item) => {
    return item.name.toLowerCase().indexOf(query.toLowerCase()) !== -1
  })
}
console.log(findNameHasA(users, 'a'));
// [
//   { id: 3, name: 'Sansa', score: 80 },
//   { id: 4, name: 'Arya', score: 100 },
//   { id: 5, name: 'Bran', score: 120 }
// ]
```

（気になるところのメモ）
配列が変更される（要素の変更／追加／削除）時の動きについてどう解釈するかは分からない。イテレータ中の配列の変更は元の配列にも影響するが、追加された要素がイテレータされない。

https://developer.mozilla.org/ja/docs/Web/JavaScript/Reference/Global_Objects/Array/filter#%E5%88%9D%E6%9C%9F%E3%81%AE%E9%85%8D%E5%88%97%E3%81%B8%E3%81%AE%E5%BD%B1%E9%9F%BF_%E5%A4%89%E6%9B%B4%E3%80%81%E8%BF%BD%E5%8A%A0%E3%80%81%E5%89%8A%E9%99%A4

### Transform Array: Array.prototype.map()
`map()`：コールバックで要素を処理し、新しい配列に返す。
（新しい配列を返す。）
（**empty slot**をスキップして処理しないが、**empty slot**を保留する。）

`forEach()`は要素を走査するが何も返しません。
`map()`は処理された要素を配列として返してくれます。
```jsx
// arr.map(function(item, index, array), thisArg)
let users = [
  { id: 1, name: 'Jon', score: 90 },
  { id: 2, name: 'Robin', score: 75 },
  { id: 3, name: 'Sansa', score: 80 },
  { id: 4, name: 'Arya', score: 100 },
  { id: 5, name: 'Bran', score: 120 },
  { id: 6, name: '', score: 1000 }
];

let nameLengths = users.map((user) => user.name.length);
console.log(nameLengths) // [ 3, 5, 5, 4, 4, 0 ]

// compare with forEach
let arr = [];
users.forEach((user) => {
  arr.push(user.name.length);
});
console.log(arr); // [ 3, 5, 5, 4, 4, 0 ]
```
`call()`で反復可能な文字列を指定することができます。もちろん`forEach`も同じことできますが少し手間がかかります。
```jsx
let arr = ['1', '2', '3'];
console.log(arr.map(Number)); // [ 1, 2, 3 ]

// string is iterable
let map = Array.prototype.map
let getCodePoint = map.call('I am string', (item) => item.codePointAt(0))
console.log(getCodePoint);
// [
//   73, 32, 97, 109,
//   32, 115, 116, 114,
//   105, 110, 103
// ]

// compare with forEach
let arr = []
let foreach = Array.prototype.forEach
foreach.call('I am string', (item) => arr.push(item.codePointAt(0)));
console.log(arr)
// [
//   73, 32, 97, 109,
//   32, 115, 116, 114,
//   105, 110, 103
// ]
```
`map()`と`forEach()`は`call()`を使って呼び出すのは文字列と配列だけです。（配列なら`call`を使う理由はないけど。）反復可能なオブジェクトなら`for...of`で処理します。

`map()`はよく使われる汎用性の高いメソッドですが、配列を返さないまたは返した配列を使用しないならほかのメソッドを使ったほうがよさそうです。
```jsx
// compare with filter
let over100 = users.filter((user) => user.score >= 100);
console.log(over100)

// let mappedOver100 = users.map((user) => {
//   if (user.score >= 100) return user
//   return
// });
// console.log(mappedOver100);
// // [
// //   undefined,
// //   undefined,
// //   undefined,
// //   { id: 4, name: 'Arya', score: 100 },
// //   { id: 5, name: 'Bran', score: 120 },
// //   { id: 6, name: '', score: 1000 }
// // ]

// in this case, using filter() or forEach() is better
let mappedOver100 = []
users.map((user) => {
  if (user.score >= 100) {
    mappedOver100.push(user)
  }
});
console.log(mappedOver100);
// [
//   { id: 4, name: 'Arya', score: 100 },
//   { id: 5, name: 'Bran', score: 120 },
//   { id: 6, name: '', score: 1000 }
// ]
```

### Calculate and Combine: Array.prototype.reduce()
`reduce()`：全要素を結合させるメソッド。
（結合した値を返す。）
（**empty slot**をスキップする。）
```jsx
// arr.reduce(function (previousValue, currentValue, index, array), initialValue);
let arr = [1, 2, 3, 4, 5];
// if initialValue is not specified, previousValue will be arr[0]
let result = arr.reduce((previousValue, current) => {
  return (previousValue + current);
})
console.log(result); // 15

result = arr.reduce((a, b) => (a + b), 100);
console.log(result); // 115

arr = []
// result = arr.reduce((a, b) => (a + b));
// console.log(result);
// // TypeError: Reduce of empty array with no initial value

result = arr.reduce((a, b) => (a + b), 0);
console.log(result); // 0
```
初期値がある場合は`previousValue`は初期値。
ない場合は`previousValue`は配列の最初の要素。

文字列の要素も結合することできます。
```jsx
let arr = ['a', 'b', 'c'];
let result = arr.reduce((a, b) => (a + b));
console.log(result); // abc

arr = [0, 'a', 1, 'b', 2, 3];
result = arr.reduce((a, b) => (a + b));
console.log(result); // 0a1b23

arr = [0, 1, 2, 'a', 3, 'b'];
result = arr.reduce((a, b) => (a + b));
console.log(result); // 3a3b
```
しかし数値と文字列が混在しているなら勝手に型変換されたり、予期せぬ結果が返してくるかもしれません。

`filter()`でいったんろ過して必要な要素だけ結合させましょう。
```jsx
result = arr.filter((item) => typeof item === 'number').reduce((a, b) => (a + b));
console.log(result); // 6
```

#### Array.prototype.reduceRight()
`reduceRight()`：全要素を結合させるメソッド。右（最後の要素）から左に実行する。
（結合した値を返す。）
```jsx
let arr = ['a', 'b', 'c'];
let result = arr.reduceRight((a, b) => (a + b));
console.log(result); // cba
```

### Sort: Array.prototype.sort()
`sort()`：要素をソートするメソッド。
（元の配列を変更する。ソートされた元の配列を返す。）
```jsx
// arr.sort(function compare(a, b))
let arr = [1, 2, 15];
console.log(arr.sort()); // [ 1, 15, 2 ]
// default function in sort() will change elements into string
console.log(arr.sort(function compare(a, b) {
  if (a > b) return 1;
  if (a < b) return -1;
  if (a === b) return 0;
})); // [ 1, 2, 15 ]
```
色んなタイプの要素が混在しているなら`filter()`から`sort()`。
```jsx
let arr = ['1', 2, 'a', -1, 5, undefined];
let sortedArr = arr.filter((item) => {
  return item && typeof item === 'number'
}).sort((a, b) => a - b);

console.log(sortedArr); // [ -1, 2, 5 ]
```

#### Number: function compare(a, b)
配列オブジェクトでもプロパティから値を取得してソートする。
```jsx
let users = [
  { id: 1, name: 'Jon', score: 90 },
  { id: 2, name: 'Robin', score: 75 },
  { id: 3, name: 'Sansa', score: 80 },
  { id: 4, name: 'Arya', score: 100 },
  { id: 5, name: 'Bran', score: 120 },
  { id: 6, name: '', score: 1000 }
];

// let sortByScore = users.sort((a, b) => {
//   if (a.score > b.score) return 1
//   if (a.score < b.score) return -1
//   return 0
// });
// console.log(sortByScore);

let sortByScore = users.sort((a, b) => a.score - b.score);
// note: a.score - b.score > 0 => ascending order
// note: b.score - a.score > 0 => descending order
console.log(sortByScore);
// [
//   { id: 2, name: 'Robin', score: 75 },
//   { id: 3, name: 'Sansa', score: 80 },
//   { id: 1, name: 'Jon', score: 90 },
//   { id: 4, name: 'Arya', score: 100 },
//   { id: 5, name: 'Bran', score: 120 },
//   { id: 6, name: '', score: 1000 }
// ]
```

#### String: String.prototype.localeCompare()
文字列のソートなら`str.localeCompare()`を使いましょう。
```jsx
// use str.localeCompare()
let alphabet = ['Banana', 'Lemon', 'apple'];
console.log(alphabet.sort((a, b) => {
  console.log(`${a}: ${a.codePointAt(0)}, ${b}: ${b.codePointAt(0)}`);
  // Lemon: 76, Banana: 66
  // apple: 97, Lemon: 76
  // apple: 97, Lemon: 76
  // apple: 97, Banana: 66
  // note: quickSort
  return a.localeCompare(b)
}));
// [ 'apple', 'Banana', 'Lemon' ]

let japanese = ['あ', 'た', 'さ', 'ア', 'タ', 'サ'];
console.log(japanese.sort((a, b) => a.localeCompare(b)));
// [ 'あ', 'ア', 'さ', 'サ', 'た', 'タ' ]
```

### reverse: Array.prototype.reverse()
`reverse()`：要素の順序を逆転させる。反転後の配列を返す。
（元の配列を変更する。）
```jsx
// arr.reverse()
let arr = ['a', 'b', 'c'];
console.log(arr.reverse()); // [ 'c', 'b', 'a' ]
```

## Array ⇔ String
### Array.prototype.join()
`join()`：要素を結合して文字列として返す。
（元の配列を変更しない。）
（`join()`は**empty slot**を`undefined`と見なす。**empty slot**、`undefined`、`null`は`''`を返す。）
```jsx
// arr.join(separator)
let arr = ['a', 'b', 'c'];
console.log(arr.join()); // a,b,c
console.log(arr.join('')); // abc
console.log(arr.join('.')); // a.b.c

arr = ['', null, undefined, , 'a'];
console.log(arr.join('.')); // ....a
```

### String.prototype.split()
`split()`：指定の区切り文字列で分割し、文字列の配列を返す。
```jsx
// str.split([separator[, limit]])
let fruits = 'Apple, Banana, Lemon';
console.log(fruits.split(',')); // [ 'Apple', ' Banana', ' Lemon' ]
console.log(fruits.split(', ')); // [ 'Apple', 'Banana', 'Lemon' ]
// note: Apple/, /Banana/, /Lemon

console.log(fruits.split(', ', 1)); // [ 'Apple' ]
```
サロゲートペアは分割されてしまうので、スプレット構文や`Array.from`などサロゲートペアを識別できるメソッドを使いましょう。

https://stackoverflow.com/a/34717402

```jsx
// split() can break surrogate pairs
let str = '𝟘𝟙𝟚𝟛';
let arr = str.split('');
console.log(arr);
// [
//   '\ud835', '\udfd8',
//   '\ud835', '\udfd9',
//   '\ud835', '\udfda',
//   '\ud835', '\udfdb'
// ]

// use spread syntax or Array.from()
arr = [...str];
console.log(arr);
// ['𝟘', '𝟙', '𝟚', '𝟛']
arr = Array.from(str);
console.log(arr);
// ['𝟘', '𝟙', '𝟚', '𝟛']
```

## String(CodePoint), Iterable object, Array-like, Array ⇒ Array
### Array.from()
`Array.from()`：要素を浅いコピーして新しい配列インスタンスを生成する。
（新しい配列を返す。）
`Array.from()`は文字列、反復可能なオブジェクト、配列風オブジェクトを配列にして返してくれます。第2引数は`map()`メソッドなのでそのまま配列操作や変形していい、非常に強力で便利なメソッドです。（おすすめ！）
```jsx
// Array.from(arg[, map[, thisArg]])
// String
let str = 'abc';
let result = Array.from(str, (item) => item.codePointAt().toString(16));
console.log(result); // [ '61', '62', '63' ]

str = '𝟘𝟙𝟚𝟛';
result = Array.from(str, (item) => item.codePointAt().toString(16));
console.log(result); // [ '1d7d8', '1d7d9', '1d7da', '1d7db' ]

// Iterable object
// Map([[key1, value1], [key2, value2]])
const map = new Map([[1, 2], [2, 4]])
console.log(map);
// Map(2) { 1 => 2, 2 => 4 }

map.set('a', 10); // .set(key, value)
console.log(map);
// Map(3) { 1 => 2, 2 => 4, 'a' => 10 }
console.log(Array.from(map));
// [ [ 1, 2 ], [ 2, 4 ], [ 'a', 10 ] ]

// Array-like
function addString() {
  // console.log(arguments);
  // // [Arguments] { '0': 1, '1': 2, '2': 3 }
  // console.log(Array.isArray(Array.from(arguments)));
  // // true
  return Array.from(arguments).join('')
}
console.log(addString('a', 'b', 'c')); // abc
console.log(addString(1, 2, 3)); // 123 // string
```
長さに合わせる配列の宣言では`new Array(length)`で配列を作るとき、要素が指定できない**empty slot**になり要素を補填してから配列を操作するのが非常に手間がかかります。`Array.from()`なら配列風のように`length`を指定し、あとは`map()`で要素を詰めればいいのです。
```jsx
// create array-like object => array
console.log(Array.from({ length: 5 }, (item, index) => index));
// [ 0, 1, 2, 3, 4 ]

const createRange = (start, end, step) => {
  return Array.from({ length: (end - start) / step + 1 }, (item, index) => {
    return start + (index * step);
  })
};
console.log(createRange(0, 4, 1))
// [ 0, 1, 2, 3, 4 ]
console.log(createRange(1, 10, 2))
// [ 1, 3, 5, 7, 9 ]
```

## Index & Includes
### Array.prototype.indexOf() & Array.prototype.lastIndexOf()
`indexOf()`：指定の要素を探す。見つかったらインデックスを返す。
`lastIndexOf()`：最後のインデックスから逆行して探す。
```jsx
arr.indexOf(searchElement[, fromIndex])
arr.lastIndexOf(searchElement[, fromIndex])
// true => return index, false => return -1
```
スタートのインデックスを指定しない場合は一番に見つかった要素のインデックスを返します。
```jsx
// index
let arr = [1, 'a', 2, 'b', 3, 'a'];
console.log(arr.length); // 6

console.log(arr.indexOf('a')); // 1
console.log(arr.lastIndexOf('a')); // 5
console.log(arr.indexOf('a', Math.floor(arr.length / 2))); // 5
console.log(arr.lastIndexOf('a', Math.floor(arr.length / 2))); // 1
```

### Array.prototype.find() & Array.prototype.findIndex()
`find()`：条件に当てはまる要素を探す。見つかったら**最初の要素**を返す。
`findIndex()`：条件に当てはまる要素を探す。見つかったら**最初のインデックス**を返す。
```jsx
arr.find(function(item, index, array), thisArg)
// true => return item, false return undefined
arr.findIndex(function(item, index, array), thisArg)
// true => return index, false return undefined
```
`find()`と`findIndex()`はコールバック関数で設置した条件に合わせて要素を探すが、一つの結果しか返しません。`filter()`なら見つかったすべての要素を配列にして返すが。
```jsx
// arr.find(function(item, index, array), thisArg)
let arr = [1, 'a', 2, 'b', 3, 'a'];
console.log(arr.find((item, index, array) => {
  if (index > Math.floor(array.length / 2) && item === 'a') {
    // return index; // it won't work
    return item; // find() always return item
  }
}));
// a
console.log(arr.find((item, index, array) => {
  return index > Math.floor(array.length / 2) && item === 'a'
}));
// a
```
```jsx
// arr.findIndex(function(item, index, array), thisArg)
let arr = [1, 'a', 2, 'b', 3, 'a'];
console.log(arr.findIndex((item, index, array) => {
  return index > Math.floor(array.length / 2) && item === 'a'
}));
// 5
```

### Array.prototype.some() & Array.prototype.every() & Array.prototype.includes()
`some`：配列に少なくとも一つの要素が条件に当てはまると`true`、でなければ`false`。
`every`：配列にすべての要素が条件に当てはまると`true`、でなければ`false`。
`includes()`：指定の要素の存在を確認する。あれば`true`、なければ`false`。
```jsx
arr.some(function(item, index, array), thisArg)
arr.every(function(item, index, array), thisArg)
// return true/false
arr.includes(searchElement[, fromIndex])
// return true/false
```
```jsx
// arr.some(function(item, index, array), thisArg)
let arr = [1, 'a', 2, 'b', 3, 'a'];
console.log(arr.some((item, index) => item === 'a' && index === 5)) // true
```
```jsx
// arr.every(function(item, index, array), thisArg)
let arr = [1, 'a', 2, 'b', 3, 'a'];
console.log(arr.every((item) => typeof item === 'string')); // false
arr = ['a', 'b', 'c'];
console.log(arr.every((item) => typeof item === 'string')); // true
```
```jsx
// includes()
let arr = ['a', 'a', 1, 2, 3];
console.log(arr.includes('a', Math.floor(arr.length / 2))); // false
```

#### Compare array elements
浅いコピーでなければ配列は別々のメモリー位置に保存されていますので配列の要素の比較は難しいです。`every()`は全要素の比べに使えます。
```jsx
// every()
function arrayElementsEqual(arr1, arr2) {
  return arr1.length === arr2.length
    && arr1.every((item, index) => item === arr2[index])
}
console.log(arrayElementsEqual([1, 'a'], [1, 'a'])); // true
```

## Access Element
### Array.prototype.at()
ES2022の新しいメソッドです。
`at()`：指定のインデックスの要素をアクセスする。
```jsx
// arr.at(index)
let arr = ['a', 'a', 1, 2, 3];
console.log(arr.at()); // a
console.log(arr.at(0)); // a
console.log(arr.at(-1)); // 3
console.log(arr[arr.length - 1]); // 3
```

# Flat multidimensional array
## Array.prototype.flat()
`flat()`：多次元配列を指定の深さまで平坦化する。
```jsx
// arr.flat([depth])
let arr = [0, [1, 2], [[3, 4]], [[[5], 6]]];
console.log(arr.flat());
// [ 0, 1, 2, [ 3, 4 ], [ [ 5 ], 6 ] ]
console.log(arr.flat(1));
// [ 0, 1, 2, [ 3, 4 ], [ [ 5 ], 6 ] ]
console.log(arr.flat(2));
// [
//   0, 1, 2,
//   3, 4, [5],
//   6
// ]
console.log(arr.flat(3));
// [
//   0, 1, 2, 3,
//   4, 5, 6
// ]
```
でも深さはいちいち手動で指定するのが面倒なので、関数を書いてみました。
```jsx
let arr = [0, [1, 2], [[3, 4]], [[[5], 6]]];
function flatArr(arr) {
  let depth = 0
  let type = Object.prototype.toString
  arr.forEach((item) => type.call(item).includes('Array') ? depth++ : null)
  return arr.flat(depth);
}
console.log(flatArr(arr));
// [
//   0, 1, 2, 3,
//   4, 5, 6
// ]
```

## Array.prototype.flatMap()
`flatMap()`：`map()`の後`flat(1)`するのと同じ効果です。
```jsx
// arr.flatMap(function(currentValue, index, array), thisArg)
let arr = [1, 2, 3, 4];
let arrSqrt = arr.map((item) => [item ** 2]);
console.log(arrSqrt);
// [ [ 1 ], [ 4 ], [ 9 ], [ 16 ] ]
console.log(arrSqrt.flat(1));
// [ 1, 4, 9, 16 ]
console.log(arr.flatMap((item) => [item ** 2]));
// [ 1, 4, 9, 16 ]

console.log(arr.flatMap((item) => [[item * 10]]))
// [ [ 10 ], [ 20 ], [ 30 ], [ 40 ] ]
```


# Undefined vs. Empty slots
## Identify: Object.hasOwn()
`hasOwn()`：指定されたオブジェクトに、指定のプロパティが自身のプロパティである場合は`true`、ではない場合は`false`。
```jsx
// Object.hasOwn(arr, index)
let arr = [1, undefined, 'a'];
console.log(Object.hasOwn(arr, 1)); // true
arr = [1, , 'a'];
console.log(Object.hasOwn(arr, 1)); // false
```

## Skip empty slot
`forEach()`、`filter()`、`reduce()`、`some()`、`every()`。
```jsx
// skip
let arrUndefined = [undefined, 'a'];
let arrEmptySlot = [, 'a'];

// forEach()
arrUndefined.forEach((item) => console.log(item));
// undefined
// a
arrEmptySlot.forEach((item) => console.log(item));
// a

// filter()
console.log(arrUndefined.filter((item) => item));
// [ 'a' ]
console.log(arrUndefined.filter((item) => item === undefined));
// [ undefined ]
console.log(arrEmptySlot.filter((item) => item));
// [ 'a' ]

// reduce()
console.log(arrUndefined.reduce((a, b) => a + b));
// undefineda
console.log(arrEmptySlot.reduce((a, b) => a + b));
// a

// some()
console.log(arrUndefined.some((item) => item !== 'a'));
// true
console.log(arrEmptySlot.some((item) => item !== 'a'));
// false

// every()
console.log(arrUndefined.every((item) => item === 'a'));
// false
console.log(arrEmptySlot.every((item) => item === 'a'));
// true
```

## Keep/Iterate/Copy empty slot
`map()`：`map()`は**empty slot**を保留するけどイテレータしない。
`Array.from()`、`Spread Syntax`、`fill()`：**empty slot**を`undefined`に転換するかイテレータする。
`copyWithin()`：**empty slot**でもコピーする。
```jsx
let arrUndefined = [undefined, 'a'];
let arrEmptySlot = [, 'a'];

// Keep
// map()
console.log(arrUndefined.map((item) => item));
// [ undefined, 'a' ]
console.log(arrEmptySlot.map((item) => item));
// [ <1 empty item>, 'a' ]
console.log(arrUndefined.map((item) => 'abc'));
// [ 'abc', 'abc' ]
console.log(arrEmptySlot.map((item) => 'abc'));
// [ <1 empty item>, 'abc' ]
```
```jsx
// Iterate
// Array.from()
console.log(Array.from(arrUndefined));
// [ undefined, 'a' ]
console.log(Array.from(arrEmptySlot));
// [ undefined, 'a' ]

// Spread Syntax
console.log([...arrUndefined]);
// [ undefined, 'a' ]
console.log([...arrEmptySlot]);
// [ undefined, 'a' ]

// fill()
console.log(arrUndefined.fill('abc'));
// [ 'abc', 'abc' ]
console.log(arrEmptySlot.fill('abc'));
// [ 'abc', 'abc' ]
```
```jsx
// Copy
// arr.copyWithin(targetIndex[, startIndex[, endIndex]]) // not including end
// console.log([0, 1, 2, 3].copyWithin(0, 3)); // [ 3, 1, 2, 3 ]
// console.log([0, 1, 2, 3].copyWithin(0, 1, 3)); //  1, 2, 2, 3 ]
console.log(arrUndefined.copyWithin(1, 0, 1));
// [ undefined, undefined ]
console.log(arrEmptySlot.copyWithin(1, 0, 1));
// [ <2 empty items> ]
```

## They are empty in String
`join()`、`toString()`。
```jsx
// They are empty in String
let arr = ['', undefined, null, , 'a'];
console.log(arr.join('.')); // ....a
console.log(arr.toString()); // ....a
```

## Find empty slot index
`indexOf()`、`lastIndexOf()`：**empty slot**をスキップする。引数に何も入れてない場合は`undefined`を見つける。
```jsx
// indexOf()/lastIndexOf()
let arr = ['', undefined, null, , 'a'];
console.log(arr.indexOf(null)); // 2
console.log(arr.lastIndexOf(undefined)); // 1

console.log(arr.indexOf('')); // 0
console.log(arr.lastIndexOf('')); // 0
console.log(arr.indexOf()); // 1
console.log(arr.lastIndexOf()); // 1
// note: indexOf() and lastIndexOf() can not find out empty slot
// note: they treat undefined as empty
```
`find()`、`findIndexOf()`：**empty slot**を`undefined`と見なす。
```jsx
// find()
let arr = ['', undefined, null, , 'a'];
console.log(arr.find((item) => item)); // a
console.log(arr.find((item) => item === null)); // null
console.log(arr.find((item) => item === undefined)); // undefined
console.log(arr.find((item) => item === '')); // ''

arr = [1, , 'a']
console.log(arr.find((item) => true)); // 1
arr = [, 'a']
console.log(arr.find((item) => true)); // undefined
arr = [, 'a']
console.log(arr.find((item) => item === undefined)); // undefined
// note: find() treats empty slot as undefined, a real value
```

```jsx
// findIndexOf()
let arr = ['', undefined, null, , 'a'];
console.log(arr.findIndex((item) => item)); // 4
console.log(arr.findIndex((item) => item === null)); // 2
console.log(arr.findIndex((item) => item === undefined)); // 1
console.log(arr.findIndex((item) => item === '')); // 0

arr = [, 'a']
console.log(arr.findIndex((item) => item === undefined)); // 0
arr = ['a', undefined]
console.log(arr.findIndex((item) => item === undefined)); // 1
// note: findIndexOf() also treats empty slot as undefined
```
`includes()`：引数に何も入れてない場合は**empty slot**と`undefined`を見つける。
```jsx
// includes()
let arr = ['', undefined, null, , 'a'];
console.log(arr.includes(null)); // true
console.log(arr.includes(undefined)); // true
console.log(arr.includes('')); // true

arr = [, 'a'];
console.log(arr.includes()); // true
arr = [undefined, 'a']
console.log(arr.includes()); // true
arr = [null]
console.log(arr.includes()); // false
// note: includes() treats empty slot and undefined as empty, but null is null
```
`at()`：アクセスして取得した値**empty slot**は`undefined`と見なす。
```jsx
// at()
let arr = ['', undefined, null, , 'a'];
console.log(arr.at(0)); // ''
console.log(arr.at(1)); // undefined
console.log(arr.at(2)); // null
console.log(arr.at(3)); // undefined

console.log(arr.at(3) === undefined); // true
```

## `NaN`
`NaN`なら`includes()`だけ識別できます。
```jsx
// only includes() can find NaN
let arr = [NaN];
console.log(arr.indexOf(NaN)); // -1
console.log(arr.lastIndexOf(NaN)); // -1
console.log(arr.find((item) => item === NaN)); // undefined
console.log(arr.findIndex((item) => item === NaN)); // -1

console.log(arr.includes(NaN)); // true
```

# Practices
```jsx
// practices
// shuffle an arr
// function shuffle(arr) {
//   arr.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 arr = [1, 2, 3];
  shuffle(arr)
  count[arr.join('')]++;
}

for (let key in count) {
  console.log(`${key}: ${count[key]}`);
}
// 123: 374675
// 132: 62596
// 213: 125113
// 231: 62539
// 312: 62586
// 321: 312491

function shuffle(arr) {
  for (let i = arr.length - 1; i > 0; i--) {
    let j = Math.floor(Math.random() * (i + 1));
    [arr[i], arr[j]] = [arr[j], arr[i]];
  }
}
// 123: 166666
// 132: 166429
// 213: 167335
// 231: 166500
// 312: 166718
// 321: 166352
```

```jsx
// filter unique array members
function unique(arr) {
  let result = [];
  for (let str of arr) {
    // if result have no same name, push it
    if (!result.includes(str)) {
      result.push(str);
    }
  }
  return result;
}
let strings = ['Hare', 'Krishna', 'Hare', 'Krishna',
  'Krishna', 'Krishna', 'Hare', 'Hare', ':-O'
];

console.log(unique(strings));
// [ 'Hare', 'Krishna', ':-O' ]

// Be careful of performance problem
let result = [];
strings.forEach((item) => {
  if (!result.includes(item)) {
    result.push(item);
  }
});
console.log(result);
// [ 'Hare', 'Krishna', ':-O' ]
```
```jsx
// create keyed object from array
let users = [
  { id: 'Jon', name: 'Jon Snow', score: 90 },
  { id: 'Robin', name: 'Robin Stark', score: 75 },
  { id: 'Sansa', name: 'Sansa Stark', score: 80 },
  { id: 'Arya', name: 'Arya Stark', score: 100 },
  { id: 'Bran', name: 'Bran Stark', score: 120 },
  { id: '', name: '', score: 1000 }
];

let usersById = groupByID(users);

function groupByID(arr) {
  return arr.reduce((obj, currentItem) => {
    obj[currentItem.id] = currentItem;
    return obj;
  }, {})
}

console.log(usersById);
// {
//   Jon: { id: 'Jon', name: 'Jon Snow', score: 90 },
//   Robin: { id: 'Robin', name: 'Robin Stark', score: 75 },
//   Sansa: { id: 'Sansa', name: 'Sansa Stark', score: 80 },
//   Arya: { id: 'Arya', name: 'Arya Stark', score: 100 },
//   Bran: { id: 'Bran', name: 'Bran Stark', score: 120 },
//   '': { id: '', name: '', score: 1000 }
// }
```