Skip to content

Instantly share code, notes, and snippets.

@vgarro
Created June 2, 2022 00:20
Show Gist options
  • Save vgarro/10614c6a7b2c8383033715d12311f96b to your computer and use it in GitHub Desktop.
Save vgarro/10614c6a7b2c8383033715d12311f96b to your computer and use it in GitHub Desktop.
NFX Coding challenge
# 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