Home > Enterprise >  Is it possible to generate a union type of numbers without explicitly stating each one?
Is it possible to generate a union type of numbers without explicitly stating each one?

Time:01-14

Say I wanted to generate a union type of numbers that go up by a scale, but the amount of entries in the union will be quite large, could I generate that without explicitly typing out every type in the union?

Say I have type ScaleByEight = 8 | 16 | 24 | 32 ... 400;

Could I create a utility that where I can pass a min, max and value to scale by?

Something like type ScaleByEight = ScaleBy</* min */ 8, /* max */, 400, /* scale by */ 8>;

CodePudding user response:

There is no Range type at the moment.

It's an open issue on GitHub.

CodePudding user response:

It is possible, however I would not rely on this logic too much on PROD code:

type MAXIMUM_ALLOWED_BOUNDARY = 999

type ComputeRange<
    N extends number,
    Result extends Array<unknown> = [],
> =
    (Result['length'] extends N
        ? Result
        : ComputeRange<N, [...Result, Result['length']]>
    )

type Add<A extends number, B extends number> = [...ComputeRange<A>, ...ComputeRange<B>]['length']

type IsGreater<A extends number, B extends number> = IsLiteralNumber<[...ComputeRange<B>][Last<[...ComputeRange<A>]>]> extends true ? false : true

type Last<T extends any[]> = T extends [...infer _, infer Last] ? Last extends number ? Last : never : never

type RemoveLast<T extends any[]> = T extends [...infer Rest, infer _] ? Rest : never

type IsLiteralNumber<N> = N extends number ? number extends N ? false : true : false


type AddIteration<Min extends number, Max extends number, ScaleBy extends number, Result extends Array<unknown> = [Min]> =
    IsGreater<Last<Result>, Max> extends true
    ? RemoveLast<Result>
    : AddIteration<
        Min, Max, ScaleBy, [...Result, Add<Last<Result>, ScaleBy>]
    >

// [5, 13, 21, 29, 37]
type Result = AddIteration<5, 40, 8>

Playground

Please see my answer and article

Explanation for ComputeRange you can find in this answer and my article

Add - just concatenates two arrays and returns new length. Just like :

const add=(xl:number[], yl:number[]) => [...xl,...yl].length

IsLiteralNumbe - checks whether provided number is literal number like 2 or 42 or just a general type of number.

Last returns last element of an array

IsGreater checks whether last element of ComputeRange<A> can be used as an index for ComputeRangeB. Please keep in mind that all elements in ComputedRange are also indexes.

AddIteration - is a recursive utility type, which checks whether last element of Result is greater then Max. If yes - return Result without last element otherwise, call itself recursively with addition.

Here you can test js representation. I made it as close as I could, but not everything can be expressed.

const add = (a: number, b: number) => [...computeRange(a), ...computeRange(b)]['length']

const isLiteralNumber = (value: any) => typeof value === 'number'

const last = (xl: number[]) => {
    //const [...rest, last] = xl
    return xl[xl.length - 1]
}

const computeRange = (n: number) => Array(n).fill(0).map((_, index) => index)

const isGreater = (a: number, b: number) => isLiteralNumber([...computeRange(b)][last(computeRange(a))]) ? false : true

const addIteration = (min: number, max: number, scaleBy: number, result = [min]): number[] => {
    if (isGreater(last(result), max)) {
        return result
    }

    return addIteration(min, max, scaleBy, [...result, add(last(result), scaleBy)])
}
const result = addIteration(5, 40, 8)

// [ 5, 13, 21, 29, 37, 45 ]
console.log({ result })
  • Related