Instantly share code, notes, and snippets.

@Jack-Works Jack-Works/a.lib.es20??.ts
Last active Nov 28, 2018

Embed
What would you like to do?
A polyfill for a range proposal
interface NumberConstructor {
range(from: number, to: number, step?: number): Iterator<number>
range(from: BigInt, to: BigInt, step?: BigInt): Iterator<BigInt>
}
Number.range = function*(from, to, step) {
if (
typeof from !== 'number' &&
typeof from !== 'bigint' &&
(typeof to !== 'number' && typeof to !== 'bigint') &&
(typeof step !== 'number' && typeof step !== 'bigint' && typeof step !== 'undefined')
)
throw new TypeError('All parameters must be a number or a BigInt')
if (typeof from === 'bigint' && typeof step === 'undefined') step = 1n
else if (typeof from === 'number' && typeof step === 'undefined') step = 1
if (typeof from !== typeof to || typeof from !== typeof step) throw new TypeError('Type of all parameters must be the same')
if (typeof from === "number" && Number.isNaN(from) || Number.isNaN(to) || Number.isNaN(step)) return;
// Quit early with no value yield
// Math.abs does not support BigInt.
const abs = x => (x >= (typeof x === 'bigint' ? 0n : 0) ? x : -x)
const increase = to > from
// Ignore the symbol
if (increase) step = abs(step)
else step = -abs(step)
// The original implement
//
// while (increase ? !(from >= to) : !(to >= from)) {
// yield from
// from = from + step
// }
//
// Maybe these two have different behavior on IEEE 754 floating point number
let count = typeof from === 'bigint' ? 1n : 1
let now = from
while (increase ? !(now >= to) : !(to >= now)) {
yield now
now = from + step * count
count++
}
}
// Usage:
for(let i of Number.range(1, 5)) console.log(i)
for(let i of Number.range(1, 5.4, 0.2)) console.log(i)
for(let i of Number.range(5n, -10n, 3n)) console.log(i)
for(let i of Number.range(0, Infinity)) {
if (i > 50) break;
}
var score = [...Number.range(0, 101)] // [0, 1, ... 100]
for(let i of Number.range(0, score.length))
score[i] = score[i] * 2
// Test case:
Number.range(0, 1n) // TypeError
Number.range() // TypeError
Number.range("a", "z") // TypeError
Number.range(0, 20) // 0 to 19
Number.range(20, -1) // 19 to 0
Number.range(2.1, 2.8, 0.1) // 2.1 to 2.8
Number.range(2.8, 2.1, 0.1) // 2.8 to 2.1
Number.range(0, Infinity) // 0 to Infinity
Number.range(Infinity, 0) // Don't do this. Infinity - 1 is still infinity.
// Should we throw? Or only yield 1 Infinity? Or yield nothing?
Number.range(0n, 5n) // 0 to 5, BigInt support
Number.range(5n, -2n, 2n) // 5 to -2, step 2
Number.range(0 / 0, 2) // No value yield
// This extends the usage of range. Like Kotlin or some other language,
// we can use it to check if a number is in this range.
// 100 in Number.range(99, 101) // true, in the original version is false. JavaScript in will check key in default.
Number.range = function(from, to, step) {
const _ = OriginalImplement(from, to, step)
// To fix TypeError: Method [Generator].prototype.next called on incompatible receiver [object Object]
_.next = _.next.bind(_)
return new Proxy(_, {
has(oTarget, sKey) {
// Exclude Symbol here. But Typescript also think sKey maybe a number
if (typeof sKey !== 'string') return false
const num = parseFloat(sKey)
if (Number.isNaN(num)) return false
if (step !== undefined) {
// Possible behavior:
// throw new TypeError('Cannot check if a number in range while the range has a step.')
// Possible behavior 2:
// But this need a full unfold, may cause infinite loop while Number.range(0, Infinity) don't
return [...OriginalImplement(from, to, step)].includes(num)
// Possible behavior 3:
// re-implement range and make a compare
// Possible behavior 4:
// This may cause confuse.
// return false
}
if (to > from) return to > num && num >= from
else return from > num && num >= to
}
})
function* OriginalImplement(from, to, step) {
if (
typeof from !== 'number' &&
typeof from !== 'bigint' &&
(typeof to !== 'number' && typeof to !== 'bigint') &&
(typeof step !== 'number' && typeof step !== 'bigint' && typeof step !== 'undefined')
)
throw new TypeError('All parameters must be a number or a BigInt')
if (typeof from === 'bigint' && typeof step === 'undefined') step = 1n
else if (typeof from === 'number' && typeof step === 'undefined') step = 1
if (typeof from !== typeof to || typeof from !== typeof step)
throw new TypeError('Type of all parameters must be the same')
if ((typeof from === 'number' && Number.isNaN(from)) || Number.isNaN(to) || Number.isNaN(step)) return
// Quit early with no value yield
// Math.abs does not support BigInt.
const abs = x => (x >= (typeof x === 'bigint' ? 0n : 0) ? x : -x)
const increase = to > from
// Ignore the symbol
if (increase) step = abs(step)
else step = -abs(step)
// The original implement
//
// while (increase ? !(from >= to) : !(to >= from)) {
// yield from
// from = from + step
// }
//
// Maybe these two have different behavior on IEEE 754 floating point number
let count = typeof from === 'bigint' ? 1n : 1
let now = from
while (increase ? !(now >= to) : !(to >= now)) {
yield now
now = from + step * count
count++
}
}
}
// Share test case with the original version
0 in Number.range(0, 10) // true
0 in Number.range(2, 5) // false
// Due to limit of JavaScript `in` and `Proxy`
"42 is the answer" in Number.range(30, 50) // true
// Also, limited for BigInt
42n in Number.range(12, 50) // true, we can't check type of `key`
5 in Number.range(0n, 10n)
// Undefined behavior: Step is provided
3 in Number.range(0, 10, 2)
4 in Number.range(0, 10, 2)
// There are 4 possible solution writed in the demo.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment