at_coder_friends 0.6.2 → 0.6.3

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.
@@ -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
+ %>