第三次作业
https://class.coursera.org/progfun-003/assignment/view?assignment_id=9
这次作业,是我第一次没能够完全独立完成,还是参照了网上一些学习者的评论等之后才做出来的
虽然他们没有直接给出答案,甚至有些思路都是错的,但毕竟还不算是独立完成
对于函数式编程,对于递归,对于算法这个软肋,革命尚未成功,同志仍需努力!
题目:
给定抽象基类TweetSet,表示一系列Tweet(就是墙外的那个推特)消息的集合(其实它的实现是一个二叉树)
并给出了两个子类的部分函数实现,其中Empty类似于是一个空的Node, NonEmpty 是一个非空Node
其中incl(类似于insert),remove,contains,foreach已经给出实现
需要注意的是,在函数式编程语言Scala中,incl,remove等函数都是返回一个新的TweetSet,而非直接在源对象中进行修改,类似于Java中的String,都是Immutable的
class Tweet(val user : String, val text: String, val retweets : Int) {}
abstract class TweetSet {
/**
* Returns a new `TweetSet` which contains all elements of this set, and the
* the new element ` tweet` in case it does not already exist in this set.
*
* If `this.contains( tweet)`, the current set is returned.
*/
def incl(tweet: Tweet): TweetSet
/**
* Returns a new `TweetSet` which excludes ` tweet`.
*/
def remove(tweet: Tweet): TweetSet
/**
* Tests if `tweet` exists in this `TweetSet`.
*/
def contains(tweet: Tweet): Boolean
/**
* This method takes a function and applies it to every element in the set.
*/
def foreach(f: Tweet => Unit): Unit
}
class Empty extends TweetSet {
def contains(tweet: Tweet): Boolean = false
def incl(tweet: Tweet): TweetSet = new NonEmpty(tweet, new Empty, new Empty)
def remove(tweet: Tweet): TweetSet = this
def foreach(f: Tweet => Unit): Unit = ()
}
class NonEmpty(elem: Tweet, left: TweetSet, right: TweetSet) extends TweetSet {
def contains(x: Tweet): Boolean =
if (x.text < elem.text ) left.contains(x)
else if (elem.text < x. text) right.contains(x)
else true
def incl(x: Tweet): TweetSet = {
if (x.text < elem.text ) new NonEmpty(elem, left.incl(x), right)
else if (elem.text < x. text) new NonEmpty(elem, left, right.incl(x))
else this
}
def remove(tw: Tweet): TweetSet =
if (tw.text < elem.text ) new NonEmpty(elem, left.remove(tw), right)
else if (elem.text < tw. text) new NonEmpty(elem, left, right.remove(tw))
else left.union(right)
def foreach(f: Tweet => Unit): Unit = {
f(elem)
left.foreach(f)
right.foreach(f)
}
}
题目1:
实现filter函数,返回符合函数对象p的TweetSet
提示: 首先实现filterAcc辅助函数,第二个参数作为accumulator
/** This method takes a predicate and returns a subset of all the elements
* in the original set for which the predicate is true.
*/
def filter(p: Tweet => Boolean): TweetSet
def filterAcc(p: Tweet => Boolean, acc: TweetSet): TweetSet
思路:
这题没什么大困难,和之前做过的练习类似,第一个参数是条件,第二个参数是累加器,用于返回最后的结果
先递归左子树,再递归右子树,最后处理自身
有一点不同的是,NonEmpty和Empty 的实现有所不同,以此来结束递归.
因为以前的练习大多类似于:
if(终止条件())return
else 递归
而现在往往是把终止条件写入终止类中(这里就是Empty类),以多态来代替以前的终止条件()函数
答案:
基类中:
def filter(p: Tweet => Boolean): TweetSet = {
filterAcc(p, new Empty)
}
def filterAcc(p: Tweet => Boolean, acc: TweetSet): TweetSet
子类中:
// NonEmpty
def filterAcc(p: Tweet => Boolean, acc: TweetSet): TweetSet = {
val accResult1 = left.filterAcc(p, acc)
val accResult2 = right.filterAcc(p, accResult1)
if (p(elem)) accResult2.incl(elem)
else accResult2
}
// Empty
def unionAcc(acc: TweetSet): TweetSet = acc
题目2:(有难度,用时1小时)
编写union函数,返回两个TweetSet的并集
def union(that: TweetSet): TweetSet
我本来以为很简单,随手写:
// NonEmpty
def union(that: TweetSet): TweetSet = {
that.union(left).union(right).incl(elem)
}
// Empty
def union(that: TweetSet): TweetSet = that
运行Scala的UnitTest也没问题,可是到了做第四题的时候发现这个实现有问题,会出现无限递归的情况
最后查看课程的论坛,发现很多人都有这个问题,助教也说,要仔细考虑这个实现可能出现的问题,后来发现,当集合过大时,会出现多个集合递归的情况
于是参照第一题的实现,使用一个accumulator来作为函数的返回值
// 基类
def union(that: TweetSet): TweetSet = {
unionAcc(that)
}
// 暂不实现
def unionAcc(that: TweetSet): TweetSet
// Empty
def unionAcc(acc: TweetSet): TweetSet = acc
// NonEmpty
def unionAcc(acc: TweetSet): TweetSet = {
left.unionAcc(right.unionAcc(acc)).incl(elem)
}
题目3:
按照Tweet的转发次数排序descendingByRetweet
按照题目的意思是,首先实现一个辅助函数(mostRetweeted)用于计算TweetSet中转发最多次数的Tweet,然后取出,放入一个List中,同时TweetSet把这个Tweet删除
我的实现如下:
// List定义
class Cons(val head : Tweet, val tail: TweetList) extends TweetList {
def isEmpty = false
}
def compareSet(t1: Tweet, s: TweetSet): Tweet = {
/**
* It's Error that:
*
* if (t1. retweets >= v.retweets) t1
* else s.mostRetweeted.retweets
*/
val v = s.mostRetweeted
if (t1. retweets >= v. retweets) t1
else v
}
def mostRetweeted: Tweet = {
if (left.isEmpty && right.isEmpty) elem
else if (left.isEmpty) compareSet(elem, right)
else if (right.isEmpty) compareSet(elem, left)
else compareSet(compareSet(elem, left), right)
}
def descendingByRetweet: TweetList = {
new Cons(mostRetweeted, this.remove(mostRetweeted).descendingByRetweet)
}
做这个练习中也遇到一个问题,如果compareSet写成注释中那样,那么会出现无限递归的情况,思考后发现mostRetweeted前后调用两次,永远没有退出的情况
题目4:(巧妙之极)
有一个allTweets对象,存放着所有Tweet消息
又有一个对象GoogleVsApple
object GoogleVsApple {
val google = List( "android", "Android" , "galaxy" , "Galaxy" , "nexus" , "Nexus" )
val apple = List( "ios", "iOS" , "iphone" , "iPhone" , "ipad" , "iPad" )
lazy val googleTweets : TweetSet = allTweets.filter(???)
lazy val appleTweets : TweetSet = allTweets.filter(???)
}
问题:
The first TweetSet, googleTweets, should contain all tweets that mention (in their “text”) one of the keywords in the google list
use the exists method of List and contains method of class java.lang.String.
此题的精髓:也就是说用Scala List的 exists函数和Java String 的contains函数完成googleTweets的赋值
exists的签名如下:
// Tests whether a predicate holds for some of the elements of this list.
def exists(p: (A)=>Boolean): Boolean
也就是说,exixts的参数是一个函数对象,接受一个Type(这里是String),返回Boolean
一开始我也想不出为什么要exists,熟悉命令式语言的话一般都是写下类似代码:
def func(s:String /* Tweet内容 */):Booean{
{
对于每一个google里的String str
if(s.contains(str)){
return true
}
}
return false
}
lazy val googleTweets : TweetSet = allTweets.filter(x=>func(x.text))
后来我看到Coursera上有人说了句:
// That hint is one of the best hints in this assignment.
我就思考:
def exists(p: (A)=>Boolean): Boolean 中,p一定是这样一个函数,输入一个Tweet,如果Tweet的字符串包含List中的某一个字符串,那么就 return true,
于是写下代码:
lazy val googleTweets : TweetSet = allTweets.filter(x => google.exists(s => x.text.contains(s)))
意思就是:
googleTweets是一个TweetSet,是allTweets符合以下条件Tweet消息x的一个集合:
这个条件是:
google List 中存在着某个字符串s, 使得x.text包含(Java String contains) 这个s
全文完
以下是 10.0/10.0 的代码:
package objsets
import common._
import TweetReader._
/**
* A class to represent tweets.
*/
class Tweet(val user : String, val text: String, val retweets : Int) {
override def toString: String =
"User: " + user + "\n" +
"Text: " + text + " [" + retweets + "]"
}
/**
* This represents a set of objects of type ` Tweet` in the form of a binary search
* tree. Every branch in the tree has two children (two `TweetSet`s). There is an
* invariant which always holds: for every branch `b`, all elements in the left
* subtree are smaller than the tweet at `b`. The eleemnts in the right subtree are
* larger.
*
* Note that the above structure requires us to be able to compare two tweets (we
* need to be able to say which of two tweets is larger, or if they are equal). In
* this implementation, the equality / order of tweets is based on the tweet's text
* (see `def incl`). Hence, a `TweetSet` could not contain two tweets with the same
* text from different users.
*
*
* The advantage of representing sets as binary search trees is that the elements
* of the set can be found quickly. If you want to learn more you can take a look
* at the Wikipedia page [1], but this is not necessary in order to solve this
* assignment.
*
* [1] http://en.wikipedia.org/wiki/Binary_search_tree
*/
abstract class TweetSet {
/**
* This method takes a predicate and returns a subset of all the elements
* in the original set for which the predicate is true.
*
* Question: Can we implment this method here, or should it remain abstract
* and be implemented in the subclasses?
*/
def filter(p: Tweet => Boolean): TweetSet = {
filterAcc(p, new Empty)
}
/**
* This is a helper method for `filter` that propagetes the accumulated tweets.
*/
def filterAcc(p: Tweet => Boolean, acc: TweetSet): TweetSet
/**
* Returns a new `TweetSet` that is the union of `TweetSet`s `this` and `that`.
*
* Question: Should we implment this method here, or should it remain abstract
* and be implemented in the subclasses?
*/
/**
* At first I implement it by that.union(left).union(right). incl(elem )
* However it's error and will cause infinite loop
*/
def union(that: TweetSet): TweetSet = {
unionAcc(that)
}
def unionAcc(that: TweetSet): TweetSet
/**
* Returns the tweet from this set which has the greatest retweet count.
*
* Calling `mostRetweeted` on an empty set should throw an exception of
* type `java.util.NoSuchElementException`.
*
* Question: Should we implment this method here, or should it remain abstract
* and be implemented in the subclasses?
*/
def mostRetweeted: Tweet
def isEmpty: Boolean
/**
* Returns a list containing all tweets of this set, sorted by retweet count
* in descending order. In other words, the head of the resulting list should
* have the highest retweet count.
*
* Hint: the method `remove` on TweetSet will be very useful.
* Question: Should we implment this method here, or should it remain abstract
* and be implemented in the subclasses?
*/
def descendingByRetweet: TweetList
/**
* The following methods are already implemented
*/
/**
* Returns a new `TweetSet` which contains all elements of this set, and the
* the new element ` tweet` in case it does not already exist in this set.
*
* If `this.contains( tweet)`, the current set is returned.
*/
def incl(tweet: Tweet): TweetSet
/**
* Returns a new `TweetSet` which excludes ` tweet`.
*/
def remove(tweet: Tweet): TweetSet
/**
* Tests if `tweet` exists in this `TweetSet`.
*/
def contains(tweet: Tweet): Boolean
/**
* This method takes a function and applies it to every element in the set.
*/
def foreach(f: Tweet => Unit): Unit
}
class Empty extends TweetSet {
def filterAcc(p: Tweet => Boolean, acc: TweetSet): TweetSet = acc
def unionAcc(acc: TweetSet): TweetSet = acc
def mostRetweeted: Tweet = throw new java.util.NoSuchElementException
def isEmpty: Boolean = {
true
}
def descendingByRetweet: TweetList = Nil
/**
* The following methods are already implemented
*/
def contains(tweet: Tweet): Boolean = false
def incl(tweet: Tweet): TweetSet = new NonEmpty(tweet, new Empty, new Empty)
def remove(tweet: Tweet): TweetSet = this
def foreach(f: Tweet => Unit): Unit = ()
}
class NonEmpty(elem: Tweet, left: TweetSet, right: TweetSet) extends TweetSet {
def filterAcc(p: Tweet => Boolean, acc: TweetSet): TweetSet = {
val accResult1 = left.filterAcc(p, acc)
val accResult2 = right.filterAcc(p, accResult1)
if (p(elem)) accResult2.incl(elem)
else accResult2
}
def unionAcc(acc: TweetSet): TweetSet = {
left.unionAcc(right.unionAcc(acc)).incl(elem)
}
def isEmpty: Boolean = false
// Take me a long long time to complete
def compareSet(t1: Tweet, s: TweetSet): Tweet = {
val v = s.mostRetweeted
if (t1. retweets >= v. retweets) t1
else v
/**
* It's Error that:
*
* if (t1. retweets >= v.retweets) t1
* else s.mostRetweeted.retweets
*/
}
def mostRetweeted: Tweet = {
if (left.isEmpty && right.isEmpty) elem
else if (left.isEmpty) compareSet(elem, right)
else if (right.isEmpty) compareSet(elem, left)
else compareSet(compareSet(elem, left), right)
}
def descendingByRetweet: TweetList = {
new Cons(mostRetweeted, this.remove(mostRetweeted).descendingByRetweet)
}
/**
* The following methods are already implemented
*/
def contains(x: Tweet): Boolean =
if (x.text < elem.text ) left.contains(x)
else if (elem.text < x. text) right.contains(x)
else true
def incl(x: Tweet): TweetSet = {
if (x.text < elem.text ) new NonEmpty(elem, left.incl(x), right)
else if (elem.text < x. text) new NonEmpty(elem, left, right.incl(x))
else this
}
def remove(tw: Tweet): TweetSet =
if (tw.text < elem.text ) new NonEmpty(elem, left.remove(tw), right)
else if (elem.text < tw. text) new NonEmpty(elem, left, right.remove(tw))
else left.union(right)
def foreach(f: Tweet => Unit): Unit = {
f(elem)
left.foreach(f)
right.foreach(f)
}
}
trait TweetList {
def head: Tweet
def tail: TweetList
def isEmpty: Boolean
def foreach(f: Tweet => Unit): Unit =
if (!isEmpty) {
f(head)
tail.foreach(f)
}
}
object Nil extends TweetList {
def head = throw new java.util.NoSuchElementException("head of EmptyList")
def tail = throw new java.util.NoSuchElementException("tail of EmptyList")
def isEmpty = true
}
class Cons(val head : Tweet, val tail: TweetList) extends TweetList {
def isEmpty = false
}
object GoogleVsApple {
val google = List( "android", "Android" , "galaxy" , "Galaxy" , "nexus" , "Nexus" )
val apple = List( "ios", "iOS" , "iphone" , "iPhone" , "ipad" , "iPad" )
/**
* Take me a long time to complete
* As someone said:
* That hint is one of the best hints in this assignment.
*/
lazy val googleTweets : TweetSet = allTweets.filter(x => google.exists(s => x.text.contains(s)))
lazy val appleTweets : TweetSet = allTweets.filter(x => apple.exists(s => x.text.contains(s)))
/**
* A list of all tweets mentioning a keyword from either apple or google,
* sorted by the number of retweets.
*/
lazy val trending : TweetList = googleTweets.union(appleTweets ).descendingByRetweet
}
object Main extends App {
// Print the trending tweets
GoogleVsApple.trending foreach println
}