Created
December 29, 2015 15:33
-
-
Save pauldambra/05577be969f351c39c9c to your computer and use it in GitHub Desktop.
the checkout kata in elixir
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
defmodule Checkout do | |
defstruct [ | |
basket: %{A: 0, B: 0, C: 0, D: 0} | |
] | |
def scan(checkout, code) do | |
new_value = checkout.basket[code] + 1 | |
%{checkout | basket: Map.put(checkout.basket, code, new_value)} | |
end | |
def total(checkout) do | |
(for item <- checkout.basket, | |
do: get_price item) | |
|> Enum.sum | |
end | |
defp get_price({:A, amount}), do: get_special_price(%{amount: 3, price: 130}, 50, amount, 0) | |
defp get_price({:B, amount}), do: get_special_price(%{amount: 2, price: 45}, 30, amount, 0) | |
defp get_price({:C, amount}), do: get_price(20, amount) | |
defp get_price({:D, amount}), do: get_price(15, amount) | |
defp get_price(cost, amount) do | |
cost * amount | |
end | |
defp get_special_price(offer, normal_cost, amount, current_total) do | |
if amount >= offer.amount do | |
get_special_price(offer, normal_cost, amount - offer.amount, current_total + offer.price) | |
else # amount is now either less than the offer anmount or 0 | |
current_total + get_price(normal_cost, amount) | |
end | |
end | |
end | |
defmodule CheckoutTest do | |
use ExUnit.Case | |
doctest Checkout | |
# Item Unit Special | |
# Price Price | |
# -------------------------- | |
# A 50 3 for 130 | |
# B 30 2 for 45 | |
# C 20 | |
# D 15 | |
setup do | |
scans = [ | |
%{items: [:A], total: 50}, | |
%{items: [:B], total: 30}, | |
%{items: [:C], total: 20}, | |
%{items: [:D], total: 15}, | |
%{items: [:A, :A], total: 100}, | |
%{items: [:B, :B], total: 45}, | |
%{items: [:A, :B], total: 80}, | |
%{items: [:A, :A, :A], total: 130}, | |
%{items: [:A, :A, :A, :A, :A], total: 230}, | |
%{items: [:A, :A, :A, :B, :B], total: 175}, | |
] | |
{:ok, data: scans} | |
end | |
test "scanning an item adds it to the basket" do | |
checkout = Checkout.scan %Checkout{}, :A | |
assert checkout.basket == %{A: 1, B: 0, C: 0, D: 0} | |
end | |
test "totalling a checkout can give a result" do | |
checkout = Checkout.scan %Checkout{}, :A | |
assert Checkout.total(checkout) == 50 | |
end | |
test "scanning a basket gives expected total cost", %{data: scans} do | |
Enum.each(scans, | |
fn(scan) -> | |
total = Enum.reduce( | |
scan.items, | |
%Checkout{}, | |
fn(item, chkot) -> Checkout.scan(chkot, item) end | |
) | |
|> Checkout.total | |
assert total == scan.total | |
end | |
) | |
end | |
end |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Ok, came up with:
Any suggestions welcome - this is my first attempt at writing any elixir, along with the Red:4 course! It's great fun so far!
Think the filter_by and count functions should possibly be one thing as what I want to know is how many of a certain sku are in the cart, just wasn't sure on the best way to write it. Could just stick it in one function, but wondered if Enum has anything build in that might do this