编程要求
在Begin-End区域编写 add_search_history(user_id, keyword) 函数,实现将最新搜索词记录到搜索记录列表中的功能,具体参数与要求如下:
方法参数 user_id 是进行搜索的用户 ID,keyword 是最新搜索词
为了确保最新搜索词始终在列表顶部,若该搜索词已包含在搜索记录列表recent:search:{user_id}中,则将搜索词从该列表中移除
记录最新搜索词的实现:将搜索词添加到列表recent:search:{user_id}的最顶部,并只保留最新的50个元素
编写 remove_search_history(user_id, keyword) 函数,实现删除搜索记录列表中的指定搜索词的功能,具体参数与要求如下:
方法参数 user_id 是进行删除的用户 ID,keyword是指定搜索词
删除指定搜索词的实现:将指定搜索词从该用户搜索记录列表recent:search:{user_id}中移除
编写 fetch_autocomplete_list(user_id, prefix) 函数,实现获取自动匹配的搜索词列表的功能,具体参数与要求如下:
方法参数 user_id 是需要匹配的用户 ID,prefix是需要匹配的前缀
获取以前缀开头的搜索词列表的实现:遍历用户搜索记录列表recent:search:{user_id},将所有开头为前缀的搜索词记录到一个数组中并返回
测试说明
平台会对你编写的代码进行测试:
测试输入:无;
预期输出:
Add 51 search history...
Current search history list length: 50
Pull one of the older ones up to the front
New top-3 keywords:
['keyword-1-4', 'keyword-16-50', 'keyword-16-49']
Remove a keyword...
None
Current search history list length: 49
Test autocomplete...
Input prefix: keyword-2-
Matches list:
['keyword-2-8', 'keyword-2-7']
#!/usr/bin/env python
#-*- coding:utf-8 -*-
import redis
conn = redis.Redis()
# 将最新搜索词记录到搜索记录列表中
def add_search_history(user_id, keyword):
# 将最新搜索词记录到指定用户的搜索记录列表中
# user_id: 用户ID
# keyword: 搜索关键词
history_list = "recent:search:" + user_id
pipe = conn.pipeline()
pipe.multi()
pipe.lrem(history_list, keyword) # 移除已存在的关键词
pipe.lpush(history_list, keyword) # 将关键词插入到列表头部
pipe.ltrim(history_list, 0, 49) # 保留最新的50个搜索记录
pipe.execute()
# 删除搜索记录列表中的指定搜索词
def remove_search_history(user_id, keyword):
# 从指定用户的搜索记录列表中删除指定的搜索词
# user_id: 用户ID
# keyword: 待删除的搜索关键词
key = "recent:search:" + user_id
conn.lrem(key, keyword)
# 获取到自动匹配的搜索词列表
def fetch_autocomplete_list(user_id, prefix):
# 获取指定用户搜索记录列表中以指定前缀开头的搜索词列表
# user_id: 用户ID
# prefix: 搜索关键词前缀
candidates = conn.lrange("recent:search:" + user_id, 0, -1)
matches = [candidate.decode('utf-8').encode('utf-8') for candidate in candidates if candidate.decode('utf-8').startswith(prefix)]
return matches
编程要求
在Begin-End区域编写 find_prefix_range(prefix) 函数,实现生成起始元素和结束元素的功能,具体参数与要求如下:
方法参数 prefix 是输入的前缀;
生成排列在前缀前的第一个词的实现:根据字母顺序寻找输入前缀最后一个字符的前序字符并在末尾加字符 {,记为起始元素;
生成排列在前缀后的第一个词的实现:在输入前缀末尾加字符 {,记为结束元素;
返回起始元素和结束元素。
编写 autocomplete_on_prefix(prefix) 函数,实现获取匹配提示词的功能,具体参数与要求如下:
方法参数 prefix 是待匹配的前缀;
获取起始元素和结束元素范围区间的实现:通过find_prefix_range方法生成起始元素和结束元素,为了保证有序集合中成员唯一,在起始元素和结束元素末尾都加上全局唯一标识符,再将其加入到有序集合autocomplete:candidates中,分值均为0;
本例中有序集合 autocomplete:candidates 已存成员如下:
获取匹配区间的实现:查看起始元素和结束元素在有序集合autocomplete:candidates中的排名,记为区间的起始值和结束值,之后删除之前加入的起始元素和结束元素;然后把刚刚获取的结束值减2
获取匹配词列表的实现:使用区间起始值和修正后的区间结束值取出排名区间内的前 10 个元素,并记为 items。
测试说明
平台会对你编写的代码进行测试:
测试输入:无;
预期输出:
The start/end range of 'wh' is: ('wg{', 'wh{')
Add a few candidate word...
Add keyword: who
Add keyword: why
Add keyword: what
Add keyword: when
Add keyword: where
Add keyword: apple
Add keyword: application
Add keyword: alipay
Add keyword: adobe
Add keyword: acfun
Test autocomplete, prefix: 'wh'
['what', 'when', 'where', 'who', 'why']
Test autocomplete, prefix: 'whe'
['when', 'where']
Test autocomplete, prefix: 'ap'
['apple', 'application']
Test autocomplete, prefix: 'ali'
['alipay']
#!/usr/bin/env python
#-*- coding:utf-8 -*-
import uuid
import redis
import bisect
conn = redis.Redis()
# 生成起始元素和结束元素
def find_prefix_range(prefix):
# 根据前缀生成起始元素和结束元素,用于查找匹配的提示词
# prefix: 给定的前缀字符串
start = prefix[:-1] + chr(ord(prefix[-1]) - 1) + '{'
# 结束元素:在前缀后加 {
end = prefix + '{'
return start, end
# 获取匹配提示词列表
def autocomplete_on_prefix(prefix):
# 根据前缀获取匹配的提示词列表
# prefix: 给定的前缀字符串
zset_name = 'autocomplete:candidates'
start, end = find_prefix_range(prefix)
conn.zadd(zset_name,start, 0)
conn.zadd(zset_name,end, 0)
sindex = conn.zrank(zset_name, start)
eindex = conn.zrank(zset_name, end)
erange = min(sindex + 9, eindex - 2)
pipe = conn.pipeline()
pipe.multi()
pipe.zrem(zset_name, start, end)
pipe.zrange(zset_name, sindex, erange)
items = pipe.execute()[-1]
return [item for item in items if '{' not in item]
编程要求
在Begin-End区域编写 add_keyword_frequency(keyword) 函数,实现记录搜索词频次的功能,具体参数与要求如下:
方法参数 keyword 是待记录的搜索词;
获取所有前缀词的实现:由于每个搜索词按照拼写顺序有若干个前缀,所以为每一个前缀定义一个有序集合,键名为 keyword:{prefix};
记录搜索词频次的实现:为上述所有有序集合中的该搜索词成员的分值加 1,并只保留分值最高的 20 个成员;
使低频使用搜索词列表自动过期的实现:设置上述所有有序集合的过期时间为一天。
编写 get_search_suggestions(prefix) 函数,实现获取搜索预测列表的功能,具体参数与要求如下:
方法参数 prefix 是待匹配的前缀;
获取搜索预测列表的实现:找到该前缀对应的有序集合keyword:{prefix},并将该有序集合中的所有成员按照分值递减的顺序排列输出。
测试说明
平台会对你编写的代码进行测试:
测试输入:无;
预期输出:
100 users search keyword: 'redis'
15 users search keyword: 'redis cli'
50 users search keyword...
30 users search keyword...
Test suggeations, prefix: 're'
['redis', 'redis cli', 'redis 6', 'redis 5', 'redis 4', 'redis 3', 'redis 2', 'redis 1', 'redis 0', 'reply 6', 'reply 5', 'reply 4', 'reply 3', 'reply 2', 'reply 1', 'reply 0', 'reply 7']
Test suggeations, prefix: 'redis'
['redis cli', 'redis 6', 'redis 5', 'redis 4', 'redis 3', 'redis 2', 'redis 1', 'redis 0']
Test suggeations, prefix: 'rep'
['reply 6', 'reply 5', 'reply 4', 'reply 3', 'reply 2', 'reply 1', 'reply 0', 'reply 7']
#!/usr/bin/env python
#-*- coding:utf-8 -*-
import redis
conn = redis.Redis()
# 记录搜索词频次
def add_keyword_frequency(keyword):
# 获取所有前缀
for i in range(1, len(keyword) + 1):
prefix = keyword[:i]
key = "keyword:{}".format(prefix)
# 将当前 keyword 添加到对应的有序集合中,分值增加 1
conn.zincrby(key,keyword,1.0)
# 保留分值最高的 20 个成员
conn.zremrangebyrank(key, 20, -1)
# 设置该集合的过期时间为 1 天 (86400 秒)
conn.expire(key, 86400)
# 获取搜索预测列表
def get_search_suggestions(prefix):
# 获取前缀对应的有序集合
key = "keyword:{}".format(prefix)
# 获取该有序集合中所有成员,并按分值递减顺序排列
suggestions = conn.zrevrange(key, 0, -1)
return suggestions