由於暑期放(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

print

f

###結果:python is awesome

在例子中的

unicode。normalize

()是將字串統一按照指定的規範來完成規範表示。第一個引數可以使用NFC、NFD、NFKC、NFKD來指定。其中NFC表示字元應該是全組成的,NFD表示每個字元是能完全分解開的。至於NFC和NFD的區別如下:

Python的1001種騷操作——基礎篇(3)

Python的1001種騷操作——基礎篇(3)

對於字元 ö 在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’

>>>

print

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’

>>>

print

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\d+)’

PLUS = r‘(?P\+)’

MINUS = r‘(?P-)’

TIMES = r‘(?P\*)’

DIVIDE = r‘(?P/)’

LPAREN = r‘(?P\()’

RPAREN = r‘(?P\))’

WS = r‘(?P\s+)’

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 中文版

》第三版

只求同好,無關浮名

未完待續!