DEV Community

Cover image for TypeScript Exercises Bonus🦠 - Answers Part 2
Pragmatic Maciej
Pragmatic Maciej

Posted on • Edited on

TypeScript Exercises Bonus🦠 - Answers Part 2

For details of the questions and requirements please visit the questions. This post will include only answers for last two questions. 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 includes half of the answers, first part can be found - TypeScript Exercises Bonus🦠 - Answers Part 1

Answer 3

The question was: Make type level function which will check if given list of patients can be accommodated into the hospital with given list of free beds 🛌.

// remove first item from the tuple
type Shift<T extends Array<any>> 
= ((...a: T) => any) extends ((a: any, ...result: infer Result) => any) ? Result : never;
// alternative version would be simpler with
// Variadic Tuple Types available from TS 4.0
type Shift<T extends Array<any>> = T extends [unknown, ...infer Rest] ? Rest : T;

type _Accomodate<
  Beds extends '🛌'[],
  Patients extends Patient[],
  > = Beds['length'] extends 0 ? Patients['length'] extends 0 ? true : false :
  Patients['length'] extends 0 ? true : { x: _Accomodate<Shift<Beds>, Shift<Patients>> }

type _TraverseA<T> = T extends object ? {
  [K in keyof T]: T[K] extends boolean ? T[K] : _TraverseA<T[K]>
}[keyof T] : T

type CanAccomodate
< Beds extends '🛌'[]
, Patients extends Patient[]
, _A = _Accomodate<Beds, Patients>> 
= _TraverseA<_A>
Enter fullscreen mode Exit fullscreen mode

Ok, so now what we did here. We made three functions - _Accomodate, _TraverseA and the final CanAccomodate.

Function _Accomodate:

  • takes list of beds and list of patients as arguments
  • recursively calls itself until beds or patients list will be empty
  • in every iteration its removes element from both lists by Shift
  • { x: _Accomodate<Shift<Beds>, Shift<Patients>> } - we need to use container type in order to avoid TS blocking us with infinite recursive call, so its kinda hack 🏴‍☠️
  • function creates structure with a shape {x: x: x: true | false}, where amount of levels is equal to smaller list size, the last value is saying if Patients list is longer (false), or is smaller or equal (true)

Function _TraverseA:

  • takes an object or boolean
  • checks recursively if boolean it gives it back (this is the result)
  • if not it recursively traverse the object until the boolean will be found
  • finally it returns the final boolean in the structure prepared by _Accomodate

Function CanAccomodate

  • it is final composition of _Accomodate and _TraverseA
  • it calls _Traverse on object type made by _Accomodate

Full solution in the playground

Answer 4

The question was: Make type level function which will group people from the given list. The three groups are people sick 🤒, people healthy 💪, people in quarantine 🔒.

// utility types needed
type Unshift<A, T extends Array<any>> 
= ((a: A, ...b: T) => any) extends ((...result: infer Result) => any) ? Result : never;
type Shift<T extends Array<any>> 
= ((...a: T) => any) extends ((a: any, ...result: infer Result) => any) ? Result : never;

type AddPatient<S extends Segregated, P extends Patient> = {
  sick: P extends Sick ? Unshift<P, S['sick']> : S['sick'],
  quarantine: P extends Quarantine ? Unshift<[P], S['quarantine']> : S['quarantine'],
  healthy: P extends Healty ? Unshift<P, S['healthy']> : S['healthy'],
}

type Segragate
<Patients extends Patient[]
, Result extends Segregated = {sick: [], quarantine: [], healthy: []}
,_Next extends Patient[] = Shift<Patients>
,_NextSize extends number = _Next['length']
> = {
  [K in (keyof Patients)]:
      Patients[K] extends Patient ?
        _NextSize extends 0 
        ? AddPatient<Result, Patients[K]>
        : Segragate<_Next, AddPatient<Result, Patients[K]>>
        : never
}[0]

Enter fullscreen mode Exit fullscreen mode

Function AddPatient:

  • makes wanted result structure with three sections for patients (sick, healthy,quarantine)
  • for quarantine it additionally puts patient into isolation by [P]

Function Segragate:

  • Make recursive call until list of patients is not empty
  • For every iteration it calls AddPatient in order to put patient into correct section, and also removes this patient from the list as it was already used

Caution the function is not ideal 😪, for bigger list of patients it gives an error about infinite call. Maybe you can make it better? Give it a try 💪

The full solution is available in the playground

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 (4)

Collapse
 
ekazakov profile image
Evgeniy Kazakov

Solution 4 works for 3.8, but for 3.9.2 AfterSegregation became undefined

Collapse
 
macsikora profile image
Pragmatic Maciej

Yes thanks. I believe I have fixed that already in last update.

Collapse
 
drazbest profile image
dr-azbest • Edited

I think I found much more simpler solution to question 3, just 2 lines:

type Indexes<P extends any[]> = {[Index in keyof P]: Index}[number]

type CanAccomodate<Beds extends '🛌'[], Patients extends Patient[]> = Indexes<Patients> extends Indexes<Beds> ? true : false;
Enter fullscreen mode Exit fullscreen mode

Playground Link

Collapse
 
drazbest profile image
dr-azbest • Edited

4:

type GetPatientsGroup<Patients extends Patient[], Group> = UnionToTuple<{[P in keyof Patients] : Patients[P] extends Group ? Patients[P] : never}[number]>
type SeparateByArray<Group extends any[]> = {[Key in keyof Group]: [Group[Key]]};

type Segragate<Patients extends Patient[]> = {
  sick: GetPatientsGroup<Patients, Sick>,
  quarantine: SeparateByArray<GetPatientsGroup<Patients, Quarantine>>,
  healty: GetPatientsGroup<Patients, Healthy>,
}
Enter fullscreen mode Exit fullscreen mode

Playground Link