-
Notifications
You must be signed in to change notification settings - Fork 1
Coursera FP Assignment 3
Sean Kermes edited this page Oct 8, 2013
·
1 revision
(ns scala-class.three
(:import java.util.NoSuchElementException))
(defrecord Tweet [user text retweets])
(defn gt [tweet-a tweet-b] (> (compare (:text tweet-a) (:text tweet-b)) 0))
(defn lt [tweet-a tweet-b] (< (compare (:text tweet-a) (:text tweet-b)) 0))
(defn eq [tweet-a tweet-b] (= (compare (:text tweet-a) (:text tweet-b)) 0))
(defprotocol Tree
(contains [this target])
(incl [this new-tweet])
(_filter [this acc pred])
(tfilter [this pred])
(union [this that])
(tremove [this target])
(_most-retweeted [this acc])
(most-retweeted [this])
(descending-by-retweet [this]))
(defrecord TweetTree [tweet left right]
Tree
(contains [this target]
(cond (gt tweet target) (contains left target)
(lt tweet target) (contains right target)
:else true))
(incl [this new-tweet]
(cond (gt tweet new-tweet) (->TweetTree tweet (incl left new-tweet) right)
(lt tweet new-tweet) (->TweetTree tweet left (incl right new-tweet))
:else this))
(_filter [this acc pred]
(let [sub-filtered (_filter right (_filter left acc pred) pred)]
(if (pred tweet) (incl sub-filtered tweet) sub-filtered)))
(tfilter [this pred]
(_filter this nil pred))
(union [this that]
(->TweetTree tweet
(_filter that left #(lt % tweet))
(_filter that right #(gt % tweet))))
(tremove [this target]
(cond (gt tweet target) (->TweetTree tweet (tremove left target) right)
(lt tweet target) (->TweetTree tweet left (tremove right target))
:else (union left right)))
(_most-retweeted [this acc]
(let [best (if (> (:retweets tweet) (:retweets acc)) tweet acc)]
(_most-retweeted left (_most-retweeted right best))))
(most-retweeted [this]
(_most-retweeted this tweet))
(descending-by-retweet [this]
(let [most (most-retweeted this)]
(cons most (descending-by-retweet (tremove this most))))))
(extend-protocol Tree
nil
(contains [this target] false)
(incl [this new-tweet] (->TweetTree new-tweet nil nil))
(_filter [this acc pred] acc)
(tfilter [this pred] nil)
(union [this that] that)
(tremove [this target] nil)
(_most-retweeted [this acc] acc)
(most-retweeted [this] (throw (NoSuchElementException.)))
(descending-by-retweet [this] (list)))
(ns scala-class.three-trending
(:use scala-class.three)
(:use expectations)
(:use [scala-class.three-test :only [twit build-tree]])
(:use [scala-class.three-data :only [tech-tweets]]))
(defn re-test [regex string]
(not (nil? (re-find regex string))))
(defn any [pred lst]
(reduce #(or %1 %2) false (map pred lst)))
(def google-patterns [#"[aA]ndroid" #"[gG]alaxy" #"[nN]exus"])
(def apple-patterns [#"i[Oo][Ss]" #"i[pP]hone" #"i[pP]ad"])
(defn filter-pattern-list [patterns tree]
(tfilter tree (fn [tweet] (any #(re-test % (:text tweet)) patterns))))
(def google-tweets (filter-pattern-list google-patterns tech-tweets))
(def apple-tweets (filter-pattern-list apple-patterns tech-tweets))
(def trending (descending-by-retweet (union google-tweets apple-tweets)))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 elements 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 implement 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 propagates 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?
*/
def union(that: TweetSet): TweetSet = filterAcc( (p) => true, that.filter( (p) => true) )
/**
* 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 implement this method here, or should it remain abstract
* and be implemented in the subclasses?
*/
def mostRetweeted: Tweet
/**
* 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 implement this method here, or should it remain abstract
* and be implemented in the subclasses?
*/
def descendingByRetweet: TweetList = {
if (isEmpty) Nil
else {
val maxRetweeted = mostRetweeted
new Cons(maxRetweeted, remove(maxRetweeted).descendingByRetweet)
}
}
def isEmpty: Boolean
/**
* 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 mostRetweeted: Tweet = throw new java.util.NoSuchElementException
def isEmpty: Boolean = true
/**
* 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 = {
if (p(elem)) left.filterAcc(p, right.filterAcc(p, acc.incl(elem)))
else left.filterAcc(p, right.filterAcc(p, acc))
}
def mostRetweeted: Tweet = {
def tweetMax(t1: Tweet, t2: Tweet): Tweet = {
if (t1.retweets > t2.retweets) t1
else t2
}
if (left.isEmpty && right.isEmpty) elem
else if (left.isEmpty) tweetMax(right.mostRetweeted, elem)
else if (right.isEmpty) tweetMax(left.mostRetweeted, elem)
else tweetMax(left.mostRetweeted, tweetMax(right.mostRetweeted, elem))
}
def isEmpty: Boolean = false
/**
* 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")
lazy val googleTweets: TweetSet = TweetReader.allTweets.filter( tweet => google.exists( keyword => tweet.text.contains(keyword)))
lazy val appleTweets: TweetSet = TweetReader.allTweets.filter( tweet => apple.exists( keyword => tweet.text.contains(keyword)))
/**
* 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
}