Leaderboard golf

11

1

Golf the leaderboard of this question (id=111735). Your program should issue one HTTP or HTTPS request to StackExchange API, parse it and present to user in form similar to a typical Leaderboard Snippet

Sample output (for the question 47338 instead of this):

Leaderboard
    Author  Language    Size
1.  FryAmTheEggman  Pyth    19
2.  Peter Taylor    CJam    24
3.  Martin Ender    CJam    27
3.  Peter Taylor    GolfScript  27
5.  randomra    J   32
5.  Optimizer   CJam    32
7.  Timtech TI-Basic 83/84  40
8.  mollmerx    k   41
9.  Sp3000  ><> 45
10. Sherlock9   Ruby    47
11. Martin Ender    Mathematica 49
12. Alex A. Julia   57
13. Sp3000  Python 2    58
14. Zgarb   Haskell 68
15. Timtech GML 76
16. Jakube  Python 2    79
16. nimi    Haskell 79
18. coredump    Common Lisp 91
19. Jim Large   Ruby    92
Winners by Language
Language    User    Score
Python 2    Sp3000  58
CJam    Peter Taylor    24
Julia   Alex A. 57
TI-Basic 83/84  Timtech 40
><> Sp3000  45
Haskell Zgarb   68
GolfScript  Peter Taylor    27
Common Lisp coredump    91
Pyth    FryAmTheEggman  19
k   mollmerx    41
J   randomra    32
Ruby    Sherlock9   47
Mathematica Martin Ender    49
GML Timtech 76

Note the repeated ranks 3, 5 and 16. Maybe I'll even add a special non-competing answer just to force correct, non-simplified handing of the score duplicates.

The output should consist of:

  1. The line "Leaderboard"
  2. The line "\tAuthor\tLanguage\tSize"
  3. For each answer, tab-separated line of rank and a ., then answer author name, then language name, then score; in ascending order for score
  4. The line "Winners by Language"
  5. The line "Language\tUser\tScore"
  6. For each used language, tab-separated language name, author of the lower score answer and the score

In other words, something like as if one copies and pastes result of the leaderboard snippet of this question to a text file (without "\tLink" things). See also the reference implementation in Python.

Rules

  • No network access apart from one API request to api.stackexchange.com
  • No usage of API features or languages that appeared after the submission of this question.
  • First line of the answer post should be Leaderboard-compatible. If it breaks the leaderboard script attached to the question then answer is non-competing.
  • If newly added answer renders some existing answer broken then the author of the old answer should fix it (or it becomes non-competing).
  • Links to languages, striked out scores, etc. should be handleded.
  • Ranks should be handled like in the snippet (e.g. equal score => equal rank => gap in ranks).

Accepted answer is the answer with the lowest score after sufficient amount of inactivity (minimum 1 month).

Good idea

  • To test with question IDs 47338 (for duplicate score handling + striked out score handling) and 17005 (for links handling). This bumps the answer from Valid to Good and protects from breaks from later submissions.
  • To include output examples both for this and for overridden ID versions.

Not necessary

  • Handling of more than 100 answers (limit of API for single request)
  • Handling of comment overrides
  • Sorting of "Winners by Language" section
  • Discrimination of competing and broken answers

Leaderboard

var QUESTION_ID=111735,OVERRIDE_USER=7773;function answersUrl(e){return"https://api.stackexchange.com/2.2/questions/"+QUESTION_ID+"/answers?page="+e+"&pagesize=100&order=desc&sort=creation&site=codegolf&filter="+ANSWER_FILTER}function commentUrl(e,s){return"https://api.stackexchange.com/2.2/answers/"+s.join(";")+"/comments?page="+e+"&pagesize=100&order=desc&sort=creation&site=codegolf&filter="+COMMENT_FILTER}function getAnswers(){jQuery.ajax({url:answersUrl(answer_page++),method:"get",dataType:"jsonp",crossDomain:!0,success:function(e){answers.push.apply(answers,e.items),answers_hash=[],answer_ids=[],e.items.forEach(function(e){e.comments=[];var s=+e.share_link.match(/\d+/);answer_ids.push(s),answers_hash[s]=e}),e.has_more||(more_answers=!1),comment_page=1,getComments()}})}function getComments(){jQuery.ajax({url:commentUrl(comment_page++,answer_ids),method:"get",dataType:"jsonp",crossDomain:!0,success:function(e){e.items.forEach(function(e){e.owner.user_id===OVERRIDE_USER&&answers_hash[e.post_id].comments.push(e)}),e.has_more?getComments():more_answers?getAnswers():process()}})}function getAuthorName(e){return e.owner.display_name}function process(){var e=[];answers.forEach(function(s){var r=s.body;s.comments.forEach(function(e){OVERRIDE_REG.test(e.body)&&(r="<h1>"+e.body.replace(OVERRIDE_REG,"")+"</h1>")});var a=r.match(SCORE_REG);a&&e.push({user:getAuthorName(s),size:+a[2],language:a[1],link:s.share_link})}),e.sort(function(e,s){var r=e.size,a=s.size;return r-a});var s={},r=1,a=null,n=1;e.forEach(function(e){e.size!=a&&(n=r),a=e.size,++r;var t=jQuery("#answer-template").html();t=t.replace("{{PLACE}}",n+".").replace("{{NAME}}",e.user).replace("{{LANGUAGE}}",e.language).replace("{{SIZE}}",e.size).replace("{{LINK}}",e.link),t=jQuery(t),jQuery("#answers").append(t);var o=e.language;/<a/.test(o)&&(o=jQuery(o).text()),s[o]=s[o]||{lang:e.language,user:e.user,size:e.size,link:e.link}});var t=[];for(var o in s)s.hasOwnProperty(o)&&t.push(s[o]);t.sort(function(e,s){return e.lang>s.lang?1:e.lang<s.lang?-1:0});for(var c=0;c<t.length;++c){var i=jQuery("#language-template").html(),o=t[c];i=i.replace("{{LANGUAGE}}",o.lang).replace("{{NAME}}",o.user).replace("{{SIZE}}",o.size).replace("{{LINK}}",o.link),i=jQuery(i),jQuery("#languages").append(i)}}var ANSWER_FILTER="!t)IWYnsLAZle2tQ3KqrVveCRJfxcRLe",COMMENT_FILTER="!)Q2B_A2kjfAiU78X(md6BoYk",answers=[],answers_hash,answer_ids,answer_page=1,more_answers=!0,comment_page;getAnswers();var SCORE_REG=/<h\d>\s*([^\n,]*[^\s,]),.*?(\d+)(?=[^\n\d<>]*(?:<(?:s>[^\n<>]*<\/s>|[^\n<>]+>)[^\n\d<>]*)*<\/h\d>)/,OVERRIDE_REG=/^Override\s*header:\s*/i;
body{text-align:left!important}#answer-list,#language-list{padding:10px;width:290px;float:left}table thead{font-weight:700}table td{padding:5px}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script> <link rel="stylesheet" type="text/css" href="//cdn.sstatic.net/codegolf/all.css?v=83c949450c8b"> <div id="answer-list"> <h2>Leaderboard</h2> <table class="answer-list"> <thead> <tr><td></td><td>Author</td><td>Language</td><td>Size</td></tr></thead> <tbody id="answers"> </tbody> </table> </div><div id="language-list"> <h2>Winners by Language</h2> <table class="language-list"> <thead> <tr><td>Language</td><td>User</td><td>Score</td></tr></thead> <tbody id="languages"> </tbody> </table> </div><table style="display: none"> <tbody id="answer-template"> <tr><td>{{PLACE}}</td><td>{{NAME}}</td><td>{{LANGUAGE}}</td><td>{{SIZE}}</td><td><a href="{{LINK}}">Link</a></td></tr></tbody> </table> <table style="display: none"> <tbody id="language-template"> <tr><td>{{LANGUAGE}}</td><td>{{NAME}}</td><td>{{SIZE}}</td><td><a href="{{LINK}}">Link</a></td></tr></tbody> </table>

Vi.

Posted 2017-02-28T23:19:51.563

Reputation: 2 644

Somewhat related and this (but they are very different challenges). – Stewie Griffin – 2017-03-03T16:05:55.467

Answers

2

Perl + Mojolicious, 468 456 469 504 bytes

Using Mojolicious library.

use v5.10;use ojo;while(@i=@{(g("http://api.stackexchange.com/2.2/questions/111735/answers?site=codegolf&filter=withbody&page=".++$p)->json//{})->{items}}){push@r,[$_->{owner}{display_name},(($h=x($_->{body})->at("h1,h2")||next)->at("a")||$h)->text=~/\s*([^,]+)\s*/,$h->text=~/(\d+)[^\d]*$/]for@i}$,="   ";say"Leaderboard
",Author,$l=Language,Size;say+(++$i,$s{@$_[2]}//=$i).".",@$_
for@r=sort{@$a[2]-@$b[2]}@r;%h=map{@$_[1],$_}reverse@r;say"Winners by $l
$l",User,Score;say$_,$h{$_}[0],$h{$_}[2]for keys%h

Ungolfed:

use v5.10;
use ojo;

my @r;
while (my @i = @{ (g("http://api.stackexchange.com/2.2/questions/111735/answers?site=codegolf&filter=withbody&page=" . ++$p)->json // {})->{items} }) {
    my $h = x($_->{body})->at("h1,h2") or next;
    push(@r, [$_->{owner}{display_name}, ($h->at("a") || $h)->text =~ /\s*([^,]+)\s*/, $h->text =~ /(\d+)[^\d]*$/]) for @i;
}

$, = "\t";
my %s;
say("Leaderboard\n", "Author", (my $l = "Language"), "Size");
say((++$i, $s{$_->[2]} //= $i) . ".", @$_) for @r = sort { $a->[2] <=> $b->[2] } @r;

my %h = map { $_->[1] => $_ } reverse(@r);
say("Winners by $l\n$l", "User", "Score");
say($_, $h{$_}[0], $h{$_}[2]) for keys(%h);

Denis Ibaev

Posted 2017-02-28T23:19:51.563

Reputation: 876

Can't locate ojo.pm in @INC -> Does it mean that it is not just "Perl", but "Perl+Mojolicious"? Does't using non-included-with-the-language libraries count as a standard loophole? – Vi. – 2017-03-03T21:22:45.943

If I patch question ID to 47338, I don't see correct handling of ties. Instead of having the sane rank entries disappear. – Vi. – 2017-03-03T21:23:19.670

Correction: same-score entries receive different rank. Although this does not render this answer invalid (yet), it's Not Good. – Vi. – 2017-03-03T22:23:27.100

@Vi. Fixed ties. – Denis Ibaev – 2017-03-04T12:31:48.660

1Works. Probably the most serious remaining issue (and supposedly the source of the downvote) is the submission name. Maybe change it to "Perl + Mojolicious" to avoid competing with pure Perl solutions? Mojolicious seems to be oneliner-oriented (hence useful for golfing) library not installed in most Perl deployments, so can't be considered a part of the language. – Vi. – 2017-03-04T21:34:29.913

@Vi. Ok. Changed. – Denis Ibaev – 2017-03-05T08:31:28.663

Note: now, with the repeated "856" this answer is the main winner candidate (unless other answers get updated). – Vi. – 2017-03-18T03:29:36.077

6

Python 3, 860 856 bytes

Golfed slightly, just to bootstrap the leaderboard and provide some template for other golfers:

import json,re,html as h,requests as r
p=print
u=h.unescape;a=[];n={}
for i in json.loads(r.get("https://api.stackexchange.com/2.2/questions/111735/answers?page=1&pagesize=100&site=codegolf&filter=!t)IWYnsLAZle2tQ3KqrVveCRJfxcRLe").text)["items"]:
    m=re.match(r'<h\d>\s*([^\n,]*[^\s,]),.*?(\d+)(?=[^\n\d<>]*(?:<(?:s>[^\n<>]*<\/s>|[^\n<>]+>)[^\n\d<>]*)*<\/h\d>)',i["body"].splitlines()[0]);l=u(m.group(1));t=u(i["owner"]["display_name"]);s=m.group(2);a.append((t,l,s))
    if l not in n: n[l]=[]
    n[l].append((t,s))
p("Leaderboard\n\tAuthor\tLanguage\tSize")
z=0;y=None
for i in enumerate(sorted(a,key=lambda x:x[2])):
    if y==i[1][2]:z+=1
    else:z=0;y=i[1][2]
    p("%d.\t%s\t%s\t%s"%(i[0]+1-z,i[1][0],i[1][1],i[1][2]))
p("Winners by Language\nLanguage\tUser\tScore")
for i in n.keys():
    n[i].sort(key=lambda x:x[1])
    print("%s\t%s\t%s"%(i,n[i][0][0],n[i][0][1]))

Indented with tabs. The last print is deliberately not replaced by p to create a score tie with the Mathematica answer.

Ungolfed:

import json
import re
import html
import requests
url="https://api.stackexchange.com/2.2/questions/111735/answers?page=1&pagesize=100&site=codegolf&filter=!t)IWYnsLAZle2tQ3KqrVveCRJfxcRLe"
data=json.loads(requests.get(url).text)
answers=[]
languages={}
for i in data["items"]:
    header=i["body"].splitlines()[0]
    m=re.match(r'<h\d>\s*([^\n,]*[^\s,]),.*?(\d+)(?=[^\n\d<>]*(?:<(?:s>[^\n<>]*<\/s>|[^\n<>]+>)[^\n\d<>]*)*<\/h\d>)', header)
    lang=html.unescape(m.group(1))
    author=html.unescape(i["owner"]["display_name"])
    score=m.group(2)
    answers.append((author, lang, score))
    if lang not in languages: languages[lang]=[]
    languages[lang].append((author, score))
answers.sort(key=lambda x:x[2])
print("Leaderboard")
print("\tAuthor\tLanguage\tSize")
rankadj=0
prevscore=None
for i in enumerate(answers):
    if prevscore == i[1][2]:
        rankadj+=1
    else:
        rankadj=0
        prevscore=i[1][2]
    print("%d.\t%s\t%s\t%s" % (i[0]+1-rankadj, i[1][0], i[1][1], i[1][2]))
print("Winners by Language")
print("Language\tUser\tScore")
for i in languages.keys():
    w=languages[i]
    w.sort(key=lambda x:x[1])
    print("%s\t%s\t%s" % (i, w[0][0], w[0][1]))

Note: it does not yet handle links correctly, so fails for, for example, question 17005.

Vi.

Posted 2017-02-28T23:19:51.563

Reputation: 2 644

1Even if you answer it yourself, you have to golf an answer to a [tag:code-golf] question. – NoOneIsHere – 2017-02-28T23:27:06.937

@NoOneIsHere, I'm not sure about "have to". Only if "if you anwer to a code-golf question and want to win". – Vi. – 2017-02-28T23:29:10.200

@NoOneIsHere, Also the reason of the answer is mostly to provide initial content and to serve as template for others' golfing attempts. – Vi. – 2017-02-28T23:30:02.933

@Vi See item 3 here

– Luis Mendo – 2017-02-28T23:30:49.980

3@Vi. Then include it in the question as a reference solution. This is an answer, which does not answer the (your own) question, which asks for golfed code, per [tag:code-golf]. – NoOneIsHere – 2017-02-28T23:31:43.697

@NoOneIsHere, OK, I'll try to minify it a bit while also providing readable "ungolfed" version. – Vi. – 2017-02-28T23:35:35.607

1@Vi. nope. Either it's fully golfed or it's not allowed. If it can easily be golfed, then it's not an answer. I'd really say just to put it in the question as a reference solution. – Rɪᴋᴇʀ – 2017-02-28T23:40:56.160

@NoOneIsHere, Done. "Either fully golfed or not allowed" -> means "newbie answers not allowed"? Golfing tricks needed to really golf an answer in whatever language not just come to mind easily. – Vi. – 2017-02-28T23:54:18.793

4@Vi. No, golf it to the best of your extend. – NoOneIsHere – 2017-03-01T01:04:55.233

Will this still work once more than one page is needed (the url has ...?page=1&pagesize=100&...)? – Jonathan Allan – 2017-03-02T13:52:52.153

@JonathanAllan, It will show only the first page. API seems not to let you download more than 100 answers in one call and I want to keep it 1 run - 1 call. That's why "Handling of more than 100 answers" is in "Not necessary" list. – Vi. – 2017-03-02T21:33:22.540

Ahha, I missed that caveat. – Jonathan Allan – 2017-03-02T21:35:27.090

This isn't tested, but what about import json,re,html as h,requests as r as opposed to importing each module on a separate line? – 0WJYxW9FMN – 2017-03-03T21:09:52.420

Maybe. I forgot that one can import multiple things per move in Python. – Vi. – 2017-03-03T21:42:52.100

1

Bash + JQ, 399 bytes

Note, this can almost certainly be golfed further, by optimizing the jq expression logic.

Golfed

curl api.stackexchange.com/2.2/questions/111735/answers?site=codegolf\&filter=withbody|zcat|jq -r '[.items[]|{o:.owner.display_name}+(.body|capture("^<h1>(?<l>.*?),.*?(?<b>\\d*)\\D*</h"))]|sort_by(.b|tonumber)|("Leaderboard\n\tAuthor\tLanguage\tSize",(keys[] as $i|.[$i]|"\($i+1).\t"+.o+"\t"+.l+"\t"+.b),"Winners by Language\nLanguage\tUser\tScore",(group_by(.l)|.[]|min_by(.b)|.l+"\t"+.o+"\t"+.b))'

Sample Output

Leaderboard
    Author  Language    Size
1.  zeppelin    Bash + JQ   399
2.  Tom JavaScript ES6  454
3.  Denis Ibaev Perl    456
4.  Vi. Python 3    860
Winners by Language
Language    User    Score
Bash + JQ   zeppelin    399
JavaScript ES6  Tom 454
Perl    Denis Ibaev 456
Python 3    Vi. 860

zeppelin

Posted 2017-02-28T23:19:51.563

Reputation: 7 884

What version of jq is needed? I get error: capture is not defined – Vi. – 2017-03-03T21:17:49.853

I don't see repeated ranks when I patch the question ID to 47338. The submission may become invalid as they arrive (and I may later add a special non-competing pseudo-answer just to force correct handling of ranks). – Vi. – 2017-03-03T21:37:26.593

Martin Ender's CJAM answer has "CJam, <s>28</s> 27 bytes". It should be interpreted as 27, not 28, like in the official leaderboard snippet. Or Pyth's 19 vs 22 bytes. – Vi. – 2017-03-03T21:55:56.807

Note the point "* Links to languages, striked out scores, etc. should be handleded." in the challenge rules. – Vi. – 2017-03-03T21:57:13.390

@Vi - Should be all set now (see https://jqplay.org/s/LuZfAn2Pxr). Pyth answer is still 22 bytes, as it is simply not formatted correctly (the last byte count comes first).

– zeppelin – 2017-03-03T22:30:39.910

Now the answer is valid (it would have become invalid without the change because of the Perl answer has dropped from 468 to 456 bytes), but equal score don't lead to equal rank in the linked sample: both J and CJam are 32 bytes, but got assigned 3'th and 4'th place arbitrarily. – Vi. – 2017-03-03T22:34:45.197

ockquote>

but equal score don't lead to equal rank in the linked sample

This has never been a part of your original specification, and have only been edited in recently.

Sorry, but I never adjust my answers to match any post-factum changes in the specs (you, as OP, should have thought out all the details in beforehand, not after other people have already invested time and effort in their answers).

(no offense meant). – zeppelin – 2017-03-03T22:44:10.743

Actually the executable leaderboard snippet in question can be loosely considered part of specification. In my recent edit I have just added the emphasis. What answers produce should be like what the JavaScript snippet produces (minus links, formatting), except of what is stated in "Not necessary". – Vi. – 2017-03-03T23:18:54.910

Note: I have updated my answer and now this answer produces incorrect result: there should be two "4." ranks, like in the official leaderboard snippet. – Vi. – 2017-03-18T03:23:13.300

1

Mathematica, 852 856 bytes

Uses the built-in JSONTools package. This isn't the kind of thing Mathematica's meant to be used for...so I used it!

p=Print;S=StringRiffle;L=Length;r=Range;out=Association@JSONTools`FromJSON[Import["http://api.stackexchange.com/2.2/questions/111735/answers?site=codegolf&filter=withbody"]];l={};i=Association/@(out["items"]);
(f=("body"/.i)[[#]];h=StringPosition[f,{"<h1>","</h1>"}];a="display_name"/.("owner"/.i)[[#]];s=StringSplit[StringTake[f,{h[[1]][[2]]+1,h[[2]][[1]]-1}],{",","<a>","</a>",">","<s>","</s>"," bytes","<strike>","</strike>"}];AppendTo[l,{a,s[[1]],ToExpression@s[[-1]]}])&/@r@L["body"/.i];l=SortBy[l,Last];o=r@L@l;If[l[[#]][[3]]==l[[#-1]][[3]],o[[#]]=o[[#-1]]]&/@r[2,L@l];
p@"Leaderboard"
p@"\tAuthor\tLanguage\tSize"
For[i=1,i<=L@l,i++,p[ToString@o[[i]]<>"."<>S[l[[i]][[#]]&/@r@3,"\t"]]]
l=SortBy[l,{#[[2]],#[[3]]}&];l=DeleteDuplicatesBy[l,#[[2]]&];
p@"Winners by Language"
p@"Language\tUser\tScore"
For[i=1,i<=L@l,i++,p[S[l[[i]][[#]]&/@{2,1,3},"\t"]]]

numbermaniac

Posted 2017-02-28T23:19:51.563

Reputation: 639

How do I test it? I get ReplaceAll::reps: <content of the downloaded Mathematica answer> is neither a list of replacement rules nor a valid dispatch table, and so cannot be used for replacing., after which the prints are {$Failed, $Failed, $Failed, $Failed, $Failed, bytes, $Failed, $Failed}}. – Vi. – 2017-03-07T09:09:50.477

Maybe the solution is broken by its own source code (which obviously contains <h1>)? – Vi. – 2017-03-07T09:16:13.017

@Vi. That's strange, it's working for me. Which question did you test it on? I don't think it's the source code breaking it, because the StackExchange API uses HTML escapes for the < and > automatically. – numbermaniac – 2017-03-08T07:30:18.877

This one, 111735. But I substituted the URL with /tmp/q.json where there is a pre-downloaded JSON reply. – Vi. – 2017-03-08T10:01:14.873

Output for 47338: https://paste.debian.net/918716/

– Vi. – 2017-03-08T10:06:17.267

I used 111735 to test if my program was working from the start, here's the output: http://imgur.com/i3XVWOj . In regards to 47338, it might actually be a glitch in Mathematica itself, because it fails on the very first Import statement: Import::infer: Cannot infer format of file answers?site=codegolf&filter=withbody. (Also, you're using Mathematica 10, I'm using 11.0.1)

– numbermaniac – 2017-03-09T00:25:30.590

You can workaround the glitch by downloading the JSON outside Mathematica and importing it from file. Does the answer handle links in the language name and ties in the score? – Vi. – 2017-03-09T11:13:18.453

Yes, it handles ties in the score, and it should handle links too. I see why it's failing on a pre-downloaded file: If you use a file, Mathematica is detecting it as a JSON, and converting it to a list of rules automatically. But if you get it from a URL, as is in the question, then you need the JSONTools package (as used) to make it a list of rules; so it breaks on files because it can't convert what Mathematica has already converted! – numbermaniac – 2017-03-10T07:54:42.053

If you remove the JSONTools around the Import, then reading it from a file will work correctly. – numbermaniac – 2017-04-24T09:34:22.323