For details of questions and requirements please visit the questions. This post will include only answers. If you are not aware of what this is about, then please take some time with the questions article. Also I would recommend the read about TypeScript type system as a language, which can help with understanding what we are doing here.
This post will include half of the answers as questions and the solution difficulty is significantly higher than previous questions in the series.
Answer 1
The question was: Make type level function which will check if two patients can meet. CanMeet
should return or true or false depends if patients can or can't meet.
In order to achieve that we should use conditional type expression. This expression can be also nested in similar matter we use standard ternary operator.
type CanMeet<A extends Patient, B extends Patient> =
A extends Quarantine ? false // cannot meet with anybody
: A extends Sick ? B extends Sick ? true : false // two sick people can meet
: A extends Healthy ? B extends Healthy ? true : false // two healthy can meet
: false // other combination cannot meet
Full solution in the playground
Answer 2
The question was: Make type level function which will get all sick patients from the collection of patients. GetSick
should filter the collection for only sick patients.
Note below solution is made in TS 4.0 with use of Variadic Tuple Types. It was rewritten as previous solution had problems with new versions of TS.
// utility types needed for adding/removing head of list
type Unshift<A, T extends unknown[]> = [A, ...T];
type Shift<T extends Array<any>> = T extends [unknown, ...infer Rest] ? Rest : T
// below direct solution
// we have compiler error about circular dependency 🛑:
type GetSickNotWorking<
Patients extends Patient[]
, SickPatients extends Patient[] = []
>
= Patients['length'] extends 0
? SickPatients
:
(Patients[0] extends Sick
? GetSickNotWorking<Shift<Patients>, Unshift<Patients[0], SickPatients>>
: GetSickNotWorking<Shift<Patients>, SickPatients>);
// working solution with a mapped hack:
type GetSick<
Patients extends Patient[]
, SickPatients extends Patient[] = []
>
= Patients['length'] extends 0
? SickPatients
: {
[K in keyof Patients]:
Patients[0] extends Sick
? GetSick<Shift<Patients>, Unshift<Patients[0], SickPatients>>
: GetSick<Shift<Patients>, SickPatients>
}[0];
The goal was filtering only sick patients from given collection of patients. This was achieved by utility types Shift
and Unshift
which allow on removing/adding elements from tuple types (tuple type is exactly our collection type at the type level).
Explanation
- Second argument
SickPatients
🤒 is kind of accumulator, remember reduce function? The reason of having it is exactly accumulating sick patients. -
K in keyof Patients
- its really hack in order to avoid circular dependency error -
Patients['length'] extends 0 ? SickPatients
- if our Patients list is already empty we end the computation -
Patients[0] extends Sick ? GetSick<Shift<Patients>, Unshift<Patients[0], SickPatients>> : GetSick<Shift<Patients>, SickPatients>
- if patient is sick we put it into SickPatients list by Unshift and remove it from Patients list. If Patient is not sick we remove it from Patients but without attaching it into SickPatients -
[0]
we get first element, this is part of the hack
Let's follow the algorithm for the example use case (its simplified view):
// patients:
type John = {name: 'John'} & Sick
type Tom = {name: 'Tom'} & Healty
type Kate = {name: 'Kate'} & Sick
type Check = GetSick<[John,Tom,Kate]>
First iteration ➰:
- Patients:
[John,Tom, Kate]
- SickPatients:
[]
- We check if
John
is sick, he is - We remove
John
fromPatients
- We add
John
to the beginning ofSickPatients
// by Unshift - We call next iteration
Second iteration ➰:
- Patients:
[Tom, Kate]
- SickPatients:
[John]
- We check if
Tom
is sick, he is not - We remove
Tom
fromPatients
- We call next iteration
Third iteration ➰:
- Patients:
[Kate]
- SickPatients:
[John]
- We check if
Kate
is sick, she is - We remove
Kate
fromPatients
- We add
Kate
to theSickPatients
Fourth iteration ➰:
-
Patients
list is empty - calculation returns
SickPatients
The result is [Kate, John]
. As you can see order is reversed as we are adding items in the beginning. But the goal is achieved, we get the sick patients 👌
The full solution is available in the playground
Additional challenge 🔥
There was additional/extended question to the second one - Can you make state of the patient as an argument? And make function which will get patients for given condition? Example usage would be Get<Patients, Healthy>
. As we have now GetSick
implemented, can you try to make it more flexible? Put your answer in the comment section (preferred playground link).
Try yourself with the rest of questions! 🔥
There are two questions more in The Bonus Questions. As you see the solution of the first two questions, maybe it will inspire you to make other two. Don't give up, check your skills 💪.
This series will continue. If you want to know about new exciting questions from advanced TypeScript please follow me on dev.to and twitter. Be healthy and take care!
Top comments (9)
My solution to #1 is way simpler:
At least it passes all given conditions.
I was unable to solve #2 and #3.
Ye you have nail that as the false is only when we have different states. That is very good answer 👍
I guess it's better not to bind the implementation to discriminated field value (
state
). It should remain variable.Another solution (that already includes the generic solution):
playground
Another aproach using UnionToTuple:
Playground Link
Well, I find out the answer2 seems not work.
How can I fix it ?
Thx in advanced.
Interesting, TS can have impact. Can you share yours?
Ok the example solution was rewritten in TS 4.0 with using Variadic Tuple Types. Now works! Again thanks for the notice