自拍偷在线精品自拍偷,亚洲欧美中文日韩v在线观看不卡

用 TypeScript 實現(xiàn)斐波那契數(shù)列

開發(fā) 前端
如果之前學(xué)過 C++ 或者 Java 之類的語言,對泛型一定不陌生,泛型可以讓我們定義函數(shù)參數(shù)的時候不指定參數(shù)類型,用一個占位符代替,當(dāng)運行的時候再由外界傳過來的類型決定。

[[432333]]

前幾天在知乎看到一篇文章,用 TypeScript 類型運算實現(xiàn)一個中國象棋程序 :

邊看邊 woc,TypeScript 不是一個類型系統(tǒng)嗎,咋還實現(xiàn)象棋了,感覺發(fā)現(xiàn)了新大陸一樣,然后把大佬的代碼 clone下來,本地「運行」了一下,只能是帶引號的運行了,因為 TS就是動態(tài)推導(dǎo)類型,只需要安裝相關(guān)插件,鼠標(biāo) hover 上去就可以看到結(jié)果了。

看到這種神奇魔法,于是自己就查了查這是為什么。

圖靈完備

這是接觸到的第一個概念,維基百科是這樣定義的:

一個計算系統(tǒng)可以計算任何圖靈-可計算函數(shù),被稱作圖靈完全(或者圖靈完備)?;蛘呷魏慰梢阅M通用圖靈機的系統(tǒng)。

可計算函數(shù)粗暴的理解為「人能計算的問題」,而現(xiàn)在例如 C++、JAVA幾乎所有編程語言都是具有圖靈完備性的,關(guān)于圖靈完備是一個更大的問題,更通俗的解釋可以看 什么是圖靈完備?知乎這篇回答,很有意思。

https://www.zhihu.com/question/20115374/answer/288346717

還了解到了一門有趣的編程語言 —— 1993 年Urban Müller 發(fā)明的Brainfuck 語言,感受一下它怎么打印 Hello World。

  1. +++++ +++++             initialize counter (cell #0) to 10 
  2. [                       use loop to set 70/100/30/10 
  3.     > +++++ ++              add  7 to cell #1 
  4.     > +++++ +++++           add 10 to cell #2 
  5.     > +++                   add  3 to cell #3 
  6.     > +                     add  1 to cell #4 
  7. <<<< -                  decrement counter (cell #0) 
  8. > ++ .                  print 'H' 
  9. > + .                   print 'e' 
  10. +++++ ++ .              print 'l' 
  11. .                       print 'l' 
  12. +++ .                   print 'o' 
  13. > ++ .                  print ' ' 
  14. << +++++ +++++ +++++ .  print 'W' 
  15. > .                     print 'o' 

是的,你沒有看錯,左邊是代碼,右邊是注釋,這里有運行的單句執(zhí)行圖示,可以感受一下:

http://fatiherikli.github.io/brainfuck-visualizer/

上邊的紙帶代表內(nèi)存中的情況,然后通過左移右移加加減減,最終輸出了 Hello world!。

而在 TypeScript 倉庫的一個 issues 中也討論過 TypeScript 的圖靈完備了,作者還分享了一個判斷是否是質(zhì)數(shù)的代碼,也很有意思。

TypeScript 相關(guān)知識

為了實現(xiàn)文章的標(biāo)題 「用 TypeScript 實現(xiàn)斐波那契數(shù)列」,需要先學(xué)習(xí)下相關(guān)的知識點。

泛型

如果之前學(xué)過 C++ 或者 Java 之類的語言,對泛型一定不陌生,泛型可以讓我們定義函數(shù)參數(shù)的時候不指定參數(shù)類型,用一個占位符代替,當(dāng)運行的時候再由外界傳過來的類型決定。

舉個簡單的例子,實現(xiàn)兩個元素相加,如果用 TypeScript 限制的話,即使是相同的邏輯也要寫多次了。

  1. function addTwoNumber(a: number, b: number):number { 
  2.   return a + b; 
  3.  
  4. function addTwoNumberString(a: string, b: string):string { 
  5.   return a + b; 
  6.  
  7. addTwoNumber(1,3) 
  8. addTwoNumberString('1''2'); 

不然的話就只能 anyScript 了,完全失去了類型校驗。

  1. function addTwoNumber(a: any, b: any):any { 
  2.   return a + b; 
  3.  
  4. addTwoNumber(1,3) 
  5. addTwoNumber('1''2'); 
  6.  
  7. addTwoNumber('1', 2); //不報錯 

如果有泛型的話,就既可以達到邏輯的復(fù)用,同時對類型進行校驗。

  1. function addTwoNumber<T>(a: T, b: T): T { 
  2.   return <any>a + <any>b; //這里需要強制轉(zhuǎn)換下,不然會報 Operator '+' cannot be applied to types 'T' and 'T'.ts(2365) 
  3.  
  4. addTwoNumber(1, 3); 
  5. addTwoNumber("1""3"); 
  6.  
  7. addTwoNumber('1', 2); //報錯 

當(dāng)然上邊有強行用泛型的嫌疑了,不過能大體理解泛型的作用就好,哈哈。上邊的情況用 TS 的重載會更好些。

類型 type

TS 中除了基本的類型,number、string 、number[] 等,比較特殊的地方 1 、abc 、true 也可以單獨算一個類型。1 的類型是 1,當(dāng)然也屬于 number。

最重要的是 TS 允許我們定義新的類型,而且我們還可以通過泛型變量,進行類型的運算然后產(chǎn)生新的類型。舉幾個例子:

  1. type Type1<T> = T extends number ? number : string; 
  2. type Type2 = Type1<2121>; // 此時 Type2 就相當(dāng)于 number 
  3. type Type3 = Type1<{}>; // 此時 Type3 就相當(dāng)于 string 

exstends 和后邊的 ? 構(gòu)成了一個三元表達式,如果 extends 前面的類型能夠賦值給 extends 后面的類型,那么表達式判斷為真,否則為假。

因為單個數(shù)字也是一個類型,所以我們就可以判斷傳入的 T 是否等于某個數(shù)。

  1. type Type4<T> = T extends 1 ? true : false
  2. type Type5 = Type4<2121>; // 此時 Type5 就相當(dāng)于 false 類型 
  3. type Type6 = Type4<1>; // 此時 Type6 就相當(dāng)于 true 類型 

可以仔細體會一下這里,很關(guān)鍵,后邊寫斐波那契數(shù)列的時候,一不小心就會被繞進去,因為我們是在操控類型之間的運算,和平時的編程感覺很不一樣。

一句話總結(jié),每個類型可以看成一個函數(shù),傳入的泛型是函數(shù)參數(shù),并且也是一個類型,最后再返回一個新的類型。

  1. Type(type, type) => type 

infer

infer 表示在 extends 條件語句中待推斷的類型變量。

簡單理解,我們是為了判斷某個類型是否 extends 某個「結(jié)構(gòu)」,但結(jié)構(gòu)中參數(shù)的類型我們并不知道,此時我們寫一個 infer R(R只是占位,任何名字都可以),在類型推導(dǎo)的時候,R 就是當(dāng)前類型真正的參數(shù)類型。舉個例子:

我們判斷 T 是否是 {a: XXX, b: XXX}的類型,因為 T 是泛型,我們并不知道T 中的 a 是什么類型,此時就可以用 infer 占位。當(dāng)傳入具體的類型是就可以拿到 a 是什么類型了。

  1. type Foo<T> = T extends { a: infer U; b: infer U } ? U : never; 
  2.  
  3. type T1 = Foo<{ a: string; b: string }>; // T1 類型為 string 
  4. type T2 = Foo<{ a: string; b: number }>; // T2 類型為 string | number 

斐波那契數(shù)列

斐波那契數(shù)列就不多解釋了,先用 js 實現(xiàn)一個斐波那契數(shù)列。

  1. function Fibonacci(n) { 
  2.   if (n === 1) { 
  3.     return 1; 
  4.   } 
  5.   if (n === 2) { 
  6.     return 1; 
  7.   } 
  8.   return Fibonacci(n - 1) + Fibonacci(n - 2); 

最關(guān)鍵的遞歸不用擔(dān)心,TS 支持類型的遞歸定義,我們先寫一個大概的雛形,看象棋直接用中文定義類型挺有意思,這里也直接中文了。之前總結(jié)的那句話這里再加深一下,每個類型可以看成一個函數(shù),傳入的泛型是函數(shù)參數(shù),并且也是一個類型,最后再返回一個新的類型。

我們先定義「斐波那契」類型,泛型是傳入一個數(shù)字(這里的數(shù)字是當(dāng)作類型),先判斷傳入的類型是否是 1 或者 2,然后直接返回 1 類型。

  1. type 斐波那契<某數(shù) extends number> = 某數(shù) extends 1  
  2.   ? 1 
  3.   : 某數(shù) extends 2 
  4.   ? 1 
  5.   : 相加<斐波那契<減一<某數(shù)>>, 斐波那契<減一<減一<某數(shù)>>>>; 

: 相加<斐波那契<減一<某數(shù)>>, 斐波那契<減一<減一<某數(shù)>>>>;

上邊需要注意的是 <> 里邊的 extends 是為了限定傳入的類型,和外邊的 extends 不一樣。

我們還需要定義一個「相加」類型和一個「減一」類型。

相加是兩個類型相加,但是類型系統(tǒng)是不支持?jǐn)?shù)字直接相加的,1 + 2 并不能直接相加,這里有個 trick。

數(shù)組類型和 js 中的數(shù)組一樣,同樣擁有 length 屬性,返回一個具體的數(shù)字類型,也支持?jǐn)U展運算符。舉個例子:

  1. type A = [anyanyany
  2. type B = A["length"] // B 就是 3 類型 

所以我們可以將數(shù)字轉(zhuǎn)為數(shù)組,操作數(shù)組,然后再將數(shù)組通過 length 轉(zhuǎn)回數(shù)字。

先寫一個得到對應(yīng)數(shù)組的 Type,這里就需要用到遞歸了。

  1. type 得到長度<數(shù)組 extends any[]> = 數(shù)組["length"]; 
  2.  
  3. type 轉(zhuǎn)為數(shù)組< 
  4.   某數(shù) extends number, 
  5.   對應(yīng)數(shù)組 extends any[] = [] // 默認(rèn)值賦一個空數(shù)組,外部調(diào)用的時候不需要傳 
  6. > = 得到長度<對應(yīng)數(shù)組> extends 某數(shù) // 長度是否等于了需要的長度 
  7.   ? 對應(yīng)數(shù)組 // 如果長度等于所需要的了就返回 
  8.   : 轉(zhuǎn)為數(shù)組<某數(shù), [any, ...對應(yīng)數(shù)組]>; // 否則再添加一個元素進入數(shù)組,然后遞歸調(diào)用 

: 轉(zhuǎn)為數(shù)組<某數(shù), [any, ...對應(yīng)數(shù)組]>; // 否則再添加一個元素進入數(shù)組,然后遞歸調(diào)用

有了轉(zhuǎn)為數(shù)組的的 Type,相加方法就很好寫了,我們只需要將兩個數(shù)先轉(zhuǎn)為對應(yīng)的數(shù)組,將兩個數(shù)組連接,最后返回連接后的數(shù)組長度即可。

  1. type 相加<某數(shù)甲 extends number, 某數(shù)乙 extends number> = 得到長度< 
  2.   [...轉(zhuǎn)為數(shù)組<某數(shù)甲>, ...轉(zhuǎn)為數(shù)組<某數(shù)乙>] 
  3. >; 

然后定義減一的方法,也就是數(shù)組長度減 1,這里就利用到了 infer,還需要利用數(shù)組的解構(gòu)。

  1. type 數(shù)組減一<某數(shù)組類型 extends any[]> = (( 
  2.   ...參數(shù): 某數(shù)組類型 
  3. ) => any) extends (拆一個元素: any, ...剩下的數(shù)組: infer 剩下的數(shù)組類型) => any 
  4.   ? 剩下的數(shù)組類型 
  5.   : []; 

我們定義了一個函數(shù)類型,通過函數(shù)參數(shù)的解構(gòu),使得剩下的數(shù)組少了一個元素。

有了「數(shù)組減一」的類型,數(shù)字「減一」就水到渠成了,將數(shù)字轉(zhuǎn)為對應(yīng)數(shù)組,數(shù)組減去一個元素,然后恢復(fù)為數(shù)字即可。

  1. type 減一<某數(shù) extends number> = 得到長度<數(shù)組減一<轉(zhuǎn)為數(shù)組<某數(shù)>>>; 

整體代碼就出來了:

  1. type 斐波那契<某數(shù) extends number> = 某數(shù) extends 1 
  2.   ? 1 
  3.   : 某數(shù) extends 2 
  4.   ? 1 
  5.   : 相加<斐波那契<減一<某數(shù)>>, 斐波那契<減一<減一<某數(shù)>>>>; 
  6.  
  7. type 得到長度<數(shù)組 extends any[]> = 數(shù)組["length"]; 
  8.  
  9. type 轉(zhuǎn)為數(shù)組< 
  10.   某數(shù) extends number, 
  11.   對應(yīng)數(shù)組 extends any[] = [] 
  12. > = 得到長度<對應(yīng)數(shù)組> extends 某數(shù) 
  13.   ? 對應(yīng)數(shù)組 
  14.   : 轉(zhuǎn)為數(shù)組<某數(shù), [any, ...對應(yīng)數(shù)組]>; 
  15.  
  16. type 相加<某數(shù)甲 extends number, 某數(shù)乙 extends number> = 得到長度< 
  17.   [...轉(zhuǎn)為數(shù)組<某數(shù)甲>, ...轉(zhuǎn)為數(shù)組<某數(shù)乙>] 
  18. >; 
  19.  
  20. type 數(shù)組減一<某數(shù)組類型 extends any[]> = (( 
  21.   ...參數(shù): 某數(shù)組類型 
  22. ) => any) extends (拆一個元素: any, ...剩下的數(shù)組: infer 剩下的數(shù)組類型) => any 
  23.   ? 剩下的數(shù)組類型 
  24.   : []; 
  25.  
  26. type 減一<某數(shù) extends number> = 得到長度<數(shù)組減一<轉(zhuǎn)為數(shù)組<某數(shù)>>>; 
  27.  
  28. // 測試 
  29. type 斐波那契八 = 斐波那契<8> 

看下結(jié)果:

不過到斐波那契 11 就因為遞歸層度太深 gg 了。

這里主要就是體驗下 TS 的圖靈完備,優(yōu)化就不考慮了。

總結(jié)

 

通過寫一個這個例子對 TS 的類型有了更多的了解,但平時開發(fā)肯定不會這樣搞,主要是寫著玩哈哈,畢竟一個簡單的斐波那契數(shù)列都寫的這么麻煩,引用 Linus Torvalds 自傳的書名結(jié)束吧,Just for Fun!

 

責(zé)任編輯:武曉燕 來源: windliang
相關(guān)推薦

2021-05-16 18:02:52

系統(tǒng)編程JavaScript

2012-02-22 10:14:44

Java

2021-03-15 06:04:47

斐波那契數(shù)列背包問題算法

2021-12-28 07:20:44

斐波那契數(shù)算法數(shù)字

2020-05-11 14:18:14

JavaScript斐波那契數(shù)列遞歸

2021-10-22 08:22:37

線程Smt內(nèi)核

2023-06-13 06:51:15

斐波那契數(shù)算法

2021-05-08 08:28:38

Java數(shù)據(jù)結(jié)構(gòu)算法

2022-03-28 15:15:15

神經(jīng)網(wǎng)絡(luò)編程開發(fā)

2022-11-14 08:12:34

2024-03-25 08:00:00

C++遞歸函數(shù)

2021-03-17 08:37:23

算法性能分析遞歸算法遞歸樹

2013-04-10 10:58:19

LambdaC#

2020-04-20 11:09:18

Python開發(fā)語言

2012-02-22 14:12:08

算法

2022-06-27 19:19:26

算法題青蛙跳臺階

2013-09-02 10:05:06

C編程語言

2020-11-23 08:53:34

堆Heap

2022-09-14 15:24:57

typescript快排

2022-09-05 08:06:49

數(shù)據(jù)結(jié)構(gòu)Java
點贊
收藏

51CTO技術(shù)棧公眾號