diff --git a/benchmark_helper.rb b/benchmark_helper.rb new file mode 100644 index 0000000..05f9205 --- /dev/null +++ b/benchmark_helper.rb @@ -0,0 +1,220 @@ +CITIES = ["Acton, CA", + "Adelanto, CA", + "Agoura Hills, CA", + "Agua Dulce, CA", + "Alameda, CA", + "Alamo, CA", + "Albany, CA", + "Alhambra, CA", + "Aliso Viejo, CA", + "Alpine, CA", + "Alta Loma, CA", + "Altadena, CA", + "Alturas, CA", + "Amador City, CA", + "Anaheim, CA", + "Anaheim Hills, CA", + "Anderson, CA", + "Angels Camp, CA", + "Angelus Oaks, CA", + "Antelope, CA", + "Antioch, CA", + "Anza, CA", + "Apple Valley, CA", + "Applegate, CA", + "Aptos, CA", + "Arcadia, CA", + "Arcata, CA", + "Arleta, CA", + "Armona, CA", + "Arnold, CA", + "Aroura Hills, CA", + "Arroyo Grande, CA", + "Atascadero, CA", + "Atwater, CA", + "Atwood, CA", + "Auberry, CA", + "Auburn, CA", + "Avalon, CA", + "Avery, CA", + "Avila Beach, CA", + "Azusa, CA", + "Bakersfield, CA", + "Baldwin Park, CA", + "Bangor, CA", + "Banning, CA", + "Barstow, CA", + "Bay Point, CA", + "Bayside, CA", + "Bel Air, CA", + "Bell, CA", + "Bellflower, CA", + "Belmont, CA", + "Belmont Shores, CA", + "Belvedere, CA", + "Ben Lomond, CA", + "Benicia, CA", + "Berkeley, CA", + "Berry Creek, CA", + "Beverly Hills, CA", + "Big Bear City, CA", + "Big Bear Lake, CA", + "Big Sur, CA", + "Bishop, CA", + "Blue Jay, CA", + "Blue Lake, CA", + "Blythe, CA", + "Bolinas, CA", + "Bonita, CA", + "Borrego Springs, CA", + "Bothell, CA", + "Boulder Creek, CA", + "Brea, CA", + "Breenbrae, CA", + "Brentwood, CA", + "Brisbane, CA", + "Buellton, CA", + "Buena Park, CA", + "Burbank, CA", + "Burlingame, CA", + "Burlington, CA", + "Calabasas, CA", + "California City, CA", + "Calimesa, CA", + "Calistoga, CA", + "Camarillo, CA", + "Cambria, CA", + "Cameron Park, CA", + "Campbell, CA", + "Campo, CA", + "Canoga Park, CA", + "Canyon Country, CA", + "Canyon Lake, CA", + "Capistrano Beach, CA", + "Capitola, CA", + "Cardiff, CA", + "Cardiff-by-the-Sea, CA", + "Carlsbad, CA", + "Carmel, CA", + "Carmel Valley, CA", + "Carmichael, CA", + "Carpinteria, CA", + "Carson, CA", + "Castaic, CA", + "Castro Valley, CA", + "Catalina Island, CA", + "Cathedral City, CA", + "Cayucos, CA", + "Cedar Glen, CA", + "Cedar Ridge, CA", + "Ceres, CA", + "Cerritos, CA", + "Chatsworth, CA", + "Cherry Valley, CA", + "Chester, CA", + "Chico, CA", + "China Lake, CA", + "Chino, CA", + "Chino Hills, CA", + "Chula Vista, CA", + "Citrus Heights, CA", + "City of Industry, CA", + "Claremont, CA", + "Clayton, CA", + "Clearlake Oaks, CA", + "Clearlake, CA", + "Cloverdale, CA", + "Clovis, CA", + "Coarsegold, CA", + "Colfax, CA", + "Coloma, CA", + "Colton, CA", + "Columbia, CA", + "Colusa, CA", + "Commerce, CA", + "Compton, CA", + "Concord, CA", + "Cordelia, CA", + "Corning, CA", + "Corona, CA", + "Corona del Mar, CA", + "Coronado, CA", + "Corralitos, CA", + "Corte Madera, CA", + "Costa Mesa, CA", + "Cotati, CA", + "Coto de Caza, CA", + "Covina, CA", + "CresceCutten, CA", + "Cypress, CA", + "Daly City, CA", + "Dana Point, CA", + "Danville, CA", + "Davis, CA", + "Del Mar, CA", + "Desert Hot Springs, CA", + "Diamond Bar, CA", + "Diamond Springs, CA", + "Dobbins, CA", + "Dominguez Hills, CA", + "Dove Canyon, CA", + "Downey, CA", + "Duarte, CA", + "Dublin, CA", + "Hemet, CA", + "Hercules, CA", + "Hermosa Beach, CA", + "Hesperia, CA", + "Highland, CA", + "Hinkley, CA", + "Hollister, CA", + "Hollywood, CA", + "Homeland, CA", + "Honcut, CA", + "Humboldt, CA", + "Huntington Beach, CA", + "Huntington Park, CA", + "Idyllwild, CA", + "Imperial, CA", + "Imperial Beach, CA", + "Independence, CA", + "Indian Wells, CA", + "Indio, CA", + "Inglewood, CA", + "Inverness, CA", + "Ione, CA", + "Irvine, CA", + "Irwindale, CA", + "Isla Vista, CA", + "Isleton, CA", + "Jackson, CA", + "Jacumba, CA", + "Jamestown, CA", + "Jamul, CA", + "Jenner, CA", + "Joshua Tree, CA", + "Julian, CA", + "Kelseyville, CA", + "Kensington, CA", + "Kentfield, CA", + "Kenwood, CA", + "King City, CA", + "Klamath, CA", + "Klamath River, CA", + "La Canada, CA", + "La Canada Flintridge, CA", + "La Crescenta, CA", + "La Habra, CA", + "La Honda, CA", + "Lake Elsinore, CA", + "Lake Forest, CA", + "Lake Isabella, CA", + "Lakeport, CA", + "Lakeside, CA", + "Vineburg, CA", + "Visalia, CA", + "Vista, CA", + "Walnut, CA", + "Walnut Creek, CA", + "Warm Springs, CA", + "Watsoncca Valley, CA"] \ No newline at end of file diff --git a/benchmark_salesperson.rb b/benchmark_salesperson.rb new file mode 100644 index 0000000..d3949b3 --- /dev/null +++ b/benchmark_salesperson.rb @@ -0,0 +1,36 @@ +Dir["./lib/*.rb"].each {|file| require file } + +require 'benchmark' +require_relative "benchmark_helper" + +phil = SalesPerson.new + +Benchmark.bm do |x| + x.report("2 cities") do + phil.schedule_city(Place.build("Chino Hills, CA"), starting_city: true) + 1.upto(2) do |city| + phil.schedule_city(Place.build(CITIES[city])) + end + end + + x.report("10 cities") do + phil.schedule_city(Place.build("Chino Hills, CA"), starting_city: true) + 1.upto(10) do |city| + phil.schedule_city(Place.build(CITIES[city])) + end + end + + x.report("50 cities") do + phil.schedule_city(Place.build("Chino Hills, CA"), starting_city: true) + 1.upto(50) do |city| + phil.schedule_city(Place.build(CITIES[city])) + end + end + + x.report("200 cities") do + phil.schedule_city(Place.build("Chino Hills, CA"), starting_city: true) + 1.upto(200) do |city| + phil.schedule_city(Place.build(CITIES[city])) + end + end +end diff --git a/lib/calculates_route.rb b/lib/calculates_route.rb deleted file mode 100644 index 4488393..0000000 --- a/lib/calculates_route.rb +++ /dev/null @@ -1,22 +0,0 @@ -class CalculatesRoute - - def self.calculate(points) - - remaining_points = points - route = [] - route << remaining_points.slice!(0) - until remaining_points == [] do - next_point = shortest_distance(route.last, remaining_points) - route << remaining_points.slice!(remaining_points.index(next_point)) - end - route - end - - 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) - end -end - diff --git a/lib/route.rb b/lib/route.rb new file mode 100644 index 0000000..3ea85a1 --- /dev/null +++ b/lib/route.rb @@ -0,0 +1,41 @@ +class Route + attr_reader :miles_traveled + + def initialize + @miles_traveled = 0 + end + + def calculate(points) + remaining_points = points + route = [] + route << remaining_points.slice!(0) + while remaining_points.any? do + next_point = shortest_distance(route.last, remaining_points) + route << remaining_points.slice!(remaining_points.index(next_point)) + end + route + end + + def shortest_distance(from, possible) + distances = possible.map do |point| + {point: point, distance: Map.distance_between(from, point)} + end + get_the_shortest_distance_from(distances) + end + + def get_the_shortest_distance_from(distances) + shortest_distance = distances.sort{|a,b| a.fetch(:distance) <=> b.fetch(:distance)} + add_to_miles_traveled(shortest_distance.first.fetch(:distance)) + shortest_distance.first.fetch(:point) + end + + def add_to_miles_traveled(miles) + @miles_traveled += miles + end + + def time_traveled + @miles_traveled / 55 + end +end + + diff --git a/lib/sales_person.rb b/lib/sales_person.rb index d0c2890..2b13da4 100644 --- a/lib/sales_person.rb +++ b/lib/sales_person.rb @@ -1,15 +1,28 @@ class SalesPerson - attr_reader :cities - def initialize + attr_reader :cities, :starting_city + def initialize(route = Route.new) @cities = [] + @route = route end - def schedule_city(city) + def schedule_city(city, options={}) + if options[:starting_city] == true + @starting_city ||= city + @cities.unshift(@starting_city) + end @cities << city unless @cities.include?(city) end def route - CalculatesRoute.calculate(cities) + @route.calculate(cities) + end + + def miles_traveled + @route.miles_traveled + end + + def time_traveled + @route.time_traveled end end diff --git a/salesperson.rb b/salesperson.rb index 9cafbd7..e6d8eed 100644 --- a/salesperson.rb +++ b/salesperson.rb @@ -1,10 +1,15 @@ Dir["./lib/*.rb"].each {|file| require file } -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")) +jason = SalesPerson.new +jason.schedule_city(Place.build("Whittier, CA")) +jason.schedule_city(Place.build("Chino Hills, CA"), starting_city: true) +jason.schedule_city(Place.build("Brea, CA")) +jason.schedule_city(Place.build("Irvine, CA")) -puts phil.route +puts "-----------------------------" +puts "Here's my route:" +puts jason.route +puts "-----------------------------" +puts "This route will cover #{jason.miles_traveled.round(1)} miles, as the crow flies." +puts "If that crow flew 55 mph it'd take #{jason.time_traveled.round(2)} hours." \ No newline at end of file diff --git a/spec/calculates_route_spec.rb b/spec/calculates_route_spec.rb deleted file mode 100644 index 47679d6..0000000 --- a/spec/calculates_route_spec.rb +++ /dev/null @@ -1,15 +0,0 @@ -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")} - - it "should calculate the route" do - points = [dallas, el_paso, austin, lubbock] - expected = [dallas, austin, lubbock, el_paso] - CalculatesRoute.calculate(points).should eq(expected) - end -end diff --git a/spec/map_spec.rb b/spec/map_spec.rb index 2e2f6f1..9688512 100644 --- a/spec/map_spec.rb +++ b/spec/map_spec.rb @@ -10,8 +10,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 +19,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..396e0e8 100644 --- a/spec/place_spec.rb +++ b/spec/place_spec.rb @@ -14,7 +14,7 @@ describe ":build" do let(:name) { "El Paso, TX"} - let(:result) { stub("el paso", coordinates: [29, -95])} + let(:result) { double("el paso", coordinates: [29, -95])} it "should build from the map" do Map.should_receive(:search).with(name).and_return(result) diff --git a/spec/route_spec.rb b/spec/route_spec.rb new file mode 100644 index 0000000..6e0a26f --- /dev/null +++ b/spec/route_spec.rb @@ -0,0 +1,32 @@ +require_relative "../lib/route" +require_relative "../lib/place" + +describe Route 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 (:points) {[dallas, el_paso, austin, lubbock]} + let(:route) { Route.new } + + it "should calculate the route" do + expected = [dallas, austin, lubbock, el_paso] + route.calculate(points).should eq(expected) + end + + it "should provide the shortest distance between a set of routes" do + remaining_points = [lubbock, dallas, el_paso] + route.shortest_distance(austin, remaining_points). should eq(dallas) + end + + it "should calculate total miles traveled" do + route.calculate(points) + route.miles_traveled.should be_within(1).of(812) + end + + it "should calculate total travel time" do + route.calculate(points) + route.time_traveled.should be_within(0.1).of(14.76) + end +end + diff --git a/spec/sales_person_spec.rb b/spec/sales_person_spec.rb index 08a6ce9..6b5946e 100644 --- a/spec/sales_person_spec.rb +++ b/spec/sales_person_spec.rb @@ -1,30 +1,52 @@ require_relative "../lib/sales_person" -require_relative "../lib/calculates_route" +require_relative "../lib/route" describe SalesPerson do + + before do + @route_double = double + @sales_person = SalesPerson.new(@route_double) + end + it "should have many cities" do - city = stub + city = double subject.schedule_city(city) subject.cities.should include(city) end - it "should keep the cities only scheduled once" do - city = stub + it "should keep the cities only scheduled once" do + city = double expect{ subject.schedule_city(city) subject.schedule_city(city) - }.to change(subject.cities,:count).by(1) + }.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 + it "should calculate a route via the Route" do + cities = [double, double, double] + @sales_person.stub(:cities) { cities } + @route_double.should_receive(:calculate).with(cities) + @sales_person.route end - it "should returns the route from CalculatesRoute" do - route_stub = [stub, stub] - CalculatesRoute.stub(:calculate) { route_stub } - subject.route.should eq(route_stub) + it "should return the route from Route" do + route_stub = [double, double] + @route_double.stub(:calculate) { route_stub } + @sales_person.route.should eq(route_stub) + end + + it "should allow for the starting point to be specified" do + city = double + subject.schedule_city(city, starting_city: true) + subject.starting_city.should eq(city) + end + + it "should calculate total miles traveled via Route" do + @route_double.should_receive(:miles_traveled) + @sales_person.miles_traveled + end + + it "should return total time traveled at 60 mph via Route" do + @route_double.should_receive(:time_traveled) + @sales_person.time_traveled end end