CodingChallenge
Had fun with this. I published it as gem here: https://rubygems.org/gems/coding_challenge.
Quickstart
gem install coding_challenge
then
coding_challenge start
Screenshots
Installation
- via cloning
Clone repo. Then in project directory bundle install
.
- via rubygems
gem install coding_challenge
Usage
To run with a cool CLI UI at the start, either do:
- (IF CLONED) Go to project directory and:
./exe/coding_challenge start [product type] [options]
- (IF INSTALLED AS GEM) from CLI do:
coding_challenge start [product type] [options]
To run without a cool CLI UI at the start but have it appear after, either do:
- (IF CLONED) Go to project directory and:
./exe/coding_challenge start [product type] [options] --skip_intro_animation=true
- (IF INSTALLED AS GEM) from CLI do:
coding_challenge start [product type] [options] --skip_intro_animation=true
Testing
Clone and go to project directory and do rspec spec
Code Explanation
Since the product list is represented as an array, there is NO way to solve this problem in O(1) time.
At the very least, any solution is going to require 1 full iteration through the product list. What we can do, is create a hash based schema that will group all of the product types, option types, and option values as keys in a logical hierachy so that afterwards, detecting the prescence of data can be done simply by attempting to access it from the hash as a key O(1) time.
I tried to do this compactly/elegantly by creating the following method and having it execute immediately after reading the products list:
def index_product_schema(products_list)
products_schema = {}
products_list.each do |p|
if !products_schema.key?(p['product_type'])
products_schema[p['product_type']] = p['options'].transform_values { |o| Hash[o, true] }
else
products_schema[p['product_type']].merge!(p['options']) { |_, o, n| o.merge(Hash[n, true]) }
end
end
products_schema
end
The above operation going to require one loop through the product list and then for each item, a nested loop that runs for the number of option types that exist for that item.
The generated products schema would look like this:
{"tshirt"=>{"gender"=>{"male"=>true, "female"=>true}, "color"=>{"red"=>true, "green"=>true, "navy"=>true, "white"=>true, "black"=>true}, "size"=>{"small"=>true, "medium"=>true, "large"=>true, "extra-large"=>true, "2x-large"=>true}}, "mug"=>{"type"=>{"coffee-mug"=>true, "travel-mug"=>true}}, "sticker"=>{"size"=>{"x-small"=>true, "small"=>true, "medium"=>true, "large"=>true, "x-large"=>true}, "style"=>{"matte"=>true, "glossy"=>true}}}
Run Time ~> sum of O(num_option_typesi) where i goes from 1 to the length of the products_list array
Next, the following method below will execute using the product_schema produced by index_product_schema
.
You can see that validating the precesence of/accessing the options schema for a particular product type is done in O(1) in the first line.
In an option schema for a given product type, the keys are option types and the values are hashes that contain keys every possible option value for a given option type. ex for sticker:
{"size"=>{"x-small"=>true, "small"=>true, "medium"=>true, "large"=>true, "x-large"=>true}, "style"=>{"matte"=>true, "glossy"=>true}}
We can now iterate through all of the option types/option values pairs using an index value (arg_position) to keep track of our position in the hash.
Validating an options argument against possible option values is done using the index to match up the CLI argument at the position of the index to the current option types/option values pair. We do this in O(1) time by seeing if the argument exists as a key in the option values pair.
The current option values hash is transformed into a friendly string by joining the keys together with commas.
The main loop will execute for the number of option types for a given product type regardless of the size of the cli arguments input, which is a respective constant for each product type, so it is there for O(1).
def handle_query(query)
product_options_schema = @products_schema[query.product_type.downcase]
is_invalid_product_type = product_options_schema.nil?
raise InvalidProductTypeError, query.product_type if is_invalid_product_type
results = []
product_options_schema.each_with_index do |(option_type, option_values_map), arg_position|
option_argument = query.options[arg_position]
is_argument_provided = !option_argument.nil?
if is_argument_provided
is_invalid_argument = !option_values_map.key?(option_argument)
raise InvalidOptionError.new(query.product_type, option_type, option_argument) if is_invalid_argument
else
possible_option_values = option_values_map.keys
results << "#{option_type.capitalize}: #{possible_option_values.join(', ')}"
end
end
query.results = results
query
end
Contributing
Bug reports and pull requests are welcome on GitHub at https://github.com/[USERNAME]/coding_challenge. This project is intended to be a safe, welcoming space for collaboration, and contributors are expected to adhere to the code of conduct.
Code of Conduct
Everyone interacting in the CodingChallenge project's codebases, issue trackers, chat rooms and mailing lists is expected to follow the code of conduct.
Copyright
Copyright (c) 2020 Jorge Navarro. See MIT License for further details.