Skip to content

Instantly share code, notes, and snippets.

@oscherler
Last active February 26, 2017 09:14
Show Gist options
  • Save oscherler/24ceca39e68f49de713fb5cd6a468605 to your computer and use it in GitHub Desktop.
Save oscherler/24ceca39e68f49de713fb5cd6a468605 to your computer and use it in GitHub Desktop.

bits.erl

Function that takes a positive integer N and returns the sum of the bits in the binary representation. For example bits(7) is 3 and bits(8) is 1. Direct and tail-recurive versions.

shapes.erl

Calculates perimeter, area and enclosing rectangle for different types shape:

  • { circle, { X, Y }, R }: circle (centre, radius)
  • { rectangle, { X, Y }, W, H } rectangle (centre, width, height)
  • { triangle, { X1, Y1 }, { X2, Y2 }, { X3, Y3 } } triangle (P1, P2, P3, coordinates of the three points)

In this first version I stuck to what was taught so far and made sure the arguments given match what’s expected.

shapes_simpler.erl

In this version I decided to assume that when a tuple starting with rectangle is given, it matches the expected representation, and it allowed me to simplify the pattern matches. I also discovered that you can put assignments in the pattern matches, which allowed me to re-use matched expressions without re-writing them (e.g. enclose for a rectangle).

test_shapes.erl

Tests for the shapes function, using pattern matching to check calculated values against the expected ones (poor-person’s unit testing). I had to write a function to compare two floats with a margin for rounding.

-module( bits ).
-export( [ bits/1, bits_tail/1, print/1, test/0 ] ).
% sum the bits of the binary representation of N
% direct recursive
bits( N ) when is_integer( N ), N >= 0 ->
sum_bits( N ).
sum_bits( N ) when N < 2 ->
N;
sum_bits( N ) ->
N rem 2 + sum_bits( N div 2 ).
% tail recursive
bits_tail( N ) when is_integer( N ), N >= 0 ->
sum_bits_tail( N, 0 ).
sum_bits_tail( N, S ) when N < 2 ->
N + S;
sum_bits_tail( N, S ) ->
sum_bits_tail( N div 2, S + N rem 2 ).
% print a line with N, N’s binary representation, and the output of both versions of bits
print( N ) ->
io:format( "~2B ~6.2B ~2B ~2B~n", [ N, N, bits( N ), bits_tail( N ) ] ).
% test various numbers
test() ->
io:format( "~s~n", [" N 2#N dir tail"] ),
print( 0 ),
print( 1 ),
print( 2 ),
print( 3 ),
print( 7 ),
print( 8 ),
print( 14 ),
print( 15 ),
print( 16 ),
print( 27 ),
print( 31 ),
print( 32 ),
print( 33 ).
-module( shapes ).
-export( [ area/1, perimeter/1, enclose/1 ] ).
% circle (centre, radius)
% { circle, { X, Y }, R }
%
% rectangle (centre, width, height)
% { rectangle, { X, Y }, W, H }
%
% triangle (P1, P2, P3, coordinates of the three points)
% { triangle, { X1, Y1 }, { X2, Y2 }, { X3, Y3 } }
%%% area
% circle
area( { circle, { _X, _Y }, R } ) ->
math:pi() * R * R;
% rectangle
area( { rectangle, { _X, _Y }, W, H } ) ->
W * H;
% triangle
area( { triangle, { X1, Y1 }, { X2, Y2 }, { X3, Y3 } } ) ->
{ A, B, C } = triangle_sides( { X1, Y1 }, { X2, Y2 }, { X3, Y3 } ),
triangle_area( A, B, C ).
%%% perimeter
% circle
perimeter( { circle, { _X, _Y }, R } ) ->
2 * math:pi() * R;
% rectangle
perimeter( { rectangle, { _X, _Y }, W, H } ) ->
2 * ( W + H );
% triangle
perimeter( { triangle, { X1, Y1 }, { X2, Y2 }, { X3, Y3 } } ) ->
{ A, B, C } = triangle_sides( { X1, Y1 }, { X2, Y2 }, { X3, Y3 } ),
A + B + C.
%%% enclose
% circle
enclose( { circle, { X, Y }, R } ) ->
{ rectangle, { X, Y }, 2 * R, 2 * R };
% rectangle
enclose( { rectangle, { X, Y }, W, H } ) ->
{ rectangle, { X, Y }, W, H };
% triangle
enclose( { triangle, { X1, Y1 }, { X2, Y2 }, { X3, Y3 } } ) ->
{ Rx1, Rx2 } = min_max( X1, X2, X3 ),
{ Ry1, Ry2 } = min_max( Y1, Y2, Y3 ),
{ rectangle, { ( Rx1 + Rx2 ) / 2, ( Ry1 + Ry2 ) / 2 }, Rx2 - Rx1, Ry2 - Ry1 }.
%%% triangle utilities
% determine the minimum and maximum of three values
min_max( A, B, C ) ->
{ min( min( A, B ), C ), max( max( A, B ), C ) }.
% calculate the lengths of all sides of a triangle
triangle_sides( { X1, Y1 }, { X2, Y2 }, { X3, Y3 } ) ->
A = distance( { X1, Y1 }, { X2, Y2 } ),
B = distance( { X2, Y2 }, { X3, Y3 } ),
C = distance( { X3, Y3 }, { X1, Y1 } ),
{ A, B, C }.
% calculate the area a triangle from its sides
triangle_area( A, B, C ) ->
S = ( A + B + C ) / 2,
math:sqrt( S * ( S - A ) * ( S - B ) * ( S - C ) ).
% calculate the distance between two points
distance( { X1, Y1 }, { X2, Y2 } ) ->
math:sqrt( ( X2 - X1 ) * ( X2 - X1 ) + ( Y2 - Y1 ) * ( Y2 - Y1 ) ).
-module( shapes_simpler ).
-export( [ area/1, perimeter/1, enclose/1 ] ).
% circle (centre, radius)
% { circle, { X, Y }, R }
%
% rectangle (centre, width, height)
% { rectangle, { X, Y }, W, H }
%
% triangle (P1, P2, P3, coordinates of the three points)
% { triangle, { X1, Y1 }, { X2, Y2 }, { X3, Y3 } }
%%% area
% circle
area( { circle, _, R } ) ->
math:pi() * R * R;
% rectangle
area( { rectangle, _, W, H } ) ->
W * H;
% triangle
area( T = { triangle, _, _, _ } ) ->
triangle_area( triangle_sides( T ) ).
%%% perimeter
% circle
perimeter( { circle, _, R } ) ->
2 * math:pi() * R;
% rectangle
perimeter( { rectangle, _, W, H } ) ->
2 * ( W + H );
% triangle
perimeter( T = { triangle, _, _, _ } ) ->
{ A, B, C } = triangle_sides( T ),
A + B + C.
%%% enclose
% circle
enclose( { circle, C, R } ) ->
{ rectangle, C, 2 * R, 2 * R };
% rectangle
enclose( R = { rectangle, _, _, _ } ) ->
R;
% triangle
enclose( { triangle, { X1, Y1 }, { X2, Y2 }, { X3, Y3 } } ) ->
{ Cx, W } = mean_diff( min_max( X1, X2, X3 ) ),
{ Cy, H } = mean_diff( min_max( Y1, Y2, Y3 ) ),
{ rectangle, { Cx, Cy }, W, H }.
%%% triangle utilities
% determine the minimum and maximum of three values
min_max( A, B, C ) ->
{ min( min( A, B ), C ), max( max( A, B ), C ) }.
% determine the mean and difference between a min and a max value
mean_diff( { Min, Max } ) ->
{ ( Min + Max ) / 2, Max - Min }.
% calculate the lengths of all sides of a triangle
triangle_sides( { triangle, P1, P2, P3 } ) ->
{ distance( P1, P2 ), distance( P2, P3 ), distance( P3, P1 ) }.
% calculate the area a triangle from its sides
triangle_area( { A, B, C } ) ->
S = ( A + B + C ) / 2,
math:sqrt( S * ( S - A ) * ( S - B ) * ( S - C ) ).
% calculate the distance between two points
distance( { X1, Y1 }, { X2, Y2 } ) ->
math:sqrt( ( X2 - X1 ) * ( X2 - X1 ) + ( Y2 - Y1 ) * ( Y2 - Y1 ) ).
-module( test_shapes ).
-export( [ test/0 ] ).
%%% test the different shape types
test() ->
test_circles(),
test_rectangles(),
test_triangles(),
ok.
%%% circles
test_circles() ->
Pi = math:pi(),
% boring, centred circle
Circle1 = { circle, { 0.0, 0.0 }, 4.0 },
Results1 = {
8.0 * Pi,
16.0 * Pi,
{ rectangle, { 0.0, 0.0 }, 8.0, 8.0 }
},
check( Circle1, Results1 ),
% a little less boring, off-centre circle
Circle2 = { circle, { 2.0, 1.0 }, 5.3 },
Results2 = {
10.6 * Pi,
28.09 * Pi,
{ rectangle, { 2.0, 1.0 }, 10.6, 10.6 }
},
check( Circle2, Results2 ),
ok.
%%% rectangles
test_rectangles() ->
% small, boring, centred rectangle
Rect1 = { rectangle, { 0.0, 0.0 }, 4.0, 2.0 },
Results1 = {
12.0,
8.0,
Rect1
},
check( Rect1, Results1 ),
% large, off-centre, still boring rectangle
Rect2 = { rectangle, { 12.0, 3.0 }, 14.4, 21.2 },
Results2 = {
71.2,
305.28,
Rect2
},
check( Rect2, Results2 ),
ok.
%%% triangles
test_triangles() ->
% rectangle triangle
Tri1 = { triangle, { 0.0, 0.0 }, { 3.0, 0.0 }, { 3.0, 4.0 } },
Results1 = {
12.0,
6.0,
{ rectangle, { 1.5, 2.0 }, 3.0, 4.0 }
},
check( Tri1, Results1 ),
% equilateral triangle
Tri2 = { triangle, { 1.0, -1.0 }, { 5.0, -1.0 }, { 3.0, 4.0 * math:sqrt( 3.0 ) / 2.0 - 1.0 } },
Results2 = {
12.0,
4.0 * 4.0 * math:sqrt( 3.0 ) / 2.0 / 2.0,
{ rectangle, { 3.0, 4.0 * math:sqrt( 3.0 ) / 4.0 - 1.0 }, 4.0, 4.0 * math:sqrt( 3.0 ) / 2.0 }
},
check( Tri2, Results2 ),
% boring triangle
Tri3 = { triangle, { -4.0, -2.0 }, { 3.0, 3.0 }, { 0.0, 10.0 } },
Results3 = {
math:sqrt( 49.0 + 25.0 ) + math:sqrt( 9.0 + 49.0 ) + math:sqrt( 16.0 + 144.0 ),
( 7.0 * 12.0 - ( 7.0 * 5.0 + 3.0 * 7.0 + 4.0 * 12.0 ) / 2.0 ),
{ rectangle, { -0.5, 4.0 }, 7.0, 12.0 }
},
check( Tri3, Results3 ),
ok.
%%% check the result of a test
% calculate perimeter, area and enclosing rectangle for the shape
% and compare with expected resuits
check( Shape, Results ) ->
Calculated = { shapes:perimeter( Shape ), shapes:area( Shape ), shapes:enclose( Shape ) },
CalculatedSimpler = { shapes_simpler:perimeter( Shape ), shapes_simpler:area( Shape ), shapes_simpler:enclose( Shape ) },
compare_results( Calculated, Results ),
compare_results( CalculatedSimpler, Results ).
compare_results( Calculated, Results ) ->
{ P, A, E } = Calculated,
{ ExP, ExA, ExE } = Results,
almost( P, ExP ),
almost( A, ExA ),
almost( E, ExE ).
%%% compare two results with a margin for rounding errors
% in case we want to skip a test
almost( _, ignore ) ->
true;
% compare two rectangles
almost( { rectangle, { X, Y }, W, H }, { rectangle, { ExX, ExY }, ExW, ExH } ) ->
almost( X, ExX ),
almost( Y, ExY ),
almost( W, ExW ),
almost( H, ExH );
% compare two numbers
almost( A, B ) ->
true = abs( A - B ) < 1.0e-6.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment