at_coder_friends 0.3.1

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.
@@ -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