Pulpcode

捕获,搅碎,拼接,吞咽

0%

使用C风格的python代码对齐文本列

还是对这样的文本进行列对齐的实现

     apple  pear banana      orange
 dog   cat  rabbit  monkey bunny   cow
   sky moon      grubby     tod fly100%

之前的实现过这个功能 使用python对齐文本列,基本上是函数式的编程风格。

而且那段代码有一个bug,就是不能处理每行单词数目不同的文本。
我不想在之前的那段代码上修复这个bug了,我想换另一种思路。

这种思路是一种古老的c语言的思路,那就是读字符,判断,切换状态
而且我的代码也基本上是C风格的python代码(- -!),看上去怪怪的。

当然,很多地方我是”投机取巧“的,借用了python方便的地方,并非纯C风格。(因为那样太麻烦了)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
# -*- coding:utf-8 -*-

lines = []
rows = 0

with open('data') as f:
lines = [line for line in f]
rows = len(lines)

# 状态
START = 0
READBLANK = 1
FINDWORD = 2
READWORD = 3
READWORDFINISH = 4
DEAD = 5

BLANKCHAR = ' '
ENDCHAR = '\n'

# 初始游标值
INITVERNIER = 0

def logstatus(s):
'''
用来调试查看status的函数
'''
d = {0:"START",
1:"READBLANK",
2:"READWORD",
3:"READWORDFINISH",
4:"DEAD",
5:"FINDWORD"}
return d[s]

def logstringbuffers(s):
'''
用来调试查看stringbuffers的函数
'''
for i in s:
print ''.join(i)

# 每一行的状态
status = [START for i in xrange(rows)]
# 每一行的游标
vernier = [INITVERNIER for i in xrange(rows)]
# 每一行准备字符串缓冲区
stringbuffers = [[] for i in xrange(rows)]

def funcall(f,seq):
for i in seq:
if not f(i):
return False
return True

while True:
for i, line in enumerate(lines):
# 根据条件切换状态
if status[i] == DEAD:
continue
elif status[i] == START:
if line[vernier[i]] == BLANKCHAR:
status[i] = READBLANK
else:
status[i] = READWORD
elif status[i] == READBLANK and line[vernier[i]] != BLANKCHAR:
status[i] = FINDWORD
elif status[i] == READWORD :
if line[vernier[i]] == BLANKCHAR:
status[i] = READWORDFINISH
elif line[vernier[i]] == ENDCHAR:
status[i] = DEAD
# 读取状态向缓冲区发送普通字符
if status[i] == READWORD:
stringbuffers[i].append(line[vernier[i]])
# 等待状态向缓冲区发送补齐字符(空白符)
elif status[i] == READWORDFINISH:
stringbuffers[i].append(BLANKCHAR)
# 读取单词和读取空白符时候将移动游标
if status[i] == READWORD or status[i] == READBLANK:
vernier[i] += 1

# 如果所有的都是 DEAD,那么break
if funcall(lambda x: x == DEAD, status):
break
# 都读取完字符,就可以开始下一轮“杀”空字符
if funcall(lambda x: x == READWORDFINISH or x == DEAD, status):
for i,s in enumerate(status):
if s == READWORDFINISH:
status[i] = READBLANK
# 都准备好了,就可以开始下一轮读取单词
if funcall(lambda x: x == FINDWORD or x == DEAD, status):
for i,s in enumerate(status):
if s == FINDWORD:
status[i] = READWORD

# 打印字符串缓冲区
logstringbuffers(stringbuffers)

状态机变换

| ... ... tornado ... ...|
S R       F R   R R      D
T E       I E   E E      E
A A       N A   A A      A
R D       D D   D D      D
T B       W W   W B
  L       O O   O L
  A       R R   R A
  N       D D   D N
  K             F K
                I
                N
                I
                S
                H

代码说明:

首先每一行都有一个游标,用来记录程序的逻辑读取到第几个字符了。

每一行会根据自己的游标和其它行的游标改变自己的状态,而这些状态又会相应的执行动作。


比如每一行的初始状态都是START,然后程序会判断当前游标下的字符,如果是空白符,那么将进入READBLANK状态,表示它要开始读取空白符。
而如果是一个非空白符,那么状态将变为FINDWORD,表明它发现了一个单词,准备开始读取单词。

那些FINDWORD的状态会一直等待(不再移动游标),直到其它行的READBLANK状态都变为FINDWORD状态,这时它们就可以开始读入自己的第一个单词(进入READWORD),并将单词的每一个字符送到各自行的字符串缓冲区(stringbuffer)中。
接下来,有些行会因为读到空白符而变为READWORDFINISH状态,表示自己已经读完了一个单词,但因为其它的行还在READWORD状态,所以它不能闲着,而是向缓冲区发送空白符,(这就是在做对齐操作)。
直到所有行都变为READWORDFINISH状态,表示它们都读完了一个单词,这样程序就会将它们都变为READBLANK状态,表示下一轮读取字符串又将开始。


所以你应该已经发现,FINDWORD状态和READWORDFINISH状态,作为“等待状态”是不移动游标的。

还需要注意的是,如果某一行游标读到了换行符,那么这一行就会进入DEAD状态,表示自己不会再进行逻辑判了。

所以当所有行的状态都变为DEAD时,外层循环就会退出了。