题目如下: 解析一个文件,
文件格式是一行一行,
每行有字段,字段可以加引号。
字段里可以套字段。
如果被嵌套的字段有引号,引号要变成双重的,表示转义,
比如 “hello”,要变成 “…““hello””….”
2,John,45,"足球,摄影",New York
3,Carter Job,33,"""健身"",远足","河北,石家庄"
4,Steve,33,"大屏幕164""","DC""Home"""
5,"Jul,y","",,Canada
John,33,"足球,摄影",New York
John,33,"足球,""摄影",New York
要解析成如下
2 | John | 45 | 足球,摄影 | New York
3 | Carter Job | 33 | "健身",远足 | 河北,石家庄
4 | Steve | 33 | 大屏幕164 | DC"Home"
5 | Jul,y | | | Canada
John | 33 | 足球,摄影 | New York
John | 33 | 足球,摄影 | New York
这个题目没啥现实意义。
但是解答的时候却很有启发。
看懂了题目后,第一时间我想到的是栈,
因为嵌套结构,那么标识符号一定是成对的,所以push/pop刚好可以解决。
管他套几层括号转义,那还不等价于一个括号对嘛。
结果if else写了一堆,
然后我意识到一个问题,解析的时候应该分成几层状态,
一层是没引号的普通字段,一层是进入括号了,最后一层是进入嵌套括号了。
写完后忽然意识到,这是个自动机,以前写算法从没写过这种。
但是以前实现HTTP协议解析,也是这么搞的,
nats源码里,基于CRLF的状态解析,也是这么搞的。
其间,第一种解法,写到一半,20分钟已经过去了,可能好久没写算法了,也可能是水平不行。
一个教训就是,通常想到一个解法,即使写到一般,发现这样不够简单优雅,也要写下去。
不然时间不一定够。 这时候新解法的很多细节其实还没想清楚。
以下是解答:
package main
import (
"bufio"
"fmt"
"io"
"os"
"strings"
)
const (
CR = "\r"
LF = "\n"
)
func main() {
src, err := os.Open("src.txt")
if err != nil {
panic(err)
}
defer src.Close()
dst, err := os.Create("dst.txt")
if err != nil {
panic(err)
}
defer dst.Close()
parseFile(src, dst)
}
func parseFile(r io.Reader, w io.Writer) {
scanner := bufio.NewScanner(r)
for scanner.Scan() {
fields := parseLine(scanner.Text())
resu := strings.Join(fields, CR)
fmt.Println(strings.Join(fields, " | "))
w.Write([]byte(resu))
w.Write([]byte(LF))
}
if err := scanner.Err(); err != nil {
fmt.Fprintln(os.Stderr, "读取错误:", err)
}
}
func parseLine(s string) []string {
// 输出结果
rsu := []string{}
// field的头指针。
start := 0
// 当前解析的状态
// -1 表示没有开始解析,或者解析结束了。
// 0 表示 这个 field 没有 `"` 开头
// 1 表示 这个 field 有 `"` 开头
// 2 表示 这个 field 处在 子field中, 类似这种: `"abc""dd`
status := -1 // 0 没有"开头, 1,有"开头但是不在字段中, 2. 在子field中
// 可以用 go 里的蹩脚 枚举,哈哈哈
for i := 0; i < len(s); i++ {
switch status {
case -1:
if i == start {
if s[i] == '"' {
start = i + 1
status = 1
} else if s[i] == ',' {
// 防止空字段。
status = -1
start = i + 1
} else {
status = 0
}
rsu = append(rsu, "")
}
case 0:
// ,xxx,
if s[i] == ',' {
rsu[len(rsu)-1] += s[start:i]
start = i + 1
status = -1
continue
}
if i == len(s)-1 {
rsu[len(rsu)-1] += s[start:]
}
if s[i] == '"' {
// panic("文件错误")
}
case 1:
// "xxxx"
if s[i] == '"' {
// "xxxx""
if i+1 < len(s) && s[i+1] == '"' {
status = 2
}
// "xxxx",
if (i+1 < len(s) && s[i+1] == ',') || i == len(s)-1 {
status = -1
}
rsu[len(rsu)-1] += s[start:i]
// 不管是 `",` 还是 `""` 都跳过
start = i + 2
i++
}
case 2:
// "xx""xxx""
if s[i] == '"' {
if i+1 < len(s) && s[i+1] == '"' {
status = 1
rsu[len(rsu)-1] += s[start-1 : i+1]
}
// "xx""xxx",
if (i+1 < len(s) && s[i+1] == ',') || i == len(s)-1 {
status = -1
rsu[len(rsu)-1] += s[start:i]
}
start = i + 2
i++
}
}
}
return rsu
}