记一次腾讯编程题,写了3个半小时,感觉还挺好。代表我现在的水平了。希望以后每次看都会有新的想法。
笔试题目说明
名词说明
单词
由大小写英文字母组成,不含其它字符。
摘要
由多个单词和语句结束符组成。
一条语句内的单词间用一个空格分隔;
摘要中若语句结束,以英文逗号或句号结尾。
搜索次数
标识该摘要被搜索次数。
搜索次数大于等于0。
搜索关键词
由一个关键单词组成,不包含2个及其以上的单词.
全词匹配
搜索的关键词与摘要中的单词完全一致,不存在被包含关系。
包含匹配
关键词与摘要中的单词为包含关系,如关键单词operation,被cooperation包含。
搜索规则
1、搜索出所有包含关键单词的摘要。
2、每次搜索时,若该摘要被搜索到关键词,则其搜索次数自动加1。
3、输出的摘要间以#符号间隔。
4、收索次数最高的摘要排在最前面。
5、若几份摘要的搜索次数相同,则输出规则按以下方式进行排序:
A、比较全词匹配的关键字次数,按次数由大到小输出摘要。
B、若全词匹配的关键字次数相同,则比较 包含匹配次数,次数多的优先输出。
C、若全词匹配与包含匹配次数相同,则对摘要按由小到大进行排序输出。
6、进行搜索时,不区分大小写。
待实现接口
1、boolean addAbstract(String strAbstract,integer iCount)
strAbstract: 摘要输入入参,同一份摘要不会重复输入。
iCount:摘要的搜索次数。
若入参为摘要空,直接返回false。
2、String searchAbstract(String strKeyWord)
strKeyWord:搜索的关键单词。
返回值为搜索符合关键字的摘要,每份摘要间以”#”号间隔。
举例
输入内容:
addAbstract(“These are good books .You can choose one book from them.”,9);
addAbstarct(“You can search books from Google.”,8);
addAbstarct(“Search and preview millions of books from libraries and publishers worldwide using Google Book Search.”,8);
addAbstarct(“Go to Google Books Home. Advanced Book Search, About Google ... All books.”,6)
addAbstarct(“Bookshelf provides free access to books and documents in life science and healthcare.”,7)
第一次搜索 关键词为BOOK
输出:
These are good books .You can choose one book from them.# Search and preview millions of books from libraries and publishers worldwide using Google Book Search.# You can search books from Google.# Bookshelf provides free access to books and documents in life science and healthcare.# Go to Google Books Home. Advanced Book Search, About Google ... All books.
第二次搜索 关键词为Google
输出:
You can search books from Google .# Search and preview millions of books from libraries and publishers worldwide using Google Book Search .#Go to Google Books Home. Advanced Book Search, About Google ... All books.
规格
0<=摘要个数范围<=200
1<=摘要所含单词个数<=50
1<=单词所含字母数<=50
超出如上约束的输入认为是错误的,对应接口返回失败
其他要求:
已经提供初步的代码框架及测试用例,请在此框架上继续完成代码,并保证基本用例通过。
可以根据实际需要自由增加类等数据结构定义
要求考虑并发场景保证数据安全。
不得修改原有的接口定义。
不限制其他类库的使用。
注意遵从编程规范,圈复杂度不超过10。
尽善尽美地发挥,将工程师的思想发挥出来
实现接口:
package com.tencent.ied.bk;
public class CSearchAbstract {
/**
* @param strAbstract
* 标识输入的摘要。
* @param iCount
* 标识该摘要搜索的次数。
* @return 成功返回true,异常返回FALSE。
*/
public boolean addAbstract(String strAbstract, int iCount) {
return true;
}/**
* @param strKeyWord
* 搜索关键词。
* @return 返回搜索到的摘要。
*/
public String searchAbstract(String strKeyWord) {
return "";
}
}
测试用例:
package com.tencent.ied.bk.unittest;
import static org.junit.Assert.assertEquals;
import org.junit.After;
import org.junit.Before;
import org.junit.Test;import com.tencent.ied.bk.CSearchAbstract;
public class CTestSearchAbstract {
@Before
public void setUp() throws Exception {
}@After
public void tearDown() throws Exception {
}@Test
public void testFindFlushPattern() {
CSearchAbstract objSA = new CSearchAbstract();
objSA.addAbstract("These are good books .You can choose one book from them.", 9);
objSA.addAbstract("You can search books from Google.", 8);
objSA.addAbstract("Search and preview millions of books from libraries and publishers worldwide using Google Book Search.", 8);
objSA.addAbstract("Go to Google Books Home. Advanced Book Search, About Google ... All books.", 6);
objSA.addAbstract("Bookshelf provides free access to books and documents in life science and healthcare.", 7);String strRst = objSA.searchAbstract("BOOK");
String rstExpt = "These are good books .You can choose one book from them.# Search and preview millions " +
"of books from libraries and publishers worldwide using Google Book Search.# You can search books from Google.# Bookshelf provides" +
" free access to books and documents in life science and healthcare.# Go to Google Books Home. Advanced Book Search, About " +
"Google ... All books.";
assertEquals(strRst,rstExpt);
strRst = objSA.searchAbstract("google");
rstExpt = "You can search books from Google.# Search and preview millions of books from libraries and publishers worldwide using "
+ "Google Book Search.# Go to Google Books Home. Advanced Book Search, About Google ... All books.";assertEquals(strRst,rstExpt);
}
}
个人编写的程序:
package com.insightfullogic.java8;
import com.google.common.collect.Lists;
import com.google.common.collect.Sets;import java.util.Comparator;
import java.util.HashMap;
import java.util.HashSet;
import java.util.LinkedHashMap;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.stream.Collectors;public class CSearchAbstract{
//0<=摘要个数范围<=200, 缓存摘要ID对应摘要对象
private static LRUCache ABSTRACT = new LRUCache(200);
//全局摘要ID
private static AtomicInteger INC = new AtomicInteger(1);
//静态分隔符
private static String SEPA = "# ";//关键词map,内部记录包含摘要ID的集合
private static ConcurrentHashMap> WORD_MAP = new ConcurrentHashMap<>(); /**
* @param strAbstract 标识输入的摘要。
* @param iCount 标识该摘要搜索的次数。
* @return 成功返回true, 异常返回FALSE。
*/
public boolean addAbstract(String strAbstract, int iCount){
if (strAbstract == null || strAbstract.length() < 1){
return false;
}
return statWordConut(strAbstract, iCount);
}/**
* @param strKeyWord 搜索关键词。
* @return 返回搜索到的摘要。
*/
public String searchAbstract(String strKeyWord){
if (strKeyWord == null || strKeyWord.length() < 1){
return null;
}
//获取摘要ID集合,setFull全词匹配集合,setInclude包含匹配集合
SetsetAll = Sets.newHashSet();
strKeyWord = strKeyWord.toLowerCase();
for (Map.Entry> entry : WORD_MAP.entrySet()){
if (entry.getKey().contains(strKeyWord)){
setAll.addAll(entry.getValue());
}
}//获取摘要包含匹配的list
LinkedListlistAll = Lists.newLinkedList();
setAll.forEach(id -> {
if (ABSTRACT.get(id) != null){
listAll.add(ABSTRACT.get(id));
//增加摘要搜索次数
ABSTRACT.get(id).incr();
}
});//返回包含匹配的结果排序
// TODO LRUCache会把最新操作数据放到前面,如果搜索规则结果排序规则都未命中,最后写入的会优先输出。如果要求先入优先输出,这里可以倒转下
// Collections.reverse(listAll);
listAll.sort(getAbstractBeanComparator(strKeyWord));//构建返回内容
StringBuffer sb = new StringBuffer();
listAll.stream().forEach(o -> sb.append(o.getContent()).append(SEPA));
sb.delete(sb.length() - SEPA.length(), sb.length());
return sb.toString();
}/**
* 搜索规则结果排序规则
*1.搜索次数最高的摘要排在最前面。
*2.比较全词匹配的关键字次数,按次数由大到小输出摘要。
*3.若全词匹配的关键字次数相同,则比较 包含匹配次数,次数多的优先输出。
*4.若全词匹配与包含匹配次数相同,则对摘要按由小到大进行排序输出。
* @param strKeyWord
* @return
*/
private ComparatorgetAbstractBeanComparator(String strKeyWord){
return (o1, o2) -> {
AbstractBean abstractBean1 = ABSTRACT.get(o1.getId());
AbstractBean abstractBean2 = ABSTRACT.get(o2.getId());//1.搜索次数最高的摘要排在最前面
int count1 = abstractBean1.getCount().intValue();
int count2 = abstractBean2.getCount().intValue();
if (count1 != count2){
return count2 - count1;
}//2.比较全词匹配的关键字次数,按次数由大到小输出摘要。
int fullCount1 = abstractBean1.wordCountMap.entrySet()
.stream()
.filter(o -> o.getKey().equals(strKeyWord))
.mapToInt(Map.Entry :: getValue)
.sum();
int fullCount2 = abstractBean2.wordCountMap.entrySet()
.stream()
.filter(o -> o.getKey().equals(strKeyWord))
.mapToInt(Map.Entry :: getValue)
.sum();
if (fullCount1 != fullCount2){
return fullCount2 - fullCount1;
}//3.若全词匹配的关键字次数相同,则比较 包含匹配次数,次数多的优先输出。
int includeCount1 = abstractBean1.wordCountMap.entrySet()
.stream()
.filter(o -> o.getKey().contains(strKeyWord) && !o.getKey().equals(strKeyWord))
.mapToInt(Map.Entry :: getValue)
.sum();
int includeCount2 = abstractBean2.wordCountMap.entrySet()
.stream()
.filter(o -> o.getKey().contains(strKeyWord) && !o.getKey().equals(strKeyWord))
.mapToInt(Map.Entry :: getValue)
.sum();
if (includeCount1 != includeCount2){
return includeCount2 - includeCount1;
}//4.若全词匹配与包含匹配次数相同,则对摘要按由小到大进行排序输出。
return abstractBean1.getContent().length() - abstractBean2.getContent().length();
};
}/**
* 统计输入摘要中关键单词,按单词出现次数从前到后统计
*
* @param str
*/
public static boolean statWordConut(String str, int iCount){
//统计摘要关键词和出现次数map
HashMapmap = new HashMap<>(str.length(), 0.75f);
//删除标点字符,\W意思是非单词字符;
String[] array = str.split("\\W+");Integer tmp;
for (String s : array){
//1<=单词所含字母数<=50
if (s.length() > 50){
continue;
}
s = s.toLowerCase();
tmp = map.get(s);
map.put(s, tmp == null ? 1 : tmp + 1);
}//1<=摘要所含单词个数<=50 TODO 这里也可以只保留摘要按数量统计的前50个单词
if (map.isEmpty() || map.size() > 50){
return false;
}List
> list = map.entrySet()
.stream()
.collect(Collectors.toList());
list.sort((o1, o2) -> o2.getValue() - o1.getValue());//分配摘要ID,摘要记录到LRU缓存中
Integer id = INC.getAndIncrement();
ABSTRACT.put(id, new AbstractBean(id, new AtomicInteger(iCount), str, map));list.stream().forEach(obj -> {
String key = obj.getKey().toLowerCase();
if (WORD_MAP.containsKey(key)){
String finalKey = key;
//TODO 这里要直接操作value对象,时间有限没有找到底层api
synchronized (WORD_MAP){
WORD_MAP.put(key, new HashSet(){{
add(id);
addAll(WORD_MAP.get(finalKey));
}});
}}else{
WORD_MAP.put(key, Sets.newHashSet(id));
}
});return true;
}/**
* LRU(Least recently used最近最少使用)缓存,支持get和put操作,并且两者的时间复杂度为O(1)
* 实现:参考LinkedHashMap
*/
public static class LRUCache{
private LinkedHashMapmap;
private final int capacity;public LRUCache(int capacity){
this.capacity = capacity;
//accessOrder = true,按访问顺序排序,访问后会移到链尾(tail)
map = new LinkedHashMap(capacity, 0.75f, true){
//LinkedHashMap封装的淘汰方法,当put新值方法返回true时,就移除该map中最老的键和值
protected boolean removeEldestEntry(Map.Entry eldest){
//如果Map的size大于设定的最大长度,返回true,再新加入对象时删除最少使用对象(head)
return size() > capacity;
}
};
}public AbstractBean get(int key){
return map.getOrDefault(key, null);
}public void put(int key, AbstractBean value){
map.put(key, value);
}}
/**
* 摘要对象
*/
static class AbstractBean{
/**
* 摘要ID
*/
private Integer id;/**
* 摘要搜索次数
*/
private AtomicInteger count;/**
* 摘要内容
*/
private String content;/**
* 单词在摘要中出现次数,key为小写字母
*/
private MapwordCountMap; public AbstractBean(Integer id, AtomicInteger count, String content, Map
wordCountMap){
this.id = id;
this.count = count;
this.content = content;
this.wordCountMap = wordCountMap;
}/**
* 自旋锁增加摘要搜索次数
*/
public void incr(){
for(;;){
int i = count.get();
boolean flag = count.compareAndSet(i, ++i);
if(flag){
break;
}
}
}public Integer getId(){
return id;
}public void setId(Integer id){
this.id = id;
}public AtomicInteger getCount(){
return count;
}public void setCount(AtomicInteger count){
this.count = count;
}public String getContent(){
return content;
}public void setContent(String content){
this.content = content;
}public Map
getWordCountMap(){
return wordCountMap;
}public void setWordCountMap(Map
wordCountMap){
this.wordCountMap = wordCountMap;
}
}}