Skip to content

Instantly share code, notes, and snippets.

@morgaine
Created February 25, 2017 21:22
Show Gist options
  • Save morgaine/99154897f4d549b03ab5705201e4c04c to your computer and use it in GitHub Desktop.
Save morgaine/99154897f4d549b03ab5705201e4c04c to your computer and use it in GitHub Desktop.
Erlang shapes/bits Wk 1 lesson 1.24 assignment, "FP in Erlang"
#! /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().
@morgaine
Copy link
Author

morgaine commented Feb 25, 2017

Example usage from commandline:

$ ./shabits   5  5  5     # Equilateral triangle
    triangle(5.0, 5.0, 5.0) has altitude = 4.330
    triangle(5.0, 5.0, 5.0) has altitude = 4.330
    triangle(5.0, 5.0, 5.0) has altitude = 4.330

    triangle(5.0, 5.0, 5.0) has no adjacent obtuse angle, base extension = 0
    triangle(5.0, 5.0, 5.0) has no adjacent obtuse angle, base extension = 0
    triangle(5.0, 5.0, 5.0) has no adjacent obtuse angle, base extension = 0

    rectangle(4.330 x 5.000) has area          21.651 and perimeter       18.660
    rectangle(4.330 x 5.000) has area          21.651 and perimeter       18.660
    rectangle(4.330 x 5.000) has area          21.651 and perimeter       18.660

triangle(5.0,5.0,5.0) is enclosed by smallest rectangle(4.330 x 5.000) of area 21.651 and perimeter 18.660

Side A has integer length decimal     5,  binary                  101 containing   2  1-bits [2]
Side B has integer length decimal     5,  binary                  101 containing   2  1-bits [2]
Side C has integer length decimal     5,  binary                  101 containing   2  1-bits [2]

#===================
$ ./shabits   3  4  5     # Right-angle triangle
    triangle(3.0, 4.0, 5.0) has altitude = 4.000
    triangle(4.0, 5.0, 3.0) has altitude = 3.000
    triangle(5.0, 3.0, 4.0) has altitude = 2.400

    triangle(3.0, 4.0, 5.0) has no adjacent obtuse angle, base extension = 0
    triangle(4.0, 5.0, 3.0) has no adjacent obtuse angle, base extension = 0
    triangle(5.0, 3.0, 4.0) has no adjacent obtuse angle, base extension = 0

    rectangle(4.000 x 3.000) has area          12.000 and perimeter       14.000
    rectangle(3.000 x 4.000) has area          12.000 and perimeter       14.000
    rectangle(2.400 x 5.000) has area          12.000 and perimeter       14.800

triangle(3.0,4.0,5.0) is enclosed by smallest rectangle(3.000 x 4.000) of area 12.000 and perimeter 14.000

Side A has integer length decimal     3,  binary                   11 containing   2  1-bits [2]
Side B has integer length decimal     4,  binary                  100 containing   1  1-bits [1]
Side C has integer length decimal     5,  binary                  101 containing   2  1-bits [2]

#===================
$ ./shabits  21 17 31     # Left-obtuse triangle
    triangle(21.0, 17.0, 31.0) has altitude = 16.086
    triangle(17.0, 31.0, 21.0) has altitude = 19.871
    triangle(31.0, 21.0, 17.0) has altitude = 10.897

    triangle(21.0, 17.0, 31.0) is LEFT-obtuse,  base extension =      5.500
    triangle(17.0, 31.0, 21.0) is RIGHT-obtuse, base extension =      6.794
    triangle(31.0, 21.0, 17.0) has no adjacent obtuse angle, base extension = 0

    rectangle(16.086 x 26.500) has area         426.271 and perimeter       85.171
    rectangle(19.871 x 23.794) has area         472.803 and perimeter       87.329
    rectangle(10.897 x 31.000) has area         337.800 and perimeter       83.794

triangle(21.0,17.0,31.0) is enclosed by smallest rectangle(10.897 x 31.000) of area 337.800 and perimeter 83.794

Side A has integer length decimal    21,  binary                10101 containing   3  1-bits [3]
Side B has integer length decimal    17,  binary                10001 containing   2  1-bits [2]
Side C has integer length decimal    31,  binary                11111 containing   5  1-bits [5]

#===================
$ ./shabits  21 31 17     # Right-obtuse triangle
    triangle(21.0, 31.0, 17.0) has altitude = 16.086
    triangle(31.0, 17.0, 21.0) has altitude = 10.897
    triangle(17.0, 21.0, 31.0) has altitude = 19.871

    triangle(21.0, 31.0, 17.0) is RIGHT-obtuse, base extension =      5.500
    triangle(31.0, 17.0, 21.0) has no adjacent obtuse angle, base extension = 0
    triangle(17.0, 21.0, 31.0) is LEFT-obtuse,  base extension =      6.794

    rectangle(16.086 x 26.500) has area         426.271 and perimeter       85.171
    rectangle(10.897 x 31.000) has area         337.800 and perimeter       83.794
    rectangle(19.871 x 23.794) has area         472.803 and perimeter       87.329

triangle(21.0,31.0,17.0) is enclosed by smallest rectangle(10.897 x 31.000) of area 337.800 and perimeter 83.794

Side A has integer length decimal    21,  binary                10101 containing   3  1-bits [3]
Side B has integer length decimal    31,  binary                11111 containing   5  1-bits [5]
Side C has integer length decimal    17,  binary                10001 containing   2  1-bits [2]

#== End.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment