21
\$\begingroup\$

I am a very casual code golfer, and don't often see posts until they show up in the "Hot Network Questions" sidebar over on StackOverflow. Usually, I'm late to the game, and since the only language I know is Python, there is little point in me answering as there are already several Python answers. Your challenge is to figure out if a question is worth me answering.

Input:

  • Your code (function or program) will take one input parameter i

Output:

  • Truthy or Falsey value for question id i. Output Truthy if question has more than 5 answers, greater than 3 question score, and one or less answer in Python (no distinction between version).

Rules/Clarifications:

  • Input format can be anything reasonable (stdin, file, command line), but should be specified in your answer. Data types and leading/trailing whitespace doesn't matter.
  • Assume question id is valid for codegolf.stackexchange.com.
  • Ignore language specific question requirements. (i.e. if a question meets votes and answers, and has no Python answers because it is Java only, it still results in Truthy).
  • An answer qualifies as a Python answer if "python" (case insenstive) occurs anywhere before the first newline of the post.
  • This is code golf, so shortest code in bytes wins.

Sample Cases*

id = 79082 => True
id = 78591 => False (less than 5 answers, also hella hard)
id = 78410 => True
id = 76428 => False (greater than 1 Python answer)
id = 78298 => False (not high enough question score)

*Verified at time of posting, may have changed

\$\endgroup\$
  • \$\begingroup\$ I also only know Python... \$\endgroup\$ – R. Kap May 3 '16 at 18:03
  • \$\begingroup\$ I also know Python, mainly. \$\endgroup\$ – user48538 May 3 '16 at 18:04
  • \$\begingroup\$ I've got to start learning some other languages. \$\endgroup\$ – R. Kap May 3 '16 at 18:05
  • 5
    \$\begingroup\$ @R.Kap, this challenge would be a great time to start! \$\endgroup\$ – wnnmaw May 3 '16 at 18:31
  • 2
    \$\begingroup\$ This challenge is worth answering apparently. \$\endgroup\$ – Rɪᴋᴇʀ May 5 '16 at 2:27
8
\$\begingroup\$

05AB1E, 167 160 159 158 156 154 143 bytes

Damn, almost as long as a normal language...

Crap... longer currently beating the the Ruby answer by 1 byte.

Now longer than the Ruby answer, argh!.

I probably should go to bed right now.

Thanks to @wnnmaw for saving 1 byte and thanks to @R. Kap for saving another 2 bytes!

Code:

’£Ø ˆå§¾.‡¢ as g;#.¾¿„–(g.ˆåƒÛ('·Ç://ÐÏg.´¢/q/'+•œ_#()).‚Ø())’.e©’„à="Ž»"’DU¢®…ƒŠ‡¡`99£þs\®X¡¦vy’„à="‚¬-„¹"’¡¦'>¡¦¦¬l’±¸’¢s\}rUV)O2‹X5›Y3›)P

Or with more readability:

’£Ø ˆå§¾.‡¢ as g;#.¾¿„–(g.ˆåƒÛ('·Ç://ÐÏg.´¢/q/'+•œ_#()).‚Ø())’
 .e©
’„à="Ž»"’
 DU¢®
“ƒŠ‡“
 ¡`99£þs\®X¡¦
v
 y’„à="‚¬-„¹"’¡¦'>¡¦¦¬l’±¸’¢s\}rUV)O2‹X5›Y3›)P

Explanation:

First of all, a lot of text is being compressed here, which translates to good old Python. The uncompressed version is:

"import urllib.request as g
 f=g.urlopen('http://ppcg.lol/q/'+pop_#())
 #.append(f.read())"
.e©“class="answer"“¢®"useful and clear"¡`99£þs\®“class="answer"“¡¦vy“class="post-text"“¡¦'>¡¦¦¬l"python"¢s\}rUV)O2‹X5›Y3›)P

This part:

import urllib.request as g
stack.append(g.urlopen('http://ppcg.lol/q/'+pop_stack()).read())`

actually pops a stack value, copies it into the url and fetches all HTML data. The HTML data is pushed on top of the stack using #.append(f.read()).

We count the number of answers, by count the number of occurences of class="answer".

To count the number of votes, we just split the data at "useful and clear" and keep only the digit values of [0:99] using ®"useful and clear"¡`99£þ. This is the number of upvotes.

Eventually, we need to check every answer if the text "Python" exists before the closing header text. To get all answers, we just split the data on class="post-text" and split each of them again on <. We remove the first two elements to get the part in which the language is displayed and check if the lowercase version is in this string.

So, now our stack looks like this for id = 79273:

`[6, '14', 0, 0, 0, 1, 0, 0]`
  │    │   └───────┬──────┘
  │    │           │
  │    │   is python answer?
  │    │
  │    └── number of upvotes
  │
  └─── number of answers

This can also be seen with the -debug flag on in the interpreter.

So, it's just a matter of processing the data:

rUV)O2‹X5›Y3›)P

r                # Reverse the stack
 U               # Pop the number of answers value and store into X
  V              # Pop the number of upvotes value and store into Y
   )O            # Wrap everything together and sum it all up
     2‹          # Check if smaller than 2
       X5›       # Push X and check if greater than 5
          Y3›    # Push Y and check if greater than 3
             )P  # Wrap everything into an array and take the product.
                   This results into 1 if and only if all values are 1 (and not 0).

Uses CP-1252 encoding. You can download the interpreter here.

\$\endgroup\$
  • 12
    \$\begingroup\$ I like the "more readable" version; those extra line breaks really make a difference! ;) \$\endgroup\$ – Wildcard May 4 '16 at 1:16
  • \$\begingroup\$ @Wildcard They do indeed make a difference ;) \$\endgroup\$ – Erik the Outgolfer May 4 '16 at 9:21
  • \$\begingroup\$ Could you save bytes by using ppcg.lol/q/id compression? \$\endgroup\$ – wnnmaw May 4 '16 at 16:41
  • \$\begingroup\$ @wnnmaw Thanks, now I'm just 1 byte away from the Ruby answer :p. \$\endgroup\$ – Adnan May 4 '16 at 17:00
  • 1
    \$\begingroup\$ Oh no! I don't think I can cut enough corners to save the 7 bytes I need to get ahead again... I guess I just have to settle for second place \$\endgroup\$ – Value Ink May 7 '16 at 5:43
5
\$\begingroup\$

Python 3.5, 280 272 260 242 240 bytes:

(Thanks to Adnan for the trick about using the * operator in comparisons resulting in 2 saved bytes!)

def g(o):import urllib.request as u,re;R=re.findall;w=bytes.decode(u.urlopen('http://ppcg.lol/q/'+o).read());print((len(R('(?:<h[0-9]>|<p>).*python',w.lower()))<2)*(int(R('(?<="vote-count-post ">)[0-9]+',w)[0])>3)*w.count('answercell">')>5)

Simple enough. Uses Python's built in urllib library to go to the question's site, and then uses regular expressions to find the vote count, answer count, and the count of Python specific answers in the decoded text returned from the website. Finally, these values are compared against the conditions required to return a truthy value, and if they satisfy all the conditions, then True is returned. Otherwise False is.

The only thing I may be worried about here is that the regular expressions give a lot of lee way in the terms of the number of python specific answers to save bytes, so it may be a bit inaccurate at times, though it's probably good enough for the purposes of this challenge. However, if you want a much more accurate one, I have added one below, although its longer than the one above. The one shown below is currently 298 bytes since it uses a much longer regular expression–one which you couldn't know how long it took me to discover–for counting Python answers than my original function for the sake of accuracy. This one should work for about at least 80% to 90% of all test cases thrown at it.

def g(o):import urllib.request as u,re;R=re.findall;w=bytes.decode(u.urlopen('http://ppcg.lol/q/'+o).read());print(len(R('(?<=answercell">).*?(?:<h[0-9]>|<strong>)[^\n]*python[^\n]*(?=</h[0-9]>|</strong>)',w.lower()))<2and int(R('(?<="vote-count-post ">)[0-9]+',w)[0])>3and w.count('answercell">')>5)

But, what about those questions with multiple pages of answers? Neither of the above will work very well in that situation, if, say, 1 python answer is on the first page and another is on the second. Well, I took the liberty to fix this issue by creating another version of my function (shown below) that checks every page of answers, if multiple ones exist, for Python answers, and it has done quite well on many of the test cases I have thrown at it. Well, without further ado, here is the new and updated function:

def g(o):
 import urllib.request as u,re;R=re.findall;w=bytes.decode(u.urlopen('http://ppcg.lol/q/'+o).read());t=0if len(re.findall('="go to page ([0-9]+)">',w))<1else max([int(i)for i in re.findall('="go to page ([0-9]+)">',w)])
 if t<1:print(len(R('(?<=answercell">).*?(?:<h[0-9]>|<strong>)[^\n]*python[^\n]*(?=</h[0-9]>|</strong>)',w.lower(),re.DOTALL))<2and int(R('(?<="vote-count-post ">)[0-9]+',w)[0])>3and w.count('answercell">')>5)
 else:
  P=[];U=[];K=[]
  for i in range(2,t+2):P.append(len(R('(?<=answercell">).*?(?:<h[0-9]>|<strong>)[^\n]*python[^\n]*(?=</h[0-9]>|</strong>)',w.lower(),re.DOTALL)));U.append(int(R('(?<="vote-count-post ">)[0-9]+',w)[0]));K.append(w.count('answercell">'));w=bytes.decode(u.urlopen('http://ppcg.lol/questions/'+o+'/?page='+str(i)).read())
  print(sum(P)<2and U[0]>3and sum(K)>5);print('# Python answers: ',sum(P));print('# Votes: ',U[0]);print('# Answers: ',sum(K))

Quite long, isn't it? I wasn't really going much for code golf with this, although, if you want, I can golf it down a bit more. Otherwise, I love it, and could not be happier. Oh, I almost forgot, as an added bonus, this also outputs the total number of Python answers on the question, total votes on the question, and total number of answers on the question if the question id corresponds to a question with more than 1 page of answers. Otherwise, if the question only consists of a single page of answers, it just outputs the truthy/falsy value. I really did get a bit carried away with this challenge.

These each take the question's id in the form of a string.

I would put Try It Online! links here for each function, but unfortunately, neither repl.it nor Ideone allow fetching of resources via Python's urllib library.

\$\endgroup\$
  • \$\begingroup\$ You can use http://codegolf.stackexchange.com/q/ to fetch the question. Also, is http:// mandatory? \$\endgroup\$ – Marv May 3 '16 at 19:39
  • \$\begingroup\$ Ideone and repl.it don't allow fetching external resources a la urllib. \$\endgroup\$ – Mego May 3 '16 at 19:42
  • \$\begingroup\$ @Mego Dang...well then, I guess people are going to have to confirm it works using their own Python interpreters. \$\endgroup\$ – R. Kap May 3 '16 at 19:42
  • \$\begingroup\$ @Marv Yes, apparently it is. Otherwise, I get a unknown url type error. \$\endgroup\$ – R. Kap May 3 '16 at 19:44
  • 6
    \$\begingroup\$ ppcg.lol/q/id also works \$\endgroup\$ – removed May 3 '16 at 20:47
4
\$\begingroup\$

Julia, 275 bytes

using Requests
f(q,p=(s,t)->JSON.parse(readall(get("https://api.stackexchange.com/2.2/questions/$q$s",query=Dict(:site=>"codegolf",:filter=>"$t"))))["items"],x=p("","")[1])=x["answer_count"]>5&&x["score"]>3&&count(i->ismatch(r"python",i["body"]),p("/answers","!9YdnSMKKT"))<2

This is a function that accepts an integer and returns a boolean. It connects to the Stack Exchange API and each run of the function makes 2 API requests, so don't run it too many times or you'll exhaust your 300 requests/day quota.

Ungolfed:

using Requests

function f(q)
    # Define a function that takes two strings and returns a Dict
    # that connects to the SE API
    p = (s,t) -> JSON.parse(readall(get("https://api.stackexchange.com/2.2/questions/$q$s",
        query = Dict(:site => "codegolf", :filter=> "$t"))))["items"]

    # Get the question object
    x = p("", "")[1]

    # Get all answers using the `withbody` API filter
    y = p("/answers", "!9YdnSMKKT")

    x["answer_count"] > 3 && x["score"] > 5 &&
        count(i -> ismatch(r"python", i["body"], y) < 2
end
\$\endgroup\$
  • \$\begingroup\$ I wasn't aware of the "withbody" API filter! +1. If it saves bytes on my Ruby answer, can I use that trick as well? \$\endgroup\$ – Value Ink May 3 '16 at 20:42
  • 1
    \$\begingroup\$ @KevinLau-notKenny Of course! Do what you gotta do in the name of golf. :P \$\endgroup\$ – Alex A. May 3 '16 at 21:06
  • \$\begingroup\$ I didn't want to plagiarize =3 but alas, after learning of ppcg.lol as a short link to all things codegolf, the API version just wasn't enough \$\endgroup\$ – Value Ink May 3 '16 at 23:17
4
\$\begingroup\$

Racket, 339 Bytes

(λ(q)((λ(h)((λ(g)((λ(j)(and(>(h j'score)3)(>(h j'answer_count)5)(<(for/sum([a(g"~a/answers"q)]#:when(regexp-match #rx"(?i:python)"(h a'body)))1)2)))(car(g"~a"q))))(λ(s d)(define-values(x y b)(http-sendrecv"api.stackexchange.com"(format"/2.2/questions/~a?site=codegolf&filter=withbody"(format s d))))(h(read-json b)'items))))hash-ref))

There is much to golf, still.

\$\endgroup\$
  • 1
    \$\begingroup\$ Beat me to it! :P \$\endgroup\$ – cat May 4 '16 at 2:52
  • \$\begingroup\$ TODO: make a racket-like that is golf-able. :) \$\endgroup\$ – Winny May 4 '16 at 4:25
  • 1
    \$\begingroup\$ 339 bytes of which 68 are parens... so a LISP for golf would need short identifiers and no parens. Not very LISPy :( \$\endgroup\$ – cat May 6 '16 at 18:01
4
\$\begingroup\$

Ruby + HTTParty, 170 146 145 142 139 138 + 11 (-rhttparty flag) = 181 157 156 153 150 149 bytes

I don't think there's any edge cases that would cause my regex patterns to break, I hope...

Updated to the shortlink provided by @WashingtonGuedes and discovering that HTTParty doesn't complain if I start with // instead of http://.

Updated for slightly more secure regexes. I saved bytes anyways by discovering that HTTParty response objects inherit from String, which means I don't even need to use .body when matching the regex!

@manatwork pointed out an accidental character addition that I had left in, and for the sake of golf, i has to be accepted as a String now.

Updated regexes. Same length. -1 byte by cutting out a paren.

->i{/"up.*?(\d+)/=~s=HTTParty.get("//ppcg.lol/q/"+i)
$1.to_i>3&&(a=s.scan /st.*xt".*\n(.*)/).size>5&&a[1..-1].count{|e|e[0]=~/python/i}<2}

Extra notes:

  • The first line of an answer (which should contain the language according to the spec) is two lines after the HTML Tag with class "post-text", which we matched with st.*xt". A more secure version would have added a space after it, but we're sacrificing that for the sake of golf.
  • HTTParty is used over the native net/http modules because of proper redirect handling for the given URL.
  • "up*?\d was the shortest sequence I found that corresponded with the number of votes. We only need the first one, so thankfully answers don't affect this.
\$\endgroup\$
  • 3
    \$\begingroup\$ ppcg.lol/q/#{i} also works \$\endgroup\$ – removed May 3 '16 at 20:47
  • \$\begingroup\$ @WashingtonGuedes ppcg.ga/q#{i} maybe? (I don't know Ruby) \$\endgroup\$ – Erik the Outgolfer May 4 '16 at 9:22
  • \$\begingroup\$ @ΈρικΚωνσταντόπουλος ppcg.ga is not a wildcard redirect, try it yourself - ppcg.ga/q/79273 \$\endgroup\$ – Timtech May 4 '16 at 12:24
  • \$\begingroup\$ @Timtech So ppcg.lol/q#{i} is applicable I think? (a/#b is the same as a#b) \$\endgroup\$ – Erik the Outgolfer May 4 '16 at 12:50
  • 1
    \$\begingroup\$ The " is ruining the /"e-c.*?(\d+)/ regular expression. By the way, the requirement say about the input that “Data types (…) doesn't matter.” So better pass the i parameter as string, so you can replace substitution with concatenation: "//ppcg.lol/q/"+i. \$\endgroup\$ – manatwork May 5 '16 at 8:28
3
\$\begingroup\$

Groovy, 179 161 157

{i->t=new URL("http://ppcg.lol/q/$i").text;a=0;p=0;(t=~/"(?i)p.{25}>\n.*python/).each{p++};(t=~/(?m)v.{13}t ">(\d+)/).each{if(it[1].toLong()>3)a++};a>5&&p<2}

Thanks to Timtech 17 chars saved.

Keyword def is also not necessary.

\$\endgroup\$
  • \$\begingroup\$ You can replace codegolf.stackexchange.com with ppcg.lol \$\endgroup\$ – Timtech May 4 '16 at 12:25
  • \$\begingroup\$ see also: Tips for golfing in Groovy \$\endgroup\$ – cat May 6 '16 at 17:57

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.