diff --git a/Gemfile.lock b/Gemfile.lock index 28827b4..61e4080 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -1,22 +1,30 @@ GEM remote: https://rubygems.org/ specs: - activesupport (3.2.3) - i18n (~> 0.6) - multi_json (~> 1.0) - diff-lcs (1.1.3) - geocoder (1.1.1) - i18n (0.6.0) - multi_json (1.3.4) - rake (0.9.2.2) - rspec (2.10.0) - rspec-core (~> 2.10.0) - rspec-expectations (~> 2.10.0) - rspec-mocks (~> 2.10.0) - rspec-core (2.10.0) - rspec-expectations (2.10.0) - diff-lcs (~> 1.1.3) - rspec-mocks (2.10.1) + activesupport (4.0.2) + i18n (~> 0.6, >= 0.6.4) + minitest (~> 4.2) + multi_json (~> 1.3) + thread_safe (~> 0.1) + tzinfo (~> 0.3.37) + atomic (1.1.14) + diff-lcs (1.2.5) + geocoder (1.1.9) + i18n (0.6.9) + minitest (4.7.5) + multi_json (1.8.4) + rake (10.1.1) + rspec (2.14.1) + rspec-core (~> 2.14.0) + rspec-expectations (~> 2.14.0) + rspec-mocks (~> 2.14.0) + rspec-core (2.14.7) + rspec-expectations (2.14.4) + diff-lcs (>= 1.1.3, < 2.0) + rspec-mocks (2.14.4) + thread_safe (0.1.3) + atomic + tzinfo (0.3.38) PLATFORMS ruby diff --git a/lib/calculates_route.rb b/lib/calculates_route.rb index 4488393..b056fc7 100644 --- a/lib/calculates_route.rb +++ b/lib/calculates_route.rb @@ -1,13 +1,13 @@ class CalculatesRoute def self.calculate(points) - remaining_points = points route = [] - route << remaining_points.slice!(0) + route << {point: remaining_points.slice!(0), distance: 0} until remaining_points == [] do - next_point = shortest_distance(route.last, remaining_points) - route << remaining_points.slice!(remaining_points.index(next_point)) + next_point = shortest_distance(route.last.fetch(:point), remaining_points) + remaining_points.delete(next_point.fetch(:point)) + route << next_point end route end @@ -16,7 +16,8 @@ def self.shortest_distance(from, possible) distances = possible.map do |point| {point: point, distance: Map.distance_between(from, point)} end - distances.sort{|a,b| a.fetch(:distance) <=> b.fetch(:distance)}.first.fetch(:point) + sorted = distances.sort{|a,b| a.fetch(:distance) <=> b.fetch(:distance)} + sorted.first end end diff --git a/lib/place.rb b/lib/place.rb index 99eaadf..bcae5db 100644 --- a/lib/place.rb +++ b/lib/place.rb @@ -1,13 +1,14 @@ require_relative "./map" class Place - attr_accessor :name, :coordinates + attr_accessor :address, :coordinates, :name - def self.build(name) - results = Map.search(name) + def self.build(address, options= {}) + results = Map.search(address) Place.new.tap do |p| - p.name = name + p.address = address p.coordinates = results.coordinates + p.name = options[:name].nil? ? address : options[:name] end end diff --git a/lib/sales_person.rb b/lib/sales_person.rb index d0c2890..f52c03d 100644 --- a/lib/sales_person.rb +++ b/lib/sales_person.rb @@ -1,6 +1,9 @@ +require_relative 'place' + class SalesPerson attr_reader :cities + def initialize @cities = [] end @@ -9,7 +12,27 @@ def schedule_city(city) @cities << city unless @cities.include?(city) end - def route + def route(starting_point_name = nil) + move_starting_point(starting_point_name) if starting_point_name CalculatesRoute.calculate(cities) end + + def calculate_total_miles(route) + route.map {|leg| leg.fetch(:distance)}.reduce(:+) + end + + def calculate_traveling_time(total_miles, speed = 55) + total_miles/speed + end + + private + + def find_starting_point(starting_point_name) + @cities.bsearch {|place| place.name.downcase == starting_point_name.downcase } + end + + def move_starting_point(starting_point_name) + starting_point = find_starting_point(starting_point_name) + @cities.unshift(@cities.delete(starting_point)) + end end diff --git a/salesperson.rb b/salesperson.rb index 9cafbd7..1be7ca1 100644 --- a/salesperson.rb +++ b/salesperson.rb @@ -1,10 +1,59 @@ +require 'benchmark' + Dir["./lib/*.rb"].each {|file| require file } +@all_cities = ["Abilene", "Alamo", "Alamo Heights", "Alice", "Allen", "Alpine", "Alvarado", "Alvin", "Amarillo", "Andrews", "Angleton", "Argyle", "Arlington", "Austin", "Azle", "Balch Springs", "Bastrop", "Bay City", "Baytown", "Beaumont", "Bellaire", "Belton", "Benbrook", "Big Spring", "Boerne", "Brenham", "Bridgeport", "Brownfield", "Brownsville", "Brownwood", "Bryan", "Bulverde", "Burkburnett", "Burleson", "Burnet", "Canyon", "Carrollton", "Carthage", "Cedar Hill", "Cedar Park", "Cleburne", "Clute", "College Station", "Colleyville", "Columbus", "Conroe", "Converse", "Coppell", "Copperas Cove", "Corinth", "Corpus Christi", "Corsicana", "Crockett", "Crowley", "Dallas", "Deer Park", "Del Rio", "Denison", "Denton", "Desoto", "Devine", "Dickinson", "Donna", "Dumas", "Duncanville", "Eagle Pass", "Edinburg", "El Campo", "El Paso", "Ennis", "Euless", "Farmers Branch", "Flower Mound", "Fort Stockton", "Fort Worth", "Fredericksburg", "Freeport", "Friendswood", "Frisco", "Gainesville", "Galveston", "Garden Ridge", "Garland", "Gatesville", "Georgetown", "Gonzales", "Granbury", "Grand Prairie", "Granite Shoals", "Grapevine", "Greenville", "Haltom City", "Hamilton", "Harker Heights", "Harlingen", "Hearne", "Henderson", "Hewitt", "Highland Park", "Highland Village", "Houston", "Humble", "Hunters Creek Village", "Huntsville", "Hurst", "Irving", "Jacksonville", "Jasper", "Jersey Village", "Katy", "Keller", "Kemah", "Kennedale", "Kerrville", "Killeen", "Kingsville", "Krum", "Kyle", "La Grange", "La Porte", "Lacy Lakeview", "Lago Vista", "Lake Jackson", "Lakeway", "Lamesa", "Lampasas", "Lancaster", "Laredo", "League City", "Leander", "Leon Valley", "Levelland", "Lewisville", "Littlefield", "Live Oak", "Lockhart", "Longview", "Lowry Crossing", "Lubbock", "Lucas", "Lufkin", "Mansfield", "Manvel", "Marble Falls", "Marshall", "McAllen", "McKinney", "Mesquite", "Midland", "Midlothian", "Mission", "Missouri City", "Mount Pleasant", "Muleshoe", "Murphy", "Nacogdoches", "Nassau Bay", "New Braunfels", "Newark", "Newton", "North Richland Hills", "Oak Ridge North", "Odessa", "Orange", "Overton", "Palacios", "Palestine", "Pampa", "Paris", "Pasadena", "Pearland", "Perryton", "Pflugerville", "Plainview", "Plano", "Port Aransas", "Port Arthur", "Port Lavaca", "Portland", "Richardson", "Richland Hills", "Ridge City", "Rio Grande City", "River Oaks", "Rockport", "Rockwall", "Rosenberg", "Round Rock", "Rowlett", "Royse City", "Rusk", "Sachse", "Saginaw", "San Angelo", "San Antonio", "San Benito", "San Marcos", "San Saba", "Santa Fe", "Schertz", "Seabrook", "Sealy", "Seguin", "Selma", "Seymour", "Shenandoah", "Sherman", "Smithville", "Snyder", "Socorro", "Sonora", "Southlake", "Southside Place", "Spring Valley", "Stafford", "Stephenville", "Sugar Land", "Sulphur Springs", "Sweeny", "Tahoka", "Taylor", "Temple", "Terrell", "Texarkana", "Texas City", "The Colony", "The Woodlands", "Tomball", "Tyler", "Universal City", "University Park", "Victoria", "Waco", "Watauga", "Waxahachie", "Weatherford", "Webster", "Weslaco", "West Columbia", "West Lake Hills", "West Orange", "West University Place", "Weston", "Wharton", "White Settlement", "Wichita Falls", "Willow Park", "Windcrest", "Woodway", "Wylie", "Yoakum"] + + phil = SalesPerson.new +def personal_route(phil) + phil.schedule_city(Place.build("3900 16th st nw, DC", name: "Home")) + phil.schedule_city(Place.build("3505 Connecticut Ave, DC", name: "Petco")) + phil.schedule_city(Place.build("1250 24th st nw, DC", name: "Work")) + phil.schedule_city(Place.build("3100 14th St NW, DC", name: "Target")) + phil.schedule_city(Place.build("1440 P St NW, DC", name: "Whole Foods")) + phil.route("Work") +end + +# puts "This is the route from home: #{phil.route}" + +def load_cities(num_cities, sales_person) + times = 0 + until times == num_cities do + @all_cities.shuffle.take(1).each do |city| + sales_person.schedule_city(Place.build("#{city}, TX")) + end + times +=1 + sleep(2) + end +end + +def Benchmark(num_cities) + phil = SalesPerson.new + load_cities(num_cities,phil) + Benchmark.bm do |x| + x.report do + phil.route + end + end +end + +def pretty_time(seconds) + mm, ss = seconds.divmod(60) #=> [4515, 21] + hh, mm = mm.divmod(60) #=> [75, 15] + dd, hh = hh.divmod(24) #=> [3, 3] + "%d days, %d hours, %d minutes and %d seconds" % [dd, hh, mm, ss] +end + +route = personal_route(phil) +puts route +puts "This is my personal route: #{route.map {|leg| leg.fetch(:point).name}}" +miles = phil.calculate_total_miles(route) +puts "Phil traveled #{miles} miles" +time = phil.calculate_traveling_time(miles, 25)*60*60 +puts "It took phil #{pretty_time(time)}" +# puts "This is the benchmark for 2 cities:\n#{Benchmark(2)}" +# puts "This is the benchmark for 10 cities:\n#{Benchmark(10)}" +# puts "This is the benchmark for 50 cities:\n#{Benchmark(50)}" +# puts "This is the benchmark for 2 cities:\n#{Benchmark(200)}" -phil = SalesPerson.new -phil.schedule_city(Place.build("Dallas, TX")) -phil.schedule_city(Place.build("El Paso, TX")) -phil.schedule_city(Place.build("Austin, TX")) -phil.schedule_city(Place.build("Lubbock, TX")) -puts phil.route diff --git a/spec/calculates_route_spec.rb b/spec/calculates_route_spec.rb index 47679d6..6157dd8 100644 --- a/spec/calculates_route_spec.rb +++ b/spec/calculates_route_spec.rb @@ -1,15 +1,22 @@ +require_relative "spec_helper" require_relative "../lib/calculates_route" require_relative "../lib/place" describe CalculatesRoute do - let(:dallas) {Place.build("Dallas, TX") } - let(:austin ) { Place.build("Austin, TX")} - let(:lubbock ) { Place.build("Lubbock, TX")} - let(:el_paso ) { Place.build("El Paso, TX")} + let(:dallas ) { Place.build("Dallas, TX") } + let(:austin ) { Place.build("Austin, TX") } + let(:lubbock ) { Place.build("Lubbock, TX") } + let(:el_paso ) { Place.build("El Paso, TX") } + let(:points ) { [dallas, el_paso, austin, lubbock] } it "should calculate the route" do - points = [dallas, el_paso, austin, lubbock] expected = [dallas, austin, lubbock, el_paso] - CalculatesRoute.calculate(points).should eq(expected) + route = CalculatesRoute.calculate(points) + route.map {|leg| leg.fetch(:point)}.should eq(expected) end + + it "should also output the distance of each leg" do + CalculatesRoute.calculate(points)[0].fetch(:distance).should eq(0) + end + end diff --git a/spec/map_spec.rb b/spec/map_spec.rb index 2e2f6f1..32406af 100644 --- a/spec/map_spec.rb +++ b/spec/map_spec.rb @@ -1,3 +1,4 @@ +require_relative "spec_helper" require_relative "../lib/map" require 'geocoder' @@ -10,8 +11,8 @@ end it "should use the first item in the array" do - austin = stub("Austin") - dallas = stub("Dallas") + austin = double("Austin") + dallas = double("Dallas") Geocoder.stub(:search) {[austin, dallas]} Map.search("austin, tx").should eq(austin) end @@ -19,8 +20,8 @@ describe ":distance" do it "should calculate distance between two sets of coordinates" do - alpha = stub - beta = stub + alpha = double + beta = double Geocoder::Calculations.should_receive(:distance_between).with(alpha, beta) Map.distance_between(alpha, beta) end diff --git a/spec/place_spec.rb b/spec/place_spec.rb index 7d48250..c017cd7 100644 --- a/spec/place_spec.rb +++ b/spec/place_spec.rb @@ -1,34 +1,49 @@ +require_relative "spec_helper" require_relative "../lib/place" require_relative "../lib/map" describe Place do - it "should have a name" do - subject.should respond_to(:name) + it "should have a address" do + subject.should respond_to(:address) end + it "should have a coordinates" do subject.coordinates = [29,-95] subject.coordinates.should eq([29,-95]) end + it "should have a pretty name" do + subject.should respond_to(:name) + end + describe ":build" do - let(:name) { "El Paso, TX"} - let(:result) { stub("el paso", coordinates: [29, -95])} + let(:address) { "El Paso, TX"} + let(:result) { double("el paso", coordinates: [29, -95])} + let(:name) {"El Paso"} + + it "should return the pretty name if included" do + expect(Place.build(address, name: name).name).to eq("El Paso") + end + + it "should return the address as name if pretty name isn't included" do + expect(Place.build(address).name).to eq("El Paso, TX") + end it "should build from the map" do - Map.should_receive(:search).with(name).and_return(result) - Place.build(name) + Map.should_receive(:search).with(address).and_return(result) + Place.build(address) end it "should be place" do - Map.stub(:search).with(name).and_return(result) - Place.build(name).should be_a(Place) + Map.stub(:search).with(address).and_return(result) + Place.build(address).should be_a(Place) end end describe "#to_s" do - it "should use the city as the to_s" do + it "should use the name as the to_s" do subject.stub(:name) { "Boston" } subject.to_s.should eq("Boston") end diff --git a/spec/sales_person_spec.rb b/spec/sales_person_spec.rb index 08a6ce9..1b0ca77 100644 --- a/spec/sales_person_spec.rb +++ b/spec/sales_person_spec.rb @@ -1,30 +1,70 @@ +require_relative "spec_helper" require_relative "../lib/sales_person" require_relative "../lib/calculates_route" +require_relative "../lib/place" describe SalesPerson do + + let(:city1) {Place.build("3505 Connecticut Ave, DC", name: "Petco")} + let(:city2) {Place.build("3900 16th st nw, DC", name: "Home")} + let(:city) {double} + let(:scheduled) { subject.schedule_city(city) } + + it "should have many cities" do - city = stub - subject.schedule_city(city) + scheduled subject.cities.should include(city) end it "should keep the cities only scheduled once" do - city = stub expect{ - subject.schedule_city(city) - subject.schedule_city(city) + scheduled + scheduled }.to change(subject.cities,:count).by(1) end - it "should calculate a route via the CalculatesRoute" do - cities = [stub, stub, stub] - subject.stub(:cities) { cities } - CalculatesRoute.should_receive(:calculate).with(cities) - subject.route + describe "#route" do + it "should calculate a route via the CalculatesRoute" do + cities = [Place, double, double] + subject.stub(:cities) { cities } + CalculatesRoute.should_receive(:calculate).with(cities) + subject.route + end + + it "should returns the route from CalculatesRoute" do + route_stub = [double, double] + CalculatesRoute.stub(:calculate) { route_stub } + subject.route.should eq(route_stub) + end + + it "should be able to add a starting city by name" do + subject.schedule_city(city1) + subject.schedule_city(city2) + starting_point = "home" + first_route_point = subject.route(starting_point)[0] + expect(first_route_point.fetch(:point)).to eq(city2) + end + + it "should use first city if a starting city wasn't specified" do + subject.schedule_city(city1) + subject.schedule_city(city2) + first_route_point = subject.route[0] + expect(first_route_point.fetch(:point)).to eq(city1) + end + end + + describe "#calculate_total_miles" do + it "should report the total miles traveled on the route" do + route = [{point: city1, distance: 0}, {point: city2, distance: 1.1}] + expect(subject.calculate_total_miles(route)).to eq(1.1) + end end - it "should returns the route from CalculatesRoute" do - route_stub = [stub, stub] - CalculatesRoute.stub(:calculate) { route_stub } - subject.route.should eq(route_stub) + + describe "#calculate_traveling_time" do + it "shoudld report the traveling time of the route" do + total_miles = 200 + expect(subject.calculate_traveling_time(total_miles)).to eq(200/55) + end end + end diff --git a/spec/spec_helper.rb b/spec/spec_helper.rb new file mode 100644 index 0000000..5a2dea5 --- /dev/null +++ b/spec/spec_helper.rb @@ -0,0 +1,69 @@ +require 'rspec' +require 'bundler/setup' +require 'geocoder' + + +RSpec.configure do |config| + # Use color in STDOUT + config.color_enabled = true + + # Use color not only in STDOUT but also in pagers and files + config.tty = true + + # Use the specified formatter + config.formatter = :documentation # :progress, :html, :textmate +end + +Geocoder.configure(:lookup => :test) + +Geocoder::Lookup::Test.add_stub( + "3505 Connecticut Ave, DC", [ + { + 'latitude' => 38.935838, + 'longitude' => -77.05994108029152 + } + ] +) +Geocoder::Lookup::Test.add_stub( + "3900 16th st nw, DC", [ + { + 'latitude' => 38.939297, + 'longitude' => -77.037307 + } + ] +) + +#texas +Geocoder::Lookup::Test.add_stub( + "Dallas, TX", [ + { + 'latitude' => 32.7801399, + 'longitude' => -96.80045109999999 + } + ] +) +Geocoder::Lookup::Test.add_stub( + "Austin, TX", [ + { + 'latitude' => 30.267153, + 'longitude' => -97.7430608, + } + ] +) +Geocoder::Lookup::Test.add_stub( + "Lubbock, TX", [ + { + 'latitude' => 33.70845389999999, + 'longitude' => -101.748554, + } + ] +) +Geocoder::Lookup::Test.add_stub( + "El Paso, TX", [ + { + 'latitude' => 31.7699559, + 'longitude' => -106.4968055, + } + ] +) +