[[配列 (JavaScript)|配列]]やその要素を変換する方法について学習しましょう。実際の開発で多用する知識であり、これをマスターすれば `for` を使う機会はグっと減ります。
## Reference
- [配列 · JavaScript Primer \#jsprimer](https://jsprimer.net/basic/array/)
## Lesson
### map
配列の要素を変換するには[[Array.prototype.map (JavaScript)|Array.prototype.map]]を使います。
```js
["one", "two", "three", "four", "five"].map((x) => x.length);
// [ 3, 3, 5, 4, 4 ]
```
[[Array.prototype.map (JavaScript)|Array.prototype.map]]の第1引数は `(element, index, array) => newElement` の形をとる関数です。先の例では `x` を `x.length` という値に変換しましたが、以下のように名前に連番をつけることもできます。
```js
["one", "two", "three"].map((x, i) => `[${i + 1}] ${x}`);
// [ "[1] one", "[2] two", "[3] three" ]
```
### join
[[Array.prototype.join (JavaScript)|Array.prototype.join]]を使うと、配列の要素を結合して1つの文字列を生成できます。
```js
["a", "b", "c"].join("-"),
// a-b-c
```
### flat
[[Array.prototype.flat (JavaScript)|Array.prototype.flat]]を使うと、ネストした[[配列 (JavaScript)|配列]]を平坦にできます。
```js
[1, [2, 3], [4]].flat()
// [ 1, 2, 3, 4 ]
[1, [2, 3], [4, [5]]].flat()
// [ 1, 2, 3, 4, [ 5 ] ]
[1, [2, 3], [4, [5]]].flat(2) // 2段階以上ネストしている場合は引数が必要
// [ 1, 2, 3, 4, 5 ]
```
### flatMap
[[Array.prototype.flatMap (JavaScript)|Array.prototype.flatMap]]を使うと、関数で変換後の[[配列 (JavaScript)|配列]]がネストしている場合に平坦にできます。
```js
[1, 2, 3].flatMap((x) => [x, x])
// [ 1, 1, 2, 2, 3, 3 ]
[1, 2, 3].map((x) => [x, x]) // 普通のmapだとこう
// [ [ 1, 1 ], [ 2, 2 ], [ 3, 3 ] ]
```
### 関数の分離
変換処理が複雑になる場合は無名の[[アロー関数 (JavaScript)|アロー関数]]ではなく、名前付きの関数を事前に定義して指定した方が可読性が上がります。たとえば以下のコードについて。
```js
const members = [
{ name: "Ichiro", age: 35 },
{ name: "Jiro", age: 29 },
{ name: "Saburo", age: 22 },
{ name: "Shiro", age: 19 },
{ name: "Goro", age: 15 },
];
members.map((x) => {
const nameLength = x.name.length;
const isAdult = x.age >= 20;
return `${
isAdult ? "[大人]" : "[子供]"
} ${x.name} (${nameLength} characters)`;
});
```
関数名にしたほうがドキュメントの側面でも読みやすくなり、テストも書きやすくなるでしょう。
```js
const members = [
{ name: "Ichiro", age: 35 },
{ name: "Jiro", age: 29 },
{ name: "Saburo", age: 22 },
{ name: "Shiro", age: 19 },
{ name: "Goro", age: 15 },
];
function toDisplayMember(member) {
const nameLength = member.name.length;
const isAdult = member.age >= 20;
return `${
isAdult ? "[大人]" : "[子供]"
} ${member.name} (${nameLength} characters)`;
}
members.map(toDisplayMember);
```
[[Array.prototype.filter (JavaScript)|Array.prototype.filter]]と一緒に利用するときはより効果があります。
```js
const members = [
{ name: "Ichiro", age: 35 },
{ name: "Jiro", age: 29 },
{ name: "Saburo", age: 22 },
{ name: "Shiro", age: 19 },
{ name: "Goro", age: 15 },
];
const isAdult = (member) => member.age >= 20;
const toDisplayMember = (member) =>
`- ${member.name} (${member.name.length} characters)`;
members.filter(isAdult).map(toDisplayMember).join("\n");
// - Ichiro (6 characters)
// - Jiro (4 characters)
// - Saburo (6 characters)
```
## Mission 1
#🙂NORMAL
以下 `members` について。
```js
const members = [
{ firstName: "Ichiro", lastName: "Suzuki" },
{ firstName: "Jiro", lastName: "Tanaka" },
{ firstName: "Saburo", lastName: "Sato" },
];
```
以下のような文字列を生成するコードを書いてください。
```js
Suzuki Ichiro
Tanaka Jiro
Sato Saburo
```
%%
解答例
```js
const members = [
{ firstName: "Ichiro", lastName: "Suzuki" },
{ firstName: "Jiro", lastName: "Tanaka" },
{ firstName: "Saburo", lastName: "Sato" },
];
members.map((x) => `${x.lastName} ${x.firstName}`).join("\n");
```
%%
> [!hint]- Hint 1
> [[#map]] と [[#join]] を使います。
## Mission 2
#🙂NORMAL
以下のコードを `for` を使わないようにリファクタリングしてください。
```js
const members = [
{ firstName: "Ichiro", lastName: "Suzuki" },
{ firstName: "Jiro", lastName: "Tanaka" },
{ firstName: "Saburo", lastName: "Sato" },
];
const displayMembers = [];
for (const member of members) {
const lowerFirstName = member.firstName.toLowerCase();
const lowerLastName = member.lastName.toLowerCase();
displayMembers.push(`${lowerFirstName}-${lowerLastName}`);
}
console.log(displayMembers);
```
%%
解答例
```js
const members = [
{ firstName: "Ichiro", lastName: "Suzuki" },
{ firstName: "Jiro", lastName: "Tanaka" },
{ firstName: "Saburo", lastName: "Sato" },
];
const toDisplay = (member) =>
`${member.firstName.toLowerCase()}-${member.lastName.toLowerCase()}`;
const displayMembers = members.map(toDisplay);
console.log(displayMembers);
```
%%
> [!hint]- Hint 1
> `for` 文の中を関数に切り出して見ると、コードの見通しが良くなるかもしれません...
## Mission 3
#😵HARD
以下のコードを `for` を使わないようにリファクタリングしてください。
```js
const members = [
{ id: 1, name: "タツヲ", animal: "ゴリラ" },
{ id: 2, name: "マサハル", animal: "犬" },
{ id: 3, name: "みみこ", animal: "フクロウ" },
{ id: 4, name: "みみぞう", animal: "フクロウ" },
{ id: 5, name: "わんべえ", animal: "犬" },
];
function filterAsAnimals(animals) {
const result = [];
for (const member of members) {
for (const animal of animals) {
if (member.animal === animal) {
result.push(member);
}
}
}
return result;
}
console.log(
filterAsAnimals(["犬", "フクロウ"]),
);
```
%%
解答例
```js
const members = [
{ id: 1, name: "タツヲ", animal: "ゴリラ" },
{ id: 2, name: "マサハル", animal: "犬" },
{ id: 3, name: "みみこ", animal: "フクロウ" },
{ id: 4, name: "みみぞう", animal: "フクロウ" },
{ id: 5, name: "わんべえ", animal: "犬" },
];
const filterAsAnimals = (animals) =>
members.filter((x) => animals.includes(x.animal));
console.log(
filterAsAnimals(["犬", "フクロウ"]),
);
```
%%
> [!hint]- Hint 1
> [[Array.prototype.includes (JavaScript)|Array.prototype.includes]] を使うと簡潔に書けます。
## Mission 4
#😱NIGHTMARE
以下のコードを完成させてください。ただし、`for` や `push` を使ってはいけません。
```js
import { expect } from "jsr:@std/expect";
// FIXME:
function repeatBySelf() {}
Deno.test("数字の数だけ要素を複製する", () => {
expect(repeatBySelf(1, 2, 3)).toEqual([1, 2, 2, 3, 3, 3]);
});
```
%%
解答例
```js
import { expect } from "jsr:@std/expect";
function repeatBySelf(...nums) {
return nums.flatMap((n) => Array(n).fill(n));
}
Deno.test("数字の数だけ要素を複製する", () => {
expect(repeatBySelf(1, 2, 3)).toEqual([1, 2, 2, 3, 3, 3]);
});
```
%%
> [!hint]- Hint 1
> `repeatBySelf(...nums)` となるnumber[[配列 (JavaScript)|配列]]の[[仮引数]] `nums` を別の配列へと変換しましょう。
> [!hint]- Hint 2
> 同じ値の[[配列 (JavaScript)|配列]]を作るには `Array(要素数).fill(値)` のようにします。
> [!hint]- Hint 3
> 変換結果はどうしても二次元配列になります。ネストした配列を平坦にするには...
---
*NEXT* >> [[📗TDQ-017 配列の変換 その2]]