The Perl and Raku Conference 2025: Greenville, South Carolina - June 27-29 Learn more

use strict;
sub _PARENT () { 0 }
sub _RANK () { 1 }
sub new {
my $class = shift;
bless { }, $class;
}
sub add {
my ($self, @elems) = @_;
@elems = grep !defined $self->{$_}, @elems;
@$self{ @elems } = map [ $_, 0 ], @elems;
}
sub _parent {
return undef unless defined $_[1];
Graph::__carp_confess(__PACKAGE__ . "::_parent: bad arity") if @_ < 2 or @_ > 3;
if (@_ == 2) {
exists $_[0]->{ $_[ 1 ] } ? $_[0]->{ $_[1] }->[ _PARENT ] : undef;
} else {
$_[0]->{ $_[1] }->[ _PARENT ] = $_[2];
}
}
sub _rank {
return unless defined $_[1];
Graph::__carp_confess(__PACKAGE__ . "::_rank: bad arity") if @_ < 2 or @_ > 3;
if (@_ == 2) {
exists $_[0]->{ $_[1] } ? $_[0]->{ $_[1] }->[ _RANK ] : undef;
} else {
$_[0]->{ $_[1] }->[ _RANK ] = $_[2];
}
}
sub find {
my ($self, @v) = @_;
my @ret;
for my $x (@v) {
push(@ret, undef), next unless defined(my $px = $self->_parent($x));
$self->_parent( $x, $self->find( $px ) ) if $px ne $x;
push @ret, $self->_parent( $x );
}
@ret;
}
sub union {
my ($self, @edges) = @_;
$self->add(map @$_, @edges);
for my $e (@edges) {
my ($px, $py) = $self->find( @$e );
next if $px eq $py;
my $rx = $self->_rank( $px );
my $ry = $self->_rank( $py );
# print "union($x, $y): px = $px, py = $py, rx = $rx, ry = $ry\n";
if ( $rx > $ry ) {
$self->_parent( $py, $px );
} else {
$self->_parent( $px, $py );
$self->_rank( $py, $ry + 1 ) if $rx == $ry;
}
}
}
sub same {
my ($uf, $u, $v) = @_;
my ($fu, $fv) = $uf->find($u, $v);
return undef if grep !defined, $fu, $fv;
$fu eq $fv;
}
1;
__END__
=pod
=head1 NAME
Graph::UnionFind - union-find data structures
=head1 SYNOPSIS
use Graph::UnionFind;
my $uf = Graph::UnionFind->new;
# Add the vertices to the data structure.
$uf->add($u);
$uf->add($v);
# Join the partitions of the vertices.
$uf->union( $u, $v );
# Find the partitions the vertices belong to
# in the union-find data structure. If they
# are equal, they are in the same partition.
# If the vertex has not been seen,
# undef is returned.
my $pu = $uf->find( $u );
my $pv = $uf->find( $v );
$uf->same($u, $v) # Equal to $pu eq $pv.
# Has the union-find seen this vertex?
$uf->has( $v )
=head1 DESCRIPTION
I<Union-find> is a special data structure that can be used to track the
partitioning of a set into subsets (a problem also known as I<disjoint sets>).
C<Graph::UnionFind> is used for L<Graph/connected_components>,
L<Graph/connected_component>, and L<Graph/same_connected_components>
if you specify a true C<unionfind> parameter when you create an undirected
graph.
Union-find is one way: you cannot (easily) 'ununion' vertices once you
have 'unioned' them. This is why L<Graph> throws an exception if you
try to delete edges from a union-find graph.
=head2 API
=over 4
=item add
$uf->add(@v)
Add the vertices to the union-find.
=item union
$uf->union([$u, $v], [$w, $x], ...)
Add the edge u-v to the union-find. Also implicitly adds the vertices.
=item find
@partitions = $uf->find(@v)
For each given vertex, return the union-find partition it belongs to,
or C<undef> if it has not been added.
=item new
$uf = Graph::UnionFind->new()
The constructor.
=item same
$uf->same($u, $v)
Return true of the vertices belong to the same union-find partition
the vertex v belongs to, false otherwise.
=back
=head1 AUTHOR AND COPYRIGHT
Jarkko Hietaniemi F<jhi@iki.fi>
=head1 LICENSE
This module is licensed under the same terms as Perl itself.
=cut