Ex. 4.7
extractMother が行うことをもっと一般的な方法で表すことができます。全て文字列である 3 つの引数を取る between 関数を書いてください。最初の引数の部分であり、かつ 2番目と 3番目の引数で与えられるパターンの間に現れる部分が返されます。
したがって、between("born 15/11/2003 (mother Spot): White Fang", "(mother ", ")") は "Spot" になります。
between("bu ] boo [ bah ] gzz", "[ ", " ]") は "bah" を返します。
2番目のテストを有効にするためには、indexOf に 2番目のパラメーターとして検索の開始ポイントを指定する任意パラメーターを与えることができることを知っておくと便利です。
function between(string, start, end) {
var startAt = string.indexOf(start) + start.length;
var endAt = string.indexOf(end, startAt);
return string.slice(startAt, endAt);
}
show(between("bu ] boo [ bah ] gzz", "[ ", " ]"));
between は extractMother をよりシンプルに表すことを可能にします:
function extractMother(paragraph) {
return between(paragraph, "(mother ", ")");
}
改良した新しい猫アルゴリズムは次のようになります:
function findCats() {
var mailArchive = retrieveMails();
var cats = {"Spot": catRecord("Spot", new Date(1997, 2, 5),
"unknown")};
function handleParagraph(paragraph) {
if (startsWith(paragraph, "born"))
addCats(cats, catNames(paragraph), extractDate(paragraph),
extractMother(paragraph));
else if (startsWith(paragraph, "died"))
deadCats(cats, catNames(paragraph), extractDate(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 cats;
}
var catData = findCats();
追加情報によって叔母 Emily の猫についての手掛かりをついに掴むことができます。このような関数は便利です:
function formatDate(date) {
return date.getDate() + "/" + (date.getMonth() + 1) +
"/" + date.getFullYear();
}
function catInfo(data, name) {
if (!(name in data))
return "No cat by the name of " + name + " is known.";
var cat = data[name];
var message = name + ", born " + formatDate(cat.birth) +
" from mother " + cat.mother;
if ("death" in cat)
message += ", died " + formatDate(cat.death);
return message + ".";
}
print(catInfo(catData, "Fat Igor"));
catInfo の最初の return ステートメントは非常口として使います。その猫に関するデータが無い場合、関数の残り部分は無意味になります。そこで、すぐに値を返して以降のコードの実行を防ぎます。
昔、複数の return ステートメントを含む関数は悪いことだと考えるプログラマー集団がいました。実行されているコードの判別を困難にするからと言うのが理由でしたが、5 章 で述べるテクニックでこの考えが事実上時代遅れになってしまいました。しかし今でも return のような 'ショートカット' ステートメントの使用を批判する人がいるかもしれません。
Ex. 4.8
catInfo で使われる formatDate 関数は、1桁の数字の場合は月部分と日部分の前にゼロを追加しません。1桁の数字の場合にゼロを追加する関数に書き換えてください。
function formatDate(date) {
function pad(number) {
if (number < 10)
return "0" + number;
else
return number;
}
return pad(date.getDate()) + "/" + pad(date.getMonth() + 1) +
"/" + date.getFullYear();
}
print(formatDate(new Date(2000, 0, 1)));
Ex. 4.9
引数として猫を含むオブジェクトを与えると、生きている中で最年長の猫の名前を返す関数 oldestCat を書いてください。
function oldestCat(data) {
var oldest = null;
for (var name in data) {
var cat = data[name];
if (!("death" in cat) &&
(oldest == null || oldest.birth > cat.birth))
oldest = cat;
}
if (oldest == null)
return null;
else
return oldest.name;
}
print(oldestCat(catData));
if ステートメント中の条件は少々威圧的に思えますが、'猫が死んでおらず、oldest が null または現在の猫以後に誕生した場合、現在の猫を変数 oldest に保存せよ' という意味です。
この関数は data 中に生きている猫がいない場合は null を返します。この場合どのように解決しますか?
配列については既にご存知と思いますので、それに関連したことを述べます。関数が呼び出されると、関数本体の実行環境に arguments という特別な変数が追加されます。この変数は配列に似たオブジェクトを参照します。関数に与えられた引数は全て、最初の引数は属性 0、2番目の引数は属性 1 ...、という属性を持ちます。そしてこの変数は length 属性も持ちます。
このオブジェクトは実際には配列ではありませんが、push のようなメソッドを持たず、何かを追加しても length 属性を自動更新しません。何故なのかを調べたことはありませんが、調べてみる必要がありそうです。
function argumentCounter() {
print("You gave me ", arguments.length, " arguments.");
}
argumentCounter("Death", "Famine", "Pestilence");
関数のなかには print のように幾つでも引数を取ることができるものもあります。これらは通常 arguments オブジェクト内の値をループして処理を行います。その他の関数は、呼び出し元が指定しない場合は妥当な省略時値を使う、任意引数を取ることができます。
function add(number, howmuch) {
if (arguments.length < 2)
howmuch = 1;
return number + howmuch;
}
show(add(6));
show(add(6, 4));
Ex. 4.10
exercise 4.2 の range 関数を任意に 2番目の引数を取るように拡張してください。引数が 1つの場合は練習問題のように 0 から指定数までの範囲を生成し、引数が 2つの場合は最初の引数が範囲の最初で 2番目の引数が範囲の終わりになります。
function range(start, end) {
if (arguments.length < 2) {
end = start;
start = 0;
}
var result = [];
for (var i = start; i <= end; i++)
result.push(i);
return result;
}
show(range(4));
show(range(2, 4));
この任意引数は上記の add 例のそれとは同じではありません。これが与えられないと最初の引数は end として働き、start は 0 になります。
Ex. 4.11
「はじめに」で示したコードの次の行を思い出してください: