【Eloquent JavaScript和訳】 Chapter 4:データ構造: オブジェクトと配列

オブジェクトの値は色々な役割を果たします。セットのような役割を果たすのもその一つです。本章ではその役割の幾つかを示し、8 章 でオブジェクトの重要な使い方を説明します。

猫の問題を解決するプランで ― これから述べることを考えると、プランではなく アルゴリズム を呼んだほうが良いでしょう ― アルゴリズムの中で、アーカイブの全ての Eメール調べる、と述べました。このアーカイブとは一体どんなものでしょう? 何処からやって来たのでしょう?

ここでは 2 番目の質問は無視してください。14 章 でプログラムに重要なデータを取り込む方法を説明します。とりあえず、マジックか何かで Eメールが存在することにしましょう。マジックもコンピューターの中では簡単に実現することもあります。

しかしアーカイブがどのように保存されるかは興味深い疑問です。そこには複数の Eメール が保存されています。Eメールが文字列であることは明らかに可能です。アーカイブ全体を大きな文字列に置くことは可能ですが、実用的ではありません。我々が欲しいのはバラバラの文字列の集合です。

集合はオブジェクトが対象とするものです。次のようにオブジェクトを作ることができます:

var mailArchive = {"the first Eメール": "Dear nephew, ...",
"the second Eメール": "..."
/* and so on ... */};

これでは Eメール は初めから終わりまで調べるのは困難です ― どうやってプログラムは属性名を推測するのか? これはもっと予測可能な属性名を使うことで解決します:

var mailArchive = {0: "Dear nephew, ... (mail number 1)",
1: "(mail number 2)",
2: "(mail number 3)"};

for (var current = 0; current in mailArchive; current++)
print("Processing Eメール #", current, ": ", mailArchive[current]);

幸運にも、このような目的に使える特別なオブジェクトがあります。配列と呼ばれるもので、配列中に複数の値を含む length 属性や、この種の集合に対して有益な処理をいくつも提供します。

配列を各括弧 ([ と ]) を使って新たに作成することができます:

var mailArchive = ["mail one", "mail two", "mail three"];

for (var current = 0; current < mailArchive.length; current++)
print("Processing Eメール #", current, ": ", mailArchive[current]);

この例で、エレメントの数は明示的に指定されていません。最初のエレメントは 0、次のエレメントは 1、という具合にカウントされます。

何故 0 から始めるのでしょう?人はカウントは 1 から始めるものと思っています。直感的に理にかなっていないように思えますが、集合内のエレメントに 0 から番号を振ることは実用的な場合が多いのです。次第に分かってくると思いますので、このまま続けましょう。

エレメント 0 から始めることは、集合内に X 個のエレメントがあり、最後のエレメントは X - 1 の場所にあることも意味します。これが例題の for ループが current <mailArchive.length をチェックする理由です。mailArchive.length の場所にはエレメントが無いため、current がその値になるとループを止めます。

Ex. 4.2

正数を引数として取り、0 から与えられた数までの全てを含む配列を返す関数 range を書いてください。

[] を入力することで空の配列ができます。= 演算子を持つ値を指定することでオブジェクトに属性を追加し、配列にも追加されることを思い出してください。length 属性はエレメントが追加されると自動的に更新されます。

function range(upto) {
var result = [];
for (var i = 0; i <= upto; i++)
result[i] = i;
return result;
}
show(range(4));

今までのようにループ変数に counter または current という名前を付ける代わりに、ここでは簡単に i を使います。ループ変数に i、j、または k のような 1 文字を使うことはプログラマーの間ではよく行われていることです。元を辿れば怠惰がその理由ですが、タイプするには 7 文字よりは 1 文字のほうが簡単だし、counter や current が引数の意味をはっきりさせる名前だとも思えません。

プログラムで頻繁に 1 文字の変数を使うと、信じられないほど紛らわしいものになってしまいます。私はよくあるケースに限ってこれを使うようにしています。小さなループがその例です。ループに別のループがループが含まれ、そのループに変数名 i が使われている場合、内部ループは外部ループが使っている変数を変更してしまい、全てが中断してしまいます。内部ループに j を使うことはできますが、一般的に、ループ本体が大きい場合は意味の分かりやすい変数名を考えたほうが良いでしょう。

文字列オブジェクトも配列オブジェクトも、length 属性だけでなく、関数値を参照する複数の属性を含みます。

var doh = "Doh";
print(typeof doh.toUpperCase);
print(doh.toUpperCase());

全ての文字列は toUpperCase 属性を持ちます。呼び出されると、全ての文字を大文字に変換して文字列のコピーを返します。toLowerCase 属性もあります。どうなるかはご想像の通りです。

toUpperCase 呼び出しが引数を渡さなくても関数は文字列 "Doh" にアクセスすることに注意してください。そしてその値は属性です。これについては 8 章 で詳しく述べます。

'toUpperCase は文字列オブジェクトのメソッドである' と同様、関数を含む属性は一般的に メソッド と呼ばれます。

var mack = [];
mack.push("Mack");
mack.push("the");
mack.push("Knife");
show(mack.join(" "));
show(mack.pop());
show(mack);

配列に関連するメソッド push を使って値を追加することができます。前の例では、result[i] = i の代わりとして使うことができました。push に対して pop は値を取り除き、配列の最後の値を返します。join は文字列の配列から 1 つの大きな文字列を作成します。与えられたパラメーターは配列中の値の間でペーストされます。

猫の話に戻り、配列が Eメールのアーカイブを保存するのに良い方法だと言うことが分かりました。本ページでは、関数 retrieveMails を使って (マジックのように) この配列を手に入れます。Eメールを一つ一つ調べるのもロケット科学ほど難しくなくなりました:

var mailArchive = retrieveMails();

for (var i = 0; i < mailArchive.length; i++) {
var email = mailArchive[i];
print("Processing Eメール #", i);
// Do more things...
}

生存している猫のセットを表す方法も決まりました。次の問題は、Eメール中の "誕生 (born)" または "死亡 (died)" で終わる段落を見つけることです。

まず、段落とは一体何かという疑問が浮かんできます。この場合、文字列の値は役に立ちません: JavaScript でのテキストの概念は '文字の並び' 以上に深く入り込んでいきません。そこで段落を定義する必要があります。

前に改行文字のようなものがあることを知りました。段落を分割するのに使われます。そこで、段落を、改行文字でコンテンツが始まり次の改行文字でコンテンツが終わる部分を、Eメールの一部と考えます。

文字列を段落に分割するアルゴリズムを自分で書く必要もありません。文字列には既に split をいうメソッドがあり、配列の join メソッドと (ほとんど) 逆の機能を持っています。自身のアルゴリズムで何処で分けるかを判断し、与えられた文字列を配列に分割します。

var words = "Cities of the Interior";
show(words.split(" "));

こうして、改行 ("\n") 毎に分け、Eメールを段落に分割することができます。

Ex. 4.3

split と join は互いに全く逆のものとは言えません。string.split(x).join(x) は常に元の値を生成しますが、array.join(x).split(x) は違います。.join(" ").split(" ") が異なる値を生成する配列の例を考えてください。

var array = ["a", "b", "c d"];
show(array.join(" ").split(" "));