【问题标题】:coursera progfun1: scala union performancecoursera progfun1:scala联合性能
【发布时间】:2016-12-12 01:51:14
【问题描述】:

在完成课程“Scala 中的函数式编程原理”@ coursera 第 3 周的作业时,我发现当我实现视频课程中所示的函数联合时:

  override def union(that: TweetSet): TweetSet = {
    left union(right) union(that) incl(elem)
  }

执行过程中花费的时间太长,但是当我以这种方式实现时:

  override def union(that: TweetSet): TweetSet = {
    right.union(left.union(that)).incl(elem)
  }

执行过程中花费的时间更少,我得到满分。

问题是我不知道这两种实现有什么区别,一种比另一种快吗?

为赋值(以及使用的数据结构的实现)给出的代码是:

package objsets

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 implment this method here, or should it remain abstract
   * and be implemented in the subclasses?
   */
    def filter(p: Tweet => Boolean): TweetSet = ???

  /**
   * 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?
   */
    def union(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 = ???

  /**
   * 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 = ???

  /**
   * 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 = ???


  /**
   * 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 = ???
  lazy val appleTweets: TweetSet = ???

  /**
   * A list of all tweets mentioning a keyword from either apple or google,
   * sorted by the number of retweets.
   */
     lazy val trending: TweetList = ???
  }

object Main extends App {
  // Print the trending tweets
  GoogleVsApple.trending foreach println
}

【问题讨论】:

    标签: performance scala functional-programming time-complexity


    【解决方案1】:

    我发了解释here

    这是它的内容:

    一些符号:

    Root : 树的根元素。 左/右:如果我们谈到联合,则为左/右树,如果我们谈到“包括左”,则为元素

    A. (left union(right union (other incl elem)))的含义

    首先:您将当前访问的节点包含在 other 中(这会探索树,沿着右叶向下,然后将您的项目添加到 Other。无需在其中调用 union)

    第二步:你用右子树重复这一步。

    第三步:你用左子树重复这一步。

    全球意义:每次,您将当前元素添加到其他元素,然后尝试向右走。如果可以,您将正确的元素添加到其他元素,然后再添加一次,直到无法正确为止。然后,你试着向左走……你可以吗?再向右走!你不能?也不能左转?回溯。

    您可以将其视为“优先运动”。每次添加项目时,然后根据偏好向右,然后向左,然后返回并重复!通过这样做,您只需探索一次整棵树,并为每个节点将其添加到其他节点!

    B. ((left union right) union other) incl elem(或left union right union other)的含义

    只是大声笑。简而言之,您想要添加您拥有的当前项目,您可以在可能的最后一步添加现在。但这还不是最糟糕的部分。当您调用 (left union right) 时,您现在正在将左侧项目添加到右侧子树,这与您之前所做的低效方式相同。这意味着:您尚未将 elem 包含到 other 中,但您必须将 left.item 包含到 right 中。然后,由于您将调用 (left.left union left.right),因此必须将 left.left.item 包含到 left.right .. 每次执行 A.union(B) 时,通过复制删除 A 的一个项目它完全(而不是像 incl 方法返回的不可变集合那样的智能副本),然后将其添加到 B。但是由于删除 A 的项目需要调用 A.left.union(A.right),因此您首先将拥有复制 A.left/A.right ... 等等。如果您可以想象一棵树,这就像将每个左兄弟聚集到右兄弟,并且每次您只想将一个项目添加到另一个项目。

    一些注意事项:

    如果你可以说a empty.union(that) = that,你可以说 NonEmpty.union(that : TweetSet) = if that is empty then this then (((union ... ) .. ) other包括元素)。这就是方法和诸如 Empty/NonEmpty 模式的问题,你不能将这两个基本情况集中在一个方法中,在这里,我们很多人在空中实现第一个,但在 NonEmpty 中忘记了另一个。始终确保如果 A.f(b) 是对称的 (= b.f(A)),那么您已经实现了两种基本情况 确定并直接进入您的基本案例。然后,从它递归到您的全局解决方案。对于“left union right union other incl elem”,基本情况是 other incl elem,因为您不希望您的替换位于“Empty incl n1 incl n2 incl ...”末尾。所以直接关注它,(其他包括元素)。 最后,但更重要的是:直觉!!!使用非常简单的案例,例如,如果您对我在这里的解释有困难,想象一下“复制”方法的相同参数,您可以写为 (left copy right) incl elem 或 (left copy (right incl elem))。通过像这样的简单示例,您可以更轻松、更快速地使用替换,了解为什么某些解决方案比其他解决方案差得多! 希望它会有所帮助!如果你有意见,告诉我!

    【讨论】:

      【解决方案2】:

      我找到了解释here.

      基本上当我们这样做时

       left union(right) union(that) incl(elem)
      

      首先left union (right) 被处理,然后union(that)被处理, 所以我们使第二个union 左侧的树更大,这将花费更多时间来完成递归,因为当union 的左参数为空时递归结束(检查union 的实现班级Empty)。

      【讨论】:

        猜你喜欢
        • 2017-05-05
        • 1970-01-01
        • 2016-12-15
        • 1970-01-01
        • 1970-01-01
        • 2018-06-05
        • 1970-01-01
        • 2017-07-23
        • 1970-01-01
        相关资源
        最近更新 更多