at_coder_friends 0.6.0 → 0.6.1
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/Gemfile.lock +1 -1
- data/lib/at_coder_friends.rb +2 -0
- data/lib/at_coder_friends/generator/cxx_builtin.rb +18 -4
- data/lib/at_coder_friends/generator/ruby_builtin.rb +12 -0
- data/lib/at_coder_friends/parser/binary.rb +3 -3
- data/lib/at_coder_friends/parser/constraints.rb +72 -12
- data/lib/at_coder_friends/parser/interactive.rb +10 -5
- data/lib/at_coder_friends/parser/introduction_wrapper.rb +39 -0
- data/lib/at_coder_friends/parser/main.rb +1 -0
- data/lib/at_coder_friends/parser/modulo.rb +76 -0
- data/lib/at_coder_friends/parser/section_wrapper.rb +11 -5
- data/lib/at_coder_friends/parser/sections.rb +81 -46
- data/lib/at_coder_friends/problem.rb +8 -5
- data/lib/at_coder_friends/scraping/agent.rb +2 -0
- data/lib/at_coder_friends/version.rb +1 -1
- data/tasks/regression/check_const.rake +136 -0
- data/tasks/regression/check_diff.rake +2 -1
- data/tasks/regression/check_parse.rake +1 -1
- data/tasks/regression/regression.rb +7 -2
- data/tasks/regression/section_list.rake +19 -7
- data/templates/ruby_builtin_default.rb +2 -0
- data/templates/ruby_builtin_interactive.rb +2 -0
- metadata +5 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 54e37c0175b9736f4e99b6243af3b8970f34bef3
|
4
|
+
data.tar.gz: 588075eda25ca77b7f5fcef1f7b6158e97274af5
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: e2f1ef0a2dc869c89ff693c884fa23a61675d472cc3d3ef55f0d4f3d7faf880114584e0b9fde3207493502963a8d8b2cab4ef64aaca830dd1b580514c4bf1e6c
|
7
|
+
data.tar.gz: 2e707b0b63b1dedbd9df0f93bd7cd9cfaf4614cf7c731c63d2f4cd5382ad1944baed2bbac6bf829d1362c61b8761a37e6299c35e42e80b0d903edc3ce2c582c6
|
data/Gemfile.lock
CHANGED
data/lib/at_coder_friends.rb
CHANGED
@@ -16,10 +16,12 @@ require 'at_coder_friends/scraping/submission'
|
|
16
16
|
require 'at_coder_friends/scraping/tasks'
|
17
17
|
require 'at_coder_friends/scraping/agent'
|
18
18
|
require 'at_coder_friends/parser/section_wrapper'
|
19
|
+
require 'at_coder_friends/parser/introduction_wrapper'
|
19
20
|
require 'at_coder_friends/parser/sections'
|
20
21
|
require 'at_coder_friends/parser/sample_data'
|
21
22
|
require 'at_coder_friends/parser/input_format'
|
22
23
|
require 'at_coder_friends/parser/constraints'
|
24
|
+
require 'at_coder_friends/parser/modulo'
|
23
25
|
require 'at_coder_friends/parser/interactive'
|
24
26
|
require 'at_coder_friends/parser/binary'
|
25
27
|
require 'at_coder_friends/parser/main'
|
@@ -88,10 +88,24 @@ module AtCoderFriends
|
|
88
88
|
cfg['interactive_template'] || INTERACTIVE_TMPL
|
89
89
|
end
|
90
90
|
|
91
|
-
def gen_consts(
|
92
|
-
|
93
|
-
|
94
|
-
|
91
|
+
def gen_consts(constants = pbm.constants)
|
92
|
+
constants.map { |c| gen_const(c) }
|
93
|
+
end
|
94
|
+
|
95
|
+
def gen_const(c)
|
96
|
+
v = cnv_const_value(c.value)
|
97
|
+
if c.type == :max
|
98
|
+
"const int #{c.name.upcase}_MAX = #{v};"
|
99
|
+
else
|
100
|
+
"const int MOD = #{v};"
|
101
|
+
end
|
102
|
+
end
|
103
|
+
|
104
|
+
def cnv_const_value(v)
|
105
|
+
v
|
106
|
+
.sub(/\b10\^/, '1e')
|
107
|
+
.sub(/\b2\^/, '1<<')
|
108
|
+
.gsub(',', "'")
|
95
109
|
end
|
96
110
|
|
97
111
|
def gen_decls(inpdefs = pbm.formats)
|
@@ -25,6 +25,7 @@ module AtCoderFriends
|
|
25
25
|
File
|
26
26
|
.read(select_template)
|
27
27
|
.gsub('### URL ###', pbm.url)
|
28
|
+
.gsub('### CONSTS ###', gen_consts.join("\n"))
|
28
29
|
.gsub('### DCLS ###', gen_decls.join("\n"))
|
29
30
|
.gsub('### OUTPUT ###', gen_output)
|
30
31
|
end
|
@@ -41,6 +42,17 @@ module AtCoderFriends
|
|
41
42
|
cfg['interactive_template'] || INTERACTIVE_TMPL
|
42
43
|
end
|
43
44
|
|
45
|
+
def gen_consts(constants = pbm.constants)
|
46
|
+
constants
|
47
|
+
.select { |c| c.type == :mod }
|
48
|
+
.map { |c| gen_mod(c) }
|
49
|
+
end
|
50
|
+
|
51
|
+
def gen_mod(c)
|
52
|
+
v = c.value.gsub('^', '**').gsub(',', '_')
|
53
|
+
"MOD = #{v}"
|
54
|
+
end
|
55
|
+
|
44
56
|
def gen_decls(inpdefs = pbm.formats)
|
45
57
|
inpdefs.map { |inpdef| gen_decl(inpdef) }.flatten
|
46
58
|
end
|
@@ -10,9 +10,9 @@ module AtCoderFriends
|
|
10
10
|
vs = exp_values(pbm)
|
11
11
|
return unless vs.size == 2
|
12
12
|
return if vs.any? { |v| v.include?("\n") }
|
13
|
-
return if vs.any? { |v| v =~
|
13
|
+
return if vs.any? { |v| v =~ /\A[0-9\s]*\z/ }
|
14
14
|
|
15
|
-
out_fmt =
|
15
|
+
out_fmt = output_format(pbm)
|
16
16
|
re1, re2 = vs.map { |v| Regexp.escape(v) }
|
17
17
|
|
18
18
|
pbm.options.binary_values =
|
@@ -31,7 +31,7 @@ module AtCoderFriends
|
|
31
31
|
.uniq
|
32
32
|
end
|
33
33
|
|
34
|
-
def
|
34
|
+
def output_format(pbm)
|
35
35
|
pbm.sections[Problem::SECTION_OUT_FMT]&.content || ''
|
36
36
|
end
|
37
37
|
end
|
@@ -7,29 +7,89 @@ module AtCoderFriends
|
|
7
7
|
module_function
|
8
8
|
|
9
9
|
SECTIONS = [
|
10
|
+
Problem::SECTION_CONSTRAINTS,
|
10
11
|
Problem::SECTION_IN_FMT,
|
11
12
|
Problem::SECTION_IO_FMT,
|
12
|
-
Problem::
|
13
|
+
Problem::SECTION_STATEMENT
|
13
14
|
].freeze
|
15
|
+
NAME_PAT = /[0-9a-z_{},]+/i.freeze
|
16
|
+
NAMES_PAT = /#{NAME_PAT}(?:\s*,\s*#{NAME_PAT})*/.freeze
|
17
|
+
NUM_PAT = /[-+*^0-9{}, ]+/.freeze
|
18
|
+
MAX_PATTERN = /
|
19
|
+
(?:
|
20
|
+
(#{NAMES_PAT})\s*<\s*(#{NUM_PAT})
|
21
|
+
|(#{NAMES_PAT})\s*は\s*#{NUM_PAT}\s*以上\s*(#{NUM_PAT})\s*以下の整数
|
22
|
+
)
|
23
|
+
/xmi.freeze
|
14
24
|
|
15
25
|
def process(pbm)
|
16
|
-
|
17
|
-
|
26
|
+
maxs = []
|
27
|
+
SECTIONS.any? do |section|
|
28
|
+
next unless (text = pbm.sections[section]&.html)
|
29
|
+
|
30
|
+
!(maxs = parse(text)).empty?
|
18
31
|
end
|
19
|
-
|
20
|
-
pbm.constraints = constraints
|
32
|
+
pbm.constants += maxs
|
21
33
|
end
|
22
34
|
|
23
35
|
def parse(str)
|
36
|
+
str = normalize_content(str)
|
24
37
|
str
|
25
|
-
.
|
26
|
-
.
|
27
|
-
.
|
28
|
-
.
|
29
|
-
|
30
|
-
|
31
|
-
|
38
|
+
.scan(MAX_PATTERN)
|
39
|
+
.map(&:compact)
|
40
|
+
.map { |k, v| [normalize_names(k), normalize_value(v)] }
|
41
|
+
.select { |_, v| v && !v.empty? }
|
42
|
+
.flat_map { |ks, v| ks.map { |k| [k, v] } }
|
43
|
+
.uniq
|
44
|
+
.map { |k, v| Problem::Constant.new(k, :max, v) }
|
45
|
+
end
|
46
|
+
|
47
|
+
def normalize_content(s)
|
48
|
+
# 1) &npsp; , fill-width space -> half width space
|
49
|
+
# 2) {i, j}->{i,j} {N-1}->{N} shortest match
|
50
|
+
s
|
51
|
+
.tr('0-9A-Za-z', '0-9A-Za-z')
|
52
|
+
.gsub(/[[:space:]]/) { |c| c.gsub(/[^\t\r\n]/, ' ') } # 1)
|
53
|
+
.gsub(%r{</?var>}i, "\t")
|
54
|
+
.gsub(%r{<sup>([^<>]+)</sup>}i, '^\1')
|
55
|
+
.gsub(%r{<sub>([^<>]+)</sub>}i, '_{\1}')
|
56
|
+
.gsub('&', '&')
|
57
|
+
.gsub(/<("[^"]*"|'[^']*'|[^'"<>])*>/, '')
|
58
|
+
.gsub(/(<|≦|≤|<|&leq?;|\\lt|\\leq?q?)(\{\})?/i, '<')
|
59
|
+
.gsub('\\ ', ' ')
|
60
|
+
.gsub('\\,', ',')
|
61
|
+
.gsub('\\|', '|')
|
62
|
+
.gsub(',', ', ')
|
63
|
+
.gsub('×', '*')
|
64
|
+
.gsub('\\lvert', '|')
|
65
|
+
.gsub('\\rvert', '|')
|
66
|
+
.gsub('\\mathit', '')
|
67
|
+
.gsub('\\times', '*')
|
68
|
+
.gsub('|', '')
|
69
|
+
.gsub(/\{.*?\}/) { |w| w.delete(' ()').gsub(/{(.+)-1}\z/, '\1') } # 2)
|
70
|
+
end
|
71
|
+
|
72
|
+
def normalize_names(s)
|
73
|
+
# 1) {i,j}->{ij} shortest match
|
74
|
+
s
|
75
|
+
.gsub(/\{.*?\}/) { |w| w.delete(',') } # 1)
|
76
|
+
.delete('{}')
|
77
|
+
.gsub(/\s+/, '')
|
78
|
+
.split(',')
|
79
|
+
.reject(&:empty?)
|
80
|
+
end
|
81
|
+
|
82
|
+
def normalize_value(s)
|
83
|
+
s
|
84
|
+
.split(', ')
|
85
|
+
&.map do |v|
|
86
|
+
v
|
87
|
+
.delete(' {}')
|
88
|
+
.gsub(/\A[+*^,]+/, '') # remove preceding symbols
|
89
|
+
.gsub(/[+*^,]+\z/, '') # remove trailing symbols
|
32
90
|
end
|
91
|
+
&.reject(&:empty?)
|
92
|
+
&.first
|
33
93
|
end
|
34
94
|
end
|
35
95
|
end
|
@@ -6,15 +6,20 @@ module AtCoderFriends
|
|
6
6
|
module Interactive
|
7
7
|
module_function
|
8
8
|
|
9
|
-
INTERACTIVE_PAT =
|
10
|
-
|
9
|
+
INTERACTIVE_PAT = /
|
10
|
+
インタラクティブ
|
11
|
+
|interactive
|
12
|
+
|リアクティブ
|
13
|
+
|reactive
|
14
|
+
/xi.freeze
|
15
|
+
FLUSH_PAT = /flush/i.freeze
|
11
16
|
|
12
17
|
def process(pbm)
|
13
18
|
pbm.options.interactive = false
|
14
19
|
|
15
|
-
body = pbm.
|
16
|
-
f_int = body =~
|
17
|
-
f_flush = body =~
|
20
|
+
body = pbm.body_content
|
21
|
+
f_int = body =~ INTERACTIVE_PAT
|
22
|
+
f_flush = body =~ FLUSH_PAT
|
18
23
|
f_io = pbm.sections[Problem::SECTION_IO_FMT]
|
19
24
|
f_tbl =
|
20
25
|
pbm
|
@@ -0,0 +1,39 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module AtCoderFriends
|
4
|
+
module Parser
|
5
|
+
# holds introduction of problrem page
|
6
|
+
class IntroductionWrapper
|
7
|
+
attr_reader :div
|
8
|
+
|
9
|
+
def initialize(div)
|
10
|
+
@div = div
|
11
|
+
end
|
12
|
+
|
13
|
+
def intro
|
14
|
+
@intro ||= begin
|
15
|
+
div2 = div.dup
|
16
|
+
extract_intro(div2)
|
17
|
+
div2
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
21
|
+
def extract_intro(node)
|
22
|
+
found = false
|
23
|
+
node.children.each do |cld|
|
24
|
+
found = true if %w[h2 h3].any? { |h| cld.name == h }
|
25
|
+
if found
|
26
|
+
cld.remove
|
27
|
+
else
|
28
|
+
found = extract_intro(cld)
|
29
|
+
end
|
30
|
+
end
|
31
|
+
found
|
32
|
+
end
|
33
|
+
|
34
|
+
def html
|
35
|
+
@html ||= intro.to_html.gsub("\r\n", "\n")
|
36
|
+
end
|
37
|
+
end
|
38
|
+
end
|
39
|
+
end
|
@@ -0,0 +1,76 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module AtCoderFriends
|
4
|
+
module Parser
|
5
|
+
# parses problem page and extract modulo values
|
6
|
+
module Modulo
|
7
|
+
module_function
|
8
|
+
|
9
|
+
# rubocop:disable Style/AsciiComments
|
10
|
+
SECTIONS = [
|
11
|
+
Problem::SECTION_OUT_FMT,
|
12
|
+
Problem::SECTION_STATEMENT,
|
13
|
+
Problem::SECTION_TASK,
|
14
|
+
Problem::SECTION_INTRO
|
15
|
+
].freeze
|
16
|
+
# \(998244353\)
|
17
|
+
# 十億九
|
18
|
+
VALUE_PATTERN = %r{
|
19
|
+
(?:
|
20
|
+
<var>([^<>]+)</var>
|
21
|
+
|\\\(([^()]+)\\\)
|
22
|
+
|\$([^$]+)\$
|
23
|
+
|\{([^{}]+)\}
|
24
|
+
|([\d,]+)
|
25
|
+
|([一二三四五六七八九十百千万億]+)
|
26
|
+
)
|
27
|
+
}x.freeze
|
28
|
+
# <var>1,000,000,007</var> (素数)で割った余り
|
29
|
+
MOD_PATTERN = /
|
30
|
+
(?:
|
31
|
+
#{VALUE_PATTERN}\s*(?:\([^()]+\)\s*)?で割った(?:剰余|余り|あまり)
|
32
|
+
|(?:modulo|mod|divided\s*by|dividing\s*by)\s*#{VALUE_PATTERN}
|
33
|
+
)
|
34
|
+
/xi.freeze
|
35
|
+
# rubocop:enable Style/AsciiComments
|
36
|
+
|
37
|
+
def process(pbm)
|
38
|
+
mods = []
|
39
|
+
SECTIONS.any? do |section|
|
40
|
+
next unless (html = pbm.sections[section]&.html)
|
41
|
+
|
42
|
+
!(mods = parse(html)).empty?
|
43
|
+
end
|
44
|
+
pbm.constants += mods
|
45
|
+
end
|
46
|
+
|
47
|
+
def parse(str)
|
48
|
+
str = normalize_content(str)
|
49
|
+
str
|
50
|
+
.scan(MOD_PATTERN)
|
51
|
+
.map(&:compact)
|
52
|
+
.map { |(v)| normalize_value(v) }
|
53
|
+
.reject(&:empty?)
|
54
|
+
.uniq
|
55
|
+
.map { |v| Problem::Constant.new('mod', :mod, v) }
|
56
|
+
end
|
57
|
+
|
58
|
+
def normalize_content(s)
|
59
|
+
s
|
60
|
+
.tr('0-9A-Za-z', '0-9A-Za-z')
|
61
|
+
.gsub(/[[:space:]]/, ' ')
|
62
|
+
.gsub(%r{[^一-龠_ぁ-ん_ァ-ヶーa-zA-Z0-9 -/:-@\[-`\{-~]}, '')
|
63
|
+
.gsub(/{\\rm\s*mod\s*}\\?/i, 'mod') # {\rm mod} -> mod
|
64
|
+
.gsub(/\\rm\s*{\s*mod\s*}\\?/i, 'mod') # \rm{mod}\ -> mod
|
65
|
+
.gsub(/\\mbox\s*{\s*mod\s*}/i, 'mod') # \mbox{mod} -> mod
|
66
|
+
.gsub(%r{<var>\s*mod\s*</var>}i, 'mod') # <var>mod</var> -> mod
|
67
|
+
end
|
68
|
+
|
69
|
+
def normalize_value(s)
|
70
|
+
s
|
71
|
+
.gsub(/\A([^(=]+)[(=].*\z/, '\1') # 1000000007 (10^9+7), ... =10^9+7
|
72
|
+
.gsub(/[{}()=\\ ]/, '')
|
73
|
+
end
|
74
|
+
end
|
75
|
+
end
|
76
|
+
end
|
@@ -2,7 +2,7 @@
|
|
2
2
|
|
3
3
|
module AtCoderFriends
|
4
4
|
module Parser
|
5
|
-
# holds section
|
5
|
+
# holds a section of problrem page
|
6
6
|
class SectionWrapper
|
7
7
|
attr_reader :h
|
8
8
|
|
@@ -28,14 +28,20 @@ module AtCoderFriends
|
|
28
28
|
end
|
29
29
|
end
|
30
30
|
|
31
|
+
def html
|
32
|
+
@html ||= begin
|
33
|
+
siblings.reduce('') { |m, node| m + node.to_html.gsub("\r\n", "\n") }
|
34
|
+
end
|
35
|
+
end
|
36
|
+
|
31
37
|
def find_element(tags)
|
32
|
-
|
33
|
-
|
38
|
+
elem = nil
|
39
|
+
siblings.any? do |node|
|
40
|
+
tags.any? do |tag|
|
34
41
|
elem = node.name == tag ? node : node.search(tag)[0]
|
35
|
-
return elem if elem
|
36
42
|
end
|
37
43
|
end
|
38
|
-
|
44
|
+
elem
|
39
45
|
end
|
40
46
|
|
41
47
|
def code_block
|
@@ -4,65 +4,98 @@ module AtCoderFriends
|
|
4
4
|
module Parser
|
5
5
|
module SectionsConstants
|
6
6
|
SECTION_DEFS = [
|
7
|
+
{
|
8
|
+
key: Problem::SECTION_STATEMENT,
|
9
|
+
pattern: /
|
10
|
+
\A(
|
11
|
+
問題文?
|
12
|
+
|Problem\s*(Statement|Setting)?
|
13
|
+
|Statement
|
14
|
+
|Description
|
15
|
+
)\z
|
16
|
+
/xi
|
17
|
+
},
|
18
|
+
{
|
19
|
+
key: Problem::SECTION_TASK,
|
20
|
+
pattern: /
|
21
|
+
\A(
|
22
|
+
課題
|
23
|
+
|Task
|
24
|
+
)\z
|
25
|
+
/xi
|
26
|
+
},
|
7
27
|
{
|
8
28
|
key: Problem::SECTION_CONSTRAINTS,
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
29
|
+
pattern: /
|
30
|
+
\A(
|
31
|
+
(入力(の|に関する)?)?(制約|制限)
|
32
|
+
|Constraints
|
33
|
+
)\z
|
34
|
+
/xi
|
14
35
|
},
|
15
36
|
{
|
16
37
|
key: Problem::SECTION_IN_FMT,
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
38
|
+
pattern: /
|
39
|
+
\A(
|
40
|
+
入力(形式)?
|
41
|
+
|Inputs?\s*(Format)?
|
42
|
+
)\z
|
43
|
+
/xi
|
21
44
|
},
|
22
45
|
{
|
23
46
|
key: Problem::SECTION_OUT_FMT,
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
47
|
+
pattern: /
|
48
|
+
\A(
|
49
|
+
出力(形式)?
|
50
|
+
|Outputs?\s*(Format)?
|
51
|
+
)\z
|
52
|
+
/xi
|
28
53
|
},
|
29
54
|
{
|
30
55
|
key: Problem::SECTION_IO_FMT,
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
56
|
+
pattern: /
|
57
|
+
\A(
|
58
|
+
入出力(形式)?
|
59
|
+
|Input\s*(and)?\s*Output\s*(Format)?
|
60
|
+
)\z
|
61
|
+
/xi
|
35
62
|
},
|
36
63
|
{
|
37
64
|
key: Problem::SECTION_IN_SMP,
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
|
65
|
+
pattern: /
|
66
|
+
\A(
|
67
|
+
入力例\s*(?<no>\d+)?
|
68
|
+
|入力\s*(?<no>\d+)
|
69
|
+
|Sample\s*Input\s*(?<no>\d+)?
|
70
|
+
|Input\s*Example\s*(?<no>\d+)?
|
71
|
+
|Input\s*(?<no>\d+)
|
72
|
+
)\z
|
73
|
+
/xi
|
45
74
|
},
|
46
75
|
{
|
47
76
|
key: Problem::SECTION_OUT_SMP,
|
48
|
-
|
49
|
-
|
50
|
-
|
51
|
-
|
52
|
-
|
53
|
-
|
54
|
-
|
55
|
-
|
56
|
-
|
77
|
+
pattern: /
|
78
|
+
\A(
|
79
|
+
出力例\s*(?<no>\d+)?
|
80
|
+
|出力\s*(?<no>\d+)
|
81
|
+
|入力例\s*(?<no>\d+)?\s*に対する出力例
|
82
|
+
|Sample\s*Output\s*(?<no>\d+)?
|
83
|
+
|Output\s*Example\s*(?<no>\d+)?
|
84
|
+
|Output\s*(?<no>\d+)
|
85
|
+
|Output\s*for\s*(the)?\s*Sample\s*Input\s*(?<no>\d+)?
|
86
|
+
)\z
|
87
|
+
/xi
|
57
88
|
},
|
58
89
|
{
|
59
90
|
key: Problem::SECTION_IO_SMP,
|
60
|
-
|
61
|
-
|
62
|
-
|
63
|
-
|
64
|
-
|
65
|
-
|
91
|
+
pattern: /
|
92
|
+
\A(
|
93
|
+
入出力の?例\s*(\d+)?
|
94
|
+
|サンプル\s*(\d+)?
|
95
|
+
|Sample\s*Input\s*(and)?\s*Output\s*(\d+)?
|
96
|
+
|Samples?\s*(\d+)?
|
97
|
+
)\z
|
98
|
+
/xi
|
66
99
|
}
|
67
100
|
].freeze
|
68
101
|
end
|
@@ -75,6 +108,8 @@ module AtCoderFriends
|
|
75
108
|
|
76
109
|
def process(pbm)
|
77
110
|
sections = collect_sections(pbm.page)
|
111
|
+
div = pbm.page.search('div#task-statement')[0]
|
112
|
+
div && sections[Problem::SECTION_INTRO] = IntroductionWrapper.new(div)
|
78
113
|
pbm.sections = sections
|
79
114
|
end
|
80
115
|
|
@@ -91,20 +126,20 @@ module AtCoderFriends
|
|
91
126
|
|
92
127
|
def find_key(h)
|
93
128
|
title = normalize(h.content)
|
94
|
-
|
95
|
-
|
96
|
-
|
97
|
-
|
129
|
+
key = nil
|
130
|
+
SECTION_DEFS.any? do |grp|
|
131
|
+
if (m = title.match(grp[:pattern]))
|
98
132
|
no = m.names.include?('no') && m['no'] || '1'
|
99
|
-
|
133
|
+
key = format(grp[:key], no: no)
|
100
134
|
end
|
101
135
|
end
|
102
|
-
|
136
|
+
key
|
103
137
|
end
|
104
138
|
|
105
139
|
def normalize(s)
|
106
140
|
s
|
107
|
-
.tr('
|
141
|
+
.tr('0-9A-Za-z', '0-9A-Za-z')
|
142
|
+
.gsub(/[[:space:]]/, ' ') # &npsp; full-width space
|
108
143
|
.gsub(/[^一-龠_ぁ-ん_ァ-ヶーa-zA-Z0-9 ]/, '')
|
109
144
|
.strip
|
110
145
|
end
|
@@ -3,6 +3,9 @@
|
|
3
3
|
module AtCoderFriends
|
4
4
|
# holds problem information
|
5
5
|
class Problem
|
6
|
+
SECTION_INTRO = 'INTRODUCTION'
|
7
|
+
SECTION_STATEMENT = 'STATEMENT'
|
8
|
+
SECTION_TASK = 'TASK'
|
6
9
|
SECTION_IN_FMT = 'INPUT_FORMAT'
|
7
10
|
SECTION_OUT_FMT = 'OUTPUT_FORMAT'
|
8
11
|
SECTION_IO_FMT = 'INOUT_FORMAT'
|
@@ -21,14 +24,14 @@ module AtCoderFriends
|
|
21
24
|
end
|
22
25
|
end
|
23
26
|
|
24
|
-
|
27
|
+
Constant = Struct.new(:name, :type, :value)
|
25
28
|
|
26
29
|
Options = Struct.new(:interactive, :binary_values)
|
27
30
|
|
28
31
|
SourceCode = Struct.new(:ext, :txt)
|
29
32
|
|
30
33
|
attr_reader :q, :samples, :sources, :options
|
31
|
-
attr_accessor :page, :sections, :formats, :
|
34
|
+
attr_accessor :page, :sections, :formats, :constants
|
32
35
|
|
33
36
|
def initialize(q, page = Mechanize::Page.new)
|
34
37
|
@q = q
|
@@ -36,7 +39,7 @@ module AtCoderFriends
|
|
36
39
|
@sections = {}
|
37
40
|
@samples = []
|
38
41
|
@formats = []
|
39
|
-
@
|
42
|
+
@constants = []
|
40
43
|
@options = Options.new
|
41
44
|
@sources = []
|
42
45
|
yield self if block_given?
|
@@ -46,8 +49,8 @@ module AtCoderFriends
|
|
46
49
|
@url ||= page.uri.to_s
|
47
50
|
end
|
48
51
|
|
49
|
-
def
|
50
|
-
@
|
52
|
+
def body_content
|
53
|
+
@body_content ||= page.search('body')[0]&.content
|
51
54
|
end
|
52
55
|
|
53
56
|
def add_smp(no, ext, txt)
|
@@ -15,12 +15,14 @@ module AtCoderFriends
|
|
15
15
|
include Submission
|
16
16
|
|
17
17
|
BASE_URL = 'https://atcoder.jp/'
|
18
|
+
CONTACT = 'https://github.com/nejiko96/at_coder_friends'
|
18
19
|
|
19
20
|
attr_reader :ctx, :agent
|
20
21
|
|
21
22
|
def initialize(ctx)
|
22
23
|
@ctx = ctx
|
23
24
|
@agent = Mechanize.new
|
25
|
+
agent.user_agent = "AtCoderFriends/#{VERSION} (#{CONTACT})"
|
24
26
|
agent.pre_connect_hooks << proc { sleep 0.1 }
|
25
27
|
agent.log = Logger.new(STDERR) if ctx.options[:debug]
|
26
28
|
load_session
|
@@ -0,0 +1,136 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require_relative 'regression'
|
4
|
+
|
5
|
+
module AtCoderFriends
|
6
|
+
# tasks for regression
|
7
|
+
module Regression
|
8
|
+
module_function
|
9
|
+
|
10
|
+
CONST_PAT = {
|
11
|
+
mod: /
|
12
|
+
(.{,30}(?:
|
13
|
+
で割った|modulo|mod\b|divided\s+by|dividing\s+by
|
14
|
+
).{,30})
|
15
|
+
/xmi,
|
16
|
+
max: /
|
17
|
+
(.{,30}(?:
|
18
|
+
≦|≤|\\le|&leq?;
|
19
|
+
|<|\\lt|<
|
20
|
+
|以上.{1,25}以下の整数
|
21
|
+
).{,30})
|
22
|
+
/xmi
|
23
|
+
}.freeze
|
24
|
+
|
25
|
+
def collect(tgt)
|
26
|
+
File.open(const_log('collect', tgt), 'w') do |f|
|
27
|
+
local_pbm_list.each do |contest, q, url|
|
28
|
+
page = agent.get(url)
|
29
|
+
body = page.body.force_encoding('utf-8')
|
30
|
+
ms = body.scan(CONST_PAT[tgt.to_sym])
|
31
|
+
ms.each do |m|
|
32
|
+
s = m[0].delete("\r\n\t\"")
|
33
|
+
f.puts [contest, q, s].join("\t")
|
34
|
+
end
|
35
|
+
end
|
36
|
+
end
|
37
|
+
end
|
38
|
+
|
39
|
+
def check_mod
|
40
|
+
File.open(const_log('check', 'mod'), 'w') do |f|
|
41
|
+
local_pbm_list.each do |contest, q, url|
|
42
|
+
pbm = scraping_agent(nil, contest).fetch_problem(q, url)
|
43
|
+
Parser::Sections.process(pbm)
|
44
|
+
Parser::Modulo.process(pbm)
|
45
|
+
pbm.constants.each do |cnst|
|
46
|
+
f.puts [contest, q, cnst.value].join("\t")
|
47
|
+
end
|
48
|
+
end
|
49
|
+
end
|
50
|
+
end
|
51
|
+
|
52
|
+
def check_max
|
53
|
+
File.open(const_log('check', 'max'), 'w') do |f|
|
54
|
+
local_pbm_list.each do |contest, q, url|
|
55
|
+
pbm = scraping_agent(nil, contest).fetch_problem(q, url)
|
56
|
+
Parser::Sections.process(pbm)
|
57
|
+
Parser::Constraints.process(pbm)
|
58
|
+
pbm.constants.each do |cns|
|
59
|
+
f.puts [contest, q, "#{cns.name}:#{cns.value}"].join("\t")
|
60
|
+
end
|
61
|
+
end
|
62
|
+
end
|
63
|
+
end
|
64
|
+
|
65
|
+
def merge_list(tgt)
|
66
|
+
tbl = load_merge_list(tgt)
|
67
|
+
save_merge_list(tgt, tbl)
|
68
|
+
end
|
69
|
+
|
70
|
+
def load_merge_list(tgt)
|
71
|
+
tbl = {}
|
72
|
+
%w[collect check]
|
73
|
+
.map { |act| const_log(act, tgt) }
|
74
|
+
.each
|
75
|
+
.with_index(1) do |file, n|
|
76
|
+
list_from_file(file)
|
77
|
+
.group_by { |contest, q, _| "#{contest}\t#{q}" }
|
78
|
+
.map { |key, grp| [key, grp.map { |row| row[2] }.join("\n")] }
|
79
|
+
.each do |key, txt|
|
80
|
+
tbl[key] ||= { 'v1' => '', 'v2' => '' }
|
81
|
+
tbl[key]["v#{n}"] = '"' + txt + '"'
|
82
|
+
end
|
83
|
+
end
|
84
|
+
tbl
|
85
|
+
end
|
86
|
+
|
87
|
+
def save_merge_list(tgt, tbl)
|
88
|
+
File.open(const_log('merge', tgt), 'w') do |f|
|
89
|
+
tbl.sort.each do |k, h|
|
90
|
+
f.puts [k, h['v1'], h['v2']].join("\t")
|
91
|
+
end
|
92
|
+
end
|
93
|
+
end
|
94
|
+
|
95
|
+
def const_log(act, tgt)
|
96
|
+
log_path("#{act}_#{tgt}.txt")
|
97
|
+
end
|
98
|
+
|
99
|
+
def list_from_file(file)
|
100
|
+
Encoding.default_external = 'utf-8'
|
101
|
+
CSV.read(file, col_sep: "\t", headers: false)
|
102
|
+
end
|
103
|
+
end
|
104
|
+
end
|
105
|
+
|
106
|
+
namespace :regression do
|
107
|
+
desc 'list all mod values'
|
108
|
+
task :collect_mod do
|
109
|
+
AtCoderFriends::Regression.collect('mod')
|
110
|
+
end
|
111
|
+
|
112
|
+
desc 'check extracted mod values'
|
113
|
+
task :check_mod do
|
114
|
+
AtCoderFriends::Regression.check_mod
|
115
|
+
end
|
116
|
+
|
117
|
+
desc 'merge mod values list'
|
118
|
+
task :merge_mod do
|
119
|
+
AtCoderFriends::Regression.merge_list('mod')
|
120
|
+
end
|
121
|
+
|
122
|
+
desc 'list all max values'
|
123
|
+
task :collect_max do
|
124
|
+
AtCoderFriends::Regression.collect('max')
|
125
|
+
end
|
126
|
+
|
127
|
+
desc 'check extracted max values'
|
128
|
+
task :check_max do
|
129
|
+
AtCoderFriends::Regression.check_max
|
130
|
+
end
|
131
|
+
|
132
|
+
desc 'merge max values list'
|
133
|
+
task :merge_max do
|
134
|
+
AtCoderFriends::Regression.merge_list('max')
|
135
|
+
end
|
136
|
+
end
|
@@ -11,7 +11,7 @@ module AtCoderFriends
|
|
11
11
|
def check_parse(arg)
|
12
12
|
arg ||= 'fmt,smp,int'
|
13
13
|
list = local_pbm_list.map do |contest, q, url|
|
14
|
-
pbm = scraping_agent(
|
14
|
+
pbm = scraping_agent(nil, contest).fetch_problem(q, url)
|
15
15
|
Parser::Main.process(pbm)
|
16
16
|
tbl = {
|
17
17
|
'fmt' => !fmt?(pbm),
|
@@ -13,8 +13,8 @@ module AtCoderFriends
|
|
13
13
|
module_function
|
14
14
|
|
15
15
|
CONTEST_LIST_URL = 'https://kenkoooo.com/atcoder/resources/contests.json'
|
16
|
-
|
17
|
-
|
16
|
+
ACF_HOME = File.expand_path(File.join(__dir__, '..', '..'))
|
17
|
+
REGRESSION_HOME = File.join(ACF_HOME, 'regression')
|
18
18
|
PAGES_DIR = File.join(REGRESSION_HOME, 'pages')
|
19
19
|
EMIT_ORG_DIR = File.join(REGRESSION_HOME, 'emit_org')
|
20
20
|
EMIT_DIR_FMT = File.join(REGRESSION_HOME, 'emit_%<now>s')
|
@@ -46,6 +46,7 @@ module AtCoderFriends
|
|
46
46
|
end
|
47
47
|
|
48
48
|
def scraping_agent(root, contest)
|
49
|
+
root ||= REGRESSION_HOME
|
49
50
|
@ctx = Context.new({}, File.join(root, contest))
|
50
51
|
@ctx.scraping_agent
|
51
52
|
end
|
@@ -63,5 +64,9 @@ module AtCoderFriends
|
|
63
64
|
def rmdir_force(dir)
|
64
65
|
FileUtils.rm_r(dir) if Dir.exist?(dir)
|
65
66
|
end
|
67
|
+
|
68
|
+
def log_path(file)
|
69
|
+
File.join(REGRESSION_HOME, file)
|
70
|
+
end
|
66
71
|
end
|
67
72
|
end
|
@@ -7,27 +7,39 @@ module AtCoderFriends
|
|
7
7
|
module Regression
|
8
8
|
module_function
|
9
9
|
|
10
|
+
SectionInfo = Struct.new(:contest, :q, :title)
|
11
|
+
|
10
12
|
def section_list
|
11
|
-
list =
|
13
|
+
list = load_section_list
|
14
|
+
save_section_list(list)
|
15
|
+
end
|
16
|
+
|
17
|
+
def load_section_list
|
18
|
+
local_pbm_list.flat_map do |contest, q, url|
|
12
19
|
page = agent.get(url)
|
13
20
|
%w[h2 h3].flat_map do |tag|
|
14
21
|
page.search(tag).map do |h|
|
15
|
-
|
22
|
+
SectionInfo.new(contest, q, normalize(h.content))
|
16
23
|
end
|
17
24
|
end
|
18
25
|
end
|
19
|
-
|
20
|
-
|
26
|
+
end
|
27
|
+
|
28
|
+
def save_section_list(list)
|
29
|
+
File.open(log_path('section_list.txt'), 'w') do |f|
|
30
|
+
list.group_by(&:title).each do |k, vs|
|
31
|
+
f.puts [k, vs.size, vs[0].contest, vs[0].q].join("\t")
|
32
|
+
end
|
21
33
|
end
|
22
34
|
end
|
23
35
|
|
24
36
|
def normalize(s)
|
25
37
|
s
|
26
|
-
.tr('
|
38
|
+
.tr('0-9A-Za-z', '0-9A-Za-z')
|
39
|
+
.gsub(/[[:space:]]/, '')
|
27
40
|
.gsub(/[^一-龠_ぁ-ん_ァ-ヶーa-zA-Z0-9 ]/, '')
|
28
|
-
.gsub(/\d+/, '{N}')
|
29
|
-
.gsub(' ', '')
|
30
41
|
.downcase
|
42
|
+
.gsub(/\d+/, '{N}')
|
31
43
|
.strip
|
32
44
|
end
|
33
45
|
end
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: at_coder_friends
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.6.
|
4
|
+
version: 0.6.1
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- nejiko96
|
8
8
|
autorequire:
|
9
9
|
bindir: exe
|
10
10
|
cert_chain: []
|
11
|
-
date: 2019-10-
|
11
|
+
date: 2019-10-28 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: colorize
|
@@ -165,7 +165,9 @@ files:
|
|
165
165
|
- lib/at_coder_friends/parser/constraints.rb
|
166
166
|
- lib/at_coder_friends/parser/input_format.rb
|
167
167
|
- lib/at_coder_friends/parser/interactive.rb
|
168
|
+
- lib/at_coder_friends/parser/introduction_wrapper.rb
|
168
169
|
- lib/at_coder_friends/parser/main.rb
|
170
|
+
- lib/at_coder_friends/parser/modulo.rb
|
169
171
|
- lib/at_coder_friends/parser/sample_data.rb
|
170
172
|
- lib/at_coder_friends/parser/section_wrapper.rb
|
171
173
|
- lib/at_coder_friends/parser/sections.rb
|
@@ -182,6 +184,7 @@ files:
|
|
182
184
|
- lib/at_coder_friends/test_runner/sample.rb
|
183
185
|
- lib/at_coder_friends/verifier.rb
|
184
186
|
- lib/at_coder_friends/version.rb
|
187
|
+
- tasks/regression/check_const.rake
|
185
188
|
- tasks/regression/check_diff.rake
|
186
189
|
- tasks/regression/check_parse.rake
|
187
190
|
- tasks/regression/regression.rb
|