Skip to content

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
You can’t perform that action at this time.