Initial commit

This commit is contained in:
2023-12-17 00:26:14 -05:00
commit 59d97be20c
89 changed files with 3184 additions and 0 deletions

View File

@@ -0,0 +1,24 @@
{
"authors": [
"angelikatyborska"
],
"contributors": [
"neenjaw"
],
"files": {
"solution": [
"lib/freelancer_rates.ex"
],
"test": [
"test/freelancer_rates_test.exs"
],
"exemplar": [
".meta/exemplar.ex"
]
},
"language_versions": ">=1.10",
"forked_from": [
"javascript/freelancer-rates"
],
"blurb": "Learn about integers and floating point numbers by helping a freelancer communicate with a project manager about billing."
}

View File

@@ -0,0 +1 @@
{"track":"elixir","exercise":"freelancer-rates","id":"25db5ef05b9b40a09965866b08a13f3f","url":"https://exercism.org/tracks/elixir/exercises/freelancer-rates","handle":"negrienko","is_requester":true,"auto_approve":false}

View File

@@ -0,0 +1,4 @@
# Used by "mix format"
[
inputs: ["{mix,.formatter}.exs", "{config,lib,test}/**/*.{ex,exs}"]
]

24
elixir/freelancer-rates/.gitignore vendored Normal file
View File

@@ -0,0 +1,24 @@
# The directory Mix will write compiled artifacts to.
/_build/
# If you run "mix test --cover", coverage assets end up here.
/cover/
# The directory Mix downloads your dependencies sources to.
/deps/
# Where third-party dependencies like ExDoc output generated docs.
/doc/
# Ignore .fetch files in case you like to edit your project deps locally.
/.fetch
# If the VM crashes, it generates a dump, let's ignore it too.
erl_crash.dump
# Also ignore archive artifacts (built via "mix archive.build").
*.ez
# Ignore package tarball (built via "mix hex.build").
numbers-*.tar

View File

@@ -0,0 +1,75 @@
# Help
## Running the tests
From the terminal, change to the base directory of the exercise then execute the tests with:
```bash
$ mix test
```
This will execute the test file found in the `test` subfolder -- a file ending in `_test.exs`
Documentation:
* [`mix test` - Elixir's test execution tool](https://hexdocs.pm/mix/Mix.Tasks.Test.html)
* [`ExUnit` - Elixir's unit test library](https://hexdocs.pm/ex_unit/ExUnit.html)
## Pending tests
In test suites of practice exercises, all but the first test have been tagged to be skipped.
Once you get a test passing, you can unskip the next one by commenting out the relevant `@tag :pending` with a `#` symbol.
For example:
```elixir
# @tag :pending
test "shouting" do
assert Bob.hey("WATCH OUT!") == "Whoa, chill out!"
end
```
If you wish to run all tests at once, you can include all skipped test by using the `--include` flag on the `mix test` command:
```bash
$ mix test --include pending
```
Or, you can enable all the tests by commenting out the `ExUnit.configure` line in the file `test/test_helper.exs`.
```elixir
# ExUnit.configure(exclude: :pending, trace: true)
```
## Useful `mix test` options
* `test/<FILE>.exs:LINENUM` - runs only a single test, the test from `<FILE>.exs` whose definition is on line `LINENUM`
* `--failed` - runs only tests that failed the last time they ran
* `--max-failures` - the suite stops evaluating tests when this number of test failures
is reached
* `--seed 0` - disables randomization so the tests in a single file will always be ran
in the same order they were defined in
## Submitting your solution
You can submit your solution using the `exercism submit lib/freelancer_rates.ex` command.
This command will upload your solution to the Exercism website and print the solution page's URL.
It's possible to submit an incomplete solution which allows you to:
- See how others have completed the exercise
- Request help from a mentor
## Need to get help?
If you'd like help solving the exercise, check the following pages:
- The [Elixir track's documentation](https://exercism.org/docs/tracks/elixir)
- The [Elixir track's programming category on the forum](https://forum.exercism.org/c/programming/elixir)
- [Exercism's programming category on the forum](https://forum.exercism.org/c/programming/5)
- The [Frequently Asked Questions](https://exercism.org/docs/using/faqs)
Should those resources not suffice, you could submit your (incomplete) solution to request mentoring.
If you're stuck on something, it may help to look at some of the [available resources](https://exercism.org/docs/tracks/elixir/resources) out there where answers might be found.

View File

@@ -0,0 +1,30 @@
# Hints
## General
- Read about basic arithmetic in the official [Getting Started guide][getting-started-basic-arithmetic].
- Browse the [`Float`][float-functions] and [`Kernel`][kernel-arithmetic-operators] modules to learn about functions and operators that work with floats.
## 1. Calculate the daily rate given an hourly rate
- [Basic arithmetic operations][kernel-arithmetic-operators] where one argument is an integer, and the other is a float, will return a float.
## 2. Calculate a discounted price
- [Basic arithmetic operations][kernel-arithmetic-operators] where one argument is an integer, and the other is a float, will return a float.
## 3. Calculate the monthly rate, given an hourly rate and a discount
- There is a [built-in function][kernel-trunc] for changing floats to integers.
- There is a [built-in function][float-ceil] for rounding floats up.
## 4. Calculate the number of workdays given a budget, hourly rate and discount
- There is a [built-in function][float-floor] for rounding floats down with desired precision.
[getting-started-basic-arithmetic]: https://elixir-lang.org/getting-started/basic-types.html#basic-arithmetic
[kernel-arithmetic-operators]: https://hexdocs.pm/elixir/Kernel.html#*/2
[kernel-trunc]: https://hexdocs.pm/elixir/Kernel.html#trunc/1
[float-functions]: https://hexdocs.pm/elixir/Float.html#functions
[float-ceil]: https://hexdocs.pm/elixir/Float.html#ceil/2
[float-floor]: https://hexdocs.pm/elixir/Float.html#floor/2

View File

@@ -0,0 +1,125 @@
# Freelancer Rates
Welcome to Freelancer Rates on Exercism's Elixir Track.
If you need help running the tests or submitting your code, check out `HELP.md`.
If you get stuck on the exercise, check out `HINTS.md`, but try and solve it without using those first :)
## Introduction
## Integers
There are two different kinds of numbers in Elixir - integers and floats.
Integers are whole numbers.
```elixir
integer = 3
# => 3
```
## Floating Point Numbers
Floats are numbers with one or more digits behind the decimal separator. They use the 64-bit double precision floating-point format.
```elixir
float = 3.45
# => 3.45
```
### Working with numbers
In the [`Integer`][integer-functions] and [`Float`][float-functions] modules you can find some useful functions for working with those types. Basic arithmetic operators are defined in the [`Kernel`][kernel-arithmetic-operators] module.
### Conversion
Integers and floats can be mixed together in a single arithmetic expression. Using a float in an expression ensures the result will be a float too.
```elixir
2 * 3
# => 6
2 * 3.0
# => 6.0
```
However, when doing division, the result will always be a float, even if only integers are used.
```elixir
6 / 2
# => 3.0
```
To convert a float to an integer, you can discard the decimal part with [`trunc/1`][trunc-1].
[integer-functions]: https://hexdocs.pm/elixir/Integer.html#functions
[float-functions]: https://hexdocs.pm/elixir/Float.html#functions
[kernel-arithmetic-operators]: https://hexdocs.pm/elixir/Kernel.html#*/2
[trunc-1]: https://hexdocs.pm/elixir/Kernel.html#trunc/1
## Instructions
In this exercise you'll be writing code to help a freelancer communicate with a project manager by providing a few utilities to quickly calculate daily and
monthly rates, optionally with a given discount.
We first establish a few rules between the freelancer and the project manager:
- The daily rate is 8 times the hourly rate.
- A month has 22 billable days.
Sometimes, the freelancer is offering to apply a discount on their daily rate (for example for their most loyal customers or for non-for-profit customers).
Discounts are modeled as fractional numbers representing percentage, for example `25.0` (25%).
## 1. Calculate the daily rate given an hourly rate
Implement a function to calculate the daily rate given an hourly rate:
```elixir
FreelancerRates.daily_rate(60)
# => 480.0
```
The returned daily rate should be a float.
## 2. Calculate a discounted price
Implement a function to calculate the price after a discount.
```elixir
FreelancerRates.apply_discount(150, 10)
# => 135.0
```
The returned value should always be a float, not rounded in any way.
## 3. Calculate the monthly rate, given an hourly rate and a discount
Implement a function to calculate the monthly rate, and apply a discount:
```elixir
FreelancerRates.monthly_rate(77, 10.5)
# => 12130
```
The returned monthly rate should be rounded up (take the ceiling) to the nearest integer.
## 4. Calculate the number of workdays given a budget, hourly rate and discount
Implement a function that takes a budget, an hourly rate, and a discount, and calculates how many days of work that covers.
```elixir
FreelancerRates.days_in_budget(20000, 80, 11.0)
# => 35.1
```
The returned number of days should be rounded down (take the floor) to one decimal place.
## Source
### Created by
- @angelikatyborska
### Contributed to by
- @neenjaw

View File

@@ -0,0 +1,28 @@
defmodule FreelancerRates do
def daily_rate(hourly_rate) do
8.0 * hourly_rate
end
def monthly_rate(hourly_rate) do
22.0 * daily_rate(hourly_rate)
end
def monthly_rate(hourly_rate, discount) do
hourly_rate
|> monthly_rate()
|> apply_discount(discount)
|> ceil()
end
def apply_discount(before_discount, discount) do
before_discount - (before_discount * (discount / 100))
end
def days_in_budget(budget, hourly_rate, discount) do
hourly_rate
|> daily_rate()
|> apply_discount(discount)
|> then(fn rate -> budget / rate end)
|> Float.floor(1)
end
end

View File

@@ -0,0 +1,28 @@
defmodule FreelancerRates.MixProject do
use Mix.Project
def project do
[
app: :freelancer_rates,
version: "0.1.0",
# elixir: "~> 1.10",
start_permanent: Mix.env() == :prod,
deps: deps()
]
end
# Run "mix help compile.app" to learn about applications.
def application do
[
extra_applications: [:logger]
]
end
# Run "mix help deps" to learn about dependencies.
defp deps do
[
# {:dep_from_hexpm, "~> 0.3.0"},
# {:dep_from_git, git: "https://github.com/elixir-lang/my_dep.git", tag: "0.1.0"}
]
end
end

View File

@@ -0,0 +1,89 @@
defmodule FreelancerRatesTest do
use ExUnit.Case
describe "daily_rate/1" do
@tag task_id: 1
test "it's the hourly_rate times 8" do
assert FreelancerRates.daily_rate(50) == 400.0
end
@tag task_id: 1
test "it always returns a float" do
assert FreelancerRates.daily_rate(60) === 480.0
end
@tag task_id: 1
test "it does not round" do
assert FreelancerRates.daily_rate(55.1) == 440.8
end
end
describe "apply_discount/2" do
@tag task_id: 2
test "a discount of 10% leaves 90% of the original price" do
assert FreelancerRates.apply_discount(140.0, 10) == 126.0
end
@tag task_id: 2
test "it always returns a float" do
assert FreelancerRates.apply_discount(100, 10) == 90.0
end
@tag task_id: 2
test "it doesn't round" do
assert_in_delta FreelancerRates.apply_discount(111.11, 13.5), 96.11015, 0.000001
end
end
describe "monthly_rate/2" do
@tag task_id: 3
test "it's the daily_rate times 22" do
assert FreelancerRates.monthly_rate(62, 0.0) == 10_912
end
@tag task_id: 3
test "it always returns an integer" do
assert FreelancerRates.monthly_rate(70, 0.0) === 12_320
end
@tag task_id: 3
test "the result is rounded up" do
# 11_052.8
assert FreelancerRates.monthly_rate(62.8, 0.0) == 11_053
# 11_475.2
assert FreelancerRates.monthly_rate(65.2, 0.0) == 11_476
end
@tag task_id: 3
test "gives a discount" do
# 11_792 - 12% * 11_792 = 10_376.96
assert FreelancerRates.monthly_rate(67, 12.0) == 10_377
end
end
describe "days_in_budget/3" do
@tag task_id: 4
test "it's the budget divided by the daily rate" do
assert FreelancerRates.days_in_budget(1_600, 50, 0.0) == 4
end
@tag task_id: 4
test "it always returns a float" do
assert FreelancerRates.days_in_budget(520, 65, 0.0) === 1.0
end
@tag task_id: 4
test "it rounds down to one decimal place" do
# 10.02273
assert FreelancerRates.days_in_budget(4_410, 55, 0.0) == 10.0
# 10.18182
assert FreelancerRates.days_in_budget(4_480, 55, 0.0) == 10.1
end
@tag task_id: 4
test "it applies the discount" do
# 1.25
assert FreelancerRates.days_in_budget(480, 60, 20) == 1.2
end
end
end

View File

@@ -0,0 +1,2 @@
ExUnit.start()
ExUnit.configure(exclude: :pending, trace: true, seed: 0)