├── .gitignore ├── LICENSE ├── README.md ├── main.cc ├── main.go ├── main.kt └── parse.py /.gitignore: -------------------------------------------------------------------------------- 1 | a.out 2 | Past/ 3 | [0-9][0-9][0-9] -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2018 Jonathan Hao 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | Codeforces Parser v1.5.1 2 | ================= 3 | 4 | Summary 5 | ------- 6 | 7 | [Codeforces](http://codeforces.com/) is a website for _competitive programming_. It holds contests, so-called **Codeforces Rounds**, about every week. 8 | 9 | This is a python program that parses the sample tests from the contest problem pages. For each problem, it generates the sample input/output files and a shell script to run sample tests. 10 | 11 | You can also find this article here, [http://codeforces.com/blog/entry/10416](http://codeforces.com/blog/entry/10416) 12 | 13 | ### Installation 14 | 15 | * Arch Linux (AUR): https://aur.archlinux.org/packages/codeforces-parser-git/ (note that parse.py is renamed codeforces-parser) 16 | 17 | ### Example: 18 | `./parse.py contest_number (e.g. ./parse.py 513)` 19 | 20 | Where `512` is the contest number, not the round number! Check the URL of the contest on your browser, that is the number you are supposed to use. 21 | 22 | ### Effect: 23 | 24 | ##### What will happen, for example, if `./parse.py 512` is executed? 25 | 26 | 1. Directories `512/A`, `512/B`, `512/C`, `512/D` and so on are created depending on the contest number of problems. 27 | 2. For each problem, `main.cc` is copied and renamed to the problem letter to the corresponding directory. **You can put the path of your usual template in `parse.py:20`**. 28 | 3. Problem page is downloaded from Codeforces website, and parsed. Sample input/output files are generated, e.g. `input1`, `output1`, `input2`, `output2` and so on. You can create your own test cases after that, just keep the same naming format as others test cases. 29 | 4. A script `test.sh` is generated. You can use it to compile and run the sample tests after you finish coding. Just run `./test.sh` in the problem directory. 30 | 31 | ##### What will happen if `./test.sh` is executed? 32 | 33 | 1. Compilation: `g++ -g -std=c++0x -Wall $DBG main.cc`. **You can change the compile options in `parse.py:21`**. Variable $DBG is set to -DDEBUG if you start "./test.sh -d", otherwise it is empty. This allows for compilation with and without debug macros. 34 | 2. Run each sample tests on your program (`a.out`), and check the output by `diff`. If it's correct, print **Accepted**, or else print the sample test that went wrong. 35 | 3. Please note that for problems with multiple correct answers it might say that your output is incorrect. 36 | 37 | ### Collaborators and Versions: 38 | 39 | ##### List of CodeForces Collaborators: 40 | + [johnathan79717](http://codeforces.com/profile/johnathan79717) 41 | + [brunoja](http://codeforces.com/profile/brunoja) 42 | + [Matthias Kauer (mini addition)] 43 | If you have any suggestions and/or bugs drop a message! 44 | 45 | ##### Versions Changes: 46 | + **1.5.1:** 47 | Minor bug fixes related to Python 2 vs Python 3. 48 | Makes the template file to use the proper language extension. 49 | + **1.5:** 50 | Added debug flag (-d) to enable DEBUG macro (read above for details). 51 | Fixed problems parsing for problem names that are not called A, B, etc. Such as A1, A2.. 52 | + **1.4.1:** 53 | Minor fixes, such as typos, bugs and special characters handling. 54 | + **1.4:** 55 | Changed how the parser gets the problems. During the competitions the page is slightly different. 56 | Fixed some invalid character on input and output causing the script to crash. 57 | Forcing a new line on the input/output if there is none. 58 | Fixed some line number information in this README file. 59 | + **1.3:** 60 | Some minor fixes and code organizing. Also fixed some typos. 61 | Removed the _sample_ from default input and output files. 62 | + **1.2:** 63 | Fixed some typos and constants. 64 | Fetching contest info, printing contest name and problem names. 65 | The contest may now have more or less than 5 problems, it will auto detect. 66 | The script will now generate the template with the problem letter. 67 | Fixed test cases fetching. The script was stopping for escaped html characters, such as '<'. 68 | Fixed script to work with python 3. 69 | + **1.1:** 70 | Cleaner generation of the test script, now it auto detects the test cases, making you able to create your own cases. 71 | Echo color output, for accepted we get a green message, otherwise it is red. 72 | Added the time measurement for running the test cases. 73 | For the runtime error case, it now outputs the input case. 74 | Created some constants, such as compile options. These user modifiable constants should be easily spotted at the first lines of the python script. 75 | + **1.0:** Initial Version. 76 | 77 | ##### Todo, Bugs & Troubleshootings: 78 | 79 | + In OS X it is necessary to install the `gnu-time` to measure time. 80 | + This parser currently works only on Unix OSes. If you want to add Windows/Other support let us know. 81 | -------------------------------------------------------------------------------- /main.cc: -------------------------------------------------------------------------------- 1 | // Create your own template by modifying this file! 2 | #include 3 | using namespace std; 4 | 5 | #ifdef DEBUG 6 | #define debug(args...) {dbg,args; cerr< debugger& operator , (const T& v) 14 | { 15 | cerr< debug(vararg vals: T): Unit { 3 | if (!_debug) { return } 4 | for (v in vals) { System.err.print(v); System.err.print(" ") } 5 | System.err.println() 6 | } 7 | 8 | fun main(args: Array) { 9 | if (args.size > 0 && args[0] == "-d") { 10 | _debug = true; 11 | } 12 | 13 | val n = readLine()!!.toInt() 14 | val (a, b) = readLine()!!.split(' ').map(String::toLong) 15 | } 16 | -------------------------------------------------------------------------------- /parse.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python 2 | 3 | # Python 2->3 libraries that were renamed. 4 | try: 5 | from urllib2 import urlopen 6 | except: 7 | from urllib.request import urlopen 8 | try: 9 | from HTMLParser import HTMLParser 10 | except: 11 | from html.parser import HTMLParser 12 | 13 | # Other libraries. 14 | from sys import argv 15 | from subprocess import call 16 | from functools import partial, wraps 17 | 18 | import re 19 | import argparse 20 | import platform 21 | 22 | ########################### 23 | # User modifiable constants 24 | ########################### 25 | language_params = { 26 | 'c++14' : { 27 | 'TEMPLATE' : 'main.cc', 28 | 'DEBUG_FLAGS' : '-DDEBUG', 29 | 'COMPILE_CMD' : 'g++ -g -std=c++14 -Wall $DBG', 30 | 'RUN_CMD' : './a.out' 31 | }, 32 | 'go' : { 33 | 'TEMPLATE' : 'main.go', 34 | 'COMPILE_CMD' : 'go build $DBG -o a.out', 35 | 'DEBUG_FLAGS' : '''"-ldflags '-X=main.DEBUG=Y'"''', 36 | 'RUN_CMD' : './a.out' 37 | }, 38 | 'kotlin' : { 39 | 'TEMPLATE' : 'main.kt', 40 | 'COMPILE_CMD' : 'kotlinc -include-runtime -d out.jar', 41 | 'DEBUG_FLAGS' : "-d", 42 | 'RUN_CMD' : 'java -jar out.jar $DBG' 43 | }, 44 | } 45 | 46 | SAMPLE_INPUT='input' 47 | SAMPLE_OUTPUT='output' 48 | MY_OUTPUT='my_output' 49 | 50 | # Do not modify these! 51 | VERSION='CodeForces Parser v1.5.1: https://github.com/johnathan79717/codeforces-parser' 52 | RED_F='\033[31m' 53 | GREEN_F='\033[32m' 54 | BOLD='\033[1m' 55 | NORM='\033[0m' 56 | if (platform.system() == "Darwin"): 57 | TIME_CMD='`which gtime` -o time.out -f "(%es)"' 58 | else: 59 | TIME_CMD='`which time` -o time.out -f "(%es)"' 60 | TIME_AP='`cat time.out`' 61 | 62 | # Problems parser. 63 | class CodeforcesProblemParser(HTMLParser): 64 | 65 | def __init__(self, folder): 66 | HTMLParser.__init__(self) 67 | self.folder = folder 68 | self.num_tests = 0 69 | self.testcase = None 70 | self.start_copy = False 71 | 72 | def handle_starttag(self, tag, attrs): 73 | if tag == 'div': 74 | if attrs == [('class', 'input')]: 75 | self.num_tests += 1 76 | self.testcase = open( 77 | '%s/%s%d' % (self.folder, SAMPLE_INPUT, self.num_tests), 'wb') 78 | elif attrs == [('class', 'output')]: 79 | self.testcase = open( 80 | '%s/%s%d' % (self.folder, SAMPLE_OUTPUT, self.num_tests), 'wb') 81 | elif tag == 'pre': 82 | if self.testcase != None: 83 | self.start_copy = True 84 | 85 | def handle_endtag(self, tag): 86 | if tag == 'br': 87 | if self.start_copy: 88 | self.testcase.write('\n'.encode('utf-8')) 89 | self.end_line = True 90 | if tag == 'pre': 91 | if self.start_copy: 92 | if not self.end_line: 93 | self.testcase.write('\n'.encode('utf-8')) 94 | self.testcase.close() 95 | self.testcase = None 96 | self.start_copy = False 97 | 98 | def handle_entityref(self, name): 99 | if self.start_copy: 100 | self.testcase.write(self.unescape(('&%s;' % name)).encode('utf-8')) 101 | 102 | def handle_data(self, data): 103 | if self.start_copy: 104 | self.testcase.write(data.strip('\n').encode('utf-8')) 105 | self.end_line = False 106 | 107 | # Contest parser. 108 | class CodeforcesContestParser(HTMLParser): 109 | 110 | def __init__(self, contest): 111 | HTMLParser.__init__(self) 112 | self.contest = contest 113 | self.start_contest = False 114 | self.start_problem = False 115 | self.name = '' 116 | self.problem_name = '' 117 | self.problems = [] 118 | self.problem_names = [] 119 | 120 | def handle_starttag(self, tag, attrs): 121 | if self.name == '' and attrs == [('style', 'color: black'), ('href', '/contest/%s' % (self.contest))]: 122 | self.start_contest = True 123 | elif tag == 'option': 124 | if len(attrs) == 1: 125 | regexp = re.compile(r"'[A-Z][0-9]?'") # The attrs will be something like: ('value', 'X'), or ('value', 'X1') 126 | string = str(attrs[0]) 127 | search = regexp.search(string) 128 | if search is not None: 129 | self.problems.append(search.group(0).split("'")[-2]) 130 | self.start_problem = True 131 | 132 | def handle_endtag(self, tag): 133 | if tag == 'a' and self.start_contest: 134 | self.start_contest = False 135 | elif self.start_problem: 136 | self.problem_names.append(self.problem_name) 137 | self.problem_name = '' 138 | self.start_problem = False 139 | 140 | def handle_data(self, data): 141 | if self.start_contest: 142 | self.name = data 143 | elif self.start_problem: 144 | self.problem_name += data 145 | 146 | # Parses each problem page. 147 | def parse_problem(folder, contest, problem): 148 | url = 'http://codeforces.com/contest/%s/problem/%s' % (contest, problem) 149 | html = urlopen(url).read() 150 | parser = CodeforcesProblemParser(folder) 151 | parser.feed(html.decode('utf-8')) 152 | # .encode('utf-8') Should fix special chars problems? 153 | return parser.num_tests 154 | 155 | # Parses the contest page. 156 | def parse_contest(contest): 157 | url = 'http://codeforces.com/contest/%s' % (contest) 158 | html = urlopen(url).read() 159 | parser = CodeforcesContestParser(contest) 160 | parser.feed(html.decode('utf-8')) 161 | return parser 162 | 163 | # Generates the test script. 164 | def generate_test_script(folder, language, num_tests, problem): 165 | param = language_params[language] 166 | 167 | with open(folder + 'test.sh', 'w') as test: 168 | test.write( 169 | ('#!/bin/bash\n' 170 | 'DBG=""\n' 171 | 'while getopts ":d" opt; do\n' 172 | ' case $opt in\n' 173 | ' d)\n' 174 | ' echo "-d was selected; compiling in DEBUG mode!" >&2\n' 175 | ' DBG=' + param["DEBUG_FLAGS"] +'\n' 176 | ' ;;\n' 177 | ' \?)\n' 178 | ' echo "Invalid option: -$OPTARG" >&2\n' 179 | ' ;;\n' 180 | ' esac\n' 181 | 'done\n' 182 | '\n' 183 | 'if ! ' + param["COMPILE_CMD"] +' {0}.{1}; then\n' 184 | ' exit\n' 185 | 'fi\n' 186 | 'INPUT_NAME='+SAMPLE_INPUT+'\n' 187 | 'OUTPUT_NAME='+SAMPLE_OUTPUT+'\n' 188 | 'MY_NAME='+MY_OUTPUT+'\n' 189 | 'rm -R $MY_NAME* &>/dev/null\n').format(problem, param["TEMPLATE"].split('.')[1])) 190 | test.write( 191 | 'for test_file in $INPUT_NAME*\n' 192 | 'do\n' 193 | ' i=$((${{#INPUT_NAME}}))\n' 194 | ' test_case=${{test_file:$i}}\n' 195 | ' if ! {5} {run_cmd} < $INPUT_NAME$test_case > $MY_NAME$test_case; then\n' 196 | ' echo {1}{4}Sample test \#$test_case: Runtime Error{2} {6}\n' 197 | ' echo ========================================\n' 198 | ' echo Sample Input \#$test_case\n' 199 | ' cat $INPUT_NAME$test_case\n' 200 | ' else\n' 201 | ' if diff --brief --ignore-space-change --ignore-blank-lines $MY_NAME$test_case $OUTPUT_NAME$test_case; then\n' 202 | ' echo {1}{3}Sample test \#$test_case: Accepted{2} {6}\n' 203 | ' else\n' 204 | ' echo {1}{4}Sample test \#$test_case: Wrong Answer{2} {6}\n' 205 | ' echo ========================================\n' 206 | ' echo Sample Input \#$test_case\n' 207 | ' cat $INPUT_NAME$test_case\n' 208 | ' echo ========================================\n' 209 | ' echo Sample Output \#$test_case\n' 210 | ' cat $OUTPUT_NAME$test_case\n' 211 | ' echo ========================================\n' 212 | ' echo My Output \#$test_case\n' 213 | ' cat $MY_NAME$test_case\n' 214 | ' echo ========================================\n' 215 | ' fi\n' 216 | ' fi\n' 217 | 'done\n' 218 | .format(num_tests, BOLD, NORM, GREEN_F, RED_F, TIME_CMD, TIME_AP, run_cmd=param["RUN_CMD"])) 219 | call(['chmod', '+x', folder + 'test.sh']) 220 | 221 | 222 | # Main function. 223 | def main(): 224 | print (VERSION) 225 | parser = argparse.ArgumentParser() 226 | parser.add_argument('--language', '-l', default="c++14", help="The programming language you want to use " 227 | "(c++14, go)") 228 | parser.add_argument('contest', help="") 229 | args = parser.parse_args() 230 | 231 | contest = args.contest 232 | language = args.language 233 | 234 | # Find contest and problems. 235 | print ('Parsing contest %s for language %s, please wait...' % (contest, language)) 236 | content = parse_contest(contest) 237 | print (BOLD+GREEN_F+'*** Round name: '+content.name+' ***'+NORM) 238 | print ('Found %d problems!' % (len(content.problems))) 239 | 240 | # Find problems and test cases. 241 | TEMPLATE = language_params[language]["TEMPLATE"] 242 | for index, problem in enumerate(content.problems): 243 | print ('Downloading Problem %s: %s...' % (problem, content.problem_names[index])) 244 | folder = '%s-%s/%s/' % (contest, language, problem) 245 | call(['mkdir', '-p', folder]) 246 | call(['cp', '-n', TEMPLATE, '%s/%s.%s' % (folder, problem, TEMPLATE.split('.')[1])]) 247 | num_tests = parse_problem(folder, contest, problem) 248 | print('%d sample test(s) found.' % num_tests) 249 | generate_test_script(folder, language, num_tests, problem) 250 | print ('========================================') 251 | 252 | print ('Use ./test.sh to run sample tests in each directory.') 253 | 254 | 255 | if __name__ == '__main__': 256 | main() 257 | --------------------------------------------------------------------------------