Algorithm-SetCovering

 view release on metacpan or  search on metacpan

SetCovering.pm  view on Meta::CPAN

            if($combo & $masks[$key_idx]) {
                # Key combo contains the current key. Iterate
                # over all locks and store in @covered if
                # the current key opens them.
                for(0..$self->{columns}-1) {
                    $covered[$_] ||= $self->{rows}->[$key_idx]->[$_];
                }
                push @keys, $key_idx;
            }
        }

        DEBUG "Combo '@keys' covers '@covered'";

            # Push hash ref and combo fields to 'combos'
            # array
        push @{$self->{combos}}, [\@covered, \@keys];
    }

    $self->{prepared} = 1;
}

##############################################
sub bitcount {
##############################################
# Count the number of '1' bits in a number
##############################################
    my($num) = @_;

    my $count = 0;

    while ($num) {
         $count += ($num & 0x1) ;
         $num >>= 1 ;
    }

    return $count ;
}

##############################################
sub greedy_run {
##############################################
    my($self, @columns_to_cover) = @_;

    my @hashed_rows    = ();
    my %column_hash    = ();
    my @result         = ();

    for(my $i=0; $i<@columns_to_cover; $i++) {
        $column_hash{$i} = 1 if $columns_to_cover[$i];
    }

    for my $row (@{$self->{rows}}) {
        my $rowhash = {};
        for(my $i=0; $i<@columns_to_cover; $i++) {
            $rowhash->{$i}++ if $columns_to_cover[$i] and $row->[$i];
        }
        push @hashed_rows, $rowhash;
        DEBUG("Hash of idx (", join('-', keys %$rowhash), ")");
    }

    my %not_covered = %column_hash;

    do {
            # Get the longest list
        my $max_len  = 0;
        my @max_keys = ();
        my $max_idx  = 0;
        for my $idx (0..$#hashed_rows) {
            my $row = $hashed_rows[$idx];
            my @keys = keys %$row;
            if(scalar @keys > $max_len) {
                @max_keys = @keys;
                $max_len  = scalar @keys;
                $max_idx  = $idx;
            }
        }

        # Return empty solution if rows can't cover columns_to_cover
        return () unless $max_len;
  
        DEBUG("Removing max_keys: @max_keys");

        delete $not_covered{$_} for @max_keys;
        push @result, $max_idx;

            # Remove max_keys columns from all keys
        foreach my $row (@hashed_rows) {
            delete $row->{$_} for @max_keys;
            DEBUG("Remain (", join('-', keys %$row), ")");
        }
 
        DEBUG("Not covered: (", join('-', keys %not_covered), ")");
        
    } while(scalar keys %not_covered);

    return @result;
}
    
1;

__END__

=head1 NAME

Algorithm::SetCovering - Algorithms to solve the "set covering problem"

=head1 SYNOPSIS

    use Algorithm::SetCovering;

    my $alg = Algorithm::SetCovering->new(
        columns => 4,
        mode    => "greedy");

    $alg->add_row(1, 0, 1, 0);
    $alg->add_row(1, 1, 0, 0);
    $alg->add_row(1, 1, 1, 0);
    $alg->add_row(0, 1, 0, 1);
    $alg->add_row(0, 0, 1, 1);

    my @to_be_opened = (@ARGV || (1, 1, 1, 1));
    
    my @set = $alg->min_row_set(@to_be_opened);
    
    print "To open (@to_be_opened), we need ",
          scalar @set, " keys:\n";

    for(@set) {
        print "$_: ", join('-', $alg->row($_)), "\n";
    }

=head1 DESCRIPTION

Consider having M keys and N locks. Every key opens one or more locks:

         | lock1 lock2 lock3 lock4
    -----+------------------------
    key1 |   x           x
    key2 |   x     x
    key3 |   x     x     x
    key4 |         x           x
    key5 |               x     x

Given an arbitrary set of locks you have to open (e.g. 2,3,4), 
the task is to find a minimal set of keys to accomplish this.
In the example above, the set [key4, key5] fulfils that condition.

The underlying problem is called "set covering problem" and
the corresponding decision problem is NP-complete.

=head2 Methods

=over 4



( run in 1.926 second using v1.01-cache-2.11-cpan-f889d44b568 )