inventory

This commit is contained in:
Ruidy 2022-02-09 17:27:18 -04:00
parent 93c0ac72e3
commit bd682bc97e
11 changed files with 453 additions and 0 deletions

View file

@ -0,0 +1,21 @@
{
"blurb": "Learn about the Enum module by preparing your fashion boutique for the big annual sale.",
"authors": [
"angelikatyborska"
],
"contributors": [
"neenjaw"
],
"files": {
"solution": [
"lib/boutique_inventory.ex"
],
"test": [
"test/boutique_inventory_test.exs"
],
"exemplar": [
".meta/exemplar.ex"
]
},
"language_versions": ">=1.10"
}

View file

@ -0,0 +1 @@
{"track":"elixir","exercise":"boutique-inventory","id":"7976ad812b9646b1b2fcf65bbc36a9d6","url":"https://exercism.org/tracks/elixir/exercises/boutique-inventory","handle":"rjNemo","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
boutique-inventory/.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").
nil-*.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/boutique_inventory.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)
- [Exercism's support channel on gitter](https://gitter.im/exercism/support)
- 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.
If you can't find what you're looking for in the documentation, feel free to ask help in the Exercism's BEAM [gitter channel](https://gitter.im/exercism/xerlang).

View file

@ -0,0 +1,36 @@
# Hints
## General
- Read about the `Enum` module in the [official Getting Started guide][getting-started-enum] or on [elixirschool.com][elixir-school-enum].
- Take a look in the documentation for the full [list of functions in the `Enum` module][enum-functions].
## 1. Sort items by price
- There is a [built-in function][enum-sort-by] for sorting enumerables using a sorter function.
## 2. Find all items with missing prices
- There is a [built-in function][enum-filter] for filtering enumerables.
## 3. Increment the item's quantity
- Maps implement the enumerable protocol.
- `Enum` functions convert maps to a list of `{key, value}` tuples.
- There is a [built-in function][enum-map] for replacing every element in an enumerable with another element.
- There is a [built-in function][enum-into] that can transform a list of `{key, value}` tuples back into a map.
## 4. Calculate the item's total quantity
- Maps implement the enumerable protocol.
- `Enum` functions convert maps to a list of `{key, value}` tuples.
- There is a [built-in function][enum-reduce] for reducing an enumerable to a single value.
[getting-started-enum]: https://elixir-lang.org/getting-started/enumerables-and-streams.html#enumerables
[elixir-school-enum]: https://elixirschool.com/en/lessons/basics/enum/
[enum-functions]: https://hexdocs.pm/elixir/Enum.html#functions
[enum-sort-by]: https://hexdocs.pm/elixir/Enum.html#sort_by/3
[enum-filter]: https://hexdocs.pm/elixir/Enum.html#filter/2
[enum-map]: https://hexdocs.pm/elixir/Enum.html#map/2
[enum-into]: https://hexdocs.pm/elixir/Enum.html#into/2
[enum-reduce]: https://hexdocs.pm/elixir/Enum.html#reduce/3

View file

@ -0,0 +1,138 @@
# Boutique Inventory
Welcome to Boutique Inventory 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
## Enum
`Enum` is a very useful module that provides a set of algorithms for working with enumerables. It offers sorting, filtering, grouping, counting, searching, finding min/max values, and much more.
In general, an _enumerable_ is any data that can be iterated over, a collection. In Elixir, an enumerable is any data type that implements the `Enumerable` protocol. The most common of those are lists and maps.
Many `Enum` functions accept a function as an argument.
```elixir
Enum.all?([1, 2, 3, 4, 5], fn x -> x > 3 end)
# => false
```
The most common `Enum` functions are `map` and `reduce`.
### `map/2`
`Enum.map/2` allows you to replace every element in an enumerable with another element. The second argument to `Enum.map/2` is a function that accepts the original element and returns its replacement.
### `reduce/3`
`Enum.reduce/3` allows you to _reduce_ the whole enumerable to a single value. To achieve this, a special variable called the _accumulator_ is used. The accumulator carries the intermediate state of the reduction between iterations.
The second argument to `Enum.reduce/3` is the initial value of the accumulator. The third argument is a function that accepts an element and an accumulator, and returns the new value for the accumulator.
### Working with maps
When using maps with `Enum` functions, the map gets automatically converted to a list of 2 `{key, value}` tuples.
To transform it back to a map, use `Enum.into/2`. `Enum.into/2` is a function that transforms an enumerable into a collectable - any data structure implementing the `Collectable` protocol. It can be thought of as the opposite of `Enum.reduce/3`.
## Instructions
You are running an online fashion boutique. Your big annual sale is coming up, so you need to take stock of your inventory to make sure you're ready.
A single item in the inventory is represented by a map, and the whole inventory is a list of such maps.
```elixir
%{
name: "White Shirt",
price: 40,
quantity_by_size: %{s: 3, m: 7, l: 8, xl: 4}
}
```
## 1. Sort items by price
Implement the `sort_by_price/1` function. It should take the inventory and return it sorted by item price, ascending.
```elixir
BoutiqueInventory.sort_by_price([
%{price: 65, name: "Maxi Brown Dress", quantity_by_size: %{}},
%{price: 50, name: "Red Short Skirt", quantity_by_size: %{}},
%{price: 50, name: "Black Short Skirt", quantity_by_size: %{}},
%{price: 20, name: "Bamboo Socks Cats", quantity_by_size: %{}}
])
# => [
# %{price: 20, name: "Bamboo Socks Cats", quantity_by_size: %{}},
# %{price: 50, name: "Red Short Skirt", quantity_by_size: %{}},
# %{price: 50, name: "Black Short Skirt", quantity_by_size: %{}},
# %{price: 65, name: "Maxi Brown Dress", price: 65, quantity_by_size: %{}}
# ]
```
## 2. Find all items with missing prices
After sorting your inventory by price, you noticed that you must have made a mistake when you were taking stock and forgot to fill out prices for a few items.
Implement the `with_missing_price/1` function. It should take the inventory and return a list of items that do not have prices.
```elixir
BoutiqueInventory.with_missing_price([
%{price: 40, name: "Black T-shirt", quantity_by_size: %{}},
%{price: nil, name: "Denim Pants", quantity_by_size: %{}},
%{price: nil, name: "Denim Skirt", quantity_by_size: %{}},
%{price: 40, name: "Orange T-shirt", quantity_by_size: %{}}
])
# => [
# %{price: nil, name: "Denim Pants", quantity_by_size: %{}},
# %{price: nil, name: "Denim Skirt", quantity_by_size: %{}}
# ]
```
## 3. Increment the item's quantity
Some items were selling especially well, so you ordered more, in all sizes.
Implement the `increase_quantity/2` function. It should take a single item and a number `n`, and return that item with the quantity for each size increased by `n`.
```elixir
BoutiqueInventory.increase_quantity(
%{
name: "Polka Dot Skirt",
price: 68,
quantity_by_size: %{s: 3, m: 5, l: 3, xl: 4}
},
6
)
# => %{
# name: "Polka Dot Skirt",
# price: 68,
# quantity_by_size: %{l: 9, m: 11, s: 9, xl: 10}
# }
```
## 4. Calculate the item's total quantity
To know how much space you need in your storage, you need to know how many of each item you have in total.
Implement the `total_quantity/1` function. It should take a single item and return how many pieces you have in total, in any size.
```elixir
BoutiqueInventory.total_quantity(%{
name: "Red Shirt",
price: 62,
quantity_by_size: %{s: 3, m: 6, l: 5, xl: 2}
})
# => 16
```
## Source
### Created by
- @angelikatyborska
### Contributed to by
- @neenjaw

View file

@ -0,0 +1,11 @@
defmodule BoutiqueInventory do
def sort_by_price(inventory), do: Enum.sort_by(inventory, & &1.price, &(&1 <= &2))
def with_missing_price(inventory), do: Enum.filter(inventory, &(&1.price == nil))
def increase_quantity(%{quantity_by_size: qty} = item, count),
do: %{item | quantity_by_size: Enum.into(qty, %{}, fn {s, q} -> {s, q + count} end)}
def total_quantity(%{quantity_by_size: qty}),
do: Enum.reduce(qty, 0, fn {_, q}, total -> total + q end)
end

View file

@ -0,0 +1,28 @@
defmodule BoutiqueInventory.MixProject do
use Mix.Project
def project do
[
app: :boutique_inventory,
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,113 @@
defmodule BoutiqueInventoryTest do
use ExUnit.Case
describe "sort_by_price/1" do
@tag task_id: 1
test "works for an empty inventory" do
assert BoutiqueInventory.sort_by_price([]) == []
end
@tag task_id: 1
test "sorts items by price" do
assert BoutiqueInventory.sort_by_price([
%{price: 65, name: "Maxi Yellow Summer Dress", quantity_by_size: %{}},
%{price: 60, name: "Cream Linen Pants", quantity_by_size: %{}},
%{price: 33, name: "Straw Hat", quantity_by_size: %{}}
]) == [
%{price: 33, name: "Straw Hat", quantity_by_size: %{}},
%{price: 60, name: "Cream Linen Pants", quantity_by_size: %{}},
%{price: 65, name: "Maxi Yellow Summer Dress", quantity_by_size: %{}}
]
end
@tag task_id: 1
test "the order of items of equal price is preserved" do
assert BoutiqueInventory.sort_by_price([
%{price: 65, name: "Maxi Yellow Summer Dress", quantity_by_size: %{}},
%{price: 60, name: "Cream Linen Pants", quantity_by_size: %{}},
%{price: 33, name: "Straw Hat", quantity_by_size: %{}},
%{price: 60, name: "Brown Linen Pants", quantity_by_size: %{}}
]) == [
%{price: 33, name: "Straw Hat", quantity_by_size: %{}},
%{price: 60, name: "Cream Linen Pants", quantity_by_size: %{}},
%{price: 60, name: "Brown Linen Pants", quantity_by_size: %{}},
%{price: 65, name: "Maxi Yellow Summer Dress", quantity_by_size: %{}}
]
end
end
describe "with_missing_price/1" do
@tag task_id: 2
test "works for an empty inventory" do
assert BoutiqueInventory.with_missing_price([]) == []
end
@tag task_id: 2
test "filters out items that do have a price" do
assert BoutiqueInventory.with_missing_price([
%{name: "Red Flowery Top", price: 50, quantity_by_size: %{}},
%{name: "Purple Flowery Top", price: nil, quantity_by_size: %{}},
%{name: "Bamboo Socks Avocado", price: 10, quantity_by_size: %{}},
%{name: "Bamboo Socks Palm Trees", price: 10, quantity_by_size: %{}},
%{name: "Bamboo Socks Kittens", price: nil, quantity_by_size: %{}}
]) == [
%{name: "Purple Flowery Top", price: nil, quantity_by_size: %{}},
%{name: "Bamboo Socks Kittens", price: nil, quantity_by_size: %{}}
]
end
end
describe "increase_quantity/2" do
@tag task_id: 3
test "works for an empty quantity map" do
assert BoutiqueInventory.increase_quantity(
%{
name: "Long Black Evening Dress",
price: 105,
quantity_by_size: %{}
},
1
) == %{
name: "Long Black Evening Dress",
price: 105,
quantity_by_size: %{}
}
end
@tag task_id: 3
test "increases quantity of an item" do
assert BoutiqueInventory.increase_quantity(
%{
name: "Green Swimming Shorts",
price: 46,
quantity_by_size: %{s: 1, m: 2, l: 4, xl: 1}
},
3
) == %{
name: "Green Swimming Shorts",
price: 46,
quantity_by_size: %{s: 4, m: 5, l: 7, xl: 4}
}
end
end
describe "total_quantity/1" do
@tag task_id: 4
test "works for an empty quantity map" do
assert BoutiqueInventory.total_quantity(%{
name: "Red Denim Pants",
price: 77,
quantity_by_size: %{}
}) == 0
end
@tag task_id: 4
test "sums up total quantity" do
assert BoutiqueInventory.total_quantity(%{
name: "Black Denim Skirt",
price: 50,
quantity_by_size: %{s: 4, m: 11, l: 6, xl: 8}
}) == 29
end
end
end

View file

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