Python的1001種騷操作——基礎篇(3)
由於暑期放(mo)假(yu)兩星期(不止),拖更也近一個月了。前一篇講到了文字匹配,也簡單實現了KMP文字匹配演算法。那麼這次我們就聊一下如何對文字進行過濾和清洗,
字串
的連線和合並,字串中變數名的插值處理,簡單的文字分詞。4個內容,但是都是一些比較簡單的操作。
*文字過濾以及清洗
眾所周知,資料分析的第一步就是對資料進行過濾和清洗,對原始資料進行合乎自身需要的過濾處理可以減少資料的噪聲(無效資料),提高最終結果的準確程度。那麼我們以字串為例,來簡單介紹一下translate()和Unicode。normalize()這兩種方法。
現在我們有一個混亂的字串
>>>s = “pythöñ\fis\tawesome\r\n”
>>>s
‘pythöñ\
x0cis\tawesome
\r\n’
>>>print(s)
pythöñ is awesome
第一步我們需要清除類似空格和換行符的東西。那麼我們需要建立一個置換表remap,remap是以字典結構表示。
>>>remap = {
ord(‘\t’):‘ ’,
ord(‘\f’):‘ ’,
ord(‘\r’):‘ ’,
ord(‘ñ’):‘n’,
}
>>>a = s。translate(remap)
>>>a
‘pythön is awesome \n’
上例中的ord()函式用來返回對應字元的ascii碼,相反chr()用來返回對應ascii碼的字元(10進位制/
16進位制
)。可見,我們手動定義一個置換表可以完成對字串的過濾,但是遇到大批次的文字資料時,這種方法就不太符合實際了。既然我們輸入的字元都可以轉化為ascii碼,那麼我們就可以建立一個龐大的置換表來去除unicode的組合字元
import
unicodedata
as
uc
import
sys
s
=
‘pythöñ
\x0c
is
\t
awesome
\r\n
’
remap
=
{
ord
(
‘
\t
’
):
‘ ’
,
ord
(
‘
\f
’
):
‘ ’
,
ord
(
‘
\r
’
):
‘ ’
,
ord
(
‘ñ’
):
‘n’
,
}
a
=
s
。
translate
(
remap
)
#騷操作5,建立cmb_code置換表
cmb_code
=
dict
。
fromkeys
(
c
for
c
in
range
(
sys
。
maxunicode
)
if
uc
。
combining
(
chr
(
c
)))
b
=
uc
。
normalize
(
‘NFD’
,
a
)
f
=
b
。
translate
(
cmb_code
)
(
f
)
###結果:python is awesome
在例子中的
unicode。normalize
()是將字串統一按照指定的規範來完成規範表示。第一個引數可以使用NFC、NFD、NFKC、NFKD來指定。其中NFC表示字元應該是全組成的,NFD表示每個字元是能完全分解開的。至於NFC和NFD的區別如下:
對於字元 ö 在NFC中是看做一個獨立的字元,所以它的字元碼是自己的字元碼;而在NFD中,被看做是一個組合字元是o加上面兩個點表示,所以它的字元碼是o自己加上兩個點的字元碼。所以在上慄 f = b。translate(cmb_code)的處理中會將\u0308去除。And dict。fromkeys()可以對字典統一賦值:
>>>
d
=
{
‘a’
:
1
,
‘b’
:
2
}
>>>
s
=
dict
。
fromkeys
(
d
,
0
)
>>>
s
{
‘b’
:
0
,
‘a’
:
0
}
#不允許以下寫法:s = dict。fromkeys(c for c in range(5),0)
SyntaxError
:
Generator
expression
must
be
parenthesized
if
not
sole
argument
,
使用IO編碼解碼函式來清理文字
使用
ascii編碼
解碼僅僅只在需要的最終文字為ascii形式才有用。
#接著使用上例
>>>
f
=
b
。
encode
(
‘ascii’
,
‘ignore’
)
。
decode
(
‘ascii’
)
>>>
(
f
)
python
is
awesome
*字串連線和合並
這部分比較簡單幾句帶過,我們常用的字串連線可以使用加號,join()方法,有時在print()中還可以使用逗號來連線,但是注意在定義字串時不要使用逗號,因為系統會認為你的輸入是元組形式。下面就簡單地講一下join()方法。join()方法可以合併來自不同資料結構的內容:列表,元組,字典,集合,生成器等。因此只要指定想要的分隔符,使用join()方法就可以將文字粘合在一起。
>>>parts = [‘Is’,‘Chicago’,‘Not’,‘NY’]
>>>print(‘ ’。join(parts))
Is Chicago Not NY
>>> def sample():
yield ‘Is’
yield ‘Chicago’
yield ‘Not’
yield ‘NY’
>>>print(‘ ’。join(sample()))
Is Chicago Not NY
哦,還有這種操作,當我們列印多個引數時為了防止分不清楚,我們一般會使用
分隔符
|來區分。
>>>
a
=
‘a’
>>>
s
=
‘s’
>>>
c
=
‘c’
>>>
(
a
,
s
,
c
,
sep
=
‘|’
)
#騷操作6
a
|
s
|
c
*字串變數插值處理
這部分也比較簡單一般對變數可以使用%s\%i\%f等來表示並在字串語句結束使用% 變數值進行賦值。遇到多變數時使用% (var0,var1,var2。。。。。)
>>>a = ‘%i’ % 5
>>>a
5
*簡單的文字分詞------實現一個簡單的遞迴下降解析器V1.0
一般情況下,我們會使用一組語法規則來解析文字,為了可以執行相應的操作或者構建一個抽象語法樹來表示輸入。為了根據特定的語法來解析文字,我們一般會使用BNF或者EBNF來定義出語法的正式規格。下面,我們會構建一個文字表示式計算器
解析器
。
BNF解析過程:
expr
expr ::= term {(+|-) term}*
expr ::= factor{(*|/) factor}* {(+|-) term}*
expr ::= NUM{(*|/) factor}* {(+|-) term}*
expr ::= NUM{(+|-) term}*
expr ::= NUM + term{(+|-) term}*
expr ::= NUM + factor{(*|/) factor}* term{(+|-) term}*
expr ::= NUM + NUM{(*|/) factor}* term{(+|-) term}*}
expr ::= NUM + NUM * factor{(*|/) factor}* {(+|-) term}*
expr ::= NUM + NUM * NUM{(*|/) factor}* {(+|-) term}*
expr ::= NUM + NUM * NUM{(+|-) term}*
expr ::= NUM + NUM * NUM
接著,我們要將上述解析過程轉化成python中的方法
# 語法規則
def
expr
(
self
):
“expr ::= term {(+|-) term}*”
exprval
=
self
。
term
()
while
self
。
_accept
(
‘PLUS’
)
or
self
。
_accept
(
‘MINUS’
):
op
=
self
。
tok
。
type
right
=
self
。
term
()
if
op
==
‘PLUS’
:
exprval
+=
right
elif
op
==
‘MINUS’
:
exprval
-=
right
return
exprval
def
term
(
self
):
“term ::= factor{(*|/) factor}*”
termval
=
self
。
factor
()
while
self
。
_accept
(
‘TIMES’
)
or
self
。
_accept
(
‘DIVIDE’
):
op
=
self
。
tok
。
type
right
=
self
。
factor
()
if
op
==
‘TIMES’
:
termval
*=
right
elif
op
==
‘DIVIDE’
:
termval
/=
right
return
termval
def
factor
(
self
):
“factor ::= NUM | (expr)*”
if
self
。
_accept
(
‘NUM’
):
return
float
(
self
。
tok
。
value
)
elif
self
。
_accept
(
‘LPAREN’
):
exprval
=
self
。
expr
()
self
。
_expect
(
‘RPAREN’
)
return
exprval
else
:
raise
SyntaxError
(
‘Expected NUMBER or LPAREN!’
)
以下是完整程式碼:
import re
import collections
# 提取特徵
NUM = r‘(?P
PLUS = r‘(?P
MINUS = r‘(?P
TIMES = r‘(?P
DIVIDE = r‘(?P
LPAREN = r‘(?P
RPAREN = r‘(?P
WS = r‘(?P
master_pat = re。compile(‘|’。join([NUM,PLUS,MINUS,TIMES,
DIVIDE,LPAREN,RPAREN,WS]))
# 提取器(生成器)
Token = collections。namedtuple(‘Token’,[‘type’,‘value’]) # 命名元組
def generater_tokens(text):
scanner = master_pat。scanner(text)
# 騷操作7:scanner()方法來完成分詞操作,scanner()會建立一個掃描物件,在給定文字中重複呼叫match(),一次匹配一個模式
for m in iter(scanner。match, None):
tok = Token(m。lastgroup, m。group())
if tok。type != ‘WS’:
yield tok
# tok是命名元組形式,返回資料eg: Token(type=‘NAME’,value=‘foo’)
# Parser
class ExpressionEvaluator:
def parse(self,text): # 執行入口
self。tokens = generater_tokens(text) # (tok)
self。tok = None # Last
symbol consumed
(tok)
self。nexttok = None # Next symbol tokenized (tok)
self。_advance() # Loac first lookahead token (tok)
return self。expr()
def _advance(self):
self。tok, self。nexttok = self。nexttok , next(self。tokens,None)
def _accept(self,toktype):
if self。nexttok and self。nexttok。type == toktype:
self。_advance()
return True
else:
return False
# 異常丟擲
def _expect(self,toktype):
if not
self。_accept
(toktype):
raise SyntaxError(‘Expected’ + toktype)
# 語法規則
def expr(self):
exprval = self。term()
while self。_accept(‘PLUS’) or self。_accept(‘MINUS’):
op = self。tok。type
right = self。term()
if op == ‘PLUS’:
exprval += right
elif op == ‘MINUS’:
exprval -= right
return exprval
def term(self):
termval = self。factor()
while self。_accept(‘TIMES’) or self。_accept(‘DIVIDE’):
op = self。tok。type
right = self。factor()
if op == ‘TIMES’:
termval *= right
elif op == ‘DIVIDE’:
termval /= right
return termval
def factor(self):
if self。_accept(‘NUM’):
return float(self。tok。value)
elif self。_accept(‘LPAREN’):
exprval = self。expr()
self。_expect(‘RPAREN’)
return exprval
else:
raise SyntaxError(‘Expected NUMBER or LPAREN!’)
if __name__ == ‘__main__’:
e = ExpressionEvaluator()
ans = e。parse(‘2 + (3+4)*5’)
print(ans)
下一期,我會對這解析器進行一次修改,那麼我們下期再見!~(好敷衍的結尾,反正還有下一篇~)
參考資料
《
Python Cookbook 中文版
》第三版
只求同好,無關浮名
未完待續!