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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: cfcd8c547d0916bc1db5c76b5c698e410ddb3541
4
- data.tar.gz: 03e06ac13b32c2d55166cf1cefa83ad9cfbe3fa2
3
+ metadata.gz: 54e37c0175b9736f4e99b6243af3b8970f34bef3
4
+ data.tar.gz: 588075eda25ca77b7f5fcef1f7b6158e97274af5
5
5
  SHA512:
6
- metadata.gz: d73018f888e13b7a37a19b8945b879513caf4952ff5bdcbe9412c413a84d05c4c055fb00166e97fe3b5420637b0c87c767796f9c2835e82c879451f854b2fc9d
7
- data.tar.gz: 602aa37bf5f315a9bccfbd3e676f19b0651c4687fbe351e65d670359c956b74e230ebcedade158928cd1486092947af1bfb34c1504632b7389d1029da2afb70a
6
+ metadata.gz: e2f1ef0a2dc869c89ff693c884fa23a61675d472cc3d3ef55f0d4f3d7faf880114584e0b9fde3207493502963a8d8b2cab4ef64aaca830dd1b580514c4bf1e6c
7
+ data.tar.gz: 2e707b0b63b1dedbd9df0f93bd7cd9cfaf4614cf7c731c63d2f4cd5382ad1944baed2bbac6bf829d1362c61b8761a37e6299c35e42e80b0d903edc3ce2c582c6
@@ -1,7 +1,7 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- at_coder_friends (0.6.0)
4
+ at_coder_friends (0.6.1)
5
5
  colorize (~> 0.8.1)
6
6
  launchy (~> 2.4.3)
7
7
  mechanize (~> 2.0)
@@ -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(constraints = pbm.constraints)
92
- constraints
93
- .select { |c| c.type == :max }
94
- .map { |c| "const int #{c.name.upcase}_MAX = #{c.value};" }
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 =~ /^[0-9\s]*$/ }
13
+ return if vs.any? { |v| v =~ /\A[0-9\s]*\z/ }
14
14
 
15
- out_fmt = ouput_format(pbm)
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 ouput_format(pbm)
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::SECTION_CONSTRAINTS
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
- str = SECTIONS.reduce('') do |m, key|
17
- m + (pbm.sections[key]&.content || '')
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
- constraints = parse(str)
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
- .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)
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('&amp;', '&')
57
+ .gsub(/<("[^"]*"|'[^']*'|[^'"<>])*>/, '')
58
+ .gsub(/(<|≦|≤|&lt;|&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 = '(インタラクティブ|interactive|リアクティブ|reactive)'
10
- FLUSH_PAT = 'flush'
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.page_body
16
- f_int = body =~ /#{INTERACTIVE_PAT}/i
17
- f_flush = body =~ /#{FLUSH_PAT}/i
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
@@ -11,6 +11,7 @@ module AtCoderFriends
11
11
  SampleData.process(pbm)
12
12
  InputFormat.process(pbm)
13
13
  Constraints.process(pbm)
14
+ Modulo.process(pbm)
14
15
  Interactive.process(pbm)
15
16
  Binary.process(pbm)
16
17
  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 in problrem page
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
- siblings.each do |node|
33
- tags.each do |tag|
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
- nil
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
- patterns: [
10
- '^制約$',
11
- '^入力制限$',
12
- '^Constraints$'
13
- ]
29
+ pattern: /
30
+ \A(
31
+ (入力(の|に関する)?)?(制約|制限)
32
+ |Constraints
33
+ )\z
34
+ /xi
14
35
  },
15
36
  {
16
37
  key: Problem::SECTION_IN_FMT,
17
- patterns: [
18
- '^入力(形式)?$',
19
- '^Inputs?\s*(Format)?$'
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
- patterns: [
25
- '^出力(形式)?$',
26
- '^Outputs?\s*(Format)?$'
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
- patterns: [
32
- '^入出力(形式)?$',
33
- '^Input\s*(and)?\s*Output\s*(Format)?$'
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
- patterns: [
39
- '^入力例\s*(?<no>\d+)?$',
40
- '^入力\s*(?<no>\d+)$',
41
- '^Sample\s*Input\s*(?<no>\d+)?$',
42
- '^Input\s*Example\s*(?<no>\d+)?$',
43
- '^Input\s*(?<no>\d+)$'
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
- patterns: [
49
- '^出力例\s*(?<no>\d+)?$',
50
- '^出力\s*(?<no>\d+)$',
51
- '^入力例\s*(?<no>\d+)?\s*に対する出力例$',
52
- '^Sample\s*Output\s*(?<no>\d+)?$',
53
- '^Output\s*Example\s*(?<no>\d+)?$',
54
- '^Output\s*(?<no>\d+)$',
55
- '^Output\s*for\s*(the)?\s*Sample\s*Input\s*(?<no>\d+)?$'
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
- patterns: [
61
- '^入出力の?例\s*(\d+)?$',
62
- '^サンプル\s*(\d+)?$',
63
- '^Sample\s*Input\s*(and)?\s*Output\s*(\d+)?$',
64
- '^Samples?\s*(\d+)?$'
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
- SECTION_DEFS.each do |grp|
95
- grp[:patterns].each do |pat|
96
- next unless (m = title.match(/#{pat}/i))
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
- return format(grp[:key], no: no)
133
+ key = format(grp[:key], no: no)
100
134
  end
101
135
  end
102
- nil
136
+ key
103
137
  end
104
138
 
105
139
  def normalize(s)
106
140
  s
107
- .tr(' 0-9A-Za-z', ' 0-9A-Za-z')
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
- Constraint = Struct.new(:name, :type, :value)
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, :constraints
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
- @constraints = []
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 page_body
50
- @page_body ||= page.body.force_encoding('utf-8')
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
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module AtCoderFriends
4
- VERSION = '0.6.0'
4
+ VERSION = '0.6.1'
5
5
  end
@@ -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|&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
@@ -16,7 +16,8 @@ module AtCoderFriends
16
16
  pipeline(pbm)
17
17
  end
18
18
 
19
- system("diff -r #{EMIT_ORG_DIR} #{emit_dir}")
19
+ diff_log = log_path('check_diff.txt')
20
+ system("diff -r --exclude=.git #{EMIT_ORG_DIR} #{emit_dir} > #{diff_log}")
20
21
  end
21
22
  end
22
23
  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(REGRESSION_HOME, contest).fetch_problem(q, url)
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
- REGRESSION_HOME =
17
- File.expand_path(File.join(__dir__, '..', '..', 'regression'))
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 = local_pbm_list.flat_map do |contest, q, url|
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
- { contest: contest, q: q, text: normalize(h.content) }
22
+ SectionInfo.new(contest, q, normalize(h.content))
16
23
  end
17
24
  end
18
25
  end
19
- list.group_by { |sec| sec[:text] }.each do |k, vs|
20
- puts [k, vs.size, vs[0][:contest], vs[0][:q]].join("\t")
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(' 0-9A-Za-z', ' 0-9A-Za-z')
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
@@ -1,5 +1,7 @@
1
1
  # ### URL ###
2
2
 
3
+ ### CONSTS ###
4
+
3
5
  ### DCLS ###
4
6
 
5
7
  ### OUTPUT ###
@@ -13,6 +13,8 @@ end
13
13
 
14
14
  $DEBUG = true
15
15
 
16
+ ### CONSTS ###
17
+
16
18
  ### DCLS ###
17
19
 
18
20
  if $DEBUG
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.0
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-21 00:00:00.000000000 Z
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