Math-ConvexHull-1.04/0000755000175000017500000000000011526024037013076 5ustar tseetseeMath-ConvexHull-1.04/Changes0000644000175000017500000000142411526023734014375 0ustar tseetseeRevision history for Perl extension Math::ConvexHull. 1.04 Sun Feb 13 19:50 2011 - Fixed issues with points that lie on the convex hull but aren't part of it. - Optimize the implementation a bit. 1.03 Thu May 29 14:24 2008 - Document that you can actually break the implementation by passing in randomly ordered data with duplicates. Don't do that! (Thanks to Anton Berezin!) 1.02 Thu Sep 06 11:39 2007 - Fixed bug with duplicate points. (Thanks to John Harvey!) 1.01 Fri Jun 30 21:25 2006 - Distribution upgrade: --> Added META.yml --> Added t/ and moved test.pl --> Added POD tests. 1.00 Sat Jul 26 15:46 2003 - original version as uploaded to CPAN Math-ConvexHull-1.04/Makefile.PL0000644000175000017500000000120311526010356015043 0ustar tseetseeuse 5.006; use ExtUtils::MakeMaker; # See lib/ExtUtils/MakeMaker.pm for details of how to influence # the contents of the Makefile that is written. WriteMakefile( 'NAME' => 'Math::ConvexHull', 'VERSION_FROM' => 'lib/Math/ConvexHull.pm', # finds $VERSION 'LICENSE' => 'perl', 'PREREQ_PM' => { 'Test::More' => '0', 'List::Util' => '0', 'Data::Dumper' => '0', }, # e.g., Module::Name => 1.1 ($] >= 5.005 ? ## Add these new keywords supported since 5.005 (ABSTRACT_FROM => 'lib/Math/ConvexHull.pm', # retrieve abstract from module AUTHOR => 'Steffen Mueller ') : ()), ); Math-ConvexHull-1.04/t/0000755000175000017500000000000011526024037013341 5ustar tseetseeMath-ConvexHull-1.04/t/0-podcover.t0000644000175000017500000000031411332011453015473 0ustar tseetsee#!/usr/bin/perl use strict; use warnings; use Test::More; eval "use Test::Pod::Coverage 1.00"; plan skip_all => "Test::Pod::Coverage 1.00 required for testing POD coverage" if $@; all_pod_coverage_ok(); Math-ConvexHull-1.04/t/2-same-coord.t0000644000175000017500000000330411526021571015716 0ustar tseetseeuse strict; use warnings; use Test::More; use lib 'lib'; use List::Util qw(sum); use Math::ConvexHull qw/convex_hull/; use Data::Dumper; my @tests = ( { name => 'square', input => [[0,0],[1,0],[1,1],[0,1]], output => [[0,0],[1,0],[1,1],[0,1]], }, { name => 'square with extra point inside', input => [[0,0],[1,0],[1,1],[0.5,0.999],[0,1]], output => [[0,0],[1,0],[1,1],[0,1]], }, { name => 'square with extra point outside', input => [[0,0],[1,0],[1,1],[0.5,1.001],[0,1]], output => [[0,0],[1,0],[1,1],[0.5,1.001],[0,1]], }, { name => 'square with extra point on hull', input => [[0,0],[1,0],[1,1],[0.5,1],[0,1]], output => [[0,0],[1,0],[1,1],[0,1]], }, ); plan tests => 2 * @tests + sum(map scalar(@{$_->{output}}), @tests); foreach my $test (@tests) { my $expect = $test->{output}; my $res = convex_hull($test->{input}); ok(ref($res) eq 'ARRAY', "$test->{name}: convex_hull() returns an array reference"); if (not is(scalar(@$res), scalar(@$expect), "$test->{name}: convex_hull() correct no. of RVs")) { diag( "Got: " . Dumper($res) . "\nExpected: " . Dumper($expect) ); } SKIP: { skip 'Bad no. of returned points, no point checking individuals', scalar(@$expect) if scalar(@$res) != scalar(@$expect); foreach my $ip (0..$#$res) { ok( _feq($res->[$ip][0], $expect->[$ip][0]) && _feq($res->[$ip][1], $expect->[$ip][1]), "$test->{name}: Point number $ip in hull is correct" ); } } # end SKIP } sub _feq { return 1 if ($_[0]+1e-10 > $_[1]) && ($_[0]-1e-10 < $_[1]); return 0; } Math-ConvexHull-1.04/t/0-pod.t0000644000175000017500000000021611332011453014435 0ustar tseetseeuse strict; use Test::More; eval "use Test::Pod 1.00"; plan skip_all => "Test::Pod 1.00 required for testing POD" if $@; all_pod_files_ok(); Math-ConvexHull-1.04/t/1-basic.t0000644000175000017500000000212711332011453014740 0ustar tseetseeuse strict; use warnings; use Test::More tests => 8; use lib 'lib'; use_ok('Math::ConvexHull'); use Math::ConvexHull qw/convex_hull/; my $start = [[1,0],[1,1],[0.1,0.05],[0.9,0.5],[0,0]]; my $t = convex_hull($start); ok(1, 'convex_hull() did not complain'); my %results = (map {($_, undef)} @$start); ok( ( @$t == 3 and (map {delete $results{$_}} @$t) and ( exists $results{$start->[2]} and exists $results{$start->[3]} ) ), 'convex_hull returned correct result.' ); { # another one my $from = [[0,0], [1,0],[0.2,0.9], [0.2,0.5], [0.2,0.5], [0,1], [1,1],]; #my $from = [[0,0], [1,0], [0.2,0.5], [0.2,0.5], [0.2,0.9], [0,1], [1,1]]; my $to = [[0,0], [1,0], [1,1], [0,1]]; my $res = convex_hull($from); ok(@$res == @$to, "convex_hull returns correct number of points"); for (0..3) { ok( _feq($res->[$_][0], $to->[$_][0]) && _feq($res->[$_][1], $to->[$_][1]), "Point number $_ in hull is correct" ); } } sub _feq { return 1 if ($_[0]+1e-15 > $_[1]) && ($_[0]-1e-15 < $_[1]); return 0; } Math-ConvexHull-1.04/MANIFEST0000644000175000017500000000032511526024037014227 0ustar tseetseeChanges lib/Math/ConvexHull.pm Makefile.PL MANIFEST This list of files README t/0-pod.t t/0-podcover.t t/1-basic.t t/2-same-coord.t META.yml Module meta-data (added by MakeMaker) Math-ConvexHull-1.04/README0000644000175000017500000000625611526023752013772 0ustar tseetseeNAME Math::ConvexHull - Calculate convex hulls using Graham's scan (n*log(n)) SYNOPSIS use Math::ConvexHull qw/convex_hull/; $hull_array_ref = convex_hull(\@points); DESCRIPTION "Math::ConvexHull" is a simple module that calculates convex hulls from a set of points in 2D space. It is a straightforward implementation of the algorithm known as Graham's scan which, with complexity of O(n*log(n)), is the fastest known method of finding the convex hull of an arbitrary set of points. There are some methods of eliminating points that cannot be part of the convex hull. These may or may not be implemented in a future version. The implementation cannot deal with duplicate points. Therefore, points which are very, very close (think floating point close) to the previous point are dropped since version 1.02 of the module. However, if you pass in randomly ordered data which contains duplicate points, this safety measure might not help you. In that case, you will have to remove duplicates yourself. EXPORT None by default, but you may choose to have the "convex_hull()" subroutine exported to your namespace using standard Exporter semantics. convex_hull() subroutine "Math::ConvexHull" implements exactly one public subroutine which, surprisingly, is called "convex_hull()". "convex_hull()" expects an array reference to an array of points and returns an array reference to an array of points in the convex hull. In this context, a point is considered to be a reference to an array containing an x and a y coordinate. So an example use of "convex_hull()" would be: use Data::Dumper; use Math::ConvexHull qw/convex_hull/; print Dumper convex_hull( [ [0,0], [1,0], [0.2,0.9], [0.2,0.5], [0,1], [1,1], ] ); # Prints out the points [0,0], [1,0], [0,1], [1,1]. Please note that "convex_hull()" does not return *copies* of the points but instead returns the same array references that were passed in. SEE ALSO New versions of this module can be found on http://steffen-mueller.net or CPAN. After implementing the algorithm from my CS notes, I found the exact same implementation in the German translation of Orwant et al, "Mastering Algorithms with Perl". Their code reads better than mine, so if you looked at the module sources and don't understand what's going on, I suggest you have a look at the book. In early 2011, much of the module was rewritten to use the formulation of the algorithm that was shown on the Wikipedia article on Graham's scan at the time. This takes care of issues with including collinear points in the hull. One of these days, somebody should implement Chan's algorithm instead... AUTHOR Steffen Mueller, COPYRIGHT AND LICENSE Copyright (C) 2003-2011 by Steffen Mueller This library is free software; you can redistribute it and/or modify it under the same terms as Perl itself, either Perl version 5.6 or, at your option, any later version of Perl 5 you may have available. Math-ConvexHull-1.04/META.yml0000644000175000017500000000115711526024037014353 0ustar tseetsee--- #YAML:1.0 name: Math-ConvexHull version: 1.04 abstract: Calculate convex hulls using Graham's scan (n*log(n)) author: - Steffen Mueller license: perl distribution_type: module configure_requires: ExtUtils::MakeMaker: 0 build_requires: ExtUtils::MakeMaker: 0 requires: Data::Dumper: 0 List::Util: 0 Test::More: 0 no_index: directory: - t - inc generated_by: ExtUtils::MakeMaker version 6.55_02 meta-spec: url: http://module-build.sourceforge.net/META-spec-v1.4.html version: 1.4 Math-ConvexHull-1.04/lib/0000755000175000017500000000000011526024037013644 5ustar tseetseeMath-ConvexHull-1.04/lib/Math/0000755000175000017500000000000011526024037014535 5ustar tseetseeMath-ConvexHull-1.04/lib/Math/ConvexHull.pm0000644000175000017500000001623011526023613017163 0ustar tseetseepackage Math::ConvexHull; use 5.006; use strict; use warnings; use constant PI => 3.1415926535897932384626433832795; require Exporter; our $VERSION = '1.04'; our @ISA = qw(Exporter); our @EXPORT_OK = qw( convex_hull ); our %EXPORT_TAGS = ( 'all' => \@EXPORT_OK ); sub convex_hull { my $points = shift; my $start_index = _find_start_point($points); # O(n) my $angles_points = _calculate_angles($points, $start_index); # O(n) my $start = splice(@$angles_points, $start_index, 1); @$angles_points = sort { $a->[0] <=> $b->[0] || $a->[1][0] <=> $b->[1][0] || $a->[1][1] <=> $b->[1][1] } @$angles_points; # O(n*log(n)) unshift @$angles_points, $start; # remove duplicates (O(n)) # At the same time, drop the angle my $prev = $angles_points->[0][1]; my @hull; push @hull, $prev; for my $r (@$angles_points) { my $p = $r->[1]; push @hull, $p if ( $p->[0]+1e-15 <= $prev->[0] || $p->[0]-1e-15 >= $prev->[0] || $p->[1]+1e-15 <= $prev->[1] || $p->[1]-1e-15 >= $prev->[1]); $prev = $p; } # copy of the reference point as sentinel to stop loop unshift @hull, $hull[0]; my $n_in_hull = 2; # O(n) for (my $i = 3; $i < @hull; ++$i) { while ( _ccw( $hull[$n_in_hull-1], $hull[$n_in_hull], $hull[$i] ) <= 0 ) { if ($n_in_hull == 2) { ($hull[$i], $hull[$n_in_hull]) = (@hull[$n_in_hull, $i]); ++$i; } else { --$n_in_hull; } } ++$n_in_hull; ($hull[$i], $hull[$n_in_hull]) = (@hull[$n_in_hull, $i]); } # return points in hull return [@hull[1..$n_in_hull]]; } sub _ccw { my $p1 = shift; my $p2 = shift; my $p3 = shift; return( ($p2->[0] - $p1->[0])*($p3->[1] - $p1->[1]) - ($p2->[1] - $p1->[1])*($p3->[0] - $p1->[0]) ); } sub _calculate_angles { my $points = shift; my $start = shift; my $s_x = $points->[$start]->[0]; my $s_y = $points->[$start]->[1]; my $angles = []; my $p_no = 0; foreach my $p (@$points) { my $angle; if ($p_no == $start) { $angle = 0; } else { my $x_diff = $p->[0] - $s_x; my $y_diff = $p->[1] - $s_y; $angle = atan2($y_diff, $x_diff); $angle = PI-$angle if $angle < 0; } push @$angles, [$angle, $p]; $p_no++; } return $angles; } # Returns the index of the starting point. sub _find_start_point { my $points = shift; # Looking for the lowest, then leftmost point. my $s_point = 0; for (my $i = 1; $i < @$points; $i++) { my ($p0, $p1) = @{ $points->[$i] }; my ($sp0, $sp1) = @{ $points->[$s_point] }; if ( $p1 <= $sp1 and $p1 < $sp1 || $p0 < $sp0 ) { $s_point = $i; } } return $s_point; } 1; __END__ =head1 NAME Math::ConvexHull - Calculate convex hulls using Graham's scan (n*log(n)) =head1 SYNOPSIS use Math::ConvexHull qw/convex_hull/; $hull_array_ref = convex_hull(\@points); =head1 DESCRIPTION C is a simple module that calculates convex hulls from a set of points in 2D space. It is a straightforward implementation of the algorithm known as Graham's scan which, with complexity of O(n*log(n)), is the fastest known method of finding the convex hull of an arbitrary set of points. There are some methods of eliminating points that cannot be part of the convex hull. These may or may not be implemented in a future version. The implementation cannot deal with duplicate points. Therefore, points which are very, very close (think floating point close) to the previous point are dropped since version 1.02 of the module. However, if you pass in randomly ordered data which contains duplicate points, this safety measure might not help you. In that case, you will have to remove duplicates yourself. =head2 EXPORT None by default, but you may choose to have the C subroutine exported to your namespace using standard Exporter semantics. =head2 convex_hull() subroutine C implements exactly one public subroutine which, surprisingly, is called C. C expects an array reference to an array of points and returns an array reference to an array of points in the convex hull. In this context, a point is considered to be a reference to an array containing an x and a y coordinate. So an example use of C would be: use Data::Dumper; use Math::ConvexHull qw/convex_hull/; print Dumper convex_hull( [ [0,0], [1,0], [0.2,0.9], [0.2,0.5], [0,1], [1,1], ] ); # Prints out the points [0,0], [1,0], [0,1], [1,1]. Please note that C does not return I of the points but instead returns the same array references that were passed in. =head1 SEE ALSO New versions of this module can be found on http://steffen-mueller.net or CPAN. After implementing the algorithm from my CS notes, I found the exact same implementation in the German translation of Orwant et al, "Mastering Algorithms with Perl". Their code reads better than mine, so if you looked at the module sources and don't understand what's going on, I suggest you have a look at the book. In early 2011, much of the module was rewritten to use the formulation of the algorithm that was shown on the Wikipedia article on Graham's scan at the time. This takes care of issues with including collinear points in the hull. L One of these days, somebody should implement Chan's algorithm instead... =head1 AUTHOR Steffen Mueller, Esmueller@cpan.orgE =head1 COPYRIGHT AND LICENSE Copyright (C) 2003-2011 by Steffen Mueller This library is free software; you can redistribute it and/or modify it under the same terms as Perl itself, either Perl version 5.6 or, at your option, any later version of Perl 5 you may have available. =cut