この記事の詳しい内容には毎日新聞の「ロボットが水をまく日は?」ページから有料記事に進むことで読めます。
目次
概要
ある公園に水撒きロボットがいます。ロボットは、天気が晴で気温が25度より高いときに水を撒きます。次の1週間の天候のとき、水を撒いたのはいつか? を求めるプログラミングを考えます。
答えは、人間なら簡単に分かります。晴で25度以上を探せばよいので、月曜と金曜です。このとき、ぱっと見で判断される方もおられるでしょうが、まず晴の曜日を探して月、木、金に絞り、その3つの気温を見て月、金という答えを出す方もおられるでしょう。
データ
データとは、コトバンクの「データ」項によると、「コンピュータで、プログラムを使った処理の対象となる記号化・数字化された資料」とあります(デジタル大辞泉の解説)。
今の場合、データは言うまでもなく、上図の天気と気温です。これをプログラムに持ち込んで、プログラミングで処理します。データで重要なことは、並びに一貫性があることです。上図で言うなら、曜日の1つ下には天気があり、その下に気温があることです。これが曜日によって逆になっていると、データの読み込みが不必要に面倒になります。
データを処理するプログラムを作成するときには、データを外部に置くと柔軟性が生まれます。一貫性のあるデータなら変更しても、プログラミングに手を加える必要はありません。
データの保持には、大きなものではデータベースが利用されますが、少量の場合にはテキストファイルも利用されます。プログラムを作成するときには、最初は内部に保持し、うまく動作するようになってから、データを外部に置いて読み込むようにします。
データとして分かりやすいのが、下図に示したエクセルの表です。エクセルを使うと、この表を簡単にグラフ化できます。
論理を考える
このお題のポイントは、ロボットが水を撒くときの条件です。ロボットは「天気が晴で気温が25度より高いとき」に水を撒くので、これをどうすれば、プログラミングコードで表すことができるか? です。
条件の要素は2つあります。
- 天気が晴
- 気温が25度以上
天気が晴ということの裏には、天気が晴でない(くもりか飴)ということがあります。同様に気温が25度以上ということの裏には、気温が25度以上でない(25度未満)ということがあります。これを場合分けすると、
- A:天気が晴で気温が25度以上
- B:天気が晴で気温が25度以上でない
- C:天気が晴でなく気温が25度以上
- D:天気が晴でなく気温が25度以上でない
の4つに分けることができます。
ベン図で考える
この場合分けは、数学で習った集合のベン図を使うと、下図のように表すことができます。
上図の条件を、それが当てはまる曜日に置き換えると、下図になります。火曜と水曜は、「晴でなく25度以上でない」Dの部分に含まれ、逆の「晴で25度以上」の月曜と金曜はAに含まれることになります。
下図はウィキペディアの「ベン図」項からの借り物です。赤い部分は「論理積」(交わり)よ呼ばれ、「晴で25度以上」という2つの条件が同時に成り立つ月曜と金曜が含まれる部分です。
下図の左は「論理和」(結び)と呼ばれ、「晴かまたは25度以上」が該当する月木金土日が含まれます。右の「論理否定」は「含まれない」ことを表し、今の場合には「晴でない」ことや「25度以上でない」でないことが表せます。
プログラミングで考える
プログラミングでは、何々が何々であるかどうかを、if文で調べることができます。たとえば、月曜の天気が晴であるかどうかを調べることができます。またelseをつづけると、何々が何々でない場合も調べることができます。
const mondayWeather = '晴';
if (mondayWeather === '晴') {
print('月曜は晴');
}else {
print('月曜は晴でない');
}
ifにつづく()の中には条件を表す式を入れます。JavaScriptはifを見つけるとそれにつづく()の中の式を評価(計算)して、その結果が真(true)である場合に、後につづく{と}の中に書かれたコードを実行します。真でなく偽(false)である場合には、elseの後につづく{と}の中に書かれたコードを実行します。式と評価の意味については「1:ブラウザのコンソール内のJavaScript Creative Coding p5.js」を参照してください。
if文の中でif文を使うと、何々が何々である場合に限ってこれこれがこれこれである場合を調べることができます。たとえば、月曜が晴でありかつ25度以上である場合を調べることができます。
const mondayWeather = '晴';
const mondayTemperature = 25;
if (mondayWeather === '晴') {
print('月曜は晴');
if (mondayTemperature >= 25) {
print('月曜は晴で25度以上');
}
}
else {
print('月曜は晴でない');
}
上記コードのprint(‘月曜は晴で25度以上’);は、前に述べた論理積に当たる部分であることにお気づきでしょうか? プログラミングではこのように、if…else文をうまく使うと、ベン図で表した部分を表すことができます。
データを配列で与える
上記のコード例では、月曜の天気と気温を別々の変数に入れています。この方法でも曜日ごとの結果は得られますが、全部の曜日の変数を合計で14個作ることになるので、少し大変です。そこで、曜日と天気、気温を1つの配列に入れて、曜日ごとに調べるようにします。
let arr = [‘月曜’, ‘晴’, 25];
if…elseのネスト
何々が何々である場合にこうして、何々が何々でない場合には別のことをする、ことが実行できるif…elseをネストすると、日本語で言ってもすぐには理解できないくらい複雑な場合分けが実行できます。
let arr = ['月曜', '晴', 25];
// arr = ['火曜', 'くもり', 23];
// arr = ['水曜', '雨', 22];
// arr = ['木曜', '晴', 23];
// arr = ['金曜', '晴', 28];
// arr = ['土曜', 'くもり', 26];
// arr = ['日曜', '雨', 25];
print(arr[0]);
if (arr[1] === '晴') {
// print('晴');
if (arr[2] >= 25) {
print('晴で25度以上'); // A:月曜、金曜 => この日に水を撒く
}
else {
print('晴で25度以上でない'); // B:木曜
}
}
else {
// print('晴でない');
if (arr[2] >= 25) {
print('晴でなく25度以上'); // C:土曜、日曜
}
else {
print('晴でなく25度以上でない'); // D:火曜、水曜
}
}
このコードを実行すると、変数arrには月曜のデータが割り当てられ(ほかの曜日のデータは// コメントされているので無視されます)、ネストされたif文で評価されます。[‘月曜’, ‘晴’, 25]の場合には、arr[1]が’晴’なので、最初のifにつづく{…}が実行され、2つめのifで(arr[2] >= 25)が調べられ、これはtrueなので、2つめのifにつづく{…}が実行され、’晴で25度以上’が[コンソール]に出力されます。
火曜を調べたいときには、// arr = [‘火曜’, ‘くもり’, 23];の//を削除します。すると前の行で変数arrに割り当てられた月曜の配列は火曜の配列に上書きされます。以降も同様にして各曜日を調べます。
if…elseのネストは複雑になるので、いきなり全部を書くのではなく、対応するifとelseのペアを書き、そこにprint()を使ってどの場合かを出力して進むようにします。対応するペアが分からなくなったら論理的に破綻するので、そうなった場合には最初から書き直すのが一番です。
全部の曜日の配列を実行すると、if…elseのネストで実行される各コードが、下図に示すベン図のA、B、C、Dに対応しているのが分かります。
論理積の&&
if…elseを使うと、A、B、C、Dすべての場合分けが行えますが、「晴で25度以上」の曜日だけ知りたい場合には、論理積の演算を行う&&演算子が使用できます。&&は’かつ’に読み替えることができます。次のifの条件式は、「arr配列の2番めの要素が’晴’でかつ3番めの要素が25以上なら」と解釈できます。
if (arr[1] === ‘晴’ && arr[2] >= 25) {…)
このifにelseをつなげると、水を撒かなかった曜日が特定できます。
if (arr[1] === '晴' && arr[2] >= 25) {
print('水を撒く');
}
else {
print('水は撒かない');
}
データをオブジェクトで表す
曜日のデータは、[‘月曜’, ‘晴’, 25]のように配列に入れて扱うこともできますし、Objectオブジェクトのプロパティとしてオブジェクトに持たせることもできます。データをオブジェクトし、それらを配列に入れると、forループで一括処理でき、記述コード量が減ります。
// 曜日と天気、気温を表すオブジェクトを曜日ごとに作成する
const monday = {
dayOfWeek: '月曜',
weatherString: '晴',
temperature: 25
}
const tuesday = {
dayOfWeek: '火曜',
weatherString: 'くもり',
temperature: 23
}
const wednesday = {
dayOfWeek: '水曜',
weatherString: '雨',
temperature: 22
}
const thursday = {
dayOfWeek: '木曜',
weatherString: '晴',
temperature: 23
}
const friday = {
dayOfWeek: '金曜',
weatherString: '晴',
temperature: 28
}
const saturday = {
dayOfWeek: '土曜',
weatherString: 'くもり',
temperature: 26
}
const sunday = {
dayOfWeek: '日曜',
weatherString: '雨',
temperature: 25
}
// 作成したオブジェクトを配列に入れる(forループで一括処理できるように)
const weekArray = [
monday, tuesday, wednesday, thursday, friday, saturday, sunday
];
let wateringDays = []; // 水を撒いた曜日のすオブジェクトを入れる配列
let noWateringDays = []; // 水を撒かなかった曜日のオブジェクトを入れる配列
// 一括処理
for (let i = 0; i < weekArray.length; i++) {
// オブジェクトから曜日と天気、気温のデータを取り出す
const day = weekArray[i].dayOfWeek;
const weather = weekArray[i].weatherString;
const temp = weekArray[i].temperature;
// 晴でかつ25度以上なら
if (weather === '晴' && temp >= 25) {
// 水を撒いた曜日の配列に追加
wateringDays.push(weekArray[i]);
// そうでないなら
}
else {
// 水を撒かなかった曜日の配列に追加
noWateringDays.push(weekArray[i]);
}
}
// 結果を出力
print(wateringDays);
print(noWateringDays);
このプログラムを実行すると、結果が[コンソール]に出力されます。黒い三角マークをクリックして展開すると、下図に示すように、曜日のオブジェクトが区別されているのが分かります。
とは言え、このように曜日のオブジェクトを手作りするのも大変です。最後におまけで、エクセルで作ったデータを利用するプログラムを見ていきます。
エクセルのデータを使用する
エクセルを使うと、本稿冒頭の図で示したような曜日と天気、気温のデータが容易に作成できます。JavaScriptのプログラムには、エクセルの.xlsxファイルはそのままでは読み込めませんが、エクセルから.csvファイル形式に書き出すことで、読み込めるようになります。具体的な方法については、「11_1:表データ p5.js JavaScript」で述べています。
* インターネットを介したデータの受け取りには、JSONと呼ばれる形式がよく使用されます。これについては「11_2:JSONデータ p5.js JavaScript」で述べています。
// 水を撒いた日のデータを入れる配列
let wateringDays = [];
// 水を撒かなかった日のデータを入れる配列
let noWateringDays = [];
function setup() {
createCanvas(400, 300);
background(200);
// CSVデータを読み込む
loadAndCheck('data/wateringData.csv');
}
// 渡されたCSVデータを読み込み、解析して、天気と気温の条件によって水を撒いた日と撒かなかった日に分ける
function loadAndCheck(csvData) {
loadTable(csvData, 'csv', 'header', (data) => {
// 読み込んだデータはこの関数にp5.Tableオブジェクトとして渡される
//print(data);
// データを走査し、必要な値を取り出してObjectオブジェクトにまとめる
for (let i = 0; i < data.getRowCount(); i++) {
const obj = {};
// i行の0、1、2、3列から値を得て変数に代入
const dateOfWeek = data.get(i, 0);
const weather = data.get(i, 1);
const temp = data.get(i, 2);
// CSVデータが空の場合があるので、それをのぞく
if (weather !== '') {
// Objectオブジェクトににデータを割り当てる
obj.dateOfWeek = dateOfWeek; // 曜日
obj.weather = weather; // 天気
obj.temp = temp; // 気温
// 晴で25度以上なら、水を撒いた配列に入れる
if (weather === '晴' && temp >= 25) {
wateringDays.push(obj);
// そうでなければ、水を撒かなかった配列に入れる
} else {
noWateringDays.push(obj);
}
}
}
// ボタンは、データの取り出しが終わってから作成する
const wateringButton = setButton('水を撒いた', {
x: 10,
y: 250
});
wateringButton.mousePressed(() => {
// 結果はオブジェクトの配列に入っている
print(wateringDays);
// 曜日だけを取り出して出力
const result = getOnlyDate(wateringDays);
print(result);
});
const noWateringButton = setButton('水を撒かなかった', {
x: 180,
y: 250
});
noWateringButton.mousePressed(() => {
print(noWateringDays);
const result = getOnlyDate(noWateringDays);
print(result);
});
});
}
// 曜日だけを取り出して,でつないで返す
function getOnlyDate(arr) {
// 空の文字列で開始
let dateStr = ''
// 配列に含まれるオブジェクトから曜日を取り出し、その文字列に','を加えてつなげる
for (i = 0; i < arr.length; i++) {
dateStr += arr[i].dateOfWeek + ',';
}
// 末尾の,を削除
const resultStr = dateStr.slice(0, -1);
// 結果の文字列を返す
return resultStr;
}
function setButton(label, pos) {
const button = createButton(label);
button.size(150, 40);
button.position(pos.x, pos.y);
return button;
}
プログラムを実行し、[水を撒いた]ボタンをクリックすると水を撒いた曜日が、[水を撒かなかった]ボタンをクリックすると水を撒かなかった曜日が、それぞれ[コンソール]に表示されます。
p5.jsのloadTable()関数など、CSVファイルのデータの読み取り方法については「11_1:表データ p5.js JavaScript」で述べています。