at_coder_friends 0.5.2 → 0.6.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 +4 -4
- data/.rubocop.yml +1 -0
- data/.rubocop_todo.yml +1 -4
- data/Gemfile.lock +1 -1
- data/README.md +1 -1
- data/config/default.yml +3 -0
- data/docs/CONFIGURATION.md +74 -9
- data/lib/at_coder_friends.rb +10 -5
- data/lib/at_coder_friends/cli.rb +2 -5
- data/lib/at_coder_friends/context.rb +4 -0
- data/lib/at_coder_friends/emitter.rb +2 -2
- data/lib/at_coder_friends/generator/cxx_builtin.rb +191 -0
- data/lib/at_coder_friends/generator/main.rb +53 -0
- data/lib/at_coder_friends/generator/ruby_builtin.rb +128 -0
- data/lib/at_coder_friends/parser/binary.rb +39 -0
- data/lib/at_coder_friends/parser/constraints.rb +36 -0
- data/lib/at_coder_friends/parser/{format_parser.rb → input_format.rb} +42 -30
- data/lib/at_coder_friends/parser/interactive.rb +29 -0
- data/lib/at_coder_friends/parser/main.rb +6 -3
- data/lib/at_coder_friends/parser/sample_data.rb +24 -0
- data/lib/at_coder_friends/parser/section_wrapper.rb +49 -0
- data/lib/at_coder_friends/parser/{page_parser.rb → sections.rb} +44 -50
- data/lib/at_coder_friends/problem.rb +40 -24
- data/lib/at_coder_friends/scraping/agent.rb +1 -5
- data/lib/at_coder_friends/scraping/authentication.rb +2 -2
- data/lib/at_coder_friends/scraping/session.rb +1 -1
- data/lib/at_coder_friends/scraping/tasks.rb +2 -6
- data/lib/at_coder_friends/test_runner/base.rb +36 -31
- data/lib/at_coder_friends/test_runner/judge.rb +2 -6
- data/lib/at_coder_friends/test_runner/sample.rb +8 -6
- data/lib/at_coder_friends/version.rb +1 -1
- data/tasks/regression/check_diff.rake +29 -0
- data/tasks/regression/check_parse.rake +56 -0
- data/tasks/regression/regression.rb +67 -0
- data/tasks/regression/section_list.rake +41 -0
- data/tasks/regression/setup.rake +48 -0
- data/templates/cxx_builtin_default.cxx +26 -0
- data/templates/cxx_builtin_interactive.cxx +61 -0
- data/templates/ruby_builtin_default.rb +5 -0
- data/templates/ruby_builtin_interactive.rb +32 -0
- metadata +21 -8
- data/lib/at_coder_friends/cxx_generator.rb +0 -169
- data/lib/at_coder_friends/parser/constraints_parser.rb +0 -26
- data/lib/at_coder_friends/ruby_generator.rb +0 -97
- data/tasks/regression.rake +0 -163
@@ -0,0 +1,128 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module AtCoderFriends
|
4
|
+
module Generator
|
5
|
+
# generates C++ source code from problem description
|
6
|
+
class RubyBuiltin
|
7
|
+
ACF_HOME = File.realpath(File.join(__dir__, '..', '..', '..'))
|
8
|
+
TMPL_DIR = File.join(ACF_HOME, 'templates')
|
9
|
+
DEFAULT_TMPL = File.join(TMPL_DIR, 'ruby_builtin_default.rb')
|
10
|
+
INTERACTIVE_TMPL = File.join(TMPL_DIR, 'ruby_builtin_interactive.rb')
|
11
|
+
|
12
|
+
attr_reader :cfg, :pbm
|
13
|
+
|
14
|
+
def initialize(cfg = nil)
|
15
|
+
@cfg = cfg || {}
|
16
|
+
end
|
17
|
+
|
18
|
+
def process(pbm)
|
19
|
+
src = generate(pbm)
|
20
|
+
pbm.add_src(:rb, src)
|
21
|
+
end
|
22
|
+
|
23
|
+
def generate(pbm)
|
24
|
+
@pbm = pbm
|
25
|
+
File
|
26
|
+
.read(select_template)
|
27
|
+
.gsub('### URL ###', pbm.url)
|
28
|
+
.gsub('### DCLS ###', gen_decls.join("\n"))
|
29
|
+
.gsub('### OUTPUT ###', gen_output)
|
30
|
+
end
|
31
|
+
|
32
|
+
def select_template(interactive = pbm.options.interactive)
|
33
|
+
interactive ? interactive_template : default_template
|
34
|
+
end
|
35
|
+
|
36
|
+
def default_template
|
37
|
+
cfg['default_template'] || DEFAULT_TMPL
|
38
|
+
end
|
39
|
+
|
40
|
+
def interactive_template
|
41
|
+
cfg['interactive_template'] || INTERACTIVE_TMPL
|
42
|
+
end
|
43
|
+
|
44
|
+
def gen_decls(inpdefs = pbm.formats)
|
45
|
+
inpdefs.map { |inpdef| gen_decl(inpdef) }.flatten
|
46
|
+
end
|
47
|
+
|
48
|
+
def gen_decl(inpdef)
|
49
|
+
case inpdef.container
|
50
|
+
when :single
|
51
|
+
gen_single_decl(inpdef)
|
52
|
+
when :harray
|
53
|
+
gen_harray_decl(inpdef)
|
54
|
+
when :varray
|
55
|
+
if inpdef.names.size == 1
|
56
|
+
gen_varray_1_decl(inpdef)
|
57
|
+
else
|
58
|
+
gen_varray_n_decl(inpdef)
|
59
|
+
end
|
60
|
+
when :matrix
|
61
|
+
gen_matrix_decl(inpdef)
|
62
|
+
end
|
63
|
+
end
|
64
|
+
|
65
|
+
def gen_single_decl(inpdef)
|
66
|
+
names = inpdef.names
|
67
|
+
dcl = names.join(', ')
|
68
|
+
expr = gen_expr(inpdef.item, names.size > 1)
|
69
|
+
"#{dcl} = #{expr}"
|
70
|
+
end
|
71
|
+
|
72
|
+
def gen_harray_decl(inpdef)
|
73
|
+
v = inpdef.names[0]
|
74
|
+
dcl = "#{v}s"
|
75
|
+
expr = gen_expr(inpdef.item, true)
|
76
|
+
"#{dcl} = #{expr}"
|
77
|
+
end
|
78
|
+
|
79
|
+
def gen_varray_1_decl(inpdef)
|
80
|
+
v = inpdef.names[0]
|
81
|
+
sz = inpdef.size[0]
|
82
|
+
dcl = "#{v}s"
|
83
|
+
expr = gen_expr(inpdef.item, false)
|
84
|
+
"#{dcl} = Array.new(#{sz}) { #{expr} }"
|
85
|
+
end
|
86
|
+
|
87
|
+
def gen_varray_n_decl(inpdef)
|
88
|
+
names = inpdef.names
|
89
|
+
sz = inpdef.size[0]
|
90
|
+
dcl = names.map { |v| "#{v}s[i]" }.join(', ')
|
91
|
+
expr = gen_expr(inpdef.item, true)
|
92
|
+
ret = []
|
93
|
+
ret += names.map { |v| "#{v}s = Array.new(#{sz})" }
|
94
|
+
ret << "#{sz}.times do |i|"
|
95
|
+
ret << " #{dcl} = #{expr}"
|
96
|
+
ret << 'end'
|
97
|
+
ret
|
98
|
+
end
|
99
|
+
|
100
|
+
def gen_matrix_decl(inpdef)
|
101
|
+
v = inpdef.names[0]
|
102
|
+
sz = inpdef.size[0]
|
103
|
+
decl = "#{v}ss"
|
104
|
+
expr = gen_expr(inpdef.item, true)
|
105
|
+
"#{decl} = Array.new(#{sz}) { #{expr} }"
|
106
|
+
end
|
107
|
+
|
108
|
+
def gen_expr(item, split)
|
109
|
+
case item
|
110
|
+
when :number
|
111
|
+
split ? 'gets.split.map(&:to_i)' : 'gets.to_i'
|
112
|
+
when :string
|
113
|
+
split ? 'gets.chomp.split' : 'gets.chomp'
|
114
|
+
when :char
|
115
|
+
'gets.chomp'
|
116
|
+
end
|
117
|
+
end
|
118
|
+
|
119
|
+
def gen_output(vs = pbm.options.binary_values)
|
120
|
+
if vs
|
121
|
+
"puts cond ? '#{vs[0]}' : '#{vs[1]}'"
|
122
|
+
else
|
123
|
+
'puts ans'
|
124
|
+
end
|
125
|
+
end
|
126
|
+
end
|
127
|
+
end
|
128
|
+
end
|
@@ -0,0 +1,39 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module AtCoderFriends
|
4
|
+
module Parser
|
5
|
+
# detect binary problem
|
6
|
+
module Binary
|
7
|
+
module_function
|
8
|
+
|
9
|
+
def process(pbm)
|
10
|
+
vs = exp_values(pbm)
|
11
|
+
return unless vs.size == 2
|
12
|
+
return if vs.any? { |v| v.include?("\n") }
|
13
|
+
return if vs.any? { |v| v =~ /^[0-9\s]*$/ }
|
14
|
+
|
15
|
+
out_fmt = ouput_format(pbm)
|
16
|
+
re1, re2 = vs.map { |v| Regexp.escape(v) }
|
17
|
+
|
18
|
+
pbm.options.binary_values =
|
19
|
+
if out_fmt =~ /#{re1}.+#{re2}/m
|
20
|
+
vs
|
21
|
+
elsif out_fmt =~ /#{re2}.+#{re1}/m
|
22
|
+
vs.reverse
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
26
|
+
def exp_values(pbm)
|
27
|
+
pbm
|
28
|
+
.samples
|
29
|
+
.select { |smp| smp.ext == :exp }
|
30
|
+
.map { |smp| smp.txt.chomp }
|
31
|
+
.uniq
|
32
|
+
end
|
33
|
+
|
34
|
+
def ouput_format(pbm)
|
35
|
+
pbm.sections[Problem::SECTION_OUT_FMT]&.content || ''
|
36
|
+
end
|
37
|
+
end
|
38
|
+
end
|
39
|
+
end
|
@@ -0,0 +1,36 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module AtCoderFriends
|
4
|
+
module Parser
|
5
|
+
# parses constraints
|
6
|
+
module Constraints
|
7
|
+
module_function
|
8
|
+
|
9
|
+
SECTIONS = [
|
10
|
+
Problem::SECTION_IN_FMT,
|
11
|
+
Problem::SECTION_IO_FMT,
|
12
|
+
Problem::SECTION_CONSTRAINTS
|
13
|
+
].freeze
|
14
|
+
|
15
|
+
def process(pbm)
|
16
|
+
str = SECTIONS.reduce('') do |m, key|
|
17
|
+
m + (pbm.sections[key]&.content || '')
|
18
|
+
end
|
19
|
+
constraints = parse(str)
|
20
|
+
pbm.constraints = constraints
|
21
|
+
end
|
22
|
+
|
23
|
+
def parse(str)
|
24
|
+
str
|
25
|
+
.gsub(/[,\\(){}|]/, '')
|
26
|
+
.gsub(/(≤|leq?)/i, '≦')
|
27
|
+
.scan(/([\da-z_]+)\s*≦\s*(\d+)(?:\^(\d+))?/i)
|
28
|
+
.map do |v, sz, k|
|
29
|
+
sz = sz.to_i
|
30
|
+
sz **= k.to_i if k
|
31
|
+
Problem::Constraint.new(v, :max, sz)
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|
35
|
+
end
|
36
|
+
end
|
@@ -2,28 +2,11 @@
|
|
2
2
|
|
3
3
|
module AtCoderFriends
|
4
4
|
module Parser
|
5
|
-
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
class Iterator
|
11
|
-
def initialize(array)
|
12
|
-
@array = array
|
13
|
-
@i = 0
|
14
|
-
end
|
15
|
-
|
16
|
-
def next?
|
17
|
-
@i < @array.size
|
18
|
-
end
|
19
|
-
|
20
|
-
def next
|
21
|
-
ret = @array[@i]
|
22
|
-
@i += 1
|
23
|
-
ret
|
24
|
-
end
|
25
|
-
end
|
26
|
-
|
5
|
+
module InputFormatConstants
|
6
|
+
SECTIONS = [
|
7
|
+
Problem::SECTION_IN_FMT,
|
8
|
+
Problem::SECTION_IO_FMT
|
9
|
+
].freeze
|
27
10
|
PARSERS = [
|
28
11
|
{
|
29
12
|
container: :harray,
|
@@ -77,18 +60,47 @@ module AtCoderFriends
|
|
77
60
|
size: ->(_) { [] }
|
78
61
|
}
|
79
62
|
].freeze
|
63
|
+
end
|
64
|
+
|
65
|
+
# parses input data format and generates input definitons
|
66
|
+
module InputFormat
|
67
|
+
include InputFormatConstants
|
68
|
+
|
69
|
+
module_function
|
70
|
+
|
71
|
+
# Iterates through elements of an array
|
72
|
+
class Iterator
|
73
|
+
def initialize(array)
|
74
|
+
@array = array
|
75
|
+
@i = 0
|
76
|
+
end
|
77
|
+
|
78
|
+
def next?
|
79
|
+
@i < @array.size
|
80
|
+
end
|
81
|
+
|
82
|
+
def next
|
83
|
+
ret = @array[@i]
|
84
|
+
@i += 1
|
85
|
+
ret
|
86
|
+
end
|
87
|
+
end
|
80
88
|
|
81
89
|
def process(pbm)
|
82
|
-
|
83
|
-
|
90
|
+
str =
|
91
|
+
SECTIONS
|
92
|
+
.map { |key| pbm.sections[key]&.code_block }
|
93
|
+
.find(&:itself) || ''
|
94
|
+
inpdefs = parse(str, pbm.samples)
|
95
|
+
pbm.formats = inpdefs
|
84
96
|
end
|
85
97
|
|
86
|
-
def parse(
|
87
|
-
lines = normalize(
|
88
|
-
|
98
|
+
def parse(str, smps)
|
99
|
+
lines = normalize(str)
|
100
|
+
inpdefs = parse_fmt(lines)
|
89
101
|
smpx = max_smp(smps)
|
90
|
-
smpx && match_smp!(
|
91
|
-
|
102
|
+
smpx && match_smp!(inpdefs, smpx)
|
103
|
+
inpdefs
|
92
104
|
end
|
93
105
|
|
94
106
|
def normalize(fmt)
|
@@ -125,7 +137,7 @@ module AtCoderFriends
|
|
125
137
|
break unless pat2 && pat2 =~ cur
|
126
138
|
end
|
127
139
|
size = parser[:size].call(prv)
|
128
|
-
y <<
|
140
|
+
y << Problem::InputFormat.new(container, item, names, size)
|
129
141
|
end
|
130
142
|
end.to_a
|
131
143
|
end
|
@@ -0,0 +1,29 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module AtCoderFriends
|
4
|
+
module Parser
|
5
|
+
# detect interactive problem
|
6
|
+
module Interactive
|
7
|
+
module_function
|
8
|
+
|
9
|
+
INTERACTIVE_PAT = '(インタラクティブ|interactive|リアクティブ|reactive)'
|
10
|
+
FLUSH_PAT = 'flush'
|
11
|
+
|
12
|
+
def process(pbm)
|
13
|
+
pbm.options.interactive = false
|
14
|
+
|
15
|
+
body = pbm.page_body
|
16
|
+
f_int = body =~ /#{INTERACTIVE_PAT}/i
|
17
|
+
f_flush = body =~ /#{FLUSH_PAT}/i
|
18
|
+
f_io = pbm.sections[Problem::SECTION_IO_FMT]
|
19
|
+
f_tbl =
|
20
|
+
pbm
|
21
|
+
.sections[Problem::SECTION_IO_SMP]
|
22
|
+
&.find_element(%w[table])
|
23
|
+
return unless [f_int, f_flush, f_io, f_tbl].count(&:itself) > 1
|
24
|
+
|
25
|
+
pbm.options.interactive = true
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
@@ -7,9 +7,12 @@ module AtCoderFriends
|
|
7
7
|
module_function
|
8
8
|
|
9
9
|
def process(pbm)
|
10
|
-
|
11
|
-
|
12
|
-
|
10
|
+
Sections.process(pbm)
|
11
|
+
SampleData.process(pbm)
|
12
|
+
InputFormat.process(pbm)
|
13
|
+
Constraints.process(pbm)
|
14
|
+
Interactive.process(pbm)
|
15
|
+
Binary.process(pbm)
|
13
16
|
end
|
14
17
|
end
|
15
18
|
end
|
@@ -0,0 +1,24 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'English'
|
4
|
+
|
5
|
+
module AtCoderFriends
|
6
|
+
module Parser
|
7
|
+
# parses sample data and sets to problem
|
8
|
+
module SampleData
|
9
|
+
module_function
|
10
|
+
|
11
|
+
def process(pbm)
|
12
|
+
pbm.sections.each do |key, section|
|
13
|
+
ext =
|
14
|
+
if key =~ Problem::SECTION_IN_SMP_PAT
|
15
|
+
:in
|
16
|
+
elsif key =~ Problem::SECTION_OUT_SMP_PAT
|
17
|
+
:exp
|
18
|
+
end
|
19
|
+
ext && pbm.add_smp($LAST_MATCH_INFO[:no], ext, section.code_block)
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
@@ -0,0 +1,49 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module AtCoderFriends
|
4
|
+
module Parser
|
5
|
+
# holds section in problrem page
|
6
|
+
class SectionWrapper
|
7
|
+
attr_reader :h
|
8
|
+
|
9
|
+
def initialize(h)
|
10
|
+
@h = h
|
11
|
+
end
|
12
|
+
|
13
|
+
def siblings
|
14
|
+
@siblings ||= begin
|
15
|
+
ret = []
|
16
|
+
nx = h.next
|
17
|
+
while nx && nx.name != h.name
|
18
|
+
ret << nx
|
19
|
+
nx = nx.next
|
20
|
+
end
|
21
|
+
ret
|
22
|
+
end
|
23
|
+
end
|
24
|
+
|
25
|
+
def content
|
26
|
+
@content ||= begin
|
27
|
+
siblings.reduce('') { |m, node| m + node.content.gsub("\r\n", "\n") }
|
28
|
+
end
|
29
|
+
end
|
30
|
+
|
31
|
+
def find_element(tags)
|
32
|
+
siblings.each do |node|
|
33
|
+
tags.each do |tag|
|
34
|
+
elem = node.name == tag ? node : node.search(tag)[0]
|
35
|
+
return elem if elem
|
36
|
+
end
|
37
|
+
end
|
38
|
+
nil
|
39
|
+
end
|
40
|
+
|
41
|
+
def code_block
|
42
|
+
@code_block ||= begin
|
43
|
+
elem = find_element(%w[pre blockquote])
|
44
|
+
(elem&.content || '').lstrip.gsub("\r\n", "\n")
|
45
|
+
end
|
46
|
+
end
|
47
|
+
end
|
48
|
+
end
|
49
|
+
end
|
@@ -2,27 +2,39 @@
|
|
2
2
|
|
3
3
|
module AtCoderFriends
|
4
4
|
module Parser
|
5
|
-
|
6
|
-
|
7
|
-
module_function
|
8
|
-
|
9
|
-
SECTION_TYPES = [
|
5
|
+
module SectionsConstants
|
6
|
+
SECTION_DEFS = [
|
10
7
|
{
|
11
|
-
key:
|
8
|
+
key: Problem::SECTION_CONSTRAINTS,
|
12
9
|
patterns: [
|
13
10
|
'^制約$',
|
11
|
+
'^入力制限$',
|
14
12
|
'^Constraints$'
|
15
13
|
]
|
16
14
|
},
|
17
15
|
{
|
18
|
-
key:
|
16
|
+
key: Problem::SECTION_IN_FMT,
|
19
17
|
patterns: [
|
20
|
-
'
|
21
|
-
'^Inputs?\s*(
|
18
|
+
'^入力(形式)?$',
|
19
|
+
'^Inputs?\s*(Format)?$'
|
22
20
|
]
|
23
21
|
},
|
24
22
|
{
|
25
|
-
key:
|
23
|
+
key: Problem::SECTION_OUT_FMT,
|
24
|
+
patterns: [
|
25
|
+
'^出力(形式)?$',
|
26
|
+
'^Outputs?\s*(Format)?$'
|
27
|
+
]
|
28
|
+
},
|
29
|
+
{
|
30
|
+
key: Problem::SECTION_IO_FMT,
|
31
|
+
patterns: [
|
32
|
+
'^入出力(形式)?$',
|
33
|
+
'^Input\s*(and)?\s*Output\s*(Format)?$'
|
34
|
+
]
|
35
|
+
},
|
36
|
+
{
|
37
|
+
key: Problem::SECTION_IN_SMP,
|
26
38
|
patterns: [
|
27
39
|
'^入力例\s*(?<no>\d+)?$',
|
28
40
|
'^入力\s*(?<no>\d+)$',
|
@@ -32,7 +44,7 @@ module AtCoderFriends
|
|
32
44
|
]
|
33
45
|
},
|
34
46
|
{
|
35
|
-
key:
|
47
|
+
key: Problem::SECTION_OUT_SMP,
|
36
48
|
patterns: [
|
37
49
|
'^出力例\s*(?<no>\d+)?$',
|
38
50
|
'^出力\s*(?<no>\d+)$',
|
@@ -42,33 +54,46 @@ module AtCoderFriends
|
|
42
54
|
'^Output\s*(?<no>\d+)$',
|
43
55
|
'^Output\s*for\s*(the)?\s*Sample\s*Input\s*(?<no>\d+)?$'
|
44
56
|
]
|
57
|
+
},
|
58
|
+
{
|
59
|
+
key: Problem::SECTION_IO_SMP,
|
60
|
+
patterns: [
|
61
|
+
'^入出力の?例\s*(\d+)?$',
|
62
|
+
'^サンプル\s*(\d+)?$',
|
63
|
+
'^Sample\s*Input\s*(and)?\s*Output\s*(\d+)?$',
|
64
|
+
'^Samples?\s*(\d+)?$'
|
65
|
+
]
|
45
66
|
}
|
46
67
|
].freeze
|
68
|
+
end
|
69
|
+
|
70
|
+
# parses problem page and builds section table
|
71
|
+
module Sections
|
72
|
+
include SectionsConstants
|
73
|
+
|
74
|
+
module_function
|
47
75
|
|
48
76
|
def process(pbm)
|
49
77
|
sections = collect_sections(pbm.page)
|
50
|
-
|
78
|
+
pbm.sections = sections
|
51
79
|
end
|
52
80
|
|
53
81
|
def collect_sections(page)
|
54
|
-
|
55
|
-
%w[h2 h3].each do |tag|
|
82
|
+
%w[h2 h3].each_with_object({}) do |tag, sections|
|
56
83
|
page
|
57
84
|
.search(tag)
|
58
85
|
.each do |h|
|
59
86
|
key = find_key(h)
|
60
|
-
key && sections[key] ||=
|
87
|
+
key && sections[key] ||= SectionWrapper.new(h)
|
61
88
|
end
|
62
89
|
end
|
63
|
-
sections
|
64
90
|
end
|
65
91
|
|
66
92
|
def find_key(h)
|
67
93
|
title = normalize(h.content)
|
68
|
-
|
94
|
+
SECTION_DEFS.each do |grp|
|
69
95
|
grp[:patterns].each do |pat|
|
70
|
-
m = title.match(/#{pat}/i)
|
71
|
-
next unless m
|
96
|
+
next unless (m = title.match(/#{pat}/i))
|
72
97
|
|
73
98
|
no = m.names.include?('no') && m['no'] || '1'
|
74
99
|
return format(grp[:key], no: no)
|
@@ -77,37 +102,6 @@ module AtCoderFriends
|
|
77
102
|
nil
|
78
103
|
end
|
79
104
|
|
80
|
-
def parse_section(h)
|
81
|
-
text = ''
|
82
|
-
pre = nil
|
83
|
-
nx = h.next
|
84
|
-
while nx && nx.name != h.name
|
85
|
-
text += nx.content.gsub("\r\n", "\n")
|
86
|
-
%w[pre blockquote].each do |tag|
|
87
|
-
pre ||= (nx.name == tag ? nx : nx.search(tag)[0])
|
88
|
-
end
|
89
|
-
nx = nx.next
|
90
|
-
end
|
91
|
-
code = (pre&.text || '').lstrip.gsub("\r\n", "\n")
|
92
|
-
[text, code]
|
93
|
-
end
|
94
|
-
|
95
|
-
def apply_sections(pbm, sections)
|
96
|
-
sections.each do |key, (text, code)|
|
97
|
-
case key
|
98
|
-
when 'constraints'
|
99
|
-
pbm.desc += text
|
100
|
-
when 'input format'
|
101
|
-
pbm.desc += text
|
102
|
-
pbm.fmt = code
|
103
|
-
when /^sample input (?<no>\d+)$/
|
104
|
-
pbm.add_smp($LAST_MATCH_INFO[:no], :in, code)
|
105
|
-
when /^sample output (?<no>\d+)$/
|
106
|
-
pbm.add_smp($LAST_MATCH_INFO[:no], :exp, code)
|
107
|
-
end
|
108
|
-
end
|
109
|
-
end
|
110
|
-
|
111
105
|
def normalize(s)
|
112
106
|
s
|
113
107
|
.tr(' 0-9A-Za-z', ' 0-9A-Za-z')
|