かもメモ

自分の落ちた落とし穴に何度も落ちる人のメモ帳

JS 0や文字列を含んだ数字のチェックをしたいメモ

数値を扱う時や数値変換を行う時に0が鬼門になることが多いのでメモ

var checklist = [
  0,
  "0",
  "",
  undefined,
  "undefined",
  false,
  "false",
  true,
  "true",
  null,
  "null",
  NaN,
  "NaN",
  Infinity,
  "Infinity",
  -Infinity,
  "-Infinity"
];

-0で数値変換した場合

checklist.forEach(function(v) {
  console.log(v + " (" + typeof(v) + ") =>", v-0);
});

出力👇

0 (number) => 0
"0" (string) => 0
"" (string) => 0
undefined (undefined) => NaN
"undefined" (string)  => NaN
false (boolean)  => 0
"false" (string) => NaN
true (boolean)  => 1
"true" (string) => NaN
null (object)   => 0
"null" (string) => NaN
NaN (number)   => NaN
"NaN" (string) => NaN
Infinity (number)   => Infinity
"Infinity" (string) => Infinity
-Infinity (number)   => -Infinity
"-Infinity" (string) => -Infinity

isNaN() と Number.isNaN()

isNaN()Number.isNaN() とでは判定結果が異なる。
isNaN() は暗黙の型変換でNaNになるものも含まれるっぽい。

isNaN()

checklist.forEach(function(v) {
  if( isNaN(v) ) {
    console.log('isNaN >', v, "("+typeof(v)+")");
  }
});

出力👇

isNaN > undefined (undefined)
isNaN > "undefined" (string)
isNaN > "false" (string)
isNaN > "true" (string)
isNaN > "null" (string)
isNaN > NaN (number)
isNaN > "NaN" (string)

Number.isNaN() (ES6)

checklist.forEach(function(v) {
  if( Number.isNaN(v) ) {
    console.log('Number.isNaN >', v, "("+typeof(v)+")");
  }
});

出力👇

Number.isNaN >  NaN (number)

isFinite() と Number.isFinite()

isNaNと同様で判定結果が異なる。
isNaNと同じくglobalにある isFinite() は暗黙の型変換で判定してるっぽい。

isFinite()

checklist.forEach(function(v) {
  if( isFinite(v) ) {
    console.log('isFinite >', v, "("+typeof(v)+")");
  }
});

出力👇

isFinite > 0 (number)
isFinite > "0" (string)
isFinite > "" (string)
isFinite > false (boolean)
isFinite > true (boolean)
isFinite > null (object)

""(空文字列) true, false, null はすり抜けてしまう。

Number.isFinite() (ES6)

checklist.forEach(function(v) {
  if( Number.isFinite(v) ) {
    console.log('Number.isFinite >', v, "("+typeof(v)+")");
  }
});

出力👇

Number.isFinite > 0 (number)

"0", "123"のような文字列化しているものは弾かれてしまう。

Number.isInteger() (ES6)

型チェック込で整数かどうかを判別するので、少数や文字列になっているものも弾かれる。

Number.isFinite("0");   // false
Number.isFinite(12.8); // false

数値と文字列化している数字のみチェックしたい

var checklist = [
  0,
  "0",
  "",
  undefined,
  "undefined",
  false,
  "false",
  true,
  "true",
  null,
  "null",
  NaN,
  "NaN",
  Infinity,
  "Infinity",
  -Infinity,
  "-Infinity",
  "123",
  -5,
  12.8,
  "A"
];

var a = checklist.filter(function(val, i) {
  // 文字列になった数字も取り出したいので、isFinite() を使う
  // true, false がすり抜けてしまうので、`boolean` 型も弾く
  if( !isFinite(val) || typeof(val) === "boolean" ) {
    return false;
  }
  if( val-0 === 0 ) {
    // 暗黙の型変換で 0 になるものは 数字で構成されているかチェックする
    if(/[0-9]/g.test(val)) return true;
  } else {
    return true;
  }
});

console.log(a); // [ 0, '0', '123', -5, 12.8 ]

暗黙の型変換で0になるものだけ正規表現でチェックをしているので、そこまで実行速度に影響は無いとは思います。

条件式版も作ってみましたが、条件がこれでOKかちょっと不安があります…

var a = checklist.filter(function(val, i) {
  // 文字列になった数字も取り出したいので、isFinite() を使う
  // true, false がすり抜けてしまうので、`boolean` 型も弾く
  if( !isFinite(val) || typeof(val) === "boolean" ) {
    return false;
  }
  // 条件は本当にコレで大丈夫???
  if( typeof(val) === 'number'
   || (typeof(val) === 'string' && val !== "")
   || val - 0 !== 0
  ) {
    return true;
  }
});

console.log(a); // [ 0, '0', '123', -5, 12.8 ]

感想

なんとか数字と文字列化した数字を判定することができました。
ゆるふわ型javascriptの暗黙の型変換ほんと鬼門です。
そう考えるとRubyfalsenilのときだけfalseになる。って凄く便利なんですね!


[参考]

JS 配列の要素が全て条件を満たすか調べたい

EX: 配列に含まれる数が全て0 <= N <= 255 かどうか調べたい
次のデータから条件にマッチする配列だけを取り出す。

// 調べる配列の入ったデータ
var data = [
  [ 217, 0, 0, 0 ],
  [ 169, 254, 0, 1 ],
  [ 0, 0, 0, 256 ],
  [ 1, 0, -1, 255 ],
  [ '', 0, 0, 0 ] 
];

forEachだとループの途中でbreakできないっぽい。
なので条件に合わないものがあっても最後までチェックするので無駄があり。

for break

条件に合わない値が出てきたらbreakでループを抜ける方法

var a = [];
d.forEach(function(arr, i) {
  let isOK = true;
  for(let j=0,l=arr.length; j<l; j+=1) {
    let n = arr[j];
    if( typeof(n) !== 'number' || (n < 0 || n > 255) ) {
      isOK = false;
      break;
    }
  }
  if(isOK) {
    a[a.length] = arr;
  }
});
console.log(a); // =>  [ [ 217, 0, 0, 0 ], [ 169, 254, 0, 1 ] ]

Array.prototype.every (ES5)

allPassed = array.every(callback[, thisObject]);
与えられた関数によって実行されるテストに配列のすべての要素が合格するかどうかをテストします。
出典: [https://developer.mozilla.org/ja/docs/Web/JavaScript/Reference/Global_Objects/Array/every:title]

callbackが1つでもfalseを返した時点でループから抜ける

d.forEach(function(arr, i) {
  let callback = function(i) {
    return (typeof(i) === 'number' && (i >= 0 && i <= 255) );
    // 条件に合わないの否定でもOK
    // return !(typeof(i) !== 'number' || (i < 0 || i > 255) );
  };
  if( arr.every(callback) ) {
    a[a.length] = arr;
  }
});
console.log(a); // => [ [ 217, 0, 0, 0 ], [ 169, 254, 0, 1 ] ]

Array.prototype.some (ES5)

array.some(callback[, thisObject]);
与えられた関数によって実行されるテストに合格する要素が配列の中にあるかどうかをテストします。
出典: [https://developer.mozilla.org/ja/docs/Web/JavaScript/Reference/Global_Objects/Array/some:title]

callbackが1つでもtrueを返した時点でループから抜ける

d.forEach(function(arr, i) {
  let callback = function(i) {
    // NGな条件式を返せばOK
    return (typeof(i) !== 'number' || (i < 0 || i > 255) );
  };
  // NGに全くマッチしなければ(falseなら)、全ての値が条件を満たしている
  if( !arr.some(callback) ) {
    a[a.length] = arr;
  }
});
console.log(a); // => [ [ 217, 0, 0, 0 ], [ 169, 254, 0, 1 ] ]

👆日本語で説明を書くと余計混乱しそう...

感想とまとめ

関数 ループから抜ける条件
for breakキーワードが出てきた段階
every callback関数がfalseを返した段階
some callback関数がtrueを返した段階

every, someを使えばシンプルに書けて良さそう。(条件式返すのがちょっと気持ち悪い気もするけど多分慣れの問題。
タイトルの意味合い的にはeveryだけど、条件式の書きやすさではsomeを使ったほうが良い場合もありそう。
少しづづES5から覚えていきたい...


[参考]

オブジェクト指向JavaScriptの原則

オブジェクト指向JavaScriptの原則

Javascript 自然数の約数の総和を求めたい

プログラム書く仕事が無いので、お脳が腐らないように息抜きにプログラムのパズルをしています。
自然数の約数の総和を求める方法を考えてたのでメモ。

数学的なやつ

自然数NP1a1P2a2素因数分解できる時
Nの約数の総和は

(1+P1+P1**2+...+P1**a1)(1+P2+P2**2+...+P2**a2)...

↓ 例

12 = 2**2 × 3
(1+2+2**2)(1+3) = 7*4 = 28

数学的な公式だと、素因数分解をするとエレガントに約数の総和が求められるのですが、素因数分解を行うプログラムが大変そうだったので別の方法を考えることにしました。

約数をループで探していく

  • N%C === 0 の時 Cは整数Nの約数
  • 1の次は2なので、N自身を除いた最大の約数は N/2 以下の数になる

と、上記の条件から作った関数がこちら

// n: int, n > 0
function divisorSum(n) {
  let c, r = n;
  if(n <= 0) return;
  c = Math.floor(n/2)+1;
  while(c--) {
    if(n%c === 0) r += c;
  }
  return r;
}

divisorSum(12) // => 28

 
N/2以下でループさせれば計算量が多少は少なくて済む気がします。(間違ってたらご指摘ください。数学苦手なので自信はありません。)
他にエレガントな方法や素因数分解するプログラムのヒントがあれば知りたいので教えてください!!
(素因数分解は脳内でどうやって素因数分解しているかを順序立ててトレースすれば良さそうな気もするけど、ループで約数探すより処理が多そうな気が…


[参考]

プログラマのための論理パズル 難題を突破する論理思考トレーニング

プログラマのための論理パズル 難題を突破する論理思考トレーニング