tcjudge 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +7 -0
- data/bin/tcjudge +9 -0
- data/lib/tcjudge.rb +1720 -0
- metadata +87 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA1:
|
3
|
+
metadata.gz: dc2d36b33659b10272c63772bd929c657abe22c9
|
4
|
+
data.tar.gz: 04c85a239341cde9f95a4dde793d294473732702
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: b85e4e0e6eb3654e5691112bc7d21e22ca10666d55b6e97b1894758f30878d4b5b3e79277799509409d9903c41bf5f136895ff90b8bc89bfc8ef608875358d7f
|
7
|
+
data.tar.gz: 70fa8111894f313f5a3813fb2075b29401f66479695e73485ddf0ebc29241c03a72e803af23d38f55d7b4ea7c6dc13feccef8d0e7f402023cbb8391eacd5008a
|
data/bin/tcjudge
ADDED
data/lib/tcjudge.rb
ADDED
@@ -0,0 +1,1720 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
|
3
|
+
# TopCoder Local Judge by peryaudo
|
4
|
+
# MIT License
|
5
|
+
|
6
|
+
require 'io/console'
|
7
|
+
require 'tmpdir'
|
8
|
+
require 'mechanize'
|
9
|
+
|
10
|
+
class TopCoderScraper
|
11
|
+
def initialize(tmpdir)
|
12
|
+
@logged_in = false
|
13
|
+
@tmpdir = tmpdir
|
14
|
+
end
|
15
|
+
|
16
|
+
# return false if succeeded
|
17
|
+
def login(user_name, password)
|
18
|
+
if @logged_in
|
19
|
+
return false
|
20
|
+
end
|
21
|
+
|
22
|
+
@agent = Mechanize.new
|
23
|
+
|
24
|
+
next_page_uri = 'http://apps.topcoder.com/wiki/display/tc/Algorithm+Overview'
|
25
|
+
login_page_uri = 'http://community.topcoder.com/tc?&module=Login'
|
26
|
+
|
27
|
+
login_page = @agent.get(login_page_uri)
|
28
|
+
|
29
|
+
wiki_page = login_page.form_with(:name => 'frmLogin') do |f|
|
30
|
+
f.action = 'http://community.topcoder.com/tc'
|
31
|
+
f['nextpage'] = next_page_uri
|
32
|
+
f['username'] = user_name
|
33
|
+
f['password'] = password
|
34
|
+
end.click_button
|
35
|
+
|
36
|
+
if wiki_page.uri.to_s == next_page_uri
|
37
|
+
# succeeded
|
38
|
+
@logged_in = true
|
39
|
+
return false
|
40
|
+
else
|
41
|
+
return true
|
42
|
+
end
|
43
|
+
|
44
|
+
end
|
45
|
+
|
46
|
+
def is_cache_available(problem_name, required)
|
47
|
+
if required.include?(:problem_definition) && !File.exist?("#{ @tmpdir }/#{ problem_name }.bpd")
|
48
|
+
return false
|
49
|
+
end
|
50
|
+
if required.include?(:test_cases) && !File.exist?("#{ @tmpdir }/#{ problem_name }.btestcase")
|
51
|
+
return false
|
52
|
+
end
|
53
|
+
return true
|
54
|
+
end
|
55
|
+
|
56
|
+
def get_problem_definition(problem_name)
|
57
|
+
cache_file_name = "#{ @tmpdir }/#{ problem_name }.bpd"
|
58
|
+
|
59
|
+
root = nil
|
60
|
+
if File.exist?(cache_file_name)
|
61
|
+
return Marshal.restore(open(cache_file_name, 'rb'))
|
62
|
+
|
63
|
+
else
|
64
|
+
unless @logged_in
|
65
|
+
return nil
|
66
|
+
end
|
67
|
+
|
68
|
+
statement_uri = get_problem_page_uri(problem_name).sub('/tc?module=ProblemDetail', '/stat?c=problem_statement')
|
69
|
+
root = @agent.get(statement_uri).root
|
70
|
+
|
71
|
+
end
|
72
|
+
|
73
|
+
problem_definition = {}
|
74
|
+
|
75
|
+
problem_definition[:points] = @agent.get(get_problem_page_uri(problem_name)). \
|
76
|
+
root.xpath('//tr[contains(td/text(), "Point Value")]/td/text()').drop(1).map { |td| td.content.to_i }
|
77
|
+
|
78
|
+
table = root.xpath('//table[contains(tr/td/text(), "Class:")]').first
|
79
|
+
|
80
|
+
table.xpath('tr').each do |tr|
|
81
|
+
td = tr.xpath('td/text()').map { |td| td.content }
|
82
|
+
if td.length != 2
|
83
|
+
next
|
84
|
+
end
|
85
|
+
|
86
|
+
case td[0]
|
87
|
+
when 'Class:'
|
88
|
+
problem_definition[:class_name] = td[1]
|
89
|
+
when 'Method:'
|
90
|
+
problem_definition[:method_name] = td[1]
|
91
|
+
when 'Returns:'
|
92
|
+
problem_definition[:return_type] = td[1]
|
93
|
+
when 'Method signature:'
|
94
|
+
td[1].match(/\((.+)\)/) do |backward|
|
95
|
+
problem_definition[:parameters] = backward[1].split(',').map do |raw_parameter|
|
96
|
+
parameter = raw_parameter.strip.split(' ')
|
97
|
+
{:type => parameter[0], :name => parameter[1]}
|
98
|
+
end
|
99
|
+
end
|
100
|
+
end
|
101
|
+
end
|
102
|
+
|
103
|
+
unless File.exist?(cache_file_name)
|
104
|
+
Marshal.dump problem_definition, open(cache_file_name, 'wb')
|
105
|
+
end
|
106
|
+
|
107
|
+
return problem_definition
|
108
|
+
end
|
109
|
+
|
110
|
+
def get_test_cases(problem_name)
|
111
|
+
cache_file_name = "#{ @tmpdir }/#{ problem_name }.btestcase"
|
112
|
+
|
113
|
+
root = nil
|
114
|
+
if File.exist?(cache_file_name)
|
115
|
+
return Marshal.restore(open(cache_file_name, 'rb'))
|
116
|
+
|
117
|
+
else
|
118
|
+
unless @logged_in
|
119
|
+
return nil
|
120
|
+
end
|
121
|
+
|
122
|
+
solution_uri = get_solution_page_uri(get_problem_page_uri(problem_name))
|
123
|
+
|
124
|
+
root = @agent.get(solution_uri).root
|
125
|
+
|
126
|
+
end
|
127
|
+
|
128
|
+
test_cases = []
|
129
|
+
|
130
|
+
table = root.xpath('//comment()[contains(., "System Testing")]/following-sibling::table').first
|
131
|
+
|
132
|
+
table.xpath('tr[@valign="top"]').each do |tr|
|
133
|
+
test_case = tr.xpath('td[@class="statText"]/text()').map { |td| td.content.gsub(/(\r|\n)/, ' ') }
|
134
|
+
test_cases.push({:input => test_case[0], :output => test_case[1]})
|
135
|
+
end
|
136
|
+
|
137
|
+
unless File.exist?(cache_file_name)
|
138
|
+
Marshal.dump test_cases, open(cache_file_name, 'wb')
|
139
|
+
end
|
140
|
+
return test_cases
|
141
|
+
end
|
142
|
+
|
143
|
+
private
|
144
|
+
def get_problem_page_uri(problem_name)
|
145
|
+
unless @logged_in
|
146
|
+
return nil
|
147
|
+
end
|
148
|
+
|
149
|
+
unless @problem_page_uri_cache
|
150
|
+
@problem_page_uri_cache = {}
|
151
|
+
end
|
152
|
+
|
153
|
+
if @problem_page_uri_cache.include?(problem_name)
|
154
|
+
return @problem_page_uri_cache[problem_name]
|
155
|
+
end
|
156
|
+
|
157
|
+
@agent.get('http://community.topcoder.com/tc?module=ProblemArchive&class=' + problem_name) do |page|
|
158
|
+
return @problem_page_uri_cache[problem_name] =
|
159
|
+
page.root.xpath(
|
160
|
+
'//tr[normalize-space(td/a/text())="' + problem_name +
|
161
|
+
'"]/td/a[starts-with(@href, "/tc?module=ProblemDetail")]/@href').first.content
|
162
|
+
end
|
163
|
+
end
|
164
|
+
|
165
|
+
def get_solution_page_uri(problem_page_uri)
|
166
|
+
@agent.get(problem_page_uri) do |page|
|
167
|
+
return page.link_with(:href => /\/stat\?c\=problem_solution.+cr=[0-9]+/).href
|
168
|
+
end
|
169
|
+
end
|
170
|
+
end
|
171
|
+
|
172
|
+
CompileError = Class.new(StandardError)
|
173
|
+
|
174
|
+
class TopCoderTester
|
175
|
+
def generate_timestamp
|
176
|
+
return "// TIMESTAMP: #{ Time.now.to_i }"
|
177
|
+
end
|
178
|
+
|
179
|
+
def print_points(code)
|
180
|
+
code.match(/^\/\/ TIMESTAMP: ([0-9]+)$/) do |backward|
|
181
|
+
seconds = Time.now.to_i - backward[1].to_i
|
182
|
+
@problem_definition[:points].each_with_index do |total, i|
|
183
|
+
points = convert_seconds_to_points(seconds, total)
|
184
|
+
warn "Score: #{ sprintf('%.2f', points) } / #{ total } (#{ seconds } secs)"
|
185
|
+
end
|
186
|
+
end
|
187
|
+
return
|
188
|
+
end
|
189
|
+
|
190
|
+
def convert_seconds_to_points(seconds, mp)
|
191
|
+
tt = 60.0
|
192
|
+
pt = seconds / 60.0
|
193
|
+
return mp * (0.3 + (0.7 * tt ** 2) / (10.0 * pt ** 2 + tt **2))
|
194
|
+
end
|
195
|
+
|
196
|
+
def generate_signature()
|
197
|
+
return convert_type(@problem_definition[:return_type]) + ' ' + @problem_definition[:method_name] + '(' + \
|
198
|
+
(@problem_definition[:parameters].map { |parameter| convert_type(parameter[:type]) + ' ' + parameter[:name] }).join(', ') + ')'
|
199
|
+
end
|
200
|
+
|
201
|
+
# strictly parse comma separated parameters
|
202
|
+
def split_parameters(parameters_string)
|
203
|
+
|
204
|
+
depth = 0
|
205
|
+
prev = 0
|
206
|
+
start = false
|
207
|
+
|
208
|
+
parameters = []
|
209
|
+
|
210
|
+
parameters_string.length.times do |index|
|
211
|
+
case parameters_string[index]
|
212
|
+
when '"'
|
213
|
+
if start
|
214
|
+
depth += 1
|
215
|
+
else
|
216
|
+
depth -= 1
|
217
|
+
end
|
218
|
+
start = !start
|
219
|
+
when '{'
|
220
|
+
depth += 1
|
221
|
+
when '}'
|
222
|
+
depth -= 1
|
223
|
+
when ','
|
224
|
+
if depth == 0
|
225
|
+
cur = parameters_string[prev .. (index - 1)].strip
|
226
|
+
if !(cur.empty?)
|
227
|
+
parameters.push(cur)
|
228
|
+
end
|
229
|
+
prev = index + 1
|
230
|
+
end
|
231
|
+
end
|
232
|
+
end
|
233
|
+
|
234
|
+
cur = parameters_string[prev .. parameters_string.length - 1].strip
|
235
|
+
if !(cur.empty?)
|
236
|
+
parameters.push(cur)
|
237
|
+
end
|
238
|
+
|
239
|
+
return parameters
|
240
|
+
end
|
241
|
+
|
242
|
+
def perform_cut(code)
|
243
|
+
return code.split(/r?\n/).inject(['', true]) do |succ, cur|
|
244
|
+
if cur =~ /^\/\/ CUT begin/
|
245
|
+
[succ[0], false]
|
246
|
+
elsif cur =~ /^^\/\/ CUT end/
|
247
|
+
[succ[0], true]
|
248
|
+
else
|
249
|
+
if succ[1] == true
|
250
|
+
[succ[0] + "\n" + cur, succ[1]]
|
251
|
+
else
|
252
|
+
succ
|
253
|
+
end
|
254
|
+
end
|
255
|
+
end.first
|
256
|
+
end
|
257
|
+
end
|
258
|
+
|
259
|
+
class TopCoderCXXTester < TopCoderTester
|
260
|
+
def get_template(template_proc)
|
261
|
+
if template_proc.nil?
|
262
|
+
return <<EOS
|
263
|
+
#include <vector>
|
264
|
+
#include <string>
|
265
|
+
#include <map>
|
266
|
+
#include <set>
|
267
|
+
#include <algorithm>
|
268
|
+
#include <iostream>
|
269
|
+
#include <cmath>
|
270
|
+
#include <climits>
|
271
|
+
#include <queue>
|
272
|
+
#include <stack>
|
273
|
+
#include <numeric>
|
274
|
+
|
275
|
+
using namespace std;
|
276
|
+
|
277
|
+
// CUT begin
|
278
|
+
#{ generate_timestamp() }
|
279
|
+
// CUT end
|
280
|
+
|
281
|
+
class #{ @problem_definition[:class_name] } {
|
282
|
+
public:
|
283
|
+
#{ generate_signature() } {
|
284
|
+
}
|
285
|
+
};
|
286
|
+
EOS
|
287
|
+
else
|
288
|
+
return instance_eval(&template_proc)
|
289
|
+
end
|
290
|
+
end
|
291
|
+
|
292
|
+
def convert_type(type_name)
|
293
|
+
cxx_type_names = {'long' => 'long long', 'String' => 'string'}
|
294
|
+
|
295
|
+
is_array = false
|
296
|
+
base_type_name = type_name
|
297
|
+
if type_name =~ /\[\]$/
|
298
|
+
is_array = true
|
299
|
+
base_type_name = type_name.sub(/\[\]$/, '').strip
|
300
|
+
end
|
301
|
+
|
302
|
+
cxx_type_name = nil
|
303
|
+
if cxx_type_names.include?(base_type_name)
|
304
|
+
cxx_type_name = cxx_type_names[base_type_name]
|
305
|
+
else
|
306
|
+
cxx_type_name = base_type_name
|
307
|
+
end
|
308
|
+
|
309
|
+
if is_array
|
310
|
+
cxx_type_name = "vector<#{ cxx_type_name }>"
|
311
|
+
end
|
312
|
+
|
313
|
+
return cxx_type_name
|
314
|
+
end
|
315
|
+
|
316
|
+
def generate_tester()
|
317
|
+
tester = <<EOS
|
318
|
+
#include <iostream>
|
319
|
+
#include <vector>
|
320
|
+
#include <string>
|
321
|
+
#include <cstdlib>
|
322
|
+
#include <cmath>
|
323
|
+
|
324
|
+
using namespace std;
|
325
|
+
|
326
|
+
#{ generate_result_comparison_function() }
|
327
|
+
#{ generate_result_dumper_function() }
|
328
|
+
#{ generate_limitter_function() }
|
329
|
+
|
330
|
+
EOS
|
331
|
+
|
332
|
+
@test_cases.length.times do |index|
|
333
|
+
tester += generate_tester_function(index)
|
334
|
+
end
|
335
|
+
|
336
|
+
|
337
|
+
tester += <<EOS
|
338
|
+
int main(int argc, char *argv[])
|
339
|
+
{
|
340
|
+
setLimit();
|
341
|
+
switch(atoi(argv[1])) {
|
342
|
+
EOS
|
343
|
+
|
344
|
+
@test_cases.length.times do |index|
|
345
|
+
tester += " case #{ index }: return test#{ index }();\n"
|
346
|
+
end
|
347
|
+
|
348
|
+
tester += <<EOS
|
349
|
+
}
|
350
|
+
}
|
351
|
+
EOS
|
352
|
+
|
353
|
+
return tester
|
354
|
+
end
|
355
|
+
|
356
|
+
def generate_result_comparison_function
|
357
|
+
cxx_type = convert_type(@problem_definition[:return_type])
|
358
|
+
|
359
|
+
func = "bool compareResult(#{ cxx_type } from, #{ cxx_type } to)\n{\n"
|
360
|
+
case cxx_type
|
361
|
+
when 'double'
|
362
|
+
func += " return fabs(from - to) <= 1e-9;\n";
|
363
|
+
when 'vector<double>'
|
364
|
+
func += <<EOS
|
365
|
+
if (from.size() != to.size()) return false;
|
366
|
+
bool res = true;
|
367
|
+
for (int i = 0; i < from.size(); ++i) {
|
368
|
+
if (!(fabs(from[i] - to[i]) <= 1e-9)) {
|
369
|
+
res = false;
|
370
|
+
break;
|
371
|
+
}
|
372
|
+
}
|
373
|
+
return res;
|
374
|
+
EOS
|
375
|
+
else
|
376
|
+
func += " return from == to;\n"
|
377
|
+
end
|
378
|
+
|
379
|
+
func += "}\n"
|
380
|
+
|
381
|
+
return func
|
382
|
+
end
|
383
|
+
|
384
|
+
def generate_result_dumper_function
|
385
|
+
cxx_type = convert_type(@problem_definition[:return_type])
|
386
|
+
|
387
|
+
func = "void dumpResult(#{ cxx_type } result_)\n{\n"
|
388
|
+
if cxx_type =~ /^vector/
|
389
|
+
func += <<EOS
|
390
|
+
cout<<"{";
|
391
|
+
for (int i = 0; i < result_.size(); ++i) {
|
392
|
+
const #{cxx_type.sub(/^vector\<(.+)\>$/, '\\1')}& result = result_[i];
|
393
|
+
EOS
|
394
|
+
else
|
395
|
+
func += " const #{cxx_type.sub(/^vector\<(.+)\>$/, '\\1')}& result = result_;\n"
|
396
|
+
end
|
397
|
+
|
398
|
+
if cxx_type == 'string' || cxx_type == 'vector<string>'
|
399
|
+
func += ' cout<<"\""<<result<<"\"";'
|
400
|
+
func += "\n"
|
401
|
+
else
|
402
|
+
func += " cout<<result;\n"
|
403
|
+
end
|
404
|
+
|
405
|
+
if cxx_type =~ /^vector/
|
406
|
+
func += <<EOS
|
407
|
+
cout<<(i == result_.size() - 1 ? "" : ", ");
|
408
|
+
}
|
409
|
+
cout<<"}";
|
410
|
+
EOS
|
411
|
+
end
|
412
|
+
|
413
|
+
func += "}\n"
|
414
|
+
|
415
|
+
return func
|
416
|
+
end
|
417
|
+
|
418
|
+
def generate_limitter_function
|
419
|
+
return "void setLimit() {return;}\n"
|
420
|
+
end
|
421
|
+
|
422
|
+
def generate_tester_function(index)
|
423
|
+
return <<EOS
|
424
|
+
int test#{ index }(void)
|
425
|
+
{
|
426
|
+
#{ @problem_definition[:class_name] } target;
|
427
|
+
#{ generate_parameters(@test_cases[index][:input]) }
|
428
|
+
#{ generate_tester_call() }
|
429
|
+
#{ generate_parameter(@problem_definition[:return_type], "expected", @test_cases[index][:output]) }
|
430
|
+
if (compareResult(result, expected)) {
|
431
|
+
return 0;
|
432
|
+
} else {
|
433
|
+
dumpResult(result);
|
434
|
+
return 1;
|
435
|
+
}
|
436
|
+
}
|
437
|
+
EOS
|
438
|
+
end
|
439
|
+
|
440
|
+
def generate_parameters(parameters_string)
|
441
|
+
result = ''
|
442
|
+
parameters = split_parameters(parameters_string)
|
443
|
+
|
444
|
+
@problem_definition[:parameters].each_with_index do |parameter, index|
|
445
|
+
result += generate_parameter(parameter[:type], "param#{index}", parameters[index])
|
446
|
+
end
|
447
|
+
|
448
|
+
return result
|
449
|
+
end
|
450
|
+
|
451
|
+
def generate_primary_value(type, value)
|
452
|
+
case type
|
453
|
+
when 'String'
|
454
|
+
return "string(#{value})"
|
455
|
+
when 'long'
|
456
|
+
return "#{value}LL"
|
457
|
+
else
|
458
|
+
return value
|
459
|
+
end
|
460
|
+
end
|
461
|
+
|
462
|
+
def generate_parameter(type, name, value)
|
463
|
+
if type =~ /\[\]$/
|
464
|
+
# vector
|
465
|
+
element_type = type.sub(/\[\]$/, '')
|
466
|
+
elements = split_parameters(value[1, value.length - 2])
|
467
|
+
parameter = " #{ convert_type(type) } #{ name }(#{ elements.length });\n"
|
468
|
+
elements.each_with_index do |element, index|
|
469
|
+
parameter += " #{ name }[#{index}] = #{ generate_primary_value(element_type, element)};\n"
|
470
|
+
end
|
471
|
+
return parameter
|
472
|
+
else
|
473
|
+
return " #{ convert_type(type) } #{ name } = #{ generate_primary_value(type, value) };\n"
|
474
|
+
end
|
475
|
+
end
|
476
|
+
|
477
|
+
def generate_tester_call
|
478
|
+
return " #{ convert_type(@problem_definition[:return_type]) } result = target.#{ @problem_definition[:method_name] }(" +
|
479
|
+
(0 .. (@problem_definition[:parameters].length - 1)).map { |index| "param#{ index }" }.join(', ') + ");"
|
480
|
+
end
|
481
|
+
|
482
|
+
def initialize(problem_definition, test_cases = nil, tmpdir = nil, code = nil, compiler = nil)
|
483
|
+
@problem_definition = problem_definition
|
484
|
+
|
485
|
+
if test_cases != nil && tmpdir != nil && code != nil && compiler != nil
|
486
|
+
@test_cases = test_cases
|
487
|
+
@tmpdir = tmpdir
|
488
|
+
|
489
|
+
@tester_source_name = "#{@tmpdir}/tester.cpp"
|
490
|
+
@tester_name = "#{@tmpdir}/tester"
|
491
|
+
|
492
|
+
open(@tester_source_name, 'w').write "#{ perform_cut(code) }\n#{ generate_tester() }"
|
493
|
+
|
494
|
+
if File.basename(compiler, '.exe').downcase == 'cl' then
|
495
|
+
@compile_options = "#{compiler} /Fe\"#{@tester_name}\" \"#{@tester_source_name}\""
|
496
|
+
else
|
497
|
+
@compile_options = "#{compiler} \"#{@tester_source_name}\" -o \"#{@tester_name}\""
|
498
|
+
end
|
499
|
+
|
500
|
+
unless system(@compile_options)
|
501
|
+
raise CompileError
|
502
|
+
end
|
503
|
+
end
|
504
|
+
end
|
505
|
+
|
506
|
+
def run(index)
|
507
|
+
tester_options = "\"#{@tester_name}\" #{index}"
|
508
|
+
if system(tester_options)
|
509
|
+
return :AC
|
510
|
+
else
|
511
|
+
case $?.exitstatus
|
512
|
+
when 1
|
513
|
+
return :WA
|
514
|
+
else
|
515
|
+
if $?.exited?
|
516
|
+
return :UNKNOWN_ERROR
|
517
|
+
else
|
518
|
+
puts "\nCompiller Options: #{@compile_options}"
|
519
|
+
puts "Tester Options: #{tester_options}"
|
520
|
+
puts "Exit Status: #{$?}"
|
521
|
+
return :RUNTIME_ERROR
|
522
|
+
end
|
523
|
+
end
|
524
|
+
end
|
525
|
+
end
|
526
|
+
end
|
527
|
+
|
528
|
+
class TopCoderJavaTester < TopCoderTester
|
529
|
+
def get_template(template_proc)
|
530
|
+
if template_proc.nil?
|
531
|
+
return <<EOS
|
532
|
+
// CUT begin
|
533
|
+
#{ generate_timestamp() }
|
534
|
+
// CUT end
|
535
|
+
public class #{ @problem_definition[:class_name] } {
|
536
|
+
public #{ generate_signature() } {
|
537
|
+
}
|
538
|
+
}
|
539
|
+
EOS
|
540
|
+
else
|
541
|
+
return instance_eval(&template_proc)
|
542
|
+
end
|
543
|
+
end
|
544
|
+
|
545
|
+
def convert_type(type_name)
|
546
|
+
return type_name
|
547
|
+
end
|
548
|
+
|
549
|
+
def generate_tester()
|
550
|
+
tester = <<EOS
|
551
|
+
public class Tester {
|
552
|
+
#{ generate_result_comparison_function() }
|
553
|
+
#{ generate_result_dumper_function() }
|
554
|
+
EOS
|
555
|
+
|
556
|
+
@test_cases.length.times do |index|
|
557
|
+
tester += generate_tester_function(index)
|
558
|
+
end
|
559
|
+
|
560
|
+
tester += <<EOS
|
561
|
+
public static void main(String[] args) {
|
562
|
+
switch (Integer.parseInt(args[0])) {
|
563
|
+
EOS
|
564
|
+
@test_cases.length.times do |index|
|
565
|
+
tester += " case #{ index }: System.exit(test#{ index }());\n"
|
566
|
+
end
|
567
|
+
tester += <<EOS
|
568
|
+
}
|
569
|
+
return;
|
570
|
+
}
|
571
|
+
}
|
572
|
+
EOS
|
573
|
+
|
574
|
+
return tester
|
575
|
+
end
|
576
|
+
|
577
|
+
def generate_result_comparison_function
|
578
|
+
java_type = convert_type(@problem_definition[:return_type])
|
579
|
+
|
580
|
+
func = "static boolean compareResult(#{ java_type } from, #{ java_type } to)\n{\n"
|
581
|
+
case java_type
|
582
|
+
when 'double'
|
583
|
+
func += " return Math.abs(from - to) <= 1e-9;\n";
|
584
|
+
when 'double[]'
|
585
|
+
func += <<EOS
|
586
|
+
if (from.length != to.length) return false;
|
587
|
+
boolean res = true;
|
588
|
+
for (int i = 0; i < from.length; ++i) {
|
589
|
+
if (!(Math.abs(from[i] - to[i]) <= 1e-9)) {
|
590
|
+
res = false;
|
591
|
+
break;
|
592
|
+
}
|
593
|
+
}
|
594
|
+
return res;
|
595
|
+
EOS
|
596
|
+
else
|
597
|
+
if java_type =~ /\[\]$/
|
598
|
+
func += <<EOS
|
599
|
+
if (from.length != to.length) return false;
|
600
|
+
boolean res = true;
|
601
|
+
for (int i = 0; i < from.length; ++i) {
|
602
|
+
if (from[i] != to[i]) {
|
603
|
+
res = false;
|
604
|
+
break;
|
605
|
+
}
|
606
|
+
}
|
607
|
+
return res;
|
608
|
+
EOS
|
609
|
+
else
|
610
|
+
func += " return from == to;\n"
|
611
|
+
end
|
612
|
+
end
|
613
|
+
|
614
|
+
func += "}\n"
|
615
|
+
|
616
|
+
return func
|
617
|
+
end
|
618
|
+
|
619
|
+
def generate_result_dumper_function
|
620
|
+
java_type = convert_type(@problem_definition[:return_type])
|
621
|
+
|
622
|
+
func = "static void dumpResult(#{ java_type } result_)\n{\n"
|
623
|
+
if java_type =~ /\[\]$/
|
624
|
+
func += <<EOS
|
625
|
+
System.out.print("{");
|
626
|
+
for (int i = 0; i < result_.length; ++i) {
|
627
|
+
#{ java_type.sub('[]','') } result = result_[i];
|
628
|
+
EOS
|
629
|
+
else
|
630
|
+
func += " #{ java_type.sub('[]','') } result = result_;\n"
|
631
|
+
end
|
632
|
+
|
633
|
+
if java_type == 'string' || java_type == 'string[]'
|
634
|
+
func += ' System.out.print("\"" + result + "\"");'
|
635
|
+
func += "\n"
|
636
|
+
else
|
637
|
+
func += " System.out.print(String.valueOf(result));\n"
|
638
|
+
end
|
639
|
+
|
640
|
+
if java_type =~ /\[\]$/
|
641
|
+
func += <<EOS
|
642
|
+
System.out.print(i == result_.length - 1 ? "" : ", ");
|
643
|
+
}
|
644
|
+
System.out.print("}");
|
645
|
+
EOS
|
646
|
+
end
|
647
|
+
|
648
|
+
func += "}\n"
|
649
|
+
|
650
|
+
return func
|
651
|
+
end
|
652
|
+
|
653
|
+
def generate_tester_function(index)
|
654
|
+
return <<EOS
|
655
|
+
static int test#{ index }()
|
656
|
+
{
|
657
|
+
#{ @problem_definition[:class_name] } target = new #{ @problem_definition[:class_name] }();
|
658
|
+
#{ generate_parameters(@test_cases[index][:input]) }
|
659
|
+
#{ generate_tester_call() }
|
660
|
+
#{ generate_parameter(@problem_definition[:return_type], "expected", @test_cases[index][:output]) }
|
661
|
+
if (compareResult(result, expected)) {
|
662
|
+
return 0;
|
663
|
+
} else {
|
664
|
+
dumpResult(result);
|
665
|
+
return 1;
|
666
|
+
}
|
667
|
+
}
|
668
|
+
EOS
|
669
|
+
end
|
670
|
+
|
671
|
+
def generate_parameters(parameters_string)
|
672
|
+
result = ''
|
673
|
+
parameters = split_parameters(parameters_string)
|
674
|
+
|
675
|
+
@problem_definition[:parameters].each_with_index do |parameter, index|
|
676
|
+
result += generate_parameter(parameter[:type], "param#{index}", parameters[index])
|
677
|
+
end
|
678
|
+
|
679
|
+
return result
|
680
|
+
end
|
681
|
+
|
682
|
+
def generate_primary_value(type, value)
|
683
|
+
case type
|
684
|
+
when 'String'
|
685
|
+
return "#{value}"
|
686
|
+
when 'long'
|
687
|
+
return "#{value}L"
|
688
|
+
else
|
689
|
+
return value
|
690
|
+
end
|
691
|
+
end
|
692
|
+
|
693
|
+
def generate_parameter(type, name, value)
|
694
|
+
if type =~ /\[\]$/
|
695
|
+
# array
|
696
|
+
element_type = type.sub(/\[\]$/, '')
|
697
|
+
elements = split_parameters(value[1, value.length - 2])
|
698
|
+
parameter = " #{ convert_type(type) } #{ name } = { "
|
699
|
+
parameter += elements.map { |element| generate_primary_value(element_type, element) }.join(', ')
|
700
|
+
parameter += " };\n"
|
701
|
+
return parameter
|
702
|
+
else
|
703
|
+
return " #{ convert_type(type) } #{ name } = #{ generate_primary_value(type, value) };\n"
|
704
|
+
end
|
705
|
+
end
|
706
|
+
|
707
|
+
def generate_tester_call
|
708
|
+
return " #{ convert_type(@problem_definition[:return_type]) } result = target.#{ @problem_definition[:method_name] }(" +
|
709
|
+
(0 .. (@problem_definition[:parameters].length - 1)).map { |index| "param#{ index }" }.join(', ') + ");"
|
710
|
+
end
|
711
|
+
|
712
|
+
def initialize(problem_definition, test_cases = nil, tmpdir = nil, code = nil, compiler = nil)
|
713
|
+
@problem_definition = problem_definition
|
714
|
+
|
715
|
+
if test_cases != nil && tmpdir != nil && code != nil && compiler != nil
|
716
|
+
@test_cases = test_cases
|
717
|
+
@tmpdir = tmpdir
|
718
|
+
|
719
|
+
@tester_source_name = "#{@tmpdir}/Tester.java"
|
720
|
+
@solution_source_name = "#{@tmpdir}/#{@problem_definition[:class_name]}.java"
|
721
|
+
|
722
|
+
open(@tester_source_name, 'w') {|f| f.write generate_tester() }
|
723
|
+
open(@solution_source_name, 'w') {|f| f.write perform_cut(code) }
|
724
|
+
|
725
|
+
@compile_options = "#{compiler} #{@solution_source_name} #{@tester_source_name}"
|
726
|
+
puts @compile_options
|
727
|
+
unless system(@compile_options)
|
728
|
+
raise CompileError
|
729
|
+
end
|
730
|
+
end
|
731
|
+
end
|
732
|
+
|
733
|
+
def run(index)
|
734
|
+
tester_options = "java -classpath \"#{@tmpdir}\" Tester #{index}"
|
735
|
+
if system(tester_options)
|
736
|
+
return :AC
|
737
|
+
else
|
738
|
+
case $?.exitstatus
|
739
|
+
when 1
|
740
|
+
return :WA
|
741
|
+
else
|
742
|
+
if $?.exited?
|
743
|
+
return :UNKNOWN_ERROR
|
744
|
+
else
|
745
|
+
puts "\nCompiller Options: #{ @compile_options }"
|
746
|
+
puts "Tester Options: #{tester_options}"
|
747
|
+
puts "Exit Status: #{$?}"
|
748
|
+
return :RUNTIME_ERROR
|
749
|
+
end
|
750
|
+
end
|
751
|
+
end
|
752
|
+
end
|
753
|
+
|
754
|
+
end
|
755
|
+
|
756
|
+
class TopCoderCSharpTester < TopCoderTester
|
757
|
+
def get_template(template_proc)
|
758
|
+
if template_proc.nil?
|
759
|
+
return <<EOS
|
760
|
+
using System;
|
761
|
+
using System.Collections.Generic;
|
762
|
+
using System.Text;
|
763
|
+
|
764
|
+
// CUT begin
|
765
|
+
#{ generate_timestamp() }
|
766
|
+
// CUT end
|
767
|
+
|
768
|
+
public class #{ @problem_definition[:class_name] }
|
769
|
+
{
|
770
|
+
public #{ generate_signature() }
|
771
|
+
{
|
772
|
+
}
|
773
|
+
}
|
774
|
+
EOS
|
775
|
+
else
|
776
|
+
return instance_eval(&template_proc)
|
777
|
+
end
|
778
|
+
end
|
779
|
+
|
780
|
+
def convert_type(type_name)
|
781
|
+
return type_name.sub('String', 'string')
|
782
|
+
end
|
783
|
+
|
784
|
+
def generate_tester()
|
785
|
+
tester = <<EOS
|
786
|
+
public class Tester {
|
787
|
+
#{ generate_result_comparison_function() }
|
788
|
+
#{ generate_result_dumper_function() }
|
789
|
+
EOS
|
790
|
+
|
791
|
+
@test_cases.length.times do |index|
|
792
|
+
tester += generate_tester_function(index)
|
793
|
+
end
|
794
|
+
|
795
|
+
tester += <<EOS
|
796
|
+
static int Main(string[] args) {
|
797
|
+
switch (int.Parse(args[0])) {
|
798
|
+
EOS
|
799
|
+
@test_cases.length.times do |index|
|
800
|
+
tester += " case #{ index }: return test#{ index }();\n"
|
801
|
+
end
|
802
|
+
tester += <<EOS
|
803
|
+
}
|
804
|
+
return 0;
|
805
|
+
}
|
806
|
+
}
|
807
|
+
EOS
|
808
|
+
|
809
|
+
return tester
|
810
|
+
end
|
811
|
+
|
812
|
+
def generate_result_comparison_function
|
813
|
+
cs_type = convert_type(@problem_definition[:return_type])
|
814
|
+
|
815
|
+
func = "static bool compareResult(#{ cs_type } from, #{ cs_type } to)\n{\n"
|
816
|
+
case cs_type
|
817
|
+
when 'double'
|
818
|
+
func += " return Math.Abs(from - to) <= 1e-9;\n";
|
819
|
+
when 'double[]'
|
820
|
+
func += <<EOS
|
821
|
+
if (from.Length != to.Length) return false;
|
822
|
+
bool res = true;
|
823
|
+
for (int i = 0; i < from.Length; ++i) {
|
824
|
+
if (!(Math.abs(from[i] - to[i]) <= 1e-9)) {
|
825
|
+
res = false;
|
826
|
+
break;
|
827
|
+
}
|
828
|
+
}
|
829
|
+
return res;
|
830
|
+
EOS
|
831
|
+
else
|
832
|
+
if cs_type =~ /\[\]$/
|
833
|
+
func += <<EOS
|
834
|
+
if (from.Length != to.Length) return false;
|
835
|
+
bool res = true;
|
836
|
+
for (int i = 0; i < from.Length; ++i) {
|
837
|
+
if (from[i] != to[i]) {
|
838
|
+
res = false;
|
839
|
+
break;
|
840
|
+
}
|
841
|
+
}
|
842
|
+
return res;
|
843
|
+
EOS
|
844
|
+
else
|
845
|
+
func += " return from == to;\n"
|
846
|
+
end
|
847
|
+
end
|
848
|
+
|
849
|
+
func += "}\n"
|
850
|
+
|
851
|
+
return func
|
852
|
+
end
|
853
|
+
|
854
|
+
def generate_result_dumper_function
|
855
|
+
cs_type = convert_type(@problem_definition[:return_type])
|
856
|
+
|
857
|
+
func = "static void dumpResult(#{ cs_type } result_)\n{\n"
|
858
|
+
if cs_type =~ /\[\]$/
|
859
|
+
func += <<EOS
|
860
|
+
Console.Write("{");
|
861
|
+
for (int i = 0; i < result_.length; ++i) {
|
862
|
+
#{ cs_type.sub('[]','') } result = result_[i];
|
863
|
+
EOS
|
864
|
+
else
|
865
|
+
func += " #{ cs_type.sub('[]','') } result = result_;\n"
|
866
|
+
end
|
867
|
+
|
868
|
+
if cs_type == 'string' || cs_type == 'string[]'
|
869
|
+
func += ' Console.Write("\"" + result + "\"");'
|
870
|
+
func += "\n"
|
871
|
+
else
|
872
|
+
func += " Console.Write(result.ToString());\n"
|
873
|
+
end
|
874
|
+
|
875
|
+
if cs_type =~ /\[\]$/
|
876
|
+
func += <<EOS
|
877
|
+
System.out.print(i == result_.length - 1 ? "" : ", ");
|
878
|
+
}
|
879
|
+
System.out.print("}");
|
880
|
+
EOS
|
881
|
+
end
|
882
|
+
|
883
|
+
func += "}\n"
|
884
|
+
|
885
|
+
return func
|
886
|
+
end
|
887
|
+
|
888
|
+
def generate_tester_function(index)
|
889
|
+
return <<EOS
|
890
|
+
static int test#{ index }()
|
891
|
+
{
|
892
|
+
#{ @problem_definition[:class_name] } target = new #{ @problem_definition[:class_name] }();
|
893
|
+
#{ generate_parameters(@test_cases[index][:input]) }
|
894
|
+
#{ generate_tester_call() }
|
895
|
+
#{ generate_parameter(@problem_definition[:return_type], "expected", @test_cases[index][:output]) }
|
896
|
+
if (compareResult(result, expected)) {
|
897
|
+
return 0;
|
898
|
+
} else {
|
899
|
+
dumpResult(result);
|
900
|
+
return 1;
|
901
|
+
}
|
902
|
+
}
|
903
|
+
EOS
|
904
|
+
end
|
905
|
+
|
906
|
+
def generate_parameters(parameters_string)
|
907
|
+
result = ''
|
908
|
+
parameters = split_parameters(parameters_string)
|
909
|
+
|
910
|
+
@problem_definition[:parameters].each_with_index do |parameter, index|
|
911
|
+
result += generate_parameter(parameter[:type], "param#{index}", parameters[index])
|
912
|
+
end
|
913
|
+
|
914
|
+
return result
|
915
|
+
end
|
916
|
+
|
917
|
+
def generate_primary_value(type, value)
|
918
|
+
case type
|
919
|
+
when 'String'
|
920
|
+
return "#{value}"
|
921
|
+
when 'long'
|
922
|
+
return "#{value}L"
|
923
|
+
else
|
924
|
+
return value
|
925
|
+
end
|
926
|
+
end
|
927
|
+
|
928
|
+
def generate_parameter(type, name, value)
|
929
|
+
if type =~ /\[\]$/
|
930
|
+
# array
|
931
|
+
element_type = type.sub(/\[\]$/, '')
|
932
|
+
elements = split_parameters(value[1, value.length - 2])
|
933
|
+
parameter = " #{ convert_type(type) } #{ name } = new #{ convert_type(type) } { "
|
934
|
+
parameter += elements.map { |element| generate_primary_value(element_type, element) }.join(', ')
|
935
|
+
parameter += " };\n"
|
936
|
+
return parameter
|
937
|
+
else
|
938
|
+
return " #{ convert_type(type) } #{ name } = #{ generate_primary_value(type, value) };\n"
|
939
|
+
end
|
940
|
+
end
|
941
|
+
|
942
|
+
def generate_tester_call
|
943
|
+
return " #{ convert_type(@problem_definition[:return_type]) } result = target.#{ @problem_definition[:method_name] }(" +
|
944
|
+
(0 .. (@problem_definition[:parameters].length - 1)).map { |index| "param#{ index }" }.join(', ') + ");"
|
945
|
+
end
|
946
|
+
|
947
|
+
def initialize(problem_definition, test_cases = nil, tmpdir = nil, code = nil, compiler = nil)
|
948
|
+
@problem_definition = problem_definition
|
949
|
+
|
950
|
+
if test_cases != nil && tmpdir != nil && code != nil && compiler != nil
|
951
|
+
@test_cases = test_cases
|
952
|
+
@tmpdir = tmpdir
|
953
|
+
|
954
|
+
@tester_source_name = "#{@tmpdir}/tester.cs"
|
955
|
+
@tester_name = "#{@tmpdir}/tester.exe"
|
956
|
+
|
957
|
+
open(@tester_source_name, 'w') {|f| f.write perform_cut(code) + generate_tester() }
|
958
|
+
|
959
|
+
@compiler = compiler
|
960
|
+
if File.basename(compiler, '.exe').downcase == 'csc'
|
961
|
+
@compile_options = "#{compiler} /r:System.Numerics.dll #{@tester_source_name} /out:\"#{@tester_name}\""
|
962
|
+
else
|
963
|
+
@compile_options = "#{compiler} /r:System.Numerics.dll #{@tester_source_name} -out:\"#{@tester_name}\""
|
964
|
+
end
|
965
|
+
puts @compile_options
|
966
|
+
unless system(@compile_options)
|
967
|
+
raise CompileError
|
968
|
+
end
|
969
|
+
end
|
970
|
+
end
|
971
|
+
|
972
|
+
def run(index)
|
973
|
+
tester_options = nil
|
974
|
+
if File.basename(@compiler, '.exe').downcase == 'csc'
|
975
|
+
tester_options = "\"#{@tester_name}\" #{index}"
|
976
|
+
else
|
977
|
+
tester_options = "mono \"#{@tester_name}\" #{index}"
|
978
|
+
end
|
979
|
+
if system(tester_options)
|
980
|
+
return :AC
|
981
|
+
else
|
982
|
+
case $?.exitstatus
|
983
|
+
when 1
|
984
|
+
return :WA
|
985
|
+
else
|
986
|
+
if $?.exited?
|
987
|
+
return :UNKNOWN_ERROR
|
988
|
+
else
|
989
|
+
puts "\nCompiller Options: #{ @compile_options }"
|
990
|
+
puts "Tester Options: #{tester_options}"
|
991
|
+
puts "Exit Status: #{$?}"
|
992
|
+
return :RUNTIME_ERROR
|
993
|
+
end
|
994
|
+
end
|
995
|
+
end
|
996
|
+
end
|
997
|
+
|
998
|
+
end
|
999
|
+
|
1000
|
+
class TopCoderHaskellTester < TopCoderTester
|
1001
|
+
def generate_timestamp
|
1002
|
+
return "-- TIMESTAMP: #{ Time.now.to_i }"
|
1003
|
+
end
|
1004
|
+
|
1005
|
+
def print_points(code)
|
1006
|
+
code.match(/^-- TIMESTAMP: ([0-9]+)$/) do |backward|
|
1007
|
+
seconds = Time.now.to_i - backward[1].to_i
|
1008
|
+
@problem_definition[:points].each_with_index do |total, i|
|
1009
|
+
points = convert_seconds_to_points(seconds, total)
|
1010
|
+
warn "Score: #{ sprintf('%.2f', points) } / #{ total } (#{ seconds } secs)"
|
1011
|
+
end
|
1012
|
+
end
|
1013
|
+
return
|
1014
|
+
end
|
1015
|
+
|
1016
|
+
def generate_signature()
|
1017
|
+
return @problem_definition[:method_name] + ' :: ' + \
|
1018
|
+
(@problem_definition[:parameters].map { |parameter| convert_type(parameter[:type]) }).join(' -> ') + ' -> ' + convert_type(@problem_definition[:return_type])
|
1019
|
+
end
|
1020
|
+
|
1021
|
+
|
1022
|
+
def get_template(template_proc)
|
1023
|
+
if template_proc.nil?
|
1024
|
+
return <<EOS
|
1025
|
+
-- CUT begin
|
1026
|
+
#{ generate_timestamp() }
|
1027
|
+
-- CUT end
|
1028
|
+
|
1029
|
+
#{ generate_signature() }
|
1030
|
+
#{ @problem_definition[:method_name] } #{ (@problem_definition[:parameters].map { |parameter| parameter[:name] }).join(' ') } =
|
1031
|
+
EOS
|
1032
|
+
else
|
1033
|
+
return instance_eval(&template_proc)
|
1034
|
+
end
|
1035
|
+
end
|
1036
|
+
|
1037
|
+
def convert_type(type_name)
|
1038
|
+
haskell_type_names = {'int' => 'Int', 'long' => 'Int', 'double' => 'Double', 'String' => 'string'}
|
1039
|
+
|
1040
|
+
is_array = false
|
1041
|
+
base_type_name = type_name
|
1042
|
+
if type_name =~ /\[\]$/
|
1043
|
+
is_array = true
|
1044
|
+
base_type_name = type_name.sub(/\[\]$/, '').strip
|
1045
|
+
end
|
1046
|
+
|
1047
|
+
haskell_type_name = nil
|
1048
|
+
if haskell_type_names.include?(base_type_name)
|
1049
|
+
haskell_type_name = haskell_type_names[base_type_name]
|
1050
|
+
else
|
1051
|
+
haskell_type_name = base_type_name
|
1052
|
+
end
|
1053
|
+
|
1054
|
+
if is_array
|
1055
|
+
haskell_type_name = "[#{ haskell_type_name }]"
|
1056
|
+
end
|
1057
|
+
|
1058
|
+
return haskell_type_name
|
1059
|
+
end
|
1060
|
+
|
1061
|
+
def generate_tester()
|
1062
|
+
tester = <<EOS
|
1063
|
+
#{ generate_result_comparison_function() }
|
1064
|
+
#{ generate_result_dumper_function() }
|
1065
|
+
|
1066
|
+
EOS
|
1067
|
+
|
1068
|
+
@test_cases.length.times do |index|
|
1069
|
+
tester += generate_tester_function(index)
|
1070
|
+
end
|
1071
|
+
|
1072
|
+
|
1073
|
+
tester += <<EOS
|
1074
|
+
|
1075
|
+
myExitWith 0 = exitWith (ExitSuccess)
|
1076
|
+
myExitWith x = exitWith (ExitFailure x)
|
1077
|
+
|
1078
|
+
main = do args <- getArgs
|
1079
|
+
res <- test (read (head args))
|
1080
|
+
myExitWith res
|
1081
|
+
EOS
|
1082
|
+
|
1083
|
+
return tester
|
1084
|
+
end
|
1085
|
+
|
1086
|
+
def generate_result_comparison_function
|
1087
|
+
haskell_type = convert_type(@problem_definition[:return_type])
|
1088
|
+
|
1089
|
+
func = nil
|
1090
|
+
|
1091
|
+
case haskell_type
|
1092
|
+
when 'Double'
|
1093
|
+
func = "compareResult from to = abs (from - to) <= 1e-9\n";
|
1094
|
+
when '[Double]'
|
1095
|
+
func = <<EOS
|
1096
|
+
compareResult (x:xs) (y:ys) = if abs (from - to) <= 1e-9 then (compareResult xs ys) else False
|
1097
|
+
compareResult [] [] = True
|
1098
|
+
compareResult _ _ = False
|
1099
|
+
EOS
|
1100
|
+
else
|
1101
|
+
func = "compareResult from to = from == to\n"
|
1102
|
+
end
|
1103
|
+
|
1104
|
+
return func
|
1105
|
+
end
|
1106
|
+
|
1107
|
+
def generate_result_dumper_function
|
1108
|
+
return "dumpResult = print"
|
1109
|
+
end
|
1110
|
+
|
1111
|
+
def generate_tester_function(index)
|
1112
|
+
return <<EOS
|
1113
|
+
test #{ index } = do
|
1114
|
+
#{ generate_parameters(@test_cases[index][:input]) }
|
1115
|
+
#{ generate_tester_call() }
|
1116
|
+
#{ generate_parameter(@problem_definition[:return_type], "expected", @test_cases[index][:output]) }
|
1117
|
+
if (compareResult result expected) then
|
1118
|
+
return 0
|
1119
|
+
else
|
1120
|
+
(dumpResult result) >> (return 1)
|
1121
|
+
|
1122
|
+
EOS
|
1123
|
+
end
|
1124
|
+
|
1125
|
+
def generate_parameters(parameters_string)
|
1126
|
+
result = ''
|
1127
|
+
parameters = split_parameters(parameters_string)
|
1128
|
+
|
1129
|
+
@problem_definition[:parameters].each_with_index do |parameter, index|
|
1130
|
+
result += generate_parameter(parameter[:type], "param#{index}", parameters[index])
|
1131
|
+
end
|
1132
|
+
|
1133
|
+
return result
|
1134
|
+
end
|
1135
|
+
|
1136
|
+
def generate_parameter(type, name, value)
|
1137
|
+
if type =~ /\[\]$/
|
1138
|
+
elements = split_parameters(value[1, value.length - 2])
|
1139
|
+
return " let #{name} = [" + elements.join(', ') + "]\n"
|
1140
|
+
else
|
1141
|
+
return " let #{name} = #{ value }\n"
|
1142
|
+
end
|
1143
|
+
end
|
1144
|
+
|
1145
|
+
def generate_tester_call
|
1146
|
+
return " let result = #{ @problem_definition[:method_name] } " +
|
1147
|
+
(0 .. (@problem_definition[:parameters].length - 1)).map { |index| "param#{ index }" }.join(' ')
|
1148
|
+
end
|
1149
|
+
|
1150
|
+
def initialize(problem_definition, test_cases = nil, tmpdir = nil, code = nil, compiler = nil)
|
1151
|
+
@problem_definition = problem_definition
|
1152
|
+
|
1153
|
+
if test_cases != nil && tmpdir != nil && code != nil && compiler != nil
|
1154
|
+
@test_cases = test_cases
|
1155
|
+
@tmpdir = tmpdir
|
1156
|
+
|
1157
|
+
@tester_source_name = "#{@tmpdir}/tester.hs"
|
1158
|
+
@tester_name = "#{@tmpdir}/tester"
|
1159
|
+
|
1160
|
+
open(@tester_source_name, 'w').write <<EOS
|
1161
|
+
import System.Environment
|
1162
|
+
import System.Exit
|
1163
|
+
|
1164
|
+
#{ perform_cut(code) }
|
1165
|
+
#{ generate_tester() }
|
1166
|
+
EOS
|
1167
|
+
|
1168
|
+
@compile_options = "#{compiler} \"#{@tester_source_name}\" -o \"#{@tester_name}\""
|
1169
|
+
|
1170
|
+
unless system(@compile_options)
|
1171
|
+
raise CompileError
|
1172
|
+
end
|
1173
|
+
end
|
1174
|
+
end
|
1175
|
+
|
1176
|
+
def run(index)
|
1177
|
+
tester_options = "\"#{@tester_name}\" #{index}"
|
1178
|
+
if system(tester_options)
|
1179
|
+
return :AC
|
1180
|
+
else
|
1181
|
+
case $?.exitstatus
|
1182
|
+
when 1
|
1183
|
+
return :WA
|
1184
|
+
else
|
1185
|
+
if $?.exited?
|
1186
|
+
return :UNKNOWN_ERROR
|
1187
|
+
else
|
1188
|
+
puts "\nCompiller Options: #{@compile_options}"
|
1189
|
+
puts "Tester Options: #{tester_options}"
|
1190
|
+
puts "Exit Status: #{$?}"
|
1191
|
+
return :RUNTIME_ERROR
|
1192
|
+
end
|
1193
|
+
end
|
1194
|
+
end
|
1195
|
+
end
|
1196
|
+
|
1197
|
+
def perform_cut(code)
|
1198
|
+
return code.split(/r?\n/).inject(['', true]) do |succ, cur|
|
1199
|
+
if cur =~ /^-- CUT begin/
|
1200
|
+
[succ[0], false]
|
1201
|
+
elsif cur =~ /^-- CUT end/
|
1202
|
+
[succ[0], true]
|
1203
|
+
else
|
1204
|
+
if succ[1] == true
|
1205
|
+
[succ[0] + "\n" + cur, succ[1]]
|
1206
|
+
else
|
1207
|
+
succ
|
1208
|
+
end
|
1209
|
+
end
|
1210
|
+
end.first
|
1211
|
+
end
|
1212
|
+
end
|
1213
|
+
|
1214
|
+
class TopCoderPythonTester < TopCoderTester
|
1215
|
+
def generate_timestamp
|
1216
|
+
return "# TIMESTAMP: #{ Time.now.to_i }"
|
1217
|
+
end
|
1218
|
+
|
1219
|
+
def print_points(code)
|
1220
|
+
code.match(/^# TIMESTAMP: ([0-9]+)$/) do |backward|
|
1221
|
+
seconds = Time.now.to_i - backward[1].to_i
|
1222
|
+
@problem_definition[:points].each_with_index do |total, i|
|
1223
|
+
points = convert_seconds_to_points(seconds, total)
|
1224
|
+
warn "Score: #{ sprintf('%.2f', points) } / #{ total } (#{ seconds } secs)"
|
1225
|
+
end
|
1226
|
+
end
|
1227
|
+
return
|
1228
|
+
end
|
1229
|
+
|
1230
|
+
def generate_signature()
|
1231
|
+
return @problem_definition[:method_name] + '(self, ' + \
|
1232
|
+
(@problem_definition[:parameters].map { |parameter| parameter[:name] }).join(', ') + ')'
|
1233
|
+
end
|
1234
|
+
|
1235
|
+
|
1236
|
+
def get_template(template_proc)
|
1237
|
+
if template_proc.nil?
|
1238
|
+
return <<EOS
|
1239
|
+
# -*- coding: utf-8 -*-
|
1240
|
+
|
1241
|
+
import math,string,itertools,fractions,heapq,collections,re,array,bisect
|
1242
|
+
|
1243
|
+
# CUT begin
|
1244
|
+
#{ generate_timestamp() }
|
1245
|
+
# CUT end
|
1246
|
+
|
1247
|
+
class #{ @problem_definition[:class_name] }:
|
1248
|
+
def #{ generate_signature() }:
|
1249
|
+
|
1250
|
+
EOS
|
1251
|
+
else
|
1252
|
+
return instance_eval(&template_proc)
|
1253
|
+
end
|
1254
|
+
end
|
1255
|
+
|
1256
|
+
def convert_type(type_name)
|
1257
|
+
return type_name
|
1258
|
+
end
|
1259
|
+
|
1260
|
+
def generate_tester()
|
1261
|
+
tester = <<EOS
|
1262
|
+
class Tester:
|
1263
|
+
#{ generate_result_comparison_function() }
|
1264
|
+
#{ generate_result_dumper_function() }
|
1265
|
+
EOS
|
1266
|
+
|
1267
|
+
@test_cases.length.times do |index|
|
1268
|
+
tester += generate_tester_function(index)
|
1269
|
+
end
|
1270
|
+
|
1271
|
+
tester += <<EOS
|
1272
|
+
import sys
|
1273
|
+
|
1274
|
+
if __name__ == '__main__':
|
1275
|
+
tester = Tester()
|
1276
|
+
sys.exit(eval('tester.test' + sys.argv[1] + '()'))
|
1277
|
+
EOS
|
1278
|
+
|
1279
|
+
return tester
|
1280
|
+
end
|
1281
|
+
|
1282
|
+
def generate_result_comparison_function
|
1283
|
+
func = <<EOS
|
1284
|
+
def compareResult(self, from_, to_):
|
1285
|
+
EOS
|
1286
|
+
case @problem_definition[:return_type]
|
1287
|
+
when 'double'
|
1288
|
+
func += <<EOS
|
1289
|
+
return abs(from_ - to_) <= 1e-9
|
1290
|
+
EOS
|
1291
|
+
when 'double[]'
|
1292
|
+
func += <<EOS
|
1293
|
+
return reduce(lambda x, y: x && (abs(y[0] - y[1]) <= 1e-9), zip(from_, to_), True)
|
1294
|
+
EOS
|
1295
|
+
else
|
1296
|
+
func += <<EOS
|
1297
|
+
return from_ == to_
|
1298
|
+
EOS
|
1299
|
+
end
|
1300
|
+
|
1301
|
+
return func;
|
1302
|
+
end
|
1303
|
+
|
1304
|
+
def generate_result_dumper_function
|
1305
|
+
return " def dumpResult(self, result_):\n print result_\n"
|
1306
|
+
end
|
1307
|
+
|
1308
|
+
def generate_tester_function(index)
|
1309
|
+
return <<EOS
|
1310
|
+
def test#{index}(self):
|
1311
|
+
target = #{ @problem_definition[:class_name] }();
|
1312
|
+
#{ generate_parameters(@test_cases[index][:input]) }
|
1313
|
+
#{ generate_tester_call() }
|
1314
|
+
#{ generate_parameter(@problem_definition[:return_type], "expected", @test_cases[index][:output]) }
|
1315
|
+
if self.compareResult(result, expected):
|
1316
|
+
return 0
|
1317
|
+
else:
|
1318
|
+
self.dumpResult(result)
|
1319
|
+
return 1
|
1320
|
+
|
1321
|
+
EOS
|
1322
|
+
end
|
1323
|
+
|
1324
|
+
def generate_parameters(parameters_string)
|
1325
|
+
result = ''
|
1326
|
+
parameters = split_parameters(parameters_string)
|
1327
|
+
|
1328
|
+
@problem_definition[:parameters].each_with_index do |parameter, index|
|
1329
|
+
result += generate_parameter(parameter[:type], "param#{index}", parameters[index])
|
1330
|
+
end
|
1331
|
+
|
1332
|
+
return result
|
1333
|
+
end
|
1334
|
+
|
1335
|
+
def generate_primary_value(type, value)
|
1336
|
+
case type
|
1337
|
+
when 'String'
|
1338
|
+
return "#{value}"
|
1339
|
+
when 'long'
|
1340
|
+
return "#{value}L"
|
1341
|
+
else
|
1342
|
+
return value
|
1343
|
+
end
|
1344
|
+
end
|
1345
|
+
|
1346
|
+
def generate_parameter(type, name, value)
|
1347
|
+
if type =~ /\[\]$/
|
1348
|
+
# array
|
1349
|
+
element_type = type.sub(/\[\]$/, '')
|
1350
|
+
elements = split_parameters(value[1, value.length - 2])
|
1351
|
+
parameter = " #{ name } = [ "
|
1352
|
+
parameter += elements.map { |element| generate_primary_value(element_type, element) }.join(', ')
|
1353
|
+
parameter += " ]\n"
|
1354
|
+
return parameter
|
1355
|
+
else
|
1356
|
+
return " #{ name } = #{ generate_primary_value(type, value) };\n"
|
1357
|
+
end
|
1358
|
+
end
|
1359
|
+
|
1360
|
+
def generate_tester_call
|
1361
|
+
return " result = target.#{ @problem_definition[:method_name] }(" +
|
1362
|
+
(0 .. (@problem_definition[:parameters].length - 1)).map { |index| "param#{ index }" }.join(', ') + ");"
|
1363
|
+
end
|
1364
|
+
|
1365
|
+
def initialize(problem_definition, test_cases = nil, tmpdir = nil, code = nil, interpreter = nil)
|
1366
|
+
@problem_definition = problem_definition
|
1367
|
+
|
1368
|
+
if test_cases != nil && tmpdir != nil && code != nil && interpreter != nil
|
1369
|
+
@test_cases = test_cases
|
1370
|
+
@tmpdir = tmpdir
|
1371
|
+
|
1372
|
+
@tester_source_name = "#{@tmpdir}/tester.py"
|
1373
|
+
|
1374
|
+
open(@tester_source_name, 'w').write "#{ perform_cut(code) }\n#{ generate_tester() }"
|
1375
|
+
|
1376
|
+
@interpreter = interpreter
|
1377
|
+
end
|
1378
|
+
end
|
1379
|
+
|
1380
|
+
def run(index)
|
1381
|
+
tester_options = "#{@interpreter} #{@tester_source_name} #{index}"
|
1382
|
+
if system(tester_options)
|
1383
|
+
return :AC
|
1384
|
+
else
|
1385
|
+
case $?.exitstatus
|
1386
|
+
when 1
|
1387
|
+
return :WA
|
1388
|
+
else
|
1389
|
+
if $?.exited?
|
1390
|
+
return :UNKNOWN_ERROR
|
1391
|
+
else
|
1392
|
+
puts "\nCompiller Options: #{ @compile_options }"
|
1393
|
+
puts "Tester Options: #{tester_options}"
|
1394
|
+
puts "Exit Status: #{$?}"
|
1395
|
+
return :RUNTIME_ERROR
|
1396
|
+
end
|
1397
|
+
end
|
1398
|
+
end
|
1399
|
+
end
|
1400
|
+
|
1401
|
+
end
|
1402
|
+
|
1403
|
+
class TCJudge
|
1404
|
+
def initialize
|
1405
|
+
@languages = { :CXX => TopCoderCXXTester, :Java => TopCoderJavaTester,
|
1406
|
+
:CSharp => TopCoderCSharpTester, :VB => nil,
|
1407
|
+
:Python => TopCoderPythonTester, :Haskell => TopCoderHaskellTester }
|
1408
|
+
@default_compilers = { :CXX => 'g++', :Java => 'javac', :CSharp => 'mcs', :VB => 'vbc', :Python => 'python', :Haskell => 'ghc' }
|
1409
|
+
@extensions = { '.cpp' => :CXX, '.cc' => :CXX, '.cxx' => :CXX,
|
1410
|
+
'.java' => :Java, '.cs' => :CSharp, '.vb' => :VB , '.py' => :Python, '.hs' => :Haskell }
|
1411
|
+
|
1412
|
+
tcjudegrc_file_name = File.expand_path('~/.tcjudgerc')
|
1413
|
+
if File.exist?(tcjudegrc_file_name)
|
1414
|
+
instance_eval File.read(tcjudegrc_file_name)
|
1415
|
+
end
|
1416
|
+
end
|
1417
|
+
|
1418
|
+
def start(argv)
|
1419
|
+
warn 'TopCoder Local Judge by peryaudo'
|
1420
|
+
|
1421
|
+
filenames = []
|
1422
|
+
options = []
|
1423
|
+
|
1424
|
+
argv.each do |arg|
|
1425
|
+
if arg =~ /^--/
|
1426
|
+
options.push arg
|
1427
|
+
else
|
1428
|
+
filenames.push arg
|
1429
|
+
end
|
1430
|
+
end
|
1431
|
+
|
1432
|
+
if filenames.length != 1 && filenames.length != 2
|
1433
|
+
warn 'usage: tcjudge MyProblemSolution.cpp'
|
1434
|
+
warn " tcjudge [command] MyProblemSolution.cpp\n"
|
1435
|
+
warn 'commands: create - create template for the problem'
|
1436
|
+
warn ' judge - judge the file'
|
1437
|
+
warn ' clean - output the cleaned code to stdout'
|
1438
|
+
return
|
1439
|
+
end
|
1440
|
+
|
1441
|
+
command = nil
|
1442
|
+
filename = nil
|
1443
|
+
|
1444
|
+
if filenames.length == 1
|
1445
|
+
if filenames[0] == 'solved' then
|
1446
|
+
command = 'solved'
|
1447
|
+
else
|
1448
|
+
command = 'judge'
|
1449
|
+
filename = filenames[0]
|
1450
|
+
end
|
1451
|
+
elsif filenames.length == 2
|
1452
|
+
command = filenames[0]
|
1453
|
+
filename = filenames[1]
|
1454
|
+
end
|
1455
|
+
|
1456
|
+
case command
|
1457
|
+
when 'create'
|
1458
|
+
write_template filename
|
1459
|
+
when 'judge'
|
1460
|
+
judge filename, options.include?('--force')
|
1461
|
+
when 'clean'
|
1462
|
+
clean filename
|
1463
|
+
warn 'tcjudge: wrote the cleaned code to stdout'
|
1464
|
+
when 'solved'
|
1465
|
+
show_solved
|
1466
|
+
else
|
1467
|
+
warn 'tcjudge: invalid command line option'
|
1468
|
+
end
|
1469
|
+
end
|
1470
|
+
|
1471
|
+
def write_template(file_name)
|
1472
|
+
if File.exist?(file_name)
|
1473
|
+
warn "tcjudge: file #{ file_name } already exists."
|
1474
|
+
return
|
1475
|
+
end
|
1476
|
+
|
1477
|
+
extension = File.extname(file_name)
|
1478
|
+
unless @extensions.include?(extension)
|
1479
|
+
warn 'tcjudge: cannot detect the language'
|
1480
|
+
return
|
1481
|
+
end
|
1482
|
+
|
1483
|
+
language = @languages[@extensions[extension]]
|
1484
|
+
|
1485
|
+
if language.nil?
|
1486
|
+
warn 'tcjudge: the language is not supported yet'
|
1487
|
+
return
|
1488
|
+
end
|
1489
|
+
|
1490
|
+
problem_name = File.basename(file_name, extension)
|
1491
|
+
|
1492
|
+
scraper = TopCoderScraper.new(Dir.tmpdir)
|
1493
|
+
unless scraper.is_cache_available(problem_name, [:problem_definition])
|
1494
|
+
if login(scraper)
|
1495
|
+
return
|
1496
|
+
end
|
1497
|
+
end
|
1498
|
+
|
1499
|
+
$stderr.print 'Obtaining problem definition...'
|
1500
|
+
problem_definition = nil
|
1501
|
+
begin
|
1502
|
+
problem_definition = scraper.get_problem_definition(problem_name)
|
1503
|
+
rescue
|
1504
|
+
warn "\ntcjudge: couldn't retrive the problem definition of #{ problem_name }."
|
1505
|
+
return
|
1506
|
+
end
|
1507
|
+
|
1508
|
+
warn 'ok.'
|
1509
|
+
|
1510
|
+
$stderr.print 'Writing template...'
|
1511
|
+
|
1512
|
+
open file_name, 'w' do |destination|
|
1513
|
+
@template ||= {}
|
1514
|
+
@template[@extensions[extension]] ||= nil
|
1515
|
+
destination.write language.new(problem_definition).get_template(@template[@extensions[extension]])
|
1516
|
+
warn 'ok.'
|
1517
|
+
end
|
1518
|
+
end
|
1519
|
+
|
1520
|
+
def clean(file_name)
|
1521
|
+
extension = File.extname(file_name)
|
1522
|
+
unless @extensions.include?(extension)
|
1523
|
+
warn 'tcjudge: cannot detect the language'
|
1524
|
+
return
|
1525
|
+
end
|
1526
|
+
|
1527
|
+
language = @languages[@extensions[extension]]
|
1528
|
+
|
1529
|
+
if language.nil?
|
1530
|
+
warn 'tcjudge: the language is not supported yet'
|
1531
|
+
return
|
1532
|
+
end
|
1533
|
+
|
1534
|
+
print language.new(nil).perform_cut(open(file_name, 'r').read)
|
1535
|
+
return
|
1536
|
+
end
|
1537
|
+
|
1538
|
+
def judge(file_name, force = false)
|
1539
|
+
code = nil
|
1540
|
+
begin
|
1541
|
+
open file_name, 'r' do |file|
|
1542
|
+
code = file.read
|
1543
|
+
end
|
1544
|
+
rescue
|
1545
|
+
warn "tcjudge: file #{ file_name } couldn't be opened or doesn't exist."
|
1546
|
+
return
|
1547
|
+
end
|
1548
|
+
|
1549
|
+
extension = File.extname(file_name)
|
1550
|
+
unless @extensions.include?(extension)
|
1551
|
+
warn 'tcjudge: cannot detect the language'
|
1552
|
+
return
|
1553
|
+
end
|
1554
|
+
|
1555
|
+
language = @languages[@extensions[extension]]
|
1556
|
+
|
1557
|
+
if language.nil?
|
1558
|
+
warn 'tcjudge: the language is not supported yet'
|
1559
|
+
return
|
1560
|
+
end
|
1561
|
+
|
1562
|
+
problem_name = File.basename(file_name, extension)
|
1563
|
+
|
1564
|
+
scraper = TopCoderScraper.new(Dir.tmpdir)
|
1565
|
+
unless scraper.is_cache_available(problem_name, [:problem_definition, :test_cases])
|
1566
|
+
if login(scraper)
|
1567
|
+
return
|
1568
|
+
end
|
1569
|
+
end
|
1570
|
+
|
1571
|
+
$stderr.print 'Obtaining problem definition...'
|
1572
|
+
problem_definition = scraper.get_problem_definition(problem_name)
|
1573
|
+
warn 'ok.'
|
1574
|
+
|
1575
|
+
$stderr.print 'Obtaining testcases...'
|
1576
|
+
test_cases = scraper.get_test_cases(problem_name)
|
1577
|
+
warn " total #{ test_cases.length } cases."
|
1578
|
+
|
1579
|
+
tester = nil
|
1580
|
+
|
1581
|
+
begin
|
1582
|
+
@compiler ||= {}
|
1583
|
+
@compiler[@extensions[extension]] ||= @default_compilers[@extensions[extension]]
|
1584
|
+
tester = language.new(problem_definition, test_cases, Dir.tmpdir, code, @compiler[@extensions[extension]])
|
1585
|
+
rescue CompileError
|
1586
|
+
warn 'tcjudge: compile error'
|
1587
|
+
return
|
1588
|
+
end
|
1589
|
+
|
1590
|
+
succeeded = true
|
1591
|
+
|
1592
|
+
test_cases.length.times do |index|
|
1593
|
+
current_succeeded = true
|
1594
|
+
|
1595
|
+
print "Test Case #{ index } ..."
|
1596
|
+
result = tester.run(index)
|
1597
|
+
case result
|
1598
|
+
when :AC
|
1599
|
+
puts 'Accepted.'
|
1600
|
+
when :WA
|
1601
|
+
puts 'WRONG ANSWER.'
|
1602
|
+
current_succeeded = false
|
1603
|
+
when :TLE
|
1604
|
+
puts 'TIME LIMIT EXCEEDED.'
|
1605
|
+
current_succeeded = false
|
1606
|
+
when :MLE
|
1607
|
+
puts 'MEMORY LIMIT EXCEEDED.'
|
1608
|
+
current_succeeded = false
|
1609
|
+
when :RUNTIME_ERROR
|
1610
|
+
puts 'RUNTIME ERROR.'
|
1611
|
+
current_succeeded = false
|
1612
|
+
else
|
1613
|
+
puts 'UNKNOWN ERROR.'
|
1614
|
+
current_succeeded = false
|
1615
|
+
end
|
1616
|
+
|
1617
|
+
unless current_succeeded
|
1618
|
+
succeeded = false
|
1619
|
+
|
1620
|
+
puts "Test Case: #{ test_cases[index][:input] }"
|
1621
|
+
puts "Expected Output: #{ test_cases[index][:output] }"
|
1622
|
+
if !force
|
1623
|
+
break
|
1624
|
+
end
|
1625
|
+
end
|
1626
|
+
end
|
1627
|
+
|
1628
|
+
if succeeded
|
1629
|
+
warn 'All Tests Succeeded.'
|
1630
|
+
else
|
1631
|
+
warn 'Testing Failed.'
|
1632
|
+
end
|
1633
|
+
|
1634
|
+
tester.print_points(code)
|
1635
|
+
|
1636
|
+
return
|
1637
|
+
end
|
1638
|
+
|
1639
|
+
# return false if succeeded
|
1640
|
+
def login(scraper)
|
1641
|
+
@user_name ||= nil
|
1642
|
+
if @user_name.nil?
|
1643
|
+
$stderr.print 'User Name: '
|
1644
|
+
@user_name = $stdin.gets().strip
|
1645
|
+
end
|
1646
|
+
|
1647
|
+
@password ||= nil
|
1648
|
+
if @password.nil?
|
1649
|
+
$stderr.print 'Password: '
|
1650
|
+
@password = $stdin.noecho(&:gets).strip
|
1651
|
+
end
|
1652
|
+
|
1653
|
+
unless scraper.login(@user_name, @password)
|
1654
|
+
warn "\ntcjudge: login succeeded"
|
1655
|
+
return false
|
1656
|
+
else
|
1657
|
+
warn "\ntcjudge: login failed"
|
1658
|
+
return true
|
1659
|
+
end
|
1660
|
+
end
|
1661
|
+
|
1662
|
+
def show_solved
|
1663
|
+
agent = Mechanize.new
|
1664
|
+
|
1665
|
+
template = {}
|
1666
|
+
@diary[:difficulties].each do |difficulty|
|
1667
|
+
template[difficulty] = false
|
1668
|
+
end
|
1669
|
+
|
1670
|
+
solved = {}
|
1671
|
+
(@diary[:from_srm]..@diary[:to_srm]).each do |i|
|
1672
|
+
solved["SRM#{i}"] = template.dup
|
1673
|
+
end
|
1674
|
+
|
1675
|
+
month = @diary[:from_month]
|
1676
|
+
while month <= @diary[:to_month]
|
1677
|
+
page = agent.get("#{@diary[:url]}archive/#{month.strftime('%Y%m')}")
|
1678
|
+
|
1679
|
+
lis = page.root.xpath('//li[@class="archive archive-section"]')
|
1680
|
+
titles = lis.map { |li| li.content }
|
1681
|
+
|
1682
|
+
titles.each do |title|
|
1683
|
+
tags = title.scan(/\[([0-9a-zA-Z]+)\]/).flatten
|
1684
|
+
srm_idx = nil
|
1685
|
+
probs = []
|
1686
|
+
tags.each do |tag|
|
1687
|
+
if tag =~ /^SRM/ then
|
1688
|
+
srm_idx = tag
|
1689
|
+
else
|
1690
|
+
probs.push tag
|
1691
|
+
end
|
1692
|
+
end
|
1693
|
+
|
1694
|
+
next unless srm_idx
|
1695
|
+
|
1696
|
+
probs.each do |prob|
|
1697
|
+
solved[srm_idx] ||= template.dup
|
1698
|
+
solved[srm_idx][prob] = true
|
1699
|
+
end
|
1700
|
+
end
|
1701
|
+
|
1702
|
+
month = month.next_month
|
1703
|
+
end
|
1704
|
+
|
1705
|
+
sorted = solved.sort do |a, b|
|
1706
|
+
if a.first.length == b.first.length then
|
1707
|
+
a.first <=> b.first
|
1708
|
+
else
|
1709
|
+
a.first.length <=> b.first.length
|
1710
|
+
end
|
1711
|
+
end
|
1712
|
+
|
1713
|
+
sorted.each do |srm|
|
1714
|
+
state = @diary[:difficulties].map { |difficulty| srm.last[difficulty] ? '*' : '-' }.join
|
1715
|
+
|
1716
|
+
puts "#{srm.first} #{state}"
|
1717
|
+
end
|
1718
|
+
|
1719
|
+
end
|
1720
|
+
end
|