Algorithm-MasterMind

 view release on metacpan or  search on metacpan

lib/Algorithm/MasterMind/Consistent_Set.pm  view on Meta::CPAN

package Algorithm::MasterMind::Consistent_Set;

use warnings;
use strict;
use Carp;

use lib qw(../../lib ../../../../Algorithm-Evolutionary/lib/ 
	   ../../Algorithm-Evolutionary/lib/
	   ../../../lib);

our $VERSION =   sprintf "%d.%03d", q$Revision: 1.8 $ =~ /(\d+)\.(\d+)/g; 

use Algorithm::MasterMind qw(partitions);
use Algorithm::MasterMind::Secret;

sub new {
  my $class = shift;
  my $combinations = shift;
  my @secrets = map ( (new Algorithm::MasterMind::Secret $_), @$combinations );
  my $self = {  _combinations => \@secrets,
		_partitions => {}};
  bless $self, $class;
  $self->{'_partitions'} = compute_partitions( \@secrets );
  $self->{'_score'} = {}; # To store scores when they're available.
  return $self;
}

sub compute_partitions {
  my $secrets_ref = shift;
  my @secrets = @$secrets_ref;
  my %partitions;
  my %hash_results;
  for ( my $i = 0; $i <= $#secrets; $i ++ ) {
    for (my $j = 0; $j <= $#secrets; $j ++ ) {
      next if $i == $j;
      my $result = { blacks => 0,
		     whites => 0 } ;
      if ( $i < $j  ) {
	$secrets[$i]->check_secret ( $secrets[$j], $result );
	$hash_results{$secrets[$i]->{'_string'}}{$secrets[$j] ->{'_string'}} = $result;
      } else {
	$result = $hash_results{$secrets[$j]->{'_string'}}{$secrets[$i] ->{'_string'}} 
      }
      $partitions{$secrets[$i]->{'_string'}}{result_as_string($result)}++;
    }
  }
  return \%partitions
}

sub create_consistent_with {
  my $class = shift;
  my $combinations = shift;
  my $rules = shift;
  my @secrets = map ( (new Algorithm::MasterMind::Secret $_), @$combinations );
  my $self = {  _combinations => [],
		_partitions => {}};
  bless $self, $class;
  my %rule_secrets;
  map( ($rule_secrets{$_->{'combination'}} = new Algorithm::MasterMind::Secret $_->{'combination'}),
       @$rules );
  for my $s (@secrets ) {
    my $matches;
    for my $r (@$rules ) {
      my $this_result = { blacks => 0,
			  whites => 0 };
      $s->check_secret( $rule_secrets{$r->{'combination'}}, $this_result);
      $matches +=  result_as_string( $this_result ) eq result_as_string( $r );
    }
    if ( $matches == @$rules ) {
      push @{$self->{'_combinations'}}, $s
    }
  }
  $self->{'_partitions'} = compute_partitions( $self->{'_combinations'} );
  $self->{'_score'} = {}; # To store scores when they're available.
  return $self;
}

sub is_in {
  my $self = shift;
  my $combination = shift;
  return exists $self->{'_partitions'}{$combination};
}

sub add_combination {
  my $self = shift;
  my $new_combination = shift;
  return if $self->is_in( $new_combination );
  my $new_secret = new Algorithm::MasterMind::Secret $new_combination;
  for (my $i = 0; $i < @{$self->{'_combinations'}}; $i ++ ) {
    my $result = { blacks => 0,
		   whites => 0 };
    $self->{'_combinations'}[$i]->check_secret ( $new_secret, $result );
    $self->{'_partitions'}{$self->{'_combinations'}[$i]->{'_string'}}{result_as_string($result)}++;
    $self->{'_partitions'}{$new_combination}{result_as_string($result)}++;
  }
  push @{$self->{'_combinations'}}, $new_secret;
}

sub result_as_string {
  my $result = shift;
  return $result->{'blacks'}."b-".$result->{'whites'}."w";
}

sub partitions_for {
  my $self = shift;
  my $string = shift;
  return $self->{'_partitions'}->{$string};
}

sub cull_inconsistent_with {
  my $self = shift;
  my $string = shift;
  my $result = shift;

  my $secret = new Algorithm::MasterMind::Secret $string;
  my $result_string = result_as_string( $result );
  my @new_set;
  for my $s (@{$self->{'_combinations'}} ) {
    my $this_result = { blacks => 0,
			whites => 0 };
    $secret->check_secret( $s, $this_result);
#    print "Checking ", $s->string, " result " , result_as_string( $this_result), " with $result_string\n";
    if ( $result_string eq result_as_string($this_result) ) {
#      print "Added\n";
      push @new_set, $s;
    }
  }
  #Compute new partitions
  $self->{'_partitions'} = compute_partitions( \@new_set );
  $self->{'_combinations'} = \@new_set;
  $self->{'_score'} = {};
}

sub compute_most_score {
  my $self = shift;
  $self->{'_score'}->{'_most'} = {};
  for my $s (keys %{$self->{'_partitions'}} ) {
    $self->{'_score'}->{'_most'}->{$s} = keys %{$self->{'_partitions'}->{$s}};
  }
}

sub compute_entropy_score {
  my $self = shift;
  $self->{'_score'}->{'_entropy'} = {};
  for my $s (keys %{$self->{'_partitions'}} ) {
    my $sum;
    map( ($sum += $self->{'_partitions'}->{$s}->{$_}), keys %{$self->{'_partitions'}->{$s}} );
    my $entropy = 0;
    for my $k ( keys %{$self->{'_partitions'}->{$s}} ) {
      my $fraction = $self->{'_partitions'}->{$s}->{$k}/ $sum;
      $entropy -= $fraction * log( $fraction );
    }
    $self->{'_score'}->{'_entropy'}->{$s} = $entropy; 
  }
}
   
sub score_most {
  my $self = shift;
  my $str = shift;
  return $self->{'_score'}->{'_most'}->{ $str };
}

sub score_entropy {
  my $self = shift;
  my $str = shift;
  return $self->{'_score'}->{'_entropy'}->{ $str };
}

sub top_scorers {
  my $self = shift;
  my $score = "_".shift; # No checks
  my @keys = keys %{$self->{'_partitions'}};
  my @top_scorers;
  if ( $#keys > 1 ) {
    my $top_score = 0;
    for my $s ( @keys  ) {
      my $this_score = $self->{'_score'}->{$score}->{ $s } ;
      if ( $this_score > $top_score ) {
	$top_score = $this_score;
      }
    } 
    for my $s ( @keys  ) {
      if ( $self->{'_score'}{$score}->{ $s }  == $top_score ) {
	push @top_scorers, $s;
      }
    }
  } else { # either 0 or 1
    @top_scorers = @keys;
  } 
  return @top_scorers;
}

sub consistent_strings {
  return keys %{shift->{'_partitions'}};
}   

"As Jack the Ripper said..."; # Magic true value required at end of module

__END__

=head1 NAME

Algorithm::MasterMind::Consistent_Set - Class for handling the set of consistent combinations


=head1 SYNOPSIS

    use Algorithm::MasterMind::Consistent_Set;

  
=head1 DESCRIPTION

The consistent set in Mastermind contains the set of strings that
could possibly be a solution, that is, those that meet all partitions
made so far. 

=head1 INTERFACE 

=head2 new( @string_array )

Creates set and associated data structures

=head2 compute_partitions ( \@secrets ) 

Computes partitions for an array of secrets, returns a hashref to the
partition set.

=head2 is_in ( $string )

Checks whether the combination is in the consistent set already

=head2 add_combination ( $string )

Adds another combination checking it against previous combinations

=head2 result_to_string ( $result )

Converts result hash into string in a more or less standard way, to
avoid conversion errors

=head2 partitions_for ( $string )

Returns the partition hash for combination $string

=head2 cull_inconsistent_with ( $string, $result )

After a move, eliminates inconsistent elements, recomputing the partitions.

=head2 compute_entropy_score

Computes the entropy score of existent partitions

=head2 compute_most_score

Computes the Most Parts score of existent partitions, that is, the number of non-zero parts

=head2 consistent_strings

Returns the consistent set

=head2 create_consistent_with( $combinations, $rules )

Creates a consistent eliminating from the set of combinations those
not consistent with the rules

=head2 result_as_string

Returns the response as a fixed format string, for comparisons

=head2 score_entropy ($string)

Returns the Entropy score of the C<$string>, if it's in the consistent
set. 

=head2 score_most ($string)

Returns the Most Parts score of the C<$string>, if it's in the consistent
set. 

=head2 top_scorers ( $mode )

Returns the set of top scorers for a particular mode. Now $mode can be
"most" or "entropy"

=head1 AUTHOR



( run in 0.653 second using v1.01-cache-2.11-cpan-39bf76dae61 )