Take me to your leaderboard

11

1

Have you ever found a good challenge to answer, answered it and then found out somebody posted a solution with a better score in the same language?

Challenge

Write a program/function that takes a PPCG question ID and outputs a leaderboard.

A leaderboard consists of the rank, the language, the score, the author and the URL of each answer. This can be output as a newline delimited string, a list of strings, a "dictionary", etc..

  • Rank
    • The rank of an answer is its position in the score hierarchy (lowest first).
    • Answers with shared scores must have the same rank.
  • Language
    • The name of the language is always between the last # on the first line and the first ,.
    • It may be a link to documentation, which must be handled by taking the user-facing text.
  • Score
    • The score of a answer is the last number on the first line.
    • It may be a decimal number.
  • Author
    • The author of an answer is the display name of the user that posted it.
  • URL

Specifications

  • Standard I/O rules apply.
  • Standard loopholes are forbidden.
  • This challenge is not about finding the shortest approach in all languages, rather, it is about finding the shortest approach in each language.
  • Your code will be scored in bytes, usually in the encoding UTF-8, unless specified otherwise.
  • Explanations, even for "practical" languages, are encouraged.

Test cases

Note that these show example output formats which need not be strictly followed. These examples were taken at the time of sandboxing, they might be outdated.

Input: 28821

1. PCRE flavor, score 40 by jimmy23013 (https://codegolf.stackexchange.com/a/31863)

Input: 92944

1. Jelly, score 12 by Dennis (https://codegolf.stackexchange.com/a/92958)
2. Befunge-98, score 38 by Hactar (https://codegolf.stackexchange.com/a/98334)
3. ><>, score 41 by Sp3000 (https://codegolf.stackexchange.com/a/92980)
4. TovTovTov, score 810147050 by Yaniv (https://codegolf.stackexchange.com/a/93048)

Input: 47604*

1. Pyth, score 28.08 by Jakube (https://codegolf.stackexchange.com/a/47642)
2. APL, score 39.6 by jimmy23013 (https://codegolf.stackexchange.com/a/47606)

Input: 133793

1. Lua, score 9.06 by TehPers (https://codegolf.stackexchange.com/a/135775)
2. C# (.NET Core), score 10.20 by Kamil Drakari (https://codegolf.stackexchange.com/a/135764)
3. Cubically, score 86.98 by Kamil Drakari (https://codegolf.stackexchange.com/a/137695)

*Note that this challenge has 3 answers, however the GolfScript answer doesn't have a comma in the header which makes it undefined behaviour.

In a few better formats:

28821, 92944, 47604, 133793

28821 92944 47604 133793

28821
92944
47604
133793

This challenge was sandboxed.

totallyhuman

Posted 2017-08-06T23:07:24.797

Reputation: 15 378

Question was closed 2017-08-09T16:42:25.880

Is using libraries allowed? For example the HTML Agility Pack for C#.

– Ian H. – 2017-08-07T07:34:22.097

@IanH. That is the same as always, you can use libraries but make sure to specify that in the header. Wouldn't want it to mislead people on leaderboards. ;) – totallyhuman – 2017-08-07T07:43:52.870

Does the PPCG default leader board count as a language? If yes does it count as 1 byte? :P – TheLethalCoder – 2017-08-07T08:58:53.307

@TheLethalCoder You can golf the stack snippet and post it as a JS answer (no, it does not count as 1 byte :P). However, I'm sure you guys can come up with more interesting answers. – totallyhuman – 2017-08-07T13:03:58.233

@Downvoter Do you, you know, have a reason to downvote? Just curious... – totallyhuman – 2017-08-07T13:37:06.957

3I like your title :P – Daniel – 2017-08-07T14:31:14.207

Also, is 12.000000 ok instead of 12 because of fickle float precision? – Daniel – 2017-08-07T21:01:57.467

@Dopapp Yes, that is fine. – totallyhuman – 2017-08-07T21:02:29.223

For questions that contain answers with poorly formed headers (as in test case 47604), is the allowance for undefined behaviour applied just to those answers or does it cover the full output for the question? How should outputs that contain answers with the same score be numbered? Consider, for an example, a question with 2 answers sharing second place, should the ranking go 1,2,2,3 or 1,2,2,4 or is either acceptable? – Shaggy – 2017-08-08T10:42:23.580

@Shaggy Undefined behaviour only for the answer with a bad header. Both 1,2,2,3 and 1,2,2,4 are acceptable but they must share the rank if they share scores. – totallyhuman – 2017-08-08T12:39:19.547

Answers

3

C# (.NET Core), 1059 1049 1033 998 973 bytes

Saved 53 bytes thanks to @TheLethalCoder.
Saved 25 bytes thanks to @Mr. Xcoder.

using HtmlAgilityPack;namespace System.Linq{n=>{var u="http://codegolf.stackexchange.com/";var l=new Collections.Generic.List<Tuple<string,float,string,string>>();foreach(var x in new HtmlWeb().Load(u+"questions/"+n).GetElementbyId("answers").Descendants("div").Where(p=>(p.Attributes["id"]?.Value??"").Contains("answer-"))){var b=x.Descendants().First(p=>(p.Attributes["class"]?.Value)=="post-text");var h=b.Descendants().ElementAt(1).InnerText;l.Add(new Tuple<string,float,string,string>(h.Split(',',' ')[0],float.Parse(h.Split(' ').Reverse().First(p=>float.TryParse(p.Replace('.',','),out var y)).Replace('.', ',')),b.ParentNode.Descendants("td").Last(p=>(p.Attributes["class"]?.Value)=="post-signature").Descendants("div").Reverse().ElementAt(1).Descendants("a").First().InnerText,u+"a/"+x.Attributes["id"].Value.Split('-')[1]));}var s=new string[l.Count];for(var i=0;i<l.Count;){var m=l[i];s[i]=++i+$". {m.Item1}, score {m.Item2} by {m.Item3} ({m.Item4})";}return s;}}

Uses the HTMLAgilityPack.

There are probably better solutions to this using the StackOverflow API, but this is a raw HTML solution.


Ungolfed code: (Unchanged for readability & reference)

n =>
{
    // Retrieve the HTML of the question page with ID n
    var doc = new HtmlWeb().Load("https://codegolf.stackexchange.com/questions/" + n);
    // Get all the answers
    var answers = doc.GetElementbyId("answers").Descendants("div").Where(d => (d.Attributes["id"]?.Value ?? "").Contains("answer-"));

    // Format: Language, Score, Author, URL
    var l = new Collections.Generic.List<Tuple<string, float, string, string>>();

    // Loop over every answer element
    foreach (var a in answers)
    {
        // Get the content of the answer post
        var b = a.Descendants().First(d => (d.Attributes["class"]?.Value ?? "") == "post-text");
        // Get the header
        var h = b.Descendants().ElementAt(1);
        // Dummy variable for the TryParse
        float y;
        // Create a new Tuple
        var v = new Tuple<string, float, string, string>(
            h.InnerText.Split(',', ' ')[0], // The language
            float.Parse(h.InnerText.Split(' ').Reverse().First(x => float.TryParse(x.Replace('.',','), out y)).Replace('.', ',')), // The score of the answer
            b.ParentNode.Descendants("td").Last(d => (d.Attributes["class"]?.Value ?? "") == "post-signature").Descendants("div").Reverse().ElementAt(1).Descendants("a").First().InnerText, // The author
            "https://codegolf.stackexchange.com/a/" + a.Attributes["id"].Value.Split('-')[1] // The url to the answer
            );
        l.Add(v); // Add the tuple to the collection
    }
    var s = new string[l.Count]; // An array to save the leadboard content
    for (var i = 0; i < l.Count;i++)
    {
        var m = l[i]; // The current element
        s[i] = $"{i+1}. {m.Item1}, score {m.Item2} by {m.Item3} ({m.Item4})"; // Format the data
    }
    return s;
}

Ian H.

Posted 2017-08-06T23:07:24.797

Reputation: 2 431

Place your code into namespace System.Linq then move so you can remove the using. Also could you add a formatted version so it's easier to read all that code? :) – TheLethalCoder – 2017-08-07T10:02:04.227

As far i can see you only use d and a in one place so you could probably inline them inside the foreach making it foreach(var x in new HtmlWeb().Load("https://codegolf.stackexchange.com/questions/"+n).GetElementbyId("answers").Descendants("div").Where(p=>(p.Attributes["id"]?.Value??"").Contains("answer-"))) also if you're using C# 7 you should be able to remove float y; and change the TryParse to float.TryParse(p.Replace('.',','),out var y) – grabthefish – 2017-08-07T10:23:09.283

you could probably also save a lot of bytes if you use the new C# 7 tuple declaration – grabthefish – 2017-08-07T10:29:34.377

You can also inline v into the Add() call. Set h to .InnerText to save the repeated calls to it. Pre increment i at {i+1}->{++i}. Then move it out of the string ++i + $". {m.... – TheLethalCoder – 2017-08-07T10:54:28.927

Thought it had already been mentioned but you can inline b as well. – TheLethalCoder – 2017-08-07T11:01:00.310

And are the ??"" needed? Comparing null to a string returns false anyway? – TheLethalCoder – 2017-08-07T11:02:01.997

@TheLethalCoder I don't think I can inline be, as I use it mutliple times (once for h and once in the tuple initializer. And you are right, the ??"" should be able to be cutted. Although, do you need them when calling .Contains()? (To prevent null.Contains()) – Ian H. – 2017-08-07T11:04:19.717

Somehow I missed you using it at h and not sure, probably though. – TheLethalCoder – 2017-08-07T11:05:43.763

Question: Can https: be replaced by http: for -1 bytes? – Mr. Xcoder – 2017-08-07T13:47:24.943

@Mr.Xcoder Yes it can! And it gave me some idea for additional URL shorting, for -25 bytes ;) – Ian H. – 2017-08-07T13:55:11.797

3Can someone explain why they downvoted this answer? – Ian H. – 2017-08-08T06:31:47.807

2

Python 2 + requests + BeautifulSoup + re, 547 519 512 bytes

from requests import*
from bs4 import BeautifulSoup as B
import re
w,h='.stackexchange.com/','http://'
def D(i):l=B(i['body'].split('\n')[0],'html.parser').text;return[l.split(',')[0],float(re.findall(r'\d*\.\d+|\d+',l)[-1]),i['owner']['display_name'],'%scodegolf%sa/'%(h,w)+`i['answer_id']`]
d=sorted([D(i)for i in get('%sapi%squestions/%d/answers?site=codegolf&filter=withbody'%(h,w,input())).json()['items']],key=lambda x:x[1])
h=r=0
for x in d:r+=x[1]>h;h=x[1];print'%d. %s, score %f by %s (%s)'%tuple([r]+x)

Output for this question (137878):

1. Python 2 + requests + BeautifulSoup + re, score 512.000000 by Dopapp (http://codegolf.stackexchange.com/a/138043)
2. C# (.NET Core), score 973.000000 by Ian H. (http://codegolf.stackexchange.com/a/137933)

Ungolfed:

from requests import*
from bs4 import BeautifulSoup
import re

# [score, owner, lang, link]
def data(item): # extracts data from each answer
    owner = item['owner']['display_name']
    link = 'http://codegolf.stackexchange.com/a/'+`item['answer_id']`
    line1 = BeautifulSoup(item['body'].split('\n')[0], 'html.parser').text # first line of the answer
    lang = line1.split(',')[0]
    score = float(re.findall(r'\d*\.\d+|\d+', line1)[-1])
    return [score, owner, lang, link]

# datas :P
datas = [data(item)for item in get('http://api.stackexchange.com/questions/%d/answers?site=codegolf&filter=withbody'%input()).json()['items']]

sortedDatas = sorted(datas, key=lambda x: x[0]) # sort the data by the score

highestScore=rank=0 # This is for doing the ranking in case there are identical scores
for d in sortedDatas:
    if d[0] > highestScore:
        highestScore = d[0]
        rank += 1
    print '%d. %s, score %f by %s (%s)' % (rank, d[2], d[0], d[1], d[3]) # format the line

Daniel

Posted 2017-08-06T23:07:24.797

Reputation: 6 425

You can remove some bytes with import BeautifulSoup as b, no? – Dave – 2017-08-09T14:25:00.560

@pizzakingme, yes good idea! – Daniel – 2017-08-09T14:40:30.563