BOP 2016 SEMI 结束之结束 2

挖一个大坑,我是来观光的。

坑还没填完。

BOP 2016 SEMI Rank#11 团队 yaoyao 代码。

基于 Python 2.7 ,使用 flask 提供 Web API 终结点。665行的人生。

使用的库

REST API 入口:cca

求解主入口:get_all_possible_ans

TOC

cca

[挖坑]

get_all_possible_ans

#@profile
def get_all_possible_ans(Id1,Id2):
	entities = get_entity(expr='Or(Or(Or(Composite(AA.AuId=%s),Composite(AA.AuId=%s)),Id=%s),Id=%s)' % (Id1, Id2, Id1, Id2))

与 Elecky 类似,在查询 Id 类型的同时,将作者的所有论文全部下载下来,然后判断节点对的类型,分别使用 get_Id_Id 、 get_AuId_Id 、 get_Id_AuId 和 get_AuId_AuId 子过程解决问题。

get_entity

#@profile
def get_entity(expr,attr='Id,AA.AuId,AA.AfId,J.JId,C.CId,RId,F.FId,CC',thl=[]):
	t_l = []
	q = Queue.Queue()
	entities = []
	pe_l = []
	try:
		for i in xrange(2):
			t_l.append(threading.Thread(target=get_entity_one,args=(expr,q,i,attr)))
			t_l[i].start()
		gevent.joinall(thl)
		ans = q.get()
		entities = json.loads(ans)['entities']
	except:
		para['expr'] = expr
		para['attributes'] = attr
		ans = get_ans_http(para)
		entities = json.loads(ans)['entities']

	for en in entities:
		pe = Paper_Entity(en)
		pe_l.append(pe)
	return pe_l


#@profile
def get_entity_one(expr,q,i,attr='Id,AA.AuId,AA.AfId,J.JId,C.CId,RId,F.FId,CC'):
	para['expr'] = expr
	para['attributes'] = attr
	ans = get_ans(para,i)
	q.put(ans)

#167:这就是“同一查询,两次请求”的实现。注意到 get_entity_one 会将下载下来的论文信息放到 q 中。

#170:只是为了等待其它的 greenlet 。目前使用的都是默认值(空)。

#171:注意到线程安全队列的 Queue.get 在队列为空时会阻塞调用方。此处十分巧妙地利用了这一特点,如果 t_l 中有任意一次请求先完成,那么阻塞就会解除, ans 马上被赋值。

加速的方法:3.一个语句同时查询两次(单次查询时间波动较大,两次查询同时进行,并且使用先返回的结果,会更稳定) [1]

随后就是 JSON 解析和返回了。

#176:可能作者也觉得强行 socket (参阅下文)有点丧心病狂,所以如果在用 socket 的时候出现任何问题,就回退到 get_ans_http ,使用 httplib.HTTPConnection (对应 Python 3 的 http.client.HTTPConnection )进行连接。

get_ans

def get_ans(para, i=0):
	global host, port, domain
	try:
		sock_c = s_q.get(block=False)
	except:
		sock_c = socket_class()
	sock = sock_c.get_sock()
	para = '&'.join(['='.join(item) for item in para.items()])

	request_str = '''GET /%s?%s HTTP/1.1\r\nHost: %s\r\n\r\n''' %(domain, para, host)
	sock.send(request_str)
	total_data=[]
	#sock.settimeout(5)
	data = sock.recv(150)
	length = int(data.split('\n')[1].split(' ')[1])
	header_len = len(data.split('\r\n\r\n')[0])
	total_data.append(data[header_len+4:])
	length = length-(len(data) - header_len - 4)

	while 1:
		if length>0:
			data = sock.recv(min(length,8192))
			length -= len(data)
			total_data.append(data)
		else:
			break
	ans = ''.join(total_data)
	s_q.put(sock_c)
	return ans


for i in range(total_sock):
	s_q.put(socket_class())

其中,para 是一个字典,包含了URL请求的查询属性和值;i 不知道是干什么的,可能是之前向控制台输出以区分 `get_entity` 的两次连接的。

#95:socket pool 不多说。

加速的方法: 1.socket 长连接 [1]

#99:将字典转换为完整的 URL 。

#101:好吧,强行 socket

requests > urllib2 >httplib > socket长连接 时间消耗依次减少 [1]

那么先把 HTTP 1.1 完整的请求和回复放在这里备用吧。例如

GET https://oxfordhk.azure-api.net/academic/v1.0/evaluate?expr=Or(Id=2147152072,Composite(AA.AuId=2147152072))&model=latest&count=2&offset=0&orderby=&attributes=Id,Y,AA.AuId,AA.AfId,F.FId,J.JId,C.CId,RId,CC&subscription-key=f7cc29509a8443c5b3a5e56b0e38b5a6 HTTP/1.1
Host: oxfordhk.azure-api.net
HTTP/1.1 200 OK
Content-Length: 683
Content-Type: application/json
X-Powered-By: ASP.NET
Date: Sat, 14 May 2016 07:32:12 GMT

{
"expr" : "Or(Id=2147152072,Composite(AA.AuId=2147152072))",
"entities" : 
[{ "logprob":-15.440, "Id":2147152072, "Y":1990, "CC":5293, "RId":[2151561903, 1965061793, 2019911971, 2114804204, 2000215628, 2043343585, 134022301, 2158495995, 96199841, 1602667807, 35738896, 2041565863, 2072371179, 124362989, 2107827038, 1991631392, 71581993], "AA":[{"AuId":2033059188,"AfId":40347166}, {"AuId":676500258}, {"AuId":2019832499}, {"AuId":2114332599}, {"AuId":2004554093,"AfId":125749732}], "F":[{"FId":170133592}, {"FId":56666940}, {"FId":22789450}, {"FId":10879293}, {"FId":186311912}, {"FId":124246873}, {"FId":23123220}, {"FId":105795698}, {"FId":41008148}], "J":{"JId":135954941} }]
}

#105, #113:注意 socket.recv 是阻塞函数。不过前面的调用方基本上都是在工作线程调用此函数的,所以也就无谓了。

#106-#109:为了从回复中套出 Content-Length 标头的值。

#113:随后就可以开始愉快地接收数据了。缓冲区大小 8KB 。

数据收完之后就可以返回了。

#123:在程序一开始运行的时候,就向池中填满新鲜的 sockets 。

heart_beat

在此函数中生成了一个简单的请求,然后把 s_q socket 池中的 socket 取出来,一个一个地使用 get_ans 向外发送请求。函数保证了每一个 socket 在一分钟内会被使用一次。

threading.Thread(target=heart_beat)#.start()

然而这里的 start 被注释掉了……什么状况……

get_Id_Id

		if (Id1_en.AuId or Id1_en.entity.get('RId',[])) and (Id2_en.AuId or Id2_en.entity.get('RId',[])):	
			print 'Id-Id'
			if Id2_en.entity.get('CC',0)>1000:
				print 'CC>1000'
				ans+=get_Id_Id(Id1, Id2, Id1_en, Id2_en)
			else:
				print 'CC<1000'
				ans += get_Id_Id_CC(Id1, Id2, Id1_en, Id2_en)

与 Elecky 类似,如果 Id2 的被引次数大于1000,则使用 get_Id_Id 从左向右搜索;否则使用 get_Id_Id_CC 从右向左搜索。

#@profile
def get_Id_Id(Id1, Id2, Id1_en, Id2_en):
	ans = []

这里 Id1_en 、 Id2_en 分别对应了 Id1 、 Id2 的论文详细信息。

对于从左向右搜索的情况

#266:先把 Id1 引用的所有文献(潜在的 Id3 )下载下来,存入 Id1_RId_en_l 中,然后按顺序考虑以下情况

Id1 - AuId/FId/CId/JId3 - Id4 - Id2 (分支):构造查询表达式

查找(包含 Id1.AuId/FId/JId/CId
或位于 Id3.RId 列表中)且引用了 Id2 的论文

很明显,这就是为了查找 Id4 。顺带一提,Or_expr_Id 实现了按照允许的最大表达式长度对查询进行拆分的功能。

#275:作者应该是 JId/CId 不分的,统统称为 CId

#286:然后把拆分后的查询表达式丢给 `gevent.spawn` ,在工作线程中进行下载。

Id - Id :这个很明显。

Id1 - Id3 - Id2 :在 Id_RId_Id 子过程中,由于此时已经把所有可能的 Id3 下载下来了,所以直接判断其 RId 中是否包含 Id2 即可。

Id1 - CId/JId/FId/AuId3 - Id2 :这个也很明显。

Id1 - Id3 - CId/JId/FId/AuID4 - Id2 :这个也很明显,考虑到所有的 Id3 都被下载下来了。

Id1 - AuId/FId/CId/JId3 - Id4 - Id2 (汇合):等待前面 spawn 出来的线程工作完毕,然后查一下返回的 Id4 论文的属性,就可以一条一条地返回了。

对于从右向左搜索的情况

 

[未完待续]

发表回复

您的电子邮箱地址不会被公开。 必填项已用*标注

ERROR: si-captcha.php plugin: GD image support not detected in PHP!

Contact your web host and ask them to enable GD image support for PHP.

ERROR: si-captcha.php plugin: imagepng function not detected in PHP!

Contact your web host and ask them to enable imagepng for PHP.

此站点使用Akismet来减少垃圾评论。了解我们如何处理您的评论数据

Content is available under CC BY-SA 3.0 unless otherwise noted.