アルゴリズムの分解を続けましょう:
function findLivingCats() {
var mailArchive = retrieveMails();
var livingCats = {"Spot": true};
function handleParagraph(paragraph) {
if (startsWith(paragraph, "born"))
addToSet(livingCats, catNames(paragraph));
else if (startsWith(paragraph, "died"))
removeFromSet(livingCats, catNames(paragraph));
}
for (var mail = 0; mail < mailArchive.length; mail++) {
var paragraphs = mailArchive[mail].split("\n");
for (var i = 0; i < paragraphs.length; i++)
handleParagraph(paragraphs[i]);
}
return livingCats;
}
var howMany = 0;
for (var cat in findLivingCats())
howMany++;
print("There are ", howMany, " cats.");
アルゴリズム全体が 1 つの関数によってカプセル化されました。これで実行後の乱雑さもなくなります: livingCats は最上位変数ではなく関数内のローカル変数となり、関数が実行中の間だけ存在します。このセットを必要とするコードは findLivingCats を呼び出すことができ、返される値を使います。
handleParagraph を別の関数にすることによって明確さも増しました。しかしこれは猫のアルゴリズムに直結しているため、他の状況では意味を持ちません。さらに、livingCats 変数にアクセスする必要があるため、完全に 関数内関数 の候補です。findLivingCats 内にいる時だけ意味を持ち、親関数の変数にアクセスします。
この解決策は前のものよりも 大きく なってしまいました。しかし前の解決策よりは整然としているし、読み易さも向上したと思います。
プログラムはまだ Eメール内の他の多くの情報を無視しています。誕生日、死亡日、母親の名前等です。
日付から始めます: 日付を保存する良い方法として、オブジェクトに 3 つの属性 year、month、および day を作り、それらに数字を保存することができます。
var when = {year: 1980, month: 2, day: 1};
JavaScript は既にこの目的のためのオブジェクトを提供しています。このようなオブジェクトはキーワード new を使って作成することができます:
var when = new Date(1980, 1, 1);
show(when);
既に知っている中括弧とコロンを使った表記法のように、new はオブジェクト値を作成する方法です。全ての属性名と値を指定するのではなく、関数を使ってオブジェクトを作ります。これはオブジェクト作成の標準的な手順を定義することを可能にします。このような関数を コンストラクターと呼びます。この書き方は 8 章 で説明します。
Date コンストラクターは色々な方法で使うことができます。
show(new Date());
show(new Date(1980, 1, 1));
show(new Date(2007, 2, 30, 8, 20, 30));
このように、これらのオブジェクトは日付と共に時間を保存することができます。引数を与えないと、現在日時を表すオブジェクトが作られます。引数で日付と時間を指定します。年、月、日、時間、分、秒、ミリ秒の順で、最後の 4 つは任意です。指定しない場合は 0 になります。
月は 0 から 11 です。一方、日は 1 から始まる ため、間違えないように気を付けてください。
Date オブジェクトのコンテンツは複数の get... メソッドで調べることができます。
var today = new Date();
print("Year: ", today.getFullYear(), ", month: ",
today.getMonth(), ", day: ", today.getDate());
print("Hour: ", today.getHours(), ", minutes: ",
today.getMinutes(), ", seconds: ", today.getSeconds());
print("Day of week: ", today.getDay());
getDay を除く全てのメソッドは set... 変数を持ち、これを使って日付オブジェクトの値を変えることができます。
オブジェクト内では、日付は 1970 年 1月 1日からの累算ミリ秒で表されます。すごい量の数字になることは想像に易いと思います。
var today = new Date();
show(today.getTime());
日付を比較すると便利です。
var wallFall = new Date(1989, 10, 9);
var gulfWarOne = new Date(1990, 6, 2);
show(wallFall < gulfWarOne);
show(wallFall == wallFall);
// but
show(wallFall == new Date(1989, 10, 9));
<、>、<=、>= で望みどおりの比較を行うことができます。1 つの日付オブジェクトを == で自身と比較すると結果は true です。当然です。しかし、== で同じ日付を持つ異なる日付オブジェクトと比較したら? false が返されますよネ?
前に述べたように、== で 2 つの異なるオブジェクトを比較すると、それらの属性が同じでも false が返されます。人は >= も == も大して変わらないと思っているので、これでは少々不器用で間違いの元になります。2 つの日付が等しいかをテストするには次のように行います:
var wallFall1 = new Date(1989, 10, 9),
wallFall2 = new Date(1989, 10, 9);
show(wallFall1.getTime() == wallFall2.getTime());
日付と時間に加え、Date オブジェクトには timezone の情報も含まれます。Amsterdam で 1時 のとき、時期によって異なりますが、London では正午、New York では朝の 7時 です。このような時間はタイムゾーンを考慮に入れてはじめて比較可能になります。Date の getTimezoneOffset 関数で GMT (Greenwich Mean Time: グリニッジ標準時) からの差を求めることができます。
var now = new Date();
print(now.getTimezoneOffset());
Ex. 4.6
"died 27/04/2006: Black Leclère"
都合の良いことに、日付部分は常に段落の同じ場所にあります。日付のある段落を引数とし、日付を抜き出し、それを日付オブジェクトとして返す関数 extractDate を書いてください。
function extractDate(paragraph) {
function numberAt(start, length) {
return Number(paragraph.slice(start, start + length));
}
return new Date(numberAt(11, 4), numberAt(8, 2) - 1,
numberAt(5, 2));
}
show(extractDate("died 27-04-2006: Black Leclère"));
Number を呼び出さなくてもかまわないでしょうが、前にも述べたように、私は文字列を数字のように使うことを好みません。内部関数は Number と slice の部分を 3 回繰り返すことを避けるために導入しました。
月に - 1 を使っていることに注意してください。多くの人がそうであるように、叔母 Emily も月を 1 から数えます。そこで Date コンストラクターに渡す前に値を調整しなければなりません。(Date オブジェクトは日数を人間と同じ方法でカウントするため、日にはこの問題はありません。)
10 章 では、固定構造を持った文字列からより実用的なより確実な方法で断片を抽出します。
ここから猫情報の保存方法が変わります。セットにただ値 true を置くのではなく、猫情報を持ったオブジェクトを保存します。猫が死ぬと、それをセットから削除せずに、オブジェクトに death 属性を追加して死亡した日付を保存します。
これは addToSet と removeFromSet 関数が不要になったことを意味します。同じようなものが必要ですが、それは誕生日と母親の名前も保存できるものでなければなりません。
function catRecord(name, birthdate, mother) {
return {name: name, birth: birthdate, mother: mother};
}
function addCats(set, names, birthdate, mother) {
for (var i = 0; i < names.length; i++)
set[names[i]] = catRecord(names[i], birthdate, mother);
}
function deadCats(set, names, deathdate) {
for (var i = 0; i < names.length; i++)
set[names[i]].death = deathdate;
}
catRecord はこれら保存オブジェクトを作成するための別の関数です。Spot のためのオブジェクトを作成するような時に役に立ちます。'Record' はこのようなオブジェクトに良く使われる用語で、限られた数の値をグループ化するのに使われます。
では段落から母猫の名前を抽出しましょう。
"born 15/11/2003 (mother Spot): White Fang"
これを行う 1 つの方法としては...
function extractMother(paragraph) {
var start = paragraph.indexOf("(mother ") + "(mother ".length;
var end = paragraph.indexOf(")");
return paragraph.slice(start, end);
}
show(extractMother("born 15/11/2003 (mother Spot): White Fang"));
開始場所を "(mother " の長さのために調節しなければならないことに注意してください。indexOf が終了場所ではなく開始場所を返すからです。