shalmaneser-fred 1.2.0.rc4 → 1.2.rc5

Sign up to get free protection for your applications and to get access to all the features.
Files changed (68) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +47 -18
  3. data/bin/fred +8 -3
  4. data/lib/fred/FredConventions.rb +190 -189
  5. data/lib/fred/abstract_context_provider.rb +246 -0
  6. data/lib/fred/abstract_fred_feature_access.rb +43 -0
  7. data/lib/fred/answer_key_access.rb +130 -0
  8. data/lib/fred/aux_keep_writers.rb +94 -0
  9. data/lib/fred/baseline.rb +153 -0
  10. data/lib/fred/context_provider.rb +55 -0
  11. data/lib/fred/feature_extractors/fred_context_feature_extractor.rb +48 -0
  12. data/lib/fred/feature_extractors/fred_context_pos_feature_extractor.rb +48 -0
  13. data/lib/fred/feature_extractors/fred_feature_extractor.rb +50 -0
  14. data/lib/fred/feature_extractors/fred_ngram_feature_extractor.rb +65 -0
  15. data/lib/fred/feature_extractors/fred_syn_feature_extractor.rb +33 -0
  16. data/lib/fred/feature_extractors/fred_synsem_feature_extractor.rb +32 -0
  17. data/lib/fred/feature_extractors.rb +5 -0
  18. data/lib/fred/file_zipped.rb +43 -0
  19. data/lib/fred/find_all_targets.rb +94 -0
  20. data/lib/fred/find_targets_from_frames.rb +92 -0
  21. data/lib/fred/fred.rb +43 -40
  22. data/lib/fred/fred_error.rb +15 -0
  23. data/lib/fred/fred_eval.rb +311 -0
  24. data/lib/fred/fred_feature_access.rb +420 -0
  25. data/lib/fred/fred_feature_info.rb +56 -0
  26. data/lib/fred/fred_featurize.rb +525 -0
  27. data/lib/fred/fred_parameters.rb +190 -0
  28. data/lib/fred/fred_split.rb +86 -0
  29. data/lib/fred/fred_split_pkg.rb +189 -0
  30. data/lib/fred/fred_test.rb +571 -0
  31. data/lib/fred/fred_train.rb +125 -0
  32. data/lib/fred/grammatical_function_access.rb +63 -0
  33. data/lib/fred/md5.rb +6 -0
  34. data/lib/fred/meta_feature_access.rb +185 -0
  35. data/lib/fred/non_contiguous_context_provider.rb +532 -0
  36. data/lib/fred/opt_parser.rb +182 -161
  37. data/lib/fred/plot_and_r_eval.rb +486 -0
  38. data/lib/fred/single_sent_context_provider.rb +76 -0
  39. data/lib/fred/slide_var.rb +148 -0
  40. data/lib/fred/targets.rb +136 -0
  41. data/lib/fred/toggle_var.rb +61 -0
  42. data/lib/fred/word_lemma_pos_ne.rb +51 -0
  43. data/lib/fred/write_features_binary.rb +95 -0
  44. data/lib/fred/write_features_nary.rb +51 -0
  45. data/lib/fred/write_features_nary_or_binary.rb +51 -0
  46. data/lib/shalmaneser/fred.rb +1 -0
  47. metadata +57 -30
  48. data/lib/fred/Baseline.rb +0 -150
  49. data/lib/fred/FileZipped.rb +0 -31
  50. data/lib/fred/FredBOWContext.rb +0 -877
  51. data/lib/fred/FredDetermineTargets.rb +0 -319
  52. data/lib/fred/FredEval.rb +0 -312
  53. data/lib/fred/FredFeatureExtractors.rb +0 -322
  54. data/lib/fred/FredFeatures.rb +0 -1061
  55. data/lib/fred/FredFeaturize.rb +0 -602
  56. data/lib/fred/FredNumTrainingSenses.rb +0 -27
  57. data/lib/fred/FredParameters.rb +0 -402
  58. data/lib/fred/FredSplit.rb +0 -84
  59. data/lib/fred/FredSplitPkg.rb +0 -180
  60. data/lib/fred/FredTest.rb +0 -606
  61. data/lib/fred/FredTrain.rb +0 -144
  62. data/lib/fred/PlotAndREval.rb +0 -480
  63. data/lib/fred/fred_config_data.rb +0 -185
  64. data/test/frprep/test_opt_parser.rb +0 -94
  65. data/test/functional/functional_test_helper.rb +0 -58
  66. data/test/functional/test_fred.rb +0 -47
  67. data/test/functional/test_frprep.rb +0 -99
  68. data/test/functional/test_rosy.rb +0 -40
@@ -1,185 +0,0 @@
1
- # FredConfigData
2
- # Katrin Erk April 05
3
- #
4
- # Frame disambiguation system:
5
- # access to a configuration and experiment description file
6
-
7
- require "common/config_data"
8
-
9
- ##############################
10
- # Class FredConfigData
11
- #
12
- # inherits from ConfigData,
13
- # sets variable names appropriate to WSD task
14
-
15
- class FredConfigData < ConfigData
16
- CONFIG_DEFS = {
17
- "experiment_ID" => "string", # experiment ID
18
- "enduser_mode" => "bool", # work in enduser mode? (disallowing many things)
19
-
20
- "preproc_descr_file_train" => "string", # path to preprocessing files
21
- "preproc_descr_file_test" => "string",
22
- "directory_output" => "string", # path to Salsa/Tiger XML output directory
23
-
24
- "verbose" => "bool" , # print diagnostic messages?
25
- "apply_to_all_known_targets" => "bool", # apply to all known targets rather than the ones with a frame?
26
-
27
- "fred_directory" => "string",# directory for internal info
28
- "classifier_dir" => "string", # write classifiers here
29
-
30
- "classifier" => "list", # classifiers
31
-
32
- "dbtype" => "string", # "mysql" or "sqlite"
33
-
34
- "host" => "string", # DB access: sqlite only
35
- "user" => "string",
36
- "passwd" => "string",
37
- "dbname" => "string",
38
-
39
- # featurization info
40
- "feature" => "list", # which features to use for the classifier?
41
- "binary_classifiers" => "bool",# make binary rather than n-ary clasifiers?
42
- "negsense" => "string", # binary classifier: negative sense is..?
43
- "numerical_features" => "string", # do what with numerical features?
44
-
45
- # what to do with items that have multiple senses?
46
- # 'binarize': binary classifiers, and consider positive
47
- # if the sense is among the gold senses
48
- # 'join' : make one joint sense
49
- # 'repeat' : make multiple occurrences of the item, one sense per occ
50
- # 'keep' : keep as separate labels
51
- #
52
- # multilabel: consider as assigned all labels
53
- # above a certain confidence threshold?
54
- "handle_multilabel" => "string",
55
- "assignment_confidence_threshold" => "float",
56
-
57
- # single-sentence context?
58
- "single_sent_context" => "bool",
59
-
60
- # noncontiguous input? then we need access to a larger corpus
61
- "noncontiguous_input" => "bool",
62
- "larger_corpus_dir" => "string",
63
- "larger_corpus_format" => "string",
64
- "larger_corpus_encoding" => "string",
65
- # Imported from PrepConfigData
66
- 'do_postag' => 'bool',
67
- 'do_lemmatize' => 'bool',
68
- 'do_parse' => 'bool',
69
- 'pos_tagger' => 'string',
70
- 'lemmatizer' => 'string',
71
- 'parser' => 'string',
72
- 'directory_preprocessed' => 'string',
73
- 'language' => 'string'
74
- }
75
-
76
- def initialize(filename)
77
-
78
- super(filename, CONFIG_DEFS, ["train", "exp_ID"])
79
-
80
- # set access functions for list features
81
- set_list_feature_access("classifier", method("access_classifier"))
82
- set_list_feature_access("feature", method("access_feature"))
83
- end
84
-
85
- ###
86
- # protected
87
-
88
- #####
89
- # access_feature
90
- #
91
- # access function for feature 'feature'
92
- #
93
- # assumed format:
94
- #
95
- # feature = context 50
96
- # feature = context 2
97
- # feature = syn
98
- #
99
- # i.e. first the name of the feature type to use, then
100
- # optionally a parameter,
101
- # and the same feature can occur more than once (which makes sense
102
- # only in case of parameters)
103
- #
104
- #
105
- # returns:
106
- # - If a feature is given as a parameter,
107
- # - If the feature is not set in the experiment file, nil
108
- # - If the feature is set and has a parameter, the list of
109
- # parameter values set for it. It is assumed that the parameters
110
- # are integers, and they are returned as integers
111
- # - If the feature is set and has no parameter, true
112
- # - If no feature is given as parameter:
113
- # a list of all features that have been set in the experiment file
114
- # Each feature is given as a tuple: the first element is the feature (a string),
115
- # all further elements are options (integers)
116
- def access_feature(val_list, # array:array:string: list of tuples defined in config file
117
- # for feature 'feature'
118
- feature=nil) # string: feature type name
119
-
120
- if feature
121
- # access options for this feature
122
-
123
- # get the right tuples
124
- positives = val_list.select { |entries|
125
- entries.first() == feature
126
- }.map { |entries|
127
- entries[1]
128
- }
129
-
130
- if positives.empty?
131
- # feature not defined
132
- return nil
133
-
134
- elsif positives.compact().empty?
135
- # feature defined, but no parameters
136
- return true
137
-
138
- else
139
- # feature defined, and has values
140
- return positives.map { |par| par.to_i() }
141
- end
142
-
143
- else
144
- # return all features that have been set
145
- return val_list.map { |feature_name, *options|
146
- [feature_name] + options.map { |o| o.to_i() }
147
- }
148
- end
149
- end
150
-
151
- #####
152
- # access_classifier
153
- #
154
- # access function for feature 'classifier'
155
- #
156
- # assumed format in the config file:
157
- #
158
- # feature = path [option]*
159
- #
160
- # i.e. first the name of the feature type to use, then
161
- # optionally options associated with that feature,
162
- # e.g. 'argrec': use that feature only when computing argrec
163
- #
164
- # the access function is called with parameter val_list, an array of
165
- # string tuples, one string tuple for each feature defined.
166
- # the first string in the tuple is the feature name, the rest are the options
167
- #
168
- # returns: a list of pairs [feature_name(string), options(array:string)]
169
- # of defined features
170
- # @param val_list [Array] array:array:string: list of tuples defined
171
- # in config file for feature 'feature'
172
- def access_classifier(val_list)
173
- if val_list.nil?
174
- []
175
- else
176
- val_list.map do |cl_descr_tuple|
177
- [cl_descr_tuple.first, cl_descr_tuple[1..-1]]
178
- end
179
- end
180
- end
181
-
182
- end
183
-
184
-
185
-
@@ -1,94 +0,0 @@
1
- # -*- coding: utf-8 -*-
2
-
3
- require 'test/unit'
4
- require 'stringio' # for helper methods
5
- require 'frprep/opt_parser'
6
-
7
- include FrPrep
8
-
9
- class TestOptParser < Test::Unit::TestCase
10
-
11
- def setup
12
- @exp_file = 'test/frprep/data/prp_test.salsa'
13
- @valid_opts = ['--expfile', @exp_file,
14
- '--help'
15
- ]
16
- end
17
-
18
- def test_public_methods
19
- assert_respond_to(OptParser, :parse)
20
- end
21
-
22
- # It should return a FrPrepConfigData object.
23
- def test_parse_method
24
- input = ['-e', @exp_file]
25
- return_value = OptParser.parse(input)
26
- assert(return_value.instance_of?(FrPrepConfigData))
27
- end
28
-
29
- # It should reject the empty input and exit.
30
- def test_empty_input
31
- out, err = intercept_output do
32
- assert_raises(SystemExit) { OptParser.parse([]) }
33
- end
34
- assert_match(/You have to provide some options./, err)
35
- end
36
-
37
- # It should accept correct options.
38
- # Invalid options is the matter of OptionParser itself,
39
- # do not test it here.
40
- # We test only, that OP exits and does not raise an exception.
41
- def test_accept_correct_options
42
- # this options we should treat separately
43
- @valid_opts.delete('--help')
44
- assert_nothing_raised { OptParser.parse(@valid_opts) }
45
-
46
- stdout, stderr = intercept_output do
47
- assert_raises(SystemExit) { OptParser.parse(['--invalid-option']) }
48
- end
49
-
50
- assert_match(/You have provided an invalid option:/, stderr)
51
- end
52
-
53
- # It should successfully exit with some options.
54
- def test_successful_exit
55
- quietly do
56
- success_args = ['-h', '--help']
57
- success_args.each do |arg|
58
- assert_raises(SystemExit) { OptParser.parse(arg.split) }
59
- end
60
- end
61
- end
62
-
63
- end
64
- ################################################################################
65
- # It is a helper method, many testable units provide some verbose output
66
- # to stderr and/or stdout. It is usefull to suppress any kind of verbosity.
67
- def quietly(&b)
68
- begin
69
- orig_stderr = $stderr.clone
70
- orig_stdout = $stdout.clone
71
- $stderr.reopen(File.new('/dev/null', 'w'))
72
- $stdout.reopen(File.new('/dev/null', 'w'))
73
- b.call
74
- ensure
75
- $stderr.reopen(orig_stderr)
76
- $stdout.reopen(orig_stdout)
77
- end
78
- end
79
-
80
- # It is a helper method for handling stdout and stderr as strings.
81
- def intercept_output
82
- orig_stdout = $stdout
83
- orig_stderr = $stderr
84
- $stdout = StringIO.new
85
- $stderr = StringIO.new
86
-
87
- yield
88
-
89
- return $stdout.string, $stderr.string
90
- ensure
91
- $stdout = orig_stdout
92
- $stderr = orig_stderr
93
- end
94
-
@@ -1,58 +0,0 @@
1
- require 'erb'
2
-
3
-
4
- # Setting $DEBUG will produce all external output.
5
- # Otherwise it is suppreced.
6
- module FunctionalTestHelper
7
- PREF = 'test/functional/sample_experiment_files'
8
-
9
- PRP_TEST_FILE = "#{PREF}/prp_test.salsa"
10
- PRP_TEST_FILE_FRED_STD = "#{PREF}/prp_test.salsa.fred.standalone"
11
- PRP_TEST_FILE_ROSY_STD = "#{PREF}/prp_test.salsa.rosy.standalone"
12
- PRP_TRAIN_FILE = "#{PREF}/prp_train.salsa"
13
- PRP_TRAIN_FILE_FRED_STD = "#{PREF}/prp_train.salsa.fred.standalone"
14
- PRP_TRAIN_FILE_ROSY_STD = "#{PREF}/prp_train.salsa.rosy.standalone"
15
-
16
- FRED_TEST_FILE = 'test/functional/sample_experiment_files/fred_test.salsa'
17
- FRED_TRAIN_FILE = 'test/functional/sample_experiment_files/fred_train.salsa'
18
- ROSY_TEST_FILE = 'test/functional/sample_experiment_files/rosy_test.salsa'
19
- ROSY_TRAIN_FILE = 'test/functional/sample_experiment_files/rosy_train.salsa'
20
-
21
- # Testing input for Preprocessor.
22
- PRP_PLAININPUT = "#{PREF}/prp_plaininput"
23
- PRP_STXMLINPUT = "#{PREF}/prp_stxmlinput"
24
- PRP_TABINPUT = "#{PREF}/prp_tabinput"
25
- PRP_FNXMLINPUT = "#{PREF}/prp_fnxmlinput"
26
- PRP_FNCORPUSXMLINPUT = "#{PREF}/prp_fncorpusxmlinput"
27
-
28
- # Testing output for Preprocessor.
29
- PRP_STXMLOUTPUT = "#{PREF}/prp_stxmloutput"
30
- PRP_TABOUTPUT = "#{PREF}/prp_taboutput"
31
-
32
- # Run an external process for functional testing and check the return code.
33
- # <system> returns <true> if the external code exposes no errors.
34
- # <@msg> is defined for every test object.
35
- # @param cmd [String]
36
- def execute(cmd)
37
- unless $DEBUG
38
- cmd = cmd + ' 1>/dev/null 2>&1'
39
- end
40
- status = system(cmd)
41
- assert(status, @msg)
42
- end
43
-
44
- # Create a temporary exp file only for this test.
45
- # Shalmaneser needs absolute paths, we provide them in exp files
46
- # using templating.
47
- def create_exp_file(file)
48
- template = File.read("#{file}.erb")
49
- text = ERB.new(template).result
50
- File.open(file, 'w') do |f|
51
- f.write(text)
52
- end
53
- end
54
-
55
- def remove_exp_file(file)
56
- File.delete(file)
57
- end
58
- end
@@ -1,47 +0,0 @@
1
- # -*- encoding: utf-8 -*-
2
-
3
- require 'test/unit'
4
- require 'functional/functional_test_helper'
5
-
6
- class TestFred < Test::Unit::TestCase
7
-
8
- include FunctionalTestHelper
9
-
10
- def setup
11
- @msg = "Fred is doing bad, you've just broken something!"
12
- @test_file = FRED_TEST_FILE
13
- @train_file = FRED_TRAIN_FILE
14
- end
15
-
16
- def test_fred_testing_featurization
17
- create_exp_file(@test_file)
18
- create_exp_file(PRP_TEST_FILE_FRED_STD)
19
- execute("ruby -I lib bin/fred -t featurize -e #{@test_file} -d test")
20
- remove_exp_file(@test_file)
21
- remove_exp_file(PRP_TEST_FILE_FRED_STD)
22
- end
23
-
24
- def test_fred_testing_tests
25
- create_exp_file(@test_file)
26
- create_exp_file(PRP_TEST_FILE_FRED_STD)
27
- execute("ruby -I lib bin/fred -t test -e #{@test_file}")
28
- remove_exp_file(@test_file)
29
- remove_exp_file(PRP_TEST_FILE_FRED_STD)
30
- end
31
-
32
- def test_fred_training_featurization
33
- create_exp_file(@train_file)
34
- create_exp_file(PRP_TRAIN_FILE_FRED_STD)
35
- execute("ruby -I lib bin/fred -t featurize -e #{@train_file} -d train")
36
- remove_exp_file(@train_file)
37
- remove_exp_file(PRP_TRAIN_FILE_FRED_STD)
38
- end
39
-
40
- def test_fred_training_train
41
- create_exp_file(@train_file)
42
- create_exp_file(PRP_TRAIN_FILE_FRED_STD)
43
- execute("ruby -I lib bin/fred -t train -e #{@train_file}")
44
- remove_exp_file(@train_file)
45
- remove_exp_file(PRP_TRAIN_FILE_FRED_STD)
46
- end
47
- end
@@ -1,99 +0,0 @@
1
- # -*- encoding: utf-8 -*-
2
-
3
- require 'test/unit'
4
- require 'functional/functional_test_helper'
5
- #require 'fileutils' # File.delete(), File.rename(), File.symlink()
6
-
7
- class TestFrprep < Test::Unit::TestCase
8
-
9
- include FunctionalTestHelper
10
-
11
- def setup
12
- @msg = "FrPrep is doing bad, you've just broken something!"
13
- @test_file = PRP_TEST_FILE
14
- @train_file = PRP_TRAIN_FILE
15
- @ptb = 'lib/frprep/interfaces/berkeley_interface.rb'
16
- #link_berkeley
17
- ENV['SHALM_BERKELEY_MODEL'] = 'sc_dash_labeled_1_smoothing.gr'
18
- end
19
-
20
- def teardown
21
- #unlink_berkeley
22
- end
23
- def test_frprep_testing
24
- create_exp_file(@test_file)
25
- execute("ruby -I lib bin/frprep -e #{@test_file}")
26
- remove_exp_file(@test_file)
27
- end
28
-
29
- def test_frprep_training
30
- create_exp_file(@train_file)
31
- execute("ruby -I lib bin/frprep -e #{@train_file}")
32
- remove_exp_file(@train_file)
33
- end
34
-
35
- # Testing input in different formats.
36
- def test_frprep_plaininput
37
- create_exp_file(PRP_PLAININPUT)
38
- execute("ruby -I lib bin/frprep -e #{PRP_PLAININPUT}")
39
- remove_exp_file(PRP_PLAININPUT)
40
- end
41
-
42
- def test_frprep_stxmlinput
43
- create_exp_file(PRP_STXMLINPUT)
44
- execute("ruby -I lib bin/frprep -e #{PRP_STXMLINPUT}")
45
- remove_exp_file(PRP_STXMLINPUT)
46
- end
47
-
48
- def test_frprep_tabinput
49
- create_exp_file(PRP_TABINPUT)
50
- execute("ruby -I lib bin/frprep -e #{PRP_TABINPUT}")
51
- remove_exp_file(PRP_TABINPUT)
52
- end
53
-
54
- def test_frprep_fncorpusxmlinput
55
- create_exp_file(PRP_FNCORPUSXMLINPUT)
56
- execute("ruby -I lib bin/frprep -e #{PRP_FNCORPUSXMLINPUT}")
57
- remove_exp_file(PRP_FNCORPUSXMLINPUT)
58
- end
59
-
60
- def test_frprep_fnxmlinput
61
- create_exp_file(PRP_FNXMLINPUT)
62
- execute("ruby -I lib bin/frprep -e #{PRP_FNXMLINPUT}")
63
- remove_exp_file(PRP_FNXMLINPUT)
64
- end
65
-
66
- # Testing output in different formats.
67
- # We test only on German input assuming English input to work.
68
- def test_frprep_stxmloutput
69
- create_exp_file(PRP_STXMLOUTPUT)
70
- execute("ruby -I lib bin/frprep -e #{PRP_STXMLOUTPUT}")
71
- remove_exp_file(PRP_STXMLOUTPUT)
72
- end
73
-
74
- def test_frprep_taboutput
75
- create_exp_file(PRP_TABOUTPUT)
76
- execute("ruby -I lib bin/frprep -e #{PRP_TABOUTPUT}")
77
- remove_exp_file(PRP_TABOUTPUT)
78
- end
79
-
80
-
81
- private
82
- # Berkeley Parser takes a long time which is bad for testing.
83
- # We ran it once and reuse the result file in our tests.
84
- # Before every test we link the Berkeley interface to a stub
85
- # with the BP invocation switched off.
86
- def link_berkeley
87
- File.rename(@ptb, "#{@ptb}.bak")
88
- File.symlink(
89
- File.expand_path('test/functional/berkeley_interface.rb.stub'),
90
- File.expand_path(@ptb)
91
- )
92
- end
93
-
94
- # After testing we bring the right interface back, the program remains intact.
95
- def unlink_berkeley
96
- File.delete(@ptb)
97
- File.rename("#{@ptb}.bak", @ptb)
98
- end
99
- end
@@ -1,40 +0,0 @@
1
- # -*- encoding: utf-8 -*-
2
-
3
- require 'test/unit'
4
- require 'functional/functional_test_helper'
5
-
6
- class TestRosy < Test::Unit::TestCase
7
- include FunctionalTestHelper
8
-
9
- def setup
10
- @msg = "Rosy is doing bad, you've just broken something!"
11
- end
12
-
13
- def test_rosy_testing
14
- create_exp_file(ROSY_TEST_FILE)
15
- create_exp_file(PRP_TEST_FILE_ROSY_STD)
16
- execute("ruby -rubygems -I lib bin/rosy -t featurize -e #{ROSY_TEST_FILE} -d test")
17
- execute("ruby -rubygems -I lib bin/rosy -t test -e #{ROSY_TEST_FILE}")
18
- remove_exp_file(ROSY_TEST_FILE)
19
- remove_exp_file(PRP_TEST_FILE_ROSY_STD)
20
- end
21
-
22
- def test_rosy_training
23
- create_exp_file(ROSY_TRAIN_FILE)
24
- create_exp_file(PRP_TRAIN_FILE_ROSY_STD)
25
- execute("ruby -rubygems -I lib bin/rosy -t featurize -e #{ROSY_TRAIN_FILE} -d train")
26
- execute("ruby -rubygems -I lib bin/rosy -t train -e #{ROSY_TRAIN_FILE} -s argrec")
27
- execute("ruby -rubygems -I lib bin/rosy -t train -e #{ROSY_TRAIN_FILE} -s arglab")
28
- remove_exp_file(ROSY_TRAIN_FILE)
29
- remove_exp_file(PRP_TRAIN_FILE_ROSY_STD)
30
- end
31
-
32
- def test_rosy_training_onestep
33
- create_exp_file(ROSY_TRAIN_FILE)
34
- create_exp_file(PRP_TRAIN_FILE_ROSY_STD)
35
- execute("ruby -rubygems -I lib bin/rosy -t featurize -e #{ROSY_TRAIN_FILE} -d train")
36
- execute("ruby -rubygems -I lib bin/rosy -t train -e #{ROSY_TRAIN_FILE} -s onestep")
37
- remove_exp_file(ROSY_TRAIN_FILE)
38
- remove_exp_file(PRP_TRAIN_FILE_ROSY_STD)
39
- end
40
- end