[[JavaScript]]編、前半戦の最後は[[正規表現]]です。慣れるまでは難しいかもしれませんが、一度使いこなせるようになると、少ないコード量で多くの表現が可能になります。
## Reference
- [文字列 · JavaScript Primer \#jsprimer](https://jsprimer.net/basic/string/)
## Lesson
[[正規表現]]は `/パターン/` で囲むか、[[RegExp (JavaScript)|RegExp]]インスタンスを生成して利用します。
```js
// 以下はどちらも同じ正規表現オブジェクト
const regExp1 = /[0-9]{3}/;
const regExp2 = new RegExp("[0-9]{3}");
```
### 判定
文字列が[[正規表現]]にマッチするかを確認するには[[RegExp.prototype.test (JavaScript)|RegExp.prototype.test]]を使います。
```js
const regExp = /[0-9]{3}/;
regExp.test("10");
// false
regExp.test("100");
// true
regExp.test("1000");
// true
```
### 抽出
文字列から[[正規表現]]にマッチする部分を取得するには[[String.prototype.match (JavaScript)|String.prototype.match]]を使います。
```js
const tasks = `
- [ ] 2025-02-18 task1
- [ ] 2025-02-19 task2
- [ ] 2025-02-19 task3
`;
tasks.match(/\d{4}-\d{2}-\d{2}/g);
// [ "2025-02-18", "2025-02-19", "2025-02-19" ]
```
マッチした文字列以外の詳細情報(マッチした位置など)が欲しい場合や、[[キャプチャグループ]]を使用する場合は[[String.prototype.matchAll (JavaScript)|String.prototype.matchAll]]を使います。
```js
const tasks = `
- [ ] 2025-02-18 task1
- [ ] 2025-02-19 task2
- [ ] 2025-02-19 task3
`;
tasks.matchAll(
/- \[ \] (?<date>\d{4}-\d{2}-\d{2}) (?<name>.+)/g,
).toArray();
/*
[
[
"- [ ] 2025-02-18 task1",
"2025-02-18",
"task1",
index: 1,
input: "\n- [ ] 2025-02-18 task1\n- [ ] 2025-02-19 task2\n- [ ] 2025-02-19 task3\n",
groups: [Object: null prototype] { date: "2025-02-18", name: "task1" }
],
[
"- [ ] 2025-02-19 task2",
"2025-02-19",
"task2",
index: 24,
input: "\n- [ ] 2025-02-18 task1\n- [ ] 2025-02-19 task2\n- [ ] 2025-02-19 task3\n",
groups: [Object: null prototype] { date: "2025-02-19", name: "task2" }
],
[
"- [ ] 2025-02-19 task3",
"2025-02-19",
"task3",
index: 47,
input: "\n- [ ] 2025-02-18 task1\n- [ ] 2025-02-19 task2\n- [ ] 2025-02-19 task3\n",
groups: [Object: null prototype] { date: "2025-02-19", name: "task3" }
]
]
*/
```
> [!caution]
> [[String.prototype.matchAll (JavaScript)|String.prototype.matchAll]]はイテレーターを返却するので、[[配列 (JavaScript)|配列]]に変換するため[[Iterator.prototype.toArray (JavaScript)|Iterator.prototype.toArray]]を使ったが、 #2025/02/19 時点では[[Safari]]に対応していない。プロダクトで実装するなら[[Array.from (JavaScript)|Array.from]]を使った方がよい。
### 置換
文字列を置換するには[[String.prototype.replaceAll (JavaScript)|String.prototype.replaceAll]]を使います。
```js
"123 456 789".replaceAll(/\d/g, "7");
// "777 777 777"
// 第1引数は正規表現でなくてもOK
"私はたつおです。たつおはゴリラなんです。".replaceAll("たつお", "タツヲ");
// "私はタツヲです。タツヲはゴリラなんです。"
```
[[キャプチャグループ (JavaScript)|キャプチャグループ]]を使った置換もできます。
```js
const message = "2025年10月11日 2025年11月02日";
message.replaceAll(/(\d{4})年(\d{2})月(\d{2})日/g, "$1/$2/$3");
// 2025/10/11 2025/11/02
```
### 注意
[[String.prototype.matchAll (JavaScript)|String.prototype.matchAll]] や [[String.prototype.replaceAll (JavaScript)|String.prototype.replaceAll]] の第1引数に指定する正規表現パターンには `/パターン/g` のように [[gフラグ (JavaScript)|gフラグ]] を指定する必要があります。これを忘れるとエラーになります。
## Mission 1
#🙂NORMAL
[[正規表現]]の説明として**誤っているものをすべて**選び、その理由を説明してください。
1. `*` は任意の文字0文字以上である
2. `.+` は任意の文字1文字以上である
3. `[0-1][0-9]{3}` は1, 2, 3, ..., 9999, 10000の数字を表現できる
4. `[a-z]+` でアルファベットのみからなる単語すべてを表現できる
%%
解答例
1, 3, 4が誤り。
1 `.*` じゃないといけない
2 `0000` ~ `1999` の範囲しか表現できない
4 小文字しか表現できない
%%
> [!hint]- Hint 1
> 正しいものは1つしかありません。
## Mission 2
#😵HARD
以下のテストが通るように `swapColumns` を実装してください。
```js
function swapColumns(text) {
// FIXME: 実装
}
import { expect } from "jsr:@std/expect";
Deno.test("テーブルの2列目と3列目を入れ替える", () => {
expect(swapColumns(`
| col1 | col2 | col3 | col4 |
| ---- | ---- | ---- | ---- |
| val1 | val2 | val3 | val4 |
| val1 | val2 | val3 | val4 |
`)).toBe(`
| col1 | col3 | col2 | col4 |
| ---- | ---- | ---- | ---- |
| val1 | val3 | val2 | val4 |
| val1 | val3 | val2 | val4 |
`);
});
```
%%
解答例
```js
function swapColumns(text) {
return text.replaceAll(/\|(.*?)\|(.*?)\|(.*?)\|/g, "|$1|$3|$2|");
}
```
%%
> [!hint]- Hint 1
> [[String.prototype.replaceAll (JavaScript)|String.prototype.replaceAll]]で[[キャプチャグループ (JavaScript)|キャプチャグループ]]を使います。
> [!hint]- Hint 2
> [[貪欲マッチング]] (`.*`) ではパターンが複雑になってしまうので、[[最短マッチング]]を使いましょう。
## Mission 3
#😵HARD
以下のテストが通るように `createTasks` を実装してください。
```js
function createTasks(text) {
// FIXME: 実装
}
import { expect } from "jsr:@std/expect";
Deno.test("タスク文字列からタスクオブジェクトの配列を作成できる", () => {
expect(createTasks(`
- [ ] 2025-02-18 task1
- [ ] 2025-02-19 task2
- [ ] 2025-02-19 task3
`)).toEqual(
[
{ date: "2025-02-18", name: "task1" },
{ date: "2025-02-19", name: "task2" },
{ date: "2025-02-19", name: "task3" },
],
);
});
```
%%
解答例
```js
function createTasks(text) {
return text.matchAll(
/- \[ \] (?<date>\d{4}-\d{2}-\d{2}) (?<name>.+)/g,
).toArray().map((x) => x.groups);
}
```
%%
> [!hint]- Hint 1
> [[String.prototype.matchAll (JavaScript)|String.prototype.matchAll]]を使います。
> [!hint]- Hint 2
> [[名前付きキャプチャグループ (JavaScript)|名前付きキャプチャグループ]]を使います。
---
*NEXT* >> [[📗TDQ-BOSS1 Promiseへの番人]]