The generic Fizz Buzz test built entirely with TypeScript type annotations.
ts-fizz-buzz
The generic Fizz Buzz test built entirely with TypeScript type annotations.
This means that is works solely on typescript types - so it is a compile time Fizz Buzz "Solution".
And using VSCode IntelliSense you see the solution without even "running" your code!
This post is a breakdown/explanation of the types created & used in the Repo.
Outline & Plan.
FizzBuzz is a basic programming task that is (was?) used in interviews.
For those who've never seen the Fizz Buzz problem here it is:
Write a program that prints the numbers from 1 to 100. But for multiples of three print “Fizz” instead of the number and for the multiples of five print “Buzz”. For numbers which are multiples of both three and five print “FizzBuzz”.
Before we delve into the solution, I think its worth breaking down the tasks we need to accomplish:
Build our Predicate Types. We need a way to see if a number is divisible by 3 or 5.
Combine our Predicate Types & solve FizzBuzz for a single number.
Solve FizzBuzz for an array of numbers.
Predicate Types - check the divisibility
We want to check if a number is divisible by 3 or 5, but sadly TypeScript (currently) does not support arithmetic operations - e.g. we can't use a modulus to see if a number is divisible.
But TS Template Literals now allow us to build some of the divisibility rules!
// 5 Divisibility rule, we only need to look at last digit & see if it is a 0 or 5.exporttypeIsDivisibleBy5<Textendsnumber>=`${T}`extends`${inferOtherDigits}${0|5}`?true:false;
Breakdown:
<T extends number> - This enforces users to only input numbers.
`${T}` - The back ticks are the new template literal syntax, this allows us to convert the number input into a string.
`${infer OtherDigits}...` - the infer acts like a wildcard that is (in this case) a string without the final character.
`${0 | 5} ? true : false - checks if the last number is 0 or a 5. It if is, then the Type resolves to true otherwise false.
Sadly I couldn't generate the number 3 divisibility rule (since it includes adding each digit, which requires an arithmetic operation), so stuck with a simple union of valid numbers.
// Unsure if it is possible to generate a 3 Divisibility rule using current version of TypescripttypeFinite3Tuple=[0,3,6,9,12,15,18,21,24,27,30,33,36,39,42,45,48,51,54,57,60,63,66,69,72,75,78,81,84,87,90,93,96,99]exporttypeIsDivisibleBy3<Textendsnumber>=TextendsFinite3Tuple[number]?true:false;
Fizz Buzz for a single number
// This is to remove the number at end of recursive concatenation in FizzBuzz below.// E.g. Fizz4, Fizz2, ...typeRemoveTrailingNumber<Strextendsstring|number,Textendsnumber>=`${Str}`extends`${inferRest}${T}`?Rest:Str// Helper type to abstract the recursion & conversiontypeConcatBuzz<Textendsnumber,BuzzPredicate>=RemoveTrailingNumber<FizzBuzz<T,null,BuzzPredicate>,T>/* Fizz Buzz type used for single number */exporttypeFizzBuzz<Textendsnumber,FizzPredicate=IsDivisibleBy3<T>,BuzzPredicate=IsDivisibleBy5<T>>=FizzPredicateextendstrue?`Fizz${ConcatBuzz<T,BuzzPredicate>}`:BuzzPredicateextendstrue?`Buzz`:T
The idea behind it is that it will recursively build the FizzBuzz string. Whenever the Fizz is found, we can then combine the Buzz onto it (if found). To prevent the Fizz/Buzz from continuously being concatenated, we set their predicate to null.
Lets focus on the Main FizzBuzz type, the other types are just helpers to abstract out the recursion & trimming the output.
It takes 3 arguments/generics. T extends number which is the input; and the Fizz & Buzz Predicates (FizzPredicate & BuzzPredicate).
If FizzPredicate is valid, then we start building our string `Fizz${ConcatBuzz<T, BuzzPredicate>}`.
The ConcatBuzz is just a helper type to abstract away the recursion. It recalls FizzBuzz, but sets the FizzPredicate to null, so the remaining iterations go through the rest of the predicates.
If BuzzPredicate is valid, then we just return Buzz.
Finally, if none of the predicates are valid then we just return the number.
CalculateFizzBuzz is just a helper type that allows us to simplify FizzBuzzArray type.
Breakdown of FizzBuzzArray:
Arr extends number[] - enforces that the type must take a number array. This will be our input.
{ [K in keyof Arr]: ... } - Arrays can be treated as objects where the keys are the indices. So we can use the keyof to go through each key/index.
CalculateFizzBuzz<Arr[K] & number> - This runs FizzBuzz for this value with the given index. The & number is the TS intersection type - we can use it to enforce that the given value is a number.
Similarly, we could have used a ternary as well: Arr[K] extends number ? CalculateFizzBuzz<Arr[K]> : never
Thats it!
Now if we give it an input array, we get the FizzBuzz result out of it!
Check out the TS playground in the Github repo for more (& for an extended version of FizzBuzz).