TypeScript Exercises Bonus🦠 - Answers Part 1

Pragmatic Maciej - Mar 23 '20 - - Dev Community

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
Enter fullscreen mode Exit fullscreen mode

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];

Enter fullscreen mode Exit fullscreen mode

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]>
Enter fullscreen mode Exit fullscreen mode

First iteration ➰:

  • Patients: [John,Tom, Kate]
  • SickPatients: []
  • We check if John is sick, he is
  • We remove John from Patients
  • We add John to the beginning of SickPatients // 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 from Patients
  • We call next iteration

Third iteration ➰:

  • Patients: [Kate]
  • SickPatients: [John]
  • We check if Kate is sick, she is
  • We remove Kate from Patients
  • We add Kate to the SickPatients

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!

. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
Terabox Video Player