Determine If a Challenge is Worth Answering

21

2

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

wnnmaw

Posted 2016-05-03T17:54:01.700

Reputation: 1 618

I also only know Python... – R. Kap – 2016-05-03T18:03:44.727

I also know Python, mainly. – user48538 – 2016-05-03T18:04:55.900

I've got to start learning some other languages. – R. Kap – 2016-05-03T18:05:58.833

5@R.Kap, this challenge would be a great time to start! – wnnmaw – 2016-05-03T18:31:53.650

2This challenge is worth answering apparently. – Rɪᴋᴇʀ – 2016-05-05T02:27:44.757

Answers

8

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.

Adnan

Posted 2016-05-03T17:54:01.700

Reputation: 41 965

12I like the "more readable" version; those extra line breaks really make a difference! ;) – Wildcard – 2016-05-04T01:16:54.533

@Wildcard They do indeed make a difference ;) – Erik the Outgolfer – 2016-05-04T09:21:23.943

Could you save bytes by using ppcg.lol/q/id compression? – wnnmaw – 2016-05-04T16:41:32.103

@wnnmaw Thanks, now I'm just 1 byte away from the Ruby answer :p. – Adnan – 2016-05-04T17:00:05.343

If possible, you can do import urllib.request as u and then use u in place of the second urllib.request to save 8 bytes. – R. Kap – 2016-05-04T21:46:22.443

@R.Kap Thanks! That actually saved 2 bytes in the compressed version (beating the Ruby answer :p). – Adnan – 2016-05-04T22:05:13.963

Hi! I'm beating you again, sorry... – Value Ink – 2016-05-05T02:08:11.677

nvm I messed up. We're actually tied – Value Ink – 2016-05-05T02:13:35.537

@KevinLau-notKenny Okay, now this is a war! :p. – Adnan – 2016-05-06T23:29:10.597

1Oh 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 – Value Ink – 2016-05-07T05:43:41.530

5

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.

R. Kap

Posted 2016-05-03T17:54:01.700

Reputation: 4 730

You can use http://codegolf.stackexchange.com/q/ to fetch the question. Also, is http:// mandatory? – Marv – 2016-05-03T19:39:42.113

Ideone and repl.it don't allow fetching external resources a la urllib. – Mego – 2016-05-03T19:42:13.543

@Mego Dang...well then, I guess people are going to have to confirm it works using their own Python interpreters. – R. Kap – 2016-05-03T19:42:58.410

@Marv Yes, apparently it is. Otherwise, I get a unknown url type error. – R. Kap – 2016-05-03T19:44:31.380

I can't test it, but I think you can save one byte by using from urllib.request import* – wnnmaw – 2016-05-03T20:25:52.347

6ppcg.lol/q/id also works – removed – 2016-05-03T20:47:14.200

@WashingtonGuedes Really? Thanks! :) What kind of a url ending is .lol though?... – R. Kap – 2016-05-03T21:00:42.500

@Mego Do you, by any chance, know of any other online interpreter(s) that actually allow the fetching of resources via urllib? – R. Kap – 2016-05-04T00:14:58.143

@R.Kap You can thank Quill for that short URL. I'm not aware of any online interpreters that allow external resource fetching. – Mego – 2016-05-04T02:16:13.213

4

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.

Value Ink

Posted 2016-05-03T17:54:01.700

Reputation: 10 608

3ppcg.lol/q/#{i} also works – removed – 2016-05-03T20:47:46.263

@WashingtonGuedes ppcg.ga/q#{i} maybe? (I don't know Ruby) – Erik the Outgolfer – 2016-05-04T09:22:29.183

@ΈρικΚωνσταντόπουλος ppcg.ga is not a wildcard redirect, try it yourself - http://ppcg.ga/q/79273/

– Timtech – 2016-05-04T12:24:32.287

@Timtech So ppcg.lol/q#{i} is applicable I think? (a/#b is the same as a#b) – Erik the Outgolfer – 2016-05-04T12:50:03.420

@ΈρικΚωνσταντόπουλος You don't need the number symbol in ppcg.lol/q/{i} right? And yeah I guess it works. – Timtech – 2016-05-04T14:27:01.153

@Timech @ΈρικΚωνσταντόπουλος Nope, #{...} is a special string substitution sequence in Ruby, similar to %s formatting but putting the value directly into the string. Which means the # isn't actually part of the URL at all. – Value Ink – 2016-05-05T01:46:53.007

1The " 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. – manatwork – 2016-05-05T08:28:31.827

4

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

Alex A.

Posted 2016-05-03T17:54:01.700

Reputation: 23 761

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? – Value Ink – 2016-05-03T20:42:18.860

1@KevinLau-notKenny Of course! Do what you gotta do in the name of golf. :P – Alex A. – 2016-05-03T21:06:01.237

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 – Value Ink – 2016-05-03T23:17:47.600

4

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.

Winny

Posted 2016-05-03T17:54:01.700

Reputation: 1 120

1Beat me to it! :P – cat – 2016-05-04T02:52:41.030

TODO: make a racket-like that is golf-able. :) – Winny – 2016-05-04T04:25:41.020

1339 bytes of which 68 are parens... so a LISP for golf would need short identifiers and no parens. Not very LISPy :( – cat – 2016-05-06T18:01:58.667

3

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.

Krzysztof Atłasik

Posted 2016-05-03T17:54:01.700

Reputation: 189

You can replace codegolf.stackexchange.com with ppcg.lol – Timtech – 2016-05-04T12:25:05.587

see also: Tips for golfing in Groovy

– cat – 2016-05-06T17:57:53.273