[英]How to change this tree-recursion to a tail-recursion?
我正在編寫一個函數ChrNumber
, ChrNumber
字符串轉換為中文金融數字字符串。 我計算出一個樹遞歸形式。 但是,當我試圖讓一個尾遞歸形式,實在是我很難處理的情況bit
等於6,7或8或10,更大的。
您可以在我的問題結尾處看到它的工作原理。
這是樹遞歸解決方案。 有用:
# -*- coding:utf-8 -*-
unitArab=(2,3,4,5,9)
#unitStr=u'十百千萬億' #this is an alternative
unitStr=u'拾佰仟萬億'
unitDic=dict(zip(unitArab,(list(unitStr))))
numArab=list(u'0123456789')
#numStr=u'零一二三四五六七八九' #this is an alternative
numStr=u'零壹貳叄肆伍陸柒捌玖'
numDic=dict(zip(numArab,list(numStr)))
def ChnNumber(s):
def wrapper(v):
'this is to adapt the string to a abbreviation'
if u'零零' in v:
return wrapper(v.replace(u'零零',u'零'))
return v[:-1] if v[-1]==u'零' else v
def recur(s,bit):
'receives the number sting and its length'
if bit==1:
return numDic[s]
if s[0]==u'0':
return wrapper(u'%s%s' % (u'零',recur(s[1:],bit-1)))
if bit<6 or bit==9:
return wrapper(u'%s%s%s' % (numDic[s[0]],unitDic[bit],recur(s[1:],bit-1)))
'below is the hard part to be converted to tail-recurion'
if bit<9:
return u'%s%s%s' % (recur(s[:-4],bit-4),u"萬",recur(s[-4:],4))
if bit>9:
return u'%s%s%s' % (recur(s[:-8],bit-8),u"億",recur(s[-8:],8))
return recur(s,len(s))
我的嘗試版本僅在recur
函數中,我使用閉包res
並在recur
移動該bit
,以便減少參數。
res=[]
def recur(s):
bit=len(s)
print s,bit,res
if bit==0:
return ''.join(res)
if bit==1:
res.append(numDic[s])
return recur(s[1:])
if s[0]==u'0':
res.append(u'零')
return recur(s[1:])
if bit<6 or bit==9:
res.append(u'%s%s' %(numDic[s[0]],unitDic[bit]))
return recur(s[1:])
if bit<9:
#...can't work it out
if bit>9:
#...can't work it out
測試代碼是:
for i in range(17):
v1='9'+'0'*(i+1)
v2='9'+'0'*i+'9'
v3='1'*(i+2)
print '%s->%s\n%s->%s\n%s->%s'% (v1,ChnNumber(v1),v2,ChnNumber(v2),v3,ChnNumber(v3))
應該輸出:
>>>
90->玖拾
99->玖拾玖
11->壹拾壹
900->玖佰
909->玖佰零玖
111->壹佰壹拾壹
9000->玖仟
9009->玖仟零玖
1111->壹仟壹佰壹拾壹
90000->玖萬
90009->玖萬零玖
11111->壹萬壹仟壹佰壹拾壹
900000->玖拾萬
900009->玖拾萬零玖
111111->壹拾壹萬壹仟壹佰壹拾壹
9000000->玖佰萬
9000009->玖佰萬零玖
1111111->壹佰壹拾壹萬壹仟壹佰壹拾壹
90000000->玖仟萬
90000009->玖仟萬零玖
11111111->壹仟壹佰壹拾壹萬壹仟壹佰壹拾壹
900000000->玖億
900000009->玖億零玖
111111111->壹億壹仟壹佰壹拾壹萬壹仟壹佰壹拾壹
9000000000->玖拾億
9000000009->玖拾億零玖
1111111111->壹拾壹億壹仟壹佰壹拾壹萬壹仟壹佰壹拾壹
90000000000->玖佰億
90000000009->玖佰億零玖
11111111111->壹佰壹拾壹億壹仟壹佰壹拾壹萬壹仟壹佰壹拾壹
900000000000->玖仟億
900000000009->玖仟億零玖
111111111111->壹仟壹佰壹拾壹億壹仟壹佰壹拾壹萬壹仟壹佰壹拾壹
9000000000000->玖萬億
9000000000009->玖萬億零玖
1111111111111->壹萬壹仟壹佰壹拾壹億壹仟壹佰壹拾壹萬壹仟壹佰壹拾壹
90000000000000->玖拾萬億
90000000000009->玖拾萬億零玖
11111111111111->壹拾壹萬壹仟壹佰壹拾壹億壹仟壹佰壹拾壹萬壹仟壹佰壹拾壹
900000000000000->玖佰萬億
900000000000009->玖佰萬億零玖
111111111111111->壹佰壹拾壹萬壹仟壹佰壹拾壹億壹仟壹佰壹拾壹萬壹仟壹佰壹拾壹
9000000000000000->玖仟萬億
9000000000000009->玖仟萬億零玖
1111111111111111->壹仟壹佰壹拾壹萬壹仟壹佰壹拾壹億壹仟壹佰壹拾壹萬壹仟壹佰壹拾壹
90000000000000000->玖億億
90000000000000009->玖億億零玖
11111111111111111->壹億壹仟壹佰壹拾壹萬壹仟壹佰壹拾壹億壹仟壹佰壹拾壹萬壹仟壹佰壹拾壹
900000000000000000->玖拾億億
900000000000000009->玖拾億億零玖
111111111111111111->壹拾壹億壹仟壹佰壹拾壹萬壹仟壹佰壹拾壹億壹仟壹佰壹拾壹萬壹仟壹佰壹拾壹
Python不支持消除尾部調用也不支持尾部調用優化。 但是,可以通過多種方法來模仿這種方法(蹦床是其他語言中使用最廣泛的方法。)
尾調用遞歸函數應類似於以下偽代碼:
def tail_call(*args, acc):
if condition(*args):
return acc
else:
# Operations happen here, producing new_args and new_acc
return tail_call(*new_args, new_acc)
在您的示例中,由於您正在引入副作用和有狀態的操作,因此我不會對任何事物進行封閉。 相反,任何需要修改的內容都應孤立於其他所有內容進行修改。 這使得更容易推理。
復制您要更改的任何內容(使用string.copy
作為最終輸出),並將其作為參數傳遞給下一個遞歸調用。 這就是acc
變量起作用的地方。 到目前為止,它正在“累積”所有更改。
從這個片段中可以得到一個經典的蹦床。 在那里,它們將函數包裝在一個對象中,該對象最終將導致結果或返回另一個應調用的函數對象。 我更喜歡這種方法,因為我發現它更容易推理。
這不是唯一的方法。 看一下這個代碼片段 。 當達到“解決”條件的點並引發異常以逃避無限循環時,就會發生“魔術”。
這些天,我一直在研究這個問題。 現在, 我解決了!
注意,不僅僅是尾遞歸, 它也是純函數式編程!
關鍵是要以不同的方式思考 (樹遞歸版本是從左到右處理數字,而此版本是從右到左)
unitDic=dict(zip(range(8),u'拾佰仟萬拾佰仟億'))
numDic=dict(zip('0123456789',u'零壹貳叄肆伍陸柒捌玖'))
wapDic=[(u'零拾',u'零'),(u'零佰',u'零'),(u'零仟',u'零'),
(u'零萬',u'萬'),(u'零億',u'億'),(u'億萬',u'億'),
(u'零零',u'零'),]
#pure FP
def ChnNumber(s):
def wrapper(s,wd=wapDic):
def rep(s,k,v):
if k in s:
return rep(s.replace(k,v),k,v)
return s
if not wd:
return s
return wrapper(rep(s,*wd[0]),wd[1:])
def recur(s,acc='',ind=0):
if s=='':
return acc
return recur(s[:-1],numDic[s[-1]]+unitDic[ind%8]+acc,ind+1)
def end(s):
if s[-1]!='0':
return numDic[s[-1]]
return ''
def result(start,end):
if end=='' and start[-1]==u'零':
return start[:-1]
return start+end
return result(wrapper(recur(s[:-1])),end(s))
for i in range(18):
v1='9'+'0'*(i+1)
v2='9'+'0'*i+'9'
v3='1'*(i+2)
print ('%s->%s\n%s->%s\n%s->%s'% (v1,ChnNumber(v1),v2,ChnNumber(v2),v3,ChnNumber(v3)))
如果有人說當面對大量數字(大約十億數字)時它不起作用,是的,我承認,但是這個版本可以解決它(雖然它不是純FP,但純FP可以解決)不需要這個版本..):
class TailCaller(object) :
def __init__(self, f) :
self.f = f
def __call__(self, *args, **kwargs) :
ret = self.f(*args, **kwargs)
while type(ret) is TailCall :
ret = ret.handle()
return ret
class TailCall(object) :
def __init__(self, call, *args, **kwargs) :
self.call = call
self.args = args
self.kwargs = kwargs
def handle(self) :
if type(self.call) is TailCaller :
return self.call.f(*self.args, **self.kwargs)
else :
return self.f(*self.args, **self.kwargs)
def ChnNumber(s):
def wrapper(s,wd=wapDic):
@TailCaller
def rep(s,k,v):
if k in s:
return TailCall(rep,s.replace(k,v),k,v)
return s
if not wd:
return s
return wrapper(rep(s,*wd[0]),wd[1:])
@TailCaller
def recur(s,acc='',ind=0):
if s=='':
return acc
return TailCall(recur,s[:-1],numDic[s[-1]]+unitDic[ind%8]+acc,ind+1)
def end(s):
if s[-1]!='0':
return numDic[s[-1]]
return ''
def result(start,end):
if end=='' and start[-1]==u'零':
return start[:-1]
return start+end
return result(wrapper(recur(s[:-1])),end(s))
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.