{"action":"created","comment":{"url":"https://api.github.com/repos/maasg/petulant-avenger/pulls/comments/32314499","id":32314499,"diff_hunk":"@@ -1 +1,241 @@\n+import org.apache.spark.SparkContext\n+import org.apache.spark.SparkContext._\n+import org.apache.spark.SparkConf\n+import org.apache.spark.rdd.RDD\n+\n+\n+object WordDistanceInSpark {\n+  // I'm obviously doing something very wrong, because I get the following exception at the end on the run:\n+/* ERROR ContextCleaner: Error in cleaning thread\n+java.lang.InterruptedException\n+  at java.lang.Object.wait(Native Method)\n+  at java.lang.ref.ReferenceQueue.remove(ReferenceQueue.java:143)\n+  at org.apache.spark.ContextCleaner$$anonfun$org$apache$spark$ContextCleaner$$keepCleaning$1.apply$mcV$sp(ContextCleaner.scala:146)\n+  at org.apache.spark.ContextCleaner$$anonfun$org$apache$spark$ContextCleaner$$keepCleaning$1.apply(ContextCleaner.scala:144)\n+  at org.apache.spark.ContextCleaner$$anonfun$org$apache$spark$ContextCleaner$$keepCleaning$1.apply(ContextCleaner.scala:144)\n+  at org.apache.spark.util.Utils$.logUncaughtExceptions(Utils.scala:1618)\n+  at org.apache.spark.ContextCleaner.org$apache$spark$ContextCleaner$$keepCleaning(ContextCleaner.scala:143)\n+  at org.apache.spark.ContextCleaner$$anon$3.run(ContextCleaner.scala:65)\n+ERROR Utils: Uncaught exception in thread SparkListenerBus\n+java.lang.InterruptedException\n+  at java.util.concurrent.locks.AbstractQueuedSynchronizer.doAcquireSharedInterruptibly(AbstractQueuedSynchronizer.java:998)\n+  at java.util.concurrent.locks.AbstractQueuedSynchronizer.acquireSharedInterruptibly(AbstractQueuedSynchronizer.java:1304)\n+  at java.util.concurrent.Semaphore.acquire(Semaphore.java:312)\n+  at org.apache.spark.util.AsynchronousListenerBus$$anon$1$$anonfun$run$1.apply$mcV$sp(AsynchronousListenerBus.scala:62)\n+  at org.apache.spark.util.AsynchronousListenerBus$$anon$1$$anonfun$run$1.apply(AsynchronousListenerBus.scala:61)\n+  at org.apache.spark.util.AsynchronousListenerBus$$anon$1$$anonfun$run$1.apply(AsynchronousListenerBus.scala:61)\n+  at org.apache.spark.util.Utils$.logUncaughtExceptions(Utils.scala:1618)\n+  at org.apache.spark.util.AsynchronousListenerBus$$anon$1.run(AsynchronousListenerBus.scala:60)*/\n+\n+\n+  \n+  \n+  \n+  type Hit = (Long, String) // (index, the significant word found at index)\n+  type SameWordInterval = ((Long,Long),String) // ( (startIdx, endIdx), significant word)\n+  \n+  /**\n+   * Finds the distance between given words in the text, defined as \n+   * minimum amount of other words in between occurrences of the given words.\n+   * \n+   * Distance between a word and itself is 0.\n+   * If one of the words does not occur in the text, distance is None\n+   *  \n+   * @param textFilePath\n+   * @param word1\n+   * @param word2\n+   * @param sc sparkContext\n+   * @return\n+   */\n+  def wordDistance(textFilePath:String, word1:String, word2:String)(implicit sc:SparkContext):Option[Long] = {\n+    if (word1 == word2) return Some(0)\n+    if (word1==null || word2==null) return None\n+    \n+    // need access to a cluster to tweak this experimentally, so this is just a guess. \n+    // I don't know whether even to fiddle with this at all or just take the defaultParallelism.\n+    implicit val nbPartitions = sc.defaultParallelism * 2\n+    \n+    // would this normally have to be broadcasted to every node then?\n+    val lines:RDD[String] = sc.textFile(textFilePath, nbPartitions)\n+    \n+    // look at text file as sequence of words\n+    val words = asFlatWordList(lines)\n+    \n+    // we filter out all words that are not word1 or word2\n+    val orderedHits:RDD[Hit] = findHitsInOrder(words, word1, word2)\n+    \n+    val smallestDistance:Option[Long] = findSmallestDistanceBetweenDifferentWordHits(orderedHits)\n+    smallestDistance\n+  }\n+  \n+  def asFlatWordList(lines:RDD[String]):RDD[String] = lines.flatMap { l => l.split(\" \") }\n+\n+  def findHitsInOrder(words:RDD[String], word1:String, word2:String)(implicit nbPartitions:Int):RDD[Hit] = {\n+    // this part should be embarrassingly parallel, as we like it\n+    val hits = words.zipWithIndex().filter(p=> (p._1 == word1 || p._1 == word2)).map(p => p.swap)\n+    // the sort destroys the parallelism, but is necessary for avoiding unnecessary comparisons\n+    // I'm assuming here that spark implements this sort well, in order to make this still an overall win.\n+    val sorted = hits.sortByKey(ascending=true, numPartitions = nbPartitions)\n+    sorted\n+  }\n+  \n+  def findSmallestDistanceBetweenDifferentWordHits(orderedHits:RDD[Hit]): Option[Long] = {\n+    // println(\"\"\"***** \"\"\"+orderedHits.top(100).toList.mkString(\", \"))\n+    \n+    // Our input data is like a pseudo-timeseries, with word indices as time axis, \n+    // and only two possible values: word1 or word2.\n+    // At this point we can still have repetitions of the same word.\n+    // But a smallest distance can only happen when the signal transitions between hits of word1 and word2 or other other.\n+    // Our interface allows us to not care about the indices of the transition itself, \n+    // only about the index-distance  between the words forming the transition\n+    // Since we don't care about the transitions, automatic/manual stream fusion should in theory \n+    // allow to get rid of the transition-finding as a separate step.\n+    // But given the time limitation, and because this kind of tweaking might be \n+    // much less important than other changes, I'm writing it here as the conceptual sequence of \n+    // first finding transitions, and then finding the smallest one.\n+\n+    // I wish I knew spark well enough already to be able to split the RDD of Hits in partitions\n+    // which do not break a transition across partitions, in order to avoid a synchronization step.\n+    // I immediately see two approaches that would work: \n+    // 1) If I could include the hit which happens to be part of a signal transition in both partitions, I'd be sure\n+    // that no transition is broken up across partitions, and then the partitions \n+    // would only need to communicate back their winning candidate. seems fail-safe\n+    // 2) If I could align the partition boundaries to only fall in the middle of hits of the same word\n+    // we also would not have a broken transition.\n+    // But if we only do option 2, our number of partitions would depend strongly on the input text.\n+    \n+    // Without a solution for avoiding the synchronization, I don't yet have a sense for how much of an improvement \n+    // it gives to do this part of the computation on the cluster instead of in the driver.\n+    // For a non-pathological case, the filtering all words to just our word1 and word2 should already be \n+    // a huge shrink in dataset size.\n+    \n+    // but since there was a big warning against the trivial \"make it run on spark\" \n+    // implementation of immediately collecting, I'll try to write it in a spark-compatible style.\n+    // \n+    // Stylewise, I'm unhappy because it becomes a bit car/cdr soup though, \n+    // which could be solved by defining case classes.\n+\n+    // I can't find a spark foldLeft which lets me fold Hits into SameWordIntervals.\n+    // A verbose workaround but pretty foolproof solution would be to create a trait as union type.\n+    // In this case I took the \"wrap everything as a list\" approach. Pretty ugly.\n+    // I'm not sure whether defining a custom accumulator would help for making it less ugly \n+    // and for making it more performant.\n+     \n+    val reverseOrderedSameWordIntervals:List[SameWordInterval] = convertHitsToSameWordIntervals(orderedHits)\n+    // the fact that I do a fold which is an action instead of a transform, \n+    // is bumping me out of RDDs into regular scala-land at this point anyway.\n+    // At this point we have had another huge data shrink from skipping consecutive hits of the same word,\n+    // and it is already late, so I'm going to do the last part in pure scala\n+    \n+    // println(\"\"\"***** \"\"\"+reverseOrderedSameWordIntervals.take(100).toList.mkString(\", \"))\n+    \n+    // if one of the words does not exist, there will only be one element in this list\n+    if (reverseOrderedSameWordIntervals.size == 1) {\n+      None \n+    } else {\n+      Some(findMinimumDistanceBetweenAlternatingIntervals(reverseOrderedSameWordIntervals))\n+    }\n+  }\n+   \n+  def convertHitsToSameWordIntervals(orderedHits:RDD[Hit]):List[SameWordInterval] = {","path":"WordDistanceInSpark.scala","position":140,"original_position":140,"commit_id":"cc536ca19369f44c29afdd9300832d80d89aced6","original_commit_id":"cc536ca19369f44c29afdd9300832d80d89aced6","user":{"login":"maasg","id":874997,"avatar_url":"https://avatars.githubusercontent.com/u/874997?v=3","gravatar_id":"","url":"https://api.github.com/users/maasg","html_url":"https://github.com/maasg","followers_url":"https://api.github.com/users/maasg/followers","following_url":"https://api.github.com/users/maasg/following{/other_user}","gists_url":"https://api.github.com/users/maasg/gists{/gist_id}","starred_url":"https://api.github.com/users/maasg/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/maasg/subscriptions","organizations_url":"https://api.github.com/users/maasg/orgs","repos_url":"https://api.github.com/users/maasg/repos","events_url":"https://api.github.com/users/maasg/events{/privacy}","received_events_url":"https://api.github.com/users/maasg/received_events","type":"User","site_admin":false},"body":"I'm lost in the intention of this method","created_at":"2015-06-12T13:18:24Z","updated_at":"2015-06-12T13:18:24Z","html_url":"https://github.com/maasg/petulant-avenger/pull/1#discussion_r32314499","pull_request_url":"https://api.github.com/repos/maasg/petulant-avenger/pulls/1","_links":{"self":{"href":"https://api.github.com/repos/maasg/petulant-avenger/pulls/comments/32314499"},"html":{"href":"https://github.com/maasg/petulant-avenger/pull/1#discussion_r32314499"},"pull_request":{"href":"https://api.github.com/repos/maasg/petulant-avenger/pulls/1"}}},"pull_request":{"url":"https://api.github.com/repos/maasg/petulant-avenger/pulls/1","id":37559506,"html_url":"https://github.com/maasg/petulant-avenger/pull/1","diff_url":"https://github.com/maasg/petulant-avenger/pull/1.diff","patch_url":"https://github.com/maasg/petulant-avenger/pull/1.patch","issue_url":"https://api.github.com/repos/maasg/petulant-avenger/issues/1","number":1,"state":"open","locked":false,"title":"Code Review","user":{"login":"maasg","id":874997,"avatar_url":"https://avatars.githubusercontent.com/u/874997?v=3","gravatar_id":"","url":"https://api.github.com/users/maasg","html_url":"https://github.com/maasg","followers_url":"https://api.github.com/users/maasg/followers","following_url":"https://api.github.com/users/maasg/following{/other_user}","gists_url":"https://api.github.com/users/maasg/gists{/gist_id}","starred_url":"https://api.github.com/users/maasg/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/maasg/subscriptions","organizations_url":"https://api.github.com/users/maasg/orgs","repos_url":"https://api.github.com/users/maasg/repos","events_url":"https://api.github.com/users/maasg/events{/privacy}","received_events_url":"https://api.github.com/users/maasg/received_events","type":"User","site_admin":false},"body":"Let's use this PR as review medium","created_at":"2015-06-12T12:56:04Z","updated_at":"2015-06-12T13:18:24Z","closed_at":null,"merged_at":null,"merge_commit_sha":"3b92ab042bb93d157a4c55eea4c749a8a0b41893","assignee":null,"milestone":null,"commits_url":"https://api.github.com/repos/maasg/petulant-avenger/pulls/1/commits","review_comments_url":"https://api.github.com/repos/maasg/petulant-avenger/pulls/1/comments","review_comment_url":"https://api.github.com/repos/maasg/petulant-avenger/pulls/comments{/number}","comments_url":"https://api.github.com/repos/maasg/petulant-avenger/issues/1/comments","statuses_url":"https://api.github.com/repos/maasg/petulant-avenger/statuses/cc536ca19369f44c29afdd9300832d80d89aced6","head":{"label":"maasg:review","ref":"review","sha":"cc536ca19369f44c29afdd9300832d80d89aced6","user":{"login":"maasg","id":874997,"avatar_url":"https://avatars.githubusercontent.com/u/874997?v=3","gravatar_id":"","url":"https://api.github.com/users/maasg","html_url":"https://github.com/maasg","followers_url":"https://api.github.com/users/maasg/followers","following_url":"https://api.github.com/users/maasg/following{/other_user}","gists_url":"https://api.github.com/users/maasg/gists{/gist_id}","starred_url":"https://api.github.com/users/maasg/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/maasg/subscriptions","organizations_url":"https://api.github.com/users/maasg/orgs","repos_url":"https://api.github.com/users/maasg/repos","events_url":"https://api.github.com/users/maasg/events{/privacy}","received_events_url":"https://api.github.com/users/maasg/received_events","type":"User","site_admin":false},"repo":{"id":37321329,"name":"petulant-avenger","full_name":"maasg/petulant-avenger","owner":{"login":"maasg","id":874997,"avatar_url":"https://avatars.githubusercontent.com/u/874997?v=3","gravatar_id":"","url":"https://api.github.com/users/maasg","html_url":"https://github.com/maasg","followers_url":"https://api.github.com/users/maasg/followers","following_url":"https://api.github.com/users/maasg/following{/other_user}","gists_url":"https://api.github.com/users/maasg/gists{/gist_id}","starred_url":"https://api.github.com/users/maasg/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/maasg/subscriptions","organizations_url":"https://api.github.com/users/maasg/orgs","repos_url":"https://api.github.com/users/maasg/repos","events_url":"https://api.github.com/users/maasg/events{/privacy}","received_events_url":"https://api.github.com/users/maasg/received_events","type":"User","site_admin":false},"private":false,"html_url":"https://github.com/maasg/petulant-avenger","description":"foobar","fork":false,"url":"https://api.github.com/repos/maasg/petulant-avenger","forks_url":"https://api.github.com/repos/maasg/petulant-avenger/forks","keys_url":"https://api.github.com/repos/maasg/petulant-avenger/keys{/key_id}","collaborators_url":"https://api.github.com/repos/maasg/petulant-avenger/collaborators{/collaborator}","teams_url":"https://api.github.com/repos/maasg/petulant-avenger/teams","hooks_url":"https://api.github.com/repos/maasg/petulant-avenger/hooks","issue_events_url":"https://api.github.com/repos/maasg/petulant-avenger/issues/events{/number}","events_url":"https://api.github.com/repos/maasg/petulant-avenger/events","assignees_url":"https://api.github.com/repos/maasg/petulant-avenger/assignees{/user}","branches_url":"https://api.github.com/repos/maasg/petulant-avenger/branches{/branch}","tags_url":"https://api.github.com/repos/maasg/petulant-avenger/tags","blobs_url":"https://api.github.com/repos/maasg/petulant-avenger/git/blobs{/sha}","git_tags_url":"https://api.github.com/repos/maasg/petulant-avenger/git/tags{/sha}","git_refs_url":"https://api.github.com/repos/maasg/petulant-avenger/git/refs{/sha}","trees_url":"https://api.github.com/repos/maasg/petulant-avenger/git/trees{/sha}","statuses_url":"https://api.github.com/repos/maasg/petulant-avenger/statuses/{sha}","languages_url":"https://api.github.com/repos/maasg/petulant-avenger/languages","stargazers_url":"https://api.github.com/repos/maasg/petulant-avenger/stargazers","contributors_url":"https://api.github.com/repos/maasg/petulant-avenger/contributors","subscribers_url":"https://api.github.com/repos/maasg/petulant-avenger/subscribers","subscription_url":"https://api.github.com/repos/maasg/petulant-avenger/subscription","commits_url":"https://api.github.com/repos/maasg/petulant-avenger/commits{/sha}","git_commits_url":"https://api.github.com/repos/maasg/petulant-avenger/git/commits{/sha}","comments_url":"https://api.github.com/repos/maasg/petulant-avenger/comments{/number}","issue_comment_url":"https://api.github.com/repos/maasg/petulant-avenger/issues/comments{/number}","contents_url":"https://api.github.com/repos/maasg/petulant-avenger/contents/{+path}","compare_url":"https://api.github.com/repos/maasg/petulant-avenger/compare/{base}...{head}","merges_url":"https://api.github.com/repos/maasg/petulant-avenger/merges","archive_url":"https://api.github.com/repos/maasg/petulant-avenger/{archive_format}{/ref}","downloads_url":"https://api.github.com/repos/maasg/petulant-avenger/downloads","issues_url":"https://api.github.com/repos/maasg/petulant-avenger/issues{/number}","pulls_url":"https://api.github.com/repos/maasg/petulant-avenger/pulls{/number}","milestones_url":"https://api.github.com/repos/maasg/petulant-avenger/milestones{/number}","notifications_url":"https://api.github.com/repos/maasg/petulant-avenger/notifications{?since,all,participating}","labels_url":"https://api.github.com/repos/maasg/petulant-avenger/labels{/name}","releases_url":"https://api.github.com/repos/maasg/petulant-avenger/releases{/id}","created_at":"2015-06-12T12:43:46Z","updated_at":"2015-06-12T12:46:01Z","pushed_at":"2015-06-12T12:56:04Z","git_url":"git://github.com/maasg/petulant-avenger.git","ssh_url":"git@github.com:maasg/petulant-avenger.git","clone_url":"https://github.com/maasg/petulant-avenger.git","svn_url":"https://github.com/maasg/petulant-avenger","homepage":null,"size":0,"stargazers_count":0,"watchers_count":0,"language":"Scala","has_issues":true,"has_downloads":true,"has_wiki":true,"has_pages":false,"forks_count":0,"mirror_url":null,"open_issues_count":1,"forks":0,"open_issues":1,"watchers":0,"default_branch":"master"}},"base":{"label":"maasg:master","ref":"master","sha":"c61152c86dd74a29377aad74a997a6b10d5fb051","user":{"login":"maasg","id":874997,"avatar_url":"https://avatars.githubusercontent.com/u/874997?v=3","gravatar_id":"","url":"https://api.github.com/users/maasg","html_url":"https://github.com/maasg","followers_url":"https://api.github.com/users/maasg/followers","following_url":"https://api.github.com/users/maasg/following{/other_user}","gists_url":"https://api.github.com/users/maasg/gists{/gist_id}","starred_url":"https://api.github.com/users/maasg/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/maasg/subscriptions","organizations_url":"https://api.github.com/users/maasg/orgs","repos_url":"https://api.github.com/users/maasg/repos","events_url":"https://api.github.com/users/maasg/events{/privacy}","received_events_url":"https://api.github.com/users/maasg/received_events","type":"User","site_admin":false},"repo":{"id":37321329,"name":"petulant-avenger","full_name":"maasg/petulant-avenger","owner":{"login":"maasg","id":874997,"avatar_url":"https://avatars.githubusercontent.com/u/874997?v=3","gravatar_id":"","url":"https://api.github.com/users/maasg","html_url":"https://github.com/maasg","followers_url":"https://api.github.com/users/maasg/followers","following_url":"https://api.github.com/users/maasg/following{/other_user}","gists_url":"https://api.github.com/users/maasg/gists{/gist_id}","starred_url":"https://api.github.com/users/maasg/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/maasg/subscriptions","organizations_url":"https://api.github.com/users/maasg/orgs","repos_url":"https://api.github.com/users/maasg/repos","events_url":"https://api.github.com/users/maasg/events{/privacy}","received_events_url":"https://api.github.com/users/maasg/received_events","type":"User","site_admin":false},"private":false,"html_url":"https://github.com/maasg/petulant-avenger","description":"foobar","fork":false,"url":"https://api.github.com/repos/maasg/petulant-avenger","forks_url":"https://api.github.com/repos/maasg/petulant-avenger/forks","keys_url":"https://api.github.com/repos/maasg/petulant-avenger/keys{/key_id}","collaborators_url":"https://api.github.com/repos/maasg/petulant-avenger/collaborators{/collaborator}","teams_url":"https://api.github.com/repos/maasg/petulant-avenger/teams","hooks_url":"https://api.github.com/repos/maasg/petulant-avenger/hooks","issue_events_url":"https://api.github.com/repos/maasg/petulant-avenger/issues/events{/number}","events_url":"https://api.github.com/repos/maasg/petulant-avenger/events","assignees_url":"https://api.github.com/repos/maasg/petulant-avenger/assignees{/user}","branches_url":"https://api.github.com/repos/maasg/petulant-avenger/branches{/branch}","tags_url":"https://api.github.com/repos/maasg/petulant-avenger/tags","blobs_url":"https://api.github.com/repos/maasg/petulant-avenger/git/blobs{/sha}","git_tags_url":"https://api.github.com/repos/maasg/petulant-avenger/git/tags{/sha}","git_refs_url":"https://api.github.com/repos/maasg/petulant-avenger/git/refs{/sha}","trees_url":"https://api.github.com/repos/maasg/petulant-avenger/git/trees{/sha}","statuses_url":"https://api.github.com/repos/maasg/petulant-avenger/statuses/{sha}","languages_url":"https://api.github.com/repos/maasg/petulant-avenger/languages","stargazers_url":"https://api.github.com/repos/maasg/petulant-avenger/stargazers","contributors_url":"https://api.github.com/repos/maasg/petulant-avenger/contributors","subscribers_url":"https://api.github.com/repos/maasg/petulant-avenger/subscribers","subscription_url":"https://api.github.com/repos/maasg/petulant-avenger/subscription","commits_url":"https://api.github.com/repos/maasg/petulant-avenger/commits{/sha}","git_commits_url":"https://api.github.com/repos/maasg/petulant-avenger/git/commits{/sha}","comments_url":"https://api.github.com/repos/maasg/petulant-avenger/comments{/number}","issue_comment_url":"https://api.github.com/repos/maasg/petulant-avenger/issues/comments{/number}","contents_url":"https://api.github.com/repos/maasg/petulant-avenger/contents/{+path}","compare_url":"https://api.github.com/repos/maasg/petulant-avenger/compare/{base}...{head}","merges_url":"https://api.github.com/repos/maasg/petulant-avenger/merges","archive_url":"https://api.github.com/repos/maasg/petulant-avenger/{archive_format}{/ref}","downloads_url":"https://api.github.com/repos/maasg/petulant-avenger/downloads","issues_url":"https://api.github.com/repos/maasg/petulant-avenger/issues{/number}","pulls_url":"https://api.github.com/repos/maasg/petulant-avenger/pulls{/number}","milestones_url":"https://api.github.com/repos/maasg/petulant-avenger/milestones{/number}","notifications_url":"https://api.github.com/repos/maasg/petulant-avenger/notifications{?since,all,participating}","labels_url":"https://api.github.com/repos/maasg/petulant-avenger/labels{/name}","releases_url":"https://api.github.com/repos/maasg/petulant-avenger/releases{/id}","created_at":"2015-06-12T12:43:46Z","updated_at":"2015-06-12T12:46:01Z","pushed_at":"2015-06-12T12:56:04Z","git_url":"git://github.com/maasg/petulant-avenger.git","ssh_url":"git@github.com:maasg/petulant-avenger.git","clone_url":"https://github.com/maasg/petulant-avenger.git","svn_url":"https://github.com/maasg/petulant-avenger","homepage":null,"size":0,"stargazers_count":0,"watchers_count":0,"language":"Scala","has_issues":true,"has_downloads":true,"has_wiki":true,"has_pages":false,"forks_count":0,"mirror_url":null,"open_issues_count":1,"forks":0,"open_issues":1,"watchers":0,"default_branch":"master"}},"_links":{"self":{"href":"https://api.github.com/repos/maasg/petulant-avenger/pulls/1"},"html":{"href":"https://github.com/maasg/petulant-avenger/pull/1"},"issue":{"href":"https://api.github.com/repos/maasg/petulant-avenger/issues/1"},"comments":{"href":"https://api.github.com/repos/maasg/petulant-avenger/issues/1/comments"},"review_comments":{"href":"https://api.github.com/repos/maasg/petulant-avenger/pulls/1/comments"},"review_comment":{"href":"https://api.github.com/repos/maasg/petulant-avenger/pulls/comments{/number}"},"commits":{"href":"https://api.github.com/repos/maasg/petulant-avenger/pulls/1/commits"},"statuses":{"href":"https://api.github.com/repos/maasg/petulant-avenger/statuses/cc536ca19369f44c29afdd9300832d80d89aced6"}}}}