JavaScript: range関数。逆順も小数もいけるやつ


追記

  • ジェネレーター版を作った。
  • getDigit: 元記事のやりかただと望み通りに丸めてくれない小数があるので、回避策として文字列にして有効桁を推定することにした。
const toFixedToN = digit => n => Number.parseFloat( n.toFixed( digit ) ) 
const getDigit = n =>{ 
  const a = n.toString() 
  return (!a.includes('.'))? 0
    : a.length - a.indexOf('.') - 1  
}
const maxDigit = (...params) => Math.max( ...params.map( getDigit ) )
const fixedToMaxDigit = (...params) => toFixedToN(maxDigit(...params))

const rangeG = begin => end => step => function*(){
  if( step === 0 ) return 
  if( Math.sign( end - begin ) !== Math.sign( step ) ) return 
  for(let i=begin; (end-i) * Math.sign(step) > 0; i+=step)yield fixedToMaxDigit(begin, step)(i)
}()

const range = begin => end => step =>
  ( step === 0 )? []
  : ( Math.sign( end - begin ) !== Math.sign( step ) )? []
  : [...Array( Math.ceil( ( end - begin ) / step ) ).keys()]
      .map( e => fixedToMaxDigit(begin, step)( begin + e * step ) )

//使用例:
> [...rangeG(0.111)(-10)(-1.7777)]
=> [ 0.111, -1.6667, -3.4444, -5.2221, -6.9998, -8.7775 ]
> range(0.111)(-10)(-1.7777)
=> [ 0.111, -1.6667, -3.4444, -5.2221, -6.9998, -8.7775 ]

以下、元記事。

ES6で指定範囲の整数配列を作るにコメントした内容が自分でもいまいちだなあと思ったんで、後でよく考えてみた、という内容です。

時々あったらいいなと思うrange関数を考えてみました。逆順も小数もいけるやつです。

  • begin、end、step必須。
  • beginは含み、endは含まれない。
  • stepが良くない数(0または逆向き)だと空の配列を返します。
const range = begin => end => step  =>
  (step === 0) ? []
  : ( Math.sign( end - begin ) !== Math.sign( step ) ) ? []
  : [ ...Array( Math.ceil( ( end - begin ) / step ) ).keys() ]
      .map( e => begin + e * step )

//使用例:
//整数
> range(0)(10)(1)
=> [ 0, 1, 2, 3, 4, 5, 6, 7, 8, 9 ]
> range(0)(10)(3)
=> [ 0, 3, 6, 9 ]
//小数 & 逆順
> range(0.1)(-3)(-0.7)
=> [ 0.1,
  -0.6,
  -1.2999999999999998,
  -1.9999999999999996,
  -2.6999999999999997 ]
//stepが0だと空の配列を返す
> range(0)(10)(0)
=> []
//stepが逆向きだと空の配列を返す
> range(0)(10)(-1)
=> []

だいたいこれでいいんだけど、小数を使うと仕様上端数というか誤差というかがでてくるので、もうちょっと工夫してみます。

const range = begin => end => step =>{
  if( step === 0 ) return []
  if( Math.sign( end - begin ) !== Math.sign( step ) ) return []

  const getDigit = digit => n =>
    Number.isInteger( n ) ? digit 
    :getDigit( digit + 1 )( n * 10 )
  const maxDigit = Math.max( ...[ begin, end, step ].map( getDigit( 0 ) ) )
  const toFixedToN = digit => n => Number.parseFloat( n.toFixed( digit ) )

  return [...Array( Math.ceil( ( end - begin ) / step ) ).keys()]
      .map( e => toFixedToN( maxDigit )( begin + e * step ) )
}
//使用例:
//整数
> range(0)(10)(1)
=> [ 0, 1, 2, 3, 4, 5, 6, 7, 8, 9 ]
> range(0)(10)(3)
=> [ 0, 3, 6, 9 ]
//小数 & 逆順
> range(0.1)(-3)(-0.7)
=> [ 0.1, -0.6, -1.3, -2, -2.7 ]
//stepが0だと空の配列を返す
> range(0)(10)(0)
=> []
//stepが逆向きだと空の配列を返す
> range(0)(10)(-1)
=> []

返り値を引数の小数桁の一番大きいものに合わせる、という作戦です。