Skip to content

Problem statement 1 & 2 completed #105

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 5 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
136 changes: 20 additions & 116 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,116 +1,20 @@
# Qube Cinemas Challenge 2019
Qube delivers the movie content to theatres all around the world. There are multiple delivery partners to help us deliver the content.

Delivery partners specify the rate of delivery and cost in following manner (All costs are in paise):

Table 1:

| Theatre | Size Slab (in GB) | Minimum cost | Cost Per GB | Partner ID |
| ------------- |:----------------: |:-------------:| :----------:|:----------:|
| T1 | 0-200 | 2000 | 20 | P1 |
| T1 | 200-400 | 3000 | 15 |P1 |
| T3 | 100-200 | 4000 | 30 |P1 |
| T3 | 200-400 | 5000 | 25 |P1 |
| T5 | 100-200 | 2000 | 30 |P1 |
| T1 | 0-400 | 1500 | 25 |P2 |

First row allows 0 to 200 GB content to be sent to theatre T1 with the rate 20 paise per GB. However, if total cost comes less than minimum cost, minimum cost (2000 paise) will be charged.

*NOTE*:
- Multiple partners can deliver to same theatre


- Write programs in any language you want. Feel free to hold the datasets in whatever data structure you want, but try not to use external databases - as far as possible stick to your langauage without bringing in MySQL/Postgres/MongoDB/Redis/Etc.

- We've provided a CSV `partners.csv` with the list of all partners, theatres, content size, minimum cost and cost per GB. Please use the data mentioned there for this program instead of data given in Table 1 and 2. The codes you see in csv may be different from what you see in tables, so please always use the codes in the CSV. This Readme is only an example.

This challenge consist of two problems. Aim to solve atleast Problem Statement 1.

## Problem Statement 1
Given a list of content size and Theatre ID, Find the partner for each delivery where cost of delivery is minimum. If delivery is not possible, mark that delivery impossible.

Use the data given in `partners.csv`.


**Input**: A CSV file `input.csv`. Each row containing delivery ID, size of delivery and theatre ID.

**Expected Output**: A CSV `output.csv`. Each row containing delivery ID, indication if delivery is possible (true/false), selected partner and cost of delivery.

#### Sample Scenarios (Based on above table 1):
**INPUT**:
```
D1, 100, T1
D2, 300, T1
D3, 350, T1
```
**OUTPUT**:
```
D1, true, P1, 2000
D2, true, P1, 4500
D3, true, P1, 5250
```
---
**INPUT**:
```
D1, 70, T1
D2, 300, T1
```
**OUTPUT**:
```
D1, true, P2, 1750
D2, true, P1, 4500
```

---
**INPUT**:
```
D1, 70, T3
D2, 300, T1
```
**OUTPUT**:
```
D1, false, "", ""
D2, true, P1, 4500
```

## Problem Statement 2

Each partner specifies the **maximum capacity** they can serve, across all their deliveries in following manner:

Table 2:

| Partner ID | Capacity (in GB) |
| ------------- |:----------------:|
| P1 | 500 |
| P2 | 300 |

We have provided `capacities.csv` which contain ID and capacities for each partner.

Given a list of content size and Theatre ID, Assign deliveries to partners in such a way that all deliveries are possible (Higher Priority) and overall cost of delivery is minimum (i.e. First make sure no delivery is impossible and then minimise the sum of cost of all the delivery). If delivery is not possible to a theatre, mark that delivery impossible. Take partner capacity into consideration as well.

Use `partners.csv` and `capacities.csv`.

**Input**: Same as Problem statement 1.

**Expected Output**: Same as Problem statement 1.

#### Sample Scenario (Based on above table 1 and 2):
**INPUT**:
```
D1, 100, T1
D2, 240, T1
D2, 260, T1
```

**OUTPUT**:
```
D1, true, P2, 2500
D2, true, P1, 3600
D3, true, P1, 3900
```

**Explanation**: Only partner P1 and P2 can deliver content to T1. Lowest cost of delivery will be achieved if all three deliveries are given to partner P1 (100\*20+240\*15+260\*15 = 9,500). However, P1 has capacity of 500 GB and total assigned capacity is (100+240+260) 600 GB in this case. Assigning any one of the delivery to P2 will bring the capacity under 500. Assigning the D1, D2 and D3 to P2 is increasing the total cost of delivery by 500 (100\*25+240\*15+260\*15-9500), 2400 (100\*20+240\*25+260*15-9500) and 2600 (100\*20+240\*15+260\*25-9500) respectively. Hence, Assigning D1 to P2.

To submit a solution, fork this repo and send a Pull Request on Github.

For any questions or clarifications, raise an issue on this repo and we'll answer your questions as fast as we can.
## Question
https://github.com/RealImage/challenge2019

## Specifications
Language: Ruby
Version: 2.7.1

## Solution-
I developed the solution in Ruby version 2.7.1. There are three files in the project
1. database.rb
This file contain 'Database' class which is the database. It is basically an array of hashes. There are different methods to access the data
2. information_system.rb
This file contains the 'InformationSystem' class. This class contains code to display menu, accept user inputs and process user inputs.
3. main.rb
Main program to execute the whole project.

### How to run this project
1. cd in to the project repository
Example: `cd Workspace/challenge2019`
2. run the main program `ruby main.rb`
27 changes: 27 additions & 0 deletions csv_operations.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
# frozen_string_literal: true

require 'English'
require 'csv'
module CsvOperations
def load_csv(file:)
csv = File.read file.strip
head, *rest = csv.split($INPUT_RECORD_SEPARATOR).map do |row|
row.split(/["']*\s*,\s*['"]*/).map do |col|
col.tr("\r\n", '').delete('\\"')
end
end
rest.map { |row| head.zip(row).to_h }
end

def load_input_csv
inputs = []
CSV.foreach('input.csv', headers: false) do |row|
input_row = {}
input_row['Delivery ID'] = row[0]
input_row['Delivery Size'] = row[1]
input_row['Theatre ID'] = row[2]
inputs.push(input_row)
end
inputs
end
end
30 changes: 30 additions & 0 deletions database.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
# frozen_string_literal: true

# Database with methods to read and write
class Database
attr_reader :data

@@row_id = 0
def initialize(hash_collection = [])
@data = hash_collection
end

def find(key_value_pair: nil, keys: [])
return @data if key_value_pair.nil? && keys.empty?

unless key_value_pair.nil?
filterd_data = @data.select { |hash| pair_present?(hash, key_value_pair) }
return filterd_data if keys.empty?

return filterd_data.map { |hash| hash.slice(*keys) }
end

@data.map { |hash| hash.slice(*keys) }
end

private

def pair_present?(hash, key_value_pair)
Enumerable.instance_method(:include?).bind(hash).call(key_value_pair.to_a.flatten)
end
end
126 changes: 126 additions & 0 deletions information_system.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,126 @@
# frozen_string_literal: true

require './database'
require './csv_operations'
require 'pp'
# Information system to display menu and output results
class InformationSystem
include CsvOperations
def initialize
@partners = Database.new(load_csv(file: 'partners.csv'))
@capacities = Database.new(load_csv(file: 'capacities.csv'))
@input = Database.new(load_input_csv)
end

def display_menu
puts "What would you like to do?
1: Display partners
2: Display capacities
3: Display input
4: Problem Statement 1
5: Problem Statement 2
Any other key to exit"
choice = gets
process_choice(choice)
end

private

def process_choice(choice)
case choice.to_i
when 1
pp @partners.data
display_menu
when 2
pp @capacities.data
display_menu
when 3
pp @input.data
display_menu
when 4
pp problem_statement_1
display_menu
when 5
pp problem_statement_2
display_menu
else
exit
end
end

def problem_statement_1
output = []
@input.data.each do |row|
partner_with_cost = cost_effective_partner(row['Theatre ID'], row['Delivery Size'])
output_row = []
output_row[0] = row['Delivery ID']
output_row[1] = !partner_with_cost.nil?
output_row[2] = partner_with_cost.nil? ? '' : partner_with_cost.first
output_row[3] = partner_with_cost.nil? ? '' : partner_with_cost.last
output.push(output_row)
end
File.write('output1.csv', output.map(&:to_csv).join)
output
end

def problem_statement_2
output = []
balance = @capacities.data.map(&:values).to_h

@input.data.sort_by { |x| x['Delivery Size'] }.each do |input_row|
size = input_row['Delivery Size'].to_i
theatre_id = input_row['Theatre ID']

eligible_partners = possible_partners(theatre_id, size)
if eligible_partners.any?
cheapest_partner, balance = cost_effective_partner_with_balance(eligible_partners, balance, size)

final_price = cost_for_size(cheapest_partner, size)
output.push([input_row['Delivery ID'], 'true', cheapest_partner['Partner ID'], final_price])
else
output.push([input_row['Delivery ID'], 'false', '', ''])
end
end
File.write('output2.csv', output.map(&:to_csv).join)
output
end

def cost_effective_partner_with_balance(eligible_partners, balance, size)
cheapest_partner = nil

eligible_partners.each do |partner|
next unless balance[partner['Partner ID']].to_i >= size

cheapest_partner = partner
balance[partner['Partner ID']] = balance[partner['Partner ID']].to_i - size
break
end
[cheapest_partner, balance]
end

def cost_effective_partner(theatre_id, size)
possible_partner_plans = possible_partners(theatre_id, size)
return if possible_partner_plans.empty?

cost_per_partner = {}
possible_partner_plans.each { |hsh| cost_per_partner[hsh['Partner ID']] = cost_for_size(hsh, size) }
cost_per_partner.min_by(&:last)
end

def possible_partners(theatre_id, size)
partners_for_theatre = @partners.find(key_value_pair: { 'Theatre' => theatre_id })
return nil if partners_for_theatre.empty?

partners_for_theatre.select do |x|
Range.new(*x['Size Slab (in GB)'].split('-').map(&:to_i)).include?(size.to_i)
end
end

def cost_for_size(plan, size)
total_cost = plan['Cost Per GB'].to_i * size.to_i
return plan['Minimum cost'].to_i if total_cost < plan['Minimum cost'].to_i

total_cost
end
end

12 changes: 12 additions & 0 deletions main.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
# frozen_string_literal: true

require './information_system'
begin
puts 'Initializing information system'
info = InformationSystem.new
info.display_menu
rescue StandardError => e
puts "Exception: #{e}"
ensure
puts 'Exiting the program'
end
6 changes: 3 additions & 3 deletions output1.csv
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
D1,true ,P1,2000
D2,true ,P1,3250
D3,true ,P3,15300
D1,true,P1,2000
D2,true,P1,3250
D3,true,P3,15300
D4,false,"",""
6 changes: 3 additions & 3 deletions output2.csv
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
D1,true ,P2,3000
D2,true ,P1,3250
D3,true ,P3,15300
D1,true,P1,2000
D2,true,P2,3500
D3,true,P3,15300
D4,false,"",""