フロントエンド勉強会 TypeScript基礎

# 概要

[フロントエンド勉強会](https://qiita.com/masayasviel/private/0e5a403388509e98313d)
プログラミング問題を解きながらTypeScriptに慣れ親しむことを目指す。

# 環境構築

```sh
npm init -y
npm install typescript ts-node
npx tsc --init
touch main.ts
```

# 実行

```sh
npx ts-node main.ts
```

# 問題

## 1. FizzBuzz

inputsに半角区切りで正の整数が与えられる。
各自然数において、

- 3の倍数ならFizz
- 5の倍数ならBuzz
- 3の倍数かつ5の倍数ならFizzBuzz
- いずれの条件にも当てはまらなければ数字そのまま

を出力する。

```ts
const inputs = '';

const arr = inputs.trim().split(' ').map((n) => parseInt(n));
function fizzBuzz(n: number): string {
    if (n % 15 === 0) {
        return 'FizzBuzz';
    } else if (n % 3 === 0) {
        return 'Fizz';
    } else if (n % 5 === 0) {
        return 'Buzz';
    }
    return `${n}`;
}
arr.forEach((n: number) =>
    console.log(fizzBuzz(n))
);
```

- [trim](https://developer.mozilla.org/ja/docs/Web/JavaScript/Reference/Global_Objects/String/trim)
- [split](https://developer.mozilla.org/ja/docs/Web/JavaScript/Reference/Global_Objects/String/split)
- [map](https://developer.mozilla.org/ja/docs/Web/JavaScript/Reference/Global_Objects/Array/map)
- [parseInt](https://developer.mozilla.org/ja/docs/Web/JavaScript/Reference/Global_Objects/parseInt)
- [function](https://jsprimer.net/basic/function-declaration/)
- [関数の型の宣言](https://typescriptbook.jp/reference/functions/function-type-declaration)
- [条件分岐](https://jsprimer.net/basic/condition/)
- [演算子](https://jsprimer.net/basic/operator/)
- [テンプレートリテラル](https://developer.mozilla.org/ja/docs/Web/JavaScript/Reference/Template_literals)
- [forEach](https://developer.mozilla.org/ja/docs/Web/JavaScript/Reference/Global_Objects/Array/forEach)

### ワンライナー

```ts
const inputs = '';

const fizzBuzz = (n: number): string => (['', 'Fizz'][Number(n % 3 === 0)] + ['', 'Buzz'][Number(n % 5 === 0)]) || ('' + n);
inputs.trim().split(' ').forEach((n: number) =>
    console.log(fizzBuzz(+n))
);
```

- `['', 'Fizz'][Number(n % 3 === 0)]`

`n%3===0` はboolean型。
boolean型を[Number](https://developer.mozilla.org/ja/docs/Web/JavaScript/Reference/Global_Objects/Number)に通すと、

```ts
Number(true)  // 1
Number(false)  // 0
```

3の倍数なら `['', 'Fizz'][1] = 'Fizz'`
3の倍数でないなら `['', 'Fizz'][0] = ''` となる。

5の倍数の箇所 `['', 'Buzz'][Number(n % 5 === 0)]` も同様。

これで、
3の倍数なら `'Fizz' + '' = 'Fizz'`
5の倍数なら `'' + 'Buzz' = 'Buzz'`
3の倍数かつ5の倍数なら `'Fizz' + 'Buzz' = 'FizzBuzz'`
になった。
ただしどちらの倍数でもないときは `'' + '' = ''` となる。

そこで[論理和(||)](https://developer.mozilla.org/ja/docs/Web/JavaScript/Reference/Operators/Logical_OR)を用いる。
論理和は左が[Truthy](https://developer.mozilla.org/ja/docs/Glossary/Truthy)なら左をそのまま評価、[Falsy](https://developer.mozilla.org/ja/docs/Glossary/Falsy)なら右を評価する。
今回は `'' + '' = ''` の場合空文字となり、Falsyな値になるため、右が評価され、`('' + n)` となる。

```ts
>> true || 'aaa'
true
>> false || 'aaa'
'aaa'
>> '' || 'aaa'
'aaa'
>> 'bbb' || 'ccc'
'bbb'
>> 0 || 'aaa'
'aaa'
>> 1 || 'aaa'
1
```

TSに限らずJSでも

`string + number = string` となるため、number型であるnに空文字を足してあげれば `` `${n}` ``と同じ表現になる。

```ts
>> 100 + ''
'100'
>> '' + 100
'100'
>> `${100}`
'100'
```

また、[論理積(&&)](https://developer.mozilla.org/ja/docs/Web/JavaScript/Reference/Operators/Logical_AND)は論理和とは逆に、左が[Truthy](https://developer.mozilla.org/ja/docs/Glossary/Truthy)なら右を評価し、[Falsy](https://developer.mozilla.org/ja/docs/Glossary/Falsy)なら左をそのまま評価する。

```ts
>> true && 'aaa'
'aaa'
>> false && 'aaa'
false
>> '' && 'aaa'
''
>> 'bbb' && 'ccc'
'ccc'
>> 0 && 'aaa'
0
>> 1 && 'aaa'
'aaa'
```

さらに[Null合体演算子(??)](https://developer.mozilla.org/ja/docs/Web/JavaScript/Reference/Operators/Nullish_coalescing)というのも存在し、これは左辺が `null` または `undefined` ならば右辺を評価する。 `null` または `undefined` でなければそのまま左辺を評価する。

```ts
>> null ?? 'aaa'
'aaa'
>> undefined ?? 'aaa'
'aaa'
>> false ?? 'aaa'
false
>> NaN ?? 'aaa'
NaN
>> 'aaa' ?? null
'aaa'
```

`+n` については[単項プラス](https://developer.mozilla.org/ja/docs/Web/JavaScript/Reference/Operators/Unary_plus)を参照。
`-n` は符号が逆転する。
[ビット否定](https://developer.mozilla.org/ja/docs/Web/JavaScript/Reference/Operators/Bitwise_NOT)でも2重否定で数値にできる。

```ts
>> +'100'
100
>> +'q'
NaN
>> -'100'
-100
>> -'-100'
100
>> ~~'100'
100
>> ~~'q'
0
```

- [NaN](https://developer.mozilla.org/ja/docs/Web/JavaScript/Reference/Global_Objects/NaN)

## 2. 各桁の和

正の整数の各桁の数字を加算した値を出力する。

### ans1

```ts
let n = 123456789;
let sum = 0;
while (n > 0) {
    sum += n % 10;
    n = (n / 10) | 0; 
}
console.log(sum);
```

### ans2

```ts
const arr = inputs.trim().split('').map((n) => parseInt(n));
let sum = 0;
arr.forEach((n): void => {
    sum += n;
});
console.log(sum);
```

- [ループと反復処理](https://developer.mozilla.org/ja/docs/Web/JavaScript/Guide/Loops_and_iteration)
- [ループと反復処理](https://jsprimer.net/basic/loop/)

forEach部分は以下のようにも書き換え可能。

- [for-of](https://developer.mozilla.org/ja/docs/Web/JavaScript/Reference/Statements/for...of)

参考: [for-of文 - 拡張for文](https://typescriptbook.jp/reference/statements/for-of)

```ts
for (let n of arr) {
    sum += n;
}
```

- [for-in](https://developer.mozilla.org/ja/docs/Web/JavaScript/Reference/Statements/for...in)

```ts
for (let i in arr) {
    sum += arr[i];
}
```

- [for](https://developer.mozilla.org/ja/docs/Web/JavaScript/Reference/Statements/for)

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

[Array.length](https://developer.mozilla.org/ja/docs/Web/JavaScript/Reference/Global_Objects/Array/length)はドキュメントの通り、配列の長さを取得するプロパティだが、代入することで配列の長さを操作できる。

- Array.length 1

```ts
>> const alphabet = ['a', 'b', 'c', 'd', 'e'];
>> alphabet.length
5
>> alphabet.length = 0;
>> alphabet
[]
```

- Array.length 2

```ts
>> const alphabet = ['a', 'b', 'c', 'd', 'e']
>> alphabet.length = 3;
>> alphabet
['a', 'b', 'c']
```

また、for文で書けるなら[while](https://developer.mozilla.org/ja/docs/Web/JavaScript/Reference/Statements/while)文でも書ける。

```ts
const arr = inputs.trim().split('').map((n) => +n);
let sum = 0;
while (arr.length > 0) {
    sum += arr.pop() ?? 0;
}
console.log(sum);
```

[Array.pop](https://developer.mozilla.org/ja/docs/Web/JavaScript/Reference/Global_Objects/Array/pop)はundefinedを返す可能性があるので、返った場合は[Null合体演算子(??)](https://developer.mozilla.org/ja/docs/Web/JavaScript/Reference/Operators/Nullish_coalescing)で0にする。
undefinedを返すときはMDNにある通り配列が空のとき。

蛇足だが、while文は最初の時点で条件に満たなければ内部の処理が実行されないが、条件を満たしていなくとも一度は実行される[do...while](https://developer.mozilla.org/ja/docs/Web/JavaScript/Reference/Statements/do...while)というのもある。

### ans3

```ts
console.log(
    inputs.trim().split('').reduce((prev, cur) => +cur + prev, 0)
);
```

- [reduce](https://developer.mozilla.org/ja/docs/Web/JavaScript/Reference/Global_Objects/Array/reduce)

## 3. 文字列内に同じ文字がいくつずつ含まれるか

a, b, cからなる文字列が与えられる。
文字列内に同じ文字がいくつずつ含まれるかを求める。

### ans1

```ts
interface CountInterface {
    a: number;
    b: number;
    c: number;
}

const counts: CountInterface = {
    a: 0,
    b: 0,
    c: 0
};

input.trim().split('').forEach((c) => {
    switch (c) {
        case 'a':
            counts.a += 1;
            break;
        case 'b':
            counts.b += 1;
            break;
        case 'c':
            counts.c += 1;
            break;
    }
});

console.table(counts);
```

出力例

```
>> input = 'abcabcabc'
┌─────────┬────────┐
│ (index) │ Values │
├─────────┼────────┤
│    a    │   3    │
│    b    │   3    │
│    c    │   3    │
└─────────┴────────┘
```

- [switch-case](https://developer.mozilla.org/ja/docs/Web/JavaScript/Reference/Statements/switch)
- [object](https://jsprimer.net/basic/object/)
- [interface](https://typescriptbook.jp/reference/object-oriented/interface)
- [console.table](https://developer.mozilla.org/ja/docs/Web/API/console/table)


### ans2

```ts
const CHARS = ['a', 'b', 'c'] as const;  // type: readonly ['a', 'b', 'c']
type CharsType = typeof CHARS[number];  // type: 'a' | 'b' | 'c'

const countsMap = new Map<CharsType, number>(
    [
        ['a', 0],
        ['b', 0],
        ['c', 0]
    ]
);

const CharSet = new Set<string>(CHARS);
input
    .trim()
    .split('')
    .filter((c: string): c is CharsType => CharSet.has(c))
    .forEach((c: CharsType) => {
        const currentValue = countsMap.get(c)!;
        countsMap.set(c, currentValue+1)
    });

console.table(countsMap);
```

出力例

```
>> input = 'abcabcabc'
┌───────────────────┬─────┬────────┐
│ (iteration index) │ Key │ Values │
├───────────────────┼─────┼────────┤
│         0         │ 'a' │   3    │
│         1         │ 'b' │   3    │
│         2         │ 'c' │   3    │
└───────────────────┴─────┴────────┘
```

- [constアサーション「as const」](https://typescriptbook.jp/reference/values-types-variables/const-assertion)
- [読み取り専用の配列 (readonly array)](https://typescriptbook.jp/reference/values-types-variables/array/readonly-array)
- [配列から型を生成する](https://typescriptbook.jp/tips/generates-type-from-array)
- [Map](https://developer.mozilla.org/ja/docs/Web/JavaScript/Reference/Global_Objects/Map)
- [ジェネリクス](https://typescriptbook.jp/reference/generics)

補足1

```ts
filter((c: string): c is CharsType => CharSet.has(c))
```

問題の制約でinputsは `a, b, c` いずれかになっているとは言え、プログラム上では型安全が確定しているわけではない。
そこで[filter](https://developer.mozilla.org/ja/docs/Web/JavaScript/Reference/Global_Objects/Array/filter)の返り値に[型ガード](https://typescriptbook.jp/reference/functions/type-guard-functions)を用いれば解決するのだが、一応 `a, b, c` いずれかに該当する確認をするために `new Set<string>(CHARS)` でSetを作成し、[Set.has](https://developer.mozilla.org/ja/docs/Web/JavaScript/Reference/Global_Objects/Set/has)を用いる。
`CHARS` は `('a' | 'b' | 'c')[]` 型のため、 [Array.includes](https://developer.mozilla.org/ja/docs/Web/JavaScript/Reference/Global_Objects/Array/includes)の引数に `'a' | 'b' | 'c'` より広いstringを入れることはできない。
なので `new Set<string>(CHARS)` の形でstring型の[Set](https://developer.mozilla.org/ja/docs/Web/JavaScript/Reference/Global_Objects/Set)にキャストしている。 `new Array<string>` にキャストすればincludesが使えるが、Setを紹介したいためにこの形とした。

> キャストとは実行時にある値の型を別の型に変換すること。
> 引用：[型アサーション「as」(type assertion)](https://typescriptbook.jp/reference/values-types-variables/type-assertion-as)

補足2

```ts
countsMap.get(c)!
```

末尾 `!` について。
これはnon nullアサーション演算子という。
これはnullまたはundefinedを取り得る値の場合に用いて、nullまたはundefinedを除く型アサーションする演算子。
[Map.get](https://developer.mozilla.org/ja/docs/Web/JavaScript/Reference/Global_Objects/Map/get)は与えられたキーに合致する値があればそれを返却するのだが、なければundefinedを返却する。
今回は補足1の方で存在することが確定しているため、ないことがない。
なのでundefinedは返ってこないものとしてnon nullアサーションしている。

また、似たものとしてオプショナルチェーン(?)がある。
これについては下記リンクを参照されたい。

- [MDN](https://developer.mozilla.org/ja/docs/Web/JavaScript/Reference/Operators/Optional_chaining)
- [サバイバルTypeScript](https://typescriptbook.jp/reference/values-types-variables/object/optional-chaining)

## 4. 文字列内に同じ文字種がいくつ含まれるか

3の応用。
英大文字、英小文字、数字が混ざった文字列が与えられる。
文字列内に英大文字、英小文字、数字がそれぞれ何種類ずつあるかを求める。

```ts
const CHAR_TYPE = ['upper', 'lower', 'num'] as const;
type CharsType = typeof CHAR_TYPE[number];

type CountInterface = {
    [k in CharsType]: number;
};

const counts: CountInterface = {
    upper: 0,
    lower: 0,
    num: 0,
};

input.trim().split('').forEach((c) => {
    // 英小文字a~z
    if (/^[a-z]+$/.test(c)) {
        counts.lower += 1;
    }
    // 英大文字A~Z
    if (/^[A-Z]+$/.test(c)) {
        counts.upper += 1;
    }
    // 数字0~9
    if (/^[0-9]+$/.test(c)) {
        counts.num += 1;
    }
});

console.table(counts);
```

出力例

```
>> input = 'abc123XYZdef456IJK'
┌─────────┬────────┐
│ (index) │ Values │
├─────────┼────────┤
│  upper  │   6    │
│  lower  │   6    │
│   num   │   6    │
└─────────┴────────┘
```

- [正規表現](https://developer.mozilla.org/ja/docs/Web/JavaScript/Guide/Regular_expressions)
- [RegExp.test](https://developer.mozilla.org/ja/docs/Web/JavaScript/Reference/Global_Objects/RegExp/test)

補足

```ts
type CountInterface = {
    [k in CharsType]: number;
};
// 下記のように解釈される
type CountInterface = {
    upper: number;
    lower: number;
    num: number;
};
```

typeでないとできないので注意