Created
February 25, 2017 21:22
-
-
Save morgaine/99154897f4d549b03ab5705201e4c04c to your computer and use it in GitHub Desktop.
Erlang shapes/bits Wk 1 lesson 1.24 assignment, "FP in Erlang"
This file contains hidden or 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
#! /bin/env escript | |
% NAME | |
% shabits.erl -- "FP in Erlang" course, lesson 01.24 assignment | |
% | |
% SYNOPSIS | |
% shabits {triangle-side-A} {triangle-side-B} {triangle-side-C} | |
% | |
% INSTALL | |
% ln -s shabits.erl shabits | |
% | |
% DESCRIPTION | |
% FutureLean course "Functional Programming in Erlang". | |
% Lesson 1.24, week 1 assignment | |
% 1) Shapes | |
% 2) Summing the bits | |
% | |
% The "Shapes" part of this assignment encloses triangles within rectangles, | |
% and therefore those are the two kinds of shapes defined in this program. | |
% The assignment does not specify whether "smallest enclosing rectangle" is | |
% determined by rectangle area or perimeter. We implement both and pick one. | |
% | |
% The "Summing the bits" part of the assignment shares the Shapes inputs for | |
% simplicity, and performs its bit-sums on the integer sides of each triangle. | |
% Bit-sums are produced using direct recursion and confirmed by tail recursion. | |
% | |
% EXAMPLE USAGE | |
% ./shabits 5 5 5 # Equilateral triangle | |
% ./shabits 3 4 5 # Right-angle triangle | |
% ./shabits 21 17 31 # Left-obtuse triangle | |
% ./shabits 21 31 17 # Right-obtuse triangle | |
% | |
-module(shabits). | |
-export([main/1, perimeter/1, area/1, enclose/1, rectangle/2, triangle/3]). | |
-export([bits/1, bits_tail/1, bitsInfo/2]). | |
-export([altitude/3, smallestRect/2, base_extension/4]). | |
%% ----- Functions over shapes | |
perimeter({triangle, A, B, C}) -> | |
A + B + C; | |
perimeter({rectangle, H, W}) -> | |
2*H + 2*W. | |
area({triangle, A, B, C}) -> | |
S = (A+B+C)/2.0, | |
math:sqrt(S*(S-A)*(S-B)*(S-C)); | |
area({rectangle, H, W}) -> | |
H*W. | |
%% Altitude of a triangle w.r.to side A as its Base. | |
%% See https://en.wikipedia.org/wiki/Altitude_(triangle), "Altitude in terms of the sides", | |
altitude(Base,B,C) -> | |
S = (0.0+Base+B+C)/2.0, %% Semi-perimeter | |
H = 2*(math:sqrt(S*(S-Base)*(S-B)*(S-C))) / Base, | |
io:format(" triangle(~p, ~p, ~p) has altitude = ~.3f~n", [ | |
Base, B, C, | |
H + 0.0 | |
]), | |
H. | |
%% Design approach for obtaining the extended base of the smallest enclosing rectangle: | |
%% | |
%% If the obtuse angle of an obtuse triangle has the base of the triangle as one side, | |
%% then the altitude of the triangle relative to that base intersects the base line | |
%% OUTSIDE of the triangle. This extends the base of the smallest enclosing rectangle. | |
%% | |
%% Consequently, we can detect this adjacent obtuseness on one side or the other of the base | |
%% of the triangle, and if it exists then calculate the altitude and the size of the extension | |
%% and add it to the size of the triangle base. If neither left nor right obtuseness is found | |
%% then the width of the smallest enclosing rectangle is just the size of that particular base. | |
%% The sides A,B,C of a triangle have particular semantics in this calculation, given by order: | |
%% A - The chosen base (ie. this side rests on the horizontal) | |
%% B - The side attached to the LEFT end of side A. | |
%% C - The side attached to the RIGHT end of side A. | |
%% and H - The previously calculated Altitude relative to A as base. | |
base_extension(H,A,B,C) when C*C > (A*A + B*B) -> %% Found: LEFT-obtuse angle | |
E = math:sqrt(B*B - H*H), | |
io:format(" triangle(~w, ~w, ~w) is LEFT-obtuse, base extension = ~10.3f~n", [A,B,C, E]), | |
E; | |
base_extension(H,A,B,C) when B*B > (A*A + C*C) -> %% Found: RIGHT-obtuse angle | |
E = math:sqrt(C*C - H*H), | |
io:format(" triangle(~w, ~w, ~w) is RIGHT-obtuse, base extension = ~10.3f~n", [A,B,C, E]), | |
E; | |
base_extension(_,A,B,C) -> %% There is no adjacent obtuse angle | |
io:format(" triangle(~w, ~w, ~w) has no adjacent obtuse angle, base extension = 0~n", [A,B,C]), | |
0. | |
%% A conventional list processor returning the smallest rectangle from list of {Size,Rect} pairs. | |
%% Note that "Size" is any comparable scalar property from the point of view of this function. | |
smallestRect({_,XR}, []) -> %% No more pairs to process, so return current rectangle. | |
XR; | |
smallestRect({XS,_}, [Y={YS,YR}|Next]) when XS >= YS -> %% Y is smaller, grab it | |
showRect(YR), | |
smallestRect(Y, Next); | |
smallestRect(X, [{_,YR}|Next]) -> %% X is smaller, keep it | |
showRect(YR), | |
smallestRect(X, Next). | |
% We'll use the perimeter as our basis for determining "smallest size" of enclosing rectangle. | |
% If some other metric is desired, just replace the call to perimeter by another, eg. area(R). | |
enclosing_size(R) -> | |
perimeter(R). | |
%% Returns the smallest rectangle enclosing the given triangle shape. | |
enclose({triangle, A, B, C}) -> | |
%% A rectangle's paramaters are Height and Width (H,W): | |
%% - H is the altitude of the triangle w.r.to a chosen side as Base. | |
%% - W is the length of Base, extended to intersection with altitude. | |
HA = altitude(A,B,C), % A is base, B and C are assigned clockwise. | |
HB = altitude(B,C,A), % Rotate triangle anticlockwise | |
HC = altitude(C,A,B), % Rotate triangle anticlockwise again | |
io:format("~n"), | |
EA = base_extension(HA, A,B,C), % Obtain base extension for side A as base. | |
EB = base_extension(HB, B,C,A), % Obtain base extension for side B as base. | |
EC = base_extension(HC, C,A,B), % Obtain base extension for side C as base. | |
%% Create the 3 enclosing rectangles H x W, taking each side as Base in turn. | |
R1 = rectangle(HA, A + EA), % | |
R2 = rectangle(HB, B + EB), % Rectangles {H,W} are {Altitude, Base+Extension} | |
R3 = rectangle(HC, C + EC), % | |
% The choice of metric for determining "smallest size" is factored out into its own fun. | |
S1 = {enclosing_size(R1), R1}, % | |
S2 = {enclosing_size(R2), R2}, % Create {Size,Rectangle} pairs for sorting. | |
S3 = {enclosing_size(R3), R3}, % | |
io:format("~n"), | |
smallestRect(S1, [S1,S2,S3]). % Given {S,R} pairs, find smallest S and return its R. | |
%% Validate that side lengths comply with required properties of triangles. | |
validate_triangle(A,B,C) -> | |
if | |
(A =< 0) or (B =< 0) or (C =< 0) -> | |
io:format("shabits: INVALID triangle, one or more non-positive sides~n"), | |
false; | |
true -> | |
[X,Y,Z] = lists:sort([A,B,C]), %% Reorder sides as numerically ascending | |
if | |
X + Y =< Z -> | |
io:format("shabits: INVALID triangle, side size ~w is not shorter than sum of other two~n",[Z]), | |
false; | |
true -> | |
true | |
end | |
end. | |
%% ----- Shape constructors | |
triangle(A,B,C) -> | |
{triangle, A, B, C}. | |
rectangle(H,W) -> | |
{rectangle, H, W}. | |
%% ----- Shape reporting functions | |
showEnclosed({triangle, A, B, C}, R={rectangle, H, W}) -> | |
io:format("triangle(~p,~p,~p) is enclosed by smallest rectangle(~.3f x ~.3f) of area ~.3f and perimeter ~.3f~n", [ | |
A, B, C, | |
H+0.0, W+0.0, | |
area(R), | |
perimeter(R) | |
]). | |
showRect(R={rectangle, H, W}) -> | |
io:format(" rectangle(~.3f x ~.3f) has area ~15.3f and perimeter ~12.3f~n", [ | |
H+0.0, W+0.0, | |
area(R), | |
perimeter(R) | |
]). | |
%% ----- Bit-oriented functions | |
%% Sums the bits of the binary representation of the positive integer X, using direct recursion. | |
bits(0) -> | |
0; | |
bits(B) -> | |
(B band 2#1) + bits(B bsr 1). | |
%% Sums the bits of the binary representation of the positive integer X, using tail recursion. | |
bits_tail(B) -> | |
bits_tail(0, B). | |
bits_tail(N, 0) -> | |
N; | |
bits_tail(N, B) -> | |
bits_tail(N + (B band 2#1), B bsr 1). | |
%% Display a named triangle side length in binary form and it's sum of 1-bits. | |
bitsInfo(Name,Len) -> | |
io:format("Side ~s has integer length decimal ~5w, binary ~020.2B containing ~3w 1-bits [~w]~n", [ | |
Name, | |
Len, | |
Len, | |
bits(Len), | |
bits_tail(Len) | |
]). | |
%% ---- Converts commandline "numeric" strings | |
string_to_float(S) -> | |
case string:to_float(S) of | |
{error, no_float} -> list_to_integer(S) + 0.0; | |
{F, _Rest} -> F | |
end. | |
%% ---- MAIN ---- | |
%% Note that apart from the first two functions, the remaining entries are clauses of a single main(). | |
%% ---- MAIN ---- | |
main() -> | |
io:format("Usage: shabits {triangle-side-A} {triangle-side-B} {triangle-side-C}~n"), | |
halt(1). | |
main([]) -> | |
main(); | |
main([_]) -> | |
io:format("Too few arguments.~n"), | |
main(); | |
main([_,_]) -> | |
io:format("Too few arguments.~n"), | |
main(); | |
%% Main phases: | |
%% | |
%% 1) Commandline input conversion. | |
%% 2) Triangle validation. | |
%% 3) Triangle creation. | |
%% 4) Compute smallest enclosing rectangle. | |
%% 5) Display info for this rectangle. | |
%% 6) Use both bit-summation functions on the three truncated integer sides as a test. | |
main([SideA, SideB, SideC]) -> | |
A = string_to_float(SideA), % | |
B = string_to_float(SideB), % Both list_to_float and string:to_float have problems. | |
C = string_to_float(SideC), % | |
T = case validate_triangle(A,B,C) of | |
true -> triangle(A,B,C); | |
false -> halt(1) | |
end, | |
R = enclose(T), %% Obtain the smallest enclosing rectangle | |
io:format("~n"), | |
showEnclosed(T, R), | |
io:format("~n"), | |
bitsInfo("A", trunc(A)), | |
bitsInfo("B", trunc(B)), | |
bitsInfo("C", trunc(C)); | |
main([_,_,_,_|_]) -> | |
io:format("Too many arguments.~n"), | |
main(). | |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Example usage from commandline: