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