From fd6f8a13bd780b50cbfee310a9e1c1645713fbe5 Mon Sep 17 00:00:00 2001 From: Brian Temple Date: Sat, 14 Sep 2013 15:56:25 -0600 Subject: [PATCH 1/2] Initial check-in of solution. --- .rspec | 2 ++ lib/callback_handler.rb | 30 +++++++++++++++++++++ lib/line_counter.rb | 26 ++++++++++++++++++ lib/shakespeare_analyzer.rb | 6 +++++ output.txt | 41 ++++++++++++++++++++++++++++ spec/callback_handler_spec.rb | 30 +++++++++++++++++++++ spec/empty_file.xml | 0 spec/line_counter_spec.rb | 51 +++++++++++++++++++++++++++++++++++ spec/multiple_speakers.xml | 28 +++++++++++++++++++ spec/one_speaker.xml | 8 ++++++ spec/spec_helper.rb | 17 ++++++++++++ 11 files changed, 239 insertions(+) create mode 100644 .rspec create mode 100644 lib/callback_handler.rb create mode 100644 lib/line_counter.rb create mode 100644 lib/shakespeare_analyzer.rb create mode 100644 output.txt create mode 100644 spec/callback_handler_spec.rb create mode 100644 spec/empty_file.xml create mode 100644 spec/line_counter_spec.rb create mode 100644 spec/multiple_speakers.xml create mode 100644 spec/one_speaker.xml create mode 100644 spec/spec_helper.rb diff --git a/.rspec b/.rspec new file mode 100644 index 0000000..5f16476 --- /dev/null +++ b/.rspec @@ -0,0 +1,2 @@ +--color +--format progress diff --git a/lib/callback_handler.rb b/lib/callback_handler.rb new file mode 100644 index 0000000..950291b --- /dev/null +++ b/lib/callback_handler.rb @@ -0,0 +1,30 @@ +require 'nokogiri' +require 'open-uri' +require_relative 'line_counter' + +include Nokogiri + +class CallbackHandler < XML::SAX::Document + def initialize + @counter = LineCounter.new + @parsing_speaker = false + end + + def results + @counter.count + end + + def start_element(element, attributes) + @counter.count_line if element == 'LINE' + @counter.new_speech if element == 'SPEECH' + @parsing_speaker = (element == 'SPEAKER') + end + + def end_element(element) + @parsing_speaker = false if element == 'SPEAKER' + end + + def characters string + @counter.add_speaker string if @parsing_speaker + end +end diff --git a/lib/line_counter.rb b/lib/line_counter.rb new file mode 100644 index 0000000..0418ba1 --- /dev/null +++ b/lib/line_counter.rb @@ -0,0 +1,26 @@ +class LineCounter + def initialize + @speaker = [] + @count = Hash.new(0) + end + + def count + @count + end + + def add_speaker(name) + @speaker.push name + end + + def count_line + @speaker.each do |name| + @count[name] += 1 + end + + raise ArgumentError, 'Unable to count lines without a speaker.' if @speaker.empty? + end + + def new_speech + @speaker = [] + end +end diff --git a/lib/shakespeare_analyzer.rb b/lib/shakespeare_analyzer.rb new file mode 100644 index 0000000..9771324 --- /dev/null +++ b/lib/shakespeare_analyzer.rb @@ -0,0 +1,6 @@ +require_relative 'callback_handler' + +handler = CallbackHandler.new +XML::SAX::Parser.new(handler).parse_io(open("http://www.ibiblio.org/xml/examples/shakespeare/macbeth.xml")) +results = handler.results.sort_by {|k,v| v}.reverse +results.each {|k,v| puts " #{v} #{k.capitalize}" } diff --git a/output.txt b/output.txt new file mode 100644 index 0000000..b129c3e --- /dev/null +++ b/output.txt @@ -0,0 +1,41 @@ + 719 Macbeth + 265 Lady macbeth + 212 Malcolm + 180 Macduff + 135 Ross + 113 Banquo + 74 Lennox + 70 Duncan + 62 First witch + 46 Porter + 45 Doctor + 41 Lady macduff + 39 Hecate + 35 Sergeant + 30 First murderer + 30 Siward + 27 Third witch + 27 Second witch + 24 All + 23 Gentlewoman + 23 Messenger + 21 Lord + 21 Angus + 20 Son + 15 Second murderer + 12 Menteith + 11 Old man + 11 Caithness + 10 Donalbain + 8 Third murderer + 7 Young siward + 5 Third apparition + 5 Seyton + 5 Servant + 4 Second apparition + 3 Lords + 2 First apparition + 2 Fleance + 2 Both murderers + 1 Attendant + 1 Soldiers diff --git a/spec/callback_handler_spec.rb b/spec/callback_handler_spec.rb new file mode 100644 index 0000000..30b620e --- /dev/null +++ b/spec/callback_handler_spec.rb @@ -0,0 +1,30 @@ +require 'callback_handler' + +describe CallbackHandler do + it "correctly parses an empty file" do + handler = CallbackHandler.new + parser = XML::SAX::Parser.new(handler) + parser.parse_file("spec/empty_file.xml") + handler.results.keys.should be_empty + end + + it "correctly parses a file with only one speaker" do + handler = CallbackHandler.new + parser = XML::SAX::Parser.new(handler) + parser.parse_file("spec/one_speaker.xml") + handler.results.keys.should_not be_empty + handler.results["MACBETH"].should eq(1) + end + + it "correctly parses a file with multiple speakers" do + handler = CallbackHandler.new + parser = XML::SAX::Parser.new(handler) + parser.parse_file("spec/multiple_speakers.xml") + handler.results.keys.should_not be_empty + + handler.results["LENNOX"].should eq(1) + handler.results["MACBETH"].should eq(2) + handler.results["MACDUFF"].should eq(6) + end + +end diff --git a/spec/empty_file.xml b/spec/empty_file.xml new file mode 100644 index 0000000..e69de29 diff --git a/spec/line_counter_spec.rb b/spec/line_counter_spec.rb new file mode 100644 index 0000000..9926043 --- /dev/null +++ b/spec/line_counter_spec.rb @@ -0,0 +1,51 @@ +require 'line_counter' + +describe LineCounter, "#count" do + it "returns an empty list when no lines are counted" do + counter = LineCounter.new + counter.count.should be_empty + end + + it "increments the current speaker when a speaker is added" do + counter = LineCounter.new + counter.add_speaker "Macbeth" + + counter.count_line + counter.count["Macbeth"].should eq(1) + + counter.count_line + counter.count["Macbeth"].should eq(2) + end + + it "throws exception when a line is counted without a speaker" do + counter = LineCounter.new + expect {counter.count_line}.to raise_error(ArgumentError) + end + + it "allows two people to speak at the same time" do + counter = LineCounter.new + counter.add_speaker "Macbeth" + counter.add_speaker "Banquo" + + counter.count_line + counter.count["Macbeth"].should eq(1) + counter.count["Banquo"].should eq(1) + + counter.count_line + counter.count["Macbeth"].should eq(2) + counter.count["Banquo"].should eq(2) + end + + it "does not count lines from speakers of previous speeches" do + counter = LineCounter.new + counter.add_speaker "Macbeth" + + counter.new_speech + + counter.add_speaker "Duncan" + counter.count_line + + counter.count["Duncan"].should eq(1) + counter.count.key?("Macbeth").should be_false + end +end diff --git a/spec/multiple_speakers.xml b/spec/multiple_speakers.xml new file mode 100644 index 0000000..01a72b5 --- /dev/null +++ b/spec/multiple_speakers.xml @@ -0,0 +1,28 @@ + + + + +MACDUFF +O horror, horror, horror! Tongue nor heart +Cannot conceive nor name thee! + + + +MACBETH +LENNOX +What's the matter. + + + +MACDUFF +Confusion now hath made his masterpiece! +Most sacrilegious murder hath broke ope +The Lord's anointed temple, and stole thence +The life o' the building! + + + +MACBETH +What is 't you say? the life? + + diff --git a/spec/one_speaker.xml b/spec/one_speaker.xml new file mode 100644 index 0000000..7f5babf --- /dev/null +++ b/spec/one_speaker.xml @@ -0,0 +1,8 @@ + + + + +MACBETH +Methought I heard a voice cry 'Sleep no more! + + diff --git a/spec/spec_helper.rb b/spec/spec_helper.rb new file mode 100644 index 0000000..dbc4f1a --- /dev/null +++ b/spec/spec_helper.rb @@ -0,0 +1,17 @@ +# This file was generated by the `rspec --init` command. Conventionally, all +# specs live under a `spec` directory, which RSpec adds to the `$LOAD_PATH`. +# Require this file using `require "spec_helper"` to ensure that it is only +# loaded once. +# +# See http://rubydoc.info/gems/rspec-core/RSpec/Core/Configuration +RSpec.configure do |config| + config.treat_symbols_as_metadata_keys_with_true_values = true + config.run_all_when_everything_filtered = true + config.filter_run :focus + + # Run specs in random order to surface order dependencies. If you find an + # order dependency and want to debug it, you can fix the order by providing + # the seed, which is printed after each run. + # --seed 1234 + config.order = 'random' +end From 91f6fead8d1d7cd7ea863864f6f646103fea4a57 Mon Sep 17 00:00:00 2001 From: Brian Temple Date: Sun, 15 Sep 2013 20:05:34 -0600 Subject: [PATCH 2/2] Have scenes where the speaker is "ALL" increment all speakers in the scene instead of a speaker named "ALL". --- lib/callback_handler.rb | 1 + lib/line_counter.rb | 18 +++++-- output.txt | 41 ++++++++------- spec/all_speaker.xml | 54 ++++++++++++++++++++ spec/callback_handler_spec.rb | 40 +++++++++------ spec/line_counter_spec.rb | 93 +++++++++++++++++++++++++---------- 6 files changed, 181 insertions(+), 66 deletions(-) create mode 100644 spec/all_speaker.xml diff --git a/lib/callback_handler.rb b/lib/callback_handler.rb index 950291b..f84b9e7 100644 --- a/lib/callback_handler.rb +++ b/lib/callback_handler.rb @@ -17,6 +17,7 @@ def results def start_element(element, attributes) @counter.count_line if element == 'LINE' @counter.new_speech if element == 'SPEECH' + @counter.new_scene if element == 'SCENE' @parsing_speaker = (element == 'SPEAKER') end diff --git a/lib/line_counter.rb b/lib/line_counter.rb index 0418ba1..1dc4538 100644 --- a/lib/line_counter.rb +++ b/lib/line_counter.rb @@ -1,6 +1,9 @@ +require 'set' + class LineCounter def initialize @speaker = [] + @scene_speakers = Set.new @count = Hash.new(0) end @@ -10,17 +13,26 @@ def count def add_speaker(name) @speaker.push name + @scene_speakers.add name unless name == 'ALL' end def count_line - @speaker.each do |name| + names = (@speaker.include?("ALL")) ? @scene_speakers : @speaker + increment_names(names) + raise ArgumentError, 'Unable to count lines without a speaker.' if @speaker.empty? + end + + def increment_names(names) + names.each do |name| @count[name] += 1 end - - raise ArgumentError, 'Unable to count lines without a speaker.' if @speaker.empty? end def new_speech @speaker = [] end + + def new_scene + @scene_speakers = Set.new + end end diff --git a/output.txt b/output.txt index b129c3e..fe90e63 100644 --- a/output.txt +++ b/output.txt @@ -1,41 +1,40 @@ - 719 Macbeth - 265 Lady macbeth - 212 Malcolm - 180 Macduff - 135 Ross - 113 Banquo - 74 Lennox + 729 Macbeth + 267 Lady macbeth + 215 Malcolm + 183 Macduff + 136 Ross + 115 Banquo + 83 First witch + 76 Lennox 70 Duncan - 62 First witch - 46 Porter + 48 Porter + 48 Third witch + 48 Second witch + 46 Hecate 45 Doctor 41 Lady macduff - 39 Hecate 35 Sergeant + 31 Siward 30 First murderer - 30 Siward - 27 Third witch - 27 Second witch - 24 All 23 Gentlewoman 23 Messenger - 21 Lord 21 Angus + 21 Lord 20 Son 15 Second murderer + 12 Donalbain 12 Menteith 11 Old man 11 Caithness - 10 Donalbain + 8 Third apparition + 8 Second apparition 8 Third murderer 7 Young siward - 5 Third apparition - 5 Seyton + 6 First apparition 5 Servant - 4 Second apparition + 5 Seyton 3 Lords - 2 First apparition 2 Fleance 2 Both murderers - 1 Attendant 1 Soldiers + 1 Attendant diff --git a/spec/all_speaker.xml b/spec/all_speaker.xml new file mode 100644 index 0000000..db72196 --- /dev/null +++ b/spec/all_speaker.xml @@ -0,0 +1,54 @@ + + + + + +SCENE I. A desert place. +Thunder and lightning. Enter three Witches + +First Witch +When shall we three meet again +In thunder, lightning, or in rain? + + +Second Witch +When the hurlyburly's done, +When the battle's lost and won. + + +Third Witch +That will be ere the set of sun. + + +First Witch +Where the place? + + +Second Witch +Upon the heath. + + +Third Witch +There to meet with Macbeth. + + +First Witch +I come, Graymalkin! + + +Second Witch +Paddock calls. + + +Third Witch +Anon. + + +ALL +Fair is foul, and foul is fair: +Hover through the fog and filthy air. + +Exeunt + + + diff --git a/spec/callback_handler_spec.rb b/spec/callback_handler_spec.rb index 30b620e..2f91aa2 100644 --- a/spec/callback_handler_spec.rb +++ b/spec/callback_handler_spec.rb @@ -1,30 +1,38 @@ require 'callback_handler' describe CallbackHandler do + before (:each) do + @handler = CallbackHandler.new + @parser = XML::SAX::Parser.new(@handler) + end + it "correctly parses an empty file" do - handler = CallbackHandler.new - parser = XML::SAX::Parser.new(handler) - parser.parse_file("spec/empty_file.xml") - handler.results.keys.should be_empty + @parser.parse_file("spec/empty_file.xml") + @handler.results.keys.should be_empty end it "correctly parses a file with only one speaker" do - handler = CallbackHandler.new - parser = XML::SAX::Parser.new(handler) - parser.parse_file("spec/one_speaker.xml") - handler.results.keys.should_not be_empty - handler.results["MACBETH"].should eq(1) + @parser.parse_file("spec/one_speaker.xml") + @handler.results.keys.should_not be_empty + @handler.results["MACBETH"].should eq(1) end it "correctly parses a file with multiple speakers" do - handler = CallbackHandler.new - parser = XML::SAX::Parser.new(handler) - parser.parse_file("spec/multiple_speakers.xml") - handler.results.keys.should_not be_empty + @parser.parse_file("spec/multiple_speakers.xml") + @handler.results.keys.should_not be_empty - handler.results["LENNOX"].should eq(1) - handler.results["MACBETH"].should eq(2) - handler.results["MACDUFF"].should eq(6) + @handler.results["LENNOX"].should eq(1) + @handler.results["MACBETH"].should eq(2) + @handler.results["MACDUFF"].should eq(6) end + it "correctly parses a file with a speaker of all" do + @parser.parse_file("spec/all_speaker.xml") + @handler.results.keys.should_not be_empty + + @handler.results["First Witch"].should eq(6) + @handler.results["Second Witch"].should eq(6) + @handler.results["Third Witch"].should eq(5) + @handler.results["ALL"].should eq(0) + end end diff --git a/spec/line_counter_spec.rb b/spec/line_counter_spec.rb index 9926043..1812bd0 100644 --- a/spec/line_counter_spec.rb +++ b/spec/line_counter_spec.rb @@ -1,51 +1,92 @@ require 'line_counter' describe LineCounter, "#count" do + before (:each) do + @counter = LineCounter.new + end + it "returns an empty list when no lines are counted" do - counter = LineCounter.new - counter.count.should be_empty + @counter.count.should be_empty end it "increments the current speaker when a speaker is added" do - counter = LineCounter.new - counter.add_speaker "Macbeth" + @counter.add_speaker "Macbeth" - counter.count_line - counter.count["Macbeth"].should eq(1) + @counter.count_line + @counter.count["Macbeth"].should eq(1) - counter.count_line - counter.count["Macbeth"].should eq(2) + @counter.count_line + @counter.count["Macbeth"].should eq(2) end it "throws exception when a line is counted without a speaker" do - counter = LineCounter.new - expect {counter.count_line}.to raise_error(ArgumentError) + expect {@counter.count_line}.to raise_error(ArgumentError) end it "allows two people to speak at the same time" do - counter = LineCounter.new - counter.add_speaker "Macbeth" - counter.add_speaker "Banquo" + @counter.add_speaker "Macbeth" + @counter.add_speaker "Banquo" - counter.count_line - counter.count["Macbeth"].should eq(1) - counter.count["Banquo"].should eq(1) + @counter.count_line + @counter.count["Macbeth"].should eq(1) + @counter.count["Banquo"].should eq(1) - counter.count_line - counter.count["Macbeth"].should eq(2) - counter.count["Banquo"].should eq(2) + @counter.count_line + @counter.count["Macbeth"].should eq(2) + @counter.count["Banquo"].should eq(2) end it "does not count lines from speakers of previous speeches" do - counter = LineCounter.new - counter.add_speaker "Macbeth" + @counter.add_speaker "Macbeth" - counter.new_speech + @counter.new_speech - counter.add_speaker "Duncan" - counter.count_line + @counter.add_speaker "Duncan" + @counter.count_line + + @counter.count["Duncan"].should eq(1) + @counter.count.key?("Macbeth").should be_false + end + + it "increments all speakers in the scene when the speaker is all" do + @counter.add_speaker "Macbeth" + @counter.count_line + + @counter.new_speech + @counter.add_speaker "Banquo" + @counter.count_line + @counter.count_line + + @counter.new_speech + @counter.add_speaker "ALL" + @counter.count_line + + @counter.count["Macbeth"].should eq(2) + @counter.count["Banquo"].should eq(3) + @counter.count["ALL"].should eq(0) + end + + it "increments only the speakers in the scene when the speaker is all" do + @counter.add_speaker "Macbeth" + @counter.count_line + + @counter.new_scene + @counter.new_speech + @counter.add_speaker "Banquo" + @counter.count_line + + @counter.new_speech + @counter.add_speaker "Duncan" + @counter.count_line + @counter.count_line + + @counter.new_speech + @counter.add_speaker "ALL" + @counter.count_line - counter.count["Duncan"].should eq(1) - counter.count.key?("Macbeth").should be_false + @counter.count["Macbeth"].should eq(1) + @counter.count["Banquo"].should eq(2) + @counter.count["Duncan"].should eq(3) + @counter.count["ALL"].should eq(0) end end