Skip to content

Instantly share code, notes, and snippets.

@gorkaio
Created June 6, 2020 07:23
Show Gist options
  • Save gorkaio/fa0f93de3e8f139eb6c47a5cb47fc350 to your computer and use it in GitHub Desktop.
Save gorkaio/fa0f93de3e8f139eb6c47a5cb47fc350 to your computer and use it in GitHub Desktop.
-module(billing).
-export([print/1]).
-import(catalog, [find/1]).
-define(WIDTH, 30).
-define(STORE_NAME, "Erlang Stores").
-define(PRECISSION, "~.2f").
print(Cart) ->
Bill = format_bill(Cart),
lists:map(fun(L) -> io:format("~s~n", [L]) end, Bill).
format_bill({cart, CartLines}) ->
BillingLines = billing_lines(CartLines),
Header = header(?STORE_NAME, ?WIDTH),
Lines = lists:map(fun(L) -> format_line(L, ?WIDTH) end, BillingLines),
Footer = footer(BillingLines, ?WIDTH),
Header ++ Lines ++ Footer.
% Generate header
header(StoreName, Width) ->
[string:centre(StoreName, Width), []].
% Generate footer
footer(BillingLines, Width) ->
Total = total(BillingLines),
[[], format_total(Total, Width)].
% Transform cart lines into bill lines
billing_lines(CartLines) ->
BillLines = lists:map(
fun({_, Code, Quantity}) ->
case catalog:find(Code) of
nil -> nil; % Ignore any cart line not existing on DB
{_, _, Desc, Price} -> {Desc, Quantity, Price}
end
end,
CartLines
),
lists:filter(fun(X) -> X /= nil end, BillLines).
% Bill line formatter
format_line({Desc, Quantity, Price}, Width) ->
PriceString = io_lib:format(?PRECISSION, [float(Price)]),
ItemString = integer_to_list(Quantity) ++ "x " ++ Desc,
ItemString ++ string:right(PriceString, Width - length(ItemString), $.).
format_total(Total, Width) ->
PriceString = io_lib:format(?PRECISSION, [float(Total)]),
"Total" ++ string:right(PriceString, Width - length("Total"), $.).
% Calculate bill totals
total(BillingLines) ->
lists:foldl(fun({_, Quantity, Price}, Sum) -> Sum + (Quantity * Price) end, 0.0, BillingLines).
-module(cart).
-export(
[
new/0,
add_item/2, add_item/3, add_promo/2, add_promo/3,
remove_item/2, remove_item/3, remove_promo/2, remove_promo/3,
has_item/2, has_promo/2
]
).
-export([test/0]).
% TODO: Add method to remove all quantities of given promo
% Create new cart
new() -> new([]).
new(Lines) -> {cart, Lines}.
% Create new line
new_line(Type, Code, Quantity) ->
case Type of
item -> {item, Code, Quantity};
promo -> {promo, Code, Quantity}
end.
new_item(Code, Quantity) -> new_line(item, Code, Quantity).
new_promo(Code, Quantity) -> new_line(promo, Code, Quantity).
% Add item to cart
add_item({cart, _Lines} = Cart, Code) ->
add_item(Cart, Code, 1).
add_item(Cart, _, Quantity) when Quantity == 0 -> Cart;
add_item(Cart, Code, Quantity) when Quantity > 0 ->
add(Cart, new_item(Code, Quantity)).
add_promo({cart, _Lines} = Cart, Code) ->
add_promo(Cart, Code, 1).
add_promo(Cart, _, Quantity) when Quantity == 0 -> Cart;
add_promo(Cart, Code, Quantity) when Quantity > 0 ->
add(Cart, new_promo(Code, Quantity)).
add({cart, Lines} = Cart, {Type, Code, Quantity} = NewLine) ->
NewLines = case has(Cart, Code, Type) of
false -> Lines ++ [NewLine];
{Type, Code, OldQuantity} = FoundLine -> (Lines -- [FoundLine]) ++ [new_line(Type, Code, OldQuantity + Quantity)]
end,
new(NewLines).
add_test() ->
{cart, [{item, 1234, 1}]} = add_item(new(), 1234),
{cart, [{promo, 1234, 1}]} = add_promo(new(), 1234),
{cart, [{item, 1234, 2}]} = add_item(new(), 1234, 2),
{cart, [{promo, 1234, 2}]} = add_promo(new(), 1234, 2),
{cart, [{item, 1234, 3}]} = add_item(new([new_item(1234, 1)]), 1234, 2),
{cart, [{promo, 1234, 3}]} = add_promo(new([new_promo(1234, 1)]), 1234, 2),
{cart, [{promo, 1234, 1}, {item, 1234, 3}]} = add_item(new([new_item(1234, 1), new_promo(1234, 1)]), 1234, 2),
{cart, [{item, 1234, 1}, {promo, 1234, 3}]} = add_promo(new([new_item(1234, 1), new_promo(1234, 1)]), 1234, 2),
passed.
% Remove item from cart
remove_item({cart, _Lines} = Cart, Code) ->
remove_item(Cart, Code, 1).
remove_item({cart, _Lines} = Cart, Code, all) ->
remove_all(Cart, item, Code);
remove_item(Cart, _, Quantity) when Quantity == 0 -> Cart;
remove_item(Cart, Code, Quantity) when Quantity > 0 ->
remove(Cart, new_item(Code, Quantity)).
% Remove promo from cart
remove_promo({cart, _Lines} = Cart, Code) ->
remove_promo(Cart, Code, 1).
remove_promo({cart, _Lines} = Cart, Code, all) ->
remove_all(Cart, promo, Code);
remove_promo(Cart, _, Quantity) when Quantity == 0 -> Cart;
remove_promo(Cart, Code, Quantity) when Quantity > 0 ->
remove(Cart, new_promo(Code, Quantity)).
remove_all({cart, Lines}, Type, Code) ->
NewLines = lists:filter(fun({T, C, _Q}) -> not((T == Type) and (C == Code)) end, Lines),
new(NewLines).
remove({cart, Lines} = Cart, {Type, Code, Quantity}) ->
NewLines = case has(Cart, Code, Type) of
false -> Lines;
{Type, Code, OldQuantity} = FoundLine -> (Lines -- [FoundLine]) ++ [new_line(Type, Code, OldQuantity - Quantity)]
end,
LinesWithQuantity = lists:filter(fun({_, _, Q}) -> Q > 0 end, NewLines),
new(LinesWithQuantity).
remove_test() ->
{cart, []} = remove_item(new(), 1234),
{cart, []} = remove_promo(new(), 1234),
{cart, [{item, 1, 1}]} = remove_item(new([new_item(1, 2)]), 1, 1),
{cart, [{promo, 1, 1}]} = remove_promo(new([new_promo(1, 2)]), 1, 1),
{cart, [{item, 1, 1}]} = remove_item(new([new_item(1, 3)]), 1, 2),
{cart, [{promo, 1, 1}]} = remove_promo(new([new_promo(1, 3)]), 1, 2),
{cart, [{item, 2, 1}, {item, 1, 1}]} = remove_item(new([new_item(1, 2), new_item(2, 1)]), 1, 1),
{cart, [{promo, 2, 1}, {promo, 1, 1}]} = remove_promo(new([new_promo(1, 2), new_promo(2, 1)]), 1, 1),
{cart, []} = remove_item(new([new_item(1234, 1)]), 1234, 1),
{cart, []} = remove_promo(new([new_promo(1234, 1)]), 1234, 1),
{cart, [{item, 1, 3}, {promo, 1, 1}]} = remove_promo(new([new_promo(1, 2), new_item(1, 3)]), 1, 1),
{cart, [{promo, 1, 2}, {item, 1, 2}]} = remove_item(new([new_promo(1, 2), new_item(1, 3)]), 1, 1),
{cart, [{promo, 3, 2}, {item, 1, 3}]} = remove_promo(new([new_promo(1, 2), new_promo(3, 2), new_item(1, 3)]), 1, all),
{cart, [{promo, 1, 2}, {item, 3, 2}]} = remove_item(new([new_promo(1, 2), new_item(3, 2), new_item(1, 3)]), 1, all),
passed.
% Determine if line with code is in cart
has_item(Cart, Code) -> has(Cart, Code, item).
has_promo(Cart, Code) -> has(Cart, Code, promo).
has({cart, []}, _, _) -> false;
has({cart, [{T, C, _Q} = Line | _]}, Code, Type) when T == Type, C == Code ->
Line;
has({cart, [_ | ILs]}, Code, Type) ->
has({cart, ILs}, Code, Type).
has_test() ->
false = has_item(new(), 1),
false = has_promo(new(), 1),
{item, 2, 3} = has_item(new([new_item(2, 3), new_item(5,6)]), 2),
{promo, 2, 3} = has_promo(new([new_promo(2, 3), new_item(5,6)]), 2),
{promo, 2, 4} = has_promo(new([new_item(2, 3), new_promo(2,4)]), 2),
passed.
test() ->
add_test(),
remove_test(),
has_test(),
passed.
-module(catalog).
-export([find/1]).
-export([test/0]).
% find product in database
find(Code) -> find(database(), Code).
find([], _) -> nil;
find([{C, _, _, _} = Item|_], Code) when C == Code ->
Item;
find([_|Is], Code) ->
find(Is, Code).
find_test() ->
nil = find([], 1),
nil = find([{1, item, "TEST_1", 23.45}], 2),
{1, item, "TEST_1", 23.45} = find([{1, item, "TEST_1", 23.45}], 1),
{1, item, "TEST_1", 23.45} = find([{1, item, "TEST_1", 23.45}, {2, item, "TEST_2", -1.23}], 1),
{2, item, "TEST_2", -1.23} = find([{1, item, "TEST_1", 23.45}, {2, item, "TEST_2", -1.23}], 2),
passed.
% Fake database
database() ->
[
{4719, item, "Fish Fingers" , 121},
{5643, item, "Nappies" , 1010},
{3814, item, "Orange Jelly", 56},
{1111, item, "Hula Hoops", 21},
{1112, item, "Hula Hoops (Giant)", 133},
{1234, item, "Dry Sherry, 1lt", 540},
{9999, promo, "2x1 Dry Sherry", -540}
].
test() ->
find_test(),
passed.
-module(promotions).
-import(cart,[add_promo/2, remove_promo/3, has_item/2]).
-import(cart,[new/0, add_item/2, has_promo/2]). % Used for testing
-export([apply_promos/1]).
-export([test/0]).
-define(SHERRY_CODE, 1234).
-define(SHERRY2X1_CODE, 9999).
apply_promos(Cart) ->
Promos = lists:filter(fun({_, Cond, _}) -> Cond(Cart) end, promotions()),
apply_promos(Cart, Promos).
apply_promos(Cart, []) -> Cart;
apply_promos(Cart, [{_, _, F}|Ps]) ->
apply_promos(F(Cart), Ps).
apply_test() ->
{promo, ?SHERRY2X1_CODE, 1} = cart:has_promo(apply_promos(cart:add_item(cart:new(), ?SHERRY_CODE, 3)), ?SHERRY2X1_CODE),
{promo, ?SHERRY2X1_CODE, 2} = cart:has_promo(apply_promos(cart:add_item(cart:new(), ?SHERRY_CODE, 4)), ?SHERRY2X1_CODE),
false = cart:has_promo(apply_promos(cart:add_item(cart:new(), ?SHERRY_CODE, 1)), ?SHERRY2X1_CODE),
false = cart:has_promo(apply_promos(cart:new()), ?SHERRY2X1_CODE),
passed.
test() ->
apply_test(),
passed.
promotions() -> [
{
?SHERRY2X1_CODE,
fun(Cart) -> cart:has_item(Cart, ?SHERRY_CODE) /= false end,
fun(Cart) ->
case cart:has_item(Cart, ?SHERRY_CODE) of
false -> Cart;
{item, ?SHERRY_CODE, Quantity} ->
cart:remove_promo(Cart, ?SHERRY2X1_CODE, all),
cart:add_promo(Cart, ?SHERRY2X1_CODE, Quantity div 2)
end
end
}
].
-module(supermarket).
-export([new/0, add/2, add/3, remove/2, remove/3, bill/1]).
%% Create new cart
new() ->
cart:new().
%% Add items to cart
add(Cart, Code) -> add(Cart, Code, 1).
add(Cart, Code, Quantity) ->
NewCart = cart:add_item(Cart, Code, Quantity),
update(NewCart).
%% Remove items from cart
remove(Cart, Code) -> remove(Cart, Code, 1).
remove(Cart, Code, Quantity) ->
NewCart = cart:add_item(Cart, Code, Quantity),
update(NewCart).
%% Update cart: apply promotions, etc...
update(Cart) ->
promotions:apply_promos(Cart).
%% Print bill for given cart
bill(Cart) ->
billing:print(Cart).
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment