学习笔记

Posted FserSuN

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了学习笔记相关的知识,希望对你有一定的参考价值。

第8章介绍了四个例子,讲述了Antlr了实际应用。下面的阅读笔记中,最终实现与书中并非完全一致。其中调用关系仅输出关系,而未转换为Dot语言。

加载CSV数据

CSV是逗号分隔值的缩写,其形式为。

Details,Month,Amount
Mid Bonus,June,"$2,000"
,January,"""zippo"""
Total Bonuses,"","$5,000"

接下来将从CSV文件中读取每行数据,然后将读取的数据存储于Map中。最终将读取的数据打印,输出的形式如下。

[{Details=Mid Bonus, Month=June, Amount="$2,000"},
{Details=, Month=January, Amount="""zippo"""},
{Details=Total Bonuses, Month="", Amount="$5,000"}]

首先写出CSV的文法。

grammar csv;
file : hdr row+;
hdr : row;
row : field (',' field)*'\\r'?'\\n';
field 
	: TEXT   # text
	| STRING # string
	|        # empty
	;
TEXT : ~[,\\n\\r"]+;
STRING : '"' ('""' | ~'"')*  '"';

hdr定义了文件头,在示例文件中是Details,Month,Amount且只出现一次。而普通的行则可以出现多次。

为了进行更准确的分析,文法中使用了符号#,为每条规则增加标签,最终在所生成的代码中包含处理响应规则的函数。

最终实现的思路是,每当访问一行时创建一个列表用来存放每行中的数据项。当访问结束时,将头和数据行拼接成(title,value)对,然后放入map,最后将map放入List,List为Map的集合。最后完整的代码如下。

public class Loader extends csvBaseListener{
	private List<Map<String,String>> rows =  new ArrayList<Map<String, String>>();
	private List<String> header;
	private List<String> currentRowFieldValues;
	private final String EMPTY = "";  
	public List<Map<String,String>> getRows(){
		return rows;
	}
	
	@Override
	public void exitEmpty(EmptyContext ctx) {
		currentRowFieldValues.add(EMPTY);
	}

	// Hdr访问结束将数据存储于header中  
	@Override
	public void exitHdr(HdrContext ctx) {
		header = new ArrayList<String>();
		header.addAll(currentRowFieldValues);
	}

	@Override
	public void exitText(TextContext ctx) {
		currentRowFieldValues.add(ctx.TEXT().getText());
	}

	// 为每个Row创建一个列表存储CSV文件中的数据
	@Override
	public void enterRow(RowContext ctx) {
		currentRowFieldValues = new ArrayList<String>();
	}

	@Override
	public void exitRow(RowContext ctx) {
		// 如果当前行不是文件头,则将内数据转换为<标题,数据> 并存于Map中
		if(ctx.getParent().getRuleIndex() == csvParser.RULE_hdr){
			return;
		}
		Map<String,String> m = new LinkedHashMap<String, String>();
		int i = 0;
		for(String v : currentRowFieldValues){
			m.put(header.get(i), v);
			i++;
		}
		rows.add(m);
	}
	// 当访问完终结点则将结点文本添加到本行所关联的列表中
	@Override
	public void exitString(StringContext ctx) {
		currentRowFieldValues.add(ctx.STRING().getText());
	}
}

public class Main {

	public static void main(String[] args) throws IOException {
		File csvFile = new File("D:\\\\csv.csv");
		InputStream fi = new FileInputStream(csvFile);
		ANTLRInputStream inputStream = new ANTLRInputStream(fi);
		csvLexer lexer = new csvLexer(inputStream);
		CommonTokenStream tokenStream = new CommonTokenStream(lexer);
		csvParser parser = new csvParser(tokenStream);
		ParseTreeWalker walker = new ParseTreeWalker();
		Loader loader = new Loader();
		walker.walk(loader,parser.file());
		List<Map<String,String>> rows = loader.getRows();
		for(Map<String,String> map : rows){
			System.out.println(map);
		}
	}
}

csv.csv数据为
Details,Month,Amount
Mid Bonus,June,"$2,000"
,January,"""zippo"""
Total Bonuses,"","$5,000"
test,,sun

程序打印结果
{Details=Mid Bonus, Month=June, Amount="$2,000"}
{Details=, Month=January, Amount="""zippo"""}
{Details=Total Bonuses, Month="", Amount="$5,000"}
{Details=test, Month=, Amount=sun}

将JSON转换为XML

将json转换为xml,就是将json中key,value对以xml标签的形式表示。例如{x:v}表示为<x>v</x>,而数组元素则以<element>数组值</element>的形式表示。在上面两种情况的基础上嵌套即可。

在程序中实现时,当某个json元素访问结束生成对应的xml即可。完整的演示代码如下。

public class XMLEmitter extends JSONBaseListener{
	public ParseTreeProperty<String> xml = new ParseTreeProperty<String>();
	String getXML(ParseTree ctx){
		return xml.get(ctx);
	}
	
	void setXML(ParseTree ctx,String s){
		xml.put(ctx, s);
	}

	@Override
	public void exitAtom(AtomContext ctx) {
		setXML(ctx, ctx.getText());
	}
	
	@Override
	public void exitArrayValue(ArrayValueContext ctx) {
		setXML(ctx,getXML(ctx.array()));
	}
	
	@Override
	public void exitString(StringContext ctx) {
		setXML(ctx,ctx.getText().replaceAll("\\"", ""));
	}

	@Override
	public void exitObjectValue(ObjectValueContext ctx) {
		setXML(ctx,getXML(ctx.object()));
	}

	@Override
	public void exitPair(PairContext ctx) {
		String tag = ctx.STRING().getText().replace("\\"", "");
		ValueContext vctx = ctx.value();
		String x = String.format("<%s>%s<%s>\\n",tag,getXML(vctx),tag);
		setXML(ctx,x);
	}

	@Override
	public void exitAnObject(AnObjectContext ctx) {
		StringBuilder buf = new StringBuilder();
		buf.append("\\n");
		for(PairContext pctx : ctx.pair()){
			buf.append(getXML(pctx));
		}
		setXML(ctx,buf.toString());
	}

	@Override
	public void exitEmptyObject(EmptyObjectContext ctx) {
		setXML(ctx,"");
	}

	@Override
	public void exitArrayOfValues(ArrayOfValuesContext ctx) {
		StringBuilder buf = new StringBuilder();
		buf.append("\\n");
		for(ValueContext vctx : ctx.value()){
			buf.append("<element>")
			   .append(getXML(vctx))
			   .append("<element>")
			   .append("\\n");
		}
		setXML(ctx,buf.toString());
	}

	@Override
	public void exitEmptyArray(EmptyArrayContext ctx) {
		setXML(ctx,"");
	}

	@Override
	public void exitJson(JsonContext ctx) {
		setXML(ctx,getXML(ctx.getChild(0)));
	}
}
public class Main {

	public static void main(String[] args) throws IOException {
		File csvFile = new File("D:\\\\csv.txt");
		InputStream fi = new FileInputStream(csvFile);
		ANTLRInputStream inputStream = new ANTLRInputStream(fi);
		JSONLexer lexer = new JSONLexer(inputStream);
		CommonTokenStream tokenStream = new CommonTokenStream(lexer);
		JSONParser parser = new JSONParser(tokenStream);
		ParseTreeWalker walker = new ParseTreeWalker();
		XMLEmitter xml = new XMLEmitter();
		ParseTree json = parser.json();
		walker.walk(xml,json);
		System.out.println(xml.xml.get(json));
	}
}

输入:
{"a":[1.234,"abcd",{"key":"value"}],"key2":"value2"}

输出:
<a>
<element>1.234<element>
<element>abcd<element>
<element>
<key>value<key>
<element>
<a>
<key2>value2<key2>

JSON转换为XML总结

通过json转xml的例子也可以将相关方法应用到其它格式转换。通过这个例子总结以下转换方式:

  • 转换都是发生在exit方法上,即一个文法符号访问完毕后处理。
  • 处理过程是递归的进行,最终获取转换后的数据。在处理一个节点的时候将通过map存储。父节点通过map获取子节点的结果。最终通过根拿到最终结果。

在处理其它转换时应用此方式即可完成转换。

生成调用关系

这里调用关系是指Cymbol语言(书中介绍为C的一个子集)中的调用关系,例如下面的程序片段。

int main() { fact(); a(); }
float fact(int n) {
print(n);
if ( n==0 ) then return 1;
return n * fact(n-1);
}

从上面的程序可以看出,调用语句是写在函数声明的内部。例如int main(){}内部的fact(),a()。因此生成调用关系的思路如下:

  1. 编写Cymbol语言文法,并生成相应的XXListener。
  2. 在访问到函数声明时,记录当前函数名。
  3. 在访问到函数声明内部的调用语句时,记录调用关系。(当前函数名,调用语句)构成一对调用关系。

最后完成的文法与程序代码如下。

grammar Cymbol;  
file : (functionDecl | varDecal)+;  
varDecal : type ID ('=' expr)? ';';  
  
functionDecl : type ID '(' formalParameters? ')' block;  
  
formalParameters : formalParameter (',' formalParameter)*;  
formalParameter : type ID;  
type : 'float' | 'int' | 'void';  
  
block : '{' stat* '}';  
stat : block  
     | varDecal  
     | 'if' expr 'then' stat ('else' stat)?  
     | 'return' expr? ';'  
     | expr '=' expr ';'  
     | expr ';'  
     ;  
  
expr : ID '(' exprList? ')'  # Call
     | expr '[' expr ']'     # Index
     | '-'expr               # Negate
     | '!'expr               # Not
     | expr '*' expr         # Mult
     | expr ('+'|'-') expr   # AddSub
     | expr '==' expr        # Equal
     | ID                    # var
     | INT                   # Int
     | '(' expr ')'          # Parens
     ;  
       
exprList :  expr (',' expr)*;  
   
ID : [a-zA-Z]+;  
INT : '0' | [1-9][0-9]*;  
WS : [ \\t\\n\\r]+ -> skip;  

程序代码。

public class FunctionListener extends CymbolBaseListener{
	Graph graph = new Graph();
	String currentFunctionName = null;
	
	public void printGraph(){
		MultiMap<String,String> mMap = graph.edges;
		List<Pair<String, String>> pairs = mMap.getPairs();
		System.out.println("=====================");
		for(Pair<String, String> pair : pairs){
			System.out.println(pair.a + "->" + pair.b);
		}
	}
	
	@Override
	public void enterFunctionDecl(FunctionDeclContext ctx) {
		// 进入函数语法子树时,获取函数名。
		currentFunctionName = ctx.ID().getText();
		graph.nodes.add(currentFunctionName);
	}

	@Override
	public void exitCall(CallContext ctx) {
		// 当调用语句子树访问结束
		String funcName = ctx.ID().getText();
		graph.edge(currentFunctionName,funcName);
	}

	private static class Graph{ 
		Set<String> nodes = new OrderedHashSet<String>();
		MultiMap<String,String> edges = 
				new MultiMap<String,String>();
		public void edge(String source,String target){
			edges.map(source, target);
		}
	}
}

public class CallRelation {
	public static void main(String[] args) throws IOException {
		InputStream cymbol = new FileInputStream(new File("D:\\\\antlrInput\\\\cymbol.txt"));
		ANTLRInputStream aInputStream = new ANTLRInputStream(cymbol);
		CymbolLexer lexer = new CymbolLexer(aInputStream);
		CommonTokenStream cts = new CommonTokenStream(lexer);
		CymbolParser parser = new CymbolParser(cts);
		ParseTreeWalker walker = new ParseTreeWalker();
		FunctionListener collector = new FunctionListener();
		walker.walk(collector, parser.file());
		collector.printGraph();
	}
}

输入文件:
int main() 
{ 
	a();
	fact();
	b();
}
float fact(int n) 
{
	print(n);
	if ( n==0 ) then return 1;
	return n * fact(n-1);
}


输出结果
main->a
main->fact
main->b
fact->print
fact->fact
输出结果中 a->b 的这种形式表示a调用b。

验证程序中符号的使用

本节对Cymbol语言中使用的符号进行验证,验证包括下面几个方面。

  1. 在作用域内所引用的变量有响应的定义。
  2. 所引用的函数有相应的定义。
  3. 变量不能被当作函数来使用。
  4. 函数不能被当作变量来使用。

对于下面的例子,经检查后有些语句不满足1-4点中的某些要求。

int f(int x, float y) {
g();   // forward reference is ok
i = 3; // 没有i的声明(错误)
g = 4; // g不是变量 (错误)
return x + y; 
}
void g() {
int x = 0;
float y;
y = 9; 
f();  
z(); // 函数z不存在(错误)
y(); // 函数y不存在(错误)
x = f; // f是函数,但赋给整型变量 (错误)
}

为了完成上述的验证任务,需要用到符号表。通过符号表来检查是否正确使用了所引用的符号。

符号表用来保存程序语言中所使用的符号,并根据符号所在的作用域将不同符号组织在一起。符号表的两种基本操作是定义符号与解析符号。定义符号意味着将符号添加到作用域中。解析符号是根据名称在作用域中查找对应的符号,以下面的程序为例。

1.int x; 
2.int y;
3.void a() 
{ 
	int x;
	x = 1; // x resolves to current scope, not x in global scope
	y = 2; // y is not found in current scope, but resolves in global
4.{ int y = x; } 
}
5.void b(int z) 
6.{ }

在全局作用域中,包含了变量x,y以及函数a()与函数b().而3,4,6则处于函数作用域内。在上面的例子中,x变量有两个定义。这时对于x的使用就需要根据作用域来判断了。

这里写图片描述

数字所标注的表示作用域。从任意一个结点到根结点形成一个作用域栈。为了寻找符号,首先从所引用符号所在的定义域内开始,一直到根结点去寻找要找的符号。

根据符号表的操作,最终的实现可以分为符号定义与查找两部分。对于定义,需要监听变

以上是关于学习笔记的主要内容,如果未能解决你的问题,请参考以下文章

《Python机器学习》笔记

pytorch学习笔记:多维特征的分类问题

数据清洗《黑马程序员》著本人学习笔记

mongodb 学习笔记 07 -- 数据备份恢复

有没有其他方法可以加载数据

黑马程序员《数据清洗》学习笔记CSVJSON数据抽取