Created
June 2, 2022 00:20
-
-
Save vgarro/10614c6a7b2c8383033715d12311f96b to your computer and use it in GitHub Desktop.
NFX Coding challenge
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
# Use coderpad (https://coderpad.io/demo). | |
# This prompt can be done in any order | |
# Write a script that can be run in the terminal that can: | |
# -- Take in and validate data about an individual startup and store in memory in a list of startups. | |
# - Company name (Can’t be empty) | |
# - Founder first name (Can’t be empty) | |
# - Founder last name (Can’t be empty) | |
# - Fundraising stage (Allowed values: Pre-seed, Seed, Series A, Series B) | |
# - Fundraising target minimum (Can’t be a negative number or greater than Fundraising target maximum) | |
# - Fundraising target maximum (Can’t be a negative number or lower than Fundraising target minimum) | |
# - Amount raised (Can’t be a negative number) | |
# # Example: | |
# Company name: NFX | |
# First name: James | |
# Last name: Currier | |
# Firm name: NFX Guild | |
# Fundraising stage: Series A | |
# Fundraising target minimum: $500,000 | |
# Fundraising target maximum: $1,200,000 | |
# Amount raised so far: $220,000 | |
# -- Output the list of startups stored from the first feature (simple built-in output is fine) | |
# - In addition to the output, allow sorting and filtering: | |
# - Sorting: | |
# ---- company name (default if no sort is selected) | |
# ---- founder first name | |
# ---- founder last name | |
# ---- fundraising stage (In this order - Pre-seed, Seed, Series A, Series B) | |
# ---- fundraising target minimum | |
# ---- fundraising target maximum | |
# ---- amount raised so far | |
# - Filter by: | |
# ---- Company name (i.e. filtering ‘a’ will return “Astro Inc”) | |
# ---- Founder full name (i.e. filtering by ‘Ja’ will return James Currier, Jack Johnson, etc.) | |
# ---- Fundraising stage (multiple selection - i.e. filtering by Series A, Seed - will only return startups who are Series A or Seed) | |
# ---- Fundraising target range (filters available - 0-50k, 50k-250k, 250k - 1M, 1M - 5M, 5M - 15M, 15M+) (i.e. choosing 0-50k will bring back startups whose min, and max fundraising target range are within those bounds) | |
# Here is some sample startup inputs for the script to take in: | |
# { | |
# “company_name”: “NFX”, | |
# "founder_first_name": "James", | |
# "founder_last_name": "Currier", | |
# "fundraising_stage": "Series A", | |
# "fundraising_target_min": 500000, | |
# "fundraising_target_max": 1000000, | |
# "amount_raised": 120000 | |
# } | |
# { | |
# “company_name”: “Acme Inc.”, | |
# "founder_first_name": "Wile", | |
# "founder_last_name": "Coyote", | |
# "fundraising_stage": "Seed", | |
# "fundraising_target_min": 100000, | |
# "fundraising_target_max": 500000, | |
# "amount_raised": 10000 | |
# } | |
# { | |
# “company_name”: “Wayne Industries”, | |
# "founder_first_name": "Bruce", | |
# "founder_last_name": "Wayne", | |
# "fundraising_stage": "Series B", | |
# "fundraising_target_min": 20000000, | |
# "fundraising_target_max": 80000000, | |
# "amount_raised": 15000000 | |
# } | |
class StartUp | |
attr_reader :company_name, :founder_first_name, :founder_last_name, :fundraising_stage, :fundraising_target_min, :fundraising_target_max, :amount_raised | |
# TODO: Perhaps move to it's own Fundraising class? | |
def initialize(attributes = {}) | |
@company_name = attributes[:company_name] | |
@founder_first_name = attributes[:founder_first_name] | |
@founder_last_name = attributes[:founder_last_name] | |
@fundraising_stage = attributes[:fundraising_stage] | |
@fundraising_target_min = attributes[:fundraising_target_min] | |
@fundraising_target_max = attributes[:fundraising_target_max] | |
@amount_raised = attributes[:amount_raised] | |
end | |
def full_name | |
"#{founder_first_name} #{founder_last_name}" | |
end | |
def fundrasing_stage_sorting_value | |
["Pre-seed", "Seed", "Series A", "Series B"].index(@fundraising_stage) | |
end | |
end | |
class StartUpValidation | |
attr_reader :errors | |
def initialize(attributes = {}) | |
@errors = [] | |
end | |
FUNDRACING_STAGES = ["Pre-seed", "Seed", "Series A", "Series B"] | |
def valid? | |
errors << "CompanyName can't be blank" if empty?(:company_name) | |
errors << "founder_first_name can't be blank" if empty?(:founder_first_name) | |
errors << "founder_last_name can't be blank" if empty?(:founder_last_name) | |
raise "fundraising_stage Should be within valid values" unless within_values?(:fundraising_stage, FUNDRACING_STAGES) | |
errors << "fundraising_target_min can't be blank" unless positive_number?(:fundraising_target_min) | |
errors << "fundraising_target_max can't be blank" unless positive_number?(:fundraising_target_max) | |
errors << "amount_raised can't be blank" unless positive_number?(:amount_raised) | |
errors << "fundraising_target_min should be lower than fundraising_target_max" unless compare_against_operator?(:fundraising_target_min, :fundraising_target_max, ">") | |
errors << "fundraising_target_max should be greater than fundraising_target_min" unless compare_against_operator?(:fundraising_target_max, :fundraising_target_min, "<") | |
return errors.empty? | |
end | |
private | |
def empty?(field) | |
attributes[field].blank? | |
end | |
def within_values?(field, range) | |
range.include?(attributes[field]) | |
end | |
def positive_number?(field) | |
!attributes[field] > 0 | |
end | |
def compare_against_operator?(field, comparasion_field, operator) | |
!attributes[field].send(operator, comparasion_field) | |
end | |
end | |
class SortBy | |
attr_reader :collection, :sorting_field | |
def initialize(collection, sorting_field) | |
@collection = collection | |
@sorting_field = sorting_field | |
end | |
def sort | |
collection.sort_by { |item| item.send(sorting_field.to_sym) } | |
end | |
end | |
class Filtering | |
attr_reader :collection, :filter_key, :filter_value | |
def initialize(collection, filter_key, filter_value) | |
@collection = collection | |
@filter_key = filter_key | |
@filter_value = filter_value | |
end | |
def filter | |
if (filter_key == :company_name) | |
CompanyNameFilter.new(collection, filter_value).filter! | |
elsif (filter_key == :founder_full_name) | |
FounderFullNameFilter.new(collection, filter_value).filter! | |
elsif (filter_key == :fundrasing_state) | |
FundrasingStateFilter.new(collection, filter_value).filter! | |
elsif (filter_key == :fundrasing_targer) | |
FundraisingTargetRangeFilter.new(collection, filter_value).filter! | |
else | |
@collection | |
end | |
end | |
# Each filter class has different rules, therefore using a Strategy Pattern here | |
# ---- Company name (i.e. filtering ‘a’ will return “Astro Inc”) | |
class CompanyNameFilter | |
attr_reader :collection, :filter_value | |
def initialize(collection, filter_value) | |
@collection = collection | |
@filter_value = filter_value | |
end | |
def filter! | |
@collection.select { |item | item.company_name.downcase.include?(filter_value) } | |
end | |
end | |
# ---- Founder full name (i.e. filtering by ‘Ja’ will return James Currier, Jack Johnson, etc.) | |
class FounderFullNameFilter | |
attr_reader :collection, :filter_value | |
def initialize(collection, filter_value) | |
@collection = collection | |
@filter_value = filter_value | |
end | |
def filter! | |
@collection.select { |item | item.full_name.downcase.include?(filter_value) } | |
end | |
end | |
# ---- Fundraising stage (multiple selection - i.e. filtering by Series A, Seed - will only return startups who are Series A or Seed) | |
class FundrasingStateFilter | |
attr_reader :collection, :filter_value | |
def initialize(collection, filter_value) | |
@collection = collection | |
@filter_value = filter_value | |
end | |
def filter! | |
@collection.select { |item | stage_options.include?(item.fundraising_stage) } | |
end | |
private | |
def stage_options | |
filter_value.split(",") | |
end | |
end | |
# ---- Fundraising target range (filters available - 0-50k, 50k-250k, 250k - 1M, 1M - 5M, 5M - 15M, 15M+) (i.e. choosing 0-50k will bring back startups whose min, | |
# and max fundraising target range are within those bounds) | |
class FundraisingTargetRangeFilter | |
attr_reader :collection, :filter_value | |
def initialize(collection, filter_value) | |
@collection = collection | |
@filter_value = filter_value | |
end | |
def filter! | |
@collection.select { |item | item.company_name.downcase.include?(filter_value) } | |
end | |
private | |
end | |
end | |
class StartupsApi | |
# Consider moving to a Singleton since it's really a Database | |
attr_reader :startup_list | |
def initialize() | |
@startup_list = [] | |
end | |
def create(attributes = {}) | |
# TODO: Implement validation | |
valid = StartUpValidation.new(attributes).valid? | |
if valid | |
@startup_list << StartUp.new(attributes) | |
else | |
# print errors to customer | |
end | |
end | |
def sort(sorting_field = :company_name) | |
SortBy.new(startup_list, sorting_field_map(sorting_field)).sort | |
end | |
def filter(filter_key, filter_values = []) | |
Filtering.new(startup_list, filter_key, filter_values).filter | |
end | |
private | |
SORT_MAP = { fundrasing_stage: :fundrasing_stage_sorting_value } | |
def sorting_field_map(sorting_field) | |
SORT_MAP[sorting_field] || sorting_field | |
end | |
end | |
testing_startups = [ | |
{ | |
company_name: "NFX", | |
founder_first_name: "James", | |
founder_last_name: "Currier", | |
fundraising_stage: "Series A", | |
fundraising_target_min: 500000, | |
fundraising_target_max: 1000000, | |
amount_raised: 120000 | |
}, | |
{ | |
company_name: "Acme Inc.", | |
founder_first_name: "Wile", | |
founder_last_name: "Coyote", | |
fundraising_stage: "Seed", | |
fundraising_target_min: 100000, | |
fundraising_target_max: 500000, | |
amount_raised: 10000 | |
}, | |
{ | |
company_name: "Wayne Industries", | |
founder_first_name: "Bruce", | |
founder_last_name: "Wayne", | |
fundraising_stage: "Series B", | |
fundraising_target_min: 20000000, | |
fundraising_target_max: 80000000, | |
amount_raised: 15000000 | |
} | |
] | |
testing_failty_startups = [ | |
{ | |
company_name: nil, | |
founder_first_name: '', | |
founder_last_name: "Coyote", | |
fundraising_stage: "Seed", | |
fundraising_target_min: 100000, | |
fundraising_target_max: 500000, | |
amount_raised: 10000 | |
}, | |
] | |
# | |
startup_api = StartupsApi.new | |
# Create the list of records | |
testing_startups.each do |startup| | |
startup_api.create(startup) | |
end | |
puts "Context Creating Start Ups" | |
puts "List of Startup shoudl be 3: #{startup_api.startup_list.size == testing_startups.size}" | |
puts "Context: Sorting" | |
sorted_results = startup_api.sort(:company_name) | |
puts "Sorting by Company Name should return Acme Industries first : #{sorted_results.first.company_name == 'Acme Inc.'} " | |
sorted_results = startup_api.sort(:founder_first_name) | |
puts "Sorting by Founder First Name should return Bruce first : #{sorted_results.first.founder_first_name == 'Bruce'} " | |
sorted_results = startup_api.sort(:founder_last_name) | |
puts "Sorting by Founder Last Name should return Coyote first : #{sorted_results.first.founder_last_name == 'Coyote'} " | |
sorted_results = startup_api.sort(:fundraising_stage) | |
puts "Sorting FundRacing Stage Name should return Seed as first item : #{sorted_results.first.fundraising_stage == 'Seed'} " | |
# .... etc | |
# .... etc | |
puts "Context Filtering" | |
company_name_filter_results = startup_api.filter(:company_name, "a") | |
puts "Filtering by Compnay name with `a` should return 2 records #{company_name_filter_results.size == 2}" | |
puts "Filtering by Compnay name with `a` should include a #{company_name_filter_results.first.company_name.downcase.include?("a")}" | |
founder_full_name_filter_results = startup_api.filter(:founder_full_name, "a") | |
puts "Filtering by Compnay name with `a` should return 2 records #{founder_full_name_filter_results.size == 2}" | |
puts "Filtering by Compnay name with `a` should include a #{founder_full_name_filter_results.first.founder_first_name == "James"}" |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment