LoginSignup
1
2

More than 5 years have passed since last update.

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

Last updated at Posted at 2018-12-13

追記

  • ジェネレーター版を作った。
  • 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)
=> []

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

1
2
1

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
1
2