配列したい([] / Array)

1// JavaScript
2const array = ["a1", "a2", "a3"];
3
4// TypeScript
5const array: string[] = ["a1", "a2", "a3"];
6const array: Array<string> = ["a1", "a2", "a3"];

[]リテラルで配列を初期化できます。 TypeScriptでは、配列の要素の型名も指定する必要があります。

1const array = new Array("a1", "a2", "a3");

new Array()コンストラクターでも配列を初期化できます。 ただし、引数の扱いに注意が必要なため、通常はリテラル記法で記述すれば問題ありません。

値を追加したい(Array.push)

1// 空の配列を作成し、値を追加する
2const array = new Array();
3array.push("a1");
4array.push("a2");
5array.push("a3");

.pushで配列に値を追加できます。

1// 配列の要素を取得
2console.log(array[0]);  // -> a1
3console.log(array[1]);  // -> a2
4console.log(array[3]);  // -> a3

配列のインデックスを指定して、要素を取得できます。

1array.length;  // -> 3
2for (let i = 0; i < array.length; i++ ) {
3    const item = array[i];
4    console.log(item);
5}

.lengthで配列の長さを取得できます。

結合したい(Array.concat)

1const array1 = ["a", "b", "c"];
2const array2 = [1, 2, 3];
3const array3 = ["7", "8", "9"];
4const array4 = array1.concat(array2, array3);
5console.log(array4);
6// -> ["a", "b", "c", 1, 2, 3, "7", "8", "9"];

concatメソッドで配列を連結できます。 複数の配列を連結できます。

1const array5 = [...array1, ...array2];
2console.log(array5);
3// -> ["a", "b", "c", 1, 2, 3];

スプレッド演算子(...配列)でも連結できます。

ループしたい

1// for...ofループ
2for (const item of arrays) {
3    console.log(item);
4}

forEachしたい

1// forEachメソッド
2const newArrays = arrays.forEach(item => {
3    // 処理
4    console.log(item)
5})

mapしたい

1// mapメソッド
2const newArrays = arrays.map(item => {
3    // 処理
4    return 結果;
5})

mapメソッドで配列の要素に対して、同じ処理を適用できます。

1// 平方根を計算したい
2const numbers = [1, 4, 9, 16];
3const sqrtNumbers = numbers.map(Math.sqrt);
4console.log(sqrtNumbers);
5// -> [1, 2, 3, 4];

filterしたい

1// filterメソッド
2const newArrays = arrays.filter(callbackFn);
3
4const newArrays = arrays.filter(item => {
5    // フィルター処理
6    return 条件;
7})

filterメソッドを使って、配列から条件にマッチした要素を抽出できます。

1// 正の数を取得したい
2const numbers = [1, -4, 9, -16];
3const positives = numbers.filter(num => num > 0);
4console.log(positives);
5// -> [1, 9];

reduceしたい

1const newScalar = arrays.reduce(callbackFn, initialValue);
 1const numbers = [10, 20, 30, 40];
 2const sum = numbers.reduce((left, right) => left + right, 0);
 3// step1. 初期値を 0 に設定
 4// step2. left=0, right=10 で計算 -> 10
 5// step3. left=直前の値, right=20 で計算 -> 30
 6// step4. left=直前の値, right=30 で計算 -> 60
 7// step5. left=直前の値, right=40 で計算 -> 100
 8const average = sum / numbers.length;
 9console.log(sum);  // -> 100
10console.log(average);  // -> 25

flatしたい

1const array1 = [1, 2, [3, 4]];
2const array2 = array1.flat();
3console.log(array2);
4// -> [1, 2, 3, 4];

flatメソッドで配列を平坦化できます。 引数に平坦化の深さを指定できます。 平坦化する際、配列の空要素は削除されます。

最大値・最小値したい(Math.max / Math.min)

1const numbers = [10, 20, 5, 40];
2const max = Math.max(...numbers);
3const min = Math.min(...numbers);

Math.max、Math.minとスプレッド演算子(...配列名)を使って、 最大値、最小値を取得できます。

配列同士の演算したい

1left = [1, 2, 3];
2right = [4, 5, 6];
3const added = left.map((value, index) => value + right[index]);
4const subtracted = left.map((value, index) => value - right[index]);
5const multiplied = left.map((value, index) => value * right[index]);
6const divided = left.map((value, index) => value / right[index]);
7const modulo = left.map((value, index) => value % right[index]);
8const powered = left.map((value, index) => Math.pow(value, right[index]));

配列同士の演算はビルトインされていないので、mapメソッドを使って自分で定義します。

1function addLists(left, right) {
2    if (left.length !== right.length) {
3        throw new Error("Arrays must have the same length.");
4    }
5    return left.map((value, index) => value + right[index]);
6}

それぞれ関数にしておくとよさそうです。

1const arrays = [
2    [1, 2, 3],
3    [4, 5, 6],
4    [7, 8, 9]
5];
6const result = arrays.reduce((left, right) => addLists(left, right));
7console.log(result);  // -> [12, 15, 18]

3つ以上の配列をまとめて処理したい場合は、前述のreduceと組み合わせると簡潔に書けます。

2次元配列したい([][])

1// セル用の型を定義
2type Cell = string | number | boolean | Date | null;
3const values: Cell[][] = sheet.getDataRange().getValues();

スプレッドシートにあるデータを取得すると、2次元配列(行x列)として返ってきます。 Cell型を定義することで型安全を保つことができます。 getDataRange / getValuesによるデータの取得方法についてはシートを操作したいを参照してください。

1// ヘッダー(header)とデータ(rows)に分割
2// ヘッダー = 1行目:文字列に変換(Cell[] -> string[])
3// データ = 2行目以降:型はそのまま(Cell[])
4const header = values[0].map(String);
5const rows = values
6  .slice(1)
7  .filter(row => row.some(cell => cell !== null && cell !== ""));

さらに、1行目がヘッダー、2行目以降がデータ領域となっていることが多いです。 ヘッダー(header)とデータ(rows)に分割しておくとよいです。

ヒント

スプレッドシートから取得したデータは Cell[][]型となっています。 ヘッダーに相当する1行目もCell[]になっているため、 上記のサンプルではstring[]に変換しています。 また、データ領域からは、["", ""] や ["", null]、[null, null]のようなどのカラムにも値が入っていない空行を除外しています。

データ(rows)は、そのまま2次元配列(Cell[][])として扱えます。 用途に応じて、行アクセス、列アクセス、オブジェクト・Map型への変換などができます。

行インデックスでアクセスしたい

1for (const row of rows) {
2    const col0 = row[0];
3    const col1 = row[1];
4}

for...ofで1行(Cell[])ずつ取り出せます。 行の中の値には、さらに列インデックスでアクセスします。

列インデックスでアクセスしたい

1// age列(インデックス1)だけを取り出す
2const ageColumn: Cell[] = rows.map(row => row[1]);

row[列インデックス]で特定の列だけを取り出せます。 ただし列の並び順が変わるとインデックスもズレるため、 次の「カラム名でアクセスしたい」のほうが安全です。

カラム名でアクセスしたい

1function getColumn(rows: Cell[][], header: string[], columnName: string): Cell[] {
2  const index = header.indexOf(columnName);
3  if (index === -1) {
4    throw new Error(`列が見つかりません: ${columnName}`);
5  }
6  return rows.map(row => row[index]);
7}
8
9const ageColumn = getColumn(rows, header, "age");

header.indexOf(カラム名)で列インデックスを求めてからアクセスすると、 列の並び順が変わってもコードを修正せずに済みます。 存在しないカラム名を指定したときにすぐ気づけるよう、-1(見つからない)はエラーにしています。

特定のカラムのデータ型をチェックしたい

1function isNumberColumn(rows: Cell[][], header: string[], columnName: string): boolean {
2  return getColumn(rows, header, columnName).every(value => typeof value === "number");
3}
4
5const ok = isNumberColumn(rows, header, "age");

typeofとArray.everyを組み合わせて、列内のすべての値が期待する型かチェックできます。 getValues()が返すCell[][]は型が混在しうるため、 集計処理の前にこうしたチェックを挟んでおくと、 数値のつもりが文字列だった、といった事故を早期に検出できます。

特定のカラムのデータ内容でフィルタリングしたい

1const ageIndex = header.indexOf("age");
2const adults = rows.filter(
3  row => typeof row[ageIndex] === "number" && (row[ageIndex] as number) >= 30
4);

前述のfilterとindexOfを組み合わせると、特定カラムの値で行を絞り込めます。 Cell型はnumber以外の型も含むため、 比較演算子を使う前にtypeofで数値であることを確認しておくと安全です。

オブジェクトに変換したい

1// ヘッダー名をキーとして利用
2const data = rows.map(row =>
3  Object.fromEntries(
4    header.map( (key, i) => [key, row[i]] )
5  )
6);

Object.fromEntriesとheaderを組み合わせると、 1行を「カラム名: 値」のオブジェクトに変換できます。 data[0].ageのようにプロパティ名でアクセスできるようになるので、 列インデックスやカラム名を毎回指定するより読みやすくなります。

Mapに変換したい

1const maps: Map<string, Cell>[] = rows.map(row =>
2  new Map(header.map((key, i) => [key, row[i]]))
3);

オブジェクトの代わりにMap型に変換することもできます。 キーの並び順を保証したい場合や、キーが動的に変わる場合はMapのほうが扱いやすいです。

ヒント

シートへの書き込み(setValues)は、 セル操作したい(Range)で扱います。

リファレンス