Panda Noir

JavaScript の限界を究めるブログです。

配列の使いみちを実例付きで解説する

(この記事はQiitaで僕が書いたものを移行した記事です。記事中のコメントはQiitaの該当記事を参照ください)

対象読者

「配列なんて何に使うんだよ…変数でいいじゃん。」みたいに感じている初心者だった頃の私のような人。

「配列は役立つなぁ。便利だなぁ。」と実感できるような例を集めてみました。

  1. 同じようなデータに対して同じ処理ができる
  2. 数学の数列がそのまま扱える
  3. 配列ならではの処理ができる
  4. データのキャッシュに使う
  5. 関数から複数の値を返すことができる

1. 同じようなデータに対して同じ処理ができる

例えばある商品A、B、Cがあるとします。それらの税抜きの値段をそれぞれ30、350、1000円とします(これが「同じようなデータ」です)。プログラムにするとこんな感じ。

const a = 30, b = 350, c = 1000;

まだ、配列の出番はありませんね。次にそれぞれ税込みの値段を出してみます。

const tax = 1.08;// 2015年現在の消費税。
console.log(Math.floor(a * tax));
console.log(Math.floor(b * tax));
console.log(Math.floor(c * tax));

これでもまだ配列の出番はありませんね(あると処理が楽にはなりますが)。

次の場合はどうでしょうか? まず、外部ファイル price.json を用意します。そしてこのファイルから値段を読み込むようにします。

(price.json)

{
    "a": 30,
    "b": 350,
    "c": 1000
}
let a, b, c;
$.getJSON('price.json', function(data) {
    // 読み込む処理
    a = data.a; // 30
    b = data.b; // 350
    c = data.c; // 1000
})

ここまでは何も変わりません。消費税計算は変わりませんね。

ここに商品Dを追加します。

(price.json)

{
    "a": 30,
    "b": 350,
    "c": 1000,
    "d": 5000000
}

もちろん商品Dの値段dを新たに読み込む処理をJavaScript側に追加しなければなりませんね。

let a, b, c, d;
$.getJSON('price.json', function(data) {
    a = data.a;
    b = data.b;
    c = data.c;
    d = data.d;
})

消費税の計算部分にも変更が出ます。

const tax = 1.08;
console.log(Math.floor(a * tax));
console.log(Math.floor(b * tax));
console.log(Math.floor(c * tax));
console.log(Math.floor(d * tax));

さらに商品Eを、商品Fを商品G…と追加したくなったらどうしますか?面倒ですよね。ここで配列の登場です。配列は同じような種類のデータ(ここでは商品の値段)に対して同じ処理をすることに長けています。

(price.json)

[30, 350, 1000, 500000]
let prices = [];
$.getJSON('price.json', function(data) {
    // price.jsonから読み込んで値段を prices という配列に格納します。
    prices = data;
})
const tax = 1.08;
for (let i = 0; i < price.length; i++) {
    console.log(Math.floor(price[i] * tax));
}

これなら商品を追加したくなっても、 price.json に変更を加えるだけで、プログラムに変更を加えなくて済みますね。楽です。

2. 数学の数列がそのまま扱える

例えば

a[0] = 5
b[0] = 6
a[n+1] = a[n] + 2b[n]
b[n+1] = 3a[n] + 4b[n]

という連立漸化式を計算できます(a[n]の一般項を出す、という意味ではなくa[n]を計算して出せる、という意味です)。 コードにするとこうです。

const a = [], b = [];
const n = 3;
a[0] = 5, b[0] = 6;
for (let i = 0; i < n; i++) {
    a[i + 1] = a[i] + 2 * b[i];
    b[i + 1] = 3 * a[i] + 4 * b[i];
}
console.log(a[n]);

漸化式をほぼ数式そのままで表すことができています。もちろん連立漸化式以外の漸化式も計算できます。

3. 配列ならではの処理ができる

.reduce()、 .join()、 .slice()、 .map()など配列ならではのメソッドがあります。他にも二分探索という、配列の中からある値を探しだすアルゴリズムもありますし、配列に格納しておけばソート処理なんかもできます。文字列も根本的には配列に通ずるところがあります(「文字」という似ているデータを格納した配列に見えますよね。実際Cなんかではそうなっています)。

次はよく使いそうな感じのサンプルです。

const files = ['text2.txt', 'text1.txt', 'text4.txt', 'text3.txt'];// 今のディレクトリ内のファイル一覧
files.sort();
console.log(files.join(', '));// text1.txt, text2.txt, text3.txt, text4.txt
const cart = [185, 117, 78, 78, 175, 138, 350, 295];// 買い物カゴの中の商品の税抜き値段
const sum = cart.map(n => Math.floor(n * tax)).reduce((a, b) => a + b, 0);// 税込みでカート内の合計金額
console.log(sum);
let cards = ['Spade A', 'Diamond 13', 'Heart 6'];// 手札のカード
deck.push(cards[0]);// 山札に捨てる
cards = cards.slice(1,2);// カードを捨てた後の手札

4. データのキャッシュに使う

これは頻繁に、本当に頻繁に使います。処理が重くなった時にバンバン使います(まあ配列よりはオブジェクトをキャッシュに使うことが多いですが)。

const cache = [];
const CACHE_LIMIT = 1950;
function calendar(year, month) {
    //year年month+1月のカレンダーを配列で返す関数。month+1となっているのはJavaScriptのDate型に合わせたほうが何かと都合がいいから。
    if (cache[year - CACHE_LIMIT] && cache[year - CACHE_LIMIT][month]) {
        return cache[year - CACHE_LIMIT][month];
    }
    const res = [];
    const week = [];
    const first = new Date(year, month, 1);//月初
    const last = new Date(year, month + 1, 0);//月末
    for(let i = 0, _i = first.getDay(); i < _i; i = 0|i+1) {
        week.push('');
    }
    for(let i = first.getDate(); i < last.getDate(); i++) {
        week.push(i);
        if (week.length === 7) {
            res.push(week);
            week = [];
        }
    }
    if (week.length > 0){
        for(var i = week.length; i < 7; i++) {
            week.push('');
        }
        res.push(week);
    }
    if (!cache[year - CACHE_LIMIT]) cache[year - CACHE_LIMIT] = [];
    cache[year - CACHE_LIMIT][month] = res;
    return res;
};

CACHE_LIMITを使う理由は、配列が穴だらけになっているとパフォーマンスが落ちてキャッシュを使う意味がなくなるからです。 例えば calendar(2015, 0); と呼び出すと cache は2014まで何もない配列となります。2014まで何もないというような大きい配列はかなり処理に負担がかかるそうです(詳細な原因はわかりませんが、以前Google I/O 2012でやってました)。

1950年より前のカレンダーは読み込まないだろう、という想定でやっています(そもそもキャッシュが必要ということは、同じ引数での関数の呼び出しが頻繁にあるということです。普通は今年から去年くらいまで関数で取得できれば問題ありません)。 まあ、この関数はほぼ拙作のRADIOの関数まんまです。実際に使っているコードなので例としては最適です。

5. 関数から複数の値を返すことができる

関数内ではreturnは一つのみです。だから、return 1;のように一つしか返せません。しかし、現実には「ユーザー一覧が欲しい」「解が複数ある」というように複数の値が欲しい時が多々あります。そんな時は配列を返すことで実現できます。

function getUserList() {
    return ['Tom', 'John', 'Mike'];
}

取り出す側は返り値を配列として扱う必要があります。今まで一つの値しか返してなかった場合何らかの変更が生じるので注意してください。