当前位置: 首页 > Web前端 > JavaScript

精读《Flip, Fibonacci, AllCombinations...》

时间:2023-03-27 12:57:52 JavaScript

解决TS题最好的方法就是多练习。这次解读类型挑战中等难度是49~56题。精读Flip实现Flip,交换对象T中的Key和Value:Flip<{a:"x",b:"y",c:"z"}>;//{x:'a',y:'b',z:'c'}翻转<{a:1,b:2,c:3}>;//{1:'a',2:'b',3:'c'}翻转<{a:false,b:true}>;//{false:'a',true:'b'}可以通过aswhenkeyof描述对象时添加变形,所以这道题应该这样处理:typeFlip={[KinkeyofTasT[K]]:K}由于Key位置只能是String或者Number,T[K]描述Key会显示错误,我们需要限制Value的类型:typeFlip>={[KinkeyofTasT[K]]:K}但是这个答案没有通过测试用例Flip<{pi:3.14;bool:true}>因为true不能用作键。只有字符串'true'才能作为Key,所以我们只好强行将Key位置转换成字符串://本题答案typeFlip>={[KinkeyofTas`${T[K]}`]:K}FibonacciSequence使用TS实现Fibonacci数列计算:typeResult1=Fibonacci<3>//2typeResult2=Fibonacci<8>//21由于测试用例不是特殊的大型用例,我们可以放心地使用递归来实现它们。JS版的Fibonacci很自然,但是在TS版中我们只能用数组的长度来模拟计算,代码写起来自然会失真。首先,需要一个额外的变量来标记递归的次数,递归在第N次结束:typeFibonacci=N['length']extendsT?(//xxx):Fibonacci每次执行上述代码时,判断是否递归完成,否则继续递归,给计数器加一。我们还需要一个数组来存储答案,以及一个数组来存储之前的数字://TheanswertothisquestiontypeFibonacci=N['length']extendsT?Prev['length']:Fibonacci递归时,用Cur代替下一个Prev,用[...Prev,...Cur]替换下一个Cur,也就是说下一个Cur符合斐波那契定义。AllCombinations实现AllCombinations以实现字符串S的完整排列:typeAllCombinations_ABC=AllCombinations<'ABC'>//shouldbe''|'一个'|'乙'|'C'|'AB'|'交流'|'广管局'|'公元前'|'CA'|'CB'|'美国广播公司'|'ACB'|'BAC'|'BCA'|联合类型,完整的排列只能通过二次组合实现:typeStrToUnion=Sextends`${inferF}${inferR}`?女|StrToUnion:neverinfer描述字符串时,first指向第一个字母,second指向其余字母;递归地将剩余的字符串一个一个地拆解成单独的字符,并用|连接它们:StrToUnion<'ABC'>//'A'|'乙'|'C'将StrToUnion<'ABC'>的结果记录为U,然后使用object-to-union类型特性创建三个字母的ABC完整排列:{[KinU]:`${K}${AllCombinations>}`}[U]//`ABC${any}`|`ACB${any}`|`BAC${any}`|`BCA${any}`|`CAB${any}`|`CBA${any}`不过,只要你巧妙地加上''|在每次递归中,您都可以直接得到答案:typeAllCombinations>=|''|{[KinU]:`${K}${AllCombinations>}`}[U]//''|'一个'|'乙'|'C'|'AB'|'交流'|'广管局'|'公元前'|一个'|'CB'|'美国广播公司'|'ACB'|'BAC'|'BCA'|'驾驶室'|“CBA”为何如此神奇?这是因为每一次递归都会经历''、'A'、'AB'、'ABC'等字符逐渐积累的过程,每次遇到''|,自然会形成一个联合类型,比如遇到'A',自然会形成联合类型'A',同时继续使用'A'和Exclude<'A'|'乙'|'C','A'>组合起来更微妙,第一个''在第一次执行时填充整个数组的第一个Case。最后,我注意到上面的结果产生了一个Error:"Typeinstantiationisexcessivelydeepandpossibleinfinite",也就是这样的递归可能会造成死循环,因为Exclude的结果可能是never,所以finally修复一开始never的判断,利用之前学过的知识,never不会展开联合类型,所以我们用[never]判断来避免://本题答案typeAllCombinations>=[U]extends[never]?'':''|{[KinU]:`${K}${AllCombinations>}`}[U]GreaterThan实现GreaterThan判断T>U:GreaterThan<2,1>//应该是trueGreaterThan<1,1>//应该是falseGreaterThan<10,100>//应该是falseGreaterThan<111,11>//应该是true因为TS不支持加减和大小判断,当你看到这道题,应该想到两种办法,一种是递归,但是会受入参个数的限制,有可能栈溢出。一种是参考了MinusOne的一个特殊方法,巧妙地构造了一个具有预期长度的数组,并与array['length']进行比较。我们先说第一种。递归必须有一个递增的Key。依次比较TU。谁先追上这个数字,谁就是较小的那个://TheanswertothisquestiontypeGreaterThan=TextendsR['length']?false:U扩展R['length']?true:GreaterThan另一种方式是快速构造两个长度分别等于TU的数组,利用数组快速判断哪个更长。构造方法不再展开,仅参考MinusOne的方法,重点介绍如何快速判断[1,1]和[1,1,1]哪个大。因为TS没有大小判断能力,拿到['length']也没用,只好考虑arr1extendsarr2的方法。不幸的是,对于长度不等的数组,extends总是等于false:[1,1,1,1]extends[1,1,1]?true:false//false[1,1,1]extends[1,1,1,1]?true:false//false[1,1,1]扩展[1,1,1]?true:false//true但我们期待以下判断:ArrGreaterThan<[1,1,1,1],[1,1,1]>//trueArrGreaterThan<[1,1,1],[1,1,1,1]>//falseArrGreaterThan<[1,1,1],[1,1,1]>//false的解很体现了TS的思想:既然两个数组相等,返回true,那么我们用[...T,...any]进行补充判断,如果能判断为真,则表示前者的长度较短(因为后者加几项后才能判断):typeArrGreaterThan=Uextends[...T,...any]?false:true来吧,第二个答案是这样的://这道题的答案typeGreaterThan=ArrGreaterThan,NumberToArr>Zip实现了TS版本的zipfunction:typeexp=Zip<[1,2],[true,false]>//expectedtobe[[1,true],[2,false]]这道题还配合辅助变量进行计数递归,并使用一个额外的类型变量来存储结果://TheanswertothisquestiontypeZip=I['length']extendsT['length']?回复:U[I['length']]扩展未定义?邮编:邮编[...R,[T[I['length']],U[I['length']]]]在每个位置根据Zip规则添加一个结果递归,其中I['length']起到类似于for循环的下标i的作用,但是在TS语法中,我们只能以数组的形式模拟这种计数IsTupleimplementsIsTuple判断T是否是一个元组类型(Tuple):typecase1=IsTuple<[number]>//truetypecase2=IsTuple//truetypecase3=IsTuple//false我要吐槽的是那到底是在TS内部还是词法分析是比较有效的判断方式,但是如果用TS来实现,就需要换一种思路了。TS中Tuple和Array的区别在于前者是有限长的,后者是无限长的。从结果来看,如果访问它的['length']属性,前者必须是一个固定的数字,而后者返回一个数字。利用这个特性判断是://TheanswertothisquestiontypeIsTuple=[T]extends[never]?false:Textendsreadonlyany[]?数字扩展T['length']?false:true:false其实这个答案是基于单次测试的一点点测试,因为有一个IsTuple<{length:1}>的单次测试用例,可以通过numberextendsT['的验证length'],但是因为不是数组类型,所以不能通过Textendsreadonlyany[]预判断。Chunk实现TS版本Chunk:typeexp1=Chunk<[1,2,3],2>//预期为[[1,2],[3]]typeexp2=Chunk<[1,2,3],4>//预期为[[1,2,3]]typeexp3=Chunk<[1,2,3],1>//预期为[[1],[2],[3]]Old该方法仍然是递归的。需要一个变量记录Chunk中当前收集的内容,当Chunk达到上限时释放。同时,也应该在没有达到上限的时候释放。typeChunk=Textends[inferFirst,...inferLast]?Chunked['length']扩展N?[Chunked,...Chunk]:Chunk:[Chunked]Chunked['length']extendsN判断Chunked数组的长度后到达N则释放,否则继续将当前数组的第一项First塞入Chunked数组,数组项从Last继续递归。我们发现Chunk<[],1>的单测失败了,因为当Chunked没有item的时候,不需要组团,所以完整的答案是://本题的答案typeChunk=Textends[inferHead,...inferTail]?Chunked['length']扩展N?[Chunked,...Chunk]:Chunk:Chunkedextends[]?Chunked:[Chunked]FillimplementsFill,用N替换数组T的每一项:typeexp=Fill<[1,2,3],0>//的问题expected为[0,0,0]也需要通过recursion+Flag来求解,即定义一个I代表当前递归的下标,一个Flag表示是否到达了要替换的下标。只要到达下标,Flag就会一直为真:typeFill由于递归会不断产生完整的答案,我们将T定义为变量,即只处理第一项,如果当前Flag为true,则使用替换值N,否则使用原来的第一个字符:typeFill=I['length']extendsEnd?T:Textends[inferF,...inferR]?旗帜延伸假?[F,..填充]:[N,...填充]:T,但是这个答案没有通过测试。仔细一想,发现是I超过Start的长度后,Flag的判断失败了。为了超出后保持为真,只要传入覆盖Flag为真时的后续值即可://本题答案类型Fill=I['length']extendsEnd?T:Textends[inferF,...inferR]?旗帜延伸假?[F,...填充]:[N,...填充]:T总结讨论地址为:精读《Flip, Fibonacci, AllCombinations...》·Issue#432·dt-fe/weekly想参与讨论的请戳这里,每周都有新话题,而且前面-周末或周一发布期末精读——帮你过滤靠谱的内容。关注前端精读微信公众号版权声明:免费转载-非商业-非衍生保留属性(CreativeCommons3.0License)