at_coder_friends 0.3.1

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,34 @@
1
+ # frozen_string_literal: true
2
+
3
+ module AtCoderFriends
4
+ # run test cases for the specified program with actual input/output.
5
+ class JudgeTestRunner < TestRunner
6
+ include PathUtil
7
+
8
+ def initialize(path)
9
+ super(path)
10
+ @cases_dir = cases_dir(@dir)
11
+ @smp_dir = smp_dir(@dir)
12
+ end
13
+
14
+ def judge_all
15
+ puts "***** judge_all #{@prg} *****"
16
+ Dir["#{@cases_dir}/#{@q}/in/*.txt"].sort.each do |infile|
17
+ id = File.basename(infile, '.txt')
18
+ judge(id)
19
+ end
20
+ end
21
+
22
+ def judge_one(id)
23
+ puts "***** judge_one #{@prg} *****"
24
+ judge(id)
25
+ end
26
+
27
+ def judge(id)
28
+ infile = "#{@cases_dir}/#{@q}/in/#{id}.txt"
29
+ outfile = "#{@smp_dir}/#{@q}_#{id}.out"
30
+ expfile = "#{@cases_dir}/#{@q}/out/#{id}.txt"
31
+ run_test(id, infile, outfile, expfile)
32
+ end
33
+ end
34
+ end
@@ -0,0 +1,33 @@
1
+ # frozen_string_literal: true
2
+
3
+ module AtCoderFriends
4
+ # Common methods and behaviors for dealing with paths.
5
+ module PathUtil
6
+ module_function
7
+
8
+ SMP_DIR = 'data'
9
+ CASES_DIR = 'cases'
10
+
11
+ def contest_name(path)
12
+ path = File.expand_path(path)
13
+ dir = File.file?(path) ? File.dirname(path) : path
14
+ File.basename(dir).delete('#').downcase
15
+ end
16
+
17
+ def split_prg_path(path)
18
+ path = File.expand_path(path)
19
+ dir, prg = File.split(path)
20
+ base, ext = prg.split('.')
21
+ q = base.split('_')[0]
22
+ [path, dir, prg, base, ext, q]
23
+ end
24
+
25
+ def smp_dir(dir)
26
+ File.join(dir, SMP_DIR)
27
+ end
28
+
29
+ def cases_dir(dir)
30
+ File.join(dir, CASES_DIR)
31
+ end
32
+ end
33
+ end
@@ -0,0 +1,48 @@
1
+ # frozen_string_literal: true
2
+
3
+ module AtCoderFriends
4
+ DataSample = Struct.new(:no, :ext, :txt) do
5
+ def initialize(no, ext, txt)
6
+ no = no.tr('0-9', '0-9').to_i
7
+ txt = txt.lstrip.gsub("\r\n", "\n")
8
+ super(no, ext, txt)
9
+ end
10
+ end
11
+
12
+ InputDef = Struct.new(:container, :item, :names, :size) do
13
+ def initialize(container, item, names, size = [])
14
+ super(container, item, names, size)
15
+ end
16
+ end
17
+
18
+ SourceSample = Struct.new(:ext, :txt)
19
+
20
+ # holds problem information
21
+ class Problem
22
+ attr_reader :q, :fmt, :smps, :srcs
23
+ attr_accessor :html, :desc, :defs
24
+
25
+ def initialize(q)
26
+ @q = q
27
+ @html = ''
28
+ @desc = ''
29
+ @fmt = ''
30
+ @smps = []
31
+ @defs = []
32
+ @srcs = []
33
+ yield self if block_given?
34
+ end
35
+
36
+ def fmt=(f)
37
+ @fmt = f.lstrip.gsub("\r\n", "\n")
38
+ end
39
+
40
+ def add_smp(no, ext, txt)
41
+ @smps << DataSample.new(no, ext, txt)
42
+ end
43
+
44
+ def add_src(ext, txt)
45
+ @srcs << SourceSample.new(ext, txt)
46
+ end
47
+ end
48
+ end
@@ -0,0 +1,97 @@
1
+ # frozen_string_literal: true
2
+
3
+ module AtCoderFriends
4
+ # generates C++ source code from definition
5
+ class RubyGenerator
6
+ TEMPLATE = <<~TEXT
7
+ ### DCLS ###
8
+
9
+ puts ans
10
+ TEXT
11
+
12
+ def process(pbm)
13
+ src = generate(pbm.defs)
14
+ pbm.add_src(:rb, src)
15
+ end
16
+
17
+ def generate(defs)
18
+ dcls = gen_decls(defs).join("\n")
19
+ TEMPLATE.sub('### DCLS ###', dcls)
20
+ end
21
+
22
+ def gen_decls(defs)
23
+ defs.map { |inpdef| gen_decl(inpdef) }.flatten
24
+ end
25
+
26
+ def gen_decl(inpdef)
27
+ case inpdef.container
28
+ when :single
29
+ gen_single_decl(inpdef)
30
+ when :harray
31
+ gen_harray_decl(inpdef)
32
+ when :varray
33
+ if inpdef.names.size == 1
34
+ gen_varray_1_decl(inpdef)
35
+ else
36
+ gen_varray_n_decl(inpdef)
37
+ end
38
+ when :matrix
39
+ gen_matrix_decl(inpdef)
40
+ end
41
+ end
42
+
43
+ def gen_single_decl(inpdef)
44
+ names = inpdef.names
45
+ dcl = names.join(', ')
46
+ expr = gen_expr(inpdef.item, names.size > 1)
47
+ "#{dcl} = #{expr}"
48
+ end
49
+
50
+ def gen_harray_decl(inpdef)
51
+ v = inpdef.names[0]
52
+ dcl = "#{v}s"
53
+ expr = gen_expr(inpdef.item, true)
54
+ "#{dcl} = #{expr}"
55
+ end
56
+
57
+ def gen_varray_1_decl(inpdef)
58
+ v = inpdef.names[0]
59
+ sz = inpdef.size[0]
60
+ dcl = "#{v}s"
61
+ expr = gen_expr(inpdef.item, false)
62
+ "#{dcl} = Array.new(#{sz}) { #{expr} }"
63
+ end
64
+
65
+ def gen_varray_n_decl(inpdef)
66
+ names = inpdef.names
67
+ sz = inpdef.size[0]
68
+ dcl = names.map { |v| "#{v}s[i]" }.join(', ')
69
+ expr = gen_expr(inpdef.item, true)
70
+ ret = []
71
+ ret += names.map { |v| "#{v}s = Array.new(#{sz})" }
72
+ ret << "#{sz}.times do |i|"
73
+ ret << " #{dcl} = #{expr}"
74
+ ret << 'end'
75
+ ret
76
+ end
77
+
78
+ def gen_matrix_decl(inpdef)
79
+ v = inpdef.names[0]
80
+ sz = inpdef.size[0]
81
+ decl = "#{v}ss"
82
+ expr = gen_expr(inpdef.item, true)
83
+ "#{decl} = Array.new(#{sz}) { #{expr} }"
84
+ end
85
+
86
+ def gen_expr(item, split)
87
+ case item
88
+ when :number
89
+ split ? 'gets.split.map(&:to_i)' : 'gets.to_i'
90
+ when :string
91
+ split ? 'gets.chomp.split' : 'gets.chomp'
92
+ when :char
93
+ 'gets.chomp'
94
+ end
95
+ end
96
+ end
97
+ end
@@ -0,0 +1,31 @@
1
+ # frozen_string_literal: true
2
+
3
+ module AtCoderFriends
4
+ # run test cases for the specified program with sample input/output.
5
+ class SampleTestRunner < TestRunner
6
+ include PathUtil
7
+
8
+ def initialize(path)
9
+ super(path)
10
+ @smp_dir = smp_dir(@dir)
11
+ end
12
+
13
+ def test_all
14
+ puts "***** test_all #{@prg} *****"
15
+ 1.upto(999) do |i|
16
+ break unless test(i)
17
+ end
18
+ end
19
+
20
+ def test_one(n)
21
+ puts "***** test_one #{@prg} *****"
22
+ test(n)
23
+ end
24
+
25
+ def test(n)
26
+ id = format('%<q>s_%<n>03d', q: @q, n: n)
27
+ files = %w[in out exp].map { |ext| "#{@smp_dir}/#{id}.#{ext}" }
28
+ run_test(id, *files)
29
+ end
30
+ end
31
+ end
@@ -0,0 +1,135 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'uri'
4
+ require 'mechanize'
5
+ require 'logger'
6
+ require 'English'
7
+
8
+ module AtCoderFriends
9
+ # scrapes AtCoder contest site and
10
+ # - fetches problems
11
+ # - submits sources
12
+ class ScrapingAgent
13
+ include PathUtil
14
+
15
+ BASE_URL = 'https://atcoder.jp/'
16
+ XPATH_SECTION = '//h3[.="%<title>s"]/following-sibling::section'
17
+ LANG_TBL = {
18
+ 'cxx' => '3003',
19
+ 'cs' => '3006',
20
+ 'java' => '3016',
21
+ 'rb' => '3024'
22
+ }.freeze
23
+
24
+ attr_reader :contest, :config, :agent
25
+
26
+ def initialize(contest, config)
27
+ @contest = contest
28
+ @config = config
29
+ @agent = Mechanize.new
30
+ # @agent.log = Logger.new(STDERR)
31
+ end
32
+
33
+ def common_url(path)
34
+ File.join(BASE_URL, path)
35
+ end
36
+
37
+ def contest_url(path)
38
+ File.join(BASE_URL, 'contests', contest, path)
39
+ end
40
+
41
+ def fetch_all
42
+ puts "***** fetch_all #{@contest} *****"
43
+ login
44
+ fetch_assignments.map do |q, url|
45
+ pbm = fetch_problem(q, url)
46
+ yield pbm if block_given?
47
+ pbm
48
+ end
49
+ end
50
+
51
+ def submit(path)
52
+ path, _dir, prg, _base, ext, q = split_prg_path(path)
53
+ puts "***** submit #{prg} *****"
54
+ src = File.read(path, encoding: Encoding::UTF_8)
55
+ login
56
+ post_src(q, ext, src)
57
+ end
58
+
59
+ def login
60
+ sleep 0.1
61
+ page = agent.get(common_url('login'))
62
+ form = page.forms[1]
63
+ form.field_with(name: 'username').value = config['user']
64
+ form.field_with(name: 'password').value = config['password']
65
+ sleep 0.1
66
+ form.submit
67
+ end
68
+
69
+ def fetch_assignments
70
+ url = contest_url('tasks')
71
+ puts "fetch list from #{url} ..."
72
+ sleep 0.1
73
+ page = agent.get(url)
74
+ ('A'..'Z').each_with_object({}) do |q, h|
75
+ link = page.link_with(text: q)
76
+ link && h[q] = link.href
77
+ end
78
+ end
79
+
80
+ def fetch_problem(q, url)
81
+ puts "fetch problem from #{url} ..."
82
+ sleep 0.1
83
+ page = agent.get(url)
84
+ Problem.new(q) do |pbm|
85
+ pbm.html = page.body
86
+ if contest == 'arc001'
87
+ page.search('//h3').each do |h3|
88
+ query = format(XPATH_SECTION, title: h3.content)
89
+ sections = page.search(query)
90
+ sections[0] && parse_section(pbm, h3, sections[0])
91
+ end
92
+ else
93
+ page.search('//*[./h3]').each do |section|
94
+ h3 = section.search('h3')[0]
95
+ parse_section(pbm, h3, section)
96
+ end
97
+ end
98
+ end
99
+ end
100
+
101
+ def parse_section(pbm, h3, section)
102
+ title = h3.content.strip
103
+ title.delete!("\u008f\u0090") # agc002
104
+ text = section.content
105
+ code = section.search('pre')[0]&.content || ''
106
+ case title
107
+ when /^制約$/
108
+ pbm.desc += text
109
+ when /^入出?力$/
110
+ pbm.desc += text
111
+ pbm.fmt = code
112
+ when /^入力例\s*(?<no>[\d0-9]+)$/
113
+ pbm.add_smp($LAST_MATCH_INFO[:no], :in, code)
114
+ when /^出力例\s*(?<no>[\d0-9]+)$/
115
+ pbm.add_smp($LAST_MATCH_INFO[:no], :exp, code)
116
+ end
117
+ end
118
+
119
+ def post_src(q, ext, src)
120
+ lang_id = LANG_TBL[ext.downcase]
121
+ raise AppError, ".#{ext} is not available." unless lang_id
122
+ sleep 0.1
123
+ page = agent.get(contest_url('submit'))
124
+ form = page.forms[1]
125
+ form.field_with(name: 'data.TaskScreenName') do |sel|
126
+ option = sel.options.find { |op| op.text.start_with?(q) }
127
+ option&.select || (raise AppError, "unknown problem:#{q}.")
128
+ end
129
+ form.add_field!('data.LanguageId', lang_id)
130
+ form.field_with(name: 'sourceCode').value = src
131
+ sleep 0.1
132
+ form.submit
133
+ end
134
+ end
135
+ end
@@ -0,0 +1,82 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'rbconfig'
4
+
5
+ module AtCoderFriends
6
+ # run tests for the specified program.
7
+ class TestRunner
8
+ include PathUtil
9
+
10
+ def initialize(path)
11
+ @path, @dir, @prg, @base, @ext, @q = split_prg_path(path)
12
+ end
13
+
14
+ # rubocop:disable Metrics/MethodLength
15
+ def run_test(id, infile, outfile, expfile)
16
+ return false unless File.exist?(infile) && File.exist?(expfile)
17
+
18
+ puts "==== #{id} ===="
19
+ ec = system("#{edit_cmd} < #{infile} > #{outfile}")
20
+
21
+ input, result, expected =
22
+ [infile, outfile, expfile].map { |file| File.read(file) }
23
+ puts '-- input --'
24
+ print input
25
+ puts '-- expected --'
26
+ print expected
27
+ puts '-- result --'
28
+ print result
29
+ if !ec
30
+ puts '!!!!! RE !!!!!'
31
+ elsif result != expected
32
+ puts '!!!!! WA !!!!!'
33
+ else
34
+ puts '<< OK >>'
35
+ end
36
+ true
37
+ end
38
+ # rubocop:enable Metrics/MethodLength
39
+
40
+ # rubocop:disable Metrics/MethodLength
41
+ def edit_cmd
42
+ case @ext
43
+ when 'java'
44
+ "java -cp #{@dir} Main"
45
+ when 'rb'
46
+ "ruby #{@dir}/#{@base}.rb"
47
+ when 'cs'
48
+ case which_os
49
+ when :windows
50
+ "#{@dir}/#{@base}.exe"
51
+ else
52
+ "mono #{@dir}/#{@base}.exe"
53
+ end
54
+ else # c, cxx
55
+ case which_os
56
+ when :windows
57
+ "#{@dir}/#{@base}.exe"
58
+ else
59
+ "#{@dir}/#{@base}"
60
+ end
61
+ end
62
+ end
63
+ # rubocop:enable Metrics/MethodLength
64
+
65
+ def which_os
66
+ @os ||= begin
67
+ case RbConfig::CONFIG['host_os']
68
+ when /mswin|msys|mingw|cygwin|bccwin|wince|emc/
69
+ :windows
70
+ when /darwin|mac os/
71
+ :macosx
72
+ when /linux/
73
+ :linux
74
+ when /solaris|bsd/
75
+ :unix
76
+ else
77
+ :unknown
78
+ end
79
+ end
80
+ end
81
+ end
82
+ end