diff --git a/README.md b/README.md index 5ea87fa..b90d4a4 100644 --- a/README.md +++ b/README.md @@ -73,6 +73,18 @@ D1, false, "", "" D2, true, P1, 4500 ``` +## Check Solution 1 + +Run in console ```irb``` command. +Run in irb console this lines: +``` +require_relative 'lib/services/deliveries_processor' + +Services::DeliveriesProcessor.new.call +``` + +It should generate **result_1.csv** file with result. + ## Problem Statement 2 Each partner specifies the **maximum capacity** they can serve, across all their deliveries in following manner: @@ -114,3 +126,15 @@ D3, true, P1, 3900 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. + +## Check Solution 2 + +Run in console ```irb``` command. +Run in irb console this lines: +``` +require_relative 'lib/services/deliveries_processor_by_capacity' + +Services::DeliveriesProcessorByCapacity.new.call +``` + +It should generate **result_2.csv** file with result. diff --git a/lib/parsers/csv/parser.rb b/lib/parsers/csv/parser.rb new file mode 100644 index 0000000..68e5c50 --- /dev/null +++ b/lib/parsers/csv/parser.rb @@ -0,0 +1,43 @@ +require_relative '../file_parser' +require 'csv' + +module Parsers + module Csv + class Parser < Parsers::FileParser + def parse + CSV.foreach(file_name, parser_opts) do |row| + content << parsed_row(row) + end + content + end + + private + + def parser_opts + { + headers: !!opts[:headers], + col_sep: opts[:col_sep] || ',' + } + end + + def parsed_row(row) + opts[:schema].each_with_object({}) do |(field_name, field_params), h| + h[field_name] = format_field(row[field_params[:index]], field_params) + end + end + + def format_field(field, field_params) + striped_field = field.strip + + formated_field = case field_params[:type].to_s + when 'Integer' + striped_field.to_i + when 'Range' + Range.new(*striped_field.split(/\-/).map(&:to_i)) + else + striped_field + end + end + end + end +end diff --git a/lib/parsers/csv/schemas.rb b/lib/parsers/csv/schemas.rb new file mode 100644 index 0000000..31e6815 --- /dev/null +++ b/lib/parsers/csv/schemas.rb @@ -0,0 +1,52 @@ +module Parsers + module Csv + module Schemas + INPUT_SCHEMA = { + delivery_id: { + index: 0, + type: ::String + }, + delivery_size: { + index: 1, + type: ::Integer + }, + theatre_id: { + index: 2, + type: ::String + } + }.freeze + PARTNERS_SCHEMA = { + theatre_id: { + index: 0, + type: ::String + }, + slab_size: { + index: 1, + type: ::Range + }, + min_cost: { + index: 2, + type: ::Integer + }, + gb_cost: { + index: 3, + type: ::Integer + }, + partner_id: { + index: 4, + type: ::String + } + }.freeze + CAPACITY_SCHEMA = { + partner_id: { + index: 0, + type: ::String + }, + capacity: { + index: 1, + type: ::Integer + } + } + end + end +end diff --git a/lib/parsers/file_parser.rb b/lib/parsers/file_parser.rb new file mode 100644 index 0000000..074d44a --- /dev/null +++ b/lib/parsers/file_parser.rb @@ -0,0 +1,16 @@ +module Parsers + class FileParser + attr_reader :file_name, :opts + attr_accessor :content + + def initialize(file_name:, **opts) + @file_name = file_name + @opts = opts + @content = [] + end + + def parse + raise NotImplementedError + end + end +end diff --git a/lib/services/deliveries_processor.rb b/lib/services/deliveries_processor.rb new file mode 100644 index 0000000..f334a49 --- /dev/null +++ b/lib/services/deliveries_processor.rb @@ -0,0 +1,77 @@ +require_relative '../parsers/csv/parser' +require_relative '../parsers/csv/schemas' + +module Services + class DeliveriesProcessor + INPUT_FILENAME = 'input.csv' + PARTNERS_FILENAME = 'partners.csv' + RESULT_FILENAME = 'result_1.csv'.freeze + + attr_reader :input_parser, :partners_parser + + def initialize(file_name = INPUT_FILENAME) + @input_parser = Parsers::Csv::Parser.new( + file_name: file_name, + schema: Parsers::Csv::Schemas::INPUT_SCHEMA + ) + @partners_parser = Parsers::Csv::Parser.new( + file_name: PARTNERS_FILENAME, + schema: Parsers::Csv::Schemas::PARTNERS_SCHEMA, + headers: true + ) + end + + def call + delete_csv(RESULT_FILENAME) + + deliveries.each do |delivery_row| + proposals_by_theatre = proposals_by_theatre(delivery_row, partners) + proposal = proposals_by_theatre.min_by { |delivery_row| delivery_row[:price] } + + write_to_csv(delivery_row, proposal) + end + end + + private + + def deliveries + @deliveries ||= input_parser.parse + end + + def partners + @partners ||= partners_parser.parse + end + + def proposals_by_theatre(delivery_row, partners) + partners.each_with_object([]) do |partner_row, partner_row_with_price| + next if !proposal_available?(partner_row, delivery_row) + + delivery_price = delivery_row[:delivery_size] * partner_row[:gb_cost] + delivery_price = partner_row[:min_cost] if delivery_price < partner_row[:min_cost] + + partner_row_with_price << partner_row.merge(price: delivery_price) + end + end + + def write_to_csv(delivery_row, partner_row) + CSV.open('result_1.csv', 'a+') do |csv| + csv << build_row(delivery_row, partner_row) + end + end + + def build_row(delivery_row, partner_row) + return [delivery_row[:delivery_id], false, '', ''] if !partner_row + + [delivery_row[:delivery_id], true, partner_row[:partner_id], partner_row[:price]] + end + + def proposal_available?(partner_row, delivery_row) + partner_row[:theatre_id] == delivery_row[:theatre_id] && + partner_row[:slab_size].include?(delivery_row[:delivery_size]) + end + + def delete_csv(file_name) + File.delete(file_name) if File.exists? file_name + end + end +end diff --git a/lib/services/deliveries_processor_by_capacity.rb b/lib/services/deliveries_processor_by_capacity.rb new file mode 100644 index 0000000..d384492 --- /dev/null +++ b/lib/services/deliveries_processor_by_capacity.rb @@ -0,0 +1,97 @@ +require_relative 'deliveries_processor' + +module Services + class DeliveriesProcessorByCapacity < DeliveriesProcessor + CAPACITIES_FILENAME = 'capacities.csv' + RESULT_FILENAME = 'result_2.csv'.freeze + + attr_reader :capacities_parser, :result + + def initialize(file_name = INPUT_FILENAME) + super + + @capacities_parser = Parsers::Csv::Parser.new( + file_name: CAPACITIES_FILENAME, + schema: Parsers::Csv::Schemas::CAPACITY_SCHEMA, + headers: true + ) + @result = [] + end + + def call + delete_csv(RESULT_FILENAME) + + deliveries.each do |delivery_row| + proposals_by_theatre = proposals_by_theatre(delivery_row, partners) + delivery_row[:proposals] = proposals_by_theatre.sort_by { |proposal| proposal[:price] } + + result << delivery_row + end + + process_proposals_by_capacity + + result.each do |delivery| + write_to_csv(delivery) + end + end + + private + + def capacities + @capacities ||= capacities_parser.parse + end + + def process_proposals_by_capacity + capacities.each do |hash| + partner_deliveries = result.select do |delivery| + delivery[:proposals].any? && + delivery[:proposals][0][:partner_id] == hash[:partner_id] + end + + next if partner_deliveries.empty? + + sum_delivery_sizes = partner_deliveries.sum { |h| h[:delivery_size] } + + if sum_delivery_sizes <= hash[:capacity] + next + else + partner_deliveries[0][:proposals].delete_if do |proposal| + proposal[:partner_id] == hash[:partner_id] + end + + process_proposals_by_capacity + end + end + end + + def proposals_by_theatre(delivery_row, partners_rows) + partners_rows.each_with_object([]) do |partner_row, partner_row_with_price| + next if !proposal_available?(partner_row, delivery_row) + + delivery_price = delivery_row[:delivery_size] * partner_row[:gb_cost] + delivery_price = partner_row[:min_cost] if delivery_price < partner_row[:min_cost] + + partner_row_with_price << partner_row.merge(price: delivery_price) + end + end + + def write_to_csv(delivery) + CSV.open('result_2.csv', 'a+') do |csv| + csv << build_row(delivery) + end + end + + def build_row(delivery) + return [delivery[:delivery_id], false, '', ''] if delivery[:proposals].empty? + + best_proposal = delivery[:proposals][0] + + [delivery[:delivery_id], true, best_proposal[:partner_id], best_proposal[:price]] + end + + def proposal_available?(partner_row, delivery_row) + partner_row[:theatre_id] == delivery_row[:theatre_id] && + partner_row[:slab_size].include?(delivery_row[:delivery_size]) + end + end +end