命令式至函數式隨記(四)

English

在命令式至函數式隨記(三)中最後談到foldRight,本來想說有機會再談,不過剛好有個範例,想說就先隨便記好了,有這之前,建議先看看:

 前幾篇都用Python作示範,那這邊就繼續用它作示範好了,首先想想Python中兩個list的串接怎麼作?用+!像是[1, 2, 3] + [4, 5, 6]如果要在一個list前放個元素呢?沒辦法用1 + [1, 2, 3],這邊考慮的是immutable資料,所以也不打算用list的insert函式,所以重新來想一下好了!list是什麼結構?

Python中是有個類似immutable list的東西,就是tuple,那就用來來構造immutable list好了,首先空的immutable list為(),定義有元素的immutable list為首元素與剩餘immutable list組成,所以擁有元素1的immutable list為(1, ()),擁有元素1、2的immutable list為(1, (2, ())),擁有元素1、2、3的immutable list為(1, (2, (3, ()))),依此類推。依此結構,如果要在immutable list前放個元素,可定義cons來作:
def cons(elem, lt):
    return (elem, lt)

為了方便,定義一個lst函式來構造immutable list:
def lst(*elems):
    return () if elems == () else cons(elems[0], lst(*elems[1:]))


如此,若要構造擁有元素1、2、3的immutable list,就可以用lst(1, 2, 3)來取得。回到剛剛那個問題,在immutable list前放個元素,是已經定義cons來完成了,那麼要進行兩個immutable list的串接呢?Python的tuple是有+可以用,不過不能這麼作lst(1, 2, 3) + lst(4, 5, 6),因為這樣會得到(1, (2, (3, ())), 4, (5, (6, ()))),這不是先前定義的immutable list結構,現在想要得到的結果應該是(1, (2, (3, (4, (5, (6, ()))))))。針對immutable list的結構,必須定義新的串接函式:
def concat(lt1, lt2):
    return lt2 if lt1 == () else cons(lt1[0], concat(lt1[1], lt2))

如此concat(lst(1, 2, 3), lst(4, 5, 6))得到的就是(1, (2, (3, (4, (5, (6, ()))))))。好吧!(1, (2, (3, (4, (5, (6, ()))))))是有點難看啦!所以寫個toStr好了:
def toStr(lt):
    return '()' if lt == () else str(lt[0]) + ':' + toStr(lt[1])

這樣的話!toStr(lst(1, 2, 3, 4, 5, 6))就會傳回1:2:3:4:5:6:(),用「:」表示是函數式語言中的慣例,這也就是為何在immutable list前置一個元素要叫cons而不叫prepend的原因。願意的話,也可以定義在immutable list附加一個元素的append函式:
def append(l, elem):
    return concat(l, lst(elem))

那麼重點在哪?對以上定義的immutable list來說,在immutable list前置一個元素很輕鬆,但兩個immutable list要concat,或在一個immutable list附加元素,那必須走訪完第一個list作作得到。

如果針對這邊設計的immutable list設計一個foldLeft:
def foldLeft(lt, func, at):
    return at if lt == () else foldLeft(lt[1], func, func(at, lt[0]))

如果要把lst(1, 2, 3)轉為lst('1', '2', '3')就要這麼作:
foldLeft(lst(1, 2, 3), lambda at, digit:  concat (at, lst(str(digit))), ())

或者是這麼作:
foldLeft(lst(1, 2, 3), lambda at, digit:  append (at, str(digit)), ())

然而append中還是用到了concat,也就是說,對於這個需求,如果用foldLeft,每次都走訪傳入的at是一定要的,如果寫一個 foldRight:
 def foldRight(lt, func, at):
     return at if lt == () else func(foldRight(lt[1], func, at), lt[0])

那麼要把lst(1, 2, 3)轉為lst('1', '2', '3')就可以這麼作:
foldRight(lst(1, 2, 3), lambda at, digit:  cons (str(digit), at), ())

就這個例子來說,不用每次走訪傳入的的at,效能會比較好。

對於走訪左結合與右結合沒差的需求來說,使用foldLeft或foldRight都可以,像是foldRight(lst(1, 2, 3), int.__add__, 0)與foldLeft(lst(1, 2, 3), int.__add__, 0)是沒差的,但對上面lst(1, 2, 3)轉為lst('1', '2', '3')就會有差別。

除了考量foldLeft或foldRight之外,在具有以上結構的語言中,使用list串接時可以多點考量,看能不能改用cons操作。來看個 Scala的例子:
def eval(expr: String) = {
    ((Nil:List[Double]) /: toPostfix(expr)) {
        (stack, c) => {
            if("+-*/".contains(c)) {
                val op1 = stack.init.last
                val op2 = stack.last
                stack.init.init ++ List(c match {
                    case '+' => op1 + op2
                    case '-' => op1 - op2
                    case '*' => op1 * op2
                    case '/' => op1 / op2
                })
            } else stack ++ List(c.toString.toDouble)
        }
    }.last
}

這邊使用到++來串接list,這會造成每次走訪stack,如果stack很長效能就不是很好,可以改為:
def eval(expr: String) = {
    ((Nil:List[Double]) /: Infix.toPostfix(expr)) {
        (stack, c) => {
            if("+-*/".contains(c)) {
                val op1 = stack.tail.head
                val op2 = stack.head
                (c match {
                    case '+' => op1 + op2
                    case '-' => op1 - op2
                    case '*' => op1 * op2
                    case '/' => op1 / op2
                }) :: stack.tail.tail
            } else c.toString.toDouble :: stack
        }
    }.head
}

結果會相同,但這次不用每次都走訪stack,效能會好一些。
  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值