at_coder_friends 0.6.0 → 0.6.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.
- 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
|