この記事の詳しい内容には毎日新聞の「野菜の重さをくらべる」ページから有料記事に進むことで読めます。
目次
概要
ニンジン、カボチャ、ダイコン、トマトという4つの野菜があり、それを重い順に並べるのが目的です。てんびんに2つの野菜をのせて計ると、下図の結果が得られました。
左から重い順に並んでいるのは、次の(1)から(4)のうちのどれでしょう?
- ダイコン、カボチャ、ニンジン、トマト
- ダイコン、カボチャ、トマト、ニンジン
- カボチャ、ダイコン、ニンジン、トマト
- カボチャ、ダイコン、トマト、ニンジン
論理を考える
上のてんびんを使った結果は、左上から下に向かって、
- ニンジンはカボチャより軽い
- ニンジンはダイコンより軽い
- ニンジンはトマトより重い
- カボチャはダイコンより重い
- ダイコンはトマトより重い
- カボチャはトマトより重い
ということを示しています。
消去法で考える
(1)から(4)で、(a)から(f)に当てはまらないものを消していきます。
- (1)と(2)は、ダイコンが一番重いことになっているので、「カボチャはダイコンより重い」に当てはまらない
- (4)は、トマトがニンジンより重いことになっているので、「ニンジンはトマトより重い」に当てはまらない
- 残ったのは(3)
よって答えは(3)です。確認してみると、(3)は(a)から(f)に全部当てはまることが分かります。
重い軽いの関係を論理的に考える
XXはXXより重い(軽い)という関係性を、不等号で表してみます。ニンジンはN、カボチャはK、ダイコンはD、トマトはTで表します。
- ニンジンはカボチャより軽い => N < K
- ニンジンはダイコンより軽い => N < D
- ニンジンはトマトより重い => N > T
- カボチャはダイコンより重い => K > D
- ダイコンはトマトより重い => D > T
- カボチャはトマトより重い => K > T
N < K、N < D、N > T なので、K > N > T、D > N > T だと分かります。
(K>N、D>Nで、N>Tなので、カボチャとダイコンは、ニンジン、トマトより重い)
K > D なので、 K > D > N > T だと分かります。
よって答えは(3)です(K > T は使わなくても分かります)。
プログラミングの論理立て
プログラミングでは、重い軽いの関係性を数値に置き換え、数値の大きい方が重い、小さい方が軽いと考えることができます。
しかし、たとえば N < K を比べるときには、NとKに数値が代入されている必要があります。そこで、以下では、各野菜を表す変数n,k,d,tにランダムな整数を割り当てることにします。
そして n < k, n < d など6つの式をif文の条件として用い、6つの条件が満たされるまで、ランダムな数値の代入をつづけるwhileループを継続させます。
なお、作成するランダムな数値は、大小を比べるためのものなので、たとえばトマトが2、ニンジンが1であっても問題にはなりません(数値は野菜の何グラムという重さを表す必要はありません)。
function setup() {
noCanvas();
// 6つの条件を満たす4つの数値が見つかったかどうか
// falseの間はずっとwhileループが実行される
let isOK = false;
while (!isOK) {
// 重さの数値をランダムに作成
const n = int(random(10)); // ニンジン
const k = int(random(10)); // カボチャ
const d = int(random(10)); // ダイコン
const t = int(random(10)); // トマト
// 6つの条件
if (n < k) {
if (n < d) {
if (n > t) {
if (k > d) {
if (d > t) {
if (k > t) {
// 6つの条件が同時に全部満たされたら、ループ終了
isOK = true;
print('OK');
print(n, k, d, t); // ニンジン、カボチャ、ダイコン、トマトの順
print(k, d, n, t); // 大きい順に並んでいればOK
}
}
}
}
}
}
}
}
現実には、カボチャよりも重いトマトはなかなかないと思われますが、ここではお化けニンジンや巨大トマトの出現の可能性も含めどの野菜も等しく扱っているということになります。
下図は実行結果の例です。条件が6つもあるので、全部が同時に成立するまで時間がかかりそうに思えるかもしれませんが、結果はすぐに出ます。前の「重い軽いの関係を論理的に考える」で述べているように、最後の k > t のチェックがなくても結果は変わりません。
野菜を画像で表し、配列を並べ替える
このお題の基本的な論理は上記コードが十分に表していますが、野菜のイメージを用意し、重い順で表示すると、結果が分かりやすくはなります。
以下はそのコードです。
let vImages;
// 野菜のイメージをプリロードし、vImages配列に入れる
// 野菜の順番はmakeVegetable()関数に渡す配列の文字列の順番と同じ
function preload() {
const ninjinImage = loadImage('images/ninjin.png');
const kabochaImage = loadImage('images/kabocha.png');
const daikonImage = loadImage('images/daikon.png');
const tomatoImage = loadImage('images/tomato.png');
vImages = [ninjinImage, kabochaImage, daikonImage, tomatoImage];
}
function setup() {
createCanvas(400, 200);
background(220);
// makeVegetable()に野菜の文字列の配列を渡して、Mapオブジェクトの配列を得る
let vArray = makeVegetable(['ニンジン', 'カボチャ', 'ダイコン', 'トマト']);
// Mapオブジェクトの配列をweightCompare()関数に渡して、
// 6個の条件を満たす重さを持った、並び替え済みのMapオブジェクトの配列を得る
let resultArray = weightCompare(vArray);
// キャンバスに描画する
const rows = 4;
const gutter = 10;
const offsetX = 20;
// 1x4のイメージの並びを描画
for (let i = 0; i < rows; i++) {
const img = resultArray[i].get('image');
image(img, offsetX + i * (gutter + 80), 50)
}
}
// 文字列の配列を受け取り、名前と重さ、イメージを属性に持つMapオブジェクトを作成して、
// 配列に入れて返す
function makeVegetable(vArray) {
let arr = [];
for (let i = 0; i < 4; i++) {
const map = new Map();
map.set('name', vArray[i]);
// 重さは最初0
map.set('weight', 0);
map.set('image', vImages[i]);
arr.push(map);
}
return arr;
}
// Mapオブジェクトの配列を受け取り、6つの条件を満たす重さを探って、
// 突き止めることができたら、重い順に並び替えて、結果の配列を返す
function weightCompare(arr) {
let isOK = false;
while (!isOK) {
const n = int(random(10)); // ニンジン
const k = int(random(10)); // カボチャ
const d = int(random(10)); // ダイコン
const t = int(random(10)); // トマト
if (n < k) {
if (n < d) {
if (n > t) {
if (k > d) {
if (d > t) {
if (k > t) {
// whileループ終了
isOK = true;
// print('OK');
// Mapオブジェクトに重さを設定
arr[0].set('weight', n);
arr[1].set('weight', k);
arr[2].set('weight', d);
arr[3].set('weight', t);
// 配列要素を重い順に並び替える
arr.sort((a, b) => {
const na = a.get('weight');
const nb = b.get('weight');
return (na < nb ? 1 : -1);
});
return arr; // 並び替えた配列を返す
}
}
}
}
}
}
}
// 結果が出ない場合はfalseを返す
return false;
}
イメージはk, d, n, tの順番で並びます。
ここでは野菜を表すのに、前の「31:2色のカードで数を表す」の「数値をMapオブジェクトに置き換えて描画する」と同じ要領で、Mapオブジェクトを使用しています。
weightはどの野菜も初め0ですが、後からweightCompare()関数によって、ランダムな整数が割り当てられます。野菜を描画するときには、Mapオブジェクトのimage属性を使用します。
野菜は初め配列に、’ニンジン’, ‘カボチャ’, ‘ダイコン’, ‘トマト’ の順で並んでいますが、weightCompare()関数内で、各野菜用のランダムな整数n,k,d,tが作成され、重さを比較する6つの条件全部が満たされた場合に、その野菜を表すMapオブジェクトのweight値に設定されます。そして、配列のsort()メソッドを使って、weight値の大きい順、つまり重い順に並び替えられます。
配列のsort()メソッドは便利に使用できます。ただし引数に比較関数と呼ばれる関数を指定しない場合、要素が文字列に変換され、並び替えが文字列として行われるので、注意が要ります。数値の並び替えでは、次の例のように、比較関数を渡すのが最善の方法です。
// Array.sort()
let testArray = [6, 4, 9, 1];
testArray = testArray.sort();
print(testArray); // [1, 4, 6, 9]
testArray = [6, 4, 19, 1];
testArray = testArray.sort();
print(testArray); // [1, 19, 4, 6] => 19が先にくる
testArray = [6, 4, 19, 1];
testArray = testArray.sort((a, b) => {
let comp = 0;
if (a > b) {
comp = 1;
}
else if (a < b) {
comp = -1;
}
return comp;
});
print(testArray); // [1, 4, 6, 19] => OK
arr.sort()の比較関数内で使用している?は三項演算子と呼ばれるもので、上記if文の短縮版として使用できます。sort()メソッドは並び替えた配列を返しますが、sort()メソッドの実行によって、呼びだした配列の要素の並びが変化するので、戻り値を設定しなくても、呼びだした配列を並び替えの終わった配列として使用できます。
// 配列要素を重い順に並び替える
arr.sort((a, b) => {
const na = a.get('weight');
const nb = b.get('weight');
return (na < nb ? 1 : -1);
});