# Make a Robo-Reviewer: Close Votes

Write a program that reviews posts for the Close Votes queue on Stack Overflow.

Since it's near-impossible to write a bot that can accurately review posts in the actual queue, we're instead going to use a grossly oversimplified version of the queue and its rules.

Your program should take in the body of a question as a string, and output an array of option_ids for the applicable close reasons. For Leave Open, you can output an empty array OR a falsey value. Note: this is a significant change from the sandbox version of this challenge so make sure to update stashed solutions.

Here is a list of the close reasons, their fresh-off-the-API option_ids, and the gross oversimplifications that should be used to flag them. All keywords and key phrases are case-insensitive and should have word boundaries on both sides.

• 28509 - duplicate
• off-topic because…

• 46431 - Questions about general computing hardware and software are off-topic for Stack Overflow unless they directly involve tools used primarily for programming. You may be able to get help on Super User.
• Contains the keyword install, or its various conjugations: installs, installing, installation, installed.
• 36074 - Questions on professional server- or networking-related infrastructure administration are off-topic for Stack Overflow unless they directly involve programming or programming tools. You may be able to get help on Server Fault.
• Contains the keyword server, but NOT php or cgi.
• 54583 - Questions asking us to recommend or find a book, tool, software library, tutorial or other off-site resource are off-topic for Stack Overflow as they tend to attract opinionated answers and spam. Instead, describe the problem and what has been done so far to solve it.

• Contains one of the mentioned words (book, tool, software library, tutorial, API) or their plurals.
• 77 - Questions seeking debugging help ("why isn't this code working?") must include the desired behavior, a specific problem or error and the shortest code necessary to reproduce it in the question itself. Questions without a clear problem statement are not useful to other readers. See: How to create a Minimal, Complete, and Verifiable example.

• The question has no code markdown. You may assume that code markdown will not be inside any other syntax.
• 44435 - This question was caused by a problem that can no longer be reproduced or a simple typographical error. While similar questions may be on-topic here, this one was resolved in a manner unlikely to help future readers. This can often be avoided by identifying and closely inspecting the shortest program necessary to reproduce the problem before posting.

• There are unpaired or incorrectly paired parentheses, brackets, or braces.
• 16055 - unclear what you're asking
• The question does not contain a question mark character.
• The question has no latin characters.
• If either of the above apply, then it should appear in the output array, but it shouldn't appear twice.
• 29650 too broad
• The question contains a question starter (who, what, when, where, why, how) followed by should I.
• 62000 primarily opinion-based
• The question contains the phrases best/good/bad practice(s), or good/bad idea(s).

As usual for a challenge, the winner is the shortest answer in bytes.

# Test Cases

• Whole word match or not? Case-sensitive or not? – user202729 Nov 16 '17 at 2:13
• @user202729 it should be a whole word match, and all but NullPointerException should be case-insensitive. – Nissa Nov 16 '17 at 2:15
• @Riker I've added test cases. – Nissa Nov 16 '17 at 3:33
• "we're instead going to use a grossly oversimplified version of the queue and its rules" I though this was what all mods used, plus a random number generator for security? – PyRulez Nov 16 '17 at 5:42
• My top three SO questions: 1: No code (or does code in the middle of text count?) 2: No code. 3: "Best practice" (in the question title and in the text). I guess they should all be closed... :( – Stewie Griffin Nov 16 '17 at 9:18

# Python 2.7, 644639593560 549 bytes

from re import*
s,g,t,w=input(),"([{)]}",[],"NullPointerException"
l=g.index
print[[29650,46431,54583,62000,16055,77,36074,28509,218384][i]for i,r in enumerate(["(wh([oy]|at|en|ere)|how) should I","install(ing|s|ation|ed)","(book|tool|software librar(ies|y)|tutorial|API)s?","(best|good|bad) (practice|idea)s?","^[^a-z]*$|^[^?]*$","^((?![#*[(~>=]|\d\.|\-{3}).)*$","(?=^.*server)(?!^.*(php|cgi))",w,w])if search(i<5and r or"(^|\W)"+r+"($|\W)",s,flags=I|S)]+[44435]*(any(t.append(g[l(c)+3])if l(c)<3else t==[]or c!=t.pop()for c in s if c in g)or[]<t)


Try it online!

The basic concept is to use a series of regex's to do most of the heavy lifting. I do rebuild regex's to reduce bytes; if you wish to see the full regexes, refer to the ungolfed section below. Then, I go through and check stuff that regex isn't good at (like matching parenthesis correctly).

# Important note regarding testing

The examples as given in OP's post are wrong. If you look through the links, you should be able to notice where my program is finding the correct solution. For instance, the OP's didn't seem to recognize > and other markdown as markdown. That is where most of the variance comes from.

# Pseudo-ungolfed version

I decided that it may be beneficial for understanding to post a precursor to my code-golf.

import re

def doesMatch(string):
openClosed = "([{)]}"
current = []
for char in string:
if char in openClosed:
if openClosed.index(char) < 3:
current.append(openClosed[openClosed.index(char)+3])
elif len(current) == 0 or current.pop() != char:
return False
return not len(current)

def take(post):
regexes = [
"(^|\W)(who|what|when|why|how|where) should I($|\W)", "(^|\W)install(ing|s|ation|ed)($|\W)",
"(^|\W)(book|tool|software library|software libraries|tutorial|API)s?($|\W)", "(?=^[^a-z]*$)|(?=^[^?]*$)", "(^|\W)(best|good|bad) (practice|idea)s?($|\W)",
"^((?![#*[(~>=]|\d\.|\-{3}).)*\$",
"(?=^.*server)(?!^.*(php|cgi))"
]
errors = [[29650,46431,54583,16055,62000,77,36074][i] for i,regex in enumerate(regexes) if re.search(regex,post,flags=re.I|re.S)]
if not doesMatch(post):
errors.append(44435)
if post.find("NullPointerException") != -1:
errors += [28509,218384]
return errors

with open("test.txt","r") as fi: