I've been using a stack snippet for a while to generate leaderboards for my simpler or more popular code golf challenges. I want to share the code for this snippet here so others can use it more easily as well.
Feel free to put feature requests and bug reports in answers, or let me know in chat.
I'm providing both the minified code (for use in your challenges) and the unminified code (for reviewing/improving the code). Both snippets in this post point to Shotgun Numbers. At the bottom you'll also find the blurb I put in my challenges to explain the header formatting.
Features and Usage
- Shows two leaderboards: one overall leaderboard listing users with their languages by score; and a list of languages used with the best score (and user) per language. The latter is sorted by languages, not by scores (because the language ranking by scores can be read off the main leaderboard).
- Correctly handles ties in the main leaderboard (listing, e.g. 1. 2. 2. 4. ...).
- It takes the last number that is not struck out as the score. This allows keeping previous scores in the header (anywhere) as well as presenting partial scores like
Python 2, 34 + 71 = 105
. - Links for language names are allowed and show up in the leaderboard. For the by-language leaderboard, HTML is stripped when comparing languages for equality.
- One user can be chosen (via the
OVERRIDE_USER
ID in the code) who can override any headers using a comment. This user will usually be the author of the challenge. The comment has to start withOverride header:
- anything after that will be treated as the new header. If you want to exclude an answer from the leaderboard, just use something likeOverride header: invalid
. This feature is only intended as a last resort to avoid edit wars if some user refuses to use a correct header or delete an invalid answer.
To use the leaderboard in your own challenge, simply copy the snippet and search for the global variable definition of QUESTION_ID
and replace it with the ID found in the URL to your question and replace OVERRIDE_USER
with your own user ID.
Minified Code
var QUESTION_ID=47338;
var OVERRIDE_USER=8478;
var ANSWER_FILTER="!t)IWYnsLAZle2tQ3KqrVveCRJfxcRLe",COMMENT_FILTER="!)Q2B_A2kjfAiU78X(md6BoYk",answers=[],answers_hash,answer_ids,answer_page=1,more_answers=!0,comment_page;function answersUrl(d){return"https://api.stackexchange.com/2.2/questions/"+QUESTION_ID+"/answers?page="+d+"&pagesize=100&order=desc&sort=creation&site=codegolf&filter="+ANSWER_FILTER}function commentUrl(d,e){return"https://api.stackexchange.com/2.2/answers/"+e.join(";")+"/comments?page="+d+"&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(d){answers.push.apply(answers,d.items),answers_hash=[],answer_ids=[],d.items.forEach(function(e){e.comments=[];var f=+e.share_link.match(/\d+/);answer_ids.push(f),answers_hash[f]=e}),d.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(d){d.items.forEach(function(e){e.owner.user_id===OVERRIDE_USER&&answers_hash[e.post_id].comments.push(e)}),d.has_more?getComments():more_answers?getAnswers():process()}})}getAnswers();var SCORE_REG=function(){var d=String.raw`h\d`,e=String.raw`\-?\d+\.?\d*`,f=String.raw`[^\n<>]*`,g=String.raw`<s>${f}</s>|<strike>${f}</strike>|<del>${f}</del>`,h=String.raw`[^\n\d<>]*`,j=String.raw`<[^\n<>]+>`;return new RegExp(String.raw`<${d}>`+String.raw`\s*([^\n,]*[^\s,]),.*?`+String.raw`(${e})`+String.raw`(?=`+String.raw`${h}`+String.raw`(?:(?:${g}|${j})${h})*`+String.raw`</${d}>`+String.raw`)`)}(),OVERRIDE_REG=/^Override\s*header:\s*/i;function getAuthorName(d){return d.owner.display_name}function process(){var d=[];answers.forEach(function(n){var o=n.body;n.comments.forEach(function(q){OVERRIDE_REG.test(q.body)&&(o="<h1>"+q.body.replace(OVERRIDE_REG,"")+"</h1>")});var p=o.match(SCORE_REG);p&&d.push({user:getAuthorName(n),size:+p[2],language:p[1],link:n.share_link})}),d.sort(function(n,o){var p=n.size,q=o.size;return p-q});var e={},f=1,g=null,h=1;d.forEach(function(n){n.size!=g&&(h=f),g=n.size,++f;var o=jQuery("#answer-template").html();o=o.replace("{{PLACE}}",h+".").replace("{{NAME}}",n.user).replace("{{LANGUAGE}}",n.language).replace("{{SIZE}}",n.size).replace("{{LINK}}",n.link),o=jQuery(o),jQuery("#answers").append(o);var p=n.language;p=jQuery("<i>"+n.language+"</i>").text().toLowerCase(),e[p]=e[p]||{lang:n.language,user:n.user,size:n.size,link:n.link,uniq:p}});var j=[];for(var k in e)e.hasOwnProperty(k)&&j.push(e[k]);j.sort(function(n,o){return n.uniq>o.uniq?1:n.uniq<o.uniq?-1:0});for(var l=0;l<j.length;++l){var m=jQuery("#language-template").html(),k=j[l];m=m.replace("{{LANGUAGE}}",k.lang).replace("{{NAME}}",k.user).replace("{{SIZE}}",k.size).replace("{{LINK}}",k.link),m=jQuery(m),jQuery("#languages").append(m)}}
body{text-align:left!important}#answer-list{padding:10px;float:left}#language-list{padding:10px;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="https://cdn.sstatic.net/Sites/codegolf/primary.css?v=f52df912b654"> <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><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><table style="display: none"> <tbody id="answer-template"> <tr><td>{{PLACE}}</td><td>{{NAME}}</td><td>{{LANGUAGE}}</td><td><a href="{{LINK}}">{{SIZE}}</a></td></tr></tbody> </table> <table style="display: none"> <tbody id="language-template"> <tr><td>{{LANGUAGE}}</td><td>{{NAME}}</td><td><a href="{{LINK}}">{{SIZE}}</a></td></tr></tbody> </table>
Unminified Code
/* Configuration */
var QUESTION_ID = 47338; // Obtain this from the url
// It will be like https://XYZ.stackexchange.com/questions/QUESTION_ID/... on any question page
var ANSWER_FILTER = "!t)IWYnsLAZle2tQ3KqrVveCRJfxcRLe";
var COMMENT_FILTER = "!)Q2B_A2kjfAiU78X(md6BoYk";
var OVERRIDE_USER = 8478; // This should be the user ID of the challenge author.
/* App */
var answers = [], answers_hash, answer_ids, answer_page = 1, more_answers = true, comment_page;
function answersUrl(index) {
return "https://api.stackexchange.com/2.2/questions/" + QUESTION_ID + "/answers?page=" + index + "&pagesize=100&order=desc&sort=creation&site=codegolf&filter=" + ANSWER_FILTER;
}
function commentUrl(index, answers) {
return "https://api.stackexchange.com/2.2/answers/" + answers.join(';') + "/comments?page=" + index + "&pagesize=100&order=desc&sort=creation&site=codegolf&filter=" + COMMENT_FILTER;
}
function getAnswers() {
jQuery.ajax({
url: answersUrl(answer_page++),
method: "get",
dataType: "jsonp",
crossDomain: true,
success: function (data) {
answers.push.apply(answers, data.items);
answers_hash = [];
answer_ids = [];
data.items.forEach(function(a) {
a.comments = [];
var id = +a.share_link.match(/\d+/);
answer_ids.push(id);
answers_hash[id] = a;
});
if (!data.has_more) more_answers = false;
comment_page = 1;
getComments();
}
});
}
function getComments() {
jQuery.ajax({
url: commentUrl(comment_page++, answer_ids),
method: "get",
dataType: "jsonp",
crossDomain: true,
success: function (data) {
data.items.forEach(function(c) {
if (c.owner.user_id === OVERRIDE_USER)
answers_hash[c.post_id].comments.push(c);
});
if (data.has_more) getComments();
else if (more_answers) getAnswers();
else process();
}
});
}
getAnswers();
var SCORE_REG = (function(){
var headerTag = String.raw `h\d`
var score = String.raw `\-?\d+\.?\d*` // with negative/floating-point support
var normalText = String.raw `[^\n<>]*` // no HTML tag, no newline
var strikethrough = String.raw `<s>${normalText}</s>|<strike>${normalText}</strike>|<del>${normalText}</del>`
var noDigitText = String.raw `[^\n\d<>]*`
var htmlTag = String.raw `<[^\n<>]+>`
return new RegExp(
String.raw `<${headerTag}>`+
String.raw `\s*([^\n,]*[^\s,]),.*?`+
String.raw `(${score})`+
String.raw `(?=`+
String.raw `${noDigitText}`+
String.raw `(?:(?:${strikethrough}|${htmlTag})${noDigitText})*`+
String.raw `</${headerTag}>`+
String.raw `)`
);
})();
var OVERRIDE_REG = /^Override\s*header:\s*/i;
function getAuthorName(a) {
return a.owner.display_name;
}
function process() {
var valid = [];
answers.forEach(function(a) {
var body = a.body;
a.comments.forEach(function(c) {
if(OVERRIDE_REG.test(c.body))
body = '<h1>' + c.body.replace(OVERRIDE_REG, '') + '</h1>';
});
var match = body.match(SCORE_REG);
if (match)
valid.push({
user: getAuthorName(a),
size: +match[2],
language: match[1],
link: a.share_link,
});
});
valid.sort(function (a, b) {
var aB = a.size,
bB = b.size;
return aB - bB
});
var languages = {};
var place = 1;
var lastSize = null;
var lastPlace = 1;
valid.forEach(function (a) {
if (a.size != lastSize)
lastPlace = place;
lastSize = a.size;
++place;
var answer = jQuery("#answer-template").html();
answer = answer.replace("{{PLACE}}", lastPlace + ".")
.replace("{{NAME}}", a.user)
.replace("{{LANGUAGE}}", a.language)
.replace("{{SIZE}}", a.size)
.replace("{{LINK}}", a.link);
answer = jQuery(answer);
jQuery("#answers").append(answer);
var lang = a.language;
lang = jQuery('<i>' + a.language + '</i>').text().toLowerCase();
languages[lang] = languages[lang] || {lang: a.language, user: a.user, size: a.size, link: a.link, uniq: lang};
});
var langs = [];
for (var lang in languages)
if (languages.hasOwnProperty(lang))
langs.push(languages[lang]);
langs.sort(function (a, b) {
if (a.uniq > b.uniq) return 1;
if (a.uniq < b.uniq) return -1;
return 0;
});
for (var i = 0; i < langs.length; ++i)
{
var language = jQuery("#language-template").html();
var lang = langs[i];
language = language.replace("{{LANGUAGE}}", lang.lang)
.replace("{{NAME}}", lang.user)
.replace("{{SIZE}}", lang.size)
.replace("{{LINK}}", lang.link);
language = jQuery(language);
jQuery("#languages").append(language);
}
}
body { text-align: left !important}
#answer-list {
padding: 10px;
float: left;
}
#language-list {
padding: 10px;
float: left;
}
table thead {
font-weight: bold;
}
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="https://cdn.sstatic.net/Sites/codegolf/primary.css?v=f52df912b654">
<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>
<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>
<table style="display: none">
<tbody id="answer-template">
<tr><td>{{PLACE}}</td><td>{{NAME}}</td><td>{{LANGUAGE}}</td><td><a href="{{LINK}}">{{SIZE}}</a></td></tr>
</tbody>
</table>
<table style="display: none">
<tbody id="language-template">
<tr><td>{{LANGUAGE}}</td><td>{{NAME}}</td><td><a href="{{LINK}}">{{SIZE}}</a></td></tr>
</tbody>
</table>
Info Section
You can copy the following Markdown into your challenge as instructions for your participants.
## Leaderboards
Here is a Stack Snippet to generate both a regular leaderboard and an overview of winners by language.
To make sure that your answer shows up, please start your answer with a headline, using the following Markdown template:
# Language Name, N bytes
where `N` is the size of your submission. If you improve your score, you *can* keep old scores in the headline, by striking them through. For instance:
# Ruby, <s>104</s> <s>101</s> 96 bytes
If there you want to include multiple numbers in your header (e.g. because your score is the sum of two files or you want to list interpreter flag penalties separately), make sure that the actual score is the *last* number in the header:
# Perl, 43 + 2 (-p flag) = 45 bytes
You can also make the language name a link which will then show up in the leaderboard snippet:
# [><>](http://esolangs.org/wiki/Fish), 121 bytes
11 Answers 11
Feature request: Column rearrangement
In my opinion, the per-language results should be on the left since we emphasize competition within each language. It would simply be a swapping of the current left and right columns.
-
2\$\begingroup\$ I'd put together a prototype but I'm not good with computer \$\endgroup\$2016年05月25日 20:41:30 +00:00Commented May 25, 2016 at 20:41
-
2\$\begingroup\$ Once we see vote support I'm sure there are lots of people who can make the change. If no one volunteers I need some practice myself. \$\endgroup\$trichoplax is on Codidact now– trichoplax is on Codidact now2016年05月25日 20:42:41 +00:00Commented May 25, 2016 at 20:42
-
\$\begingroup\$ status-complete. \$\endgroup\$user202729– user2027292018年05月28日 13:12:04 +00:00Commented May 28, 2018 at 13:12
Layout problem
I'm seeing overlapping of text in some cases. For example, this challenge.
Overlapped section of leaderboard
The Winners by Language section starts too far to the left, overlapping the Link from the Leaderboard section.
I'm not sure why this only happens sometimes. Two potential solutions:
- Shift the Winners by Language section to the right.
- Use manatwork's suggestion of making the size a link to the solution.
I don't see any ambiguity in making the size a link, as I can't imagine anything else it would link to. It does mean the link is no longer explicit though. Does anyone have any other arguments either way?
Update
Although I am still in favour of making the size a link to the solution, Martin pointed out that this doesn't solve the problem. The Winners by Language section is at a fixed horizontal position, so if the Leaderboard section is too wide due to a long author or language name, there could still be overlap even without a separate link column.
So ideally instead of just shifting the Winners by Language right a bit, it needs to be sensitive to the width of the Leaderboard section. I propose work on this be delayed until after the two sections swap sides, once that proposal gets enough votes.
-
\$\begingroup\$ The leaderboard script in the challenge you linked tries to include the old stylesheet that is not available anymore since PPCG graduated. I would say, that should be changed in first place. \$\endgroup\$manatwork– manatwork2016年05月26日 07:36:29 +00:00Commented May 26, 2016 at 7:36
-
\$\begingroup\$ @manatwork I've upvoted your suggestion of a stylesheet redirect to deal with future changes. While we wait to see if that will happen, I guess we just need to edit the snippet to reflect the current stylesheet so it works for now. \$\endgroup\$trichoplax is on Codidact now– trichoplax is on Codidact now2016年05月26日 11:45:18 +00:00Commented May 26, 2016 at 11:45
-
\$\begingroup\$ status-complete. \$\endgroup\$user202729– user2027292018年05月28日 13:48:07 +00:00Commented May 28, 2018 at 13:48
Bug
Although the documentation says that strikeout is supported, it only supports <s>
and not <strike>
or <del>
.
-
1\$\begingroup\$ @MartinEnder any confirmation, disconfirmation, or bug fix down the pipeline? \$\endgroup\$noɥʇʎԀʎzɐɹƆ– noɥʇʎԀʎzɐɹƆ2016年06月22日 19:21:40 +00:00Commented Jun 22, 2016 at 19:21
-
\$\begingroup\$ Done. (MartinEnder told me that I can edit the post here) \$\endgroup\$user202729– user2027292018年05月31日 04:56:56 +00:00Commented May 31, 2018 at 4:56
I would like to suggest a couple of minor changes. In order of appearance:
JavaScript
Preserve sign of size by adding
-?
to the size's group in SCORE_REG (ex: "3" vs. "-3")var SCORE_REG = /<h\d>\s*([^\n,]*[^\s,]),.*?(-?\d+)(?=[^\n\d<>]*(?:<(?:s>[^\n<>]*<\/s>|[^\n<>]+>)[^\n\d<>]*)*<\/h\d>)/;
HTML-ify plain text language name, so can
jQuery()
-fy it and get rid of SGML character entities when getting itstext()
(eg: "><>" vs. "><>")if (! /<a/.test(lang)) lang = '<i>' + lang + '</i>';
Transform the langage name in
toLowerCase()
(eg: "JavaScript" vs. "Javascript")lang = jQuery(lang).text().toLowerCase();
Keep the stripped language with answer data
languages[lang] = languages[lang] || {lang: a.language, user: a.user, size: a.size, link: a.link, uniq: lang};
Sort by the stripped language name
if (a.uniq > b.uniq) return 1; if (a.uniq < b.uniq) return -1;
HTML
Suggested changes applied to the following snippet, configured to show Create output twice the length of the code's score:
/* Configuration */
var QUESTION_ID = 59436; // Obtain this from the url
// It will be like https://XYZ.stackexchange.com/questions/QUESTION_ID/... on any question page
var ANSWER_FILTER = "!t)IWYnsLAZle2tQ3KqrVveCRJfxcRLe";
var COMMENT_FILTER = "!)Q2B_A2kjfAiU78X(md6BoYk";
var OVERRIDE_USER = 41505; // This should be the user ID of the challenge author.
/* App */
var answers = [], answers_hash, answer_ids, answer_page = 1, more_answers = true, comment_page;
function answersUrl(index) {
return "https://api.stackexchange.com/2.2/questions/" + QUESTION_ID + "/answers?page=" + index + "&pagesize=100&order=desc&sort=creation&site=codegolf&filter=" + ANSWER_FILTER;
}
function commentUrl(index, answers) {
return "https://api.stackexchange.com/2.2/answers/" + answers.join(';') + "/comments?page=" + index + "&pagesize=100&order=desc&sort=creation&site=codegolf&filter=" + COMMENT_FILTER;
}
function getAnswers() {
jQuery.ajax({
url: answersUrl(answer_page++),
method: "get",
dataType: "jsonp",
crossDomain: true,
success: function (data) {
answers.push.apply(answers, data.items);
answers_hash = [];
answer_ids = [];
data.items.forEach(function(a) {
a.comments = [];
var id = +a.share_link.match(/\d+/);
answer_ids.push(id);
answers_hash[id] = a;
});
if (!data.has_more) more_answers = false;
comment_page = 1;
getComments();
}
});
}
function getComments() {
jQuery.ajax({
url: commentUrl(comment_page++, answer_ids),
method: "get",
dataType: "jsonp",
crossDomain: true,
success: function (data) {
data.items.forEach(function(c) {
if (c.owner.user_id === OVERRIDE_USER)
answers_hash[c.post_id].comments.push(c);
});
if (data.has_more) getComments();
else if (more_answers) getAnswers();
else process();
}
});
}
getAnswers();
var SCORE_REG = /<h\d>\s*([^\n,]*[^\s,]),.*?(-?\d+)(?=[^\n\d<>]*(?:<(?:s>[^\n<>]*<\/s>|[^\n<>]+>)[^\n\d<>]*)*<\/h\d>)/;
var OVERRIDE_REG = /^Override\s*header:\s*/i;
function getAuthorName(a) {
return a.owner.display_name;
}
function process() {
var valid = [];
answers.forEach(function(a) {
var body = a.body;
a.comments.forEach(function(c) {
if(OVERRIDE_REG.test(c.body))
body = '<h1>' + c.body.replace(OVERRIDE_REG, '') + '</h1>';
});
var match = body.match(SCORE_REG);
if (match)
valid.push({
user: getAuthorName(a),
size: +match[2],
language: match[1],
link: a.share_link,
});
});
valid.sort(function (a, b) {
var aB = a.size,
bB = b.size;
return aB - bB
});
var languages = {};
var place = 1;
var lastSize = null;
var lastPlace = 1;
valid.forEach(function (a) {
if (a.size != lastSize)
lastPlace = place;
lastSize = a.size;
++place;
var answer = jQuery("#answer-template").html();
answer = answer.replace("{{PLACE}}", lastPlace + ".")
.replace("{{NAME}}", a.user)
.replace("{{LANGUAGE}}", a.language)
.replace("{{SIZE}}", a.size)
.replace("{{LINK}}", a.link);
answer = jQuery(answer);
jQuery("#answers").append(answer);
var lang = a.language;
if (! /<a/.test(lang)) lang = '<i>' + lang + '</i>';
lang = jQuery(lang).text().toLowerCase();
languages[lang] = languages[lang] || {lang: a.language, user: a.user, size: a.size, link: a.link, uniq: lang};
});
var langs = [];
for (var lang in languages)
if (languages.hasOwnProperty(lang))
langs.push(languages[lang]);
langs.sort(function (a, b) {
if (a.uniq > b.uniq) return 1;
if (a.uniq < b.uniq) return -1;
return 0;
});
for (var i = 0; i < langs.length; ++i)
{
var language = jQuery("#language-template").html();
var lang = langs[i];
language = language.replace("{{LANGUAGE}}", lang.lang)
.replace("{{NAME}}", lang.user)
.replace("{{SIZE}}", lang.size)
.replace("{{LINK}}", lang.link);
language = jQuery(language);
jQuery("#languages").append(language);
}
}
body { text-align: left !important}
#answer-list {
padding: 10px;
width: 290px;
float: left;
}
#language-list {
padding: 10px;
width: 290px;
float: left;
}
table thead {
font-weight: bold;
}
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/Sites/codegolf/primary.css?v=f52df912b654">
<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><a href="{{LINK}}">{{SIZE}}</a></td></tr>
</tbody>
</table>
<table style="display: none">
<tbody id="language-template">
<tr><td>{{LANGUAGE}}</td><td>{{NAME}}</td><td><a href="{{LINK}}">{{SIZE}}</a></td></tr>
</tbody>
</table>
-
\$\begingroup\$ (1) the score is only negative if there is any bonus. I don't think that's very relevant. (2) what's the difference between the css? \$\endgroup\$user202729– user2027292018年05月28日 13:54:42 +00:00Commented May 28, 2018 at 13:54
-
\$\begingroup\$ @user202729, now is different again. SE devs changed the stylesheet URL in meantime. That is why would be nice to have a Permaproxy to the current stylesheet. \$\endgroup\$manatwork– manatwork2018年05月28日 14:32:37 +00:00Commented May 28, 2018 at 14:32
-
\$\begingroup\$ Should be done now. \$\endgroup\$user202729– user2027292018年05月31日 05:22:42 +00:00Commented May 31, 2018 at 5:22
Soft-linkable Leaderboard
Adding this as an answer with a single snippet in order to simply reuse the leaderboard snippet using Calvin'sHobbies' Arbitrary stack snippet loader
To use this, simply make sure that you have the snippet from the above link in your post with the configurations as below:
- site = 'meta.codegolf'
- postID = 5314
- isAnswer = true
and an additional configuration required by the leaderboard snippet:
- QUESTION_ID = id of the question for which leaderboard is required. Ex. 47338
/* Configuration */
// It will be like http://XYZ.stackexchange.com/questions/QUESTION_ID/... on any question page
var ANSWER_FILTER = "!t)IWYnsLAZle2tQ3KqrVveCRJfxcRLe";
/* App */
var answers = [], page = 1;
function answersUrl(index) {
return "http://api.stackexchange.com/2.2/questions/" + QUESTION_ID + "/answers?page=" + index + "&pagesize=100&order=desc&sort=creation&site=codegolf&filter=" + ANSWER_FILTER;
}
function getAnswers() {
jQuery.ajax({
url: answersUrl(page++),
method: "get",
dataType: "jsonp",
crossDomain: true,
success: function (data) {
answers.push.apply(answers, data.items);
if (data.has_more) getAnswers();
else process();
}
});
}
getAnswers();
var SIZE_REG = /\d+(?=[^\d&]*(?:<(?:s>[^&]*<\/s>|[^&]+>)[^\d&]*)*$)/;
var NUMBER_REG = /\d+/;
var LANGUAGE_REG = /^#*\s*([^,]+)/;
function shouldHaveHeading(a) {
var pass = false;
var lines = a.body_markdown.split("\n");
try {
pass |= /^#/.test(a.body_markdown);
pass |= ["-", "="]
.indexOf(lines[1][0]) > -1;
pass &= LANGUAGE_REG.test(a.body_markdown);
} catch (ex) {}
return pass;
}
function shouldHaveScore(a) {
var pass = false;
try {
pass |= SIZE_REG.test(a.body_markdown.split("\n")[0]);
} catch (ex) {}
return pass;
}
function getAuthorName(a) {
return a.owner.display_name;
}
function process() {
answers = answers.filter(shouldHaveScore)
.filter(shouldHaveHeading);
answers.sort(function (a, b) {
var aB = +(a.body_markdown.split("\n")[0].match(SIZE_REG) || [Infinity])[0],
bB = +(b.body_markdown.split("\n")[0].match(SIZE_REG) || [Infinity])[0];
return aB - bB
});
var languages = {};
var place = 1;
var lastSize = null;
var lastPlace = 1;
answers.forEach(function (a) {
var headline = a.body_markdown.split("\n")[0];
//console.log(a);
var answer = jQuery("#answer-template").html();
var num = headline.match(NUMBER_REG)[0];
var size = (headline.match(SIZE_REG)||[0])[0];
var language = headline.match(LANGUAGE_REG)[1];
var user = getAuthorName(a);
if (size != lastSize)
lastPlace = place;
lastSize = size;
++place;
answer = answer.replace("{{PLACE}}", lastPlace + ".")
.replace("{{NAME}}", user)
.replace("{{LANGUAGE}}", language)
.replace("{{SIZE}}", size)
.replace("{{LINK}}", a.share_link);
answer = jQuery(answer)
jQuery("#answers").append(answer);
languages[language] = languages[language] || {lang: language, user: user, size: size, link: a.share_link};
});
var langs = [];
for (var lang in languages)
if (languages.hasOwnProperty(lang))
langs.push(languages[lang]);
langs.sort(function (a, b) {
if (a.lang > b.lang) return 1;
if (a.lang < b.lang) return -1;
return 0;
});
for (var i = 0; i < langs.length; ++i)
{
var language = jQuery("#language-template").html();
var lang = langs[i];
language = language.replace("{{LANGUAGE}}", lang.lang)
.replace("{{NAME}}", lang.user)
.replace("{{SIZE}}", lang.size)
.replace("{{LINK}}", lang.link);
language = jQuery(language);
jQuery("#languages").append(language);
}
}
body { text-align: left !important}
#answer-list {
padding: 10px;
width: 290px;
float: left;
}
#language-list {
padding: 10px;
width: 290px;
float: left;
}
table thead {
font-weight: bold;
}
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>
-
\$\begingroup\$ I'm no longer sure this is a good idea. Now I can never make a breaking change to the leaderboard snippet without affecting an unknown number of older challenges. \$\endgroup\$2015年08月19日 08:32:36 +00:00Commented Aug 19, 2015 at 8:32
-
\$\begingroup\$ @MartinBüttner Your breaking change should go in a new snippet. Most probably its a new feature addition, or a new syntax.. That anyways is not needed by the current users of the script. \$\endgroup\$Optimizer– Optimizer2015年08月19日 09:22:15 +00:00Commented Aug 19, 2015 at 9:22
-
\$\begingroup\$ I don't intend to post a new question or answer every time I add a feature... that just clutters up meta. \$\endgroup\$2015年08月19日 09:23:52 +00:00Commented Aug 19, 2015 at 9:23
-
\$\begingroup\$ Well, you cannot have the cake and eat it too. You want re usability, but want to make breaking change too. Your solution works with answers too, so why not put a new answer to an existing question \$\endgroup\$Optimizer– Optimizer2015年08月19日 09:26:35 +00:00Commented Aug 19, 2015 at 9:26
-
\$\begingroup\$ Is there any way to make Helka's snippet point to a previous version in the revision history rather than needing separate answers for new versions? \$\endgroup\$trichoplax is on Codidact now– trichoplax is on Codidact now2016年04月05日 15:37:10 +00:00Commented Apr 5, 2016 at 15:37
Formatting - Set a default font
Let's be honest. Most browsers' default font (usually Times New Roman) is pretty ugly. Let's fix that. And propose fonts please!
-
\$\begingroup\$ Roboto? Sans Serif? \$\endgroup\$Matthew Roh– Matthew Roh2017年02月12日 07:10:26 +00:00Commented Feb 12, 2017 at 7:10
-
\$\begingroup\$ @SIGSEGV Roboto is quite nice \$\endgroup\$user41805– user418052017年03月21日 16:31:42 +00:00Commented Mar 21, 2017 at 16:31
-
\$\begingroup\$ Also possible (and elegant): Noto \$\endgroup\$Matthew Roh– Matthew Roh2017年03月21日 16:35:34 +00:00Commented Mar 21, 2017 at 16:35
-
\$\begingroup\$ I find Times New Roman pretty readable... \$\endgroup\$user202729– user2027292018年05月28日 14:20:34 +00:00Commented May 28, 2018 at 14:20
Makes links target="_top"
or target="_blank"
That way they don't open in the snippet. I find that annoying.
-
\$\begingroup\$ Doesn't seem to work for me. Tested. \$\endgroup\$user202729– user2027292017年12月27日 13:49:45 +00:00Commented Dec 27, 2017 at 13:49
Permaproxy to the current stylesheet
(This feature request is not for the Leaderboare Snippet maintainer, but for the PPCG proxy owners.)
The Leaderboard Snippet includes the same stylesheet as the PPCG site itself uses. That stylesheet's URL can not be acquired programmatically by the snippet, so it is hardcoded:
<link rel="stylesheet" type="text/css" href="//cdn.sstatic.net/codegolf/all.css?v=83c949450c8b">
And this is how the snippet was included in 150++ questions.
Then PPCG graduated and the site's stylesheet changed:
<link rel="stylesheet" type="text/css" href="//cdn.sstatic.net/Sites/codegolf/primary.css?v=f52df912b654">
Now all those questions posted before graduation (and even those after) left without stylesheet and their layout suffered, ranging between ugly and unreadable/unclickable.
As further style changes will probably happen (see Graduation Design Ideas's popularity), would be nice if one of the PPCG proxy owners would set up a permanent redirect towards the current stylesheet used by the site:
Redirect /official.css //cdn.sstatic.net/Sites/codegolf/primary.css?v=f52df912b654
So Leaderboard Snippet could then include that stylesheet with permanent URL:
<link rel="stylesheet" type="text/css" href="//pp.cg/official.css">
Tip: Leader board for cops n' robbers
- To sort ascending: search for
r-a
- there should be one instance, replace it witha-r
. - To ignore cracked entries: search for
SCORE_REG
- there should be two instances, the second one is (at the time of this answer) the regex:
/<h\d>\s*([^\n,]*[^\s,]),.*?(\d+)(?=[^\n\d<>]*(?:<(?:s>[^\n<>]*<\/s>|[^\n<>]+>)*[^\n\d<>]*)*<\/h\d>)/
replace it with:
/<h\d>\s*([^\n,]*[^\s,]),.*?(\d+)(?=[^\n\d<>]*(?:<(?:s>[^\n<>]*<\/s>|[^\n<>]+>)[^\n\d<>]*)^(?:(?!cracked)\i.)*<\/h\d>)/
(that is add^(?:(?!cracked)\i.)
to not include those headers containing "cracked" with any case (\i
).
Please edit to fix bugs, regex is not my forté.
- I did notice it broke with the "bytes" text of the header being a link, so maybe ^(?:(?!cracked)\i.)
should actually be placed earlier?
This answer can be used for experimenting with Martin's snippet. You may edit this to your own desire
<script>var QUESTION_ID=99026,OVERRIDE_USER=0;</script><style>body{text-align:left!important}#answer-list{padding:10px;float:left}#language-list{padding:10px;float:left}table thead{font-weight:700}table td{padding:5px}</style><script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script><script>var ANSWER_FILTER="!t)IWYnsLAZle2tQ3KqrVveCRJfxcRLe",COMMENT_FILTER="!)Q2B_A2kjfAiU78X(md6BoYk",answers=[],answers_hash,answer_ids,answer_page=1,more_answers=!0,comment_page;function answersUrl(d){return"https://api.stackexchange.com/2.2/questions/"+QUESTION_ID+"/answers?page="+d+"&pagesize=100&order=desc&sort=creation&site=codegolf&filter="+ANSWER_FILTER}function commentUrl(d,e){return"https://api.stackexchange.com/2.2/answers/"+e.join(";")+"/comments?page="+d+"&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(d){answers.push.apply(answers,d.items),answers_hash=[],answer_ids=[],d.items.forEach(function(e){e.comments=[];var f=+e.share_link.match(/\d+/);answer_ids.push(f),answers_hash[f]=e}),d.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(d){d.items.forEach(function(e){e.owner.user_id===OVERRIDE_USER&&answers_hash[e.post_id].comments.push(e)}),d.has_more?getComments():more_answers?getAnswers():process()}})}getAnswers();var SCORE_REG=function(){var d=String.raw`h\d`,e=String.raw`\-?\d+\.?\d*`,f=String.raw`[^\n<>]*`,g=String.raw`<s>${f}</s>|<strike>${f}</strike>|<del>${f}</del>`,h=String.raw`[^\n\d<>]*`,j=String.raw`<[^\n<>]+>`;return new RegExp(String.raw`<${d}>`+String.raw`\s*([^\n,]*[^\s,]),.*?`+String.raw`(${e})`+String.raw`(?=`+String.raw`${h}`+String.raw`(?:(?:${g}|${j})${h})*`+String.raw`</${d}>`+String.raw`)`)}(),OVERRIDE_REG=/^Override\s*header:\s*/i;function getAuthorName(d){return d.owner.display_name}function process(){var d=[];answers.forEach(function(n){var o=n.body;n.comments.forEach(function(q){OVERRIDE_REG.test(q.body)&&(o="<h1>"+q.body.replace(OVERRIDE_REG,"")+"</h1>")});var p=o.match(SCORE_REG);p&&d.push({user:getAuthorName(n),size:+p[2],language:p[1],link:n.share_link})}),d.sort(function(n,o){var p=n.size,q=o.size;return p-q});var e={},f=1,g=null,h=1;d.forEach(function(n){n.size!=g&&(h=f),g=n.size,++f;var o=jQuery("#answer-template").html();o=o.replace("{{PLACE}}",h+".").replace("{{NAME}}",n.user).replace("{{LANGUAGE}}",n.language).replace("{{SIZE}}",n.size).replace("{{LINK}}",n.link),o=jQuery(o),jQuery("#answers").append(o);var p=n.language;p=jQuery("<i>"+n.language+"</i>").text().toLowerCase(),e[p]=e[p]||{lang:n.language,user:n.user,size:n.size,link:n.link,uniq:p}});var j=[];for(var k in e)e.hasOwnProperty(k)&&j.push(e[k]);j.sort(function(n,o){return n.uniq>o.uniq?1:n.uniq<o.uniq?-1:0});for(var l=0;l<j.length;++l){var m=jQuery("#language-template").html(),k=j[l];m=m.replace("{{LANGUAGE}}",k.lang).replace("{{NAME}}",k.user).replace("{{SIZE}}",k.size).replace("{{LINK}}",k.link),m=jQuery(m),jQuery("#languages").append(m)}}</script><link rel="stylesheet" type="text/css" href="https://cdn.sstatic.net/Sites/codegolf/primary.css?v=f52df912b654"> <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><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><table style="display: none"> <tbody id="answer-template"> <tr><td>{{PLACE}}</td><td>{{NAME}}</td><td>{{LANGUAGE}}</td><td><a href="{{LINK}}">{{SIZE}}</a></td></tr></tbody> </table> <table style="display: none"> <tbody id="language-template"> <tr><td>{{LANGUAGE}}</td><td>{{NAME}}</td><td><a href="{{LINK}}">{{SIZE}}</a></td></tr></tbody> </table>
Tie break on first posted
Ideally, if there is a joint first place I'd like to see the first posted appear at the top. This is complicated by the fact that the time of the post is not necessarily the time that the code first had that length.
I'm not suggesting the snippet do an analysis of the edit histories, but I'm posting this in case there's a simpler way. Maybe a comment override?
-
3\$\begingroup\$ Automating this definitely seems tricky, because not only would you have to figure out in the edit history when the score was first reached, but it might also happen that the score was reached before, but the answer was invalid at the time and only a later edit actually achieved the score with a valid solution. (Also, grace period...) \$\endgroup\$2016年05月25日 10:45:22 +00:00Commented May 25, 2016 at 10:45
-
\$\begingroup\$ It's even worse than I thought then... So I guess the only way would be with a comment override of some kind, but unless this answer gets lots of upvotes I'm guessing that's unnecessary effort. \$\endgroup\$trichoplax is on Codidact now– trichoplax is on Codidact now2016年05月25日 10:51:40 +00:00Commented May 25, 2016 at 10:51
-
\$\begingroup\$ Good point about the grace period - I guess that means even a human observer can't tell for certain which was first if the edit times are within 5 minutes of each other. \$\endgroup\$trichoplax is on Codidact now– trichoplax is on Codidact now2016年05月25日 10:53:41 +00:00Commented May 25, 2016 at 10:53
ANSWER_FILTER
isn't defined in the minified code, but it is in the unminified code. (The minified code still works though.) Also, what is it? \$\endgroup\$ANSWER_FILTER
is somewhere at the end. (In fact,QUESTION_ID
also ended up there, but I moved that back to the front so it would be easier to find.) \$\endgroup\$<code><pre>
in markdown to get a code block. \$\endgroup\$<del>
instead of<s>
? \$\endgroup\$del
instead ofs
anyway? We're still on CodeGolf.SE after all! :P \$\endgroup\$<del>
is the right tag to use. Since an element was removed (in this case, the old byte count). Visually, it makes no difference. \$\endgroup\$\d+
be changed to\-?\d+
so it accepts negative numbers (for bonuses and such)? \$\endgroup\$http://api.stackexchange.com/
with//api.stackexchange.com/
(a protocol-relative URL) so the leaderboard works on https://codegolf.stackexchange.com without having to disable mixed content blocking every time? \$\endgroup\$<!--
seems to prevent it. \$\endgroup\$\d+
in that long regex to\d+(?:\.\d+)
but I can't tell you off the top of my head how robust that will be. \$\endgroup\$