type-challenages
00002-medium-return-type
Get Return Type
type MyReturnType<T> = T extends (...args: any) => infer V ? V : T
00003-medium-omit
Implement the built-in Omit<T, K>
generic without using it.
Constructs a type by picking all properties from T
and then removing K
type MyExclude1<T, K> = T extends K ? never : T
type MyOmit<T, K> = {
[x in MyExclude1<keyof T, K>]: T[x]
}
00004-easy-pick
Implement the built-in Pick<T, K>
generic without using it.
Constructs a type by picking the set of properties K
from T
type MyInclude1<T, K> = T extends K ? T : never
type MyPick<T, K extends keyof T> = {
[x in MyInclude1<keyof T, K>]: T[x]
}
00005-extreme-readonly-keys
Implement a generic GetReadonlyKeys<T>
that returns a union of the readonly keys of an Object.
type SelfEqual<T, R> = (<J>(J: J) => J extends T ? 1 : 2) extends (<J>(J: J) => J extends R ? 1 : 2) ? true : false
type GetReadonlyKeys<T extends Record<string, any>> = keyof {
[K in keyof T as (SelfEqual<{
[P in K]: T[P]
}, {
readonly [P in K]: T[P]
}> extends true ? K : never)]: T[K]
}
00006-hard-simple-vue
In this challenge, we assume that SimpleVue take an Object with data
, computed
and methods
fields as it's only argument,
data
is a simple function that returns an object that exposes the contextthis
, but you won't be accessible to other computed values or methods.computed
is an Object of functions that take the context asthis
, doing some calculation and returns the result. The computed results should be exposed to the context as the plain return values instead of functions.methods
is an Object of functions that take the context asthis
as well. Methods can access the fields exposed bydata
,computed
as well as othermethods
. The different betweencomputed
is thatmethods
exposed as functions as-is.
The type of SimpleVue
's return value can be arbitrary.
type GetComputedProperty<C> = C extends Record<string, (args: unknown[]) => unknown> ? {
[K in keyof C]: ReturnType<C[K]>
} : never
declare function SimpleVue<D, C extends Record<string, unknown>, M>(options: {
data: (this: {}) => D
computed: C & ThisType<D & GetComputedProperty<C> & M>
methods: M & ThisType<D & GetComputedProperty<C> & M>
}): any
00007-easy-readonly
type MyReadonly<T> = {
readonly [K in keyof T]: T[K]
}
00008-medium-readonly-2
Implement a generic MyReadonly2<T, K>
which takes two type argument T
and K
.
K
specify the set of properties of T
that should set to Readonly. When K
is not provided, it should make all properties readonly just like the normal Readonly<T>
.
type MyReadonly2<T, K extends keyof T = keyof T> = Omit<T, K> & { readonly [Key in K]: T[Key] }
00009-medium-deep-readonly
Implement a generic DeepReadonly<T>
which make every parameter of an object - and its sub-objects recursively - readonly.
You can assume that we are only dealing with Objects in this challenge. Arrays, Functions, Classes and so on do not need to be taken into consideration. However, you can still challenge yourself by covering as many different cases as possible.
type DeepReadonly<T> = {
readonly [k in keyof T]: T[k] extends never | (() => any) ? T[k] : DeepReadonly<T[k]>
}
00010-medium-tuple-to-union
type Arr = ['1', '2', '3']
type Test = TupleToUnion<Arr> // expected to be '1' | '2' | '3'
type TupleToUnion<T> = T extends Array<infer V> ? V : never
00011-easy-tuple-to-object
For example:
const tuple = ['tesla', 'model 3', 'model X', 'model Y'] as const
type result = TupleToObject<typeof tuple> // expected { 'tesla': 'tesla', 'model 3': 'model 3', 'model X': 'model X', 'model Y': 'model Y'}
type TupleToObject<T extends readonly any[]> = {
[k in T[number]]: k
}
00012-medium-chainable-options
For example
declare const config: Chainable
const result = config
.option('foo', 123)
.option('name', 'type-challenges')
.option('bar', { value: 'Hello World' })
.get()
// expect the type of result to be:
interface Result {
foo: number
name: string
bar: {
value: string
}
}
type Chainable<T extends {}> = {
option<K extends string, V>(key: K, value: V): Chainable<Omit<T, K> & { [k in K]: V }>
get(): T
}
00013-warm-hello-world
type HelloWorld = string // expected to be a string
00014-easy-first
For example:
type arr1 = ['a', 'b', 'c']
type arr2 = [3, 2, 1]
type head1 = First<arr1> // expected to be 'a'
type head2 = First<arr2> // expected to be 3
type First<T extends any[]> = T extends [infer F, ...any[]] ? F : never
00015-medium-last
For example
type arr1 = ['a', 'b', 'c']
type arr2 = [3, 2, 1]
type tail1 = Last<arr1> // expected to be 'c'
type tail2 = Last<arr2> // expected to be 1
type Last<T extends any[]> = T extends [infer F, ...infer Rest] ? Rest extends [] ? F : Last<Rest> : never
00016-medium-pop
Implement a generic Pop<T>
that takes an Array T
and returns an Array without it's last element.
For example
type arr1 = ['a', 'b', 'c', 'd']
type arr2 = [3, 2, 1]
type re1 = Pop<arr1> // expected to be ['a', 'b', 'c']
type re2 = Pop<arr2> // expected to be [3, 2]
type Pop<T extends any[]> = T extends [...infer Rest, infer Last] ? Rest : []
00017-hard-currying-1
const add = (a: number, b: number) => a + b
const three = add(1, 2)
const curriedAdd = Currying(add)
const five = curriedAdd(2)(3)
The function passed to Currying
may have multiple arguments, you need to correctly type it.
type Curry<P, R> = P extends [infer H, ...infer T] ? (p: H) => Curry<T, R> : R
declare function Currying<T extends Function>(fn: T): T extends (...args: infer Arg) => infer Return
? Arg extends never[]
? () => Return
: Curry<Arg, Return>
: never
00018-easy-tuple-length
For given a tuple, you need create a generic Length
, pick the length of the tuple
For example:
type tesla = ['tesla', 'model 3', 'model X', 'model Y']
type spaceX = ['FALCON 9', 'FALCON HEAVY', 'DRAGON', 'STARSHIP', 'HUMAN SPACEFLIGHT']
type teslaLength = Length<tesla> // expected 4
type spaceXLength = Length<spaceX> // expected 5
type Length<T> = T extends readonly unknown[] ? T['length'] : never
00020-medium-promise-all
Type the function PromiseAll
that accepts an array of PromiseLike objects, the returning value should be Promise<T>
where T
is the resolved result array.
const promise1 = Promise.resolve(3);
const promise2 = 42;
const promise3 = new Promise<string>((resolve, reject) => {
setTimeout(resolve, 100, 'foo');
});
// expected to be `Promise<[number, 42, string]>`
const p = PromiseAll([promise1, promise2, promise3] as const)
// 手写Awaited<T>
type MyAwaited1<T> =
T extends null | undefined
? T
: T extends object & { then: (fulfilled: infer U) => any }
? U extends (value: infer F, ...args: any) => any
? F
: never
: T
declare function PromiseAll<T extends readonly unknown[] | []>(values: T): Promise<{
-readonly [K in keyof T]: MyAwaited1<T[K]>
}>
00043-easy-exclude
Implement the built-in Exclude<T, U>
Exclude from
T
those types that are assignable toU
For example:
type Result = MyExclude<'a' | 'b' | 'c', 'a'> // 'b' | 'c'
type MyExclude<T, U> = T extends U ? never : T
00055-hard-union-to-intersection
Implement the advanced util type UnionToIntersection<U>
For example
type I = UnionToIntersection<'foo' | 42 | true> // expected to be 'foo' & 42 & true
// 触发分派 > 函数参数交叉
type UnionToIntersection<U> = (U extends U ? (args: U) => never : never) extends (args: infer V) => never ? V : never
00057-hard-get-required
Implement the advanced util type GetRequired<T>
, which remains all the required fields
For example
type I = GetRequired<{ foo: number, bar?: string }> // expected to be { foo: number }
type GetRequired<T> = {
[K in keyof T as T[K] extends { [K in keyof T]-?: T[K] }[K] ? K : never ]: T[K]
}
00057-hard-get-required
Implement the advanced util type GetOptional<T>
, which remains all the optional fields
For example
type I = GetOptional<{ foo: number, bar?: string }> // expected to be { bar?: string }
type GetOptional<T> = {
[K in keyof T as T[K] extends { [K in keyof T]-?: T[K] }[K] ? never : K ]: T[K]
}
00062-medium-type-lookup
Sometimes, you may want to look up a type in a union by its attributes.
In this challenge, we would like to get the corresponding type by searching for the common type
field in the union Cat | Dog
. In other words, we will expect to get Dog
for LookUp<Dog | Cat, 'dog'>
and Cat
for LookUp<Dog | Cat, 'cat'>
in the following example.
interface Cat {
type: 'cat'
breeds: 'Abyssinian' | 'Shorthair' | 'Curl' | 'Bengal'
}
interface Dog {
type: 'dog'
breeds: 'Hound' | 'Brittany' | 'Bulldog' | 'Boxer'
color: 'brown' | 'white' | 'black'
}
type MyDogType = LookUp<Cat | Dog, 'dog'> // expected to be `Dog`
type LookUp<U, T> = U extends { type: T } ? U : never
00089-hard-required-keys
Implement the advanced util type RequiredKeys<T>
, which picks all the required keys into a union.
For example
type Result = RequiredKeys<{ foo: number; bar?: string }>;
// expected to be “foo”
type RequiredKeys<T> = keyof {
[K in keyof T as T[K] extends Required<T>[K] ? K : never]: T[K]
}
00090-hard-optional-keys
Implement the advanced util type OptionalKeys<T>
, which picks all the optional keys into a union.
type OptionalKeys<T> = keyof {
[K in keyof T as T[K] extends Required<T>[K] ? never : K]: never
}
00106-medium-trimleft
Implement `TrimLeft<T>` which takes an exact string type and returns a new string with the whitespace beginning removed.
For example
```ts
type trimed = TrimLeft<' Hello World '> // expected to be 'Hello World '
```
type WhiteSpace = ' ' | '\t' | '\n'
type TrimLeft<S extends string> = S extends `${WhiteSpace}${infer Rest}` ? TrimLeft<Rest> : S
00108-medium-trim
Implement Trim<T>
which takes an exact string type and returns a new string with the whitespace from both ends removed.
For example
type trimmed = Trim<' Hello World '> // expected to be 'Hello World'
type WhiteSpace = ' ' | '\t' | '\n'
type Trim<S extends string> = S extends `${WhiteSpace}${infer T}` | `${infer T}${WhiteSpace}` ? Trim<T> : S
00110-medium-capitalize
Implement Capitalize<T>
which converts the first letter of a string to uppercase and leave the rest as-is.
For example
type capitalized = Capitalize<'hello world'> // expected to be 'Hello world'
type MyCapitalize<S extends string> = S extends `${infer F}${infer R}`
? `${Uppercase<F>}${R}`
: S
00112-hard-capitalizewords
Implement CapitalizeWords<T>
which converts the first letter of *each word of a string* to uppercase and leaves the rest as-is.
For example
type capitalized = CapitalizeWords<'hello world, my friends'> // expected to be 'Hello World, My Friends'
type CapitalizeWords<
S extends string,
W extends string = '',
> = S extends `${infer A}${infer B}`
? Uppercase<A> extends Lowercase<A>
? `${Capitalize<`${W}${A}`>}${CapitalizeWords<B>}`
: CapitalizeWords<B, `${W}${A}`>
: Capitalize<W>
00114-hard-camelcase
Implement CamelCase<T>
which converts snake_case
string to camelCase
.
For example
type camelCase1 = CamelCase<'hello_world_with_types'> // expected to be 'helloWorldWithTypes'
type camelCase2 = CamelCase<'HELLO_WORLD_WITH_TYPES'> // expected to be same as previous one
type CamelCase<S extends string> = S extends Lowercase<S> ?
S extends `${infer F}_${infer RF}${infer R}`
? RF extends Uppercase<RF>
? `${F}_${CamelCase<`${RF}${R}`>}`
: `${F}${Uppercase<RF>}${CamelCase<R>}`
: S
: CamelCase<Lowercase<S>>
00116-medium-replace
Implement Replace<S, From, To>
which replace the string From
with To
once in the given string S
For example
type replaced = Replace<'types are fun!', 'fun', 'awesome'> // expected to be 'types are awesome!'
type Replace<S extends string, From extends string, To extends string> = From extends '' ? S : S extends `${infer L}${From}${infer A}` ? `${L}${To}${A}` : S
00119-medium-replaceall
Implement ReplaceAll<S, From, To>
which replace the all the substring From
with To
in the given string S
For example
type replaced = ReplaceAll<'t y p e s', ' ', ''> // expected to be 'types'
type ReplaceAll<S extends string, From extends string, To extends string> =
From extends '' ? S
: S extends `${infer A}${From}${infer B}` ? `${A}${To}${ReplaceAll<B, From, To>}` : S
00147-hard-c-printf-parser
There is a function in C language: printf
. This function allows us to print something with formatting. Like this:
printf("The result is %d.", 42);
This challenge requires you to parse the input string and extract the format placeholders like %d
and %f
. For example, if the input string is "The result is %d."
, the parsed result is a tuple ['dec']
.
Here is the mapping:
type ControlsMap = {
c: 'char',
s: 'string',
d: 'dec',
o: 'oct',
h: 'hex',
f: 'float',
p: 'pointer',
}
type ControlsMap = {
c: 'char'
s: 'string'
d: 'dec'
o: 'oct'
h: 'hex'
f: 'float'
p: 'pointer'
}
type ParsePrintFormat<PrintFS, Res extends string[] = []> =
PrintFS extends `%${infer F extends keyof ControlsMap}${infer R}`
? ParsePrintFormat<R, [...Res, ControlsMap[F]]>
: PrintFS extends `%%${infer R}`
? ParsePrintFormat<R, Res>
: PrintFS extends `${string}${infer R}`
? ParsePrintFormat<R, Res>
: Res
00151-extreme-query-string-parser
You're required to implement a type-level parser to parse URL query string into a object literal type.
Some detailed requirements:
- Value of a key in query string can be ignored but still be parsed to
true
. For example,'key'
is without value, so the parser result is{ key: true }
. - Duplicated keys must be merged into one. If there are different values with the same key, values must be merged into a tuple type.
- When a key has only one value, that value can't be wrapped into a tuple type.
- If values with the same key appear more than once, it must be treated as once. For example,
key=value&key=value
must be treated askey=value
only.
// 你的答案
type Include<T extends unknown[], Search> = T extends [infer Item, ...infer Rest]
? Search extends Item
? true
: Include<Rest, Search>
: false
type AndStrParser<
Str extends string,
StrArr extends unknown[] = [],
> = Str extends `${infer Item}&${infer Rest}`
? AndStrParser<Rest, [...StrArr, Item]>
: [...StrArr, Str]
type NormalizeArr<T extends unknown[], Result extends unknown[] = []> = T extends [
infer Item,
...infer Rest,
]
? Item extends `${infer _}=${infer _}` | ''
? NormalizeArr<Rest, [...Result, Item]>
: NormalizeArr<Rest, [...Result, `${Item & string}=true`]>
: Result
type DetermineTrueStr<T> = T extends 'true' ? true : T
type ArrParser<Arr extends unknown[] = [], Result extends Record<string, any> = {}> = Arr extends [
infer Item,
...infer Rest,
]
? Item extends `${infer Key}=${infer Val}`
? ArrParser<
Rest,
{
[K in keyof Result | Key]: K extends Key
? Result[Key] extends string | true
? Result[Key] extends DetermineTrueStr<Val>
? DetermineTrueStr<Val>
: [Result[Key], DetermineTrueStr<Val>]
: Result[Key] extends unknown[]
? Include<Result[Key], DetermineTrueStr<Val>> extends true
? Result[Key]
: [...Result[Key], DetermineTrueStr<Val>]
: DetermineTrueStr<Val>
: Result[K]
}
>
: Result
: Result
type ParseQueryString<Str extends string> = ArrParser<NormalizeArr<AndStrParser<Str>>>
00189-easy-awaited
If we have a type which is wrapped type like Promise. How we can get a type which is inside the wrapped type?
For example: if we have Promise<ExampleType>
how to get ExampleType?
type ExampleType = Promise<string>
type Result = MyAwaited<ExampleType> // string
type MyAwaited<T> = T extends PromiseLike<infer P> ? MyAwaited<P> : T
00191-medium-append-argument
type Fn = (a: number, b: string) => number
type Result = AppendArgument<Fn, boolean>
// expected be (a: number, b: string, x: boolean) => number
type AppendArgument<Fn, A> = Fn extends (...args: infer U) => infer R
? (...args: [...U, A]) => R
: Fn
00213-hard-vue-basic-props
For example
props: {
foo: Boolean
}
// or
props: {
foo: { type: Boolean }
}
should be inferred to type Props = { foo: boolean }
.
When passing multiple constructors, the type should be inferred to a union.
props: {
foo: { type: [Boolean, Number, String] }
}
// -->
type Props = { foo: boolean | number | string }
When an empty object is passed, the key should be inferred to any
.
For more specified cases, check out the Test Cases section.
required
,default
, and array props in Vue are not considered in this challenge.
type ResultType<T> = T extends (...args: any[]) => infer R
? R
: T extends new (...args: unknown[]) => infer S
? S
: any
type PropsTypes<T> = {
[P in keyof T]: T[P] extends { type: infer Result }
? Result extends readonly unknown[]
? ResultType<Result[number]>
: ResultType<Result>
: ResultType<T[P]>
}
declare function VueBasicProps<
Props,
Data,
Computed extends Record<PropertyKey, (...args: unknown[]) => unknown>,
Methods,
>(options: {
props: Props
data(this: PropsTypes<Props>): Data
computed: Computed & ThisType<Data>
methods: Methods &
ThisType<
PropsTypes<Props> &
Methods & {
[P in keyof Computed]: ReturnType<Computed[P]>
}
>
}): unknown
00216-extreme-slice
For example
type Arr = [1, 2, 3, 4, 5]
type Result = Slice<Arr, 2, 4> // expected to be [3, 4]
// your answers
type Index = ['+', number] | ['-', number]
type FormatIndex<T extends number> = `${T}` extends `-${infer R extends number}`
? ['-', R]
: ['+', T]
type NumberToTuple<T extends number, Result extends 0[] = []> = Result['length'] extends T
? Result
: NumberToTuple<T, [0, ...Result]>
type MinusOne<T extends number, Result extends 0[] = NumberToTuple<T>> = Result extends [infer F, ...infer R]
? R['length']
: 0
type Minus<L extends number, M extends number> = L extends M
? 0
: M extends 0
? L
: Minus<MinusOne<L>, MinusOne<M>>
/**
* CorrectIndex<-1, 5> // 4
* CorrectIndex<1, 5> // 1
*/
type CorrectIndex<T extends number, L extends number = 0, I extends Index = FormatIndex<T>> = I[0] extends '+'
? I[1]
: Minus<L, I[1]>
type GT<T extends number, D extends number, E extends boolean = T extends D ? true : false> = E extends true ? false : T extends D
? true
: T extends 0
? false
: GT<MinusOne<T>, D, false>
type Slice<
Arr extends unknown[],
Start extends number = 0,
End extends number = Arr['length'],
CorrectStart extends number = CorrectIndex<Start, Arr['length']>,
CorrectEnd extends number = CorrectIndex<End, Arr['length']>,
Result extends unknown[] = [],
> = GT<CorrectStart, Arr['length']> extends true
? []
: CorrectEnd extends 0
? Result
: CorrectStart extends 0
? Arr extends [infer F, ...infer R]
? Slice<R, never, never, MinusOne<CorrectStart>, MinusOne<CorrectEnd>, [...Result, F]>
: Result
: Arr extends [infer F, ...infer R]
? Slice<R, never, never, MinusOne<CorrectStart>, MinusOne<CorrectEnd>, Result>
: never
00223-hard-isany
So, let's write a utility type IsAny<T>
, which takes input type T
. If T
is any
, return true
, otherwise, return false
.
type IsAny<T> = boolean extends (T extends never ? true : false) ? true : false;
00268-easy-if
Implement the util type If<C, T, F>
which accepts condition C
, a truthy value T
, and a falsy value F
. C
is expected to be either true
or false
while T
and F
can be any type.
For example:
type A = If<true, 'a', 'b'> // expected to be 'a'
type B = If<false, 'a', 'b'> // expected to be 'b'
type If<C, T, F> = C extends true ? T : F
00270-hard-typed-get
For example
type Data = {
foo: {
bar: {
value: 'foobar',
count: 6,
},
included: true,
},
hello: 'world'
}
type A = Get<Data, 'hello'> // 'world'
type B = Get<Data, 'foo.bar.count'> // 6
type C = Get<Data, 'foo.bar'> // { value: 'foobar', count: 6 }
type Split<S extends string> = S extends `${infer First}.${infer Rest}`
? [First, ...Split<Rest>]
: [S]
type Join<S extends string[]> = S extends [infer First, ...infer Rest]
? Rest['length'] extends 0
? First
: `${First}.${Join<Rest>}`
: ''
type Get<
T,
K extends string,
StrArr extends string[] = Split<K>,
> = K extends keyof T
? T[K]
: StrArr[0] extends keyof T
? StrArr['length'] extends 1
? T[StrArr[0]]
: Get<
T[StrArr[0]],
StrArr extends [string, ...infer Rest] ? Join<Rest> : '',
StrArr extends [string, ...infer Rest] ? Rest : []
>
: never
00274-extreme-integers-comparator
Implement a type-level integers comparator. We've provided an enum for indicating the comparison result, like this:
- If a
is greater than b
, type should be Comparison.Greater
.
- If a
and b
are equal, type should be Comparison.Equal
.
- If a
is lower than b
, type should be Comparison.Lower
.
Note that a
and b
can be positive integers or negative integers or zero, even one is positive while another one is negative.
type MyEqual<A, B> = (<T>() => T extends A ? 1 : 2) extends (<T>() => T extends B ? 1 : 2) ? true : false
type IsFNumber<T extends string | number> = `${T}` extends `-${number}` ? true : false
type GetNumber<T extends string | number> = `${T}` extends `-${infer N}` ? N : T
type NTCompare<T extends string | number, N extends string | number> = IsFNumber<T> extends IsFNumber<N> ? never : IsFNumber<T> extends true ? Comparison.Lower : Comparison.Greater
type GetNumberArray<T extends string | number, R extends number[] = []> = `${R['length']}` extends `${T}` ? R : GetNumberArray<T, [...R, 0]>
type EasyCompare<A extends string | number, B extends string | number> =
GetNumberArray<GetNumber<A>> extends [...GetNumberArray<GetNumber<B>>, ...number[]]
? IsFNumber<A> extends true ? Comparison.Lower : Comparison.Greater
: IsFNumber<A> extends true ? Comparison.Greater : Comparison.Lower;
type EComparator<A extends string | number, B extends string | number> = NTCompare<A, B> extends never ? MyEqual<A, B> extends true ? Comparison.Equal : EasyCompare<A, B> : NTCompare<A, B>
type Comparator<A extends string | number, B extends string | number> = NTCompare<A, B> extends never ? MyEqual<A, B> extends true ? Comparison.Equal :
IsFNumber<A> extends true ? PEasyCompare<GetNumberPArray<B>, GetNumberPArray<A>> : PEasyCompare<GetNumberPArray<A>, GetNumberPArray<B>> : NTCompare<A, B>
type GetNumberPArray<T extends number | string> = `${T}` extends `${infer F}${infer Rest}` ? [F, ...GetNumberPArray<Rest>] : []
type PEasyCompare<A extends Array<string | number>, B extends Array<string | number>> = EComparator<A['length'], B['length']> extends Comparison.Equal
? PCompare<A, B>
: EComparator<A['length'], B['length']>
type GetFirst<T extends any[]> = T[0];
type GetRest<T extends any[]> = T extends [infer F, ...infer Rest] ? Rest : never;
type PCompare<A extends any[], B extends any[]> = EComparator<GetFirst<A>, GetFirst<B>> extends Comparison.Equal ?
A['length'] extends 1 ? Comparison.Equal : PCompare<GetRest<A>, GetRest<B>> : EComparator<GetFirst<A>, GetFirst<B>>
00296-medium-permutation
type perm = Permutation<'A' | 'B' | 'C'>; // ['A', 'B', 'C'] | ['A', 'C', 'B'] | ['B', 'A', 'C'] | ['B', 'C', 'A'] | ['C', 'A', 'B'] | ['C', 'B', 'A']
type Permutation<T, U = T> = [T] extends [never]
? []
: T extends U
? [T, ...Permutation<Exclude<U, T>>]
: []
00298-medium-length-of-string
Compute the length of a string literal, which behaves like String#length
.
type StringToArray<S extends string> = S extends `${infer F}${infer R}` ? [F, ...StringToArray<R>] : []
type LengthOfString<S extends string> = StringToArray<S>['length']
00300-hard-string-to-number
Convert a string literal to a number, which behaves like Number.parseInt
.
type ToNumber<S extends string> = S extends `${infer R extends number}` ? R : never
00399-hard-tuple-filter
Implement a type FilterOut<T, F>
that filters out items of the given type F
from the tuple T
.
For example,
type Filtered = FilterOut<[1, 2, null, 3], null> // [1, 2, 3]
type FilterOut<T extends any[], U> = T extends [infer F, ...infer R]
? [F] extends [U]
? FilterOut<R, U>
: [F, ...FilterOut<R, U>]
: T
00459-medium-flatten
In this challenge, you would need to write a type that takes an array and emitted the flatten array type.
For example:
type flatten = Flatten<[1, 2, [3, 4], [[[5]]]]> // [1, 2, 3, 4, 5]
type Flatten<T> = T extends [] ? []
: T extends [infer F, ...infer A]
? [...Flatten<F>, ...Flatten<A>]
: [T]
00462-extreme-currying-2
Currying is the technique of converting a function that takes multiple arguments into a sequence of functions that each take a single argument.
But in our daily life, currying dynamic arguments is also commonly used, for example, the Function.bind(this, [...params])
API.
const func = (a: number, b: number, c: number) => {
return a + b + c
}
const bindFunc = func(null, 1, 2)
const result = bindFunc(3) // result: 6
Thus, based on Currying 1
, we would need to have the dynamic argument version:
const add = (a: number, b: number, c: number) => a + b + c
const three = add(1, 1, 1)
const curriedAdd = DynamicParamsCurrying(add)
const six = curriedAdd(1, 2, 3)
const seven = curriedAdd(1, 2)(4)
const nine = curriedAdd(2)(3)(4)
In this challenge, DynamicParamsCurrying
may take a function with zero to multiple arguments, you need to correctly type it. The returned function may accept at least one argument. When all the arguments as satisfied, it should yield the return type of the original function correctly.
type PartialTuple<T extends any[], U extends T[number][] = []> = T extends [infer F, ...infer R] ? [...U, F] | PartialTuple<R, [...U, F]> : []
type Tails<T extends any[]> = T extends [any, ...infer R] ? R : never
type Rest<T extends any[], U extends any[], R extends any[] = []> = R['length'] extends U['length'] ? T : Rest<Tails<T>, U, [...R, unknown]>
type Curry<T extends any[], R, Args extends any[] = PartialTuple<T>> = <TargetArgs extends Args>(...args: TargetArgs) => (TargetArgs['length'] extends T['length'] ? R : Curry<Rest<T, TargetArgs>, R>)
declare function DynamicParamsCurrying<T extends any[], R>(fn: (...args: T) => R): Curry<T, R>
00472-hard-tuple-to-enum-object
The enum is an original syntax of TypeScript (it does not exist in JavaScript). So it is converted to like the following form as a result of transpilation:
let OperatingSystem;
(function (OperatingSystem) {
OperatingSystem[OperatingSystem["MacOS"] = 0] = "MacOS";
OperatingSystem[OperatingSystem["Windows"] = 1] = "Windows";
OperatingSystem[OperatingSystem["Linux"] = 2] = "Linux";
})(OperatingSystem || (OperatingSystem = {}));
In this question, the type should convert a given string tuple to an object that behaves like an enum. Moreover, the property of an enum is preferably a pascal case.
Enum<["macOS", "Windows", "Linux"]>
// -> { readonly MacOS: "macOS", readonly Windows: "Windows", readonly Linux: "Linux" }
If true
is given in the second argument, the value should be a number literal.
Enum<["macOS", "Windows", "Linux"], true>
// -> { readonly MacOS: 0, readonly Windows: 1, readonly Linux: 2 }
A1
// answer 1
type GetTupleUntilTarget<T extends readonly string[], S extends string> =
T extends readonly [infer F, ...infer R extends readonly string[]]
? S extends F
? []
: [F, ...GetTupleUntilTarget<R, S>]
: []
type Enum<T extends readonly string[], N extends boolean = false> =
N extends false
? {
readonly [P in T[number] as Capitalize<P>]: P
}
: {
readonly [K in T[number] as Capitalize<K>]: GetTupleUntilTarget<T, K>['length']
}
A2
// TODO
type PascalCase<T extends string> = Capitalize<T>
type StringToNumber<T> = T extends `${infer N extends number}` ? N : never
type Enum<T extends readonly string[], N extends boolean = false> = {
[P in keyof T as T[P] extends string ? PascalCase<T[P]> : never]:
N extends true
? StringToNumber<P>
: T[StringToNumber<P>]
}
00476-extreme-sum
Implement a type Sum<A, B>
that summing two non-negative integers and returns the sum as a string. Numbers can be specified as a string, number, or bigint.
For example,
type T0 = Sum<2, 3> // '5'
type T1 = Sum<'13', '21'> // '34'
type T2 = Sum<'328', 7> // '335'
type T3 = Sum<1_000_000_000_000n, '123'> // '1000000000123'
// sum the lengths of three arrays
type ArrToSum<T extends any[], U extends any[], S extends any[]> = `${[
...T,
...U,
...S,
]['length']}`
// convert a number string (e.g., "1", "21") to an array with the corresponding length
type StrToArr<T extends string, U extends never[] = []> = T extends ''
? []
: `${U['length']}` extends `${T}`
? U
: StrToArr<T, [...U, never]>
// sum single digits (e.g., A = "1", B = "2", C="5")
type SumDigit<A extends string, B extends string, C extends string> =
ArrToSum<StrToArr<LastDigit<A>>, StrToArr<LastDigit<B>>, StrToArr<C>>
// extract the last digit (e.g., extract "4" from "1234")
type LastDigit<T extends string> = T extends `${infer P}${infer Q}`
? Q extends ''
? P
: LastDigit<Q>
: ''
// extract the digits other than the laast digit (e.g., extract "123" from "1234")
type RestDigits<T extends string> = T extends ''
? ''
: T extends `${infer P}${infer Q}`
? Q extends ''
? ''
: `${P}${RestDigits<Q>}`
: ''
// main calculation logic. takes A, B, and Carry.
// Carry represents the carry digit from previous digit calculation.
// the calculation is done recursively, digit by digit.
type _Sum<
A extends string,
B extends string,
Carry extends string = '0',
> = `${A}${B}${Carry}` extends ''
? ''
: `${_Sum<
RestDigits<A>,
RestDigits<B>,
RestDigits<SumDigit<A, B, Carry>>
>}${LastDigit<SumDigit<A, B, Carry>>}`
type Sum<
A extends string | number | bigint,
B extends string | number | bigint,
> = _Sum<`${A}`, `${B}`>
00517-extreme-multiply
Implement a type Multiply<A, B>
that multiplies two non-negative integers and returns their product as a string. Numbers can be specified as string, number, or bigint.
For example:
type T0 = Multiply<2, 3> // '6'
type T1 = Multiply<3, '5'> // '15'
type T2 = Multiply<'4', 10> // '40'
type T3 = Multiply<0, 16> // '0'
type T4 = Multiply<'13', '21'> // '273'
type T5 = Multiply<'43423', 321543n> // '13962361689'
type IsIncludesZero<T> = [Extract<T, 0 | '0'>] extends [never] ? false : true
type MultiplyWithSmallNumber<
A extends string | number | bigint,
B extends string | number | bigint,
AC extends unknown[] = [],
T extends string = '0',
> = `${A}` extends `${infer X extends number}`
? X extends AC['length']
? T
: MultiplyWithSmallNumber<A, B, [unknown, ...AC], Sum<T, B>>
: never
type MultiplyNoZero<
A extends string | number | bigint,
B extends string | number | bigint,
T extends string = '0',
Add extends string = '',
> = ReverseString<`${A}`> extends `${infer F extends number}${infer R}`
? MultiplyNoZero<ReverseString<R>, B, Sum<`${MultiplyWithSmallNumber<F, B>}${Add}`, T>, `${Add}0`>
: T
type Multiply<
A extends string | number | bigint,
B extends string | number | bigint,
> = IsIncludesZero<A | B> extends true ? '0' : MultiplyNoZero<A, B>
00527-medium-append-to-object
For example
type Test = { id: '1' }
type Result = AppendToObject<Test, 'value', 4> // expected to be { id: '1', value: 4 }
type AppendToObject<T extends object, U extends string, V> = {
[P in keyof T | U]: P extends U
? V
: (P extends keyof T ? T[P] : never)
}
00529-medium-absolute
For example
type Test = -100
type Result = Absolute<Test> // expected to be "100"
type Absolute<T extends number | string | bigint> = `${T}` extends `-${infer U}` ? U : `${T}`
00531-medium-string-to-union
type Test = "123"
type Result = StringToUnion<Test> // expected to be "1" | "2" | "3"
type StringToUnion<T extends string> = T extends `${infer A}${infer R}`
? A | StringToUnion<R> : never
00533-easy-concat
For example:
type Result = Concat<[1], [2]> // expected to be [1, 2]
type Concat<T extends unknown[], U extends unknown[]> = [...T, ...U]
00545-hard-printf
Implement Format<T extends string>
generic.
For example,
type FormatCase1 = Format<"%sabc"> // FormatCase1 : string => string
type FormatCase2 = Format<"%s%dabc"> // FormatCase2 : string => number => string
type FormatCase3 = Format<"sdabc"> // FormatCase3 : string
type FormatCase4 = Format<"sd%abc"> // FormatCase4 : string
type FormatType = { s: string, d: number, f: number/*, ...*/ };
type Format<T extends string> = T extends `${any}%${infer S}${infer R}` ?
S extends keyof FormatType ? (arg: FormatType[S]) => Format<R> : Format<R> :
string;
00553-hard-deep-object-to-unique
For example,
import { Equal } from "@type-challenges/utils"
type Foo = { foo: 2; bar: { 0: 1 }; baz: { 0: 1 } }
type UniqFoo = DeepObjectToUniq<Foo>
declare let foo: Foo
declare let uniqFoo: UniqFoo
uniqFoo = foo // ok
foo = uniqFoo // ok
type T0 = Equal<UniqFoo, Foo> // false
type T1 = UniqFoo["foo"] // 2
type T2 = Equal<UniqFoo["bar"], UniqFoo["baz"]> // false
type T3 = UniqFoo["bar"][0] // 1
type T4 = Equal<keyof Foo & string, keyof UniqFoo & string> // true
type DeepObjectToUniq<
O extends object,
_UniqFlag extends [PropertyKey?, object?] = []
> = {
[P in keyof O]: O[P] extends object ? DeepObjectToUniq<O[P], [P, O]> : O[P]
} & { [flag: symbol]: _UniqFlag }
00599-medium-merge
Merge two types into a new type. Keys of the second type overrides keys of the first type.
For example
type foo = {
name: string
age: string
}
type coo = {
age: number
sex: string
}
type Result = Merge<foo, coo> // expected to be {name: string, age: number, sex: string}
type Merge<F, S> = {
[key in keyof F | keyof S]: key extends keyof S
? S[key]
: key extends keyof F
? F[key]
: never
}
00612-medium-kebabcase
Replace the camelCase
or PascalCase
string with kebab-case
.
FooBarBaz
-> foo-bar-baz
For example
type FooBarBaz = KebabCase<"FooBarBaz">
const foobarbaz: FooBarBaz = "foo-bar-baz"
type DoNothing = KebabCase<"do-nothing">
const doNothing: DoNothing = "do-nothing"
type KebabCase<S> = S extends `${infer S1}${infer S2}`
? S2 extends Uncapitalize<S2>
? `${Uncapitalize<S1>}${KebabCase<S2>}`
: `${Uncapitalize<S1>}-${KebabCase<S2>}`
: S
00645-medium-diff
Get an Object
that is the difference between O
& O1
type Diff<O, O1> = Omit<O & O1, keyof O & keyof O1>;