Skip to content

Instantly share code, notes, and snippets.

@ilatif
Last active September 29, 2015 11:44
Show Gist options
  • Save ilatif/459190361d138791121b to your computer and use it in GitHub Desktop.
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…
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
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
{"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"}
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
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