Code Runner
Um die Sache interessant zu machen, habe ich ein Skript erstellt, mit dem der Code aus jeder veröffentlichten Antwort automatisch heruntergeladen, bei Bedarf kompiliert und dann alle Lösungen gemäß den Regeln ausgeführt werden. Auf diese Weise können die Leute überprüfen, wie es ihnen geht. Speichern Sie dieses Skript einfach in run_all.py (erfordert BeautifulSoup) und dann:
usage:
To get the latest code: 'python run_all.py get'
To run the submissions: 'python run_all.py run <optional num_runs>'
Ein paar Dinge:
- Wenn Sie Unterstützung für weitere Sprachen hinzufügen oder die Unterstützung für einige Sprachen entfernen möchten, lesen Sie
def submission_type(lang)
.
- Das Erweitern des Skripts sollte auch für Sprachen, die kompiliert werden müssen, relativ einfach sein (siehe
CPPSubmission
). Der Sprachtyp wird aus dem Meta-Code-Tag < !-- language: lang-java -- >
abgerufen. Fügen Sie ihn daher hinzu, wenn der Code ausgeführt werden soll. (Entfernen Sie die zusätzlichen Leerzeichen vor und nach dem <>.)
UPDATE : Es gibt jetzt einige äußerst grundlegende Schlussfolgerungen, um zu versuchen, die Sprache zu erkennen, wenn sie nicht definiert ist.
- Wenn Ihr Code nicht ausgeführt werden kann oder nicht innerhalb der festgelegten Zeit fertig ist, wird er
blacklist.text
automatisch zu zukünftigen Tests hinzugefügt und aus diesen entfernt. Wenn Sie Ihren Code korrigieren, entfernen Sie einfach Ihren Eintrag von der Blacklist und führen Sie ihn erneut aus get
.
Derzeit unterstützte Sprachen:
submission_types = {
'lang-ruby': RubySubmission,
'lang-python': PythonSubmission,
'lang-py': PythonSubmission,
'lang-java': JavaSubmission,
'lang-Java': JavaSubmission,
'lang-javascript': NodeSubmission,
'lang-cpp': CPPSubmission,
'lang-c': CSubmission,
'lang-lua': LuaSubmission,
'lang-r': RSubmission,
'lang-fortran': FortranSubmission,
'lang-bash': BashSubmission
}
Ohne weiteres:
import urllib2
import hashlib
import os
import re
import subprocess
import shutil
import time
import multiprocessing
import tempfile
import sys
from bs4 import BeautifulSoup
__run_java__ = """
public class Run {
public static void main(String[] args) {
String input = "";
Human h = new __REPLACE_ME__();
if(args.length == 1)
input = args[0];
try {
System.out.println(h.takeSides(input));
}
catch(Exception e) {
}
}
}
"""
__human_java__ = """
public abstract class Human {
public abstract String takeSides(String history) throws Exception;
}
"""
class Submission():
def __init__(self, name, code):
self.name = name
self.code = code
def submissions_dir(self):
return 'submission'
def base_name(self):
return 'run'
def submission_path(self):
return os.path.join(self.submissions_dir(), self.name)
def extension(self):
return ""
def save_submission(self):
self.save_code()
def full_command(self, input):
return []
def full_path(self):
file_name = "%s.%s" % (self.base_name(), self.extension())
full_path = os.path.join(self.submission_path(), file_name)
return full_path
def save_code(self):
if not os.path.exists(self.submission_path()):
os.makedirs(self.submission_path())
with open(self.full_path(), 'w') as f:
f.write(self.code)
def write_err(self, err):
with open(self.error_log(), 'w') as f:
f.write(err)
def error_log(self):
return os.path.join(self.submission_path(), 'error.txt')
def run_submission(self, input):
command = self.full_command()
if input is not None:
command.append(input)
try:
output,err,exit_code = run(command,timeout=1)
if len(err) > 0:
self.write_err(err)
return output
except Exception as e:
self.write_err(str(e))
return ""
class CPPSubmission(Submission):
def bin_path(self):
return os.path.join(self.submission_path(), self.base_name())
def save_submission(self):
self.save_code()
compile_cmd = ['g++', '-O3', '-std=c++0x', '-o', self.bin_path(), self.full_path()]
errout = open(self.error_log(), 'w')
subprocess.call(compile_cmd, stdout=errout, stderr=subprocess.STDOUT)
def extension(self):
return 'cpp'
def full_command(self):
return [self.bin_path()]
class CSubmission(Submission):
def bin_path(self):
return os.path.join(self.submission_path(), self.base_name())
def save_submission(self):
self.save_code()
compile_cmd = ['gcc', '-o', self.bin_path(), self.full_path()]
errout = open(self.error_log(), 'w')
subprocess.call(compile_cmd, stdout=errout, stderr=subprocess.STDOUT)
def extension(self):
return 'c'
def full_command(self):
return [self.bin_path()]
class FortranSubmission(Submission):
def bin_path(self):
return os.path.join(self.submission_path(), self.base_name())
def save_submission(self):
self.save_code()
compile_cmd = ['gfortran', '-fno-range-check', '-o', self.bin_path(), self.full_path()]
errout = open(self.error_log(), 'w')
subprocess.call(compile_cmd, stdout=errout, stderr=subprocess.STDOUT)
def extension(self):
return 'f90'
def full_command(self):
return [self.bin_path()]
class JavaSubmission(Submission):
def base_name(self):
class_name = re.search(r'class (\w+) extends', self.code)
file_name = class_name.group(1)
return file_name
def human_base_name(self):
return 'Human'
def run_base_name(self):
return 'Run'
def full_name(self, base_name):
return '%s.%s' % (base_name, self.extension())
def human_path(self):
return os.path.join(self.submission_path(), self.full_name(self.human_base_name()))
def run_path(self):
return os.path.join(self.submission_path(), self.full_name(self.run_base_name()))
def replace_in_file(self, file_name, str_orig, str_new):
old_data = open(file_name).read()
new_data = old_data.replace(str_orig, str_new)
with open(file_name, 'w') as f:
f.write(new_data)
def write_code_to_file(self, code_str, file_name):
with open(file_name, 'w') as f:
f.write(code_str)
def save_submission(self):
self.save_code()
self.write_code_to_file(__human_java__, self.human_path())
self.write_code_to_file(__run_java__, self.run_path())
self.replace_in_file(self.run_path(), '__REPLACE_ME__', self.base_name())
self.replace_in_file(self.full_path(), 'package Humans;', '')
compile_cmd = ['javac', '-cp', self.submission_path(), self.run_path()]
errout = open(self.error_log(), 'w')
subprocess.call(compile_cmd, stdout=errout, stderr=subprocess.STDOUT)
def extension(self):
return 'java'
def full_command(self):
return ['java', '-cp', self.submission_path(), self.run_base_name()]
class PythonSubmission(Submission):
def full_command(self):
return ['python', self.full_path()]
def extension(self):
return 'py'
class RubySubmission(Submission):
def full_command(self):
return ['ruby', self.full_path()]
def extension(self):
return 'rb'
class NodeSubmission(Submission):
def full_command(self):
return ['node', self.full_path()]
def extension(self):
return 'js'
class LuaSubmission(Submission):
def full_command(self):
return ['lua', self.full_path()]
def extension(self):
return 'lua'
class RSubmission(Submission):
def full_command(self):
return ['Rscript', self.full_path()]
def extension(self):
return 'R'
class BashSubmission(Submission):
def full_command(self):
return [self.full_path()]
def extension(self):
return '.sh'
class Scraper():
def download_page(self, url, use_cache = True, force_cache_update = False):
file_name = hashlib.sha1(url).hexdigest()
if not os.path.exists('cache'):
os.makedirs('cache')
full_path = os.path.join('cache', file_name)
file_exists = os.path.isfile(full_path)
if use_cache and file_exists and not force_cache_update:
html = open(full_path, 'r').read()
return html
opener = urllib2.build_opener()
opener.addheaders = [('User-agent', 'Mozilla/5.0')]
response = opener.open(url)
html = response.read()
if use_cache:
f = open(full_path, 'w')
f.write(html)
f.close()
return html
def parse_post(self, post):
name = post.find(text=lambda t: len(t.strip()) > 0)
pre = post.find('pre')
lang = pre.attrs['class'][0] if pre.has_attr('class') else None
code = pre.find('code').text
user = post.find(class_='user-details').find(text=True)
return {'name':name,'lang':lang,'code':code,'user':user}
def parse_posts(self, html):
soup = BeautifulSoup(html)
# Skip the first post
posts = soup.find_all(class_ = 'answercell')
return [self.parse_post(post) for post in posts]
def get_submissions(self, page = 1, force_cache_update = False):
url = "http://codegolf.stackexchange.com/questions/33137/good-versus-evil?page=%i&tab=votes#tab-top" % page
html = self.download_page(url, use_cache = True, force_cache_update = force_cache_update)
submissions = self.parse_posts(html)
return submissions
class Timeout(Exception):
pass
def run(command, timeout=10):
proc = subprocess.Popen(command, bufsize=0, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
poll_seconds = .250
deadline = time.time()+timeout
while time.time() < deadline and proc.poll() == None:
time.sleep(poll_seconds)
if proc.poll() == None:
if float(sys.version[:3]) >= 2.6:
proc.terminate()
raise Timeout()
stdout, stderr = proc.communicate()
return stdout, stderr, proc.returncode
def guess_lang(code):
if re.search(r'class .* extends Human', code):
return 'lang-java'
if re.search(r'import sys', code):
return 'lang-python'
if re.search(r'puts', code) and (re.search(r'ARGV', code) or re.search(r'\%w', code)):
return 'lang-ruby'
if re.search(r'console\.log', code):
return 'lang-javascript'
if re.search(r'program', code) and re.search(r'subroutine', code):
return 'lang-fortran'
if re.search(r'@echo off', code):
return 'lang-bash'
return None
def submission_type(lang, code):
submission_types = {
'lang-ruby': RubySubmission,
'lang-python': PythonSubmission,
'lang-py': PythonSubmission,
'lang-java': JavaSubmission,
'lang-Java': JavaSubmission,
'lang-javascript': NodeSubmission,
'lang-cpp': CPPSubmission,
'lang-c': CSubmission,
'lang-lua': LuaSubmission,
'lang-r': RSubmission,
'lang-fortran': FortranSubmission,
'lang-bash': BashSubmission
}
klass = submission_types.get(lang)
if klass is None:
lang = guess_lang(code)
klass = submission_types.get(lang)
return klass
def instantiate(submission):
lang = submission['lang']
code = submission['code']
name = submission['name']
klass = submission_type(lang, code)
if klass is not None:
instance = klass(name, code)
return instance
print "Entry %s invalid - lang not supported: %s" % (name, lang)
return None
def get_all_instances(force_update):
scraper = Scraper()
print 'Scraping Submissions..'
pages = [1,2,3]
submissions_by_page = [scraper.get_submissions(page=i, force_cache_update=force_update) for i in pages]
submissions = [item for sublist in submissions_by_page for item in sublist]
# Get instances
raw_instances = [instantiate(s) for s in submissions]
instances = [i for i in raw_instances if i]
print "Using %i/%i Submissions" % (len(instances), len(submissions))
return instances
def save_submissions(instances):
print 'Saving Submissions..'
for instance in instances:
instance.save_submission()
def init_game(save=True, force_update=False):
instances = get_all_instances(force_update)
if save:
save_submissions(instances)
return instances
def one_run(instances, input):
valid = {
'good': 1,
'evil': 0
}
disqualified = []
results = []
for instance in instances:
out = instance.run_submission(input)
res = out.strip().lower()
if res not in valid:
disqualified.append(instance)
else:
results.append(valid[res])
return (results, disqualified)
def get_winner(scores, instances):
max_value = max(scores)
max_index = scores.index(max_value)
instance = instances[max_index]
return (instance.name, max_value)
def update_scores(results, scores, minority_counts, minority_num):
for i in range(len(results)):
if results[i] == minority_num:
minority_counts[i] += 1
scores[i] += (minority_counts[i] - 1)
else:
minority_counts[i] = 0
scores[i] += 3
def try_run_game(instances, num_runs = 1000, blacklist = None):
current_input = None
minority_str = None
num_instances = len(instances)
scores = [0] * num_instances
minority_counts = [0] * num_instances
print "Running with %i instances..." % num_instances
for i in range(num_runs):
print "Round: %i - Last minority was %s" % (i, minority_str)
results, disqualified = one_run(instances, current_input)
if len(disqualified) > 0:
for instance in disqualified:
print "Removing %s!" % instance.name
instances.remove(instance)
if blacklist is not None:
with open(blacklist, 'a') as f:
f.write("%s\n" % instance.name)
return False
latest_result = "".join(map(str,results))
current_input = "%s,%s" % (current_input, latest_result)
minority_num = 1 if results.count(1) < results.count(0) else 0
minority_str = 'good' if minority_num == 1 else 'evil'
update_scores(results, scores, minority_counts, minority_num)
name, score = get_winner(scores, instances)
print "%s is currently winning with a score of %i" % (name, score)
print "The winner is %s with a score of %i!!!" % (name, score)
return True
def find_instance_by_name(instances, name):
for instance in instances:
if instance.name == name:
return instance
return None
def maybe_add_or_remove_baelish(instances, baelish):
num_instances = len(instances)
if num_instances % 2 == 0:
print 'There are %i instances.' % num_instances
try:
instances.remove(baelish)
print 'Baelish Removed!'
except:
instances.append(baelish)
print 'Baelish Added!'
def remove_blacklisted(blacklist, instances):
blacklisted = []
try:
blacklisted = open(blacklist).readlines()
except:
return
print 'Removing blacklisted entries...'
for name in blacklisted:
name = name.strip()
instance = find_instance_by_name(instances, name)
if instance is not None:
print 'Removing %s' % name
instances.remove(instance)
def run_game(instances, num_runs):
blacklist = 'blacklist.txt'
remove_blacklisted(blacklist, instances)
baelish = find_instance_by_name(instances, 'Petyr Baelish')
maybe_add_or_remove_baelish(instances, baelish)
while not try_run_game(instances, num_runs = num_runs, blacklist = blacklist):
print "Restarting!"
maybe_add_or_remove_baelish(instances, baelish)
print "Done!"
if __name__ == '__main__':
param = sys.argv[1] if len(sys.argv) >= 2 else None
if param == 'get':
instances = init_game(save=True, force_update=True)
elif param == 'run':
instances = init_game(save=False, force_update=False)
num_runs = 50
if len(sys.argv) == 3:
num_runs = int(sys.argv[2])
run_game(instances, num_runs)
else:
self_name = os.path.basename(__file__)
print "usage:"
print "To get the latest code: 'python %s get'" % self_name
print "To run the submissions: 'python %s run <optional num_runs>'" % self_name