Algorithm-Networksort
view release on metacpan or search on metacpan
lib/Algorithm/Networksort.pm view on Meta::CPAN
inputradius => 2,
compradius => 2,
inputline => 2,
compline => 2,
);
#
# Default parameters for text-based Knuth diagrams.
#
my %textset = (
inputbegin => "o-",
inputline => "---",
inputcompline => "-|-",
inputend => "-o\n",
compbegin => "-^-",
compend => "-v-",
gapbegin => " ",
gapcompline => " | ",
gapnone => " ",
gapend => " \n",
);
#
# Default graphing color parameters.
#
my %colorset = (
foreground => undef,
inputbegin => undef,
inputline => undef,
inputend => undef,
compbegin => undef,
compline=> undef,
compend => undef,
background => undef,
);
has algorithm => (
isa => 'Str', is => 'ro',
default => 'bosenelson',
);
has inputs => (
isa => 'Int', is => 'ro', required => 1,
);
has nwid => (
isa => 'Str', is => 'rw', required => 0,
predicate => 'has_nwid',
);
has comparators => (
isa => 'ArrayRef[ArrayRef[Int]]', is => 'rw', required => 0,
predicate => 'has_comparators',
);
has network => (
isa => 'ArrayRef[ArrayRef[Int]]', is => 'rw', required => 0,
predicate => 'has_network',
);
has ['depth', 'length'] => (
isa => 'Int', is => 'rw', required => 0,
init_arg => 0,
);
has creator => (
isa => 'Str', is => 'ro', required => 0,
default => sub { return "Perl module " . __PACKAGE__ . ", " .
"version $VERSION";}
);
has title => (
isa => 'Str', is => 'rw', required => 0,
predicate => 'has_title'
);
has formats => (
isa => 'ArrayRef[Str]', is => 'rw', required => 0,
init_arg => undef,
);
has grouped_format => (
isa => 'Str', is => 'rw', required => 0,
default => "%s,\n",
);
has index_base => (
isa => 'ArrayRef[Value]', is => 'rw', required => 0,
);
#
# Variables to track sorting statistics
#
my $swaps = 0;
=pod
=encoding UTF-8
=head1 NAME
Algorithm::Networksort - Create Sorting Networks.
=begin html
<svg xmlns="http://www.w3.org/2000/svg"
xmlns:xlink="http://www.w3.org/1999/xlink" width="90" height="84" viewbox="0 0 90 84">
<title>Bose-Nelson Sort for N = 4</title>
<defs>
<g id="I_1c13" style="stroke-width:2; fill:#000; stroke:#000" >
<line x1="12" y1="0" x2="78" y2="0" />
</g>
<g id="C1_1c13" style="stroke-width:2;fill:#000; stroke:#000" >
<line x1="0" y1="0" x2="0" y2="14" />
<circle cx="0" cy="0" r="2" /> <circle cx="0" cy="14" r="2" />
</g>
<g id="C2_1c13" style="stroke-width:2;fill:#000; stroke:#000" >
<line x1="0" y1="0" x2="0" y2="28" />
<circle cx="0" cy="0" r="2" /> <circle cx="0" cy="28" r="2" />
</g>
lib/Algorithm/Networksort.pm view on Meta::CPAN
Use a naive odd-even transposition sort. This is a primitive sort closely
related to bubble sort except that it is more parallel. Because other
algorithms are more efficient, this sort is included for illustrative
purposes.
=item 'balanced'
This network is described in the 1983 paper "The Balanced Sorting Network"
by M. Dowd, Y. Perl, M Saks, and L. Rudolph. It is not a particularly
efficient sort but it has some interesting properties due to the fact
that it is constructed as a series of successive identical sub-blocks,
somewhat like with 'oddeventrans'.
=item 'none'
Do not generate a set of comparators. Instead, take the set from an
outside source, using the C<comparators> option.
#
# Test our own 5-input network.
#
@cmptr = ([1,2], [0,2], [0,1], [3,4], [0,3], [1,4], [2,4], [1,3], [2,3]);
$nw = Algorithm::Networksort->new(inputs => 5,
algorithm => 'none',
comparators => [@cmptr]);
Internally, this is what L<nwsrt_best()|Algorithm::Networksort::Best/nwsrt_best()>
of L<Algorithm::Networksort::Best> uses.
=back
=item 'comparators'
The list of comparators provided by the user instead of by an algorithm.
=back
=cut
sub BUILD
{
my $self = shift;
my $alg = $self->algorithm();
my $inputs = $self->inputs();
my @network;
my @grouped;
#
# Catch errors
#
croak "Input size must be 2 or greater" if ($inputs < 2);
#
# Providing our own-grown network?
#
if ($alg eq 'none')
{
croak "No comparators provided" unless ($self->has_comparators);
$self->length(scalar @{ $self->comparators });
#
# Algorithm::Networksort::Best will set these, so
# only go through with this if this is a user-provided
# sequence of comparators.
#
unless ($self->has_network and $self->depth > 0)
{
@grouped = $self->group();
$self->network($self->comparators);
$self->depth(scalar @grouped);
$self->network([map { @$_ } @grouped]);
$self->title("Unknown $inputs-Inputs Comparator Set") unless ($self->has_title());
}
$self->nwid("nonalgorithmic-" . sprintf("%02d", $inputs)) unless ($self->has_nwid());
return $self;
}
croak "Unknown algorithm '$alg'" unless (exists $algname{$alg});
$self->nwid($alg . sprintf("%02d", $inputs));
@network = bosenelson($inputs) if ($alg eq 'bosenelson');
@network = hibbard($inputs) if ($alg eq 'hibbard');
@network = batcher($inputs) if ($alg eq 'batcher');
@network = bitonic($inputs) if ($alg eq 'bitonic');
@network = bubble($inputs) if ($alg eq 'bubble');
@network = oddeventransposition($inputs) if ($alg eq 'oddeventrans');
@network = balanced($inputs) if ($alg eq 'balanced');
@network = oddevenmerge($inputs) if ($alg eq 'oddevenmerge');
$self->title($algname{$alg} . " for N = " . $inputs) unless ($self->has_title);
$self->length(scalar @network);
$self->comparators(\@network); # The 'raw' list of comparators.
#
# Re-order the comparator list using the parallel grouping for
# the graphs. The resulting parallelism means less stalling
# when used in a pipeline.
#
@grouped = $self->group();
#
###### @grouped
#
$self->depth(scalar @grouped);
$self->network([map { @$_ } @grouped]);
return $self;
}
=head3 comparators()
The comparators in their 'raw' form, as generated by its algorithm.
For the comparators re-ordered in such a way as to take advantage
of parallelism, see L<network()>.
=head3 network()
Returns the comparators re-ordered from the 'raw' form, providing a
parallelized version of the comparator list; e.g., the best order possible
to prevent stalling in a CPU's pipeline.
This is the form used when printing the sorting network using L<formats()>.
=cut
=head3 algorithm_name()
Return the full text name of the algorithm, given its key name.
=cut
sub algorithm_name
{
my $self = shift;
my $algthm = $_[0] // $self->algorithm();
return $algname{$algthm} if (defined $algthm);
return "";
}
#
# @network = hibbard($inputs);
#
# Return a list of two-element lists that comprise the comparators of a
# sorting network.
#
# Translated from the ALGOL listed in T. N. Hibbard's article, A Simple
# Sorting Algorithm, Journal of the ACM 10:142-50, 1963.
#
# The ALGOL code was overly dependent on gotos. This has been changed.
#
sub hibbard
{
my $inputs = shift;
my @comparators;
my($bit, $xbit, $ybit);
#
# $t = ceiling(log2($inputs - 1)); but we'll
# find it using the length of the bitstring.
#
my $t = length sprintf("%b", $inputs - 1);
my $lastbit = 1 << $t;
#
# $x and $y are the comparator endpoints.
# We begin with values of zero and one.
#
my($x, $y) = (0, 1);
while (1 == 1)
{
#
# Save the comparator pair, and calculate the next
# comparator pair.
#
### hibbard() top of loop:
##### @comparators
#
push @comparators, [$x, $y];
#
# Start with a check of X and Y's respective bits,
# beginning with the zeroth bit.
#
$bit = 1;
$xbit = $x & $bit;
$ybit = $y & $bit;
#
# But if the X bit is 1 and the Y bit is
# zero, just clear the X bit and move on.
#
while ($xbit != 0 and $ybit == 0)
{
$x &= ~$bit;
$bit <<= 1;
$xbit = $x & $bit;
$ybit = $y & $bit;
}
if ($xbit != 0) # and $ybit != 0
{
$y &= ~$bit;
next;
}
#
# The X bit is zero if we've gotten this far.
#
if ($ybit == 0)
{
$x |= $bit;
$y |= $bit;
$y &= ~$bit if ($y > $inputs - 1);
next;
}
#
# The X bit is zero, the Y bit is one, and we might
# return the results.
#
do
{
return @comparators if ($bit == $lastbit);
$x &= ~$bit;
$y &= ~$bit;
$bit <<= 1; # Next bit.
if ($y & $bit)
{
$x &= ~$bit;
next;
}
$x |= $bit;
$y |= $bit;
} while ($y > $inputs - 1);
#
# No return, so loop onwards.
#
$bit = 1 if ($y < $inputs - 1);
$x &= ~$bit;
$y |= $bit;
}
}
#
# @network = bosenelson($inputs);
#
# Return a list of two-element lists that comprise the comparators of a
# sorting network.
#
# The Bose-Nelson algorithm.
#
sub bosenelson
{
my $inputs = shift;
return bn_split(0, $inputs);
}
#
# @comparators = bn_split($i, $length);
#
# The helper function that divides the range to be sorted.
#
# Note that the work of splitting the ranges is performed with the
# 'length' variables. The $i variable merely acts as a starting
# base, and could easily have been 1 to begin with.
#
sub bn_split
{
my($i, $length) = @_;
my @comparators = ();
#
### bn_split():
#### $i
#### $length
#
if ($length >= 2)
{
my $mid = $length >> 1;
push @comparators, bn_split($i, $mid);
push @comparators, bn_split($i + $mid, $length - $mid);
push @comparators, bn_merge($i, $mid, $i + $mid, $length - $mid);
}
#
### bn_split() returns
##### @comparators
#
return @comparators;
}
#
# @comparators = bn_merge($i, $length_i, $j, $length_j);
#
# The other helper function that adds comparators to the list, for a
# given pair of ranges.
#
# As with bn_split, the different conditions all depend upon the
# lengths of the ranges. The $i and $j variables merely act as
# starting bases.
#
sub bn_merge
{
my($i, $length_i, $j, $length_j) = @_;
my @comparators = ();
#
### bn_merge():
#### $i
#### $length_i
#### $j
#### $length_j
#
if ($length_i == 1 && $length_j == 1)
{
push @comparators, [$i, $j];
}
elsif ($length_i == 1 && $length_j == 2)
{
push @comparators, [$i, $j + 1];
push @comparators, [$i, $j];
}
elsif ($length_i == 2 && $length_j == 1)
{
push @comparators, [$i, $j];
push @comparators, [$i + 1, $j];
}
else
{
my $i_mid = int($length_i/2);
my $j_mid = int(($length_i & 1)? $length_j/2: ($length_j + 1)/2);
push @comparators, bn_merge($i, $i_mid, $j, $j_mid);
push @comparators, bn_merge($i + $i_mid, $length_i - $i_mid, $j + $j_mid, $length_j - $j_mid);
push @comparators, bn_merge($i + $i_mid, $length_i - $i_mid, $j, $j_mid);
}
#
### bn_merge() returns
##### @comparators
#
return @comparators;
}
#
# @network = batcher($inputs);
#
# Return a list of two-element lists that comprise the comparators of a
# sorting network.
#
# Batcher's sort as laid out in Knuth, Sorting and Searching, algorithm 5.2.2M.
#
sub batcher
{
my $inputs = shift;
my @network;
#
# $t = ceiling(log2($inputs)); but we'll
# find it using the length of the bitstring.
#
my $t = length sprintf("%b", $inputs);
my $p = 1 << ($t -1);
while ($p > 0)
{
my $q = 1 << ($t -1);
my $r = 0;
my $d = $p;
while ($d > 0)
{
for my $i (0 .. $inputs - $d - 1)
{
push @network, [$i, $i + $d] if (($i & $p) == $r);
}
$d = $q - $p;
$q >>= 1;
$r = $p;
}
$p >>= 1;
}
return @network;
}
#
# @network = bitonic($inputs);
#
# Return a list of two-element lists that comprise the comparators of a
# sorting network.
#
# Batcher's Bitonic sort as described here:
# http://www.iti.fh-flensburg.de/lang/algorithmen/sortieren/bitonic/oddn.htm
#
sub bitonic
{
my $inputs = shift;
my @network;
my ($sort, $merge);
$sort = sub {
my ($lo, $n, $dir) = @_;
if ($n > 1) {
my $m = $n >> 1;
$sort->($lo, $m, !$dir);
$sort->($lo + $m, $n - $m, $dir);
$merge->($lo, $n, $dir);
}
};
$merge = sub {
my ($lo, $n, $dir) = @_;
if ($n > 1) {
#
# $t = ceiling(log2($n - 1)); but we'll
# find it using the length of the bitstring.
#
my $t = length sprintf("%b", $n - 1);
my $m = 1 << ($t - 1);
for my $i ($lo .. $lo+$n-$m-1)
{
push @network, ($dir)? [$i, $i+$m]: [$i+$m, $i];
}
$merge->($lo, $m, $dir);
$merge->($lo + $m, $n - $m, $dir);
}
};
$sort->(0, $inputs, 1);
return @{ make_network_unidirectional(\@network) };
}
## This function "re-wires" a bi-directional sorting network
## and turns it into a normal, uni-directional network.
sub make_network_unidirectional
{
my ($network_ref) = @_;
my @network = @$network_ref;
for my $i (0..$#network) {
my $comparator = $network[$i];
my ($x, $y) = @$comparator;
if ($x > $y) {
for my $j (($i+1)..$#network) {
my $j_comparator = $network[$j];
my ($j_x, $j_y) = @$j_comparator;
$j_comparator->[0] = $y if $x == $j_x;
$j_comparator->[1] = $y if $x == $j_y;
$j_comparator->[0] = $x if $y == $j_x;
$j_comparator->[1] = $x if $y == $j_y;
}
($comparator->[0], $comparator->[1]) = ($comparator->[1], $comparator->[0]);
}
}
return \@network;
}
#
# @network = bubble($inputs);
#
# Simple bubble sort network, only for comparison purposes.
#
sub bubble
{
my $inputs = shift;
my @network;
for my $j (reverse 0 .. $inputs - 1)
{
push @network, [$_, $_ + 1] for (0 .. $j - 1);
}
return @network;
}
#
# @network = bubble($inputs);
#
# Simple odd-even transposition network, only for comparison purposes.
#
sub oddeventransposition
{
my $inputs = shift;
my @network;
my $odd;
for my $stage (0 .. $inputs - 1)
{
for (my $j = $odd ? 1 : 0; $j < $inputs - 1; $j += 2)
{
push @network, [$j, $j+1];
}
$odd = !$odd;
}
return @network;
}
#
# @network = balanced($inputs);
#
# "The Balanced Sorting Network" by M. Dowd, Y. Perl, M Saks, and L. Rudolph
# ftp://ftp.cs.rutgers.edu/cs/pub/technical-reports/pdfs/DCS-TR-127.pdf
#
sub balanced
{
my $inputs = shift;
my @network;
#
# $t = ceiling(log2($inputs - 1)); but we'll
# find it using the length of the bitstring.
#
my $t = length sprintf("%b", $inputs - 1);
for (1 .. $t)
{
for (my $curr = 1 << $t; $curr > 1; $curr >>= 1)
{
for (my $i = 0; $i < (1 << $t); $i += $curr)
{
for (my $j = 0; $j < int($curr/2); $j++)
{
my $wire1 = $i+$j;
my $wire2 = $i+$curr-$j-1;
push @network, [$wire1, $wire2]
if $wire1 < $inputs && $wire2 < $inputs;
}
}
}
}
return @network;
}
#
# @network = oddevenmerge($inputs);
#
# Batcher's odd-even merge sort as described here:
# http://www.iti.fh-flensburg.de/lang/algorithmen/sortieren/networks/oemen.htm
# http://cs.engr.uky.edu/~lewis/essays/algorithms/sortnets/sort-net.html
#
sub oddevenmerge
{
my $inputs = shift;
my @network;
#
# $t = ceiling(log2($inputs - 1)); but we'll
# find it using the length of the bitstring.
#
my $t = length sprintf("%b", $inputs - 1);
my ($add_elem, $sort, $merge);
$add_elem = sub {
my ($i, $j) = @_;
push @network, [$i, $j]
if $i < $inputs && $j < $inputs;
};
$sort = sub {
my ($lo, $n) = @_;
if ($n > 1)
{
my $m = int($n / 2);
$sort->($lo, $m);
$sort->($lo + $m, $m);
$merge->($lo, $n, 1);
}
};
$merge = sub {
my ($lo, $n, $r) = @_;
my $m = int($r * 2);
if ($m < $n)
{
$merge->($lo, $n, $m); # even
$merge->($lo + $r, $n, $m); # odd
for (my $i=$lo + $r; $i + $r < $lo + $n; $i += $m)
{
$add_elem->($i, $i + $r);
}
}
else
{
$add_elem->($lo, $lo + $r);
}
};
$sort->(0, 1 << $t);
return @network;
}
#
# $array_ref = $nw->sort(\@array);
#
# Use the network of comparators to sort the elements in the
# array. Returns the reference to the array, which is sorted
# in-place.
#
# This function is for testing and statistical purposes only, as
# interpreting sorting pairs ad hoc in an interpreted language is
# going to be very slow.
#
lib/Algorithm/Networksort.pm view on Meta::CPAN
#
my(@formats) = $self->formats? @{ $self->formats() }: ();
unless (scalar @formats)
{
$string = $self->_dflt_formatted($network);
chop $string; # Remove trailing comma
return '[' . $string . ']';
}
my $index_base = $self->index_base();
for my $cmptr (@$network)
{
@$cmptr = @$index_base[@$cmptr] if (defined $index_base);
for my $fmt (@formats)
{
$string .= sprintf($fmt, @$cmptr);
}
}
return $string;
}
=head3 group()
Takes the comparator list and returns a list of comparator lists, each
sub-list representing a group of comparators that can be operate without
interfering with each other, depending on what is needed for
interference-free grouping.
There is one option available, 'grouping', that will produce a grouping
that represents parallel operations of comparators. Its values may be:
=over 3
=item 'graph'
Group the comparators as parallel as possible for graphing.
=item 'parallel'
Arrange the sequence in parallel so that it has a minimum depth. This,
after flattening the lists into a single list again, is what is used to
produce the sequence in L<network()>.
=item I<number>
Arrange the comparators every I<number> count. This is for simple
printing of the comparators, without any grouping for graphing or
maximum parallelization.
=back
The chances that you will need to use this function are slim, but the
following code snippet may represent an example:
my $nw = Algorithm::Networksort->new(inputs => 8, algorithm => 'batcher');
print "There are ", $nw->length(),
" comparators in this network, grouped into\n",
$nw->depth(), " parallel operations.\n\n";
print $nw, "\n";
my @grouped_network = $nw->group(grouping=>'graph');
print "\nThis will be graphed in ", scalar @grouped_network,
" columns.\n";
This will produce:
There are 19 comparators in this network, grouped into 6 parallel operations.
[[0,4], [1,5], [2,6], [3,7]]
[[0,2], [1,3], [4,6], [5,7]]
[[2,4], [3,5], [0,1], [6,7]]
[[2,3], [4,5]]
[[1,4], [3,6]]
[[1,2], [3,4], [5,6]]
This will be graphed in 11 columns.
=cut
sub group
{
my $self = shift;
my $network = $self->comparators;
my $inputs = $self->inputs;
my %opts = @_;
my @node_range_stack;
my @node_stack;
my $grp = $opts{grouping} // 'parallel';
#
# Group the comparator nodes by N.
#
if ($grp =~ /^[0-9]+$/)
{
my @s = @$network;
while (scalar @s)
{
push @node_stack, [splice(@s, 0, $grp)];
}
return @node_stack;
}
unless ($grp =~ /^(graph|parallel)$/)
{
carp "Unknown option '$grp'";
return undef;
}
#
# Group the comparator nodes by columns.
#
for my $comparator (@$network)
{
my($from, $to) = @$comparator;
lib/Algorithm/Networksort.pm view on Meta::CPAN
my @vcoord = ($grset{vt_margin}) x $inputs;
for my $idx (0..$inputs-1)
{
$vcoord[$idx] += $idx * ($grset{vt_sep} + $grset{inputline});
}
return @vcoord;
}
sub colorswithdefaults
{
#
# Get the colorset, using the foreground color as the default color
# for drawing, except for 'background', which has its own default.
#
my @keylist = grep{$_ !~ /background/} keys %colorset;
my %clrset = map{$_ => ($colorset{$_} // $colorset{foreground} // '#000')}
@keylist;
$clrset{background} = $colorset{background};
#
#### colorset after defaults are set: %clrset
#
return %clrset;
}
#
# Helper function to see if the drawing colors are all identical.
# The hash is presumed to be one returned by colorswithdefaults().
#
sub ismonotone
{
my (%clrs) = @_;
my @keylist = grep{$_ !~ /foreground|background/} keys %colorset;
return !scalar grep{$clrs{$_} ne $clrs{foreground}} @keylist;
}
#
# For the postscript setrgbcolor operator.
#
sub psrgbcolor
{
my $color = $_[0] // '000';
#
# A postscript user might have this in an 'rr gg bb' format,
# or not bother with the '#'.
#
$color =~ s/ //g;
$color =~ s/^#//;
if ($color =~ /[^0-9a-fA-F]/)
{
carp "Color '$color' is not in six or three hexadecimal digit RGB form.";
$color = "000";
}
if (length $color == 3)
{
$color =~ s/(.)(.)(.)/$1$1 $2$2 $3$3/;
}
elsif (length $color == 6)
{
$color =~ s/(..)(..)(..)/$1 $2 $3/;
}
else
{
carp "Color '$color' is not in six or three hexadecimal digit RGB form.";
$color = "0 0 0";
}
return join(" ", map{hex()/256} split(/ /, $color));
}
=head2 Methods For Graphing
=head3 graph_eps()
Returns a string that graphs out the network's comparators. The format
will be encapsulated postscript.
my $nw = nwsrt(inputs = 4, algorithm => 'bitonic');
print $nw->graph_eps();
=cut
sub graph_eps
{
my $self = shift;
my $network = $self->network();
my $inputs = $self->inputs();
my %grset = $self->graphsettings();
my @node_stack = $self->group(grouping => 'graph');
my $columns = scalar @node_stack;
#
# Set up the vertical and horizontal coordinates.
#
my @vcoord = vt_coords($inputs, %grset);
my @hcoord = hz_coords($columns, %grset);
my $xbound = $hcoord[$columns - 1] + $grset{hz_margin} + $grset{indent};
my $ybound = $vcoord[$inputs - 1] + $grset{vt_margin};
my $i_radius = $grset{inputradius};
my $c_radius = $grset{compradius};
my %clrset = colorswithdefaults();
my $monotone = ismonotone(%clrset);
map{$clrset{$_} = psrgbcolor($clrset{$_}) . " setrgbcolor"} keys %clrset;
#
# Create the necessary DSC, the arrays of vertical
# and horizontal coordinates, and the left and right
# margin definitions.
#
my $string =
qq(%!PS-Adobe-3.0 EPSF-3.0\n%%BoundingBox: 0 0 $xbound $ybound\n) .
qq(%%CreationDate: ) . localtime() .
qq(\n%%Creator: ) . $self->creator() .
( run in 1.031 second using v1.01-cache-2.11-cpan-56fb94df46f )