at_coder_friends 0.6.2 → 0.6.3

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,91 @@
1
+ # frozen_string_literal: true
2
+
3
+ module AtCoderFriends
4
+ module Parser
5
+ # parses input data types and updates input definitons
6
+ module InputType
7
+ module_function
8
+
9
+ NUMBER_PAT = /\A[+-]?[0-9]+\z/.freeze
10
+ TYPE_TBL = [
11
+ [:number, NUMBER_PAT],
12
+ [:decimal, /\A[+-]?[0-9]+(\.[0-9]+)?\z/]
13
+ ].freeze
14
+
15
+ def process(pbm)
16
+ parse(pbm.formats_src, pbm.samples)
17
+ end
18
+
19
+ def parse(inpdefs, smps)
20
+ lines = max_smp(smps)&.split("\n")
21
+ lines && match_smp(inpdefs, lines)
22
+ end
23
+
24
+ def max_smp(smps)
25
+ smps
26
+ .select { |smp| smp.ext == :in }
27
+ .max_by { |smp| smp.txt.size }
28
+ &.txt
29
+ end
30
+
31
+ def match_smp(inpdefs, lines)
32
+ vars = {}
33
+ inpdefs.each do |inpdef|
34
+ break unless (k = get_line_cnt(inpdef))
35
+
36
+ k, parsed = parse_line_cnt(k, vars)
37
+ rows = lines.shift(k).map { |line| line.split(/[#{inpdef.delim} ]/) }
38
+ break if rows.empty?
39
+
40
+ inpdef.container == :single &&
41
+ vars.merge!(inpdef.names.zip(rows[0]).to_h)
42
+ inpdef.cols = detect_cols_type(rows)
43
+ break unless parsed
44
+ end
45
+ inpdefs
46
+ end
47
+
48
+ def get_line_cnt(inpdef)
49
+ case inpdef.size.size
50
+ when 0
51
+ 1
52
+ when 1
53
+ inpdef.container == :harray ? 1 : inpdef.size[0]
54
+ when 2
55
+ inpdef.size[0]
56
+ end
57
+ end
58
+
59
+ def parse_line_cnt(k, vars)
60
+ if k.is_a?(Integer)
61
+ [k, true]
62
+ elsif k =~ NUMBER_PAT
63
+ [k.to_i, true]
64
+ elsif vars[k] =~ NUMBER_PAT
65
+ [vars[k].to_i, true]
66
+ elsif vars[(k2 = k.gsub(/-1\z/, ''))] =~ NUMBER_PAT
67
+ [vars[k2].to_i - 1, true]
68
+ else
69
+ [1, false]
70
+ end
71
+ end
72
+
73
+ def detect_cols_type(rows)
74
+ cols = fill_transpose(rows).map(&:compact)
75
+ cols.map { |col| detect_col_type(col) }
76
+ end
77
+
78
+ def fill_transpose(arr)
79
+ Array.new(arr.map(&:size).max) { |i| arr.map { |e| e[i] } }
80
+ end
81
+
82
+ def detect_col_type(arr)
83
+ ret = :string
84
+ TYPE_TBL.any? do |type, pat|
85
+ arr.all? { |v| v =~ pat } && ret = type
86
+ end
87
+ ret
88
+ end
89
+ end
90
+ end
91
+ end
@@ -10,6 +10,7 @@ module AtCoderFriends
10
10
  Sections.process(pbm)
11
11
  SampleData.process(pbm)
12
12
  InputFormat.process(pbm)
13
+ InputType.process(pbm)
13
14
  Constraints.process(pbm)
14
15
  Modulo.process(pbm)
15
16
  Interactive.process(pbm)
@@ -18,13 +18,84 @@ module AtCoderFriends
18
18
 
19
19
  SampleData = Struct.new(:no, :ext, :txt)
20
20
 
21
- InputFormat = Struct.new(:container, :item, :names, :size) do
22
- def initialize(container, item, names, size = [])
23
- super(container, item, names, size)
21
+ # holds information about input format
22
+ class InputFormat
23
+ ITEM_RANK = { number: 1, decimal: 2, string: 3 }.freeze
24
+
25
+ attr_reader :container, :names, :size, :delim
26
+ attr_accessor :cols
27
+
28
+ def initialize(
29
+ container: nil,
30
+ item: nil,
31
+ names: [],
32
+ size: [],
33
+ delim: '',
34
+ cols: []
35
+ )
36
+ @container = container
37
+ @item = item
38
+ @names = names
39
+ @size = size
40
+ @delim = delim
41
+ @cols = cols
24
42
  end
25
43
 
26
44
  def to_s
27
- "#{container} #{item} #{names} #{size}"
45
+ if container == :unknown
46
+ "#{container} #{item}"
47
+ else
48
+ "#{container} #{item}(#{cols}) #{names} #{size} #{delim}"
49
+ end
50
+ end
51
+
52
+ def item
53
+ @item || cols.max_by { |k| ITEM_RANK[k] } || :number
54
+ end
55
+
56
+ def vars
57
+ tmp = @item && [@item] || cols
58
+ names.zip(tmp).map { |(name, col)| [name, col || :number] }
59
+ end
60
+
61
+ def components
62
+ @components ||=
63
+ case container
64
+ when :varray_matrix
65
+ varray_matrix_components
66
+ when :matrix_varray
67
+ matrix_varray_components
68
+ end
69
+ end
70
+
71
+ def varray_matrix_components
72
+ [
73
+ self.class.new(
74
+ container: :varray,
75
+ names: names[0..-2], size: size[0..0],
76
+ delim: delim, cols: cols[0..-2]
77
+ ),
78
+ self.class.new(
79
+ container: :matrix, item: @item,
80
+ names: names[-1..-1], size: size,
81
+ delim: delim, cols: cols[-1..-1] || []
82
+ )
83
+ ]
84
+ end
85
+
86
+ def matrix_varray_components
87
+ [
88
+ self.class.new(
89
+ container: :matrix, item: @item,
90
+ names: names[0..0], size: size,
91
+ delim: delim, cols: cols[0..0]
92
+ ),
93
+ self.class.new(
94
+ container: :varray,
95
+ names: names[1..-1], size: size[0..0],
96
+ delim: delim, cols: cols[1..-1] || []
97
+ )
98
+ ]
28
99
  end
29
100
  end
30
101
 
@@ -35,14 +106,14 @@ module AtCoderFriends
35
106
  SourceCode = Struct.new(:ext, :txt)
36
107
 
37
108
  attr_reader :q, :samples, :sources, :options
38
- attr_accessor :page, :sections, :formats, :constants
109
+ attr_accessor :page, :sections, :formats_src, :constants
39
110
 
40
111
  def initialize(q, page = Mechanize::Page.new)
41
112
  @q = q
42
113
  @page = page
43
114
  @sections = {}
44
115
  @samples = []
45
- @formats = []
116
+ @formats_src = []
46
117
  @constants = []
47
118
  @options = Options.new
48
119
  @sources = []
@@ -57,6 +128,10 @@ module AtCoderFriends
57
128
  @body_content ||= page.search('body')[0]&.content
58
129
  end
59
130
 
131
+ def formats
132
+ @formats ||= formats_src.reject { |f| f.container == :unknown }
133
+ end
134
+
60
135
  def add_smp(no, ext, txt)
61
136
  @samples << SampleData.new(no, ext, txt)
62
137
  end
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module AtCoderFriends
4
- VERSION = '0.6.2'
4
+ VERSION = '0.6.3'
5
5
  end
@@ -20,11 +20,13 @@ require 'at_coder_friends/parser/introduction_wrapper'
20
20
  require 'at_coder_friends/parser/sections'
21
21
  require 'at_coder_friends/parser/sample_data'
22
22
  require 'at_coder_friends/parser/input_format'
23
+ require 'at_coder_friends/parser/input_type'
23
24
  require 'at_coder_friends/parser/constraints'
24
25
  require 'at_coder_friends/parser/modulo'
25
26
  require 'at_coder_friends/parser/interactive'
26
27
  require 'at_coder_friends/parser/binary'
27
28
  require 'at_coder_friends/parser/main'
29
+ require 'at_coder_friends/generator/base'
28
30
  require 'at_coder_friends/generator/cxx_builtin'
29
31
  require 'at_coder_friends/generator/ruby_builtin'
30
32
  require 'at_coder_friends/generator/main'
@@ -23,23 +23,23 @@ module AtCoderFriends
23
23
  }.freeze
24
24
 
25
25
  def collect(tgt)
26
- File.open(const_log('collect', tgt), 'w') do |f|
26
+ open_const_report('collect', tgt) do |f|
27
27
  local_pbm_list.each do |contest, q, url|
28
28
  page = agent.get(url)
29
29
  body = page.body.force_encoding('utf-8')
30
30
  ms = body.scan(CONST_PAT[tgt.to_sym])
31
31
  ms.each do |m|
32
- s = m[0].delete("\r\n\t\"")
33
- f.puts [contest, q, s].join("\t")
32
+ s = m[0].delete("\r\n")
33
+ f.puts [contest, q, tsv_escape(s)].join("\t")
34
34
  end
35
35
  end
36
36
  end
37
37
  end
38
38
 
39
39
  def check_mod
40
- File.open(const_log('check', 'mod'), 'w') do |f|
40
+ open_const_report('check', 'mod') do |f|
41
41
  local_pbm_list.each do |contest, q, url|
42
- pbm = scraping_agent(nil, contest).fetch_problem(q, url)
42
+ pbm = local_scraping_agent(nil, contest).fetch_problem(q, url)
43
43
  Parser::Sections.process(pbm)
44
44
  Parser::Modulo.process(pbm)
45
45
  pbm.constants.each do |cnst|
@@ -50,9 +50,9 @@ module AtCoderFriends
50
50
  end
51
51
 
52
52
  def check_max
53
- File.open(const_log('check', 'max'), 'w') do |f|
53
+ open_const_report('check', 'max') do |f|
54
54
  local_pbm_list.each do |contest, q, url|
55
- pbm = scraping_agent(nil, contest).fetch_problem(q, url)
55
+ pbm = local_scraping_agent(nil, contest).fetch_problem(q, url)
56
56
  Parser::Sections.process(pbm)
57
57
  Parser::Constraints.process(pbm)
58
58
  pbm.constants.each do |cns|
@@ -70,10 +70,10 @@ module AtCoderFriends
70
70
  def load_merge_list(tgt)
71
71
  tbl = {}
72
72
  %w[collect check]
73
- .map { |act| const_log(act, tgt) }
73
+ .map { |act| load_const_report(act, tgt) }
74
74
  .each
75
- .with_index(1) do |file, n|
76
- list_from_file(file)
75
+ .with_index(1) do |data, n|
76
+ data
77
77
  .group_by { |contest, q, _| "#{contest}\t#{q}" }
78
78
  .map { |key, grp| [key, grp.map { |row| row[2] }.join("\n")] }
79
79
  .each do |key, txt|
@@ -85,18 +85,19 @@ module AtCoderFriends
85
85
  end
86
86
 
87
87
  def save_merge_list(tgt, tbl)
88
- File.open(const_log('merge', tgt), 'w') do |f|
88
+ open_const_report('merge', tgt) do |f|
89
89
  tbl.sort.each do |k, h|
90
90
  f.puts [k, h['v1'], h['v2']].join("\t")
91
91
  end
92
92
  end
93
93
  end
94
94
 
95
- def const_log(act, tgt)
96
- log_path("#{act}_#{tgt}.txt")
95
+ def open_const_report(act, tgt)
96
+ open_report("#{act}_#{tgt}.txt") { |f| yield f }
97
97
  end
98
98
 
99
- def list_from_file(file)
99
+ def load_const_report(act, tgt)
100
+ file = report_path("#{act}_#{tgt}.txt")
100
101
  Encoding.default_external = 'utf-8'
101
102
  CSV.read(file, col_sep: "\t", headers: false)
102
103
  end
@@ -12,11 +12,11 @@ module AtCoderFriends
12
12
  rmdir_force(emit_dir)
13
13
 
14
14
  local_pbm_list.each do |contest, q, url|
15
- pbm = scraping_agent(emit_dir, contest).fetch_problem(q, url)
15
+ pbm = local_scraping_agent(emit_dir, contest).fetch_problem(q, url)
16
16
  pipeline(pbm)
17
17
  end
18
18
 
19
- diff_log = log_path('check_diff.txt')
19
+ diff_log = report_path('check_diff.txt')
20
20
  system("diff -r --exclude=.git #{EMIT_ORG_DIR} #{emit_dir} > #{diff_log}")
21
21
  end
22
22
  end
@@ -8,28 +8,31 @@ module AtCoderFriends
8
8
  module_function
9
9
 
10
10
  def check_fmt
11
- File.open(log_path('check_fmt.txt'), 'w') do |f|
12
- local_pbm_list.sort.each do |contest, q, url|
13
- pbm = scraping_agent(nil, contest).fetch_problem(q, url)
14
- Parser::Sections.process(pbm)
15
- fmt = Parser::InputFormat.find_fmt(pbm)
16
- next unless fmt && !fmt.empty?
11
+ open_report('check_fmt.txt') do |f|
12
+ local_pbm_list.each do |contest, q, url|
13
+ next unless (res = process_fmt(contest, q, url))
17
14
 
18
- n_fmt = Parser::InputFormat.normalize_fmt(fmt).join("\n")
19
- Parser::InputFormat.process(pbm)
20
- res = pbm.formats.map(&:to_s).join("\n")
21
15
  f.puts [
22
- contest, q,
23
- tsv_escape(fmt),
24
- tsv_escape(n_fmt),
25
- tsv_escape(res)
16
+ contest, q, *res.map { |s| tsv_escape(s) }
26
17
  ].join("\t")
27
18
  end
28
19
  end
29
20
  end
30
21
 
31
- def tsv_escape(str)
32
- '"' + str.gsub('"', '""').gsub("\t", ' ') + '"'
22
+ def process_fmt(contest, q, url)
23
+ pbm = local_scraping_agent(nil, contest).fetch_problem(q, url)
24
+ Parser::Sections.process(pbm)
25
+ Parser::SampleData.process(pbm)
26
+ fmt1 = Parser::InputFormat.find_fmt(pbm)
27
+ return unless fmt1 && !fmt1.empty?
28
+
29
+ fmt2 = Parser::InputFormat.normalize_fmt(fmt1).join("\n")
30
+ Parser::InputFormat.process(pbm)
31
+ Parser::InputType.process(pbm)
32
+ inpdefs = pbm.formats_src
33
+ fmt3 = inpdefs.map(&:to_s).join("\n")
34
+ fmt4 = inpdefs.any? { |inpdef| inpdef.cols.empty? } ? '○' : ''
35
+ [fmt1, fmt2, fmt3, fmt4]
33
36
  end
34
37
  end
35
38
  end
@@ -10,7 +10,7 @@ module AtCoderFriends
10
10
 
11
11
  def check_parse
12
12
  list = local_pbm_list.map do |contest, q, url|
13
- pbm = scraping_agent(nil, contest).fetch_problem(q, url)
13
+ pbm = local_scraping_agent(nil, contest).fetch_problem(q, url)
14
14
  Parser::Main.process(pbm)
15
15
  flags = [
16
16
  !fmt?(pbm),
@@ -24,7 +24,7 @@ module AtCoderFriends
24
24
 
25
25
  def check_bin
26
26
  list = local_pbm_list.map do |contest, q, url|
27
- pbm = scraping_agent(nil, contest).fetch_problem(q, url)
27
+ pbm = local_scraping_agent(nil, contest).fetch_problem(q, url)
28
28
  Parser::Main.process(pbm)
29
29
  flags = [pbm.options.binary_values]
30
30
  [contest, q, flags]
@@ -38,11 +38,10 @@ module AtCoderFriends
38
38
  end
39
39
 
40
40
  def report(list, file)
41
- File.open(log_path(file), 'w') do |f|
41
+ open_report(file) do |f|
42
42
  list
43
43
  .select { |_, _, flags| flags.any? }
44
44
  .map { |c, q, flags| [c, q, flags.map { |flg| f_to_s(flg) }] }
45
- .sort
46
45
  .each { |args| f.puts args.flatten.join("\t") }
47
46
  end
48
47
  end
@@ -0,0 +1,46 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'net/http'
4
+ require 'uri'
5
+ require 'json'
6
+ require 'csv'
7
+
8
+ module AtCoderFriends
9
+ # tasks for regression
10
+ module Regression
11
+ module_function
12
+
13
+ CONTEST_LIST_URL = 'https://kenkoooo.com/atcoder/resources/contests.json'
14
+ ACF_HOME = File.expand_path(File.join(__dir__, '..', '..'))
15
+ REGRESSION_HOME = File.join(ACF_HOME, 'regression')
16
+ PAGES_DIR = File.join(REGRESSION_HOME, 'pages')
17
+ EMIT_ORG_DIR = File.join(REGRESSION_HOME, 'emit_org')
18
+ EMIT_DIR_FMT = File.join(REGRESSION_HOME, 'emit_%<now>s')
19
+
20
+ def contest_id_list
21
+ uri = URI.parse(CONTEST_LIST_URL)
22
+ json = Net::HTTP.get(uri)
23
+ contests = JSON.parse(json)
24
+ puts "Total #{contests.size} contests"
25
+ contests.map { |h| h['id'] }
26
+ end
27
+
28
+ def local_pbm_list
29
+ Dir.glob(PAGES_DIR + '/**/*.html').map do |pbm_path|
30
+ contest = File.basename(File.dirname(pbm_path))
31
+ q = File.basename(pbm_path, '.html')
32
+ url = "file://#{pbm_path}"
33
+ [contest, q, url]
34
+ end.sort # .take(10)
35
+ end
36
+
37
+ def pbm_list_from_file(file)
38
+ dat = File.join(REGRESSION_HOME, file)
39
+ CSV.read(dat, col_sep: "\t", headers: false).map do |contest, q|
40
+ pbm_path = File.join(PAGES_DIR, contest, "#{q}.html")
41
+ url = "file://#{pbm_path}"
42
+ [contest, q, url]
43
+ end
44
+ end
45
+ end
46
+ end
@@ -1,56 +1,26 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require 'net/http'
4
- require 'uri'
5
- require 'json'
6
- require 'csv'
7
3
  require 'mechanize'
8
4
  require 'at_coder_friends'
5
+ require_relative 'list_handler'
6
+ require_relative 'report_handler'
9
7
 
10
8
  module AtCoderFriends
11
9
  # tasks for regression
12
10
  module Regression
13
11
  module_function
14
12
 
15
- CONTEST_LIST_URL = 'https://kenkoooo.com/atcoder/resources/contests.json'
16
- ACF_HOME = File.expand_path(File.join(__dir__, '..', '..'))
17
- REGRESSION_HOME = File.join(ACF_HOME, 'regression')
18
- PAGES_DIR = File.join(REGRESSION_HOME, 'pages')
19
- EMIT_ORG_DIR = File.join(REGRESSION_HOME, 'emit_org')
20
- EMIT_DIR_FMT = File.join(REGRESSION_HOME, 'emit_%<now>s')
21
-
22
- def contest_id_list
23
- uri = URI.parse(CONTEST_LIST_URL)
24
- json = Net::HTTP.get(uri)
25
- contests = JSON.parse(json)
26
- puts "Total #{contests.size} contests"
27
- contests.map { |h| h['id'] }
28
- end
29
-
30
- def local_pbm_list
31
- Dir.glob(PAGES_DIR + '/**/*.html').map do |pbm_path|
32
- contest = File.basename(File.dirname(pbm_path))
33
- q = File.basename(pbm_path, '.html')
34
- url = "file://#{pbm_path}"
35
- [contest, q, url]
36
- end
37
- end
38
-
39
- def pbm_list_from_file(file)
40
- dat = File.join(REGRESSION_HOME, file)
41
- CSV.read(dat, col_sep: "\t", headers: false).map do |contest, q|
42
- pbm_path = File.join(PAGES_DIR, contest, "#{q}.html")
43
- url = "file://#{pbm_path}"
44
- [contest, q, url]
45
- end
46
- end
47
-
48
13
  def scraping_agent(root, contest)
49
14
  root ||= REGRESSION_HOME
50
15
  @ctx = Context.new({}, File.join(root, contest))
51
16
  @ctx.scraping_agent
52
17
  end
53
18
 
19
+ def local_scraping_agent(root, contest)
20
+ scraping_agent(root, contest)
21
+ .tap { |sa| sa.agent.pre_connect_hooks.clear }
22
+ end
23
+
54
24
  def agent
55
25
  @agent ||= Mechanize.new
56
26
  end
@@ -64,9 +34,5 @@ module AtCoderFriends
64
34
  def rmdir_force(dir)
65
35
  FileUtils.rm_r(dir) if Dir.exist?(dir)
66
36
  end
67
-
68
- def log_path(file)
69
- File.join(REGRESSION_HOME, file)
70
- end
71
37
  end
72
38
  end
@@ -0,0 +1,20 @@
1
+ # frozen_string_literal: true
2
+
3
+ module AtCoderFriends
4
+ # tasks for regression
5
+ module Regression
6
+ module_function
7
+
8
+ def open_report(file)
9
+ File.open(report_path(file), 'wb') { |f| yield f }
10
+ end
11
+
12
+ def report_path(file)
13
+ File.join(REGRESSION_HOME, file)
14
+ end
15
+
16
+ def tsv_escape(str)
17
+ '"' + str.gsub('"', '""').gsub("\t", ' ') + '"'
18
+ end
19
+ end
20
+ end
@@ -26,7 +26,7 @@ module AtCoderFriends
26
26
  end
27
27
 
28
28
  def save_section_list(list)
29
- File.open(log_path('section_list.txt'), 'w') do |f|
29
+ open_report('section_list.txt') do |f|
30
30
  list.group_by(&:title).each do |k, vs|
31
31
  f.puts [k, vs.size, vs[0].contest, vs[0].q].join("\t")
32
32
  end
@@ -1,6 +1,8 @@
1
- // /*** URL ***/
1
+ // <%= pbm.url %>
2
2
 
3
- #include <cstdio>
3
+ <%
4
+ if pbm.options.interactive
5
+ %>#include <cstdio>
4
6
  #include <vector>
5
7
  #include <string>
6
8
 
@@ -52,8 +54,37 @@ void input() {
52
54
  #ifdef DEBUG
53
55
  scanf("%s", source);
54
56
  #endif
57
+ }<%
58
+ else
59
+ %>#include <cstdio>
60
+
61
+ using namespace std;
62
+
63
+ #define REP(i,n) for(int i=0; i<(int)(n); i++)
64
+ #define FOR(i,b,e) for(int i=(b); i<=(int)(e); i++)
65
+
66
+ /*** CONSTS ***/
67
+
68
+ /*** DCLS ***/
69
+
70
+ void solve() {
71
+ <%
72
+ if (vs = pbm.options.binary_values)
73
+ %> bool cond = false;
74
+ puts(cond ? "<%= vs[0] %>" : "<%= vs[1] %>");<%
75
+ else
76
+ %> int ans = 0;
77
+ printf("%d\n", ans);<%
78
+ end
79
+ %>
55
80
  }
56
81
 
82
+ void input() {
83
+ /*** INPUTS ***/
84
+ }<%
85
+ end
86
+ %>
87
+
57
88
  int main() {
58
89
  input();
59
90
  solve();
@@ -1,6 +1,8 @@
1
- # ### URL ###
1
+ # <%= pbm.url %>
2
2
 
3
- def query(*args)
3
+ <%
4
+ if pbm.options.interactive
5
+ %>def query(*args)
4
6
  puts "? #{args.join(' ')}"
5
7
  STDOUT.flush
6
8
  if $DEBUG
@@ -31,4 +33,17 @@ if $DEBUG
31
33
  puts "query results:"
32
34
  @responses.each { |res| puts res }
33
35
  puts "----------------------------------------"
36
+ end<%
37
+ else
38
+ %>### CONSTS ###
39
+
40
+ ### DCLS ###
41
+
42
+ <%
43
+ if (vs = pbm.options.binary_values)
44
+ %>puts cond ? '<%= vs[0] %>' : '<%= vs[1] %>'<%
45
+ else
46
+ %>puts ans<%
47
+ end
34
48
  end
49
+ %>