# Author: Chao-Kuei Hung # For more info, including license, please see doc/index.html package BST; # Binary Search Tree use strict; use Carp; use vars qw(@ISA); @ISA = qw(Collection); use Collection; use TreeNode; sub new { my ($class, %opts) = @_; $class = ref($class) if ref($class); my ($t) = delete $opts{-type}; croak "'BST' code does not know how to process '$t' data\n" unless $t eq 'sortable'; my ($self) = $class->SUPER::new(%opts); my ($init_data) = (delete $opts{-init_data} or []); my ($operation) = (delete $opts{-operation} or []); my ($dummy) = TreeNode->new($self, undef, -status=>"hidden"); $self->{rootparent} = $dummy; my ($v); foreach $v (@$init_data) { # -node_opts is taken care of in insert() $self->insert($v); $self->cget(-canvas)->set_mark(1); } while (@$operation) { my ($op) = shift @$operation; my ($data) = shift @$operation; if ($op eq 'remove') { # $data->{area} = 0 unless defined $data->{area}; # $data->{name} = '?' unless defined $data->{name}; $self->remove($data); } elsif ($op eq 'insert') { $self->insert($data); } elsif ($op eq 'rot_cw') { $self->rotate_cw($data); } elsif ($op eq 'rot_ccw') { $self->rotate_ccw($data); } else { carp "unknown operation '$op' ignored\n"; } $self->cget(-canvas)->set_mark(1); } return $self; } sub root { # my ($self, $nv) = @_; my ($self, $nv) = @_; die if defined $nv; return $self->{rootparent}->child(0); } sub search { my ($self) = shift @_; return $self->root()->search(@_); } sub insert { my ($self, $sk_cont, %opts) = @_; # $sk_cont is search key, should have the same structure as -content=>... die "insertion works only for binary trees" unless $self->cget(-ary) == 2; my ($parent, $rank, $r, $c, $nn); if (ref $self->root()) { $parent = $self->search($sk_cont, -to_leaf=>1); $rank = $self->cget(-compare)->( $sk_cont, $parent->cget(-content) ); # skewed!! bad!! please check the case when compare returns 0 $rank = $rank <= 0 ? 0 : 1; } else { $parent = $self->{rootparent}; $rank = 0; } %opts = ( %{ $self->cget(-node_opts) }, %opts ); $nn = TreeNode->new($parent, $rank, -content=>$sk_cont, %opts); # as always, the host should take care of prepending %opts with -node_opts $nn->configure(-status=>"focus"); $self->cget(-canvas)->set_mark(0); $nn->configure(-status=>"done"); return $nn; } sub hide { my ($self, $node) = @_; # $node->configure(-status=>"discard"); $node->configure(-status=>"hidden"); $node->moveto(0,-0.5); } sub remove { my ($self, $node) = @_; die "removal works only for binary trees" unless $self->cget(-ary) == 2; if (not UNIVERSAL::isa($node, "TreeNode")) { $node = $self->search($node); if (not ref $node) { carp "can't find node for removal\n"; return undef; } } $node->configure(-status=>"focus"); $self->cget(-canvas)->set_mark(0); my ($p, $r, $n) = ($node->parent(), $node->rank(), 0); ++$n if ref $node->child(0); ++$n if ref $node->child(1); if ($n == 2) { my ($subst) = $node->child(0)->findmax(); my ($subst_status) = $subst->cget(-status); $self->remove($subst); $p->set_child($r, $subst); $subst->set_child(0, $node->child(0)); $subst->set_child(1, $node->child(1)); $subst->configure(-status=>$subst_status); # $self->cget(-canvas)->set_mark(0); } elsif ($n == 1) { my ($i) = ref $node->child(0) ? 0 : 1; $p->adopt_subtree($r, $node->child($i)); } else { # $n == 0 $p->set_child($r, undef); } $self->hide($node); $self->cget(-canvas)->set_mark(0); return $node; } sub rotate_cw { my ($self, $pivot) = @_; die "removal works only for binary trees" unless $self->cget(-ary) == 2; if (not UNIVERSAL::isa($pivot, "TreeNode")) { $pivot = $self->search($pivot); if (not ref $pivot) { carp "can't find node for rotation\n"; return; } } $pivot->rotate_cw(); } sub rotate_ccw { my ($self, $pivot) = @_; die "removal works only for binary trees" unless $self->cget(-ary) == 2; if (not UNIVERSAL::isa($pivot, "TreeNode")) { $pivot = $self->search($pivot); if (not ref $pivot) { carp "can't find node for rotation\n"; return; } } $pivot->rotate_ccw(); } $::Config->{BST} = { -ary => 2, }; if ($0 =~ /BST.pm$/) { # being tested as a stand-alone program, so run test code. require "utilalgo"; my ($mw, $ctrl, $can); $mw = MainWindow->new(-title=>"main_test"); $can->{main} = gen_can($mw, undef, -elevation=>1, -maxlevel=>2); $ctrl = gen_ctrl($mw, $can); my ($tr) = BST->new(-canvas=>$can->{main}, %{ do "data/countries.gr" }); # $can->{main}->set_mark(1); $ctrl->configure(-recorder=>0); # If the canvas refuses to show any change, remember to verify that: # - set_mark() was called at least once # - -recorder is set to zero before entering MainLoop # Failing to do either of the above will result in a mysterious bug # that takes days to figure out !@#$% Tk::MainLoop(); } 1;