getTimespan()
和getMentionedUser()
设计、记录、实现测试用例该方法得到输入的推文发送的时间段。
该方法有如下形式:
getTimespan: List -> Timespan
仅有一个输入参数List
,因此只需根据其长度分类即可:
/*
* getTimeSpan(tweets) test strategy:
*
* Partition the input as follow:
* tweets.size(): 0, 1, >1
*
* Full Cartesian product
*/
一次遍历,找到最大和最小的两个时间就行了。
public static Timespan getTimespan(List<Tweet> tweets) {
if (tweets.size() == 0) {
throw new IllegalArgumentException
("Exception message: tweets size == 0!");
}
Instant start = tweets.get(0).getTimestamp(),
end = tweets.get(0).getTimestamp();
for(Tweet tweet : tweets) {
Instant current = tweet.getTimestamp();
if (current.isBefore(start)) start = current;
if (current.isAfter(end)) end = current;
}
return new Timespan(start, end);
}
该方法得到推文中提及的名称的集合。提及的名称跟在@
后面,并且在@
符号前以及提及的名称后不能有可以组成名称的字符,否则该名称不是一个可以加入结果集的名称。
getMentionedUsers: List<Tweet> -> Set<String>
需要根据List
的长度、推文中的名称数量以及名称重复考虑。
/*
* getMentionedUser(tweets) test strategy:
*
* Partition the input as follow:
* tweets.size(): 0, 1, >1
* mentioned name number: 0, 1, > 1
* tweet.name: exist names with different case or not exist
*
* Part coverage
*/
我们需要提取符合格式的名称,这里我使用了正则表达式。除此之外,名称在结果集合中都转为小写,这样便于检查是否有重复名称,同时也不致违反该方法的规范。
先实现一个辅助函数getMentionUserInOneTweet
从一条推文中获得名字集合,然后将这些集合合并即可。
注意:我的实现中,开头或结尾的@xxx
会无法加入集合,但这也不与规范冲突,因此没有再多想。
在成员变量中添加:
private static final String nameLetters = "A-Za-z0-9_-";
private static final Pattern pattern = Pattern.compile
("[^"+ nameLetters + "]@[" + nameLetters + "]+[^" + nameLetters + "]");
实现如下:
public static Set<String> getMentionedUsers(List<Tweet> tweets) {
Set<String> set = new HashSet<>();
for(Tweet tweet: tweets)
set.addAll(getMentionedUsersInOneTweet(tweet));
return set;
}
public static Set<String> getMentionedUsersInOneTweet(Tweet tweet) {
Set<String> set = new HashSet<>();
Matcher matcher = pattern.matcher(tweet.getText());
while(matcher.find()) {
String name = matcher.group();
// remove the first two and last chars
// then convert to lower case
name = name.substring(2, name.length() - 1).toLowerCase();
set.add(name);
}
return set;
}
实现函数:
以及其测试用例,并通过测试。
找出推文集中特定作者的推文。
writtenBy: List<Tweet> * String -> List<Tweet>
有两个参数需要考虑。
/*
* writtenBy test strategy:
* partition input into:
* list size: 0, >0
* name length: 0, >0
* name contains illegal char or not
*
* Part Coverage
*/
含非法字符或长度为0的姓名为不合格的输入。
public static List<Tweet> writtenBy(List<Tweet> tweets, String username) {
Pattern pattern = Pattern.compile("[^A-Za-z0-9-_]");
Matcher matcher = pattern.matcher(username);
if (matcher.find() || username.length() == 0) {
throw new IllegalArgumentException();
}
List<Tweet> list = new LinkedList();
for(Tweet tweet: tweets) {
if (tweet.getAuthor().equals(username)) {
list.add(tweet);
}
}
return list;
}
给定一个推文集和一个时间段,找出在该时间段内发出的推文,返回的推文的相对顺序与输入中相同。
inTimespan: List<Tweet> * Timespan -> List<Tweet>
有两个参数,策略设计如下:
/*
* inTimespan test strategy:
* partition input into:
* list size: 0, >0
* timespan: start==end or not
*
* Full Cartesian product
*/
在实现中,时间段的区间为[start, end)
。
public static List<Tweet> inTimespan(List<Tweet> tweets, Timespan timespan) {
List<Tweet> list = new LinkedList<>();
Instant start = timespan.getStart();
Instant end = timespan.getEnd();
for(Tweet tweet: tweets) {
Instant current = tweet.getTimestamp();
if ((current.isAfter(start) && current.isBefore(end))
||
current.equals(start)) {
list.add(tweet);
}
}
return list;
}
找出含有特定单词的推文,大小写不敏感。
containing: List<Tweet> * List<String> -> List<Tweet>
根据推文集、单词集长度,是否含有空格分类、单词大小写:
/*
* containing test strategy:
* partition input into:
* tweets list size: 0, >0
* words list size: 0, >0
* words list contain illegal word or not
* words list contain word with different case from the words of tweet text
*/
public static List<Tweet> containing(List<Tweet> tweets, List<String> words) {
for(String word : words) {
if (word.contains(" ")) {
throw new IllegalArgumentException("illegal space in " + word);
}
}
List<Tweet> list = new LinkedList<>();
for (Tweet tweet: tweets) {
String text = tweet.getText().toLowerCase();
for (String word: words) {
String lower = word.toLowerCase();
if (text.contains(lower)) {
list.add(tweet);
break;
}
}
}
return list;
}
实现
建立含<follower, authors>
键值对的map
,当然,建立反过来的<author, followers>
的map
也可以。
guessFollowsGraph: List<Tweet> -> Map<String, Set<String>>
只需根据输入列表的长度划分:
/*
* guessFollowsGraph test strategy:
* partition input into:
* input list size: 0, >0
*
* Full Cartesian product
*/
public static Map<String, Set<String>> guessFollowsGraph(List<Tweet> tweets) {
Map<String, Set<String>> map = new HashMap<>();
for(Tweet tweet: tweets) {
Set<String> mentionedUsers = Extract.getMentionedUsersInOneTweet(tweet);
if (mentionedUsers.size() > 0) {
String author = tweet.getAuthor();
if (map.containsKey(author)) map.get(author).addAll(mentionedUsers);
else map.put(author, mentionedUsers);
}
}
return map;
}
找到粉丝最多的人,可能有多个。
influencers: Map<String, Set<String>> -> Set<String>
/*
* influencers test strategy:
* partition input into:
* input map size: 0, >0
*
* Full Cartesian product
*/
由于之前实现的guessFollowsGraph()
返回的是含有<follower, authors>
的map
,而在这里我们需要的是含<author, followers>
的map
,因此需要先反转一下,然后再将粉丝最多的人加入列表即可。
public static List<String> influencers(Map<String, Set<String>> followsGraph) {
Map<String, Integer> reverseMap = new HashMap<>();
int maxFollowers = 0;
// reverse
for (Map.Entry<String, Set<String>> entry: followsGraph.entrySet()) {
Set<String> authors = entry.getValue();
for (String s: authors) {
reverseMap.put(s, 1 + (reverseMap.containsKey(s) ? reverseMap.get(s) : 0));
maxFollowers = Math.max(maxFollowers, reverseMap.get(s));
}
}
// add to list
List<String> list = new LinkedList<>();
for (Map.Entry<String, Integer> entry: reverseMap.entrySet()) {
if (entry.getValue() == maxFollowers) {
list.add(entry.getKey());
}
}
return list;
}