Last active
September 29, 2015 11:44
-
-
Save ilatif/459190361d138791121b to your computer and use it in GitHub Desktop.
We have some customer records in a text file (customers.json) -- one customer per line, JSON-encoded. We want to invite any customer within 100km of our Dublin office (GPS coordinates 53.3381985, -6.2592576) for some food and drinks on us. Write a program that will read the full list of customers and output the names and user ids of matching cus…
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
require 'distance_calculator' | |
require 'json' | |
class CustomersInvitation | |
# CustomersInvitation provides methods to invite customers from a given list of customers | |
# which are x kms away from provided base location's coordinates. | |
attr_accessor :customers, :invited_customers, :base_latitude, :base_longitude | |
# Constructor of class, used to read customers from a file and assign base latitude and longitude. | |
# If no latitude and longitude is provided then intercom's base latitude and longitude will be used. | |
def initialize(filepath=File.expand_path("lib/customers.json"), base_latitude="53.3381985", base_longitude="-6.2592576") | |
begin | |
@customers = _parse(filepath) | |
@base_latitude = base_latitude | |
@base_longitude = base_longitude | |
rescue => err | |
raise err | |
end | |
end | |
# Instance method to find and return customers that fall within provided kms range | |
def invite_within_kms(invitation_radius_in_km=100) | |
_find_customers_to_be_invited(invitation_radius_in_km) | |
end | |
# Instance method to display customers | |
def display_invited | |
_output | |
end | |
private | |
# Read file from given filepath and parse JSON | |
def _parse(filepath) | |
customers_json = [] | |
File.readlines(filepath).each do |customer| | |
customers_json << JSON.parse(customer) | |
end | |
customers_json | |
end | |
# Performs operations to find and return customers that fall within provided kms range | |
def _find_customers_to_be_invited(invitation_radius_in_km) | |
_invited_customers = [] | |
@customers.each do |customer| | |
distance = DistanceCalculator.calculate(@base_latitude, @base_longitude, customer["latitude"], customer["longitude"]) | |
if (distance <= invitation_radius_in_km) | |
_invited_customers << customer | |
end | |
end | |
@invited_customers = _invited_customers.sort_by { |ic| ic["user_id"] } | |
end | |
# Displays cusomters to STDOUT in following format: | |
# customer_name - customer_id | |
def _output | |
@invited_customers.each do |invited_customer| | |
puts "#{invited_customer["name"]} - #{invited_customer["user_id"]}" | |
end | |
end | |
end |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
require "customers_invitation" | |
RSpec.describe CustomersInvitation do | |
context "validate customers data is present" do | |
context "when customers.json is not present" do | |
it "throws an exception" do | |
expect { | |
customers = CustomersInvitation.new(File.expand_path("customer_spec.json")).invite | |
}.to raise_error(Errno::ENOENT) | |
end | |
end | |
context "when customers.json is present" do | |
it "reads the file and parse customers JSON" do | |
customers_invitation = CustomersInvitation.new(File.expand_path("spec/customers_spec.json")) | |
expect(customers_invitation.customers.length).to eq(5) | |
first_customer = customers_invitation.customers.first | |
expect(first_customer["name"]).to eq("Christina McArdle") | |
expect(first_customer["user_id"]).to eq(12) | |
expect(first_customer["latitude"]).to eq("52.986375") | |
expect(first_customer["longitude"]).to eq("-6.043701") | |
end | |
end | |
end | |
context "assign base latitude/longitude" do | |
it "by-default assigns intercom's latitude/longitude" do | |
customers_invitation = CustomersInvitation.new(File.expand_path("spec/customers_spec.json")) | |
expect(customers_invitation.base_latitude).to eq("53.3381985") | |
expect(customers_invitation.base_longitude).to eq("-6.2592576") | |
end | |
it "assigns provided laitude/longitude" do | |
customers_invitation = CustomersInvitation.new(File.expand_path("spec/customers_spec.json"), "54", "-8") | |
expect(customers_invitation.base_latitude).to eq("54") | |
expect(customers_invitation.base_longitude).to eq("-8") | |
end | |
end | |
context "invite users within x kms" do | |
it "invites users within 20 kms" do | |
invited_customers = CustomersInvitation.new(File.expand_path("spec/customers_spec.json")).invite_within_kms(20) | |
expect(invited_customers.length).to eq(1) | |
expect(invited_customers[0]["name"]).to eq("Ian Kehoe") | |
expect(invited_customers[0]["user_id"]).to eq(4) | |
end | |
it "invites users within 100 kms and returns sorted list by user_id asc" do | |
invited_customers = CustomersInvitation.new(File.expand_path("spec/customers_spec.json")).invite_within_kms | |
expect(invited_customers.length).to eq(2) | |
expect(invited_customers[0]["user_id"] <= invited_customers[1]["user_id"]).to eq(true) | |
end | |
it "invites users within 100 kms and prints name and user_id" do | |
expect { | |
customers_invitation = CustomersInvitation.new(File.expand_path("spec/customers_spec.json")) | |
customers_invitation.invite_within_kms | |
customers_invitation.display_invited | |
}.to output("Ian Kehoe - 4\nChristina McArdle - 12\n").to_stdout | |
end | |
end | |
end |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
{"latitude": "52.986375", "user_id": 12, "name": "Christina McArdle", "longitude": "-6.043701"} | |
{"latitude": "51.92893", "user_id": 1, "name": "Alice Cahill", "longitude": "-10.27699"} | |
{"latitude": "51.8856167", "user_id": 2, "name": "Ian McArdle", "longitude": "-10.4240951"} | |
{"latitude": "53.2451022", "user_id": 4, "name": "Ian Kehoe", "longitude": "-6.238335"} | |
{"latitude": "53.807778", "user_id": 28, "name": "Charlie Halligan", "longitude": "-7.714444"} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
class DistanceCalculator | |
# DistanceCalculator calculates distance in kms between two coordinates | |
# Class method which accepts two latitude and longitude pairs to calculate distance between them in kms | |
def self.calculate(latitude_1, longitude_1, latitude_2, longitude_2) | |
_calculate(latitude_1.to_f, longitude_1.to_f, latitude_2.to_f, longitude_2.to_f) | |
end | |
private | |
# Implementation of Haversine formula to calculate distance in kms | |
def self._calculate(latitude_1, longitude_1, latitude_2, longitude_2) | |
latitude_diff = (latitude_1 - latitude_2) * (Math::PI / 180.0) | |
longitude_diff = (longitude_1 - longitude_2) * (Math::PI / 180.0) | |
a = Math.sin(latitude_diff/2)**2 + Math.cos((latitude_1 * (Math::PI / 180.0))) * Math.cos((latitude_2 * (Math::PI / 180.0))) * Math.sin(longitude_diff/2) ** 2 | |
c = 2 * Math.atan2(Math.sqrt(a), Math.sqrt(1-a)) | |
return (c * 6371.00).round(2) # 6371.00 is Earth's radius in kms | |
end | |
end |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
require 'distance_calculator' | |
RSpec.describe DistanceCalculator do | |
it "calculates distance between two coordinates" do | |
distance = DistanceCalculator.calculate("53.3381985", "-6.2592576", "53.2451022", "-6.238335") | |
expect(distance).to eq(10.44) | |
end | |
end |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment