iT邦幫忙

2022 iThome 鐵人賽

DAY 8
0

今天來試試看函數式的思考吧,碎念一下當初選這個題目是因為發現自己OO中毒,沒有繼承不會寫程式。想要深入學習的FP話推薦Haskell趣學指南,我就拿裡面的題目來用C#重作一次。

後綴表示法

後綴表示法是有別於我們習慣的算式表示法,目的是為了不用括弧表現加減乘除的先後順序,舉個例子來說

1 - ( 2 + 3 ) x 4

我們知道要先計算2+3=5,再將5x4=20,最後計算1-20=-19,如果換成後綴表示法則是

1  2  3  +  4  x  -

當遇到計算符號+,就把前面兩項相加,這時候計算式會變成 1 5 4 x - ,依照這個規律把每一項消去,我們可以用堆疊的觀念去解決

https://ithelp.ithome.com.tw/upload/images/20220919/20148594dmc7DbqPHV.png

圖片來源 http://learnyouahaskell.com/functionally-solving-problems#reverse-polish-notation-calculator

依序將輸入的數字放入堆疊,如果遇到運算符號就計算堆疊最上面的兩個元素

開始

  1. 先假定輸入是”1 1 +”,我們需要先把字串分割
var input = "1 1 +";
var array = input.Split(' ') // 取得["1","1","+"]
  1. 接下來要遍歷一次array,只要遇到數字就放入堆疊,如果是運算符號的話就要進行計算,這邊可以拆成三個子題目

    1. 判斷數字或是運算符號
    2. 數字放入堆疊
    3. 符號進行運算

    判斷是不是數字可以用TryParse,我們要包裝一下方法的回傳值,然後用select將集合Map過去,(bool IsInt, int number, string operator)這個東西其實很類似Either

var input = "1 1 +";
var array = input.Split(' ') // 取得["1","1","+"]
								 .Select(ParseInt);

(bool IsInt, int number, string oper) ParseInt(string s)
{
    var isInt = int.TryParse(s, out var n);
    return (isInt, n, s);
}
  1. 要滿足2-b跟2-c,除了要遍歷以外還要有一個堆疊可以存放值,在Linq裡面的Aggregate符合需求
var input = "1 1 +";
var array = input.Split(' ') // 取得["1","1","+"]
								 .Select(ParseInt);
								 .Aggregate(new Stack<int>(), Compute)
								 .First();

(bool IsInt, int number, string oper) ParseInt(string s)
{
    var isInt = int.TryParse(s, out var n);
    return (isInt, n, s);
}

Stack<int> Compute(Stack<int> stack, (bool IsInt, int number, string oper)  s)
{
		return s switch
		{
				(true, var v, _) => PushIntoStack(stack, v),
				(false, _, var o) => Operate(stack, o),
				_ => throw new InvalidOperationException()
		}
}
  1. Stack的push方法沒有回傳值,這邊要包裝一下,另外Operate方法先實做加法即可
Stack<int> PushIntoStack(Stack<int> s, int v)
{
		s.push(v);
		return s
}

Stack<int> Operate(Stack<int> s, string o)
{
		return o switch
		{
				"+" => PushIntoStack(s, (s.Pop() + s.Pop())),
				_ => throw new InvalidOperationException()
		}	
}

到這邊程式就大致上完成了,接下來只要把其他的運算符號功能也加上去就ok了。

感想

我很難說這樣寫比較好,在C#中有提供Stack型別,不像Haskell,如果不強制要求函數有回傳值的話,其實直接用預設的方法可以更簡單。我們還是可以仔細觀察,不知道有沒有注意到這個設計方式資料與行為分離的特徵,所有的函數都只是在處理資料,然後用pattern match的方式做資料的組合或拆解。最後我自己並不喜歡沒有回傳值的方法,尤其在維護大型程式碼的時候,方法名稱能夠提供的訊息太少,下挖常常會有意想不到的驚嚇。

目前的程式碼還有一些缺陷,就當我在埋梗,之後在講講有什麼不足吧。


上一篇
Day7. Closure
下一篇
Day9. Linq and Immutable
系列文
Functional Programming with C#30
圖片
  直播研討會
圖片
{{ item.channelVendor }} {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言