logo       
Google Custom Search
    AddThis Social Bookmark Button
-->

[SPOILER] Perl 'Expert' Quiz-of-the-Week #10: msg#00070

Subject: [SPOILER] Perl 'Expert' Quiz-of-the-Week #10
As mentioned in my message about the regular quiz, my script uses a tree
where the internal nodes (questions) and the leaf nodes (animals) are
hashes, so that changing a node is very simple.

The code for removing an animal and a question from the tree looks like
this:

  my $replace = $fix->{$other};

  %$fix = %$replace;


In addition to the tree, this script stores a hash mapping animals to their
paths through the tree.  This is used to find the current location of an
animal when a contradiction occurs.  When a question is removed from the
tree, that question is spliced out of the paths of all the animals below it
in the tree.

Ronald


#!/usr/local/bin/perl -w

use strict;

use Storable;

my $data_file = $ARGV[0] || 'animal2.data';

my $data;

if (-e $data_file) {
  $data = retrieve($data_file);
}


# create a new data file with the provided animal name

if (not $data) {
  my $animal =
    ask("I don't know any animals!  Please tell me the name of an
  animal.");

  $data = { tree => { animal => $animal }, animals => { $animal => [] } };

  store $data, $data_file;

  print "Thank you!\n";

  exit;
}

my @path;


# play the game, asking questions until the end of a branch is reached

my $node = $data->{'tree'};
while (exists $node->{'question'}) {
  my $response = ask($node->{'question'});

  push @path, { question => $node->{'question'}, answer => $response };

  $node = $node->{$response};

}


# did I guess the animal?

my $response = ask_yn("Is it a $node->{'animal'}?");

if ($response eq 'y') {
  print "I win!\n";
  exit;
}


my $animal = lc ask("I give up.  What animal were you thinking of?");

if (my $old_path = $data->{'animals'}{$animal}) {

  # the animal is already in the tree somewhere else!

  my $div = find_divergence($data->{'animals'}{$animal}, \@path);

  if (not defined $div) {
    print "You just said it wasn't a $animal!\n";
    exit;
  }

  # verify the contradicted answer

  my $question = $div->{'question'};
  my $previous = $div->{'answer'};
  my $current  = $previous eq 'y' ? 'n' : 'y';

  my $response = ask_yn("Someone told me that for a $animal, the answer to
  " .
                        "'$question' is '$previous'.  Are you sure the " .
                        "answer is '$current'?");

  if ($response eq 'n') {
    print "Okay, please try again.\n";
    exit;
  }


  # remove the animal from its original location

  my $fix = $data->{'tree'};
  for (my $i = 0; $i < $#{$old_path}; ++$i) {
    $fix = $fix->{$old_path->[$i]{'answer'}};
  }
  my $other = $old_path->[-1]{'answer'} eq 'y' ? 'n' : 'y';

  my $replace = $fix->{$other};

  %$fix = %$replace;


  # update the paths of all the other affected animals

  splice_path($fix, $#{$old_path});

  if ($node == $replace) {
    # the contradiction was at the final question
    $node = $fix;

    pop @path;
  }

}


# add the animal to the tree with a new question

my $question =
  ask("What is a question that would distinguish a $animal " .
      "from a $node->{'animal'}?");

$question = ucfirst $question;
$question =~ s/[.?]$/?/;

my $answer = ask_yn("For a $animal, what would the answer be to " .
                    "'$question'?");

my $other = $answer eq 'y' ? 'n' : 'y';

$node->{'question'} = $question;
$node->{$answer}    = { animal => $animal };
$node->{$other}     = { animal => $node->{'animal'} };
delete $node->{'animal'};


# add the path for the new animal and update the path of the guessed animal

$data->{'animals'}{$animal} =
  [ @path, { question => $question, answer => $answer } ];
push @{$data->{'animals'}{$node->{$other}{'animal'}}},
  { question => $question, answer => $other };

store $data, $data_file;

print "Thank you!\n";

exit;


# ask a question until a response is given that contains non-whitespace
# remove leading and trailing whitespace from the response
sub ask {
  my($question) = @_;

  my $response = '';

  while (not($response or $response =~ /\S/)) {
    print "$question\n> ";
    chomp($response = <>);
  }

  if ($response eq 'q') {
    exit;
  }

  $response =~ s/^\s+//;
  $response =~ s/\s+$//;

  return $response;
}

# ask a question until a response is given that begins with y or n
# return y or n, respectively
sub ask_yn {
  my($question) = @_;

  my $response;
  while (not $response) {
    $response = ask($question);
    ($response) = $response=~ /^([yn])/i;
  }

  return $response;
}

# given two paths of questions and answers,
# find the position where the answers diverge
sub find_divergence {
  my($path1, $path2) = @_;

  for (my $i = 0; $i <= $#{$path1}; ++$i) {
    if ($path1->[$i]{'answer'} ne $path2->[$i]{'answer'}) {
      return $path1->[$i];
    }
  }

  return;
}

# remove the element at the given position
# from the paths of all animals under the given node
sub splice_path {
  my($node, $pos) = @_;

  if (my $animal = $node->{'animal'}) {
    splice(@{$data->{'animals'}{$animal}}, $pos, 1);
    return;
  }

  splice_path($node->{'y'}, $pos);
  splice_path($node->{'n'}, $pos);
}




<Prev in Thread] Current Thread [Next in Thread>